@@ -142,6 +142,166 @@ let BezierInterpolationTests =
142142
143143
144144
145+ [<Tests>]
146+ let integrationTests =
147+ testList " Interpolation.integrate" [
148+
149+ testCase " LinearSpline.getIntegralBetween positive linear function" <| fun () ->
150+ // y = 2x at {0,2,4} => integral [0,4] = [x^2]_0^4 = 16
151+ let coefs = LinearSpline.interpolate [| 0. ; 2. ; 4. |] [| 0. ; 4. ; 8. |]
152+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 0. 4. ) 16.0
153+ " integral of y=2x from 0 to 4 should be 16"
154+ // [1,3]: segments [0,2] and [2,4], so partial cross-segment
155+ // ∫[1,3] 2x dx = [x^2]_1^3 = 9 - 1 = 8
156+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 1. 3. ) 8.0
157+ " integral of y=2x from 1 to 3 should be 8"
158+ // ∫[0.5,1.5] 2x dx = 1.5^2 - 0.5^2 = 2.25 - 0.25 = 2
159+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 0.5 1.5 ) 2.0
160+ " integral of y=2x from 0.5 to 1.5 should be 2"
161+
162+ testCase " LinearSpline.getIntegralBetween reversed limits" <| fun () ->
163+ let coefs = LinearSpline.interpolate [| 0. ; 2. ; 4. |] [| 0. ; 4. ; 8. |]
164+ let forward = LinearSpline.getIntegralBetween coefs 1. 3.
165+ let reverse = LinearSpline.getIntegralBetween coefs 3. 1.
166+
167+ Expect.floatClose Accuracy.high forward (- reverse)
168+ " integrating with reversed limits should negate the result"
169+
170+ testCase " LinearSpline.getIntegralBetween is additive" <| fun () ->
171+ let coefs = LinearSpline.interpolate [| 0. ; 2. ; 4. |] [| 0. ; 4. ; 8. |]
172+ let whole = LinearSpline.getIntegralBetween coefs 1. 3.
173+ let partials = LinearSpline.getIntegralBetween coefs 1. 2. + LinearSpline.getIntegralBetween coefs 2. 3.
174+ Expect.floatClose Accuracy.high whole partials
175+ " integral should be additive across knot boundaries"
176+
177+ testCase " LinearSpline.getIntegralBetween returns zero for equal bounds" <| fun () ->
178+ let coefs = LinearSpline.interpolate [| 0. ; 1. ; 2. |] [| 1. ; 2. ; 3. |]
179+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 1. 1. ) 0.0
180+ " integral with equal bounds should be 0"
181+
182+ testCase " LinearSpline.getIntegralBetween negative linear function" <| fun () ->
183+ let coefs = LinearSpline.interpolate [| 0. ; 1. |] [|- 1. ; 0. |]
184+ // ∫[0,1] (x-1) dx = -1/2
185+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 0. 1. ) - 0.5
186+ " integral of negative linear function should be negative"
187+
188+ testCase " LinearSpline.getIntegralBetween across zero crossing" <| fun () ->
189+ let coefs = LinearSpline.interpolate [| 0. ; 1. ; 2. |] [|- 1. ; 0. ; 1. |]
190+ // ∫[0,2] (x-1) dx = 0
191+ Expect.floatClose Accuracy.high ( LinearSpline.getIntegralBetween coefs 0. 2. ) 0.0
192+ " equivalent positive and negative areas should cancel"
193+
194+ testCase " Step.getIntegralBetween constant positive segments" <| fun () ->
195+ // y = 2 on [0,1), y = 3 on [1,2), y = 4 on [2,3)
196+ let coefs = Step.interpolate [| 0. ; 1. ; 2. ; 3. |] [| 2. ; 3. ; 4. ; 5. |]
197+ // ∫[0,3] = 2*1 + 3*1 + 4*1 = 9
198+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 0. 3. ) 9.0
199+ " integral of step function from 0 to 3 should be 9"
200+ // ∫[0.5,2.5] = 2*0.5 + 3*1 + 4*0.5 = 1 + 3 + 2 = 6
201+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 0.5 2.5 ) 6.0
202+ " partial integral of step function from 0.5 to 2.5 should be 6"
203+ // ∫[1,2] = 3 * 1 = 3
204+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 1. 2. ) 3.0
205+ " integral within exact segment boundaries should be 3"
206+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 1.5 1.5 ) 0.0
207+ " integral over zero-width interval should be zero"
208+ // Uses first interval value for values below x = 0
209+ // ∫[-1,0] = 2 * 1 = 2
210+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs - 1 0 ) 2.0
211+ " integral before the first breakpoint should use first segment value"
212+
213+ testCase " Step.getIntegralBetween is additive" <| fun () ->
214+ let coefs = Step.interpolate [| 0. ; 1. ; 2. ; 3. |] [| 2. ; 3. ; 4. ; 5. |]
215+ let whole = Step.getIntegralBetween coefs 0.5 2.5
216+ let partials = ( Step.getIntegralBetween coefs 0.5 1.5 ) + ( Step.getIntegralBetween coefs 1.5 2.5 )
217+ Expect.floatClose Accuracy.high whole partials
218+ " integral should be additive acreoss sub-intervals"
219+
220+ testCase " Step.getIntegralBetween reversed limits" <| fun () ->
221+ let coefs = Step.interpolate [| 0. ; 1. ; 2. ; 3. |] [| 2. ; 3. ; 4. ; 5. |]
222+
223+ let forward = Step.getIntegralBetween coefs 0.5 2.5
224+ let reverse = Step.getIntegralBetween coefs 2.5 0.5
225+
226+ Expect.floatClose Accuracy.high reverse (- forward)
227+ " integrating with reversed limits should negate the result"
228+
229+ testCase " Step.getIntegralBetween constant negative segments" <| fun () ->
230+ // y = -2 on [0,1), y = -3 on [1,2)
231+ let coefs = Step.interpolate [| 0. ; 1. ; 2. |] [|- 2. ;- 3. ;- 3. |]
232+ //∫[0,2] = (-2)*1 + (-3)*1 = -5
233+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 0. 2. ) - 5.0
234+ " Integral of negative step function should be negative"
235+
236+ testCase " Step.getIntegralBetween across zero crossing" <| fun () ->
237+ // y= -2 on [0,1), y = 2 on [1,2)
238+ let coefs = Step.interpolate [| 0. ; 1. ; 2. |] [|- 2. ; 2. ; 2. |]
239+ // ∫[0,2] = (-2)*1 + 2*1 = 0
240+ Expect.floatClose Accuracy.high ( Step.getIntegralBetween coefs 0. 2. ) 0.0
241+ " equivalent positive and negative areas should cancel"
242+
243+ testCase " CubicSpline.getIntegralBetween positive quadratic function" <| fun () ->
244+ // Quadratic boundary condition reproduces y=x^2 exactly
245+ let t = vector [| 1. ; 2. ; 3. ; 4. |]
246+ let u = vector [| 1. ; 4. ; 9. ; 16. |] // y = x^2
247+ let coefs = CubicSpline.interpolate CubicSpline.Quadratic t u
248+ // ∫[1,4] x^2 dx = [x^3/3]_1^4 = 64/3 - 1/3 = 21
249+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs 1. 4. ) 21.0
250+ " integral of y=x^2 from 1 to 4 should be 21"
251+ // ∫[1,2] x^2 dx = 8/3 - 1/3 = 7/3
252+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs 1. 2. ) ( 7. / 3. )
253+ " integral of y=x^2 from 1 to 2 should be 7/3"
254+ // ∫[1.5,3.5] x² dx = (3.5³ - 1.5³)/3
255+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs 1.5 3.5 ) (( 3.5 ** 3. - 1.5 ** 3. ) / 3. )
256+ " integral across several spline intervals should be correct"
257+
258+ testCase " CubicSpline.getIntegralBetween is additive" <| fun () ->
259+ let t = vector [| 1. ; 2. ; 3. ; 4. |]
260+ let u = vector [| 1. ; 4. ; 9. ; 16. |] // y = x^2
261+ let coefs = CubicSpline.interpolate CubicSpline.Quadratic t u
262+ let whole = CubicSpline.getIntegralBetween coefs 1. 4.
263+ let partials = CubicSpline.getIntegralBetween coefs 1. 2 + CubicSpline.getIntegralBetween coefs 2. 3. + CubicSpline.getIntegralBetween coefs 3. 4.
264+ Expect.floatClose Accuracy.high whole partials
265+ " integral should be additive across sub-intervals"
266+
267+ testCase " CubicSpline.getIntegralBetween returns zero for equal bounds" <| fun () ->
268+ let t = vector [| 0. ; 1. ; 2. |]
269+ let u = vector [| 0. ; 1. ; 4. |]
270+ let coefs = CubicSpline.interpolate CubicSpline.Natural t u
271+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs 1. 1. ) 0.0
272+ " integral with equal bounds should be 0"
273+
274+ testCase " CubicSpline.getIntegralBetween reversed limits" <| fun () ->
275+ let t = vector [| 1. ; 2. ; 3. ; 4. |]
276+ let u = vector [| 1. ; 4. ; 9. ; 16. |]
277+ let coefs = CubicSpline.interpolate CubicSpline.Quadratic t u
278+
279+ let forward = CubicSpline.getIntegralBetween coefs 1. 4.
280+ let reverse = CubicSpline.getIntegralBetween coefs 4. 1.
281+
282+ Expect.floatClose Accuracy.high forward (- reverse)
283+ " integrating with reversed limits should negate the result"
284+
285+ testCase " CubicSpline.getIntegralBetween negative values" <| fun () ->
286+ let t = vector [| 1. ; 2. ; 3. ; 4. |]
287+ let u = vector [|- 1. ;- 4. ;- 9. ;- 16. |]
288+ let coefs = CubicSpline.interpolate CubicSpline.Quadratic t u
289+ // ∫[1,4] -x² dx = -21
290+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs 1. 4. ) - 21.0
291+ " integral of negative quadratic should be negative"
292+
293+ testCase " CubicSpline.getIntegralBetween positive and negative values" <| fun () ->
294+ let t = vector [|- 2. ;- 1. ; 0. ; 1. ; 2. |]
295+ let u = vector [|- 2. ;- 1. ; 0. ; 1. ; 2. |]
296+ let coefs = CubicSpline.interpolate CubicSpline.Quadratic t u
297+ // ∫[-2,2] x dx = 0
298+ Expect.floatClose Accuracy.high ( CubicSpline.getIntegralBetween coefs - 2. 2. ) 0.0
299+ " positive and negative contributions should cancel"
300+ ]
301+
302+
303+
304+
145305
146306
147307
0 commit comments