Sign in

    #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)))
    }