http://mathling.com/core/complex/vector  library module

http://mathling.com/core/complex/vector


Complex numbers (vector implementation)

Copyright© Mary Holstege 2021-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

November 2022
Status: Stable

Imports

http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/core/vector
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy"
http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config"
       at "../core/config.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy"
http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy"

Variables

Variable: $i as xs:double*

Variable: $one as xs:double*

Functions

Function: complex
declare function complex($real as xs:double, $imaginary as xs:double) as xs:double*


complex()
Construct a new complex number.

Params
  • real as xs:double: the real part of the number
  • imaginary as xs:double: the imaginary part of the number
Returns
  • xs:double*: complex number
declare function this:complex(
  $real as xs:double,
  $imaginary as xs:double
) as xs:double*
{
  ($real, $imaginary)
}

Function: as-complex
declare function as-complex($real as xs:double) as xs:double*


as-complex()
Cast a real number to a complex.

Params
  • real as xs:double: the real part of the number
Returns
  • xs:double*: complex number
declare function this:as-complex(
  $real as xs:double
) as xs:double*
{
  ($real, 0)
}

Function: is-real
declare function is-real($z as xs:double*) as xs:boolean


is-real()
Test whether the number has no imaginary part. (Within ε).

Params
  • z as xs:double*: the complex number
Returns
  • xs:boolean: true if complex number has only a real part
declare function this:is-real(
  $z as xs:double*
) as xs:boolean
{
  abs(this:pi($z)) < $config:ε
}

Function: as-real
declare function as-real($z as xs:double*) as xs:double


as-real()
Cast a complex number to a double, if possible. Raises error if not.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double: double
declare function this:as-real(
  $z as xs:double*
) as xs:double
{
  if (this:is-real($z)) then this:pr($z)
  else errors:error("UTIL-CAST", ($z,'complex'))
}
Errors

UTIL-CAST if there is an imaginary part

Function: complex-rφd
declare function complex-rφd($r as xs:double, $φ as xs:double) as xs:double*


complex-rφd()
Construct a new complex number using radius and phase angle
(polar repesentation re^iφ).

Params
  • r as xs:double: modulus of complex number
  • φ as xs:double: angle of complex number (degrees)
Returns
  • xs:double*: complex number
declare function this:complex-rφd(
  $r as xs:double,
  $φ as xs:double
) as xs:double*
{
  let $φR := util:radians($φ)
  return
    ($r * math:cos($φR), $r * math:sin($φR))
}

Function: complex-rφ
declare function complex-rφ($r as xs:double, $φ as xs:double) as xs:double*


complex-rφ()
Construct a new complex number using radius and phase angle
(polar repesentation re^iφ).

Params
  • r as xs:double: modulus of complex number
  • φ as xs:double: angle of complex number (radians)
Returns
  • xs:double*: complex number
declare function this:complex-rφ(
  $r as xs:double,
  $φ as xs:double
) as xs:double*
{
  ($r * math:cos($φ), $r * math:sin($φ))
}

Function: pr
declare function pr($z as xs:double*) as xs:double


pr()
Accessor for real part of the complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double: raw real part
declare function this:pr($z as xs:double*) as xs:double
{
  v:px($z)
}

Function: pi
declare function pi($z as xs:double*) as xs:double


pi()
Accessor for imaginary part of the complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double: raw imaginary part
declare function this:pi($z as xs:double*) as xs:double
{
  v:py($z)
}

Function: r
declare function r($z as xs:double*) as xs:integer


r()
Accessor for real part of the complex number, as integer.

Params
  • z as xs:double*: the complex number
Returns
  • xs:integer: real part, rounded
declare function this:r($z as xs:double*) as xs:integer
{
  v:x($z)
}

Function: i
declare function i($z as xs:double*) as xs:integer


i()
Accessor for imaginary part of the complex number, as integer.

Params
  • z as xs:double*: the complex number
Returns
  • xs:integer: imaginary part, rounded
declare function this:i($z as xs:double*) as xs:integer
{
  v:y($z)
}

Function: add
declare function add($z1 as xs:double*, $z2 as xs:double*) as xs:double*


add()
Add two complex numbers.

Params
  • z1 as xs:double*: one complex number
  • z2 as xs:double*: other complex number
Returns
  • xs:double*: z1+z2
declare function this:add(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  $z1=>v:add($z2)
}

Function: sum
declare function sum($zs as xs:double*) as xs:double*


sum()
Add several complex numbers in one go.

Params
  • zs as xs:double*
Returns
  • xs:double*
declare function this:sum(
  $zs as xs:double*
) as xs:double*
{
  sum(for $i in 1 to count($zs) idiv 2 return $zs[2*$i - 1]),
  sum(for $i in 1 to count($zs) idiv 2 return $zs[2*$i])
}

Function: sub
declare function sub($z1 as xs:double*, $z2 as xs:double*) as xs:double*


sub()
Subtract one complex numbers from another.

Params
  • z1 as xs:double*: the first complex number
  • z2 as xs:double*: the complex number to subtract from the first complex number
Returns
  • xs:double*: z1-z2
declare function this:sub(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  $z1=>v:sub($z2)
}

Function: minus
declare function minus($z as xs:double*) as xs:double*


minus()
Negation

Params
  • z as xs:double*1 the first complex number
Returns
  • xs:double*: -z1
declare function this:minus(
  $z as xs:double*
) as xs:double*
{
  $z=>this:times(-1)
}

