Skip to content

Commit 61260b9

Browse files
committed
clean curvature tools and docs
1 parent b493d48 commit 61260b9

File tree

1 file changed

+60
-198
lines changed

1 file changed

+60
-198
lines changed

src/Core/curvaturetools.jl

Lines changed: 60 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,4 @@
1-
"""Short helpers and operations related to finite differences and curavture"""
2-
3-
# """
4-
# d2_dx2(y::AbstractVector{<:Real}; order::Integer=length(y))
5-
6-
# Approximates the 2nd derivative of a function using only given samples y of that function.
7-
8-
# Assumes y came from f(x) where x was an evenly sampled, unit intervel grid.
9-
# Note the approximation uses centered three point finite differences for the
10-
# next-to-end points, and foward/backward three point differences for the begining/end points
11-
# respectively. The remaining interior points use five point differences.
12-
13-
# Will use the largest order method possible by defult (currently 5 points), but can force
14-
# a specific order method with the keyword `order`.
15-
# See [`d_dx`](@ref).
16-
# """
17-
# function d2_dx2(y::AbstractVector{<:Real}; order::Integer=length(y))
18-
# n = length(y)
19-
# if n < 3
20-
# throw(ArgumentError("input $y must have at least length 3"))
21-
# elseif order <= 2
22-
# throw(ArgumentError("order $order must be at least 3"))
23-
# end
24-
25-
# if order == 3
26-
# return _d2_dx2_3(y)
27-
# elseif order == 4
28-
# return _d2_dx2_4(y)
29-
# elseif order >= 5
30-
# return _d2_dx2_5(y)
31-
# else
32-
# throw(ErrorException("Something went wrong with the order $order."))
33-
# end
34-
# end
35-
# # TODO is there a package that does this? The ones I've seen require the forward function.
36-
37-
# function _d2_dx2_3(y::AbstractVector{<:Real})
38-
# d = similar(y)
39-
# for i in eachindex(y)[begin+1:end-1]
40-
# d[i] = y[i-1] - 2*y[i] + y[i+1]
41-
# end
42-
# # Assume the same curvature at the end points
43-
# d[begin] = d[begin+1]
44-
# d[end] = d[end-1]
45-
# return d
46-
# end
47-
48-
# function _d2_dx2_4(y::AbstractVector{<:Real})
49-
# d = similar(y)
50-
# each_i = eachindex(y)
51-
52-
# for i in each_i[begin+1:end-1]
53-
# d[i] = y[i-1] - 2*y[i] + y[i+1] # Note this is the same estimate as _d2_dx2_3
54-
# end
55-
56-
# # Four point forward/backwards estimate at end points
57-
# i = each_i[begin]
58-
# d[i] = (6*y[i] - 15*y[i+1] + 12*y[i+2] - 3*y[i+3])/3
59-
60-
# i = each_i[end]
61-
# d[i] = (-3*y[i-3] + 12*y[i-2] -15*y[i-1] +6*y[i])/3
62-
# return d
63-
# end
64-
65-
# function _d2_dx2_5(y::AbstractVector{<:Real})
66-
# d = similar(y)
67-
# each_i = eachindex(y)
68-
69-
# # Interior Ppints
70-
# for i in each_i[begin+2:end-2]
71-
# d[i] = (-y[i-2] + 16*y[i-1] - 30*y[i] + 16*y[i+1] - y[i+2])/12
72-
# end
73-
74-
# # Boundary and next-to boundary points
75-
# i = each_i[begin+2]
76-
# d[i-2] = (35*y[i-2] - 104*y[i-1] + 114*y[i] - 56*y[i+1] + 11*y[i+2])/12
77-
# d[i-1] = (11*y[i-2] - 20*y[i-1] + 6*y[i] + 4*y[i+1] - y[i+2])/12
78-
79-
# i = each_i[end-2]
80-
# d[i+1] = (-y[i-2] + 4*y[i-1] + 6*y[i] - 20*y[i+1] + 11*y[i+2])/12
81-
# d[i+2] = (11*y[i-2] - 56*y[i-1] + 114*y[i] - 104*y[i+1] + 35*y[i+2])/12
82-
83-
# return d
84-
# end
85-
86-
# """
87-
# d_dx(y::AbstractVector{<:Real})
88-
89-
# Approximates the 1nd derivative of a function using only given samples y of that function.
90-
91-
# Assumes y came from f(x) where x was an evenly sampled, unit intervel grid.
92-
# Note the approximation uses centered three point finite differences for the
93-
# next-to-end points, and foward/backward three point differences for the begining/end points
94-
# respectively. The remaining interior points use five point differences.
95-
96-
# Will use the largest order method possible by defult (currently 5 points), but can force
97-
# a specific order method with the keyword `order`.
98-
# See [`d2_dx2`](@ref).
99-
# """
100-
# function d_dx(y::AbstractVector{<:Real}; order::Integer=length(y))
101-
# n = length(y)
102-
# if n < 3
103-
# throw(ArgumentError("input $y must have at least length 3"))
104-
# elseif order <= 2
105-
# throw(ArgumentError("order $order must be at least 3"))
106-
# end
107-
108-
# if order == 3
109-
# return _d_dx_3(y)
110-
# elseif order == 4
111-
# return _d_dx_4(y)
112-
# elseif order >= 5
113-
# return _d_dx_5(y)
114-
# else
115-
# throw(ErrorException("Something went wrong with the order $order."))
116-
# end
117-
# end
118-
119-
# function _d_dx_3(y::AbstractVector{<:Real})
120-
# d = similar(y)
121-
# each_i = eachindex(y)
122-
123-
# for i in each_i[begin+1:end-1]
124-
# d[i] = (-y[i-1] + y[i+1])/2
125-
# end
126-
127-
# i = each_i[begin+1]
128-
# d[begin] = (-3*y[i-1] + 4*y[i] - y[i+1])/2
129-
130-
# i = each_i[end-1]
131-
# d[end] = (y[i-1] - 4*y[i] + 3*y[i+1])/2
132-
# return d
133-
# end
134-
135-
# function _d_dx_4(y::AbstractVector{<:Real})
136-
# d = similar(y)
137-
# each_i = eachindex(y)
138-
139-
# for i in each_i[begin+1:end-2]
140-
# d[i] = (-2*y[i-1] - 3*y[i] + 6*y[i+1] - y[i+2])/6 # four points is odd so using a half forward estimate
141-
# end
142-
143-
# i = each_i[begin]
144-
# d[i] = (-11*y[i] + 18*y[i+1] - 9*y[i+2] + 2*y[i+3])/6
145-
146-
# i = each_i[end]
147-
# d[i-1] = (y[i-3] - 6*y[i-2] + 3*y[i-1] + 2*y[i])/6
148-
# d[i] = (-2*y[i-3] + 9*y[i-2] - 18*y[i-1] + 11*y[i])/6
149-
150-
# return d
151-
# end
152-
153-
# function _d_dx_5(y::AbstractVector{<:Real})
154-
# d = similar(y)
155-
# each_i = eachindex(y)
156-
157-
# # Interior Ppints
158-
# for i in each_i[begin+2:end-2]
159-
# d[i] = (2*y[i-2] - 16*y[i-1] + 16*y[i+1] - 2*y[i+2])/24
160-
# end
161-
162-
# # Boundary and next-to boundary points
163-
# i = each_i[begin+2]
164-
# d[i-2] = (-50*y[i-2] + 96*y[i-1] - 72*y[i] + 32*y[i+1] - 6*y[i+2])/24
165-
# d[i-1] = (-6*y[i-2] - 20*y[i-1] + 36*y[i] - 12*y[i+1] + 2*y[i+2])/24
166-
167-
# i = each_i[end-2]
168-
# d[i+1] = (-2*y[i-2] + 12*y[i-1] - 36*y[i] + 20*y[i+1] + 6*y[i+2])/24
169-
# d[i+2] = (6*y[i-2] - 32*y[i-1] + 72*y[i] - 96*y[i+1] + 50*y[i+2])/24
170-
171-
# return d
172-
# end
1+
"""Short helpers and operations related to finite differences and curvature"""
1732

