README

#Algebra Core

core/algebra is the smallest symbolic layer in LunarUnits. It normalizes a product of named factors and integer powers into a canonical monomial.

This package is intentionally generic. It does not know about SI dimensions, units, quantities, parsing, or formatting.

#Main API

  • Monomial::one() and Monomial::scalar(value) build dimensionless values.
  • Monomial::symbol(name) creates one named factor.
  • mul, div, pow and inv implement the multiplicative algebra.
  • same_factors compares only the symbolic factors, ignoring the coefficient.
  • normalize(expr) turns an expression tree into a canonical Monomial.

let length = @algebra.Monomial::symbol("length")
let area = length.pow(2)
let speed = length / @algebra.Monomial::symbol("time")

Higher layers use this package to make dimension and unit expressions stable under ordering, multiplication and cancellation.

#
Expr

pub(all) enum Expr {
Scalar(Double)
Symbol(String)
One
Mul(Array[Expr])
Pow(Expr, Int)
} derive(Eq,
Debug
)

A syntactic expression tree (SymPy-style nodes) used only for construction.

Every Expr collapses into a unique canonical Monomial after normalize; upper layers only consume Monomial. This algebra layer handles multiplication, division and powers only (a free abelian group over symbols) — addition belongs to the dimension-checked quantity layer.

Example

test {
// "a * a" and "a^2" are two ways to write the same thing.
let lhs = normalize(Mul([Symbol("a"), Symbol("a")]))
let rhs = normalize(Pow(Symbol("a"), 2))
assert_eq(lhs, rhs)
}

#
Monomial

pub struct Monomial {
coeff : Double
factors : Array[(String, Int)]
} derive(Eq,
Debug
)

The canonical normalized form: coeff * Π (symbol ^ exp).

Invariants:
  • factors is always sorted by symbol name in ascending order;
  • no factor has exp == 0.

Thanks to these invariants, the structural equality from derive(Eq) is exactly "equality as canonical forms": two Expr values are equivalent iff their normalized Monomial results are equal.

Example

test {
let m = normalize(Mul([Scalar(2.0), Symbol("m"), Pow(Symbol("s"), -1)]))
assert_eq(m.coefficient(), 2.0)
debug_inspect(m.terms(), content="[(\"m\", 1), (\"s\", -1)]")
}
impl Show for Monomial

#
Monomial::coefficient

fn Monomial::coefficient(self : Monomial) -> Double

Returns the scalar coefficient (the unit layer uses it to carry the scale factor relative to base units).

Example

test {
let m = normalize(Mul([Scalar(1000.0), Symbol("m")]))
assert_eq(m.coefficient(), 1000.0)
}

#
Monomial::div

fn Monomial::div(self : Monomial, other : Monomial) -> Monomial

Divides one monomial by another: equivalent to multiplying by the inverse.

Example

test {
let a = normalize(Symbol("a"))
let b = normalize(Symbol("b"))
assert_eq(a.div(b), normalize(Mul([Symbol("a"), Pow(Symbol("b"), -1)])))
}

#
Monomial::inv

fn Monomial::inv(self : Monomial) -> Monomial

Inverts a monomial: the coefficient is reciprocated and every exponent is negated.

Example

test {
let i = normalize(Mul([Scalar(2.0), Symbol("m")])).inv()
assert_eq(i.coefficient(), 0.5)
debug_inspect(i.terms(), content="[(\"m\", -1)]")
}

#
Monomial::is_dimensionless

fn Monomial::is_dimensionless(self : Monomial) -> Bool

Returns whether the monomial has no symbolic factors.

Example

test {
assert_true(normalize(Scalar(5.0)).is_dimensionless())
assert_false(normalize(Symbol("m")).is_dimensionless())
}

#
Monomial::mul

fn Monomial::mul(self : Monomial, other : Monomial) -> Monomial

Multiplies two monomials: coefficients multiply and like symbols add their exponents.

Example

test {
let a = normalize(Symbol("a"))
assert_eq(a.mul(a), normalize(Pow(Symbol("a"), 2)))
}

#
Monomial::one

fn Monomial::one() -> Monomial

The multiplicative identity 1.

Example

test {
assert_true(Monomial::one().is_dimensionless())
assert_eq(Monomial::one().coefficient(), 1.0)
}

#
Monomial::pow

fn Monomial::pow(self : Monomial, n : Int) -> Monomial

Raises a monomial to an integer power: the coefficient is taken to the n-th power and every exponent is multiplied by n; n == 0 returns the identity.

Example

test {
let m = normalize(Pow(Symbol("a"), 2))
assert_eq(m.pow(3), normalize(Pow(Symbol("a"), 6)))
assert_eq(m.pow(0), Monomial::one())
}

#
Monomial::same_factors

fn Monomial::same_factors(self : Monomial, other : Monomial) -> Bool

Compares only the exponent part, ignoring the coefficient. This is the backbone of the dimension layer's "same dimension" test.

Example

test {
let two_m = normalize(Mul([Scalar(2.0), Symbol("m")]))
let five_m = normalize(Mul([Scalar(5.0), Symbol("m")]))
assert_true(two_m.same_factors(five_m))
assert_false(two_m.same_factors(normalize(Pow(Symbol("m"), 2))))
}

#
Monomial::scalar

fn Monomial::scalar(coeff : Double) -> Monomial

Creates a dimensionless monomial holding only the scalar coeff (no symbols). Useful for attaching a numeric factor, e.g. a unit's scale.

Example

test {
let m = Monomial::scalar(2.5)
assert_eq(m.coefficient(), 2.5)
assert_true(m.is_dimensionless())
}

#
Monomial::symbol

fn Monomial::symbol(name : String) -> Monomial

Creates the atomic monomial name^1 with coefficient 1. A convenient way for upper layers (dimensions, units) to build a single symbol without going through an Expr.

Example

test {
let m = Monomial::symbol("m")
assert_eq(m.coefficient(), 1.0)
debug_inspect(m.terms(), content="[(\"m\", 1)]")
}

#
Monomial::terms

fn Monomial::terms(self : Monomial) -> Array[(String, Int)]

Returns the sorted exponent factors.

Example

test {
let m = normalize(Mul([Symbol("s"), Symbol("m")]))
debug_inspect(m.terms(), content="[(\"m\", 1), (\"s\", 1)]")
}

#
normalize

fn normalize(e : Expr) -> Monomial

Normalizes a syntactic Expr into its unique canonical Monomial.

This is a total function (it never fails). The six normalization rules from the design draft are satisfied automatically by the merge semantics of mul / pow: flatten products, division as negative power, power of a power, combine like terms, drop trivial factors, and canonical sorting.

Example

test {
// a * (a * a) normalizes to a^3
let nested = normalize(Mul([Symbol("a"), Mul([Symbol("a"), Symbol("a")])]))
assert_eq(nested, normalize(Pow(Symbol("a"), 3)))
}