Function: multiply
declare function multiply($z1 as xs:double*, $z2 as xs:double*) as xs:double*


multiply()
Multiply two complex numbers.

Params
  • z1 as xs:double*: one complex number
  • z2 as xs:double*: other complex number
Returns
  • xs:double*: z1*z2
declare function this:multiply(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  (: (a + bi)*(c + di) = (ac - bd) + (ad + bc)i :)
  (
    this:pr($z1)*this:pr($z2) - this:pi($z1)*this:pi($z2),
    this:pr($z1)*this:pi($z2) + this:pi($z1)*this:pr($z2)
  )
}

Function: pow
declare function pow($z as xs:double*, $pow as xs:integer) as xs:double*


pow()
Raise a complex number to the given (positive integer) power.
A convenience function for repeated multiplication. For general powers use ppow()

Params
  • z as xs:double*: the complex number
  • pow as xs:integer: the power to raise it to
Returns
  • xs:double*: z^pow
declare function this:pow(
  $z as xs:double*,
  $pow as xs:integer
) as xs:double*
{
  if (this:pi($z)=0) then this:complex(math:pow(this:pr($z), $pow), 0)
  else
  switch ($pow)
  case 0 return $this:one
  case 1 return $z
  case 2 return $z=>this:multiply($z)
  case 3 return $z=>this:multiply($z)=>this:multiply($z)
  default return
    (: General: 
     : a + ib = r(cosφ + isinφ) r² = a² + b², φ = atan(b/a)
     : (a + ib)^N = r^N(cos(Nφ) + isin(Nφ))
     :)
    let $r := this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
    let $φ := math:atan2(this:pi($z), this:pr($z))
    let $r_N := math:pow($r,$pow)
    return
      this:complex($r_N*math:cos($pow*$φ), $r_N*math:sin($pow*$φ))
}

Function: ppow
declare function ppow($z as xs:double*, $pow as xs:double*) as xs:double*


ppow()
Principle power for non-integer powers.

Params
  • z as xs:double*: the complex number
  • pow as xs:double*: the power to raise it to
Returns
  • xs:double*: z^pow
declare function this:ppow(
  $z as xs:double*,
  $pow as xs:double*
) as xs:double*
{
  (: a^b = e^(b log(a)) :)
  this:exp(this:log($z)=>this:multiply($pow))
}

Function: log
declare function log($z as xs:double*) as xs:double*


log()
Natural log of complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: log(z)
declare function this:log(
  $z as xs:double*
) as xs:double*
{
  this:complex(math:log(this:modulus($z)), this:angle($z)=>util:radians())
}

Function: sqrt
declare function sqrt($z as xs:double*) as xs:double*


sqrt()
Principal square root of complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: √z
declare function this:sqrt(
  $z as xs:double*
) as xs:double*
{
  if (this:pi($z) = 0) then (
    if (this:pr($z) >= 0) 
    then this:complex(math:sqrt(this:pr($z)), 0)
    else this:complex(0, math:sqrt(-this:pr($z)))
  ) else (
    let $r := this:modulus($z)
    return
      this:complex(
        math:sqrt(($r + this:pr($z)) div 2),
        util:sign(this:pi($z)) * math:sqrt(($r - this:pr($z)) div 2)
      )
  )
}

Function: cbrt
declare function cbrt($z as xs:double*) as xs:double*


cbrt()
Principal cubic root of a complex number. (Highest real part.)
Roots for n integer: for 0 ≤ k < n
z^(1/n) = r^(1/n)(cos((φ + 2kπ)/n) + i sin((φ + 2kπ)/n))

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: z^1/3
declare function this:cbrt(
  $z as xs:double*
) as xs:double*
{
  if (this:pi($z) = 0) then (
    if (this:pr($z) >= 0)
    then this:complex(util:cbrt(this:pr($z)), 0)
    else this:complex(-util:cbrt(-this:pr($z)), 0)
  ) else (
    let $r := this:modulus($z)
    let $φ := this:angle($z)=>util:radians()
    let $rcbrt := util:cbrt($r)
    let $k := 
      head(for $k in 0 to 2
      order by math:cos(($φ + 2*$k*math:pi()) div 3) descending
      return $k)
    return (
      this:complex(
        $rcbrt * math:cos(($φ + 2*$k*math:pi()) div 3),
        $rcbrt * math:sin(($φ + 2*$k*math:pi()) div 3)
      )
    )
  )
}

Function: sin
declare function sin($z as xs:double*) as xs:double*


sin()
Sine of complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: sin(z)
declare function this:sin(
  $z as xs:double*
) as xs:double*
{
  (: 
   : sin(x + iy) = sin(x)cosh(y) + i cos(x)sinh(y)
   :)
  this:complex(
    math:sin(this:pr($z))*util:cosh(this:pi($z)),
    math:cos(this:pr($z))*util:sinh(this:pi($z))
  )
}

Function: cos
declare function cos($z as xs:double*) as xs:double*


cos()
Cosine of complex number.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: cos(z)
declare function this:cos(
  $z as xs:double*
) as xs:double*
{
  (: 
   : cos(x + iy) = cos(x)cosh(y) - i sin(x)sinh(y)
   :)
  this:complex(
    math:cos(this:pr($z))*util:cosh(this:pi($z)),
    -math:sin(this:pr($z))*util:sinh(this:pi($z))
  )
}

Function: sinh
declare function sinh($z as xs:double*) as xs:double*


