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
295124end
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+ """
298131function 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
331164Approximates 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+
333168See [`curvature`](@ref).
334169"""
335170function 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
351189end
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, 2 h
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
377221end
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+ """
380235function 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)
397252end
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+ """
399261function 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