Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?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);
> $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)) {
< $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}"; < return $functionName($this, ...$arguments);
> return Functions::$functionName($this, ...$arguments);
} // Test for operation calls if (in_array($functionName, self::$operations, true)) {
< $functionName = "\\" . __NAMESPACE__ . "\\{$functionName}"; < return $functionName($this, ...$arguments);
> return Operations::$functionName($this, ...$arguments);
} throw new Exception('Complex Function or Operation does not exist'); } }