sinh()
Hyperbolic sine.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: sinh(z)
declare function this:sinh($z as xs:double*) as xs:double*
{
  (: sinh(x + iy) = sinh(x)cos(y) + cosh(x)sin(y) :)
  this:complex(
    util:sinh(this:pr($z))*math:cos(this:pi($z)),
    util:cosh(this:pr($z))*math:sin(this:pi($z))
  )
}

Function: cosh
declare function cosh($z as xs:double*) as xs:double*


cosh()
Hyperbolic cosine.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: cosh(z)
declare function this:cosh($z as xs:double*) as xs:double*
{
  (: cosh(x + iy) = cosh(x)cos(y) - sinh(x)sin(y) :)
  this:complex(
    util:cosh(this:pr($z))*math:cos(this:pi($z)),
    -util:sinh(this:pr($z))*math:sin(this:pi($z))
  )
}

Function: asin
declare function asin($z as xs:double*) as xs:double*


asin()
arcsin(z): -i ln(iz + √(1 - z²))

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: asin(z)
declare function this:asin(
  $z as xs:double*
) as xs:double*
{
  this:log(
    this:multiply($this:i, $z)=>this:add(
      this:sqrt(
        $this:one=>this:sub(
          this:pow($z, 2)
        )
      )
    )
  )=>this:multiply(this:minus($this:i))
}

Function: acos
declare function acos($z as xs:double*) as xs:double*


acos()
arccos(z): (1/i) ln(z + √(z² - 1))

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: acos(z)
declare function this:acos(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:sub( $this:one )
      )
    )
  )=>this:multiply( this:reciprocal($this:i) )
}

Function: asinh
declare function asinh($z as xs:double*) as xs:double*


asinh()
Inverse hyperbolic sine: asinh(z) = ln(z + √(z²+1))

Params
  • z as xs:double*
Returns
  • xs:double*
declare function this:asinh(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:add( $this:one )
      )
    )
  )
}

Function: acosh
declare function acosh($z as xs:double*) as xs:double*


acosh()
Inverse hyperbolic cosine: acosh(z) = ln(z + √(z²-1))

Params
  • z as xs:double*
Returns
  • xs:double*
declare function this:acosh(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:sub( $this:one )
      )
    )
  )
}

Function: exp
declare function exp($z as xs:double*) as xs:double*


exp()
Exponential of z: e^z

Params
  • z as xs:double*: the complex number
Returns
  • xs:double*: e^z
declare function this:exp(
  $z as xs:double*
) as xs:double*
{
  (
    math:exp(this:pr($z)) * math:cos(this:pi($z)),
    math:exp(this:pr($z)) * math:sin(this:pi($z))
  )
}

Function: times
declare function times($z as xs:double*, $k as xs:double) as xs:double*


times()
Multiply a complex by a constant.

Params
  • z as xs:double*: the complex number
  • k as xs:double: the constant to multiply by
Returns
  • xs:double*: k*z
declare function this:times(
  $z as xs:double*,
  $k as xs:double
) as xs:double*
{
  $z=>v:times($k)
}

Function: plus
declare function plus($z as xs:double*, $k as xs:double) as xs:double*


plus()
Add a constant to a complex number. (i.e. add complex number ($k, 0))

Params
  • z as xs:double*: the complex number
  • k as xs:double: the constant to add
Returns
  • xs:double*: z+k
declare function this:plus(
  $z as xs:double*,
  $k as xs:double
) as xs:double*
{
  $z=>this:add(this:complex($k, 0))
}

Function: divide
declare function divide($z1 as xs:double*, $z2 as xs:double*) as xs:double*


divide()
Divide two complex numbers.

Params
  • z1 as xs:double*: one complex number
  • z2 as xs:double*: other complex number
Returns
  • xs:double*: z1/z2
declare function this:divide(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  (: (a + bi)/(c + di) = ((ac + bd) + i(bc - ad))/(c² + d²) :)
  let $r2 := this:pr($z2)*this:pr($z2) + this:pi($z2)*this:pi($z2)
  return
    (
      (this:pr($z1)*this:pr($z2) + this:pi($z1)*this:pi($z2)) div $r2,
      (this:pi($z1)*this:pr($z2) - this:pr($z1)*this:pi($z2)) div $r2
    )
}

Function: divide-by-i
declare function divide-by-i($z as xs:double*) as xs:double*


divide-by-i()
Divide complex numbers by i.

Params
  • z as xs:double*
Returns
  • xs:double*: z/i
declare function this:divide-by-i(
  $z as xs:double*
) as xs:double*
{
  (: (a + bi)/(c + di) = ((ac + bd) + i(bc - ad))/(c² + d²) :)
  (: (a + bi)/(0 + 1i) = ((0a + b1) + i(b0 - a1))/(0² + 1²) = b - ai :)
  ( this:pi($z), -this:pr($z) )
}

Function: modulus
declare function modulus($z as xs:double*) as xs:double


modulus()
Modulus of a complex number. Given z = re^iφ, the r.

Params
  • z as xs:double*: the complex numbers
Returns
  • xs:double: modulus
declare function this:modulus(
  $z as xs:double*
) as xs:double
{
  math:sqrt(this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z))
}

Function: modsq
declare function modsq($z as xs:double*) as xs:double


modsq()
Modulus of a complex number, squared. Given z = re^iφ, r². Used for cases
where we want to avoid a lot of square root calculations.

Params
  • z as xs:double*: the complex number
Returns
  • xs:double: modulus²
declare function this:modsq(
  $z as xs:double*
) as xs:double
{
  this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
}

