AlgebraTest.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. <?php
  2. namespace MathPHP\Tests\Algebra;
  3. use MathPHP\Algebra;
  4. use MathPHP\Number;
  5. class AlgebraTest extends \PHPUnit\Framework\TestCase
  6. {
  7. /**
  8. * @test gcd returns the greatest common divisor of two integers.
  9. * @dataProvider dataProviderForGcd
  10. * @param int $a
  11. * @param int $b
  12. * @param int $expected_gcd
  13. * @param int $_
  14. * @param int $__
  15. */
  16. public function testGCD(int $a, int $b, int $expected_gcd, int $_, int $__)
  17. {
  18. // When
  19. $gcd = Algebra::gcd($a, $b);
  20. // Then
  21. $this->assertEquals($expected_gcd, $gcd);
  22. }
  23. /**
  24. * @test extendedGCD returns the extended greatest common divisor of two integers.
  25. * @dataProvider dataProviderForGcd
  26. * @param int $a
  27. * @param int $b
  28. * @param int $expected_gcd
  29. * @param int $expected_alpha
  30. * @param int $expected_beta
  31. */
  32. public function testExtendedGcd(int $a, int $b, int $expected_gcd, int $expected_alpha, int $expected_beta)
  33. {
  34. // When
  35. [$gcd, $alpha, $beta] = Algebra::extendedGcd($a, $b);
  36. // Then
  37. $this->assertEquals($expected_gcd, $gcd);
  38. $this->assertEquals($expected_alpha, $alpha);
  39. $this->assertEquals($expected_beta, $beta);
  40. }
  41. public function dataProviderForGcd(): array
  42. {
  43. return [
  44. [0, 0, 0, 0, 1],
  45. [8, 0, 8, 1, 0],
  46. [0, 8, 8, 0, 1],
  47. [8, 12, 4, -1, 1],
  48. [12, 8, 4, 1, -1],
  49. [54, 24, 6, 1, -2],
  50. [24, 54, 6, -2, 1],
  51. [18, 84, 6, 5, -1],
  52. [84, 18, 6, -1, 5],
  53. [244, 343, 1, 97, -69],
  54. [343, 244, 1, -69, 97],
  55. [97, 577, 1, 232, -39],
  56. [577, 97, 1, -39, 232],
  57. [40902, 24140, 34, 337, -571],
  58. [24140, 40902, 34, -571, 337],
  59. [1234, 54, 2, -7, 160],
  60. [54, 1234, 2, 160, -7],
  61. ];
  62. }
  63. /**
  64. * @test lcm returns the least-common multiple of two integers.
  65. * @dataProvider dataProviderForLcm
  66. * @param int $a
  67. * @param int $b
  68. * @param int $expected_lcm
  69. */
  70. public function testLCM(int $a, int $b, int $expected_lcm)
  71. {
  72. // When
  73. $lcm = Algebra::lcm($a, $b);
  74. // Then
  75. $this->assertEquals($expected_lcm, $lcm);
  76. }
  77. /**
  78. * @return array [a, b, lcm]
  79. */
  80. public function dataProviderForLcm(): array
  81. {
  82. return [
  83. [0, 0, 0],
  84. [8, 0, 0],
  85. [0, 8, 0],
  86. [5, 2, 10],
  87. [2, 5, 10],
  88. [4, 6, 12],
  89. [6, 4, 12],
  90. [21, 6, 42],
  91. [6, 21, 42],
  92. [598, 352, 105248],
  93. [352, 598, 105248],
  94. ];
  95. }
  96. /**
  97. * @test factors returns the expected factors of an integer.
  98. * @dataProvider dataProviderForFactors
  99. * @param int $x
  100. * @param array $expected_factors
  101. */
  102. public function testFactors(int $x, array $expected_factors)
  103. {
  104. // When
  105. $factors = Algebra::factors($x);
  106. // Then
  107. $this->assertEquals($expected_factors, $factors);
  108. }
  109. /**
  110. * @return array [x, factors]
  111. */
  112. public function dataProviderForFactors(): array
  113. {
  114. return [
  115. [ 0, [\INF] ],
  116. [ 1, [1] ],
  117. [ 4, [1, 2, 4] ],
  118. [ 12, [1, 2, 3, 4, 6, 12] ],
  119. [ 14, [1, 2, 7, 14] ],
  120. [ 30, [1, 2, 3, 5, 6, 10, 15, 30] ],
  121. [ 2248, [1, 2, 4, 8, 281, 562, 1124, 2248] ],
  122. [ 983928, [1, 2, 3, 4, 6, 8, 11, 12, 22, 24, 33, 44, 66, 88, 132, 264, 3727, 7454, 11181, 14908, 22362, 29816, 40997, 44724, 81994, 89448, 122991, 163988, 245982, 327976, 491964, 983928] ],
  123. [ 9938938492, [1, 2, 4, 7, 14, 28, 79, 158, 283, 316, 553, 566, 1106, 1132, 1981, 2212, 3962, 7924, 15877, 22357, 31754, 44714, 63508, 89428, 111139, 156499, 222278, 312998, 444556, 625996, 1254283, 2508566, 4493191, 5017132, 8779981, 8986382, 17559962, 17972764, 31452337, 35119924, 62904674, 125809348, 354962089, 709924178, 1419848356, 2484734623, 4969469246, 9938938492] ],
  124. [ 9938938492873, [ 1, 13, 22637, 294281, 33773633, 439057229, 764533730221, 9938938492873]],
  125. ];
  126. }
  127. /**
  128. * @test linear returns the expected root
  129. * @dataProvider dataProviderForLinear
  130. * @param float $a
  131. * @param float $b
  132. * @param float $expected
  133. */
  134. public function testLinear(float $a, float $b, float $expected)
  135. {
  136. // When
  137. $root = Algebra::linear($a, $b);
  138. // Then
  139. $this->assertEqualsWithDelta($expected, $root, 0.00001);
  140. }
  141. /**
  142. * Test data created with Python numpy.roots([a, b])
  143. * @return array (a, b, root)
  144. */
  145. public function dataProviderForLinear(): array
  146. {
  147. return [
  148. [-1, 0, 0],
  149. [1, 0, 0],
  150. [5, 0, 0],
  151. [1, 1, -1],
  152. [-1, 1, 1],
  153. [1, -1, 1],
  154. [-1, -1, -1],
  155. [1, 2, -2],
  156. [-1, 2, 2],
  157. [1, -2, 2],
  158. [-1, -2, -2],
  159. [2, 4, -2],
  160. [-2, 4, 2],
  161. [2, -4, 2],
  162. [-2, -4, -2],
  163. [0.5, 1, -2],
  164. [-0.5, 1, 2],
  165. [0.5, -1, 2],
  166. [-0.5, -1, -2],
  167. [1, 0.5, -0.5],
  168. [-1, 0.5, 0.5],
  169. [1, -0.5, 0.5],
  170. [-1, -0.5, -0.5],
  171. [1, -3, 3],
  172. [35, 8, -0.22857143],
  173. [8, 35, -4.375],
  174. [82376, 984398, -11.95005827],
  175. [3.2, 2, -0.625],
  176. [6.2, 8.7, -1.40322581],
  177. [8.7, 6.2, -0.71264368],
  178. [0.001, 3, -3000],
  179. ];
  180. }
  181. /**
  182. * @test linear returns the no root when a = 0
  183. * @dataProvider dataProviderForLinearNoRoot
  184. * @param float $a
  185. * @param float $b
  186. */
  187. public function testLinearNoRoot(float $a, float $b)
  188. {
  189. // When
  190. $root = Algebra::linear($a, $b);
  191. // Then
  192. $this->assertNull($root);
  193. }
  194. /**
  195. * @return array (a, b)
  196. */
  197. public function dataProviderForLinearNoRoot(): array
  198. {
  199. return [
  200. [0, 0],
  201. [0, 1],
  202. [0, 5],
  203. [0, -1],
  204. [0, -5],
  205. [-0, 1],
  206. [0.0, 1],
  207. [-0.0, 1],
  208. ];
  209. }
  210. /**
  211. * @test quadratic returns the expected roots.
  212. * @dataProvider dataProviderForQuadratic
  213. * @param float $a
  214. * @param float $b
  215. * @param float $c
  216. * @param array $expected_quadratic
  217. * @throws \MathPHP\Exception\IncorrectTypeException
  218. */
  219. public function testQuadratic(float $a, float $b, float $c, array $expected_quadratic)
  220. {
  221. // Given
  222. $expected_r1 = $expected_quadratic[0];
  223. $expected_r2 = $expected_quadratic[1];
  224. // When
  225. [$r1, $r2] = Algebra::quadratic($a, $b, $c);
  226. // Then
  227. $this->assertEqualsWithDelta($expected_r1, $r1, 0.00000001);
  228. $this->assertEqualsWithDelta($expected_r2, $r2, 0.00000001);
  229. }
  230. /**
  231. * Many data examples from: http://www.themathpage.com/alg/quadratic-equations.htm
  232. */
  233. public function dataProviderForQuadratic(): array
  234. {
  235. return [
  236. [2, 4, -4, [-1 - \sqrt(3), -1 + \sqrt(3)]],
  237. [1, -3, -4, [-1, 4]],
  238. [1, 1, -4, [-2.56155281280883, 1.56155281280883]],
  239. [1, 0, -4, [-2, 2]],
  240. [6, 11, -35, [-7 / 2, 5 / 3]],
  241. [1, 0, -48, [-4 * \sqrt(3), 4 * \sqrt(3)]],
  242. [1, -7, 0, [0, 7]],
  243. [5, 6, 1, [-1, -0.2]],
  244. [1, 2, -8, [-4, 2]],
  245. [1, 2, -3, [-3, 1]],
  246. [1, -12, 36, [6, 6]],
  247. [2, 9, -5, [-5, 1 / 2]],
  248. [1, -3, 2, [1, 2]],
  249. [1, 7, 12, [-4, -3]],
  250. [1, 3, -10, [-5, 2]],
  251. [1, -1, -30, [-5, 6]],
  252. [2, 7, 3, [-3, -1 / 2]],
  253. [3, 1, -2, [-1, 2 / 3]],
  254. [1, 12, 36, [-6, -6]],
  255. [1, -2, 1, [1, 1]],
  256. [1, -5, 0, [0, 5]],
  257. [1, 1, 0, [-1, 0]],
  258. [3, 4, 0, [-4 / 3, 0]],
  259. [2, -1, 0, [0, 1 / 2]],
  260. [1, 0, -3, [-sqrt(3), \sqrt(3)]],
  261. [1, 0, -25, [-5, 5]],
  262. [1, 0, -10, [-sqrt(10), \sqrt(10)]],
  263. [1, -5, 6, [2, 3]],
  264. [1, -8, 12, [2, 6]],
  265. [3, 1, -10, [-2, 5 / 3]],
  266. [2, -1, 0, [0, 1 / 2]],
  267. [3, 5 / 2, -3, [-3 / 2, 2 / 3]],
  268. [5, 11 / 2, -3, [-3 / 2, 2 / 5]],
  269. [5, -11 / 3, -4, [-3 / 5, 4 / 3]],
  270. [1, 1, -20, [-5, 4]],
  271. [1, -3, -18, [-3, 6]],
  272. [2, -5, -3, [-1 / 2, 3]],
  273. ];
  274. }
  275. /**
  276. * @test quadratic returns the expected root for edge case where a = 0 and formula is not quadratic.
  277. * @dataProvider dataProviderForQuadraticAIsZero
  278. * @param float $a
  279. * @param float $b
  280. * @param float $c
  281. * @param array $expected_quadratic
  282. * @throws \MathPHP\Exception\IncorrectTypeException
  283. */
  284. public function testQuadraticAIsZero($a, $b, $c, array $expected_quadratic)
  285. {
  286. // When
  287. $quadratic = Algebra::quadratic($a, $b, $c);
  288. // Then
  289. $this->assertEqualsWithDelta($expected_quadratic, $quadratic, 0.00000001);
  290. }
  291. /**
  292. * @return array [a, b, c, quadratic]
  293. */
  294. public function dataProviderForQuadraticAIsZero(): array
  295. {
  296. return [
  297. [0, -5, -3, [-3 / 5]],
  298. [0, 5, -3, [3 / 5]],
  299. [0, 12, 6, [-1 / 2]],
  300. [0, 3, 7, [-7 / 3]],
  301. ];
  302. }
  303. /**
  304. * @test quadratic returns array of [NAN, NAN] if the discriminant is negative.
  305. * @dataProvider dataProviderForQuadraticNegativeDiscriminant
  306. * @param float $a
  307. * @param float $b
  308. * @param float $c
  309. * @throws \MathPHP\Exception\IncorrectTypeException
  310. */
  311. public function testQuadraticNegativeDiscriminant(float $a, float $b, float $c)
  312. {
  313. // When
  314. $roots = Algebra::quadratic($a, $b, $c);
  315. // Then
  316. $this->assertIsArray($roots);
  317. $this->assertNotEmpty($roots);
  318. $this->assertEquals(2, count($roots));
  319. foreach ($roots as $root) {
  320. $this->assertTrue(\is_nan($root));
  321. }
  322. }
  323. /**
  324. * @return array [a, b, c]
  325. */
  326. public function dataProviderForQuadraticNegativeDiscriminant(): array
  327. {
  328. return [
  329. [10, 1, 1, [\NAN, \NAN]],
  330. [3, 4, 20, [\NAN, \NAN]],
  331. ];
  332. }
  333. /**
  334. * @test quadratic returns array of Complex Number objects if the discriminant is negative.
  335. * @dataProvider dataProviderForQuadraticNegativeDiscriminantComplex
  336. * @param float $a
  337. * @param float $b
  338. * @param float $c
  339. * @param array $expected
  340. * @throws \Exception
  341. */
  342. public function testQuadraticNegativeDiscriminantComplex(float $a, float $b, float $c, array $expected)
  343. {
  344. // Given
  345. $complex0 = new Number\Complex($expected[0][0], $expected[0][1]);
  346. $complex1 = new Number\Complex($expected[1][0], $expected[1][1]);
  347. // When
  348. $roots = Algebra::quadratic($a, $b, $c, true);
  349. // Then
  350. $this->assertIsArray($roots);
  351. $this->assertInstanceOf(Number\Complex::class, $roots[0]);
  352. $this->assertInstanceOf(Number\Complex::class, $roots[1]);
  353. $this->assertNotEmpty($roots);
  354. $this->assertEquals(2, count($roots));
  355. $this->assertTrue($roots[0]->equals($complex0));
  356. $this->assertTrue($roots[1]->equals($complex1));
  357. }
  358. /**
  359. * @return array [a, b, c, quadratic]
  360. */
  361. public function dataProviderForQuadraticNegativeDiscriminantComplex(): array
  362. {
  363. return [
  364. [10, 1, 1, [[-.05, -1 * \sqrt(39) / 20], [-.05, \sqrt(39) / 20]]],
  365. [3, 4, 20, [[-2 / 3, -1 * \sqrt(14) * 2 / 3], [-2 / 3, \sqrt(14) * 2 / 3]]],
  366. ];
  367. }
  368. /**
  369. * @test discriminant returns the expected value.
  370. * @dataProvider dataProviderForDiscriminant
  371. * @param float $a
  372. * @param float $b
  373. * @param float $c
  374. * @param float $expected_discriminant
  375. */
  376. public function testDiscriminant(float $a, float $b, float $c, float $expected_discriminant)
  377. {
  378. // When
  379. $discriminant = Algebra::discriminant($a, $b, $c);
  380. // Then
  381. $this->assertEqualsWithDelta($expected_discriminant, $discriminant, 0.00000001);
  382. }
  383. /**
  384. * @return array [a, b, c, discriminant]
  385. */
  386. public function dataProviderForDiscriminant(): array
  387. {
  388. return [
  389. [2, 3, 4, -23],
  390. [2, 4, -4, 48],
  391. [1, -3, -4, 25],
  392. [1, 1, -4, 17],
  393. [1, 0, -4, 16],
  394. [6, 11, -35, 961],
  395. [1, 0, -48, 192],
  396. [1, -7, 0, 49],
  397. [10, 1, 1, -39],
  398. [3, 4, 20, -224],
  399. ];
  400. }
  401. /**
  402. * @test cubic returns the expected three real roots when D < 0 or D = 0.
  403. * @dataProvider dataProviderForCubic
  404. * @param int $a
  405. * @param int $b
  406. * @param int $c
  407. * @param int $d
  408. * @param array $expected_cubic expected roots
  409. * @throws \Exception
  410. */
  411. public function testCubic(int $a, int $b, int $c, int $d, array $expected_cubic)
  412. {
  413. // When
  414. $cubic = Algebra::cubic($a, $b, $c, $d);
  415. // Then
  416. $this->assertEqualsWithDelta($expected_cubic, $cubic, 0.00000001);
  417. }
  418. /**
  419. * Calculator used to generate and validate examples: http://www.1728.org/cubic.htm
  420. * Some examples from: http://www.mash.dept.shef.ac.uk/Resources/web-cubicequations-john.pdf
  421. * Some examples from: https://trans4mind.com/personal_development/mathematics/polynomials/cardanoMethodExamples.htm
  422. * @return array
  423. */
  424. public function dataProviderForCubic(): array
  425. {
  426. return [
  427. // D < 0: Three real roots. Nice even numbers.
  428. [1, 0, 0, 0, [0, 0, 0]],
  429. [1, -6, 11, -6, [3, 1, 2]],
  430. [1, -5, -2, 24, [4, -2, 3]],
  431. [1, 0, -7, -6, [3, -2, -1]],
  432. [1, -4, -9, 36, [4, -3, 3]],
  433. [1, 3, -6, -8, [2, -4, -1]],
  434. [1, 2, -21, 18, [3, -6, 1]],
  435. [1, -7, 4, 12, [6, -1, 2]],
  436. [1, 9, 26, 24, [-2, -4, -3]],
  437. [1, 0, -19, -30, [5, -3, -2]],
  438. [1, 2, -25, -50, [5, -5, -2]],
  439. [1, 6, 11, 6, [-1, -3, -2]],
  440. [1, 4, 1, -6, [1, -3, -2]],
  441. [2, 9, 3, -4, [0.5, -4, -1]],
  442. [2, -4, -22, 24, [4, -3, 1]],
  443. [2, 3, -11, -6, [2, -3, -1 / 2]],
  444. [2, -9, 1, 12, [4, -1, 1.5]],
  445. [2, -3, -5, 6, [2, -1.5, 1]],
  446. [3, -1, -10, 8, [4 / 3, -2, 1]],
  447. [6, -5, -17, 6, [2, -1.5, 1 / 3]],
  448. [45, 24, -7, -2, [1 / 3, -2 / 3, -0.2]],
  449. [-1, -1, 22, 40, [5, -4, -2]],
  450. [-1, 0, 19, -30, [3, -5, 2]],
  451. [-1, 6, -5, -12, [4, -1, 3]],
  452. // D < 0: Three real roots. Floats.
  453. [1, 6, 3, -5, [0.66966384064222, -5.24655136455856, -1.42311247608366]],
  454. [1, 4, 1, -5, [0.9122291784844, -3.198691243516, -1.7135379349684]],
  455. [1, -4, -6, 5, [5, -1.61803398874989, 0.61803398874989]],
  456. [1, -3, -1, 1, [3.21431974337754, -0.67513087056665, 0.46081112718911]],
  457. [1, -2, -6, 4, [3.41421356237309, -2, 0.58578643762691]],
  458. [1, 1, -16, 0, [3.53112887414927, -4.53112887414927, 0]],
  459. [2, -3, -22, 24, [3.62221312679243, -3.16796177749228, 1.04574865069985]],
  460. [2, -2, -22, 24, [3.2488979294409, -3.35109344639606, 1.10219551695516]],
  461. [1000, -1254, -496, 191, [1.49979930548345, -0.50033136443491, 0.25453205895145]],
  462. // D = 0: All real roots--at least two are equal. Nice even numbers.
  463. [1, -5, 8, -4, [2, 1, 2]],
  464. [1, -3, 3, -1, [1, 1, 1]],
  465. [1, 3, 3, 1, [-1, -1, -1]],
  466. [1, 2, -20, 24, [2, -6, 2]],
  467. [64, -48, 12, -1, [0.25, 0.25, 0.25]],
  468. ];
  469. }
  470. /**
  471. * @test cubic returns the expected roots when D > 0: one root is real, 2 are complex conjugates.
  472. * @dataProvider dataProviderForCubicOneRealRoot
  473. * @param float $a
  474. * @param float $b
  475. * @param float $c
  476. * @param float $d
  477. * @param float $real_root
  478. * @throws \Exception
  479. */
  480. public function testCubicOneRealRoot(float $a, float $b, float $c, float $d, float $real_root)
  481. {
  482. // When
  483. [$z₁, $z₂, $z₃] = Algebra::cubic($a, $b, $c, $d);
  484. // Then
  485. $this->assertEqualsWithDelta($real_root, $z₁, 0.00000001);
  486. $this->assertNan($z₂);
  487. $this->assertNan($z₃);
  488. }
  489. /**
  490. * Calculator used to generate and validate examples: http://www.1728.org/cubic.htm
  491. * Some examples from: http://www.mash.dept.shef.ac.uk/Resources/web-cubicequations-john.pdf
  492. * @return array
  493. */
  494. public function dataProviderForCubicOneRealRoot(): array
  495. {
  496. return [
  497. // D > 0: one root is real, 2 are complex conjugates.
  498. [1, 1, 1, -3, 0.9999999999999984],
  499. [1, -6, -6, -7, 7],
  500. [1, 1, 4, -8, 1.202981258316938],
  501. [1, 2, 3, -4, 0.7760454350285383],
  502. [1, -2.7, 4.5, -6, 1.9641774065933375],
  503. [1, 3, 3, -2, 0.4422495703074083],
  504. [1, 2, 10, -20, 1.3688081078213727],
  505. [1, 1, 10, -3, 0.28921621924406943],
  506. [2, -3, -4, -35, 3.5000000000000027],
  507. [2, -5, 23, -10, 0.4744277602198689],
  508. [2, -6, 7, -1, 0.1648776515186341],
  509. [2, 0, 4, 1, -0.24283973258548086],
  510. ];
  511. }
  512. /**
  513. * @test cubic returns the expected roots when D > 0: one root is real, 2 are complex conjugates.
  514. * @dataProvider dataProviderForCubicOneRealRootWithComplex
  515. * @param int $a
  516. * @param int $b
  517. * @param int $c
  518. * @param int $d
  519. * @param array $roots
  520. * @throws \Exception
  521. */
  522. public function testCubicOneRealRootWithComplex(int $a, int $b, int $c, int $d, array $roots)
  523. {
  524. // Given
  525. $real_root = $roots[0];
  526. $complex0 = new Number\Complex($roots[1]['r'], $roots[1]['i']);
  527. $complex1 = new Number\Complex($roots[2]['r'], $roots[2]['i']);
  528. // When
  529. [$z₁, $z₂, $z₃] = Algebra::cubic($a, $b, $c, $d, true);
  530. // Then
  531. $this->assertEqualsWithDelta($real_root, $z₁, 0.00000001);
  532. $this->assertInstanceOf(Number\Complex::class, $z₂);
  533. $this->assertInstanceOf(Number\Complex::class, $z₃);
  534. $this->assertTrue($z₂->equals($complex0), "Expecting $complex0 but saw $z₂");
  535. $this->assertTrue($z₃->equals($complex1), "Expecting $complex1 but saw $z₃");
  536. }
  537. /**
  538. * @return array [a, b, c, d, roots]
  539. */
  540. public function dataProviderForCubicOneRealRootWithComplex(): array
  541. {
  542. return [
  543. // D > 0: one root is real, 2 are complex conjugates.
  544. [1, 0, 1, 0, [0, ['r' => 0, 'i' => -1], ['r' => 0, 'i' => 1]]],
  545. [1, -1, 1, -1, [1, ['r' => 0, 'i' => -1], ['r' => 0, 'i' => 1]]],
  546. ];
  547. }
  548. /**
  549. * @test cubic with a₃ coefficient of z³ of 0 is the same as quadratic.
  550. * @dataProvider dataProviderForQuadratic
  551. * @param float $b
  552. * @param float $c
  553. * @param float $d
  554. * @param array $quadratic
  555. * @throws \Exception
  556. */
  557. public function testCubicCubeCoefficientZeroSameAsQuadratic(float $b, float $c, float $d, array $quadratic)
  558. {
  559. $a = 0;
  560. $this->assertEqualsWithDelta($quadratic, Algebra::cubic($a, $b, $c, $d), 0.00000001);
  561. }
  562. /**
  563. * @test cubic returns array of Complex Number objects if the quadradic discriminant is negative.
  564. * @dataProvider dataProviderForQuadraticNegativeDiscriminantComplex
  565. * @param float $a₂
  566. * @param float $a₁
  567. * @param float $a₀
  568. * @param array $expected
  569. * @throws \Exception
  570. */
  571. public function testCubicNegativeDiscriminantComplex(float $a₂, float $a₁, float $a₀, array $expected)
  572. {
  573. // Given
  574. $a₃ = 0;
  575. $complex0 = new Number\Complex($expected[0][0], $expected[0][1]);
  576. $complex1 = new Number\Complex($expected[1][0], $expected[1][1]);
  577. // When
  578. $roots = Algebra::cubic($a₃, $a₂, $a₁, $a₀, true);
  579. // Then
  580. $this->assertIsArray($roots);
  581. $this->assertInstanceOf(Number\Complex::class, $roots[0]);
  582. $this->assertInstanceOf(Number\Complex::class, $roots[1]);
  583. $this->assertNotEmpty($roots);
  584. $this->assertEquals(2, count($roots));
  585. $this->assertTrue($roots[0]->equals($complex0));
  586. $this->assertTrue($roots[1]->equals($complex1));
  587. }
  588. /**
  589. * @test quartic
  590. * @dataProvider dataProviderForQuartic
  591. * @param int $a
  592. * @param int $b
  593. * @param int $c
  594. * @param int $d
  595. * @param int $e
  596. * @param array $expected_quartic
  597. * @throws \Exception
  598. */
  599. public function testQuartic(int $a, int $b, int $c, int $d, int $e, array $expected_quartic)
  600. {
  601. // When
  602. $quartic = Algebra::quartic($a, $b, $c, $d, $e);
  603. // Then
  604. $this->assertEqualsWithDelta($expected_quartic, $quartic, 0.00000001);
  605. }
  606. /**
  607. * @return array [a, b, c, d, e, quartic]
  608. */
  609. public function dataProviderForQuartic(): array
  610. {
  611. return [
  612. [3, 6, -123, -126, 1080, [5, -6, 3, -4]],
  613. [1, -10, 35, -50, 24, [4, 1, 3, 2]],
  614. [1, -4, 6, -4, 1, [1, 1, 1, 1]],
  615. // Actually a cubic
  616. [0, 1, -6, 11, -6, [3, 1, 2]],
  617. // Zero Root
  618. [1, -6, 11, -6, 0, [0, 3, 1, 2]],
  619. // Biquadratic
  620. [1, 0, -5, 0, 4, [2, -2, 1, -1]],
  621. // Depressed quartic is biquadratic
  622. [1, 12, 49, 78, 40, [-1, -5, -2, -4]],
  623. // Depressed quartic and has 4 real roots.
  624. [1, 0, -25, 60, -36, [-6, 1, 2, 3]],
  625. ];
  626. }
  627. /**
  628. * @test quartic with two complex roots - not set to return complex
  629. * @dataProvider dataProviderForQuarticTwoComplex
  630. * @param int $a
  631. * @param int $b
  632. * @param int $c
  633. * @param int $d
  634. * @param int $e
  635. * @param array $quartic expected roots
  636. * @throws \Exception
  637. */
  638. public function testQuarticTwoComplexNotSetToReturnComplex($a, $b, $c, $d, $e, $quartic)
  639. {
  640. // When
  641. [$z₁, $z₂, $z₃, $z₄] = Algebra::quartic($a, $b, $c, $d, $e);
  642. // Then
  643. $this->assertEqualsWithDelta($quartic[0], \floatval($z₁), 0.00000001);
  644. $this->assertEqualsWithDelta($quartic[1], \floatval($z₂), 0.00000001);
  645. $this->assertNan($z₃, '');
  646. $this->assertNan($z₄, '');
  647. }
  648. /**
  649. * @test quartic with two complex roots - set to return complex
  650. * @dataProvider dataProviderForQuarticTwoComplex
  651. * @param int $a
  652. * @param int $b
  653. * @param int $c
  654. * @param int $d
  655. * @param int $e
  656. * @param array $quartic expected roots
  657. * @throws \Exception
  658. */
  659. public function testQuarticTwoComplex($a, $b, $c, $d, $e, $quartic)
  660. {
  661. // Given
  662. $complex0 = new Number\Complex($quartic[2]['r'], $quartic[2]['i']);
  663. $complex1 = new Number\Complex($quartic[3]['r'], $quartic[3]['i']);
  664. // When
  665. [$z₁, $z₂, $z₃, $z₄] = Algebra::quartic($a, $b, $c, $d, $e, true);
  666. // Then
  667. $this->assertEqualsWithDelta($quartic[0], \floatval($z₁), 0.00000001);
  668. $this->assertEqualsWithDelta($quartic[1], \floatval($z₂), 0.00000001);
  669. $this->assertTrue($z₃->equals($complex0), "Expecting $complex0 but saw $z₃, complex conjugate is $z₄");
  670. $this->assertTrue($z₄->equals($complex1), "Expecting $complex1 but saw $z₄, complex conjugate is $z₃");
  671. }
  672. /**
  673. * @return array
  674. */
  675. public function dataProviderForQuarticTwoComplex(): array
  676. {
  677. return [
  678. // Two Complex Roots
  679. [1, -5, 10, -10, 4, [1, 2, ['r' => 1, 'i' => -1], ['r' => 1, 'i' => 1]]],
  680. // And is a depressed quartic. (sum of roots=0)
  681. [1, 0, 5 / 8, -5 / 8, -51 / 256, [-.25, .75, ['r' => -.25, 'i' => -1], ['r' => -.25, 'i' => 1]]],
  682. [1, 0, -5, 10, -6, [-3, 1, ['r' => 1, 'i' => -1], ['r' => 1, 'i' => 1]]],
  683. // Biquadratic with two complex roots
  684. [1, 0, -5, 0, -36, [3, -3, ['r' => 0, 'i' => 2], ['r' => 0, 'i' => -2]]],
  685. ];
  686. }
  687. /**
  688. * @test quartic with four complex roots - not set to return complex
  689. * @dataProvider dataProviderForQuarticFourComplex
  690. * @param int $a
  691. * @param int $b
  692. * @param int $c
  693. * @param int $d
  694. * @param int $e
  695. * @param array $quartic expected roots
  696. * @throws \Exception
  697. */
  698. public function testQuarticFourComplexReturnsNansIfNotSetToReturnComplex(int $a, int $b, int $c, int $d, int $e, array $quartic)
  699. {
  700. // When
  701. [$z₁, $z₂, $z₃, $z₄] = Algebra::quartic($a, $b, $c, $d, $e);
  702. // Then
  703. $this->assertNan($z₁);
  704. $this->assertNan($z₂);
  705. $this->assertNan($z₃);
  706. $this->assertNan($z₄);
  707. }
  708. /**
  709. * @test quartic with four complex roots - set to return complex
  710. * @dataProvider dataProviderForQuarticFourComplex
  711. * @param int $a
  712. * @param int $b
  713. * @param int $c
  714. * @param int $d
  715. * @param int $e
  716. * @param array $quartic expected roots
  717. * @throws \Exception
  718. */
  719. public function testQuarticFourComplex(int $a, int $b, int $c, int $d, int $e, array $quartic)
  720. {
  721. // Given
  722. $complex0 = new Number\Complex($quartic[0]['r'], $quartic[0]['i']);
  723. $complex1 = new Number\Complex($quartic[1]['r'], $quartic[1]['i']);
  724. $complex2 = new Number\Complex($quartic[2]['r'], $quartic[2]['i']);
  725. $complex3 = new Number\Complex($quartic[3]['r'], $quartic[3]['i']);
  726. // When
  727. [$z₁, $z₂, $z₃, $z₄] = Algebra::quartic($a, $b, $c, $d, $e, true);
  728. // Then
  729. $this->assertTrue($z₁->equals($complex0), "Expecting $complex0 but saw $z₁");
  730. $this->assertTrue($z₂->equals($complex1), "Expecting $complex1 but saw $z₂");
  731. $this->assertTrue($z₃->equals($complex2), "Expecting $complex2 but saw $z₃");
  732. $this->assertTrue($z₄->equals($complex3), "Expecting $complex3 but saw $z₄");
  733. }
  734. /**
  735. * @return array
  736. */
  737. public function dataProviderForQuarticFourComplex(): array
  738. {
  739. return [
  740. // Four Complex Roots
  741. [1, -6, 18, -24, 16, [['r' => 1, 'i' => -1], ['r' => 1, 'i' => 1], ['r' => 2, 'i' => -2], ['r' => 2, 'i' => 2]]],
  742. [1, 0, -3, 12, 40, [['r' => -2, 'i' => -1], ['r' => -2, 'i' => 1], ['r' => 2, 'i' => -2], ['r' => 2, 'i' => 2]]],
  743. // Biquadratic with four complex roots
  744. [1, 0, 13, 0, 36, [['r' => 0, 'i' => 2], ['r' => 0, 'i' => -2], ['r' => 0, 'i' => 3], ['r' => 0, 'i' => -3]]],
  745. ];
  746. }
  747. }