1743
"""
1754
d_dx(y::AbstractVector{<:Real})
@@ -294,7 +123,11 @@ function make_spline(y::AbstractVector{<:Real}; h=1)
294123
return f
295124
end
296125

297-
"""Extracts the first and second derivatives of the splines at the knots"""
126+
"""
127+
d_dx_and_d2_dx2_spline(y::AbstractVector{<:Real}; h=1)
128+
129+
Extracts the first and second derivatives of the splines from y at the knots
130+
"""
298131
function d_dx_and_d2_dx2_spline(y::AbstractVector{<:Real}; h=1)
299132
_, b, c, _ = cubic_spline_coefficients(y::AbstractVector{<:Real}; h)
300133
dy_dx = c
@@ -319,7 +152,7 @@ function curvature(y::AbstractVector{<:Real}; method=:finite_differences, kwargs
319152
dy_dx, dy2_dx2 = d_dx_and_d2_dx2_spline(y; h=1)
320153
return @. dy2_dx2 / (1 + dy_dx^2)^1.5
321154
elseif method == :circles
322-
return circumscribed_standard_curvature(y)
155+
return circle_curvature(y; h=1)
323156
else
324157
throw(ArgumentError("method $method not implemented"))
325158
end
@@ -330,53 +163,75 @@ end
330163
331164
Approximates the signed curvature of a function, scaled to the unit box ``[0,1]^2``.
332165
166+
Assumes the function is 1 at 0 and (after x dimension is scaled) 0 at 1.
167+
333168
See [`curvature`](@ref).
334169
"""
335170
function standard_curvature(y::AbstractVector{<:Real}; method=:finite_differences, kwargs...)
336-
Δx = 1 / (length(y) - 1) # An interval 0:10 has length(0:10) = 11, but measure 10-0 = 10
171+
Δx = 1/length(y)
172+
# An interval 0:10 has length(0:10) = 11, but measure 10-0 = 10 so we may think to use 1/(length(y) - 1), but we need to consider the left end point of y is at 1/length(y) and not zero.
337173
if method == :finite_differences
174+
y = [1; y; 0]
338175
y_max = maximum(y)
339176
dy_dx = d_dx(y; kwargs...) / (Δx * y_max)
340177
dy2_dx2 = d2_dx2(y; kwargs...) / (Δx^2 * y_max)
341-
return @. dy2_dx2 / (1 + dy_dx^2)^1.5
178+
curvature = @. dy2_dx2 / (1 + dy_dx^2)^1.5
179+
return curvature[begin+1:end-1]
342180
elseif method == :splines
343181
# y_max = 1
344182
dy_dx, dy2_dx2 = d_dx_and_d2_dx2_spline(y; h=Δx)
345183
return @. dy2_dx2 / (1 + dy_dx^2)^1.5
346184
elseif method == :circles
347-
return circumscribed_standard_curvature(y)
185+
return circle_curvature(y / max(1,maximum(y)); h=Δx)
348186
else
349187
throw(ArgumentError("method $method not implemented"))
350188
end
351189
end
352190

