MathComplexExtension
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RicardoSantos

//@version=5

// @description A set of utility functions to handle complex numbers.
library(title='MathComplexExtension')

// references:
//      https://en.wikipedia.org/wiki/Complex_number
//      js: https://rosettacode.org/wiki/Fast_Fourier_transform
//      https://github.com/trekhleb/javascript-algorithms/blob/477f30b0bdac6024874d2976de1eca7afe0176dd/src/algorithms/math/complex-number/ComplexNumber.js#L3
//      https://github.com/infusion/Complex.js/
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs



//#region   -> imports:

import RicardoSantos/DebugConsole/11 as console
logger = console.Console.new().init()
import RicardoSantos/CommonTypesMath/1 as TMath
import RicardoSantos/MathComplexCore/3 as complex
import RicardoSantos/MathComplexOperator/2 as complex_op
import RicardoSantos/MathConstants/1 as mc

//#endregion



//#region   -> Methods:

// get_phase         () {

// @function The phase value of complex number complex number.
// @param this       complex . Complex number in the form `(real, imaginary)`.
// @param in_radians bool    . Value for the type of angle value, default=true, options=(true: radians, false: degrees)
// @returns float. Value with phase.
export method get_phase (TMath.complex this, bool in_radians=true) =>
    float _phase = math.atan(math.abs(this.im) / math.abs(this.re))

    if not in_radians
        _phase := _phase * (180 / math.pi) // radian to degrees
    else
        if this.re < 0 and this.im > 0
            _phase := math.pi - nz(_phase)
        else if this.re < 0 and this.im < 0
            _phase := -(math.pi - nz(_phase))
        else if this.re > 0 and this.im < 0
            _phase := -nz(_phase)
        else if this.re == 0 and this.im > 0
            _phase := math.pi / 2.0
        else if this.re == 0 and this.im < 0
            _phase := -math.pi / 2.0
        else if this.re < 0 and this.im == 0
            _phase := math.pi
        else if this.re > 0 and this.im == 0
            _phase := 0.0
        else if this.re == 0 and this.im == 0
            // More correctly would be to set 'indeterminate'.
            // But just for simplicity reasons let's set zero.
            _phase := 0.0
        else
            na
    _phase

// }
// natural_logarithm () {
// remarks:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L350
//      https://www.mathworks.com/help/matlab/ref/log.html

// @function Natural logarithm of complex number (base E).
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method natural_logarithm (TMath.complex this) =>
    if complex.is_real_non_negative(this)
        complex.new(math.log(this.re), 0.0)
    else
        float _re = math.pow(this.re, 2)
        float _im = math.pow(this.im, 2)
        complex.new(0.5 * math.log(complex_op.magnitude_squared(this)), get_phase(this, true))

// }
// common_logarithm  () {
// remarks:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L350
//      https://www.mathworks.com/help/matlab/ref/log10.html

// @function Common logarithm of complex number (base 10).
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method common_logarithm (TMath.complex this) =>
    complex_op.divide(natural_logarithm(this), complex.new(mc.Ln10(), 0.0))

// }
// logarithm         () {
// remarks:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L350
//      https://www.mathworks.com/help/matlab/ref/log10.html

// @function Common logarithm of complex number (custom base).
// @param this complex . Complex number in the form `(real, imaginary)`.
// @param base float   . Base value.
// @returns complex. Complex number.
export method logarithm (TMath.complex this, float base) =>
    complex_op.divide(natural_logarithm(this), complex.new(math.log(base), 0.0))

// }
// power             () {
// remarks:
// https://www.superprof.co.uk/resources/academic/maths/arithmetic/complex-numbers/power-of-a-complex-number.html

// @function Raise complex number with complex_exponent. 
// @param this     complex . Complex number in the form `(real, imaginary)`.
// @param exponent complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method power (TMath.complex this, TMath.complex exponent) =>
    float _a_re = this.re
    float _a_im = this.im
    float _b_re = exponent.re
    float _b_im = exponent.im
    if complex.is_zero(this)
        if complex.is_zero(exponent)
            complex.one()
        else if _b_re > 0.0
            complex.zero()
        else if _b_re < 0.0
            if _b_im == 0
                complex.new(float(na), 0.0) // "+∞+0i"
            else
                complex.nan() // "+∞+∞i"
        complex.nan()
    else
        // (exponent * natural_logarithm()).exponential()
        complex_op.exponential(complex_op.multiply(exponent, natural_logarithm(this)))

// }
// root              () {
// remarks:
//      https://www.intmath.com/complex-numbers/7-powers-roots-demoivre.php

// @function Raise complex number with inverse of complex_exponent. 
// @param this     complex . Complex number in the form `(real, imaginary)`.
// @param exponent complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method root (TMath.complex this, TMath.complex exponent) =>
    power(this, complex_op.divide(complex.one(), exponent))

// }
// square            () {
// remarks:
//      https://docs.microsoft.com/en-us/dotnet/api/system.numerics.complex.pow?view=net-5.0

