core API

The root package Luna-Flow/luna-complex defines Complex[T], the complex numbers a+bia + bi over any scalar type T, with construction, in-place mutation, conjugation, the arithmetic operators and the luna-generic structure traits. It contains no analytic functions; those are in float_backend. The algebra behind the instances is in the core design.

Source: src/complex.mbt, src/complex_traits.mbt and src/extends.mbt.

Importing

import {
  "Luna-Flow/luna-complex" @complex,
}

The examples use the alias @complex.

The type

Complex

A complex number with a real part re and an imaginary part im.

pub(all) struct Complex[T] {
  mut re : T
  mut im : T
} derive(Eq, @debug.Debug)

The value is re+im i\texttt{re} + \texttt{im}\,i with i2=−1i^2 = -1. Both fields are public and mutable, and the struct is pub(all), so code outside the package can read, write and construct it with a record literal. Complex[T] is a reference type: two bindings to the same value see each other’s updates.

derive(Eq) compares both parts with T’s equality (for Double, 0.0 == -0.0 and NaN is unequal to itself). derive(Debug) prints the record form { re: …, im: … }.

Construction and mutation

Complex::new

Builds re+im ire + im\,i.

pub fn[T] Complex::new(T, T) -> Complex[T]

Complex::set

Overwrites both parts in place.

pub fn[T] Complex::set(Complex[T], T, T) -> Unit

Complex::set_re

Overwrites the real part in place.

pub fn[T] Complex::set_re(Complex[T], T) -> Unit

Complex::set_im

Overwrites the imaginary part in place.

pub fn[T] Complex::set_im(Complex[T], T) -> Unit
test "construct and mutate" {
  let z = @complex.Complex::new(1.0, 2.0)
  assert_eq(z.re, 1.0)
  z.set(3.0, 4.0)
  z.set_im(-1.0)
  assert_eq(z, @complex.Complex::new(3.0, -1.0))
}

Identities

Complex::zero

Returns 0+0i0 + 0i.

pub fn[T : @luna-generic.Zero] Complex::zero() -> Complex[T]

Complex::one

Returns 1+0i1 + 0i.

pub fn[T : @luna-generic.One + @luna-generic.Zero] Complex::one() -> Complex[T]

Both are the promoted methods of the Zero and One instances.

Arithmetic

Each operator is available as an operator and as a promoted method.

ItemOperatorResultBound on T
Complex::addz + w(a+c)+(b+d)i(a + c) + (b + d)iAdd
Complex::subz - w(a−c)+(b−d)i(a - c) + (b - d)iSub
Complex::neg-z−a−bi-a - biNeg
Complex::mulz * w(ac−bd)+(ad+bc)i(ac - bd) + (ad + bc)iMul + Add + Sub
Complex::divz / w(ac+bd)+(bc−ad)ic2+d2\dfrac{(ac + bd) + (bc - ad)i}{c^2 + d^2}Field

Here z=a+biz = a + bi and w=c+diw = c + di.

Complex::add

Adds componentwise.

pub fn[T : Add] Complex::add(Complex[T], Complex[T]) -> Complex[T]
pub impl[T : Add] Add for Complex[T]

Complex::sub

Subtracts componentwise.

pub fn[T : Sub] Complex::sub(Complex[T], Complex[T]) -> Complex[T]
pub impl[T : Sub] Sub for Complex[T]

Complex::neg

Negates both parts.

pub fn[T : Neg] Complex::neg(Complex[T]) -> Complex[T]
pub impl[T : Neg] Neg for Complex[T]

Complex::mul

Multiplies with i2=−1i^2 = -1, using four multiplications.

pub fn[T : Mul + Add + Sub] Complex::mul(Complex[T], Complex[T]) -> Complex[T]
pub impl[T : Mul + Add + Sub] Mul for Complex[T]

Complex::div

Divides by multiplying with the inverse of the squared modulus.

