Файловый менеджер - Редактировать - /home/clickysoft/public_html/jmapi5.clickysoft.net/markbaker.tar
Назад
complex/classes/src/Complex.php 0000644 00000026017 15021223666 0012570 0 ustar 00 <?php /** * * Class for the management of Complex numbers * * @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPComplex) * @license https://opensource.org/licenses/MIT MIT */ namespace Complex; /** * Complex Number object. * * @package Complex * * @method float abs() * @method Complex acos() * @method Complex acosh() * @method Complex acot() * @method Complex acoth() * @method Complex acsc() * @method Complex acsch() * @method float argument() * @method Complex asec() * @method Complex asech() * @method Complex asin() * @method Complex asinh() * @method Complex atan() * @method Complex atanh() * @method Complex conjugate() * @method Complex cos() * @method Complex cosh() * @method Complex cot() * @method Complex coth() * @method Complex csc() * @method Complex csch() * @method Complex exp() * @method Complex inverse() * @method Complex ln() * @method Complex log2() * @method Complex log10() * @method Complex negative() * @method Complex pow(int|float $power) * @method float rho() * @method Complex sec() * @method Complex sech() * @method Complex sin() * @method Complex sinh() * @method Complex sqrt() * @method Complex tan() * @method Complex tanh() * @method float theta() * @method Complex add(...$complexValues) * @method Complex subtract(...$complexValues) * @method Complex multiply(...$complexValues) * @method Complex divideby(...$complexValues) * @method Complex divideinto(...$complexValues) */ class Complex { /** * @constant Euler's Number. */ const EULER = 2.7182818284590452353602874713526624977572; /** * @constant Regexp to split an input string into real and imaginary components and suffix */ const NUMBER_SPLIT_REGEXP = '` ^ ( # Real part [-+]?(\d+\.?\d*|\d*\.?\d+) # Real value (integer or float) ([Ee][-+]?[0-2]?\d{1,3})? # Optional real exponent for scientific format ) ( # Imaginary part [-+]?(\d+\.?\d*|\d*\.?\d+) # Imaginary value (integer or float) ([Ee][-+]?[0-2]?\d{1,3})? # Optional imaginary exponent for scientific format )? ( # Imaginary part is optional ([-+]?) # Imaginary (implicit 1 or -1) only ([ij]?) # Imaginary i or j - depending on whether mathematical or engineering ) $`uix'; /** * @var float $realPart The value of of this complex number on the real plane. */ protected $realPart = 0.0; /** * @var float $imaginaryPart The value of of this complex number on the imaginary plane. */ protected $imaginaryPart = 0.0; /** * @var string $suffix The suffix for this complex number (i or j). */ protected $suffix; /** * Validates whether the argument is a valid complex number, converting scalar or array values if possible * * @param mixed $complexNumber The value to parse * @return array * @throws Exception If the argument isn't a Complex number or cannot be converted to one */ private static function parseComplex($complexNumber) { // Test for real number, with no imaginary part if (is_numeric($complexNumber)) { return [$complexNumber, 0, null]; } // Fix silly human errors $complexNumber = str_replace( ['+-', '-+', '++', '--'], ['-', '-', '+', '+'], $complexNumber ); // Basic validation of string, to parse out real and imaginary parts, and any suffix $validComplex = preg_match( self::NUMBER_SPLIT_REGEXP, $complexNumber, $complexParts ); if (!$validComplex) { // Neither real nor imaginary part, so test to see if we actually have a suffix $validComplex = preg_match('/^([\-\+]?)([ij])$/ui', $complexNumber, $complexParts); if (!$validComplex) { throw new Exception('Invalid complex number'); } // We have a suffix, so set the real to 0, the imaginary to either 1 or -1 (as defined by the sign) $imaginary = 1; if ($complexParts[1] === '-') { $imaginary = 0 - $imaginary; } return [0, $imaginary, $complexParts[2]]; } // If we don't have an imaginary part, identify whether it should be +1 or -1... if (($complexParts[4] === '') && ($complexParts[9] !== '')) { if ($complexParts[7] !== $complexParts[9]) { $complexParts[4] = 1; if ($complexParts[8] === '-') { $complexParts[4] = -1; } } else { // ... or if we have only the real and no imaginary part // (in which case our real should be the imaginary) $complexParts[4] = $complexParts[1]; $complexParts[1] = 0; } } // Return real and imaginary parts and suffix as an array, and set a default suffix if user input lazily return [ $complexParts[1], $complexParts[4], !empty($complexParts[9]) ? $complexParts[9] : 'i' ]; } public function __construct($realPart = 0.0, $imaginaryPart = null, $suffix = 'i') { if ($imaginaryPart === null) { if (is_array($realPart)) { // We have an array of (potentially) real and imaginary parts, and any suffix list ($realPart, $imaginaryPart, $suffix) = array_values($realPart) + [0.0, 0.0, 'i']; } elseif ((is_string($realPart)) || (is_numeric($realPart))) { // We've been given a string to parse to extract the real and imaginary parts, and any suffix list($realPart, $imaginaryPart, $suffix) = self::parseComplex($realPart); } } if ($imaginaryPart != 0.0 && empty($suffix)) { $suffix = 'i'; } elseif ($imaginaryPart == 0.0 && !empty($suffix)) { $suffix = ''; } // Set parsed values in our properties $this->realPart = (float) $realPart; $this->imaginaryPart = (float) $imaginaryPart; $this->suffix = strtolower($suffix ?? ''); } /** * Gets the real part of this complex number * * @return Float */ public function getReal(): float { return $this->realPart; } /** * Gets the imaginary part of this complex number * * @return Float */ public function getImaginary(): float { return $this->imaginaryPart; } /** * Gets the suffix of this complex number * * @return String */ public function getSuffix(): string { return $this->suffix; } /** * Returns true if this is a real value, false if a complex value * * @return Bool */ public function isReal(): bool { return $this->imaginaryPart == 0.0; } /** * Returns true if this is a complex value, false if a real value * * @return Bool */ public function isComplex(): bool { return !$this->isReal(); } public function format(): string { $str = ""; if ($this->imaginaryPart != 0.0) { if (\abs($this->imaginaryPart) != 1.0) { $str .= $this->imaginaryPart . $this->suffix; } else { $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; } } if ($this->realPart != 0.0) { if (($str) && ($this->imaginaryPart > 0.0)) { $str = "+" . $str; } $str = $this->realPart . $str; } if (!$str) { $str = "0.0"; } return $str; } public function __toString(): string { return $this->format(); } /** * Validates whether the argument is a valid complex number, converting scalar or array values if possible * * @param mixed $complex The value to validate * @return Complex * @throws Exception If the argument isn't a Complex number or cannot be converted to one */ public static function validateComplexArgument($complex): Complex { if (is_scalar($complex) || is_array($complex)) { $complex = new Complex($complex); } elseif (!is_object($complex) || !($complex instanceof Complex)) { throw new Exception('Value is not a valid complex number'); } return $complex; } /** * Returns the reverse of this complex number * * @return Complex */ public function reverse(): Complex { return new Complex( $this->imaginaryPart, $this->realPart, ($this->realPart == 0.0) ? null : $this->suffix ); } public function invertImaginary(): Complex { return new Complex( $this->realPart, $this->imaginaryPart * -1, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } public function invertReal(): Complex { return new Complex( $this->realPart * -1, $this->imaginaryPart, ($this->imaginaryPart == 0.0) ? null : $this->suffix ); } protected static $functions = [ 'abs', 'acos', 'acosh', 'acot', 'acoth', 'acsc', 'acsch', 'argument', 'asec', 'asech', 'asin', 'asinh', 'atan', 'atanh', 'conjugate', 'cos', 'cosh', 'cot', 'coth', 'csc', 'csch', 'exp', 'inverse', 'ln', 'log2', 'log10', 'negative', 'pow', 'rho', 'sec', 'sech', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'theta', ]; protected static $operations = [ 'add', 'subtract', 'multiply', 'divideby', 'divideinto', ]; /** * Returns the result of the function call or operation * * @return Complex|float * @throws Exception|\InvalidArgumentException */ public function __call($functionName, $arguments) { $functionName = strtolower(str_replace('_', '', $functionName)); // Test for function calls if (in_array($functionName, self::$functions, true)) { return Functions::$functionName($this, ...$arguments); } // Test for operation calls if (in_array($functionName, self::$operations, true)) { return Operations::$functionName($this, ...$arguments); } throw new Exception('Complex Function or Operation does not exist'); } } complex/classes/src/Exception.php 0000644 00000000361 15021223666 0013111 0 ustar 00 <?php /** * Exception. * * @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPComplex) * @license https://opensource.org/licenses/MIT MIT */ namespace Complex; class Exception extends \Exception { } complex/classes/src/Operations.php 0000644 00000016120 15021223666 0013276 0 ustar 00 <?php namespace Complex; use InvalidArgumentException; class Operations { /** * Adds two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to add * @return Complex */ public static function add(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() + $complex->getReal(); $imaginary = $result->getImaginary() + $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideby(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($result->getReal() * $complex->getReal()) + ($result->getImaginary() * $complex->getImaginary()); $delta2 = ($result->getImaginary() * $complex->getReal()) - ($result->getReal() * $complex->getImaginary()); $delta3 = ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Divides two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to divide * @return Complex */ public static function divideinto(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } $delta1 = ($complex->getReal() * $result->getReal()) + ($complex->getImaginary() * $result->getImaginary()); $delta2 = ($complex->getImaginary() * $result->getReal()) - ($complex->getReal() * $result->getImaginary()); $delta3 = ($result->getReal() * $result->getReal()) + ($result->getImaginary() * $result->getImaginary()); $real = $delta1 / $delta3; $imaginary = $delta2 / $delta3; $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Multiplies two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to multiply * @return Complex */ public static function multiply(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = ($result->getReal() * $complex->getReal()) - ($result->getImaginary() * $complex->getImaginary()); $imaginary = ($result->getReal() * $complex->getImaginary()) + ($result->getImaginary() * $complex->getReal()); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } /** * Subtracts two or more complex numbers * * @param array of string|integer|float|Complex $complexValues The numbers to subtract * @return Complex */ public static function subtract(...$complexValues): Complex { if (count($complexValues) < 2) { throw new \Exception('This function requires at least 2 arguments'); } $base = array_shift($complexValues); $result = clone Complex::validateComplexArgument($base); foreach ($complexValues as $complex) { $complex = Complex::validateComplexArgument($complex); if ($result->isComplex() && $complex->isComplex() && $result->getSuffix() !== $complex->getSuffix()) { throw new Exception('Suffix Mismatch'); } $real = $result->getReal() - $complex->getReal(); $imaginary = $result->getImaginary() - $complex->getImaginary(); $result = new Complex( $real, $imaginary, ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) ); } return $result; } } complex/classes/src/Functions.php 0000644 00000073501 15021223666 0013131 0 ustar 00 <?php namespace Complex; use InvalidArgumentException; class Functions { /** * Returns the absolute value (modulus) of a complex number. * Also known as the rho of the complex number, i.e. the distance/radius * from the centrepoint to the representation of the number in polar coordinates. * * This function is a synonym for rho() * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The absolute (or rho) value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see rho * */ public static function abs($complex): float { return self::rho($complex); } /** * Returns the inverse cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function acos($complex): Complex { $complex = Complex::validateComplexArgument($complex); $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex))); $adjust = new Complex( $complex->getReal() - $invsqrt->getImaginary(), $complex->getImaginary() + $invsqrt->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic cosine of a complex number. * * Formula from Wolfram Alpha: * cosh^(-1)z = ln(z + sqrt(z + 1) sqrt(z - 1)). * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function acosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\acosh($complex->getReal())); } $acosh = self::ln( Operations::add( $complex, Operations::multiply( self::sqrt(Operations::add($complex, 1)), self::sqrt(Operations::subtract($complex, 1)) ) ) ); return $acosh; } /** * Returns the inverse cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acot($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atan(self::inverse($complex)); } /** * Returns the inverse hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acoth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::atanh(self::inverse($complex)); } /** * Returns the inverse cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asin(self::inverse($complex)); } /** * Returns the inverse hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function acsch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::asinh(self::inverse($complex)); } /** * Returns the argument of a complex number. * Also known as the theta of the complex number, i.e. the angle in radians * from the real axis to the representation of the number in polar coordinates. * * This function is a synonym for theta() * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The argument (or theta) value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see theta */ public static function argument($complex): float { return self::theta($complex); } /** * Returns the inverse secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asec($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acos(self::inverse($complex)); } /** * Returns the inverse hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function asech($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::acosh(self::inverse($complex)); } /** * Returns the inverse sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asin($complex): Complex { $complex = Complex::validateComplexArgument($complex); $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex))); $adjust = new Complex( $invsqrt->getReal() - $complex->getImaginary(), $invsqrt->getImaginary() + $complex->getReal() ); $log = self::ln($adjust); return new Complex( $log->getImaginary(), -1 * $log->getReal() ); } /** * Returns the inverse hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function asinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal() && ($complex->getReal() > 1)) { return new Complex(\asinh($complex->getReal())); } $asinh = clone $complex; $asinh = $asinh->reverse() ->invertReal(); $asinh = self::asin($asinh); return $asinh->reverse() ->invertImaginary(); } /** * Returns the inverse tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function atan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\atan($complex->getReal())); } $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); $uValue = new Complex(1, 0); $d1Value = clone $uValue; $d1Value = Operations::subtract($d1Value, $t1Value); $d2Value = Operations::add($t1Value, $uValue); $uResult = $d1Value->divideBy($d2Value); $uResult = self::ln($uResult); $realMultiplier = -0.5; $imaginaryMultiplier = 0.5; if (abs($uResult->getImaginary()) === M_PI) { // If we have an imaginary value at the max or min (PI or -PI), then we need to ensure // that the primary is assigned for the correct quadrant. $realMultiplier = ( ($uResult->getImaginary() === M_PI && $uResult->getReal() > 0.0) || ($uResult->getImaginary() === -M_PI && $uResult->getReal() < 0.0) ) ? 0.5 : -0.5; } return new Complex( $uResult->getImaginary() * $realMultiplier, $uResult->getReal() * $imaginaryMultiplier, $complex->getSuffix() ); } /** * Returns the inverse hyperbolic tangent of a complex number. * * Formula from Wolfram Alpha: * tanh^(-1)z = 1/2 [ln(1 + z) - ln(1 - z)]. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function atanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { $real = $complex->getReal(); if ($real >= -1.0 && $real <= 1.0) { return new Complex(\atanh($real)); } else { return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); } } $atanh = Operations::multiply( Operations::subtract( self::ln(Operations::add(1.0, $complex)), self::ln(Operations::subtract(1.0, $complex)) ), 0.5 ); return $atanh; } /** * Returns the complex conjugate of a complex number * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The conjugate of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function conjugate($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns the cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cos($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cos($complex->getReal())); } return self::conjugate( new Complex( \cos($complex->getReal()) * \cosh($complex->getImaginary()), \sin($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ) ); } /** * Returns the hyperbolic cosine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function cosh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\cosh($complex->getReal())); } return new Complex( \cosh($complex->getReal()) * \cos($complex->getImaginary()), \sinh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function cot($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::tan($complex)); } /** * Returns the hyperbolic cotangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cotangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function coth($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::tanh($complex)); } /** * Returns the cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csc($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sin($complex)); } /** * Returns the hyperbolic cosecant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic cosecant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function csch($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { return new Complex(INF); } return self::inverse(self::sinh($complex)); } /** * Returns the exponential of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The exponential of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function exp($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { return new Complex(-1.0, 0.0); } $rho = \exp($complex->getReal()); return new Complex( $rho * \cos($complex->getImaginary()), $rho * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the inverse of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The inverse of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function inverse($complex): Complex { $complex = clone Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { throw new InvalidArgumentException('Division by zero'); } return $complex->divideInto(1.0); } /** * Returns the natural logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The natural logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function ln($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } return new Complex( \log(self::rho($complex)), self::theta($complex), $complex->getSuffix() ); } /** * Returns the base-2 logarithm of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The base-2 logarithm of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log2($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log(Complex::EULER, 2)); } /** * Returns the common logarithm (base 10) of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The common logarithm (base 10) of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If the real and the imaginary parts are both zero */ public static function log10($complex): Complex { $complex = Complex::validateComplexArgument($complex); if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { throw new InvalidArgumentException(); } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); } return self::ln($complex) ->multiply(\log10(Complex::EULER)); } /** * Returns the negative of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The negative value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * * @see rho * */ public static function negative($complex): Complex { $complex = Complex::validateComplexArgument($complex); return new Complex( -1 * $complex->getReal(), -1 * $complex->getImaginary(), $complex->getSuffix() ); } /** * Returns a complex number raised to a power. * * @param Complex|mixed $complex Complex number or a numeric value. * @param float|integer $power The power to raise this value to * @return Complex The complex argument raised to the real power. * @throws Exception If the power argument isn't a valid real */ public static function pow($complex, $power): Complex { $complex = Complex::validateComplexArgument($complex); if (!is_numeric($power)) { throw new Exception('Power argument must be a real number'); } if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) { return new Complex(\pow($complex->getReal(), $power)); } $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); $rPower = \pow($rValue, $power); $theta = $complex->argument() * $power; if ($theta == 0) { return new Complex(1); } return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); } /** * Returns the rho of a complex number. * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The rho value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function rho($complex): float { $complex = Complex::validateComplexArgument($complex); return \sqrt( ($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary()) ); } /** * Returns the secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sec($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cos($complex)); } /** * Returns the hyperbolic secant of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic secant of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function sech($complex): Complex { $complex = Complex::validateComplexArgument($complex); return self::inverse(self::cosh($complex)); } /** * Returns the sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sin($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sin($complex->getReal())); } return new Complex( \sin($complex->getReal()) * \cosh($complex->getImaginary()), \cos($complex->getReal()) * \sinh($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the hyperbolic sine of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic sine of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sinh($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\sinh($complex->getReal())); } return new Complex( \sinh($complex->getReal()) * \cos($complex->getImaginary()), \cosh($complex->getReal()) * \sin($complex->getImaginary()), $complex->getSuffix() ); } /** * Returns the square root of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The Square root of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function sqrt($complex): Complex { $complex = Complex::validateComplexArgument($complex); $theta = self::theta($complex); $delta1 = \cos($theta / 2); $delta2 = \sin($theta / 2); $rho = \sqrt(self::rho($complex)); return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix()); } /** * Returns the tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws InvalidArgumentException If function would result in a division by zero */ public static function tan($complex): Complex { $complex = Complex::validateComplexArgument($complex); if ($complex->isReal()) { return new Complex(\tan($complex->getReal())); } $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the hyperbolic tangent of a complex number. * * @param Complex|mixed $complex Complex number or a numeric value. * @return Complex The hyperbolic tangent of the complex argument. * @throws Exception If argument isn't a valid real or complex number. * @throws \InvalidArgumentException If function would result in a division by zero */ public static function tanh($complex): Complex { $complex = Complex::validateComplexArgument($complex); $real = $complex->getReal(); $imaginary = $complex->getImaginary(); $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); if ($divisor == 0.0) { throw new InvalidArgumentException('Division by zero'); } return new Complex( \sinh($real) * \cosh($real) / $divisor, 0.5 * \sin(2 * $imaginary) / $divisor, $complex->getSuffix() ); } /** * Returns the theta of a complex number. * This is the angle in radians from the real axis to the representation of the number in polar coordinates. * * @param Complex|mixed $complex Complex number or a numeric value. * @return float The theta value of the complex argument. * @throws Exception If argument isn't a valid real or complex number. */ public static function theta($complex): float { $complex = Complex::validateComplexArgument($complex); if ($complex->getReal() == 0.0) { if ($complex->isReal()) { return 0.0; } elseif ($complex->getImaginary() < 0.0) { return M_PI / -2; } return M_PI / 2; } elseif ($complex->getReal() > 0.0) { return \atan($complex->getImaginary() / $complex->getReal()); } elseif ($complex->getImaginary() < 0.0) { return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); } return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); } } complex/examples/complexTest.php 0000644 00000005474 15021223666 0013066 0 ustar 00 <?php use Complex\Complex as Complex; include(__DIR__ . '/../vendor/autoload.php'); echo 'Create', PHP_EOL; $x = new Complex(123); echo $x, PHP_EOL; $x = new Complex(123, 456); echo $x, PHP_EOL; $x = new Complex(array(123,456,'j')); echo $x, PHP_EOL; $x = new Complex('1.23e-4--2.34e-5i'); echo $x, PHP_EOL; echo PHP_EOL, 'Add', PHP_EOL; $x = new Complex(123); $x->add(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->add(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->add(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->add(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->add(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->add(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Subtract', PHP_EOL; $x = new Complex(123); $x->subtract(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->subtract(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->subtract(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->subtract(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->subtract(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->subtract(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Multiply', PHP_EOL; $x = new Complex(123); $x->multiply(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->multiply(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->multiply(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->multiply(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->multiply(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->multiply(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Divide By', PHP_EOL; $x = new Complex(123); $x->divideBy(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->divideBy(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideBy(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideBy(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideBy(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideBy(new Complex(0, -1)); echo $x, PHP_EOL; echo PHP_EOL, 'Divide Into', PHP_EOL; $x = new Complex(123); $x->divideInto(456); echo $x, PHP_EOL; $x = new Complex(123.456); $x->divideInto(789.012); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideInto(new Complex(-987.654, -32.1)); echo $x, PHP_EOL; $x = new Complex(123.456, 78.90); $x->divideInto(-987.654); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideInto(new Complex(0, 1)); echo $x, PHP_EOL; $x = new Complex(-987.654, -32.1); $x->divideInto(new Complex(0, -1)); echo $x, PHP_EOL; complex/examples/testFunctions.php 0000644 00000002027 15021223666 0013416 0 ustar 00 <?php namespace Complex; include(__DIR__ . '/../vendor/autoload.php'); echo 'Function Examples', PHP_EOL; $functions = array( 'abs', 'acos', 'acosh', 'acsc', 'acsch', 'argument', 'asec', 'asech', 'asin', 'asinh', 'conjugate', 'cos', 'cosh', 'csc', 'csch', 'exp', 'inverse', 'ln', 'log2', 'log10', 'rho', 'sec', 'sech', 'sin', 'sinh', 'sqrt', 'theta' ); for ($real = -3.5; $real <= 3.5; $real += 0.5) { for ($imaginary = -3.5; $imaginary <= 3.5; $imaginary += 0.5) { foreach ($functions as $function) { $complexFunction = __NAMESPACE__ . '\\Functions::' . $function; $complex = new Complex($real, $imaginary); try { echo $function, '(', $complex, ') = ', $complexFunction($complex), PHP_EOL; } catch (\Exception $e) { echo $function, '(', $complex, ') ERROR: ', $e->getMessage(), PHP_EOL; } } echo PHP_EOL; } } complex/examples/testOperations.php 0000644 00000001131 15021223666 0013564 0 ustar 00 <?php use Complex\Complex as Complex; use Complex\Operations; include(__DIR__ . '/../vendor/autoload.php'); $values = [ new Complex(123), new Complex(456, 123), new Complex(0.0, 456), ]; foreach ($values as $value) { echo $value, PHP_EOL; } echo 'Addition', PHP_EOL; $result = Operations::add(...$values); echo '=> ', $result, PHP_EOL; echo PHP_EOL; echo 'Subtraction', PHP_EOL; $result = Operations::subtract(...$values); echo '=> ', $result, PHP_EOL; echo PHP_EOL; echo 'Multiplication', PHP_EOL; $result = Operations::multiply(...$values); echo '=> ', $result, PHP_EOL; complex/.github/workflows/main.yml 0000644 00000011437 15021223666 0013270 0 ustar 00 name: main on: [ push, pull_request ] jobs: test: runs-on: ubuntu-latest strategy: matrix: experimental: - false php-version: - '7.2' - '7.3' - '7.4' - '8.0' - '8.1' - '8.2' include: - php-version: 'nightly' experimental: true name: PHP ${{ matrix.php-version }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} coverage: none - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Delete composer lock file id: composer-lock if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} run: | rm composer.lock echo "::set-output name=flags::--ignore-platform-reqs" - name: Install dependencies run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - name: Setup problem matchers for PHPUnit run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" env: FAILURE_ACTION: "${{ matrix.experimental == true }}" run: vendor/bin/phpunit --verbose || $FAILURE_ACTION phpcs: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none tools: cs2pr - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report=checkstyle classes/src/ | cs2pr versions: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: none tools: cs2pr - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code Version Compatibility check with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 7.2- coverage: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 coverage: pcov - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Test Coverage run: ./vendor/bin/phpunit --verbose --coverage-text complex/composer.json 0000644 00000002410 15021223666 0010735 0 ustar 00 { "name": "markbaker/complex", "type": "library", "description": "PHP Class for working with complex numbers", "keywords": ["complex", "mathematics"], "homepage": "https://github.com/MarkBaker/PHPComplex", "license": "MIT", "authors": [ { "name": "Mark Baker", "email": "mark@lange.demon.co.uk" } ], "config": { "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "markbaker/ukraine": true } }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "squizlabs/php_codesniffer": "^3.7", "phpcompatibility/php-compatibility": "^9.3", "dealerdirect/phpcodesniffer-composer-installer": "dev-master" }, "autoload": { "psr-4": { "Complex\\": "classes/src/" } }, "scripts": { "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n" }, "minimum-stability": "dev" } complex/license.md 0000644 00000002125 15021223666 0010162 0 ustar 00 The MIT License (MIT) ===================== Copyright © `2017` `Mark Baker` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. complex/README.md 0000644 00000012651 15021223666 0007502 0 ustar 00 PHPComplex ========== --- PHP Class Library for working with Complex numbers [](https://github.com/MarkBaker/PHPComplex/actions) [](https://packagist.org/packages/markbaker/complex) [](https://packagist.org/packages/markbaker/complex) [](https://packagist.org/packages/markbaker/complex) [](https://xkcd.com/2028/) --- The library currently provides the following operations: - addition - subtraction - multiplication - division - division by - division into together with functions for - theta (polar theta angle) - rho (polar distance/radius) - conjugate * negative - inverse (1 / complex) - cos (cosine) - acos (inverse cosine) - cosh (hyperbolic cosine) - acosh (inverse hyperbolic cosine) - sin (sine) - asin (inverse sine) - sinh (hyperbolic sine) - asinh (inverse hyperbolic sine) - sec (secant) - asec (inverse secant) - sech (hyperbolic secant) - asech (inverse hyperbolic secant) - csc (cosecant) - acsc (inverse cosecant) - csch (hyperbolic secant) - acsch (inverse hyperbolic secant) - tan (tangent) - atan (inverse tangent) - tanh (hyperbolic tangent) - atanh (inverse hyperbolic tangent) - cot (cotangent) - acot (inverse cotangent) - coth (hyperbolic cotangent) - acoth (inverse hyperbolic cotangent) - sqrt (square root) - exp (exponential) - ln (natural log) - log10 (base-10 log) - log2 (base-2 log) - pow (raised to the power of a real number) --- # Installation ```shell composer require markbaker/complex:^1.0 ``` # Important BC Note If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPComplexFunctions](https://github.com/MarkBaker/PHPComplexFunctions) instead (available on packagist as [markbaker/complex-functions](https://packagist.org/packages/markbaker/complex-functions)). You'll need to replace `markbaker/complex`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Complex functions in the past, so no actual code changes are required. ```shell composer require markbaker/complex-functions:^3.0 ``` You should not reference this library (`markbaker/complex`) in your `composer.json`, composer wil take care of that for you. # Usage To create a new complex object, you can provide either the real, imaginary and suffix parts as individual values, or as an array of values passed passed to the constructor; or a string representing the value. e.g ```php $real = 1.23; $imaginary = -4.56; $suffix = 'i'; $complexObject = new Complex\Complex($real, $imaginary, $suffix); ``` or as an array ```php $real = 1.23; $imaginary = -4.56; $suffix = 'i'; $arguments = [$real, $imaginary, $suffix]; $complexObject = new Complex\Complex($arguments); ``` or as a string ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); ``` Complex objects are immutable: whenever you call a method or pass a complex value to a function that returns a complex value, a new Complex object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Complex result). ## Performing Mathematical Operations To perform mathematical operations with Complex values, you can call the appropriate method against a complex value, passing other values as arguments ```php $complexString1 = '1.23-4.56i'; $complexString2 = '2.34+5.67i'; $complexObject = new Complex\Complex($complexString1); echo $complexObject->add($complexString2); ``` or use the static Operation methods ```php $complexString1 = '1.23-4.56i'; $complexString2 = '2.34+5.67i'; echo Complex\Operations::add($complexString1, $complexString2); ``` If you want to perform the same operation against multiple values (e.g. to add three or more complex numbers), then you can pass multiple arguments to any of the operations. You can pass these arguments as Complex objects, or as an array, or string that will parse to a complex object. ## Using functions When calling any of the available functions for a complex value, you can either call the relevant method for the Complex object ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo $complexObject->sinh(); ``` or use the static Functions methods ```php $complexString = '1.23-4.56i'; echo Complex\Functions::sinh($complexString); ``` As with operations, you can pass these arguments as Complex objects, or as an array or string that will parse to a complex object. In the case of the `pow()` function (the only implemented function that requires an additional argument) you need to pass both arguments when calling the function ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo Complex\Functions::pow($complexObject, 2); ``` or pass the additional argument when calling the method ```php $complexString = '1.23-4.56i'; $complexObject = new Complex\Complex($complexString); echo $complexObject->pow(2); ``` matrix/classes/src/Operators/Operator.php 0000644 00000003721 15021223666 0014564 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Matrix; use Matrix\Exception; abstract class Operator { /** * Stored internally as a 2-dimension array of values * * @property mixed[][] $matrix **/ protected $matrix; /** * Number of rows in the matrix * * @property integer $rows **/ protected $rows; /** * Number of columns in the matrix * * @property integer $columns **/ protected $columns; /** * Create an new handler object for the operation * * @param Matrix $matrix The base Matrix object on which the operation will be performed */ public function __construct(Matrix $matrix) { $this->rows = $matrix->rows; $this->columns = $matrix->columns; $this->matrix = $matrix->toArray(); } /** * Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction * * @param Matrix $matrix The second Matrix object on which the operation will be performed * @throws Exception */ protected function validateMatchingDimensions(Matrix $matrix): void { if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) { throw new Exception('Matrices have mismatched dimensions'); } } /** * Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division * * @param Matrix $matrix The second Matrix object on which the operation will be performed * @throws Exception */ protected function validateReflectingDimensions(Matrix $matrix): void { if ($this->columns != $matrix->rows) { throw new Exception('Matrices have mismatched dimensions'); } } /** * Return the result of the operation * * @return Matrix */ public function result(): Matrix { return new Matrix($this->matrix); } } matrix/classes/src/Operators/Subtraction.php 0000644 00000004100 15021223666 0015256 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Matrix; use Matrix\Exception; class Subtraction extends Operator { /** * Execute the subtraction * * @param mixed $value The matrix or numeric value to subtract from the current base value * @throws Exception If the provided argument is not appropriate for the operation * @return $this The operation object, allowing multiple subtractions to be chained **/ public function execute($value): Operator { if (is_array($value)) { $value = new Matrix($value); } if (is_object($value) && ($value instanceof Matrix)) { return $this->subtractMatrix($value); } elseif (is_numeric($value)) { return $this->subtractScalar($value); } throw new Exception('Invalid argument for subtraction'); } /** * Execute the subtraction for a scalar * * @param mixed $value The numeric value to subtracted from the current base value * @return $this The operation object, allowing multiple additions to be chained **/ protected function subtractScalar($value): Operator { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] -= $value; } } return $this; } /** * Execute the subtraction for a matrix * * @param Matrix $value The numeric value to subtract from the current base value * @return $this The operation object, allowing multiple subtractions to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function subtractMatrix(Matrix $value): Operator { $this->validateMatchingDimensions($value); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1); } } return $this; } } matrix/classes/src/Operators/DirectSum.php 0000644 00000003577 15021223666 0014701 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Matrix; use Matrix\Exception; class DirectSum extends Operator { /** * Execute the addition * * @param mixed $value The matrix or numeric value to add to the current base value * @return $this The operation object, allowing multiple additions to be chained * @throws Exception If the provided argument is not appropriate for the operation */ public function execute($value): Operator { if (is_array($value)) { $value = new Matrix($value); } if ($value instanceof Matrix) { return $this->directSumMatrix($value); } throw new Exception('Invalid argument for addition'); } /** * Execute the direct sum for a matrix * * @param Matrix $value The numeric value to concatenate/direct sum with the current base value * @return $this The operation object, allowing multiple additions to be chained **/ private function directSumMatrix($value): Operator { $originalColumnCount = count($this->matrix[0]); $originalRowCount = count($this->matrix); $valColumnCount = $value->columns; $valRowCount = $value->rows; $value = $value->toArray(); for ($row = 0; $row < $this->rows; ++$row) { $this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0)); } $this->matrix = array_merge( $this->matrix, array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0)) ); for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) { array_splice( $this->matrix[$row], $originalColumnCount, $valColumnCount, $value[$row - $originalRowCount] ); } return $this; } } matrix/classes/src/Operators/Multiplication.php 0000644 00000005544 15021223666 0015773 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Matrix; use \Matrix\Builder; use Matrix\Exception; use Throwable; class Multiplication extends Operator { /** * Execute the multiplication * * @param mixed $value The matrix or numeric value to multiply the current base value by * @throws Exception If the provided argument is not appropriate for the operation * @return $this The operation object, allowing multiple multiplications to be chained **/ public function execute($value, string $type = 'multiplication'): Operator { if (is_array($value)) { $value = new Matrix($value); } if (is_object($value) && ($value instanceof Matrix)) { return $this->multiplyMatrix($value, $type); } elseif (is_numeric($value)) { return $this->multiplyScalar($value, $type); } throw new Exception("Invalid argument for $type"); } /** * Execute the multiplication for a scalar * * @param mixed $value The numeric value to multiply with the current base value * @return $this The operation object, allowing multiple mutiplications to be chained **/ protected function multiplyScalar($value, string $type = 'multiplication'): Operator { try { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] *= $value; } } } catch (Throwable $e) { throw new Exception("Invalid argument for $type"); } return $this; } /** * Execute the multiplication for a matrix * * @param Matrix $value The numeric value to multiply with the current base value * @return $this The operation object, allowing multiple mutiplications to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function multiplyMatrix(Matrix $value, string $type = 'multiplication'): Operator { $this->validateReflectingDimensions($value); $newRows = $this->rows; $newColumns = $value->columns; $matrix = Builder::createFilledMatrix(0, $newRows, $newColumns) ->toArray(); try { for ($row = 0; $row < $newRows; ++$row) { for ($column = 0; $column < $newColumns; ++$column) { $columnData = $value->getColumns($column + 1)->toArray(); foreach ($this->matrix[$row] as $key => $valueData) { $matrix[$row][$column] += $valueData * $columnData[$key][0]; } } } } catch (Throwable $e) { throw new Exception("Invalid argument for $type"); } $this->matrix = $matrix; return $this; } } matrix/classes/src/Operators/Addition.php 0000644 00000004000 15021223666 0014513 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Matrix; use Matrix\Exception; class Addition extends Operator { /** * Execute the addition * * @param mixed $value The matrix or numeric value to add to the current base value * @throws Exception If the provided argument is not appropriate for the operation * @return $this The operation object, allowing multiple additions to be chained **/ public function execute($value): Operator { if (is_array($value)) { $value = new Matrix($value); } if (is_object($value) && ($value instanceof Matrix)) { return $this->addMatrix($value); } elseif (is_numeric($value)) { return $this->addScalar($value); } throw new Exception('Invalid argument for addition'); } /** * Execute the addition for a scalar * * @param mixed $value The numeric value to add to the current base value * @return $this The operation object, allowing multiple additions to be chained **/ protected function addScalar($value): Operator { for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] += $value; } } return $this; } /** * Execute the addition for a matrix * * @param Matrix $value The numeric value to add to the current base value * @return $this The operation object, allowing multiple additions to be chained * @throws Exception If the provided argument is not appropriate for the operation **/ protected function addMatrix(Matrix $value): Operator { $this->validateMatchingDimensions($value); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { $this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1); } } return $this; } } matrix/classes/src/Operators/Division.php 0000644 00000001755 15021223666 0014562 0 ustar 00 <?php namespace Matrix\Operators; use Matrix\Div0Exception; use Matrix\Exception; use \Matrix\Matrix; use \Matrix\Functions; class Division extends Multiplication { /** * Execute the division * * @param mixed $value The matrix or numeric value to divide the current base value by * @throws Exception If the provided argument is not appropriate for the operation * @return $this The operation object, allowing multiple divisions to be chained **/ public function execute($value, string $type = 'division'): Operator { if (is_array($value)) { $value = new Matrix($value); } if (is_object($value) && ($value instanceof Matrix)) { $value = Functions::inverse($value, $type); return $this->multiplyMatrix($value, $type); } elseif (is_numeric($value)) { return $this->multiplyScalar(1 / $value, $type); } throw new Exception('Invalid argument for division'); } } matrix/classes/src/Builder.php 0000644 00000003260 15021223666 0012377 0 ustar 00 <?php /** * * Class for the creating "special" Matrices * * @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix) * @license https://opensource.org/licenses/MIT MIT */ namespace Matrix; /** * Matrix Builder class. * * @package Matrix */ class Builder { /** * Create a new matrix of specified dimensions, and filled with a specified value * If the column argument isn't provided, then a square matrix will be created * * @param mixed $fillValue * @param int $rows * @param int|null $columns * @return Matrix * @throws Exception */ public static function createFilledMatrix($fillValue, $rows, $columns = null) { if ($columns === null) { $columns = $rows; } $rows = Matrix::validateRow($rows); $columns = Matrix::validateColumn($columns); return new Matrix( array_fill( 0, $rows, array_fill( 0, $columns, $fillValue ) ) ); } /** * Create a new identity matrix of specified dimensions * This will always be a square matrix, with the number of rows and columns matching the provided dimension * * @param int $dimensions * @return Matrix * @throws Exception */ public static function createIdentityMatrix($dimensions, $fillValue = null) { $grid = static::createFilledMatrix($fillValue, $dimensions)->toArray(); for ($x = 0; $x < $dimensions; ++$x) { $grid[$x][$x] = 1; } return new Matrix($grid); } } matrix/classes/src/Matrix.php 0000644 00000026450 15021223666 0012263 0 ustar 00 <?php /** * * Class for the management of Matrices * * @copyright Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix) * @license https://opensource.org/licenses/MIT MIT */ namespace Matrix; use Generator; use Matrix\Decomposition\LU; use Matrix\Decomposition\QR; /** * Matrix object. * * @package Matrix * * @property-read int $rows The number of rows in the matrix * @property-read int $columns The number of columns in the matrix * @method Matrix antidiagonal() * @method Matrix adjoint() * @method Matrix cofactors() * @method float determinant() * @method Matrix diagonal() * @method Matrix identity() * @method Matrix inverse() * @method Matrix minors() * @method float trace() * @method Matrix transpose() * @method Matrix add(...$matrices) * @method Matrix subtract(...$matrices) * @method Matrix multiply(...$matrices) * @method Matrix divideby(...$matrices) * @method Matrix divideinto(...$matrices) * @method Matrix directsum(...$matrices) */ class Matrix { protected $rows; protected $columns; protected $grid = []; /* * Create a new Matrix object from an array of values * * @param array $grid */ final public function __construct(array $grid) { $this->buildFromArray(array_values($grid)); } /* * Create a new Matrix object from an array of values * * @param array $grid */ protected function buildFromArray(array $grid): void { $this->rows = count($grid); $columns = array_reduce( $grid, function ($carry, $value) { return max($carry, is_array($value) ? count($value) : 1); } ); $this->columns = $columns; array_walk( $grid, function (&$value) use ($columns) { if (!is_array($value)) { $value = [$value]; } $value = array_pad(array_values($value), $columns, null); } ); $this->grid = $grid; } /** * Validate that a row number is a positive integer * * @param int $row * @return int * @throws Exception */ public static function validateRow(int $row): int { if ((!is_numeric($row)) || (intval($row) < 1)) { throw new Exception('Invalid Row'); } return (int)$row; } /** * Validate that a column number is a positive integer * * @param int $column * @return int * @throws Exception */ public static function validateColumn(int $column): int { if ((!is_numeric($column)) || (intval($column) < 1)) { throw new Exception('Invalid Column'); } return (int)$column; } /** * Validate that a row number falls within the set of rows for this matrix * * @param int $row * @return int * @throws Exception */ protected function validateRowInRange(int $row): int { $row = static::validateRow($row); if ($row > $this->rows) { throw new Exception('Requested Row exceeds matrix size'); } return $row; } /** * Validate that a column number falls within the set of columns for this matrix * * @param int $column * @return int * @throws Exception */ protected function validateColumnInRange(int $column): int { $column = static::validateColumn($column); if ($column > $this->columns) { throw new Exception('Requested Column exceeds matrix size'); } return $column; } /** * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows * A $rowCount value of 0 will return all rows of the matrix from $row * A negative $rowCount value will return rows until that many rows from the end of the matrix * * Note that row numbers start from 1, not from 0 * * @param int $row * @param int $rowCount * @return static * @throws Exception */ public function getRows(int $row, int $rowCount = 1): Matrix { $row = $this->validateRowInRange($row); if ($rowCount === 0) { $rowCount = $this->rows - $row + 1; } return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); } /** * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns * A $columnCount value of 0 will return all columns of the matrix from $column * A negative $columnCount value will return columns until that many columns from the end of the matrix * * Note that column numbers start from 1, not from 0 * * @param int $column * @param int $columnCount * @return Matrix * @throws Exception */ public function getColumns(int $column, int $columnCount = 1): Matrix { $column = $this->validateColumnInRange($column); if ($columnCount < 1) { $columnCount = $this->columns + $columnCount - $column + 1; } $grid = []; for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { $grid[] = array_column($this->grid, $i); } return (new static($grid))->transpose(); } /** * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, * and $rowCount rows * A negative $rowCount value will drop rows until that many rows from the end of the matrix * A $rowCount value of 0 will remove all rows of the matrix from $row * * Note that row numbers start from 1, not from 0 * * @param int $row * @param int $rowCount * @return static * @throws Exception */ public function dropRows(int $row, int $rowCount = 1): Matrix { $this->validateRowInRange($row); if ($rowCount === 0) { $rowCount = $this->rows - $row + 1; } $grid = $this->grid; array_splice($grid, $row - 1, (int)$rowCount); return new static($grid); } /** * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, * and $columnCount columns * A negative $columnCount value will drop columns until that many columns from the end of the matrix * A $columnCount value of 0 will remove all columns of the matrix from $column * * Note that column numbers start from 1, not from 0 * * @param int $column * @param int $columnCount * @return static * @throws Exception */ public function dropColumns(int $column, int $columnCount = 1): Matrix { $this->validateColumnInRange($column); if ($columnCount < 1) { $columnCount = $this->columns + $columnCount - $column + 1; } $grid = $this->grid; array_walk( $grid, function (&$row) use ($column, $columnCount) { array_splice($row, $column - 1, (int)$columnCount); } ); return new static($grid); } /** * Return a value from this matrix, from the "cell" identified by the row and column numbers * Note that row and column numbers start from 1, not from 0 * * @param int $row * @param int $column * @return mixed * @throws Exception */ public function getValue(int $row, int $column) { $row = $this->validateRowInRange($row); $column = $this->validateColumnInRange($column); return $this->grid[$row - 1][$column - 1]; } /** * Returns a Generator that will yield each row of the matrix in turn as a vector matrix * or the value of each cell if the matrix is a column vector * * @return Generator|Matrix[]|mixed[] */ public function rows(): Generator { foreach ($this->grid as $i => $row) { yield $i + 1 => ($this->columns == 1) ? $row[0] : new static([$row]); } } /** * Returns a Generator that will yield each column of the matrix in turn as a vector matrix * or the value of each cell if the matrix is a row vector * * @return Generator|Matrix[]|mixed[] */ public function columns(): Generator { for ($i = 0; $i < $this->columns; ++$i) { yield $i + 1 => ($this->rows == 1) ? $this->grid[0][$i] : new static(array_column($this->grid, $i)); } } /** * Identify if the row and column dimensions of this matrix are equal, * i.e. if it is a "square" matrix * * @return bool */ public function isSquare(): bool { return $this->rows === $this->columns; } /** * Identify if this matrix is a vector * i.e. if it comprises only a single row or a single column * * @return bool */ public function isVector(): bool { return $this->rows === 1 || $this->columns === 1; } /** * Return the matrix as a 2-dimensional array * * @return array */ public function toArray(): array { return $this->grid; } /** * Solve A*X = B. * * @param Matrix $B Right hand side * * @throws Exception * * @return Matrix ... Solution if A is square, least squares solution otherwise */ public function solve(Matrix $B): Matrix { if ($this->columns === $this->rows) { return (new LU($this))->solve($B); } return (new QR($this))->solve($B); } protected static $getters = [ 'rows', 'columns', ]; /** * Access specific properties as read-only (no setters) * * @param string $propertyName * @return mixed * @throws Exception */ public function __get(string $propertyName) { $propertyName = strtolower($propertyName); // Test for function calls if (in_array($propertyName, self::$getters)) { return $this->$propertyName; } throw new Exception('Property does not exist'); } protected static $functions = [ 'adjoint', 'antidiagonal', 'cofactors', 'determinant', 'diagonal', 'identity', 'inverse', 'minors', 'trace', 'transpose', ]; protected static $operations = [ 'add', 'subtract', 'multiply', 'divideby', 'divideinto', 'directsum', ]; /** * Returns the result of the function call or operation * * @param string $functionName * @param mixed[] $arguments * @return Matrix|float * @throws Exception */ public function __call(string $functionName, $arguments) { $functionName = strtolower(str_replace('_', '', $functionName)); // Test for function calls if (in_array($functionName, self::$functions, true)) { return Functions::$functionName($this, ...$arguments); } // Test for operation calls if (in_array($functionName, self::$operations, true)) { return Operations::$functionName($this, ...$arguments); } throw new Exception('Function or Operation does not exist'); } } matrix/classes/src/Decomposition/Decomposition.php 0000644 00000001022 15021223666 0016433 0 ustar 00 <?php namespace Matrix\Decomposition; use Matrix\Exception; use Matrix\Matrix; class Decomposition { const LU = 'LU'; const QR = 'QR'; /** * @throws Exception */ public static function decomposition($type, Matrix $matrix) { switch (strtoupper($type)) { case self::LU: return new LU($matrix); case self::QR: return new QR($matrix); default: throw new Exception('Invalid Decomposition'); } } } matrix/classes/src/Decomposition/QR.php 0000644 00000012757 15021223666 0014162 0 ustar 00 <?php namespace Matrix\Decomposition; use Matrix\Exception; use Matrix\Matrix; class QR { private $qrMatrix; private $rows; private $columns; private $rDiagonal = []; public function __construct(Matrix $matrix) { $this->qrMatrix = $matrix->toArray(); $this->rows = $matrix->rows; $this->columns = $matrix->columns; $this->decompose(); } public function getHouseholdVectors(): Matrix { $householdVectors = []; for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row >= $column) { $householdVectors[$row][$column] = $this->qrMatrix[$row][$column]; } else { $householdVectors[$row][$column] = 0.0; } } } return new Matrix($householdVectors); } public function getQ(): Matrix { $qGrid = []; $rowCount = $this->rows; for ($k = $this->columns - 1; $k >= 0; --$k) { for ($i = 0; $i < $this->rows; ++$i) { $qGrid[$i][$k] = 0.0; } $qGrid[$k][$k] = 1.0; if ($this->columns > $this->rows) { $qGrid = array_slice($qGrid, 0, $this->rows); } for ($j = $k; $j < $this->columns; ++$j) { if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) { $s = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j]; } $s = -$s / $this->qrMatrix[$k][$k]; for ($i = $k; $i < $this->rows; ++$i) { $qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k]; } } } } array_walk( $qGrid, function (&$row) use ($rowCount) { $row = array_reverse($row); $row = array_slice($row, 0, $rowCount); } ); return new Matrix($qGrid); } public function getR(): Matrix { $rGrid = []; for ($row = 0; $row < $this->columns; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row < $column) { $rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0; } elseif ($row === $column) { $rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0; } else { $rGrid[$row][$column] = 0.0; } } } if ($this->columns > $this->rows) { $rGrid = array_slice($rGrid, 0, $this->rows); } return new Matrix($rGrid); } private function hypo($a, $b): float { if (abs($a) > abs($b)) { $r = $b / $a; $r = abs($a) * sqrt(1 + $r * $r); } elseif ($b != 0.0) { $r = $a / $b; $r = abs($b) * sqrt(1 + $r * $r); } else { $r = 0.0; } return $r; } /** * QR Decomposition computed by Householder reflections. */ private function decompose(): void { for ($k = 0; $k < $this->columns; ++$k) { // Compute 2-norm of k-th column without under/overflow. $norm = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $norm = $this->hypo($norm, $this->qrMatrix[$i][$k]); } if ($norm != 0.0) { // Form k-th Householder vector. if ($this->qrMatrix[$k][$k] < 0.0) { $norm = -$norm; } for ($i = $k; $i < $this->rows; ++$i) { $this->qrMatrix[$i][$k] /= $norm; } $this->qrMatrix[$k][$k] += 1.0; // Apply transformation to remaining columns. for ($j = $k + 1; $j < $this->columns; ++$j) { $s = 0.0; for ($i = $k; $i < $this->rows; ++$i) { $s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j]; } $s = -$s / $this->qrMatrix[$k][$k]; for ($i = $k; $i < $this->rows; ++$i) { $this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k]; } } } $this->rDiagonal[$k] = -$norm; } } public function isFullRank(): bool { for ($j = 0; $j < $this->columns; ++$j) { if ($this->rDiagonal[$j] == 0.0) { return false; } } return true; } /** * Least squares solution of A*X = B. * * @param Matrix $B a Matrix with as many rows as A and any number of columns * * @throws Exception * * @return Matrix matrix that minimizes the two norm of Q*R*X-B */ public function solve(Matrix $B): Matrix { if ($B->rows !== $this->rows) { throw new Exception('Matrix row dimensions are not equal'); } if (!$this->isFullRank()) { throw new Exception('Can only perform this operation on a full-rank matrix'); } // Compute Y = transpose(Q)*B $Y = $this->getQ()->transpose() ->multiply($B); // Solve R*X = Y; return $this->getR()->inverse() ->multiply($Y); } } matrix/classes/src/Decomposition/LU.php 0000644 00000015471 15021223666 0014154 0 ustar 00 <?php namespace Matrix\Decomposition; use Matrix\Exception; use Matrix\Matrix; class LU { private $luMatrix; private $rows; private $columns; private $pivot = []; public function __construct(Matrix $matrix) { $this->luMatrix = $matrix->toArray(); $this->rows = $matrix->rows; $this->columns = $matrix->columns; $this->buildPivot(); } /** * Get lower triangular factor. * * @return Matrix Lower triangular factor */ public function getL(): Matrix { $lower = []; $columns = min($this->rows, $this->columns); for ($row = 0; $row < $this->rows; ++$row) { for ($column = 0; $column < $columns; ++$column) { if ($row > $column) { $lower[$row][$column] = $this->luMatrix[$row][$column]; } elseif ($row === $column) { $lower[$row][$column] = 1.0; } else { $lower[$row][$column] = 0.0; } } } return new Matrix($lower); } /** * Get upper triangular factor. * * @return Matrix Upper triangular factor */ public function getU(): Matrix { $upper = []; $rows = min($this->rows, $this->columns); for ($row = 0; $row < $rows; ++$row) { for ($column = 0; $column < $this->columns; ++$column) { if ($row <= $column) { $upper[$row][$column] = $this->luMatrix[$row][$column]; } else { $upper[$row][$column] = 0.0; } } } return new Matrix($upper); } /** * Return pivot permutation vector. * * @return Matrix Pivot matrix */ public function getP(): Matrix { $pMatrix = []; $pivots = $this->pivot; $pivotCount = count($pivots); foreach ($pivots as $row => $pivot) { $pMatrix[$row] = array_fill(0, $pivotCount, 0); $pMatrix[$row][$pivot] = 1; } return new Matrix($pMatrix); } /** * Return pivot permutation vector. * * @return array Pivot vector */ public function getPivot(): array { return $this->pivot; } /** * Is the matrix nonsingular? * * @return bool true if U, and hence A, is nonsingular */ public function isNonsingular(): bool { for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) { if ($this->luMatrix[$diagonal][$diagonal] === 0.0) { return false; } } return true; } private function buildPivot(): void { for ($row = 0; $row < $this->rows; ++$row) { $this->pivot[$row] = $row; } for ($column = 0; $column < $this->columns; ++$column) { $luColumn = $this->localisedReferenceColumn($column); $this->applyTransformations($column, $luColumn); $pivot = $this->findPivot($column, $luColumn); if ($pivot !== $column) { $this->pivotExchange($pivot, $column); } $this->computeMultipliers($column); unset($luColumn); } } private function localisedReferenceColumn($column): array { $luColumn = []; for ($row = 0; $row < $this->rows; ++$row) { $luColumn[$row] = &$this->luMatrix[$row][$column]; } return $luColumn; } private function applyTransformations($column, array $luColumn): void { for ($row = 0; $row < $this->rows; ++$row) { $luRow = $this->luMatrix[$row]; // Most of the time is spent in the following dot product. $kmax = min($row, $column); $sValue = 0.0; for ($kValue = 0; $kValue < $kmax; ++$kValue) { $sValue += $luRow[$kValue] * $luColumn[$kValue]; } $luRow[$column] = $luColumn[$row] -= $sValue; } } private function findPivot($column, array $luColumn): int { $pivot = $column; for ($row = $column + 1; $row < $this->rows; ++$row) { if (abs($luColumn[$row]) > abs($luColumn[$pivot])) { $pivot = $row; } } return $pivot; } private function pivotExchange($pivot, $column): void { for ($kValue = 0; $kValue < $this->columns; ++$kValue) { $tValue = $this->luMatrix[$pivot][$kValue]; $this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue]; $this->luMatrix[$column][$kValue] = $tValue; } $lValue = $this->pivot[$pivot]; $this->pivot[$pivot] = $this->pivot[$column]; $this->pivot[$column] = $lValue; } private function computeMultipliers($diagonal): void { if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) { for ($row = $diagonal + 1; $row < $this->rows; ++$row) { $this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal]; } } } private function pivotB(Matrix $B): array { $X = []; foreach ($this->pivot as $rowId) { $row = $B->getRows($rowId + 1)->toArray(); $X[] = array_pop($row); } return $X; } /** * Solve A*X = B. * * @param Matrix $B a Matrix with as many rows as A and any number of columns * * @throws Exception * * @return Matrix X so that L*U*X = B(piv,:) */ public function solve(Matrix $B): Matrix { if ($B->rows !== $this->rows) { throw new Exception('Matrix row dimensions are not equal'); } if ($this->rows !== $this->columns) { throw new Exception('LU solve() only works on square matrices'); } if (!$this->isNonsingular()) { throw new Exception('Can only perform operation on singular matrix'); } // Copy right hand side with pivoting $nx = $B->columns; $X = $this->pivotB($B); // Solve L*Y = B(piv,:) for ($k = 0; $k < $this->columns; ++$k) { for ($i = $k + 1; $i < $this->columns; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; } } } // Solve U*X = Y; for ($k = $this->columns - 1; $k >= 0; --$k) { for ($j = 0; $j < $nx; ++$j) { $X[$k][$j] /= $this->luMatrix[$k][$k]; } for ($i = 0; $i < $k; ++$i) { for ($j = 0; $j < $nx; ++$j) { $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; } } } return new Matrix($X); } } matrix/classes/src/Div0Exception.php 0000644 00000000362 15021223666 0013472 0 ustar 00 <?php /** * Exception. * * @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix) * @license https://opensource.org/licenses/MIT MIT */ namespace Matrix; class Div0Exception extends Exception { } matrix/classes/src/Exception.php 0000644 00000000357 15021223666 0012753 0 ustar 00 <?php /** * Exception. * * @copyright Copyright (c) 2013-2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix) * @license https://opensource.org/licenses/MIT MIT */ namespace Matrix; class Exception extends \Exception { } matrix/classes/src/Operations.php 0000644 00000010116 15021223666 0013132 0 ustar 00 <?php namespace Matrix; use Matrix\Operators\Addition; use Matrix\Operators\DirectSum; use Matrix\Operators\Division; use Matrix\Operators\Multiplication; use Matrix\Operators\Subtraction; class Operations { public static function add(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Addition operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Addition arguments must be Matrix or array'); } $result = new Addition($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function directsum(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('DirectSum operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('DirectSum arguments must be Matrix or array'); } $result = new DirectSum($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function divideby(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Division operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Division arguments must be Matrix or array'); } $result = new Division($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function divideinto(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Division operation requires at least 2 arguments'); } $matrix = array_pop($matrixValues); $matrixValues = array_reverse($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Division arguments must be Matrix or array'); } $result = new Division($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function multiply(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Multiplication operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Multiplication arguments must be Matrix or array'); } $result = new Multiplication($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } public static function subtract(...$matrixValues): Matrix { if (count($matrixValues) < 2) { throw new Exception('Subtraction operation requires at least 2 arguments'); } $matrix = array_shift($matrixValues); if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Subtraction arguments must be Matrix or array'); } $result = new Subtraction($matrix); foreach ($matrixValues as $matrix) { $result->execute($matrix); } return $result->result(); } } matrix/classes/src/Functions.php 0000644 00000025222 15021223666 0012763 0 ustar 00 <?php namespace Matrix; class Functions { /** * Validates an array of matrix, converting an array to a matrix if required. * * @param Matrix|array $matrix Matrix or an array to treat as a matrix. * @return Matrix The new matrix * @throws Exception If argument isn't a valid matrix or array. */ private static function validateMatrix($matrix) { if (is_array($matrix)) { $matrix = new Matrix($matrix); } if (!$matrix instanceof Matrix) { throw new Exception('Must be Matrix or array'); } return $matrix; } /** * Calculate the adjoint of the matrix * * @param Matrix $matrix The matrix whose adjoint we wish to calculate * @return Matrix * * @throws Exception */ private static function getAdjoint(Matrix $matrix) { return self::transpose( self::getCofactors($matrix) ); } /** * Return the adjoint of this matrix * The adjugate, classical adjoint, or adjunct of a square matrix is the transpose of its cofactor matrix. * The adjugate has sometimes been called the "adjoint", but today the "adjoint" of a matrix normally refers * to its corresponding adjoint operator, which is its conjugate transpose. * * @param Matrix|array $matrix The matrix whose adjoint we wish to calculate * @return Matrix * @throws Exception **/ public static function adjoint($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Adjoint can only be calculated for a square matrix'); } return self::getAdjoint($matrix); } /** * Calculate the cofactors of the matrix * * @param Matrix $matrix The matrix whose cofactors we wish to calculate * @return Matrix * * @throws Exception */ private static function getCofactors(Matrix $matrix) { $cofactors = self::getMinors($matrix); $dimensions = $matrix->rows; $cof = 1; for ($i = 0; $i < $dimensions; ++$i) { $cofs = $cof; for ($j = 0; $j < $dimensions; ++$j) { $cofactors[$i][$j] *= $cofs; $cofs = -$cofs; } $cof = -$cof; } return new Matrix($cofactors); } /** * Return the cofactors of this matrix * * @param Matrix|array $matrix The matrix whose cofactors we wish to calculate * @return Matrix * * @throws Exception */ public static function cofactors($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Cofactors can only be calculated for a square matrix'); } return self::getCofactors($matrix); } /** * @param Matrix $matrix * @param int $row * @param int $column * @return float * @throws Exception */ private static function getDeterminantSegment(Matrix $matrix, $row, $column) { $tmpMatrix = $matrix->toArray(); unset($tmpMatrix[$row]); array_walk( $tmpMatrix, function (&$row) use ($column) { unset($row[$column]); } ); return self::getDeterminant(new Matrix($tmpMatrix)); } /** * Calculate the determinant of the matrix * * @param Matrix $matrix The matrix whose determinant we wish to calculate * @return float * * @throws Exception */ private static function getDeterminant(Matrix $matrix) { $dimensions = $matrix->rows; $determinant = 0; switch ($dimensions) { case 1: $determinant = $matrix->getValue(1, 1); break; case 2: $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) - $matrix->getValue(1, 2) * $matrix->getValue(2, 1); break; default: for ($i = 1; $i <= $dimensions; ++$i) { $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1); if (($i % 2) == 0) { $determinant -= $det; } else { $determinant += $det; } } break; } return $determinant; } /** * Return the determinant of this matrix * * @param Matrix|array $matrix The matrix whose determinant we wish to calculate * @return float * @throws Exception **/ public static function determinant($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Determinant can only be calculated for a square matrix'); } return self::getDeterminant($matrix); } /** * Return the diagonal of this matrix * * @param Matrix|array $matrix The matrix whose diagonal we wish to calculate * @return Matrix * @throws Exception **/ public static function diagonal($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Diagonal can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) ->toArray(); for ($i = 0; $i < $dimensions; ++$i) { $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1); } return new Matrix($grid); } /** * Return the antidiagonal of this matrix * * @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate * @return Matrix * @throws Exception **/ public static function antidiagonal($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Anti-Diagonal can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) ->toArray(); for ($i = 0; $i < $dimensions; ++$i) { $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i); } return new Matrix($grid); } /** * Return the identity matrix * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix * with ones on the main diagonal and zeros elsewhere * * @param Matrix|array $matrix The matrix whose identity we wish to calculate * @return Matrix * @throws Exception **/ public static function identity($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Identity can only be created for a square matrix'); } $dimensions = $matrix->rows; return Builder::createIdentityMatrix($dimensions); } /** * Return the inverse of this matrix * * @param Matrix|array $matrix The matrix whose inverse we wish to calculate * @return Matrix * @throws Exception **/ public static function inverse($matrix, string $type = 'inverse') { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix'); } $determinant = self::getDeterminant($matrix); if ($determinant == 0.0) { throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant'); } if ($matrix->rows == 1) { return new Matrix([[1 / $matrix->getValue(1, 1)]]); } return self::getAdjoint($matrix) ->multiply(1 / $determinant); } /** * Calculate the minors of the matrix * * @param Matrix $matrix The matrix whose minors we wish to calculate * @return array[] * * @throws Exception */ protected static function getMinors(Matrix $matrix) { $minors = $matrix->toArray(); $dimensions = $matrix->rows; if ($dimensions == 1) { return $minors; } for ($i = 0; $i < $dimensions; ++$i) { for ($j = 0; $j < $dimensions; ++$j) { $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j); } } return $minors; } /** * Return the minors of the matrix * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or * more of its rows or columns. * Minors obtained by removing just one row and one column from square matrices (first minors) are required for * calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of * square matrices. * * @param Matrix|array $matrix The matrix whose minors we wish to calculate * @return Matrix * @throws Exception **/ public static function minors($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Minors can only be calculated for a square matrix'); } return new Matrix(self::getMinors($matrix)); } /** * Return the trace of this matrix * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right) * of the matrix * * @param Matrix|array $matrix The matrix whose trace we wish to calculate * @return float * @throws Exception **/ public static function trace($matrix) { $matrix = self::validateMatrix($matrix); if (!$matrix->isSquare()) { throw new Exception('Trace can only be extracted from a square matrix'); } $dimensions = $matrix->rows; $result = 0; for ($i = 1; $i <= $dimensions; ++$i) { $result += $matrix->getValue($i, $i); } return $result; } /** * Return the transpose of this matrix * * @param Matrix|\a $matrix The matrix whose transpose we wish to calculate * @return Matrix **/ public static function transpose($matrix) { $matrix = self::validateMatrix($matrix); $array = array_values(array_merge([null], $matrix->toArray())); $grid = call_user_func_array( 'array_map', $array ); return new Matrix($grid); } } matrix/examples/test.php 0000644 00000000746 15021223666 0011370 0 ustar 00 <?php use Matrix\Matrix; use Matrix\Decomposition\QR; include __DIR__ . '/../vendor/autoload.php'; $grid = [ [0, 1], [-1, 0], ]; $targetGrid = [ [-1], [2], ]; $matrix = new Matrix($grid); $target = new Matrix($targetGrid); $decomposition = new QR($matrix); $X = $decomposition->solve($target); echo 'X', PHP_EOL; var_export($X->toArray()); echo PHP_EOL; $resolve = $matrix->multiply($X); echo 'Resolve', PHP_EOL; var_export($resolve->toArray()); echo PHP_EOL; matrix/.github/workflows/main.yaml 0000644 00000007730 15021223666 0013267 0 ustar 00 name: main on: [ push, pull_request ] jobs: test: runs-on: ubuntu-latest strategy: matrix: php-version: - '7.1' - '7.2' - '7.3' - '7.4' - '8.0' - '8.1' - '8.2' include: - php-version: 'nightly' experimental: true name: PHP ${{ matrix.php-version }} steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Delete composer lock file id: composer-lock if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} run: | rm composer.lock echo "::set-output name=flags::--ignore-platform-reqs" - name: Install dependencies run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} - name: Setup problem matchers for PHP run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" - name: Setup problem matchers for PHPUnit run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Test with PHPUnit run: ./vendor/bin/phpunit phpcs: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: none tools: cs2pr - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Code style with PHP_CodeSniffer run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr --graceful-warnings --colorize coverage: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup PHP, with composer and extensions uses: shivammathur/setup-php@v2 with: php-version: 7.4 extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib coverage: pcov - name: Get composer cache directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Cache composer dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies run: composer install --no-progress --prefer-dist --optimize-autoloader - name: Coverage run: | ./vendor/bin/phpunit --coverage-text matrix/composer.json 0000644 00000003364 15021223666 0010603 0 ustar 00 { "name": "markbaker/matrix", "type": "library", "description": "PHP Class for working with matrices", "keywords": ["matrix", "vector", "mathematics"], "homepage": "https://github.com/MarkBaker/PHPMatrix", "license": "MIT", "authors": [ { "name": "Mark Baker", "email": "mark@demon-angel.eu" } ], "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "phpdocumentor/phpdocumentor": "2.*", "phpmd/phpmd": "2.*", "sebastian/phpcpd": "^4.0", "phploc/phploc": "^4.0", "squizlabs/php_codesniffer": "^3.7", "phpcompatibility/php-compatibility": "^9.3", "dealerdirect/phpcodesniffer-composer-installer": "dev-master" }, "autoload": { "psr-4": { "Matrix\\": "classes/src/" } }, "autoload-dev": { "psr-4": { "MatrixTest\\": "unitTests/classes/src/" } }, "scripts": { "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", "test": "phpunit -c phpunit.xml.dist", "mess": "phpmd classes/src/ xml codesize,unusedcode,design,naming -n", "lines": "phploc classes/src/ -n", "cpd": "phpcpd classes/src/ -n", "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n", "coverage": "phpunit -c phpunit.xml.dist --coverage-text --coverage-html ./build/coverage" }, "minimum-stability": "dev", "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } matrix/buildPhar.php 0000644 00000002527 15021223666 0010504 0 ustar 00 <?php # required: PHP 5.3+ and zlib extension // ini option check if (ini_get('phar.readonly')) { echo "php.ini: set the 'phar.readonly' option to 0 to enable phar creation\n"; exit(1); } // output name $pharName = 'Matrix.phar'; // target folder $sourceDir = __DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR; // default meta information $metaData = array( 'Author' => 'Mark Baker <mark@lange.demon.co.uk>', 'Description' => 'PHP Class for working with Matrix numbers', 'Copyright' => 'Mark Baker (c) 2013-' . date('Y'), 'Timestamp' => time(), 'Version' => '0.1.0', 'Date' => date('Y-m-d') ); // cleanup if (file_exists($pharName)) { echo "Removed: {$pharName}\n"; unlink($pharName); } echo "Building phar file...\n"; // the phar object $phar = new Phar($pharName, null, 'Matrix'); $phar->buildFromDirectory($sourceDir); $phar->setStub( <<<'EOT' <?php spl_autoload_register(function ($className) { include 'phar://' . $className . '.php'; }); try { Phar::mapPhar(); } catch (PharException $e) { error_log($e->getMessage()); exit(1); } include 'phar://functions/sqrt.php'; __HALT_COMPILER(); EOT ); $phar->setMetadata($metaData); $phar->compressFiles(Phar::GZ); echo "Complete.\n"; exit(); matrix/license.md 0000644 00000002125 15021223666 0010017 0 ustar 00 The MIT License (MIT) ===================== Copyright © `2018` `Mark Baker` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. matrix/infection.json.dist 0000644 00000000563 15021223666 0011672 0 ustar 00 { "timeout": 1, "source": { "directories": [ "classes\/src" ] }, "logs": { "text": "build/infection/text.log", "summary": "build/infection/summary.log", "debug": "build/infection/debug.log", "perMutator": "build/infection/perMutator.md" }, "mutators": { "@default": true } } matrix/phpstan.neon 0000644 00000000512 15021223666 0010407 0 ustar 00 parameters: ignoreErrors: - '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#' - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#' - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has parameter \$[A-Za-z0-9]+ with no typehint specified#' checkMissingIterableValueType: false matrix/README.md 0000644 00000013143 15021223666 0007334 0 ustar 00 PHPMatrix ========== --- PHP Class for handling Matrices [](https://github.com/MarkBaker/PHPMatrix/actions) [](https://packagist.org/packages/markbaker/matrix) [](https://packagist.org/packages/markbaker/matrix) [](https://packagist.org/packages/markbaker/matrix) [](https://xkcd.com/184/) Matrix Transform --- This library currently provides the following operations: - addition - direct sum - subtraction - multiplication - division (using [A].[B]<sup>-1</sup>) - division by - division into together with functions for - adjoint - antidiagonal - cofactors - determinant - diagonal - identity - inverse - minors - trace - transpose - solve Given Matrices A and B, calculate X for A.X = B and classes for - Decomposition - LU Decomposition with partial row pivoting, such that [P].[A] = [L].[U] and [A] = [P]<sup>|</sup>.[L].[U] - QR Decomposition such that [A] = [Q].[R] ## TO DO - power() function - Decomposition - Cholesky Decomposition - EigenValue Decomposition - EigenValues - EigenVectors --- # Installation ```shell composer require markbaker/matrix:^3.0 ``` # Important BC Note If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPMatrixFunctions](https://github.com/MarkBaker/PHPMatrixFunctions) instead (available on packagist as [markbaker/matrix-functions](https://packagist.org/packages/markbaker/matrix-functions)). You'll need to replace `markbaker/matrix`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Matrix functions in the past, so no actual code changes are required. ```shell composer require markbaker/matrix-functions:^1.0 ``` You should not reference this library (`markbaker/matrix`) in your `composer.json`, composer wil take care of that for you. # Usage To create a new Matrix object, provide an array as the constructor argument ```php $grid = [ [16, 3, 2, 13], [ 5, 10, 11, 8], [ 9, 6, 7, 12], [ 4, 15, 14, 1], ]; $matrix = new Matrix\Matrix($grid); ``` The `Builder` class provides helper methods for creating specific matrices, specifically an identity matrix of a specified size; or a matrix of a specified dimensions, with every cell containing a set value. ```php $matrix = Matrix\Builder::createFilledMatrix(1, 5, 3); ``` Will create a matrix of 5 rows and 3 columns, filled with a `1` in every cell; while ```php $matrix = Matrix\Builder::createIdentityMatrix(3); ``` will create a 3x3 identity matrix. Matrix objects are immutable: whenever you call a method or pass a grid to a function that returns a matrix value, a new Matrix object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Matrix result). ## Performing Mathematical Operations To perform mathematical operations with Matrices, you can call the appropriate method against a matrix value, passing other values as arguments ```php $matrix1 = new Matrix\Matrix([ [2, 7, 6], [9, 5, 1], [4, 3, 8], ]); $matrix2 = new Matrix\Matrix([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]); var_dump($matrix1->multiply($matrix2)->toArray()); ``` or pass all values to the appropriate static method ```php $matrix1 = new Matrix\Matrix([ [2, 7, 6], [9, 5, 1], [4, 3, 8], ]); $matrix2 = new Matrix\Matrix([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]); var_dump(Matrix\Operations::multiply($matrix1, $matrix2)->toArray()); ``` You can pass in the arguments as Matrix objects, or as arrays. If you want to perform the same operation against multiple values (e.g. to add three or more matrices), then you can pass multiple arguments to any of the operations. ## Using functions When calling any of the available functions for a matrix value, you can either call the relevant method for the Matrix object ```php $grid = [ [16, 3, 2, 13], [ 5, 10, 11, 8], [ 9, 6, 7, 12], [ 4, 15, 14, 1], ]; $matrix = new Matrix\Matrix($grid); echo $matrix->trace(); ``` or you can call the static method, passing the Matrix object or array as an argument ```php $grid = [ [16, 3, 2, 13], [ 5, 10, 11, 8], [ 9, 6, 7, 12], [ 4, 15, 14, 1], ]; $matrix = new Matrix\Matrix($grid); echo Matrix\Functions::trace($matrix); ``` ```php $grid = [ [16, 3, 2, 13], [ 5, 10, 11, 8], [ 9, 6, 7, 12], [ 4, 15, 14, 1], ]; echo Matrix\Functions::trace($grid); ``` ## Decomposition The library also provides classes for matrix decomposition. You can access these using ```php $grid = [ [1, 2], [3, 4], ]; $matrix = new Matrix\Matrix($grid); $decomposition = new Matrix\Decomposition\QR($matrix); $Q = $decomposition->getQ(); $R = $decomposition->getR(); ``` or alternatively us the `Decomposition` factory, identifying which form of decomposition you want to use ```php $grid = [ [1, 2], [3, 4], ]; $matrix = new Matrix\Matrix($grid); $decomposition = Matrix\Decomposition\Decomposition::decomposition(Matrix\Decomposition\Decomposition::QR, $matrix); $Q = $decomposition->getQ(); $R = $decomposition->getR(); ```
| ver. 1.4 |
Github
|
.
| PHP 8.1.29 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка