-module(viva_math@complex). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/viva_math/complex.gleam"). -export([zero/0, one/0, i/0, real/1, from_polar/2, add/2, sub/2, mul/2, 'div'/2, neg/1, conjugate/1, scale/2, magnitude/1, magnitude_squared/1, phase/1, to_polar/1, exp/1, log/1, sqrt/1, pow_int/2, pow/2, sin/1, cos/1, tan/1, is_close/3]). -export_type([complex/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Complex numbers `a + bi`.\n" "\n" " Standard algebra plus a small set of transcendental functions sufficient\n" " for FFT, eigenvalue work and signal processing.\n" "\n" " All operations are pure and total — division by zero returns the zero\n" " complex by convention (consistent with `gleam/float`).\n" ). -type complex() :: {complex, float(), float()}. -file("src/viva_math/complex.gleam", 21). ?DOC(" 0 + 0i\n"). -spec zero() -> complex(). zero() -> {complex, +0.0, +0.0}. -file("src/viva_math/complex.gleam", 26). ?DOC(" 1 + 0i\n"). -spec one() -> complex(). one() -> {complex, 1.0, +0.0}. -file("src/viva_math/complex.gleam", 31). ?DOC(" 0 + 1i (the imaginary unit)\n"). -spec i() -> complex(). i() -> {complex, +0.0, 1.0}. -file("src/viva_math/complex.gleam", 36). ?DOC(" Build from a real number (im = 0).\n"). -spec real(float()) -> complex(). real(X) -> {complex, X, +0.0}. -file("src/viva_math/complex.gleam", 41). ?DOC(" Build from magnitude `r` and phase `theta` (polar form).\n"). -spec from_polar(float(), float()) -> complex(). from_polar(R, Theta) -> {complex, R * math:cos(Theta), R * math:sin(Theta)}. -file("src/viva_math/complex.gleam", 49). -spec add(complex(), complex()) -> complex(). add(A, B) -> {complex, erlang:element(2, A) + erlang:element(2, B), erlang:element(3, A) + erlang:element(3, B)}. -file("src/viva_math/complex.gleam", 53). -spec sub(complex(), complex()) -> complex(). sub(A, B) -> {complex, erlang:element(2, A) - erlang:element(2, B), erlang:element(3, A) - erlang:element(3, B)}. -file("src/viva_math/complex.gleam", 57). -spec mul(complex(), complex()) -> complex(). mul(A, B) -> {complex, (erlang:element(2, A) * erlang:element(2, B)) - (erlang:element(3, A) * erlang:element( 3, B )), (erlang:element(2, A) * erlang:element(3, B)) + (erlang:element(3, A) * erlang:element( 2, B ))}. -file("src/viva_math/complex.gleam", 61). -spec 'div'(complex(), complex()) -> complex(). 'div'(A, B) -> Denom = (erlang:element(2, B) * erlang:element(2, B)) + (erlang:element( 3, B ) * erlang:element(3, B)), case Denom =:= +0.0 of true -> zero(); false -> {complex, case Denom of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> ((erlang:element(2, A) * erlang:element( 2, B )) + (erlang:element(3, A) * erlang:element(3, B))) / Gleam@denominator end, case Denom of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> ((erlang:element(3, A) * erlang:element( 2, B )) - (erlang:element(2, A) * erlang:element(3, B))) / Gleam@denominator@1 end} end. -file("src/viva_math/complex.gleam", 74). -spec neg(complex()) -> complex(). neg(Z) -> {complex, +0.0 - erlang:element(2, Z), +0.0 - erlang:element(3, Z)}. -file("src/viva_math/complex.gleam", 78). -spec conjugate(complex()) -> complex(). conjugate(Z) -> {complex, erlang:element(2, Z), +0.0 - erlang:element(3, Z)}. -file("src/viva_math/complex.gleam", 82). -spec scale(complex(), float()) -> complex(). scale(Z, S) -> {complex, erlang:element(2, Z) * S, erlang:element(3, Z) * S}. -file("src/viva_math/complex.gleam", 91). ?DOC(" |z| = √(re² + im²). Uses `hypot` to avoid overflow.\n"). -spec magnitude(complex()) -> float(). magnitude(Z) -> viva_math@scalar:hypot(erlang:element(2, Z), erlang:element(3, Z)). -file("src/viva_math/complex.gleam", 96). ?DOC(" |z|² (cheaper than `magnitude` when comparing).\n"). -spec magnitude_squared(complex()) -> float(). magnitude_squared(Z) -> (erlang:element(2, Z) * erlang:element(2, Z)) + (erlang:element(3, Z) * erlang:element( 3, Z )). -file("src/viva_math/complex.gleam", 101). ?DOC(" Phase angle in radians, range (-π, π].\n"). -spec phase(complex()) -> float(). phase(Z) -> math:atan2(erlang:element(3, Z), erlang:element(2, Z)). -file("src/viva_math/complex.gleam", 106). ?DOC(" Polar decomposition: returns `(magnitude, phase)`.\n"). -spec to_polar(complex()) -> {float(), float()}. to_polar(Z) -> {magnitude(Z), phase(Z)}. -file("src/viva_math/complex.gleam", 115). ?DOC(" exp(z) = e^a · (cos(b) + i·sin(b))\n"). -spec exp(complex()) -> complex(). exp(Z) -> Ea = math:exp(erlang:element(2, Z)), {complex, Ea * math:cos(erlang:element(3, Z)), Ea * math:sin(erlang:element(3, Z))}. -file("src/viva_math/complex.gleam", 121). ?DOC(" log(z) = ln|z| + i·arg(z). Branch cut along the negative real axis.\n"). -spec log(complex()) -> complex(). log(Z) -> {complex, math:log(magnitude(Z)), phase(Z)}. -file("src/viva_math/complex.gleam", 126). ?DOC(" Principal square root √z.\n"). -spec sqrt(complex()) -> complex(). sqrt(Z) -> R = magnitude(Z), New_re = math:sqrt((R + erlang:element(2, Z)) / 2.0), Sign = case erlang:element(3, Z) < +0.0 of true -> -1.0; false -> 1.0 end, New_im = Sign * math:sqrt((R - erlang:element(2, Z)) / 2.0), {complex, New_re, New_im}. -file("src/viva_math/complex.gleam", 146). -spec pow_int_loop(complex(), integer(), complex()) -> complex(). pow_int_loop(Base, N, Acc) -> case N of 0 -> Acc; _ -> case N rem 2 of 0 -> pow_int_loop(mul(Base, Base), N div 2, Acc); _ -> pow_int_loop(mul(Base, Base), N div 2, mul(Acc, Base)) end end. -file("src/viva_math/complex.gleam", 138). ?DOC(" z^n for integer n via repeated multiplication. Handles negative exponents.\n"). -spec pow_int(complex(), integer()) -> complex(). pow_int(Z, N) -> case N of 0 -> one(); N@1 when N@1 < 0 -> 'div'(one(), pow_int(Z, 0 - N@1)); _ -> pow_int_loop(Z, N, one()) end. -file("src/viva_math/complex.gleam", 158). ?DOC(" Real exponent z^x via `exp(x · log(z))`.\n"). -spec pow(complex(), float()) -> complex(). pow(Z, X) -> exp(scale(log(Z), X)). -file("src/viva_math/complex.gleam", 163). ?DOC(" sin(z) = sin(a)·cosh(b) + i·cos(a)·sinh(b)\n"). -spec sin(complex()) -> complex(). sin(Z) -> {complex, math:sin(erlang:element(2, Z)) * math:cosh(erlang:element(3, Z)), math:cos(erlang:element(2, Z)) * math:sinh(erlang:element(3, Z))}. -file("src/viva_math/complex.gleam", 168). ?DOC(" cos(z) = cos(a)·cosh(b) - i·sin(a)·sinh(b)\n"). -spec cos(complex()) -> complex(). cos(Z) -> {complex, math:cos(erlang:element(2, Z)) * math:cosh(erlang:element(3, Z)), +0.0 - (math:sin(erlang:element(2, Z)) * math:sinh(erlang:element(3, Z)))}. -file("src/viva_math/complex.gleam", 173). ?DOC(" tan(z) = sin(z) / cos(z)\n"). -spec tan(complex()) -> complex(). tan(Z) -> 'div'(sin(Z), cos(Z)). -file("src/viva_math/complex.gleam", 182). ?DOC(" Approximate equality.\n"). -spec is_close(complex(), complex(), float()) -> boolean(). is_close(A, B, Tol) -> (gleam@float:absolute_value(erlang:element(2, A) - erlang:element(2, B)) =< Tol) andalso (gleam@float:absolute_value( erlang:element(3, A) - erlang:element(3, B) ) =< Tol).