// @function Square of complex number (power 2). 
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method square (TMath.complex this) =>
    float _re = this.re
    if complex.is_real(this)
        complex.new(math.pow(_re, 2.0), 0.0)
    else
        float _im = this.im
        complex.new(math.pow(_re, 2.0) - math.pow(_im, 2.0), 2.0 * _re * _im)

// }
// square_root       () {
// remarks:
//      https://www.brainkart.com/article/Square-roots-of-a-complex-number_39098/

// @function Square root of complex number (power 1/2). 
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns complex. Complex number.
export method square_root (TMath.complex this) =>
    float _re = this.re
    if complex.is_real_non_negative(this)
        complex.new(math.sqrt(_re), 0.0)
    else
        float _im = this.im
        float _abs_re = math.abs(_re)
        float _abs_im = math.abs(_im)
        float _w = 0.0
        
        if _abs_re >= _abs_im
            float _ratio = _im / _re
            _w := math.sqrt(_abs_re) * math.sqrt(0.5 * (1.0 + math.sqrt(1.0 + math.pow(_ratio, 2))))
        else
            float _ratio = _re / _im
            _w := math.sqrt(_abs_im) * math.sqrt(0.5 * (math.abs(_ratio) + math.sqrt(1.0 + math.pow(_ratio, 2))))
        
        if _re >= 0.0
            complex.new(_w, _im / (2.0 * _w))
        else if _im >= 0.0
            complex.new(_abs_im / (2.0 * _w), _w)
        else
            complex.new(_abs_im / (2.0 * _w), -_w)

// }
// square_roots      () {
// remarks:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L492

// @function Square root of complex number (power 1/2). 
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns `array<complex>`. Array with 2 complex numbers, (positive, negative).
export method square_roots (TMath.complex this) =>
    _principal = square_root(this)
    array.from(_principal, complex_op.negative(_principal))

// }
// cubic_roots       () {
// remarks:
//      https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L501

// @function Square root of complex number (power 1/2). 
// @param this complex . Complex number in the form `(real, imaginary)`.
// @returns `array<complex>`. Array with 3 complex numbers, (neutral, positive, negative).
export method cubic_roots (TMath.complex this) =>
    float _r = math.pow(complex_op.magnitude(this), 1.0 / 3.0)
    float _theta = get_phase(this, true)
    float _shift = mc.Pi2() / 3.0
    array.from(
     complex.from_polar_coordinates(_r, _theta)          , 
     complex.from_polar_coordinates(_r, _theta + _shift) , 
     complex.from_polar_coordinates(_r, _theta - _shift) )

// }
// to_polar_form     () {
// remarks:
//      https://www.electronics-tutorials.ws/accircuits/complex-numbers.html

// @function The polar form value of complex number.
// @param this complex . Complex number in the form `(real, imaginary)`.
// @param in_radians boolean, value for the type of angle value, default=true, options=(true: radians, false: degrees)
// @returns complex. Complex number, (radius, phase).
export method to_polar_form (TMath.complex this, bool in_radians=true) =>
    complex.new(complex_op.radius(this), get_phase(this, in_radians))

// }

//#endregion

if barstate.islastconfirmedhistory
    A = complex.new(7.0, -6.0)
    B = complex.new(-3.0, 4.0)
    logger.queue_one(str.format('input parameters: (A: {0}, B: {1})', complex.to_string(A, '#.##'), complex.to_string(B, '#.##')))

    logger.queue_one(str.format('\n##   Extended       ##\n', ''))
    logger.queue_one(str.format('phase of A = (radeans: {0}, in degrees: {1})', get_phase(A, true), get_phase(A, false)))
    logger.queue_one(str.format('Natural Logarithm (E) of A = {0}', complex.to_string(natural_logarithm(A), '#.##')))
    logger.queue_one(str.format('Common Logarithm (10) of A = {0}', complex.to_string(common_logarithm(A), '#.##')))
    logger.queue_one(str.format('Logarithm (base=10) of A = {0}', complex.to_string(logarithm(A, 10.0), '#.##')))
    logger.queue_one(str.format('Power of A^B = {0}', complex.to_string(power(A, B), '#.##')))
    logger.queue_one(str.format('Root of B√A = {0}', complex.to_string(root(A, B), '#.##')))
    logger.queue_one(str.format('Square of A = {0}', complex.to_string(square(A), '#.##')))
    logger.queue_one(str.format('Square Root of A = {0}', complex.to_string(square_root(A), '#.##')))
    Sr = square_roots(A)
    Cr = cubic_roots(A)
    logger.queue_one(str.format('Square Roots of A = ({0}, {1})', complex.to_string(Sr.get(0), '#.##'), complex.to_string(Sr.get(1), '#.##')))
    logger.queue_one(str.format('Cubic Roots of A = ({0}, {1}, {2})', complex.to_string(Cr.get(0), '#.##'), complex.to_string(Cr.get(1), '#.##'), complex.to_string(Cr.get(2), '#.##')))

    logger.queue_one(str.format('Polar form of A = {0}', complex.to_string(to_polar_form(A), '#.##')))

// update console
logger.update()