Function: angle
declare function angle($z as xs:double*) as xs:double


angle()
The angle of the complex number. Given z = re^iφ, the φ in degrees.

Params
  • z as xs:double*: the complex numbers
Returns
  • xs:double: phase angle, in degrees
declare function this:angle(
  $z as xs:double*
) as xs:double
{
  math:atan2(this:pi($z), this:pr($z))=>util:degrees()=>util:remap-degrees()
}

Function: normalize
declare function normalize($z as xs:double*) as xs:double*


normalize()
Normalize the complex number so modulus is 1.

Params
  • z as xs:double*: the complex number to normalize
Returns
  • xs:double*: complex number
declare function this:normalize($z as xs:double*) as xs:double*
{
  let $l := this:modulus($z)
  return this:complex(this:pr($z) div $l, this:pi($z) div $l)
}

Function: is-gauss-prime
declare function is-gauss-prime($z as xs:double*) as xs:boolean


is-gauss-prime()
Test whether complex number is gaussian prime; snaps to integral value.

Params
  • z as xs:double*: complex number to test
Returns
  • xs:boolean: true if z is Gaussian prime
declare function this:is-gauss-prime($z as xs:double*) as xs:boolean
{
  let $a := this:r($z)
  let $b := this:i($z)
  return (
    if ($a * $b != 0) then (
     util:is-prime($a*$a + $b*$b)
    ) else (
      let $c := abs($a + $b)
      return util:is-prime($c) and $c mod 4 = 3
    )
  )
}

Function: conjugate
declare function conjugate($z as xs:double*) as xs:double*


conjugate()
Return the complex conjugate

Params
  • z as xs:double*: complex number
Returns
  • xs:double*: conj(z)
declare function this:conjugate($z as xs:double*) as xs:double*
{
  this:complex(this:pr($z), -this:pi($z))
}

Function: reciprocal
declare function reciprocal($z as xs:double*) as xs:double*


reciprocal()
The reciprocal of z: 1 / z

Params
  • z as xs:double*: complex number
Returns
  • xs:double*: 1/z
declare function this:reciprocal($z as xs:double*) as xs:double*
{
  let $r := this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
  return
   this:complex(this:pr($z) div $r, -this:pi($z) div $r)
}

Function: abs
declare function abs($z as xs:double*) as xs:double*


abs()
The absolute value of z

Params
  • z as xs:double*: complex number
Returns
  • xs:double*: abs(z)
declare function this:abs($z as xs:double*) as xs:double*
{
  this:complex(abs(this:pr($z)), abs(this:pi($z)))
}

Function: quote
declare function quote($zs as xs:double*) as xs:string


quote()
Return a string value of the complex numbers

Params
  • zs as xs:double*: complex numbers
Returns
  • xs:string: string
declare function this:quote($zs as xs:double*) as xs:string
{
  string-join(
    let $chunks :=
      for $z at $i in $zs return (
        if ($i mod 2 = 0) then (
          array {$zs[$i - 1], $z}
        ) else ()
      )
    for $chunk in $chunks
    let $z := $chunk?*
    return (
      let $r := this:pr($z)
      let $i := this:pi($z)
      return
        if ($r = 0 and $i = 0) then "0"
        else if ($r = 0) then $i||"i"
        else if ($i = 0) then string($r)
        else if ($i < 0) then $r||$i||"i"
        else $r||"+"||$i||"i"
    )
    ,
    " "
  )
}

Function: unquote
declare function unquote($zs as xs:string) as xs:double*


unquote()
Parse a string value of a complex number to produce that complex number.
Number is of the form 33+12i

Params
  • zs as xs:string: the string representation of the number
Returns
  • xs:double*: complex number
declare function this:unquote($zs as xs:string) as xs:double*
{
  (: Cases:
   :  2
   : -2
   :     i
   :    3i
   :   -3i
   :  2+3i
   : -2+3i
   :  2-3i
   : -2-3i
   :  2+ i
   :  2- i
   : -2+ i
   : -2- i
   :)
  let $z := number($zs)
  return
    if ($z = $z) then this:complex($z, 0) (: 2 | -2 :)
    else if ($zs = "i") then this:complex(0, 1) (: i :)
    else (
      let $parts := analyze-string($zs, "[-+]?[0123456789.]+")
      return (
        switch (count($parts/fn:match))
        case 0 return
          if ($parts/fn:non-match=("i","+i"))
          then this:complex(0, 1)
          else if ($parts/fn:non-match="-i")
          then this:complex(0, -1)
          else errors:error("UTIL-BADCOMPLEX", $zs)
        case 1 return
          let $next := $parts/fn:match/following-sibling::fn:non-match[1]
          return
            if (empty($next))
            then this:complex(number($parts/fn:match), 0)
            else if ($next="+i")
            then this:complex(number($parts/fn:match), 1)
            else if ($next="-i")
            then this:complex(number($parts/fn:match), -1)
            else this:complex(0, number($parts/fn:match))
        case 2 return
          this:complex(number($parts/fn:match[1]), number($parts/fn:match[2]))
        default return errors:error("UTIL-BADCOMPLEX", $zs)
      )
    )
}

Function: eq
declare function eq($z as xs:double*, $val as xs:double) as xs:boolean


eq()
Is the complex number equal to the double value? That is, does it have
no imaginary part and a real part equal to the value?

Params
  • z as xs:double*: one complex number
  • val as xs:double: a double value
Returns
  • xs:boolean: real(z)=val and imaginary(z)=0 (within epsilon)
