Algebra.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. <?php
  2. namespace MathPHP;
  3. use MathPHP\Number\Complex;
  4. use MathPHP\Functions\Map\Single;
  5. use MathPHP\NumberTheory\Integer;
  6. class Algebra
  7. {
  8. private const ZERO_TOLERANCE = 0.000000000001;
  9. /**
  10. * Greatest common divisor - recursive Euclid's algorithm
  11. * The largest positive integer that divides the numbers without a remainder.
  12. * For example, the GCD of 8 and 12 is 4.
  13. * https://en.wikipedia.org/wiki/Greatest_common_divisor
  14. *
  15. * gcd(a, 0) = a
  16. * gcd(a, b) = gcd(b, a mod b)
  17. *
  18. * @param int $a
  19. * @param int $b
  20. *
  21. * @return int
  22. */
  23. public static function gcd(int $a, int $b): int
  24. {
  25. // Base cases
  26. if ($a == 0) {
  27. return $b;
  28. }
  29. if ($b == 0) {
  30. return $a;
  31. }
  32. // Recursive case
  33. return Algebra::gcd($b, $a % $b);
  34. }
  35. /**
  36. * Extended greatest common divisor
  37. * Compute the gcd as a multiple of the inputs:
  38. * gcd(a, b) = a*a' + b*b'
  39. * https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
  40. * Knuth, The Art of Computer Programming, Volume 2, 4.5.2 Algorithm X.
  41. *
  42. * @param int $a
  43. * @param int $b
  44. *
  45. * @return array{int, int, int} [gcd, a', b']
  46. */
  47. public static function extendedGcd(int $a, int $b): array
  48. {
  49. // Base cases
  50. if ($a == 0) {
  51. return [$b, 0, 1];
  52. }
  53. if ($b == 0) {
  54. return [$a, 1, 0];
  55. }
  56. $x₂ = 1;
  57. $x₁ = 0;
  58. $y₂ = 0;
  59. $y₁ = 1;
  60. while ($b > 0) {
  61. $q = \intdiv($a, $b);
  62. $r = $a % $b;
  63. $x = $x₂ - ($q * $x₁);
  64. $y = $y₂ - ($q * $y₁);
  65. $x₂ = $x₁;
  66. $x₁ = $x;
  67. $y₂ = $y₁;
  68. $y₁ = $y;
  69. $a = $b;
  70. $b = $r;
  71. }
  72. return [$a, $x₂, $y₂];
  73. }
  74. /**
  75. * Least common multiple
  76. * The smallest positive integer that is divisible by both a and b.
  77. * For example, the LCM of 5 and 2 is 10.
  78. * https://en.wikipedia.org/wiki/Least_common_multiple
  79. *
  80. * |a ⋅ b|
  81. * lcm(a, b) = ---------
  82. * gcd(a, b)
  83. *
  84. * @param int $a
  85. * @param int $b
  86. *
  87. * @return int
  88. * @psalm-suppress InvalidReturnType (Change to intdiv for PHP 8.0)
  89. */
  90. public static function lcm(int $a, int $b): int
  91. {
  92. // Special case
  93. if ($a === 0 || $b === 0) {
  94. return 0;
  95. }
  96. return \abs($a * $b) / Algebra::gcd($a, $b);
  97. }
  98. /**
  99. * Get factors of an integer
  100. * The decomposition of a composite number into a product of smaller integers.
  101. * https://en.wikipedia.org/wiki/Integer_factorization
  102. *
  103. * Algorithm:
  104. * - special case: if x is 0, return [\INF]
  105. * - let x be |x|
  106. * - push on 1 as a factor
  107. * - prime factorize x
  108. * - build sets of prime powers from primes
  109. * - push on the product of each set
  110. *
  111. * @param int $x
  112. * @return array<float> of factors
  113. *
  114. * @throws Exception\OutOfBoundsException if n is < 1
  115. */
  116. public static function factors(int $x): array
  117. {
  118. // 0 has infinite factors
  119. if ($x === 0) {
  120. return [\INF];
  121. }
  122. $x = \abs($x);
  123. $factors = [1];
  124. // Prime factorize x
  125. $primes = Integer::primeFactorization($x);
  126. // Prime powers from primes
  127. $sets = [];
  128. $current = [];
  129. $map = [];
  130. $exponents = \array_count_values($primes);
  131. $limit = 1;
  132. $count = 0;
  133. foreach ($exponents as $prime => $exponent) {
  134. $map[] = $prime;
  135. $sets[$prime] = [1, $prime];
  136. $primePower = $prime;
  137. for ($n = 2; $n <= $exponent; ++$n) {
  138. $primePower *= $prime;
  139. $sets[$prime][$n] = $primePower;
  140. }
  141. $limit *= \count($sets[$prime]);
  142. if ($count === 0) { // Skip 1 on the first prime
  143. $current[] = \next($sets[$prime]);
  144. } else {
  145. $current[] = 1;
  146. }
  147. ++$count;
  148. }
  149. // Multiply distinct prime powers together
  150. for ($i = 1; $i < $limit; ++$i) {
  151. $factors[] = \array_product($current);
  152. for ($i2 = 0; $i2 < $count; ++$i2) {
  153. $current[$i2] = \next($sets[$map[$i2]]);
  154. if ($current[$i2] !== false) {
  155. break;
  156. }
  157. $current[$i2] = \reset($sets[$map[$i2]]);
  158. }
  159. }
  160. \sort($factors);
  161. return $factors;
  162. }
  163. /**
  164. * Linear equation of one variable
  165. * An equation having the form: ax + b = 0
  166. * where x represents an unknown, or the root of the equation, and a and b represent known numbers.
  167. * https://en.wikipedia.org/wiki/Linear_equation#One_variable
  168. *
  169. * ax + b = 0
  170. *
  171. * -b
  172. * x = --
  173. * a
  174. *
  175. * No root exists for a = 0, as a(0) + b = b
  176. *
  177. * @param float $a a of ax + b = 0
  178. * @param float $b b of ax + b = 0
  179. *
  180. * @return float|null Root of the linear equation: x = -b / a
  181. */
  182. public static function linear(float $a, float $b): ?float
  183. {
  184. if ($a == 0) {
  185. return null;
  186. }
  187. return -$b / $a;
  188. }
  189. /**
  190. * Quadratic equation
  191. * An equation having the form: ax² + bx + c = 0
  192. * where x represents an unknown, or the root(s) of the equation,
  193. * and a, b, and c represent known numbers such that a is not equal to 0.
  194. * The numbers a, b, and c are the coefficients of the equation
  195. * https://en.wikipedia.org/wiki/Quadratic_equation
  196. *
  197. * _______
  198. * -b ± √b² -4ac
  199. * x = -------------
  200. * 2a
  201. *
  202. * Edge case where a = 0 and formula is not quadratic:
  203. *
  204. * 0x² + bx + c = 0
  205. *
  206. * -c
  207. * x = ---
  208. * b
  209. *
  210. * Note: If discriminant is negative, roots will be NAN.
  211. *
  212. * @param float $a x² coefficient
  213. * @param float $b x coefficient
  214. * @param float $c constant coefficient
  215. * @param bool $return_complex Whether to return complex numbers or NANs if imaginary roots
  216. *
  217. * @return float[]|Complex[]
  218. * [x₁, x₂] roots of the equation, or
  219. * [NAN, NAN] if discriminant is negative, or
  220. * [Complex, Complex] if discriminant is negative and complex option is on or
  221. * [x] if a = 0 and formula isn't quadratics
  222. *
  223. * @throws Exception\IncorrectTypeException
  224. */
  225. public static function quadratic(float $a, float $b, float $c, bool $return_complex = false): array
  226. {
  227. // Formula not quadratic (a = 0)
  228. if ($a == 0) {
  229. return [-$c / $b];
  230. }
  231. // Discriminant intermediate calculation and imaginary number check
  232. $⟮b² − 4ac⟯ = self::discriminant($a, $b, $c);
  233. if ($⟮b² − 4ac⟯ < 0) {
  234. if (!$return_complex) {
  235. return [\NAN, \NAN];
  236. }
  237. $complex = new Number\Complex(0, \sqrt(-1 * $⟮b² − 4ac⟯));
  238. $x₁ = $complex->multiply(-1)->subtract($b)->divide(2 * $a);
  239. $x₂ = $complex->subtract($b)->divide(2 * $a);
  240. } else {
  241. // Standard quadratic equation case
  242. $√⟮b² − 4ac⟯ = \sqrt(self::discriminant($a, $b, $c));
  243. $x₁ = (-$b - $√⟮b² − 4ac⟯) / (2 * $a);
  244. $x₂ = (-$b + $√⟮b² − 4ac⟯) / (2 * $a);
  245. }
  246. return [$x₁, $x₂];
  247. }
  248. /**
  249. * Discriminant
  250. * https://en.wikipedia.org/wiki/Discriminant
  251. *
  252. * Δ = b² - 4ac
  253. *
  254. * @param float $a x² coefficient
  255. * @param float $b x coefficient
  256. * @param float $c constant coefficient
  257. *
  258. * @return float
  259. */
  260. public static function discriminant(float $a, float $b, float $c): float
  261. {
  262. return $b ** 2 - (4 * $a * $c);
  263. }
  264. /**
  265. * Cubic equation
  266. * An equation having the form: z³ + a₂z² + a₁z + a₀ = 0
  267. * https://en.wikipedia.org/wiki/Cubic_function
  268. * http://mathworld.wolfram.com/CubicFormula.html
  269. *
  270. * The coefficient a₃ of z³ may be taken as 1 without loss of generality by dividing the entire equation through by a₃.
  271. *
  272. * If a₃ ≠ 0, then divide a₂, a₁, and a₀ by a₃.
  273. *
  274. * 3a₁ - a₂²
  275. * Q ≡ ---------
  276. * 9
  277. *
  278. * 9a₂a₁ - 27a₀ - 2a₂³
  279. * R ≡ -------------------
  280. * 54
  281. *
  282. * Polynomial discriminant D
  283. * D ≡ Q³ + R²
  284. *
  285. * If D > 0, one root is real, and two are are complex conjugates.
  286. * If D = 0, all roots are real, and at least two are equal.
  287. * If D < 0, all roots are real and unequal.
  288. *
  289. * If D < 0:
  290. *
  291. * R
  292. * Define θ = cos⁻¹ ----
  293. * √-Q³
  294. *
  295. * Then the real roots are:
  296. *
  297. * __ /θ\
  298. * z₁ = 2√-Q cos | - | - ⅓a₂
  299. * \3/
  300. *
  301. * __ /θ + 2π\
  302. * z₂ = 2√-Q cos | ------ | - ⅓a₂
  303. * \ 3 /
  304. *
  305. * __ /θ + 4π\
  306. * z₃ = 2√-Q cos | ------ | - ⅓a₂
  307. * \ 3 /
  308. *
  309. * If D = 0 or D > 0:
  310. * ______
  311. * S ≡ ³√R + √D
  312. * ______
  313. * T ≡ ³√R - √D
  314. *
  315. * If D = 0:
  316. *
  317. * -a₂ S + T
  318. * z₁ = --- - -----
  319. * 3 2
  320. *
  321. * S + T - a₂
  322. * z₂ = ----------
  323. * 3
  324. *
  325. * -a₂ S + T
  326. * z₃ = --- - -----
  327. * 3 2
  328. *
  329. * If D > 0:
  330. *
  331. * S + T - a₂
  332. * z₁ = ----------
  333. * 3
  334. *
  335. * z₂ = Complex conjugate; therefore, NAN
  336. * z₃ = Complex conjugate; therefore, NAN
  337. *
  338. * @param float $a₃ z³ coefficient
  339. * @param float $a₂ z² coefficient
  340. * @param float $a₁ z coefficient
  341. * @param float $a₀ constant coefficient
  342. * @param bool $return_complex whether to return complex numbers
  343. *
  344. * @return float[]|Complex[]
  345. * array of roots (three real roots, or one real root and two NANs because complex numbers not yet supported)
  346. * (If $a₃ = 0, then only two roots of quadratic equation)
  347. *
  348. * @throws Exception\IncorrectTypeException
  349. */
  350. public static function cubic(float $a₃, float $a₂, float $a₁, float $a₀, bool $return_complex = false): array
  351. {
  352. if ($a₃ == 0) {
  353. return self::quadratic($a₂, $a₁, $a₀, $return_complex);
  354. }
  355. // Take coefficient a₃ of z³ to be 1
  356. $a₂ = $a₂ / $a₃;
  357. $a₁ = $a₁ / $a₃;
  358. $a₀ = $a₀ / $a₃;
  359. // Intermediate variables
  360. $Q = (3 * $a₁ - $a₂ ** 2) / 9;
  361. $R = (9 * $a₂ * $a₁ - 27 * $a₀ - 2 * $a₂ ** 3) / 54;
  362. // Polynomial discriminant
  363. $D = $Q ** 3 + $R ** 2;
  364. // All roots are real and unequal
  365. if ($D < 0) {
  366. $θ = \acos($R / \sqrt((-$Q) ** 3));
  367. $2√−Q = 2 * \sqrt(-$Q);
  368. $π = \M_PI;
  369. $z₁ = $2√−Q * \cos($θ / 3) - ($a₂ / 3);
  370. $z₂ = $2√−Q * \cos(($θ + 2 * $π) / 3) - ($a₂ / 3);
  371. $z₃ = $2√−Q * \cos(($θ + 4 * $π) / 3) - ($a₂ / 3);
  372. return [$z₁, $z₂, $z₃];
  373. }
  374. // Intermediate calculations
  375. $S = Arithmetic::cubeRoot($R + \sqrt($D));
  376. $T = Arithmetic::cubeRoot($R - \sqrt($D));
  377. // All roots are real, and at least two are equal
  378. if ($D == 0 || ($D > -self::ZERO_TOLERANCE && $D < self::ZERO_TOLERANCE)) {
  379. $z₁ = -$a₂ / 3 - ($S + $T) / 2;
  380. $z₂ = $S + $T - $a₂ / 3;
  381. $z₃ = -$a₂ / 3 - ($S + $T) / 2;
  382. return [$z₁, $z₂, $z₃];
  383. }
  384. // D > 0: One root is real, and two are are complex conjugates
  385. $z₁ = $S + $T - $a₂ / 3;
  386. if (!$return_complex) {
  387. return [$z₁, \NAN, \NAN];
  388. }
  389. $quad_a = 1;
  390. $quad_b = $a₂ + $z₁;
  391. $quad_c = $a₁ + $quad_b * $z₁;
  392. $complex_roots = self::quadratic($quad_a, $quad_b, $quad_c, true);
  393. return \array_merge([$z₁], $complex_roots);
  394. }
  395. /**
  396. * Quartic equation
  397. * An equation having the form: a₄z⁴ + a₃z³ + a₂z² + a₁z + a₀ = 0
  398. * https://en.wikipedia.org/wiki/Quartic_function
  399. *
  400. * Sometimes this is referred to as a biquadratic equation.
  401. *
  402. * @param float $a₄ z⁴ coefficient
  403. * @param float $a₃ z³ coefficient
  404. * @param float $a₂ z² coefficient
  405. * @param float $a₁ z coefficient
  406. * @param float $a₀ constant coefficient
  407. * @param bool $return_complex whether to return complex numbers
  408. *
  409. * @return float[]|Complex[] array of roots
  410. *
  411. * @throws Exception\IncorrectTypeException
  412. */
  413. public static function quartic(float $a₄, float $a₃, float $a₂, float $a₁, float $a₀, bool $return_complex = false): array
  414. {
  415. // Not actually quartic.
  416. if ($a₄ == 0) {
  417. return self::cubic($a₃, $a₂, $a₁, $a₀, $return_complex);
  418. }
  419. // Take coefficient a₄ of z⁴ to be 1
  420. $a₃ = $a₃ / $a₄;
  421. $a₂ = $a₂ / $a₄;
  422. $a₁ = $a₁ / $a₄;
  423. $a₀ = $a₀ / $a₄;
  424. $a₄ = 1;
  425. // Has a zero root.
  426. if ($a₀ == 0) {
  427. return \array_merge([0.0], self::cubic($a₄, $a₃, $a₂, $a₁, $return_complex));
  428. }
  429. // Is Biquadratic
  430. if ($a₃ == 0 && $a₁ == 0) {
  431. $quadratic_roots = self::quadratic($a₄, $a₂, $a₀, $return_complex);
  432. // Sort so any complex roots are at the end of the array.
  433. \rsort($quadratic_roots);
  434. /** @var array{float, float} $quadratic_roots */
  435. $z₊ = $quadratic_roots[0];
  436. $z₋ = $quadratic_roots[1];
  437. if (!$return_complex) {
  438. return [\sqrt($z₊), -1 * \sqrt($z₊), \sqrt($z₋), -1 * \sqrt($z₋)];
  439. }
  440. $Cz₊ = new Complex($z₊, 0);
  441. $Cz₋ = new Complex($z₋, 0);
  442. $z₁ = $z₊ < 0 ? $Cz₊->sqrt() : \sqrt($z₊);
  443. // @phpstan-ignore-next-line
  444. $z₂ = $z₊ < 0 ? $z₁->negate() : $z₁ * -1;
  445. $z₃ = $z₋ < 0 ? $Cz₋->sqrt() : \sqrt($z₋);
  446. // @phpstan-ignore-next-line
  447. $z₄ = $z₋ < 0 ? $z₃->negate() : $z₃ * -1;
  448. return [$z₁, $z₂, $z₃, $z₄];
  449. }
  450. // Is a depressed quartic
  451. // y⁴ + py² + qy + r = 0
  452. if ($a₃ == 0) {
  453. $p = $a₂;
  454. $q = $a₁;
  455. $r = $a₀;
  456. // Create the resolvent cubic.
  457. // 8m³ + 8pm² + (2p² - 8r)m - q² = 0
  458. $cubic_roots = self::cubic(8, 8 * $p, 2 * $p ** 2 - 8 * $r, -1 * $q ** 2, $return_complex);
  459. // $z₁ will always be a real number, so select it.
  460. $m = $cubic_roots[0];
  461. // @phpstan-ignore-next-line (because $m is real)
  462. $roots1 = self::quadratic(1, \sqrt(2 * $m), $p / 2 + $m - $q / 2 / \sqrt(2 * $m), $return_complex);
  463. // @phpstan-ignore-next-line (because $m is real)
  464. $roots2 = self::quadratic(1, -1 * \sqrt(2 * $m), $p / 2 + $m + $q / 2 / \sqrt(2 * $m), $return_complex);
  465. // @phpstan-ignore-next-line (because $m is real)
  466. $discriminant1 = self::discriminant(1, \sqrt(2 * $m), $p / 2 + $m - $q / 2 / \sqrt(2 * $m));
  467. // @phpstan-ignore-next-line (because $m is real)
  468. $discriminant2 = self::discriminant(1, -1 * \sqrt(2 * $m), $p / 2 + $m + $q / 2 / \sqrt(2 * $m));
  469. // sort the real roots first.
  470. $sorted_results = $discriminant1 > $discriminant2
  471. ? \array_merge($roots1, $roots2)
  472. : \array_merge($roots2, $roots1);
  473. return $sorted_results;
  474. }
  475. // Create the factors for a depressed quartic.
  476. $p = $a₂ - (3 * $a₃ ** 2 / 8);
  477. $q = $a₁ + $a₃ ** 3 / 8 - $a₃ * $a₂ / 2;
  478. $r = $a₀ - 3 * $a₃ ** 4 / 256 + $a₃ ** 2 * $a₂ / 16 - $a₃ * $a₁ / 4;
  479. $depressed_quartic_roots = self::quartic(1, 0, $p, $q, $r, $return_complex);
  480. // The roots for this polynomial are the roots of the depressed polynomial minus a₃/4.
  481. if (!$return_complex) {
  482. /**
  483. * @phpstan-ignore-next-line (Single::subtract() works with real numbers only, must be real roots)
  484. * @psalm-suppress InvalidArgument
  485. */
  486. return Single::subtract($depressed_quartic_roots, $a₃ / 4);
  487. }
  488. $quartic_roots = [];
  489. foreach ($depressed_quartic_roots as $key => $root) {
  490. if (\is_float($root)) {
  491. $quartic_roots[$key] = $root - $a₃ / 4;
  492. } else {
  493. $quartic_roots[$key] = $root->subtract($a₃ / 4);
  494. }
  495. }
  496. return $quartic_roots;
  497. }
  498. }