pub fn[T : @luna-generic.Field] Complex::div(Complex[T], Complex[T]) -> Complex[T]
pub impl[T : @luna-generic.Field] Div for Complex[T]

It computes n=c2+d2n = c^2 + d^2, then Inverse::inv(n), and multiplies both parts of zwˉz\bar w by it. The formula is not scaled: for Double, c2+d2c^2 + d^2 overflows for ∣w∣≳10154|w| \gtrsim 10^{154} and underflows for ∣w∣≲10−162|w| \lesssim 10^{-162}, and Inverse::inv of Double aborts with Double::inv: division by zero when nn is zero, including after underflow. Use @fb.div for robust floating-point division.

test "complex arithmetic" {
  let z = @complex.Complex::new(1.0, 2.0)
  let w = @complex.Complex::new(3.0, -1.0)
  assert_eq(z + w, @complex.Complex::new(4.0, 1.0))
  assert_eq(z - w, @complex.Complex::new(-2.0, 3.0))
  assert_eq(z * w, @complex.Complex::new(5.0, 5.0))
  assert_eq(z * w / w, z)
  assert_eq(-z, @complex.Complex::new(-1.0, -2.0))
}

Conjugate and inverse

Complex::conjugate

Returns zˉ=a−bi\bar z = a - bi.

pub fn[T : Neg] Complex::conjugate(Complex[T]) -> Complex[T]
pub impl[T : Neg] @luna-generic.Conjugate for Complex[T]

Complex::inv

Returns z−1=zˉ/(a2+b2)z^{-1} = \bar z / (a^2 + b^2).

pub fn[T : @luna-generic.Field] Complex::inv(Complex[T]) -> Complex[T]
pub impl[T : @luna-generic.Field] @luna-generic.Inverse for Complex[T]

It has the same unscaled formula and the same abort on a zero modulus as Complex::div.

test "conjugate and inverse" {
  let z = @complex.Complex::new(3.0, 4.0)
  assert_eq(z.conjugate(), @complex.Complex::new(3.0, -4.0))
  assert_eq(z * z.conjugate(), @complex.Complex::new(25.0, 0.0))
  assert_eq(z.inv(), @complex.Complex::new(0.12, -0.16))
}

Equality and text

Complex::equal

Compares both parts; the promoted method of the derived Eq. Prefer ==.

pub fn[T : Eq] Complex::equal(Complex[T], Complex[T]) -> Bool

Complex::to_string

Formats zz as "<re> + <im>i" using T’s Show.

pub fn[T : Show] Complex::to_string(Complex[T]) -> String
pub impl[T : Show] Show for Complex[T]

The sign of the imaginary part is printed by T, so 1−2i1 - 2i prints as 1 + -2i. String interpolation "\{z}" uses the same text.

test "text form" {
  inspect(@complex.Complex::new(1.5, -2.0), content="1.5 + -2i")
  debug_inspect(@complex.Complex::new(1, 2), content="{ re: 1, im: 2 }")
}

Structure instances

Complex[T] implements the luna-generic structure traits under the bounds below. The core design derives each law.

InstanceBound on T
ZeroZero
OneOne + Zero
AddMonoidAdd + Zero
AddGroupAdd + Zero + Neg + Sub
MulMonoidRing
SemiringRing
RingRing
InverseField
MulGroupField
FieldField
ConjugateNeg
fn[T : @lg.Field] average(a : T, b : T) -> T {
  let two = @lg.One::one() + @lg.One::one()
  (a + b) / two
}

test "generic field code accepts complex numbers" {
  let m = average(@complex.Complex::new(1.0, 2.0), @complex.Complex::new(3.0, 0.0))
  assert_eq(m, @complex.Complex::new(2.0, 1.0))
}

Deprecated

These method forms come from the trait instances and are hidden from the interface file; they warn when used outside the package.

Deprecated methodReplacement
z.not_equal(w)z != w
z.output(logger)z.to_string() or "\{z}"
z.to_repr()Repr(z) or @debug.Debug::to_repr(z)