declare function this:eq($z as xs:double*, $val as xs:double) as xs:boolean
{
  this:pr($z)=$val and this:is-real($z)
}

Function: same
declare function same($z1 as xs:double*, $z2 as xs:double*) as xs:boolean


same()
Are the two complex numbers the same?

Params
  • z1 as xs:double*: one complex number
  • z2 as xs:double*: other complex number
Returns
  • xs:boolean: z1=z2
declare function this:same($z1 as xs:double*, $z2 as xs:double*) as xs:boolean
{
  this:pr($z1)=this:pr($z2) and
  this:pi($z1)=this:pi($z2)
}

Function: decimal
declare function decimal($zs as xs:double*, $digits as xs:integer) as xs:double*


decimal()
Cast the values to a values with the given number of digits.

Params
  • zs as xs:double*: the complex numbers
  • digits as xs:integer: how many digits to preserve
Returns
  • xs:double*: complex numbers
declare function this:decimal($zs as xs:double*, $digits as xs:integer) as xs:double*
{
  v:decimal($zs, $digits)
}

Original Source Code

xquery version "3.1";
(:~
 : Complex numbers (vector implementation)
 :
 : Copyright© Mary Holstege 2021-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since November 2022
 : @custom:Status Stable
 :)
module namespace this="http://mathling.com/core/complex/vector"; 

import module namespace config="http://mathling.com/core/config"
       at "../core/config.xqy";
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy";
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy";

declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";

declare variable $this:i as xs:double* := this:complex(0, 1);
declare variable $this:one as xs:double* := this:complex(1, 0);

(:~
 : complex()
 : Construct a new complex number.
 :
 : @param $real: the real part of the number
 : @param $imaginary: the imaginary part of the number
 : @return complex number
 :)
declare function this:complex(
  $real as xs:double,
  $imaginary as xs:double
) as xs:double*
{
  ($real, $imaginary)
};

(:~
 : as-complex()
 : Cast a real number to a complex.
 :
 : @param $real: the real part of the number
 : @return complex number
 :)
declare function this:as-complex(
  $real as xs:double
) as xs:double*
{
  ($real, 0)
};

(:~
 : is-real()
 : Test whether the number has no imaginary part. (Within ε). 
 :
 : @param $z: the complex number
 : @return true if complex number has only a real part
 :)
declare function this:is-real(
  $z as xs:double*
) as xs:boolean
{
  abs(this:pi($z)) < $config:ε
};

(:~
 : as-real()
 : Cast a complex number to a double, if possible. Raises error if not.
 :
 : @param $z: the complex number
 : @return double
 : @error UTIL-CAST if there is an imaginary part
 :)
declare function this:as-real(
  $z as xs:double*
) as xs:double
{
  if (this:is-real($z)) then this:pr($z)
  else errors:error("UTIL-CAST", ($z,'complex'))
};

(:~
 : complex-rφd()
 : Construct a new complex number using radius and phase angle 
 : (polar repesentation re^iφ).
 :
 : @param $r: modulus of complex number
 : @param $φ: angle of complex number (degrees)
 : @return complex number
 :)
declare function this:complex-rφd(
  $r as xs:double,
  $φ as xs:double
) as xs:double*
{
  let $φR := util:radians($φ)
  return
    ($r * math:cos($φR), $r * math:sin($φR))
};

(:~
 : complex-rφ()
 : Construct a new complex number using radius and phase angle 
 : (polar repesentation re^iφ).
 :
 : @param $r: modulus of complex number
 : @param $φ: angle of complex number (radians)
 : @return complex number
 :)
declare function this:complex-rφ(
  $r as xs:double,
  $φ as xs:double
) as xs:double*
{
  ($r * math:cos($φ), $r * math:sin($φ))
};

(:~
 : pr()
 : Accessor for real part of the complex number.
 :
 : @param $z: the complex number
 : @return raw real part
 :)
declare function this:pr($z as xs:double*) as xs:double
{
  v:px($z)
};

(:~
 : pi()
 : Accessor for imaginary part of the complex number.
 :
 : @param $z: the complex number
 : @return raw imaginary part
 :)
declare function this:pi($z as xs:double*) as xs:double
{
  v:py($z)
};

(:~
 : r()
 : Accessor for real part of the complex number, as integer.
 :
 : @param $z: the complex number
 : @return real part, rounded
 :)
declare function this:r($z as xs:double*) as xs:integer
{
  v:x($z)
};

(:~
 : i()
 : Accessor for imaginary part of the complex number, as integer.
 :
 : @param $z: the complex number
 : @return imaginary part, rounded
 :)
declare function this:i($z as xs:double*) as xs:integer
{
  v:y($z)
};

(:~
 : add()
 : Add two complex numbers.
 :
 : @param $z1: one complex number
 : @param $z2: other complex number
 : @return z1+z2
 :)
declare function this:add(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  $z1=>v:add($z2)
};

(:~
 : sum()
 : Add several complex numbers in one go.
 :)
declare function this:sum(
  $zs as xs:double*
) as xs:double*
{
  sum(for $i in 1 to count($zs) idiv 2 return $zs[2*$i - 1]),
  sum(for $i in 1 to count($zs) idiv 2 return $zs[2*$i])
};

(:~
 : sub()
 : Subtract one complex numbers from another.
 :
 : @param $z1: the first complex number
 : @param $z2: the complex number to subtract from the first complex number
 : @return z1-z2
 :)
declare function this:sub(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  $z1=>v:sub($z2)
};

