MathComplexExtension.pinescript
Copy
// 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
// imports:
import RicardoSantos/DebugConsole/2 as console
// console.init(), console.queue(), console.update()
[__T, __C] = console.init(20) //initiate console configuration variables.
//
import RicardoSantos/MathConstants/1 as mc
// mc.Pi2() -> cubic_roots()
import RicardoSantos/MathComplexCore/3 as complex
//
import RicardoSantos/MathComplexOperator/2 as complex_op
//
// @function The phase value of complex number complex_number.
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @param in_radians boolean, value for the type of angle value, default=true, options=(true: radians, false: degrees)
// @returns float value with phase.
export get_phase (float[] complex_number, bool in_radians=true) => //{
float _re = complex.get_real(complex_number)
float _im = complex.get_imaginary(complex_number)
float _phase = math.atan(math.abs(_im) / math.abs(_re))
if not in_radians
_phase := _phase * (180 / math.pi) // radian to degrees
else
if _re < 0 and _im > 0
_phase := math.pi - nz(_phase)
else if _re < 0 and _im < 0
_phase := -(math.pi - nz(_phase))
else if _re > 0 and _im < 0
_phase := -nz(_phase)
else if _re == 0 and _im > 0
_phase := math.pi / 2.0
else if _re == 0 and _im < 0
_phase := -math.pi / 2.0
else if _re < 0 and _im == 0
_phase := math.pi
else if _re > 0 and _im == 0
_phase := 0.0
else if _re == 0 and _im == 0
// More correctly would be to set 'indeterminate'.
// But just for simplicity reasons let's set zero.
_phase := 0.0
else
na
_phase
//{ usage:
// console.queue_one(__C, str.format('phase of complex number "5.2+3.0i" = in radeans: {0, number, #.##}, in degrees: {1, number, #.##}', get_phase(new(5.2, 3.0), true), get_phase(new(5.2, 3.0), false) )) // 36.0
//}}
// @function Natural logarithm of complex number (base E).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, complex number.
export natural_logarithm (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> natural_logarithm(): "complex_number" has the wrong size.')
array.new_float(na)
else if complex.is_real_non_negative(complex_number)
complex.new(math.log(complex.get_real(complex_number)), 0.0)
else
float _re = math.pow(complex.get_real(complex_number), 2)
float _im = math.pow(complex.get_imaginary(complex_number), 2)
complex.new(0.5 * math.log(complex_op.magnitude_squared(complex_number)), get_phase(complex_number, true))
//{ usage:
// console.queue_one(__C, str.format('natural logarithm of complex number "-1.0+0i" = {0} // iπ', to_string(natural_logarithm(new(-1.0, 0.0)), '#.##'))) // "0.0+3.1416i", "iπ"
//{ remarks:
// https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L350
// https://www.mathworks.com/help/matlab/ref/log.html
//}}}
// @function Common logarithm of complex number (base 10).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, complex number.
export common_logarithm (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> common_logarithm(): "complex_number" has the wrong size.')
array.new_float(na)
else
complex_op.divide(natural_logarithm(complex_number), complex.new(mc.Ln10(), 0.0))
//{ usage:
// console.queue_one(__C, str.format('common logarithm of complex number "0-2i" = {0} ', to_string(common_logarithm(new(0.0, -2.0)), '#.##'))) // "0.301-0.6822i"
//{ 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 complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @param base float, base value.
// @returns float array, complex number.
export logarithm (float[] complex_number, float base) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> common_logarithm(): "complex_number" has the wrong size.')
array.new_float(na)
else
complex_op.divide(natural_logarithm(complex_number), complex.new(math.log(base), 0.0))
//{ usage:
// console.queue_one(__C, str.format('logarithm of complex number "0-2i" = {0} ', to_string(logarithm(new(0.0, -2.0), 10.0), '#.##'))) // "0.301-0.6822i"
//{ remarks:
// https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L350
// https://www.mathworks.com/help/matlab/ref/log10.html
//}}}
// @function Raise complex_number with complex_exponent.
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @param complex_exponent float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, pseudo complex number in the form of a array [real, imaginary]
export power (float[] complex_number, float[] complex_exponent) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> power(): "complex_number" has the wrong size.')
array.new_float(na)
else
float _a_re = complex.get_real(complex_number)
float _a_im = complex.get_imaginary(complex_number)
float _b_re = complex.get_real(complex_exponent)
float _b_im = complex.get_imaginary(complex_exponent)
if complex.is_zero(complex_number)
if complex.is_zero(complex_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(complex_exponent, natural_logarithm(complex_number)))
//{ usage:
// console.queue_one(__C, str.format('power of complex number "2+1i" ^ "4+0i" = {0} ', to_string(power(new(2.0, 1.0), new(4.0, 0.0)), '#.##'))) // "-7+24i"
//{ remarks:
// https://www.superprof.co.uk/resources/academic/maths/arithmetic/complex-numbers/power-of-a-complex-number.html
//}}}
// @function Raise complex_number with inverse of complex_exponent.
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @param complex_exponent float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, pseudo complex number in the form of a array [real, imaginary]
export root (float[] complex_number, float[] complex_exponent) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> root(): "complex_number" has the wrong size.')
array.new_float(na)
else if not complex.is_complex(complex_exponent)
runtime.error('MathComplex -> root(): "complex_exponent" has the wrong size.')
array.new_float(na)
else
power(complex_number, complex_op.divide(complex.one(), complex_exponent))
//{ usage:
// console.queue_one(__C, str.format('root of complex number ("2+0i" √ "-5+12i") = {0} ', to_string(root(new(-5.0, 12.0), new(2.0, 0.0)), '#.##'))) //
//{ remarks:
// https://www.intmath.com/complex-numbers/7-powers-roots-demoivre.php
//}}}
// @function Square of complex_number (power 2).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, pseudo complex number in the form of a array [real, imaginary]
export square (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> square(): "complex_number" has the wrong size.')
array.new_float(na)
else
float _re = complex.get_real(complex_number)
if complex.is_real(complex_number)
complex.new(math.pow(_re, 2.0), 0.0)
else
float _im = complex.get_imaginary(complex_number)
complex.new(math.pow(_re, 2.0) - math.pow(_im, 2.0), 2.0 * _re * _im)
//{ usage:
// console.queue_one(__C, str.format('square of complex number "12-6i"^2 = {0} ', to_string(square(new(12.0, -6.0)), '#.##'))) //
//{ remarks:
// https://docs.microsoft.com/en-us/dotnet/api/system.numerics.complex.pow?view=net-5.0
//}}}
// @function Square root of complex_number (power 1/2).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns float array, pseudo complex number in the form of a array [real, imaginary]
export square_root (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> square_root(): "complex_number" has the wrong size.')
array.new_float(na)
else
float _re = complex.get_real(complex_number)
if complex.is_real_non_negative(complex_number)
complex.new(math.sqrt(_re), 0.0)
else
float _im = complex.get_imaginary(complex_number)
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)
//{ usage:
// console.queue_one(__C, str.format('square root of complex number "6-8i" = {0} ', to_string(square_root(new(6.0, -8.0)), '#.##'))) //
//{ remarks:
// https://www.brainkart.com/article/Square-roots-of-a-complex-number_39098/
//}}}
// @function Square root of complex_number (power 1/2).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns tuple with 2 complex numbers.
export square_roots (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> square_roots(): "complex_number" has the wrong size.')
[array.new_float(na), array.new_float(na)]
else
_principal = square_root(complex_number)
[_principal, complex_op.negative(_principal)]
//{ usage:
// [sr_pos, sr_neg] = square_roots(new(6.0, -8.0))
// console.queue_one(__C, str.format('square roots of complex number "6-8i" = {0} ___ {1}', to_string(sr_pos, '#.##'), to_string(sr_neg, '#.##'))) //
//{ remarks:
// https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L492
//}}}
// @function Square root of complex_number (power 1/2).
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @returns tuple with 2 complex numbers.
export cubic_roots (float[] complex_number) => //{
if not complex.is_complex(complex_number)
runtime.error('MathComplex -> square_roots(): "complex_number" has the wrong size.')
[array.new_float(na), array.new_float(na), array.new_float(na)]
else
float _r = math.pow(complex_op.magnitude(complex_number), 1.0 / 3.0)
float _theta = get_phase(complex_number, true)
float _shift = mc.Pi2() / 3.0
[complex.from_polar_coordinates(_r, _theta), complex.from_polar_coordinates(_r, _theta + _shift), complex.from_polar_coordinates(_r, _theta - _shift)]
//{ usage:
// [cr_neu, cr_pos, cr_neg] = cubic_roots(new(6.0, -8.0))
// console.queue_one(__C, str.format('cubic roots of complex number "6-8i" = {0} ___ {1} ___ {2}', to_string(cr_neu, '#.##'), to_string(cr_pos, '#.##'), to_string(cr_neg, '#.##'))) //
//{ remarks:
// https://github.com/mathnet/mathnet-numerics/blob/a50d68d52def605a53d129cc60ea3371a2e97548/src/Numerics/Complex32.cs#L501
//}}}
// @function The polar form value of complex_number.
// @param complex_number float array, pseudo complex number in the form of a array [real, imaginary].
// @param in_radians boolean, value for the type of angle value, default=true, options=(true: radians, false: degrees)
// @returns float array, pseudo complex number in the form of a array [real, imaginary]
// ** returns a array [radius, phase]
export to_polar_form (float[] complex_number, bool in_radians=true) => //{
complex.new(complex_op.radius(complex_number), get_phase(complex_number, in_radians))
//{ usage:
// console.queue_one(__C, str.format('polar form of complex number "5.2+3.0i" = in radeans: {0} , in degrees: {1}', to_string(to_polar_form(new(5.2, 3.0), true), '#.##'), to_string(to_polar_form(new(5.2, 3.0), false), '#.##'))) //
// cn = to_polar_form(new(5.2, 3.0), false)
// plot(get_real(cn)), plot(get_imaginary(cn))
// // reactangular form "5.2+3.0i" converts to: "6+0.5i" in radeans || "6+30i" in degrees
//{ remarks:
// https://www.electronics-tutorials.ws/accircuits/complex-numbers.html
//}}}
if barstate.islastconfirmedhistory
A = complex.new(7.0, -6.0)
B = complex.new(-3.0, 4.0)
console.queue_one(__C, str.format('input parameters: (A: {0}, B: {1})', complex.to_string(A, '#.##'), complex.to_string(B, '#.##')))
console.queue_one(__C, str.format('\n## Extended ##\n', ''))
console.queue_one(__C, str.format('phase of A = (radeans: {0}, in degrees: {1})', get_phase(A, true), get_phase(A, false)))
console.queue_one(__C, str.format('Natural Logarithm (E) of A = {0}', complex.to_string(natural_logarithm(A), '#.##')))
console.queue_one(__C, str.format('Common Logarithm (10) of A = {0}', complex.to_string(common_logarithm(A), '#.##')))
console.queue_one(__C, str.format('Logarithm (base=10) of A = {0}', complex.to_string(logarithm(A, 10.0), '#.##')))
console.queue_one(__C, str.format('Power of A^B = {0}', complex.to_string(power(A, B), '#.##')))
console.queue_one(__C, str.format('Root of B√A = {0}', complex.to_string(root(A, B), '#.##')))
console.queue_one(__C, str.format('Square of A = {0}', complex.to_string(square(A), '#.##')))
console.queue_one(__C, str.format('Square Root of A = {0}', complex.to_string(square_root(A), '#.##')))
[Spos, Sneg] = square_roots(A)
[Cneu, Cpos, Cneg] = cubic_roots(A)
console.queue_one(__C, str.format('Square Roots of A = ({0}, {1})', complex.to_string(Spos, '#.##'), complex.to_string(Sneg, '#.##')))
console.queue_one(__C, str.format('Cubic Roots of A = ({0}, {1}, {2})', complex.to_string(Cneu, '#.##'), complex.to_string(Cpos, '#.##'), complex.to_string(Cneg, '#.##')))
console.queue_one(__C, str.format('Polar form of A = {0}', complex.to_string(to_polar_form(A), '#.##')))
// update console
console.update(__T, __C)
Show all 300 lines