QuaternionTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. namespace MathPHP\Tests\Number;
  3. use MathPHP\Number\Quaternion;
  4. use MathPHP\Exception;
  5. use MathPHP\Number\ObjectArithmetic;
  6. class QuaternionTest extends \PHPUnit\Framework\TestCase
  7. {
  8. /**
  9. * @test Interfaces
  10. */
  11. public function testObjectArithmeticInterface()
  12. {
  13. // Given
  14. $c = new Quaternion(1, 2, 3, 4);
  15. // Then
  16. $this->assertInstanceOf(ObjectArithmetic::class, $c);
  17. }
  18. public function testZeroValue()
  19. {
  20. // Given
  21. $c = Quaternion::createZeroValue();
  22. // Then
  23. $this->assertEquals(0, $c->r);
  24. $this->assertEquals(0, $c->i);
  25. $this->assertEquals(0, $c->j);
  26. $this->assertEquals(0, $c->k);
  27. }
  28. /**
  29. * @test __toString returns the proper string representation of a quaternion
  30. * @dataProvider dataProviderForToString
  31. * @param number $r
  32. * @param number $i
  33. * @param number $j
  34. * @param number $k
  35. * @param string $expected
  36. */
  37. public function testToString($r, $i, $j, $k, string $expected)
  38. {
  39. // Given
  40. $c = new Quaternion($r, $i, $j, $k);
  41. // When
  42. $string = $c->__toString();
  43. // Then
  44. $this->assertEquals($expected, $string);
  45. $this->assertEquals($expected, (string) $c);
  46. }
  47. public function dataProviderForToString(): array
  48. {
  49. return [
  50. [0, 0, 0, 0, '0'],
  51. [1, 0, 0, 0, '1'],
  52. [-1, 0, 0, 0, '-1'],
  53. [0, 1, 0, 0, '1i'],
  54. [0, -1, 0, 0, '-1i'],
  55. [1, 0, 1, 0, '1 + 1j'],
  56. [1, 0, 0, 2, '1 + 2k'],
  57. [2, 0, -1, 0, '2 - 1j'],
  58. [2, 0, 0, -2, '2 - 2k'],
  59. [1, 0, -1, -1, '1 - 1j - 1k'],
  60. [1, 1, -2, 4, '1 + 1i - 2j + 4k'],
  61. ];
  62. }
  63. /**
  64. * @test __get returns r, i, j, and k
  65. */
  66. public function testGet()
  67. {
  68. // Given
  69. $r = 1;
  70. $i = 2;
  71. $j = 3;
  72. $k = 4;
  73. $c = new Quaternion($r, $i, $j, $k);
  74. // Then
  75. $this->assertEquals($r, $c->r);
  76. $this->assertEquals($i, $c->i);
  77. $this->assertEquals($j, $c->j);
  78. $this->assertEquals($k, $c->k);
  79. }
  80. /**
  81. * @test __get throws an Exception\BadParameterException if a property other than r or i is attempted
  82. */
  83. public function testGetException()
  84. {
  85. // Given
  86. $r = 1;
  87. $i = 2;
  88. $j = 3;
  89. $k = 4;
  90. $c = new Quaternion($r, $i, $j, $k);
  91. // Then
  92. $this->expectException(Exception\BadParameterException::class);
  93. // When
  94. $z = $c->z;
  95. }
  96. /**
  97. * @test complexConjugate returns the expected Quaternion
  98. * @dataProvider dataProviderForComplexConjugate
  99. * @param number $r
  100. * @param number $i
  101. * @param number $j
  102. * @param number $k
  103. */
  104. public function testComplexConjugate($r, $i, $j, $k)
  105. {
  106. // Given
  107. $c = new Quaternion($r, $i, $j, $k);
  108. // When
  109. $cc = $c->complexConjugate();
  110. // Then
  111. $this->assertEquals($c->r, $cc->r);
  112. $this->assertEquals($c->i, -1 * $cc->i);
  113. $this->assertEquals($c->j, -1 * $cc->j);
  114. $this->assertEquals($c->k, -1 * $cc->k);
  115. }
  116. public function dataProviderForComplexConjugate(): array
  117. {
  118. return [
  119. [0, 0, 0, 0],
  120. [1, 0, 0, 0],
  121. [0, 1, 0, 0],
  122. [0, 1, 1, 1],
  123. [1, 1, -1, -1],
  124. [1, 2, 3, 4],
  125. [3, 7, 11, -13],
  126. ];
  127. }
  128. /**
  129. * @test abs returns the expected value
  130. * @dataProvider dataProviderForAbs
  131. * @param number $r
  132. * @param number $i
  133. * @param number $j
  134. * @param number $k
  135. * @param number $expected
  136. */
  137. public function testAbs($r, $i, $j, $k, $expected)
  138. {
  139. // Given
  140. $c = new Quaternion($r, $i, $j, $k);
  141. // When
  142. $abs = $c->abs();
  143. // Then
  144. $this->assertEquals($expected, $abs);
  145. }
  146. public function dataProviderForAbs(): array
  147. {
  148. return [
  149. [0, 0, 0, 0, 0],
  150. [1, 0, 0, 0, 1],
  151. [0, 1, 0, 0, 1],
  152. [0, 0, 1, 0, 1],
  153. [0, 0, 0, 1, 1],
  154. [1, 2, 3, 4, \sqrt(30)],
  155. [-1, 0, 0, 0, 1],
  156. [0, -1, 0, 0, 1],
  157. [-1, 2, -3, 4, \sqrt(30)],
  158. ];
  159. }
  160. /**
  161. * @test negate returns the expected quaternion with signs negated
  162. * @dataProvider dataProviderForNegate
  163. * @param number $r₁
  164. * @param number $i₁
  165. * @param number $r₂
  166. * @param number $i₂
  167. */
  168. public function testNegate($r₁, $i₁, $j₁, $k₁, $r₂, $i₂, $j₂, $k₂)
  169. {
  170. // Given
  171. $c = new Quaternion($r₁, $i₁, $j₁, $k₁);
  172. $expected = new Quaternion($r₂, $i₂, $j₂, $k₂);
  173. // When
  174. $negated = $c->negate();
  175. // Then
  176. $this->assertTrue($negated->equals($expected));
  177. $this->assertEquals($expected->r, $negated->r);
  178. $this->assertEquals($expected->i, $negated->i);
  179. $this->assertEquals($expected->j, $negated->j);
  180. $this->assertEquals($expected->k, $negated->k);
  181. }
  182. public function dataProviderForNegate(): array
  183. {
  184. return [
  185. [0, 0, 0, 0, 0, 0, 0, 0],
  186. [1, 0, 0, 0, -1, 0, 0, 0],
  187. [0, 1, 1, 1, 0, -1, -1, -1],
  188. [1, 2, -1, -2, -1, -2, 1, 2],
  189. [3, 4, 3, 4, -3, -4, -3, -4],
  190. ];
  191. }
  192. /**
  193. * @test Constructor throws an exception when given non-numeric
  194. * @dataProvider dataProviderForConstructorException
  195. * @throws \Exception
  196. */
  197. public function testConstructorException($r, $i, $j, $k)
  198. {
  199. // Then
  200. $this->expectException(Exception\BadDataException::class);
  201. // When
  202. $c = new Quaternion($r, $i, $j, $k);
  203. }
  204. public function dataProviderForConstructorException(): array
  205. {
  206. return [
  207. ['a', 1, 1, 1],
  208. [1, true, 1, 1],
  209. [1, 1, new \stdClass(), 1],
  210. [1, 1, 1, [1]],
  211. ];
  212. }
  213. /**
  214. * @testCase inverse returns the expected quaternion
  215. * @dataProvider dataProviderForInverse
  216. * @param number $r
  217. * @param number $i
  218. * @param number $j
  219. * @param number $k
  220. * @param number $expected_r
  221. * @param number $expected_i
  222. * @param number $expected_j
  223. * @param number $expected_k
  224. */
  225. public function testInverse($r, $i, $j, $k, $expected_r, $expected_i, $expected_j, $expected_k)
  226. {
  227. $q = new Quaternion($r, $i, $j, $k);
  228. $inverse = $q->inverse();
  229. $this->assertEquals($expected_r, $inverse->r);
  230. $this->assertEquals($expected_i, $inverse->i);
  231. $this->assertEquals($expected_j, $inverse->j);
  232. $this->assertEquals($expected_k, $inverse->k);
  233. }
  234. public function dataProviderForInverse(): array
  235. {
  236. return [
  237. [1, 0, 0, 0, 1, 0, 0, 0],
  238. [0, 1, 0, 0, 0, -1, 0, 0],
  239. [0, 0, 1, 0, 0, 0, -1, 0],
  240. [0, 0, 0, 1, 0, 0, 0, -1],
  241. [1, -1, -1, -1, .25, .25, .25, .25],
  242. ];
  243. }
  244. /**
  245. * @testCase inverse throws an Exception\BadDataException when value is 0 + 0i + 0j + 0k
  246. */
  247. public function testInverseException()
  248. {
  249. $q = new Quaternion(0, 0, 0, 0);
  250. $this->expectException(Exception\BadDataException::class);
  251. $q->inverse();
  252. }
  253. /**
  254. * @test add of two complex numbers returns the expected complex number
  255. * @dataProvider dataProviderForAdd
  256. * @param array $complex1
  257. * @param array $complex2
  258. * @param array $expected
  259. */
  260. public function testAdd(array $complex1, array $complex2, array $expected)
  261. {
  262. // Given
  263. $q1 = new Quaternion($complex1['r'], $complex1['i'], $complex1['j'], $complex1['k']);
  264. $q2 = new Quaternion($complex2['r'], $complex2['i'], $complex2['j'], $complex2['k']);
  265. // When
  266. $result = $q1->add($q2);
  267. // Then
  268. $this->assertEquals($expected['r'], $result->r);
  269. $this->assertEquals($expected['i'], $result->i);
  270. $this->assertEquals($expected['j'], $result->j);
  271. $this->assertEquals($expected['k'], $result->k);
  272. }
  273. public function dataProviderForAdd(): array
  274. {
  275. return [
  276. [
  277. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  278. ['r' => 4, 'i' => -3, 'j' => -2, 'k' => -5],
  279. ['r' => 7, 'i' => -1, 'j' => -1, 'k' => -6],
  280. ],
  281. ];
  282. }
  283. /**
  284. * @test subtract of two quaternions returns the expected quaternion
  285. * @dataProvider dataProviderForSubtract
  286. * @param array $complex1
  287. * @param array $complex2
  288. * @param array $expected
  289. */
  290. public function testSubtract(array $complex1, array $complex2, array $expected)
  291. {
  292. // Given
  293. $q1 = new Quaternion($complex1['r'], $complex1['i'], $complex1['j'], $complex1['k']);
  294. $q2 = new Quaternion($complex2['r'], $complex2['i'], $complex2['j'], $complex2['k']);
  295. // When
  296. $result = $q1->subtract($q2);
  297. // Then
  298. $this->assertEquals($expected['r'], $result->r);
  299. $this->assertEquals($expected['i'], $result->i);
  300. $this->assertEquals($expected['j'], $result->j);
  301. $this->assertEquals($expected['k'], $result->k);
  302. }
  303. public function dataProviderForSubtract(): array
  304. {
  305. return [
  306. [
  307. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  308. ['r' => -4, 'i' => 3, 'j' => 2, 'k' => 5],
  309. ['r' => 7, 'i' => -1, 'j' => -1, 'k' => -6],
  310. ],
  311. ];
  312. }
  313. /**
  314. * @test add of real numbers returns the expected quaternion
  315. * @dataProvider dataProviderForAddReal
  316. */
  317. public function testAddReal($complex, $real, $expected)
  318. {
  319. // Given
  320. $q = new Quaternion($complex['r'], $complex['i'], $complex['j'], $complex['k']);
  321. // When
  322. $result = $q->add($real);
  323. // Then
  324. $this->assertEquals($expected['r'], $result->r);
  325. $this->assertEquals($expected['i'], $result->i);
  326. $this->assertEquals($expected['j'], $result->j);
  327. $this->assertEquals($expected['k'], $result->k);
  328. }
  329. public function dataProviderForAddReal()
  330. {
  331. return [
  332. [
  333. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  334. 5,
  335. ['r' => 8, 'i' => 2, 'j' => 1, 'k' => -1],
  336. ],
  337. [
  338. ['r' => 0, 'i' => 0, 'j' => 0, 'k' => 0],
  339. 5,
  340. ['r' => 5, 'i' => 0, 'j' => 0, 'k' => 0],
  341. ],
  342. [
  343. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  344. -2,
  345. ['r' => 1, 'i' => 2, 'j' => 1, 'k' => -1],
  346. ],
  347. ];
  348. }
  349. /**
  350. * @test subtract of real numbers returns the expected quaternion
  351. * @dataProvider dataProviderForSubtractReal
  352. */
  353. public function testSubtractReal($complex, $real, $expected)
  354. {
  355. // Given
  356. $q = new Quaternion($complex['r'], $complex['i'], $complex['j'], $complex['k']);
  357. // When
  358. $result = $q->subtract($real);
  359. // Then
  360. $this->assertEquals($expected['r'], $result->r);
  361. $this->assertEquals($expected['i'], $result->i);
  362. $this->assertEquals($expected['j'], $result->j);
  363. $this->assertEquals($expected['k'], $result->k);
  364. }
  365. public function dataProviderForSubtractReal()
  366. {
  367. return [
  368. [
  369. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  370. -5,
  371. ['r' => 8, 'i' => 2, 'j' => 1, 'k' => -1],
  372. ],
  373. [
  374. ['r' => 0, 'i' => 0, 'j' => 0, 'k' => 0],
  375. -5,
  376. ['r' => 5, 'i' => 0, 'j' => 0, 'k' => 0],
  377. ],
  378. [
  379. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  380. 2,
  381. ['r' => 1, 'i' => 2, 'j' => 1, 'k' => -1],
  382. ],
  383. ];
  384. }
  385. /**
  386. * @test multiply of two quaternions returns the expected quaternion
  387. * @dataProvider dataProviderForMultiply
  388. * @param array $complex1
  389. * @param array $complex2
  390. * @param array $expected
  391. */
  392. public function testMultiply(array $complex1, array $complex2, array $expected)
  393. {
  394. // Given
  395. $q1 = new Quaternion($complex1['r'], $complex1['i'], $complex1['j'], $complex1['k']);
  396. $q2 = new Quaternion($complex2['r'], $complex2['i'], $complex2['j'], $complex2['k']);
  397. // When
  398. $result = $q1->multiply($q2);
  399. // Then
  400. $this->assertEquals($expected['r'], $result->r);
  401. $this->assertEquals($expected['i'], $result->i);
  402. $this->assertEquals($expected['j'], $result->j);
  403. $this->assertEquals($expected['k'], $result->k);
  404. }
  405. public function dataProviderForMultiply(): array
  406. {
  407. return [
  408. [
  409. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  410. ['r' => 1, 'i' => 4, 'j' => 3, 'k' => 2],
  411. ['r' => -6, 'i' => 19, 'j' => 2, 'k' => 7],
  412. ],
  413. [
  414. ['r' => 3, 'i' => 13, 'j' => 5, 'k' => 7],
  415. ['r' => 19, 'i' => 17, 'j' => -11, 'k' => 2],
  416. ['r' => -123, 'i' => 385, 'j' => 155, 'k' => -89],
  417. ],
  418. [
  419. ['r' => 2, 'i' => 3, 'j' => 4, 'k' => 5],
  420. ['r' => 6, 'i' => 7, 'j' => 8, 'k' => 9],
  421. ['r' => -86, 'i' => 28, 'j' => 48, 'k' => 44],
  422. ],
  423. ];
  424. }
  425. /**
  426. * @test divide of two cquaternions returns the expected quaternion
  427. * @dataProvider dataProviderForDivide
  428. * @param array $complex1
  429. * @param array $complex2
  430. * @param array $expected
  431. */
  432. public function testDivide(array $complex1, array $complex2, array $expected)
  433. {
  434. // Given
  435. $q1 = new Quaternion($complex1['r'], $complex1['i'], $complex1['j'], $complex1['k']);
  436. $q2 = new Quaternion($complex2['r'], $complex2['i'], $complex2['j'], $complex2['k']);
  437. // When
  438. $result = $q1->divide($q2);
  439. // Then
  440. $this->assertEqualsWithDelta($expected['r'], $result->r, 0.00001);
  441. $this->assertEqualsWithDelta($expected['i'], $result->i, 0.00001);
  442. $this->assertEqualsWithDelta($expected['j'], $result->j, 0.00001);
  443. $this->assertEqualsWithDelta($expected['k'], $result->k, 0.00001);
  444. }
  445. public function dataProviderForDivide(): array
  446. {
  447. return [
  448. [
  449. ['r' => 3, 'i' => 2, 'j' => 1, 'k' => -1],
  450. ['r' => 1, 'i' => 4, 'j' => 3, 'k' => 2],
  451. ['r' => 2 / 5, 'i' => -1 / 2, 'j' => 0, 'k' => -3 / 10],
  452. ],
  453. ];
  454. }
  455. /**
  456. * @test add throws an Exception\IncorrectTypeException when the argument is not a number or quaternion
  457. */
  458. public function testQuaternionAddException()
  459. {
  460. // Given
  461. $q = new Quaternion(1, 1, 1, 1);
  462. // Then
  463. $this->expectException(Exception\IncorrectTypeException::class);
  464. // When
  465. $q->add("string");
  466. }
  467. /**
  468. * @test subtract throws an Exception\IncorrectTypeException when the argument is not a number or quaternion
  469. */
  470. public function testQuaternionSubtractException()
  471. {
  472. // Given
  473. $q = new Quaternion(1, 1, 1, 1);
  474. // Then
  475. $this->expectException(Exception\IncorrectTypeException::class);
  476. // When
  477. $q->subtract("string");
  478. }
  479. /**
  480. * @test multiply throws an Exception\IncorrectTypeException when the argument is not a number or quaternion
  481. */
  482. public function tesQuaternionMultiplyException()
  483. {
  484. // Given
  485. $q = new Quaternion(1, 1, 1, 1);
  486. // Then
  487. $this->expectException(Exception\IncorrectTypeException::class);
  488. // When
  489. $q->multiply("string");
  490. }
  491. /**
  492. * @test divide throws an Exception\IncorrectTypeException when the argument is not a number or quaternion
  493. */
  494. public function testQuaternionDivideException()
  495. {
  496. // Given
  497. $q = new Quaternion(1, 1, 1, 1);
  498. // Then
  499. $this->expectException(Exception\IncorrectTypeException::class);
  500. // When
  501. $q->divide("string");
  502. }
  503. }