(:~
 : minus()
 : Negation
 :
 : @param $z1 the first complex number
 : @return -z1
 :)
declare function this:minus(
  $z as xs:double*
) as xs:double*
{
  $z=>this:times(-1)
};

(:~
 : multiply()
 : Multiply two complex numbers.
 :
 : @param $z1: one complex number
 : @param $z2: other complex number
 : @return z1*z2
 :)
declare function this:multiply(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  (: (a + bi)*(c + di) = (ac - bd) + (ad + bc)i :)
  (
    this:pr($z1)*this:pr($z2) - this:pi($z1)*this:pi($z2),
    this:pr($z1)*this:pi($z2) + this:pi($z1)*this:pr($z2)
  )
};

(:~
 : pow()
 : Raise a complex number to the given (positive integer) power.
 : A convenience function for repeated multiplication. For general powers use ppow()
 : 
 : @param $z: the complex number
 : @param $pow: the power to raise it to
 : @return z^pow
 :)
declare function this:pow(
  $z as xs:double*,
  $pow as xs:integer
) as xs:double*
{
  if (this:pi($z)=0) then this:complex(math:pow(this:pr($z), $pow), 0)
  else
  switch ($pow)
  case 0 return $this:one
  case 1 return $z
  case 2 return $z=>this:multiply($z)
  case 3 return $z=>this:multiply($z)=>this:multiply($z)
  default return
    (: General: 
     : a + ib = r(cosφ + isinφ) r² = a² + b², φ = atan(b/a)
     : (a + ib)^N = r^N(cos(Nφ) + isin(Nφ))
     :)
    let $r := this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
    let $φ := math:atan2(this:pi($z), this:pr($z))
    let $r_N := math:pow($r,$pow)
    return
      this:complex($r_N*math:cos($pow*$φ), $r_N*math:sin($pow*$φ))
};

(:~
 : ppow()
 : Principle power for non-integer powers.
 : @param $z: the complex number
 : @param $pow: the power to raise it to
 : @return z^pow
 :)
declare function this:ppow(
  $z as xs:double*,
  $pow as xs:double*
) as xs:double*
{
  (: a^b = e^(b log(a)) :)
  this:exp(this:log($z)=>this:multiply($pow))
};

(: https://math.stackexchange.com/questions/1103102/how-to-raise-1-to-non-integer-powers :)
(: http://www.milefoot.com/math/complex/functionsofi.htm :)
(: http://en.wikipedia.org/wiki/Complex_number :)

(:~
 : log()
 : Natural log of complex number.
 :
 : @param $z: the complex number
 : @return log(z)
 :)
declare function this:log(
  $z as xs:double*
) as xs:double*
{
  this:complex(math:log(this:modulus($z)), this:angle($z)=>util:radians())
};

(:~
 : sqrt()
 : Principal square root of complex number.
 :
 : @param $z: the complex number
 : @return √z
 :)
declare function this:sqrt(
  $z as xs:double*
) as xs:double*
{
  if (this:pi($z) = 0) then (
    if (this:pr($z) >= 0) 
    then this:complex(math:sqrt(this:pr($z)), 0)
    else this:complex(0, math:sqrt(-this:pr($z)))
  ) else (
    let $r := this:modulus($z)
    return
      this:complex(
        math:sqrt(($r + this:pr($z)) div 2),
        util:sign(this:pi($z)) * math:sqrt(($r - this:pr($z)) div 2)
      )
  )
};

(:~
 : cbrt()
 : Principal cubic root of a complex number. (Highest real part.)
 : Roots for n integer: for 0 ≤ k < n
 : z^(1/n) = r^(1/n)(cos((φ + 2kπ)/n) + i sin((φ + 2kπ)/n)) 
 :
 : @param $z: the complex number
 : @return z^1/3
 :)
declare function this:cbrt(
  $z as xs:double*
) as xs:double*
{
  if (this:pi($z) = 0) then (
    if (this:pr($z) >= 0)
    then this:complex(util:cbrt(this:pr($z)), 0)
    else this:complex(-util:cbrt(-this:pr($z)), 0)
  ) else (
    let $r := this:modulus($z)
    let $φ := this:angle($z)=>util:radians()
    let $rcbrt := util:cbrt($r)
    let $k := 
      head(for $k in 0 to 2
      order by math:cos(($φ + 2*$k*math:pi()) div 3) descending
      return $k)
    return (
      this:complex(
        $rcbrt * math:cos(($φ + 2*$k*math:pi()) div 3),
        $rcbrt * math:sin(($φ + 2*$k*math:pi()) div 3)
      )
    )
  )
};

(:~
 : sin()
 : Sine of complex number.
 :
 : @param $z: the complex number
 : @return sin(z)
 :)
declare function this:sin(
  $z as xs:double*
) as xs:double*
{
  (: 
   : sin(x + iy) = sin(x)cosh(y) + i cos(x)sinh(y)
   :)
  this:complex(
    math:sin(this:pr($z))*util:cosh(this:pi($z)),
    math:cos(this:pr($z))*util:sinh(this:pi($z))
  )
};

(:~
 : cos()
 : Cosine of complex number.
 :
 : @param $z: the complex number
 : @return cos(z)
 :)
declare function this:cos(
  $z as xs:double*
) as xs:double*
{
  (: 
   : cos(x + iy) = cos(x)cosh(y) - i sin(x)sinh(y)
   :)
  this:complex(
    math:cos(this:pr($z))*util:cosh(this:pi($z)),
    -math:sin(this:pr($z))*util:sinh(this:pi($z))
  )
};

(:~ 
 : sinh()
 : Hyperbolic sine.
 :
 : @param $z: the complex number
 : @return sinh(z)
 :)
declare function this:sinh($z as xs:double*) as xs:double*
{
  (: sinh(x + iy) = sinh(x)cos(y) + cosh(x)sin(y) :)
  this:complex(
    util:sinh(this:pr($z))*math:cos(this:pi($z)),
    util:cosh(this:pr($z))*math:sin(this:pi($z))
  )
};

(:~ 
 : cosh()
 : Hyperbolic cosine.
 :
 : @param $z: the complex number
 : @return cosh(z)
 :)
declare function this:cosh($z as xs:double*) as xs:double*
{
  (: cosh(x + iy) = cosh(x)cos(y) - sinh(x)sin(y) :)
  this:complex(
    util:cosh(this:pr($z))*math:cos(this:pi($z)),
    -util:sinh(this:pr($z))*math:sin(this:pi($z))
  )
};


(:~
 : asin()
 : arcsin(z): -i ln(iz + √(1 - z²))
 :
 : @param $z: the complex number
 : @return asin(z)
 :)
 declare function this:asin(
  $z as xs:double*
) as xs:double*
{
  this:log(
    this:multiply($this:i, $z)=>this:add(
      this:sqrt(
        $this:one=>this:sub(
          this:pow($z, 2)
        )
      )
    )
  )=>this:multiply(this:minus($this:i))
};

(:~
 : acos()
 : arccos(z): (1/i) ln(z + √(z² - 1))
 :
 : @param $z: the complex number
 : @return acos(z)
 :)
 declare function this:acos(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:sub( $this:one )
      )
    )
  )=>this:multiply( this:reciprocal($this:i) )
};