353-
# """
354-
# Finds the radius of the circumscribed circle between points (a,f), (b,g), (c,h)
355-
# """
356-
# function circumscribed_radius((a,f),(b,g),(c,h))
357-
# d = 2*(a*(g-h)+b*(h-f)+c*(f-g))
358-
# p = ((a^2+f^2)*(g-h)+(b^2+g^2)*(h-f)+(c^2+h^2)*(f-g)) / d
359-
# q = ((a^2+f^2)*(b-c)+(b^2+g^2)*(c-a)+(c^2+h^2)*(a-b)) / d
360-
# r = sqrt((a-p)^2+(f-q)^2)
361-
# return r
362-
# end
363-
364-
function circumscribed_standard_curvature(y)
365-
n = length(y)
366-
ymax = maximum(y)
367-
y = y / ymax
191+
"""
192+
circle_curvature(y::AbstractVector{<:Real}; h=1, estimate_endpoints=true)
193+
194+
Inverse radius of a the circle passing through each 3 adjacent points on `y`,
195+
`(0,y[i-1])`, `(h,y[i])`, and `(2h,y[i+1])`.
196+
197+
If `estimate_endpoints=true`, assumes the function that y comes from is 1 to the left of the given values, and 0 to the right. This is typical of relative error decay as a function of rank.
198+
If `false`, pads the boundary with the adjacent curvature.
199+
200+
See [`three_point_circle`](@ref).
201+
"""
202+
function circle_curvature(y::AbstractVector{<:Real}; h=1, estimate_endpoints=true)
368203
k = zero(y)
369-
a, b, c = 0, 1/n, 2/n
370-
for i in eachindex(k)[2:end-1]
204+
a, b, c = 0, h, 2h
205+
eachindex_k = eachindex(k)
206+
for i in eachindex_k[2:end-1]
371207
k[i] = signed_circle_curvature((a,y[i-1]),(b,y[i]),(c,y[i+1]))
372-
#k[i] = 1 / circumscribed_radius((a,y[i-1]),(b,y[i]),(c,y[i+1]))
373208
end
374-
k[1] = k[2]
375-
k[end] = k[end-1]
209+
210+
if estimate_endpoints
211+
i = eachindex_k[1]
212+
k[i] = signed_circle_curvature((a, 1),(b,y[i]),(c,y[i+1]))
213+
i = eachindex_k[end]
214+
k[i] = signed_circle_curvature((a,y[i-1]),(b,y[i]),(c, 0))
215+
else
216+
k[1] = k[2]
217+
k[end] = k[end-1]
218+
end
219+
376220
return k
377221
end
378222

379-
"""radius r and center point (p,q) of the circle passing through the three points"""
223+
"""
224+
three_point_circle((a,f),(b,g),(c,h))
225+
226+
Calculates radius `r` and center point `(p, q)` of the circle passing through the three points
227+
in the xy-plane.
228+
229+
# Example
230+
```
231+
r, (p, q) = three_point_circle((1,2), (2,1), (5,2))
232+
r, (p, q) == (√5, (3, 3))
233+
````
234+
"""
380235
function three_point_circle((a,f),(b,g),(c,h))
381236
fg = f-g
382237
gh = g-h
@@ -396,6 +251,13 @@ function three_point_circle((a,f),(b,g),(c,h))
396251
return r, (p, q)
397252
end
398253

254+
"""
255+
signed_circle_curvature((a,f),(b,g),(c,h))
256+
257+
Signed inverse radius of the circle passing through the 3 points in the xy-plane.
258+
259+
See [`three_point_circle`](@ref).
260+
"""
399261
function signed_circle_curvature((a,f),(b,g),(c,h))
400262
@assert a < b < c
401263
r, _ = three_point_circle((a,f),(b,g),(c,h))

0 commit comments

Comments
 (0)