| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- <?php
- namespace MathPHP\Number;
- use MathPHP\Exception;
- /**
- * Quaternionic Numbers
- *
- * A quaternion is a number that can be expressed in the form a + bi + cj + dk,
- * where a, b, c, andd are real numbers and i, j, and k are the basic quaternions, satisfying the
- * equation i² = j² = k² = ijk = −1.
- * https://en.wikipedia.org/wiki/Quaternion
- */
- class Quaternion implements ObjectArithmetic
- {
- /** @var int|float Real part of the quaternionic number */
- protected $r;
- /** @var int|float First Imaginary part of the quaternionic number */
- protected $i;
- /** @var int|float Second Imaginary part of the quaternionic number */
- protected $j;
- /** @var int|float Third Imaginary part of the quaternionic number */
- protected $k;
- /** Floating-point range near zero to consider insignificant */
- const EPSILON = 1e-6;
- /**
- * @param int|float $r Real part
- * @param int|float $i Imaginary part
- * @param int|float $j Imaginary part
- * @param int|float $k Imaginary part
- */
- public function __construct($r, $i, $j, $k)
- {
- if (!\is_numeric($r) || !\is_numeric($i) || !\is_numeric($j) || !\is_numeric($k)) {
- throw new Exception\BadDataException('Values must be real numbers.');
- }
- $this->r = $r;
- $this->i = $i;
- $this->j = $j;
- $this->k = $k;
- }
- /**
- * Creates 0 + 0i
- *
- * @return Quaternion
- */
- public static function createZeroValue(): ObjectArithmetic
- {
- return new Quaternion(0, 0, 0, 0);
- }
- /**
- * String representation of a complex number
- * a + bi + cj + dk, a - bi - cj - dk, etc.
- *
- * @return string
- */
- public function __toString(): string
- {
- if ($this->r == 0 & $this->i == 0 & $this->j == 0 & $this->k == 0) {
- return '0';
- }
- $string = self::stringifyNumberPart($this->r);
- $string = self::stringifyNumberPart($this->i, 'i', $string);
- $string = self::stringifyNumberPart($this->j, 'j', $string);
- return self::stringifyNumberPart($this->k, 'k', $string);
- }
- /**
- * Get r or i or j or k
- *
- * @param string $part
- *
- * @return int|float
- *
- * @throws Exception\BadParameterException if something other than r or i is attempted
- */
- public function __get(string $part)
- {
- switch ($part) {
- case 'r':
- case 'i':
- case 'j':
- case 'k':
- return $this->$part;
- default:
- throw new Exception\BadParameterException("The $part property does not exist in Quaternion");
- }
- }
- /**************************************************************************
- * UNARY FUNCTIONS
- **************************************************************************/
- /**
- * The conjugate of a quaternion
- * https://en.wikipedia.org/wiki/Quaternion#Conjugation.2C_the_norm.2C_and_reciprocal
- *
- * q* = a - bi - cj -dk
- *
- * @return Quaternion
- */
- public function complexConjugate(): Quaternion
- {
- return new Quaternion($this->r, -$this->i, -$this->j, -$this->k);
- }
- /**
- * The absolute value (magnitude) of a quaternion or norm
- * https://en.wikipedia.org/wiki/Quaternion#Conjugation.2C_the_norm.2C_and_reciprocal
- *
- * If z = a + bi + cj + dk
- * _________________
- * |z| = √a² + b² + c² + d²
- *
- * @return int|float
- */
- public function abs()
- {
- return sqrt($this->r**2 + $this->i**2 + $this->j**2 + $this->k**2);
- }
- /**
- * The inverse of a quaternion (reciprocal)
- *
- * https://en.wikipedia.org/wiki/Quaternion#Conjugation.2C_the_norm.2C_and_reciprocal
- *
- * 1
- * (a + bi + cj + dk)⁻¹ = ----------------- (a - bi - cj -dk)
- * a² + b² + c² + d²
- *
- * @return Quaternion
- *
- * @throws Exception\BadDataException if = to 0 + 0i
- */
- public function inverse(): Quaternion
- {
- if ($this->r == 0 && $this->i == 0 && $this->j == 0 && $this->k == 0) {
- throw new Exception\BadDataException('Cannot take inverse of 0 + 0i');
- }
- return $this->complexConjugate()->divide($this->abs() ** 2);
- }
- /**
- * Negate the quaternion
- * Switches the signs of both the real and imaginary parts.
- *
- * @return Quaternion
- */
- public function negate(): Quaternion
- {
- return new Quaternion(-$this->r, -$this->i, -$this->j, -$this->k);
- }
- /**************************************************************************
- * BINARY FUNCTIONS
- **************************************************************************/
- /**
- * Quaternion addition
- *
- *
- * (a + bi + cj + dk) - (e + fi + gj + hk) = (a + e) + (b + f)i + (c + g)j + (d + h)k
- *
- * @param int|float|Quaternion $q
- *
- * @return Quaternion
- *
- * @throws Exception\IncorrectTypeException if the argument is not numeric or Complex.
- */
- public function add($q): Quaternion
- {
- if (!\is_numeric($q) && ! $q instanceof Quaternion) {
- throw new Exception\IncorrectTypeException('Argument must be real or quaternion' . \print_r($q, true));
- }
- if (\is_numeric($q)) {
- $r = $this->r + $q;
- return new Quaternion($r, $this->i, $this->j, $this->k);
- }
- $r = $this->r + $q->r;
- $i = $this->i + $q->i;
- $j = $this->j + $q->j;
- $k = $this->k + $q->k;
- return new Quaternion($r, $i, $j, $k);
- }
- /**
- * Quaternion subtraction
- *
- *
- * (a + bi + cj + dk) - (e + fi + gj + hk) = (a - e) + (b - f)i + (c - g)j + (d - h)k
- *
- * @param int|float|Quaternion $q
- *
- * @return Quaternion
- *
- * @throws Exception\IncorrectTypeException if the argument is not numeric or Complex.
- */
- public function subtract($q): Quaternion
- {
- if (!is_numeric($q) && ! $q instanceof Quaternion) {
- throw new Exception\IncorrectTypeException('Argument must be real or quaternion' . print_r($q, true));
- }
- if (\is_numeric($q)) {
- $r = $this->r - $q;
- return new Quaternion($r, $this->i, $this->j, $this->k);
- }
- $r = $this->r - $q->r;
- $i = $this->i - $q->i;
- $j = $this->j - $q->j;
- $k = $this->k - $q->k;
- return new Quaternion($r, $i, $j, $k);
- }
- /**
- * Quaternion multiplication (Hamilton product)
- *
- * (a₁ + b₁i - c₁j - d₁k)(a₂ + b₂i + c₂j + d₂k)
- *
- * a₁a₂ - b₁b₂ - c₁c₂ - d₁d₂
- * + (b₁a₂ + a₁b₂ + c₁d₂ - d₁c₂)i
- * + (a₁c₂ - b₁d₂ + c₁a₂ + d₁b₂)k
- * + (a₁d₂ + b₁c₂ - c₁b₂ + d₁a₂)k
- *
- * Note: Quaternion multiplication is not commutative.
- *
- * @param int|float|Quaternion $q
- *
- * @return Quaternion
- *
- * @throws Exception\IncorrectTypeException if the argument is not numeric or Complex.
- */
- public function multiply($q): Quaternion
- {
- if (!is_numeric($q) && ! $q instanceof Quaternion) {
- throw new Exception\IncorrectTypeException('Argument must be real or quaternion' . print_r($q, true));
- }
- if (\is_numeric($q)) {
- return new Quaternion($this->r * $q, $this->i * $q, $this->j * $q, $this->k * $q);
- }
- [$a₁, $b₁, $c₁, $d₁] = [$this->r, $this->i, $this->j, $this->k];
- [$a₂, $b₂, $c₂, $d₂] = [$q->r, $q->i, $q->j, $q->k];
- return new Quaternion(
- $a₁*$a₂ - $b₁*$b₂ - $c₁*$c₂ - $d₁*$d₂,
- $b₁*$a₂ + $a₁*$b₂ + $c₁*$d₂ - $d₁*$c₂,
- $a₁*$c₂ - $b₁*$d₂ + $c₁*$a₂ + $d₁*$b₂,
- $a₁*$d₂ + $b₁*$c₂ - $c₁*$b₂ + $d₁*$a₂
- );
- }
- /**
- * Quaternion division
- * Dividing two quaternions is accomplished by multiplying the first by the inverse of the second
- * This is not commutative!
- *
- * @param int|float|Quaternion $q
- *
- * @return Quaternion
- *
- * @throws Exception\IncorrectTypeException if the argument is not numeric or Complex.
- */
- public function divide($q): Quaternion
- {
- if (!is_numeric($q) && ! $q instanceof Quaternion) {
- throw new Exception\IncorrectTypeException('Argument must be real or quaternion' . print_r($q, true));
- }
- if (\is_numeric($q)) {
- $r = $this->r / $q;
- $i = $this->i / $q;
- $j = $this->j / $q;
- $k = $this->k / $q;
- return new Quaternion($r, $i, $j, $k);
- }
- return $this->multiply($q->inverse());
- }
- /**************************************************************************
- * COMPARISON FUNCTIONS
- **************************************************************************/
- /**
- * Test for equality
- * Two quaternions are equal if and only if both their real and imaginary parts are equal.
- *
- *
- * @param Quaternion $q
- *
- * @return bool
- */
- public function equals(Quaternion $q): bool
- {
- return \abs($this->r - $q->r) < self::EPSILON && \abs($this->i - $q->i) < self::EPSILON
- && \abs($this->j - $q->j) < self::EPSILON && \abs($this->k - $q->k) < self::EPSILON;
- }
- /**************************************************************************
- * PRIVATE FUNCTIONS
- **************************************************************************/
- /**
- * Stringify an additional part of the quaternion
- *
- * @param int|float $q
- * @param string $unit
- * @param string $string
- * @return string
- */
- private static function stringifyNumberPart($q, string $unit = '', string $string = ''): string
- {
- if ($q == 0) {
- return $string;
- }
- if ($q > 0) {
- $plus = $string == '' ? '' : ' + ';
- return $string . $plus . "$q" . $unit;
- }
- $minus = $string == '' ? '-' : ' - ';
- return $string . $minus . (string) \abs($q) . $unit;
- }
- }
|