(:~
 : asinh()
 : Inverse hyperbolic sine: asinh(z) = ln(z + √(z²+1))
 :)
declare function this:asinh(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:add( $this:one )
      )
    )
  )
};

(:~
 : acosh()
 : Inverse hyperbolic cosine: acosh(z) = ln(z + √(z²-1))
 :)
declare function this:acosh(
  $z as xs:double*
) as xs:double*
{
  this:log(
    $z=>this:add(
      this:sqrt(
        this:pow($z, 2)=>this:sub( $this:one )
      )
    )
  )
};

(:~
 : exp()
 : Exponential of z: e^z
 :
 : @param $z: the complex number
 : @return e^z
 :)
declare function this:exp(
  $z as xs:double*
) as xs:double*
{
  (
    math:exp(this:pr($z)) * math:cos(this:pi($z)),
    math:exp(this:pr($z)) * math:sin(this:pi($z))
  )
};

(:~
 : times()
 : Multiply a complex by a constant.
 :
 : @param $z: the complex number
 : @param $k: the constant to multiply by
 : @return k*z
 :)
declare function this:times(
  $z as xs:double*,
  $k as xs:double
) as xs:double*
{
  $z=>v:times($k)
};

(:~
 : plus()
 : Add a constant to a complex number. (i.e. add complex number ($k, 0))
 :
 : @param $z: the complex number
 : @param $k: the constant to add
 : @return z+k
 :)
declare function this:plus(
  $z as xs:double*,
  $k as xs:double
) as xs:double*
{
  $z=>this:add(this:complex($k, 0))
};

(:~
 : divide()
 : Divide two complex numbers.
 :
 : @param $z1: one complex number
 : @param $z2: other complex number
 : @return z1/z2
 :)
declare function this:divide(
  $z1 as xs:double*,
  $z2 as xs:double*
) as xs:double*
{
  (: (a + bi)/(c + di) = ((ac + bd) + i(bc - ad))/(c² + d²) :)
  let $r2 := this:pr($z2)*this:pr($z2) + this:pi($z2)*this:pi($z2)
  return
    (
      (this:pr($z1)*this:pr($z2) + this:pi($z1)*this:pi($z2)) div $r2,
      (this:pi($z1)*this:pr($z2) - this:pr($z1)*this:pi($z2)) div $r2
    )
};

(:~
 : divide-by-i()
 : Divide complex numbers by i.
 :
 : @param $z
 : @return z/i
 :)
declare function this:divide-by-i(
  $z as xs:double*
) as xs:double*
{
  (: (a + bi)/(c + di) = ((ac + bd) + i(bc - ad))/(c² + d²) :)
  (: (a + bi)/(0 + 1i) = ((0a + b1) + i(b0 - a1))/(0² + 1²) = b - ai :)
  ( this:pi($z), -this:pr($z) )
};

(:~
 : modulus()
 : Modulus of a complex number. Given z = re^iφ, the r.
 :
 : @param $z: the complex numbers 
 : @return modulus
 :)
declare function this:modulus(
  $z as xs:double*
) as xs:double
{
  math:sqrt(this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z))
};

(:~
 : modsq()
 : Modulus of a complex number, squared. Given z = re^iφ, r². Used for cases
 : where we want to avoid a lot of square root calculations.
 :
 : @param $z: the complex number
 : @return modulus²
 :)
declare function this:modsq(
  $z as xs:double*
) as xs:double
{
  this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
};

(:~
 : angle()
 : The angle of the complex number. Given z = re^iφ, the φ in degrees.
 :
 : @param $z: the complex numbers 
 : @return phase angle, in degrees
 :)
