ClampedCubicSplineTest.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. namespace MathPHP\Tests\NumericalAnalysis\Interpolation;
  3. use MathPHP\NumericalAnalysis\Interpolation\ClampedCubicSpline;
  4. use MathPHP\Expression\Polynomial;
  5. use MathPHP\Exception;
  6. class ClampedCubicSplineTest extends \PHPUnit\Framework\TestCase
  7. {
  8. /**
  9. * @test Interpolated piecewise function computes expected values: p(x) = expected
  10. * @dataProvider dataProviderForPolynomialAgrees
  11. * @param int $x
  12. * @param int $expected
  13. * @throws \Exception
  14. */
  15. public function testPolynomialAgrees(int $x, int $expected)
  16. {
  17. // Given
  18. $points = [[0, 0, 1], [1, 5, -2], [3, 2, 0], [7, 10, 3], [10, -4, 3]];
  19. // And
  20. $p = ClampedCubicSpline::interpolate($points);
  21. // When
  22. $evaluated = $p($x);
  23. // Then
  24. $this->assertEqualsWithDelta($expected, $evaluated, 0.00001);
  25. }
  26. /**
  27. * @return array (x, expected)
  28. */
  29. public function dataProviderForPolynomialAgrees(): array
  30. {
  31. return [
  32. [0, 0], // p(0) = 0
  33. [1, 5], // p(1) = 5
  34. [3, 2], // p(3) = 2
  35. [7, 10], // p(7) = 10
  36. [10, -4], // p(10) = -4
  37. ];
  38. }
  39. /**
  40. * @test Solve zero error
  41. * @dataProvider dataProviderForSolve
  42. * @param float $x
  43. * @throws \Exception
  44. *
  45. * f(x) = 8x³ -13x² -92x + 96
  46. *
  47. * The error in the Cubic Spline Interpolating Polynomial is proportional
  48. * to the max value of the 4th derivative. Thus, if our input Function
  49. * is a 3rd-degree polynomial, the fourth derivative will be zero, and
  50. * thus we will have zero error.
  51. *
  52. * p(x) agrees with f(x) at x = $_
  53. */
  54. public function testSolveZeroError($x)
  55. {
  56. // Given f(x) = 8x³ -13x² -92x + 96
  57. $f = new Polynomial([8, -13, -92, 96]);
  58. $f’ = $f->differentiate();
  59. // And
  60. $a = 0;
  61. $b = 10;
  62. $n = 50;
  63. $tol = 0;
  64. $roundoff = 0.0001; // round off error
  65. // And
  66. $p = ClampedCubicSpline::interpolate($f, $f’, $a, $b, $n);
  67. $expected = $f($x);
  68. // When
  69. $evaluated = $p($x);
  70. // Then
  71. $this->assertEqualsWithDelta($expected, $evaluated, $tol + $roundoff);
  72. }
  73. /**
  74. * @test Solve non-zero error
  75. * @dataProvider dataProviderForSolve
  76. * @param float $x
  77. * @throws \Exception
  78. *
  79. * f(x) = x⁴ + 8x³ -13x² -92x + 96
  80. *
  81. * The error is bounded by:
  82. * |f(x)-p(x)| = tol <= (5/384) * h⁴ * max f⁽⁴⁾(x)
  83. * where h = max hᵢ
  84. * and max f⁽⁴⁾(x) = f⁽⁴⁾(x) for all x given a 4th-degree polynomial f(x)
  85. *
  86. * So, tol <= (1/24) * (1/5)⁴ * 24 = (1/5)⁴
  87. *
  88. * p(x) agrees with f(x) at x = $_
  89. */
  90. public function testSolveNonZeroError($x)
  91. {
  92. // Given f(x) = x⁴ + 8x³ -13x² -92x + 96
  93. $f = new Polynomial([1, 8, -13, -92, 96]);
  94. $f’ = $f->differentiate();
  95. $f⁽⁴⁾ = $f’->differentiate()->differentiate()->differentiate();
  96. // And
  97. $a = 0;
  98. $b = 10;
  99. $n = 51;
  100. // And
  101. $h = ($b - $a) / ($n - 1);
  102. $tol = (5 / 384) * ($h ** 4) * $f⁽⁴⁾(0);
  103. $roundoff = 0.000001; // round off error
  104. // And
  105. $p = ClampedCubicSpline::interpolate($f, $f’, $a, $b, $n);
  106. $expected = $f($x);
  107. // When
  108. $evaluated = $p($x);
  109. // Then
  110. $this->assertEqualsWithDelta($expected, $evaluated, $tol + $roundoff);
  111. }
  112. /**
  113. * @return array p(x) agrees with f(x) at x = $_
  114. */
  115. public function dataProviderForSolve(): array
  116. {
  117. return [
  118. [0],
  119. [2],
  120. [4],
  121. [6],
  122. [8],
  123. [10],
  124. [7.32], // not a node
  125. ];
  126. }
  127. /**
  128. * @test Incorrect input - The input $source is neither a callback or a set of arrays
  129. * @throws \Exception
  130. */
  131. public function testIncorrectInput()
  132. {
  133. // Given
  134. $x = 10;
  135. $incorrectFunction = $x ** 2 + 2 * $x + 1;
  136. // Then
  137. $this->expectException(Exception\BadDataException::class);
  138. // When
  139. ClampedCubicSpline::getSplinePoints($incorrectFunction, [0,4,5]);
  140. }
  141. /**
  142. * @test Not coordinates - array doesn't have precisely three numbers (coordinates)
  143. * @throws \Exception
  144. */
  145. public function testNotCoordinatesException()
  146. {
  147. // Given
  148. $points = [[0,0,1], [1,2,3], [2,2]];
  149. // Then
  150. $this->expectException(Exception\BadDataException::class);
  151. // When
  152. ClampedCubicSpline::validateSpline($points);
  153. }
  154. /**
  155. * @test Not enough arrays - There are not enough arrays in the input
  156. * @throws \Exception
  157. */
  158. public function testNotEnoughArraysException()
  159. {
  160. // Given
  161. $points = [[0,0,1]];
  162. // Then
  163. $this->expectException(Exception\BadDataException::class);
  164. // When
  165. ClampedCubicSpline::validateSpline([[0,0,1]]);
  166. }
  167. /**
  168. * @test Not a function - Two arrays share the same first number (x-component)
  169. * @throws \Exception
  170. */
  171. public function testNotAFunctionException()
  172. {
  173. // Given
  174. $points = [[0,0,1], [0,5,0], [1,1,3]];
  175. // Then
  176. $this->expectException(Exception\BadDataException::class);
  177. // When
  178. ClampedCubicSpline::validateSpline($points);
  179. }
  180. }