core tutorial
This tutorial uses the generic Complex[T] type for complex arithmetic. You
will build and print complex numbers, compute with them over Double and
over the integers, write generic code that accepts complex numbers through
the luna-generic traits, and update values in place. The algebra behind
it is in the core design.
Quick start
moon add Luna-Flow/luna-complex@0.2.0
import {
"Luna-Flow/luna-complex" @complex,
}
fn main {
let z = @complex.Complex::new(1.0, 2.0)
let w = @complex.Complex::new(3.0, -1.0)
println("z + w = \{z + w}")
println("z * w = \{z * w}")
println("z / w = \{z / w}")
}
z + w = 4 + 1i
z * w = 5 + 5i
z / w = 0.1 + 0.7000000000000001i
Everyday tasks
Conjugate and squared modulus
is real:
fn main {
let z = @complex.Complex::new(3.0, 4.0)
let n = z * z.conjugate()
println("z * conj(z) = \{n}")
println("1 / z = \{z.inv()}")
}
z * conj(z) = 25 + 0i
1 / z = 0.12 + -0.16i
Gaussian integers
Over Int, Complex[Int] is the ring of Gaussian integers.
Everything except division works, and results are exact:
fn main {
let a = @complex.Complex::new(2, 1)
let b = @complex.Complex::new(2, -1)
println("(2 + i)(2 - i) = \{a * b}")
let mut p = @complex.Complex::one()
for _ in 0..<4 {
p = p * @complex.Complex::new(1, 1)
}
println("(1 + i)^4 = \{p}")
}
(2 + i)(2 - i) = 5 + 0i
(1 + i)^4 = -4 + 0i
shows that is not prime in .
Generic code over a ring
Code written against luna-generic traits accepts complex numbers. Here a
Horner evaluation of :
fn[T : @lg.Ring] horner(coefficients : Array[T], x : T) -> T {
let mut acc : T = @lg.Zero::zero()
for i = coefficients.length() - 1; i >= 0; i = i - 1 {
acc = acc * x + coefficients[i]
}
acc
}
fn main {
let one : @complex.Complex[Double] = @complex.Complex::one()
let zero : @complex.Complex[Double] = @complex.Complex::zero()
let i = @complex.Complex::new(0.0, 1.0)
println("p(i) = \{horner([one, zero, one], i)}")
println("p(2) = \{horner([1.0, 0.0, 1.0], 2.0)}")
}
p(i) = 0 + 0i
p(2) = 5
Update in place
set, set_re and set_im overwrite a value. Every binding to it sees
the change:
fn main {
let acc = @complex.Complex::new(0.0, 0.0)
let shared = acc
for k in 1..=3 {
let kd = k.to_double()
acc.set(acc.re + kd, acc.im - kd)
}
println("acc = \{acc}")
println("shared = \{shared}")
}
acc = 6 + -6i
shared = 6 + -6i
Going further
Nested complex numbers
Complex[T] works for any T with the required traits, including
Complex[Double] itself:
fn main {
let a = @complex.Complex::new(1.0, 2.0)
let b = @complex.Complex::new(-0.5, 0.25)
let z = @complex.Complex::new(a, b)
let w = z * z.inv()
println("z * z^-1 = (\{w.re}) + (\{w.im})j")
}
z * z^-1 = (1.0000000000000002 + 5.204170427930421e-17i) + (0 + 0i)j
The product is up to rounding, because the squared modulus
of this is invertible. In general Complex[Complex[Double]] is not a field; see the
pitfalls below.
Analytic functions
The core has no sqrt, exp or sin. Import
Luna-Flow/luna-complex/float_backend for Complex[Double]; the
float_backend tutorial shows how.
Common pitfalls
- Division by zero aborts.
z / wandw.inv()callInverse::invon ; forDoublethat aborts when the value is zero, also when it underflows for . Use@fb.divfromfloat_backendfor robust division. - Large values overflow in division. overflows for .
- Shared mutation.
let shared = zdoes not copy; the setters change every alias. - Text form. Negative imaginary parts print as
+ -2i; usedebug_inspector your own formatting when you need another form. - Nested types are not fields. In
Complex[Complex[Double]], values such as have a zero squared modulus andinvaborts.
Next steps
- The core API lists every method and instance.
- The core design derives the construction and the field condition.
- The float_backend tutorial adds the analytic functions.
- luna-generic documents the traits used in generic code.