declare function this:angle(
  $z as xs:double*
) as xs:double
{
  math:atan2(this:pi($z), this:pr($z))=>util:degrees()=>util:remap-degrees()
};

(:~
 : normalize()
 : Normalize the complex number so modulus is 1.
 :
 : @param $z: the complex number to normalize
 : @return complex number
 :)
declare function this:normalize($z as xs:double*) as xs:double*
{
  let $l := this:modulus($z)
  return this:complex(this:pr($z) div $l, this:pi($z) div $l)
};

(:~
 : is-gauss-prime()
 : Test whether complex number is gaussian prime; snaps to integral value.
 :
 : @param $z: complex number to test
 : @return true if z is Gaussian prime
 :)
declare function this:is-gauss-prime($z as xs:double*) as xs:boolean
{
  let $a := this:r($z)
  let $b := this:i($z)
  return (
    if ($a * $b != 0) then (
     util:is-prime($a*$a + $b*$b)
    ) else (
      let $c := abs($a + $b)
      return util:is-prime($c) and $c mod 4 = 3
    )
  )
};

(:~
 : conjugate()
 : Return the complex conjugate
 :
 : @param $z: complex number 
 : @return conj(z)
 :)
declare function this:conjugate($z as xs:double*) as xs:double*
{
  this:complex(this:pr($z), -this:pi($z))
};

(:~
 : reciprocal()
 : The reciprocal of z: 1 / z
 :
 : @param $z: complex number 
 : @return 1/z
 :)
declare function this:reciprocal($z as xs:double*) as xs:double*
{
  let $r := this:pr($z)*this:pr($z) + this:pi($z)*this:pi($z)
  return
   this:complex(this:pr($z) div $r, -this:pi($z) div $r)
};

(:~
 : abs()
 : The absolute value of z
 :
 : @param $z: complex number 
 : @return abs(z)
 :)
declare function this:abs($z as xs:double*) as xs:double*
{
  this:complex(abs(this:pr($z)), abs(this:pi($z)))
};

(:~
 : quote()
 : Return a string value of the complex numbers
 :
 : @param $zs: complex numbers
 : @return string
 :)
declare function this:quote($zs as xs:double*) as xs:string
{
  string-join(
    let $chunks :=
      for $z at $i in $zs return (
        if ($i mod 2 = 0) then (
          array {$zs[$i - 1], $z}
        ) else ()
      )
    for $chunk in $chunks
    let $z := $chunk?*
    return (
      let $r := this:pr($z)
      let $i := this:pi($z)
      return
        if ($r = 0 and $i = 0) then "0"
        else if ($r = 0) then $i||"i"
        else if ($i = 0) then string($r)
        else if ($i < 0) then $r||$i||"i"
        else $r||"+"||$i||"i"
    )
    ,
    " "
  )
};

(:~
 : unquote()
 : Parse a string value of a complex number to produce that complex number.
 : Number is of the form 33+12i
 :
 : @param $zs: the string representation of the number
 : @return complex number
 :)
declare function this:unquote($zs as xs:string) as xs:double*
{
  (: Cases:
   :  2
   : -2
   :     i
   :    3i
   :   -3i
   :  2+3i
   : -2+3i
   :  2-3i
   : -2-3i
   :  2+ i
   :  2- i
   : -2+ i
   : -2- i
   :)
  let $z := number($zs)
  return
    if ($z = $z) then this:complex($z, 0) (: 2 | -2 :)
    else if ($zs = "i") then this:complex(0, 1) (: i :)
    else (
      let $parts := analyze-string($zs, "[-+]?[0123456789.]+")
      return (
        switch (count($parts/fn:match))
        case 0 return
          if ($parts/fn:non-match=("i","+i"))
          then this:complex(0, 1)
          else if ($parts/fn:non-match="-i")
          then this:complex(0, -1)
          else errors:error("UTIL-BADCOMPLEX", $zs)
        case 1 return
          let $next := $parts/fn:match/following-sibling::fn:non-match[1]
          return
            if (empty($next))
            then this:complex(number($parts/fn:match), 0)
            else if ($next="+i")
            then this:complex(number($parts/fn:match), 1)
            else if ($next="-i")
            then this:complex(number($parts/fn:match), -1)
            else this:complex(0, number($parts/fn:match))
        case 2 return
          this:complex(number($parts/fn:match[1]), number($parts/fn:match[2]))
        default return errors:error("UTIL-BADCOMPLEX", $zs)
      )
    )
};

(:~
 : eq()
 : Is the complex number equal to the double value? That is, does it have
 : no imaginary part and a real part equal to the value?
 :
 : @param $z: one complex number
 : @param $val: a double value
 : @return real(z)=val and imaginary(z)=0 (within epsilon)
 :)
declare function this:eq($z as xs:double*, $val as xs:double) as xs:boolean
{
  this:pr($z)=$val and this:is-real($z)
};

(:~
 : same()
 : Are the two complex numbers the same?
 :
 : @param $z1: one complex number
 : @param $z2: other complex number
 : @return z1=z2
 :)
declare function this:same($z1 as xs:double*, $z2 as xs:double*) as xs:boolean
{
  this:pr($z1)=this:pr($z2) and
  this:pi($z1)=this:pi($z2)
};

(:~
 : decimal()
 : Cast the values to a values with the given number of digits.
 :
 : @param $zs: the complex numbers
 : @param $digits: how many digits to preserve
 : @return complex numbers
 :)
declare function this:decimal($zs as xs:double*, $digits as xs:integer) as xs:double*
{
  v:decimal($zs, $digits)
};