symbit

A symbolic mathematics library for Moonbit.

symbolic
math
algebra
polynomials
physics
moon add CAIMEOX/symbit@0.5.10
Download zip
Author
Version
0.5.10
License
Apache-2.0
Last updated
2 months ago
Downloads
99
README

#Symbit

Symbit is a MoonBit symbolic mathematics library with a broad symbolic algebra surface. The project is not a thin wrapper over Python: the runtime implementation lives in MoonBit packages under src/sym*, while a separate oracle-test tree is reserved for behavior checks and never participates in runtime semantics. In practice this means Symbit is meant to be linked into MoonBit programs as a native symbolic engine: expressions, simplifiers, polynomial domains, statistics, physics, geometry, logic, combinatorics, and related algebraic subsystems are all represented as MoonBit values and manipulated locally.

The module-level root package, CAIMEOX/symbit, is intentionally small and stable. It exposes the core expression type together with a practical facade for expression construction, printing, simplification, rational-function utilities, common-subexpression elimination, and the full Fu trigonometric rewrite family. The deeper mathematical subsystems live in dedicated packages such as sympolys, symstats, symphysics, symgeometry, symlogic, and symtensor. A useful way to read the project is: the root package is the ergonomic entry point for general symbolic rewriting, and the specialized packages are where domain-heavy algorithms and larger APIs live.

The examples in this README are executable mbt check blocks. They are kept intentionally concrete: if the public facade changes, the README should fail under moon test rather than silently drifting out of sync with the code. From another MoonBit package you would import CAIMEOX/symbit in moon.pkg and call the same exported functions shown below.

#QuickStart

The fastest way to start is to build expressions with symbol, integer, rational, add, mul, and pow, then render them with pretty_string or debug_inspect. This keeps the entry path explicit and avoids hiding tree structure behind parser magic. In real use you usually construct a symbolic expression, inspect it in debug or readable form, then apply one of the simplifiers depending on whether you want a general rewrite, a rational-function normalization, or a more specialized transformation.

///|
test "quickstart expression construction and simplification" {
let x = Expr::Symbol("x")
let y = Expr::Symbol("y")

let expr = add([integer(1), mul([x, pow(y, integer(2))])])
debug_inspect(expr, content="(+ (* sym:x (^ sym:y 2)) 1)")
inspect(pretty_string(expr), content="x*y**2 + 1")

let cancelled = simplify(
mul([add([x, integer(1)]), pow(add([x, integer(1)]), integer(-1))]),
)
inspect(pretty_string(cancelled), content="1")
}

Exact rationals are first-class. The root package deliberately exposes rational(num, den) rather than forcing every caller to construct rationals through lower-level numeric packages. This makes it straightforward to write exact algebraic examples without losing track of the fact that the result is symbolic rather than floating-point.

///|
test "quickstart exact rationals remain symbolic" {
let third = try! rational(1, 3)
let expr = add([third, third, third])
inspect(pretty_string(expr), content="1")
}

#Simplification Workflow

Symbit separates general simplification from targeted simplification. simplify runs the broad rewrite pipeline. trigsimp, ratsimp, radsimp, sqrtdenest, powdenest, and related helpers each target one mathematical regime and are often a better engineering choice when you know the shape of the input in advance. This mirrors how symbolic code tends to be maintained in larger systems: broad passes are convenient for interactive work, while explicit targeted passes are easier to reason about in library code and tests.

///|
test "targeted simplification keeps intent explicit" {
let x = Expr::Symbol("x")
let sin_x = @symcore.function("sin", [x])
let cos_x = @symcore.function("cos", [x])
let trig = add([pow(sin_x, integer(2)), pow(cos_x, integer(2))])
inspect(pretty_string(trigsimp(trig)), content="1")

let frac_expr = mul([
add([x, integer(1)]),
pow(Expr::Symbol("y"), integer(-1)),
])
let (num, den) = fraction(frac_expr)
inspect(pretty_string(num), content="x + 1")
inspect(pretty_string(den), content="y")
}

The project also exposes the lower-level pieces that are useful when you need to preserve or inspect structure instead of asking for a full simplification pass. fraction, numer, denom, collect, collect_const, collect_sqrt, collect_abs, epath, and epath_apply all exist for that reason. They let downstream packages and end-user code write deterministic transformations without depending on the entire global simplifier.

#Common-Subexpression Elimination

cse is the root-package entry point for common-subexpression elimination. The returned CseResult keeps both the extracted substitutions and the reduced expressions, and cse_reconstruct lets you rebuild the originals. This matters when you want optimization without losing a simple verification path: tests can assert on the extracted substitution count while still proving that reconstruction recovers the initial expressions exactly.

///|
test "cse extracts and reconstructs shared structure" {
let x = Expr::Symbol("x")
let shared = @symcore.function("sin", [x])
let exprs = [mul([shared, shared]), add([shared, shared])]

let result = cse(exprs)
inspect(result.replacement_count(), content="1")

let replacements = result.replacements_copy()
let (tmp, rhs) = replacements[0]
inspect(tmp, content="x0")
inspect(pretty_string(rhs), content="sin(x)")

let rebuilt = cse_reconstruct(result)
assert_true(pretty_string(rebuilt[0]) == pretty_string(exprs[0]))
assert_true(pretty_string(rebuilt[1]) == pretty_string(exprs[1]))
}

#Trigonometric Rewrites and Fu Rules

For code that wants precise control over trigonometric normalization, the root package exposes the Fu rewrite family directly. This is useful when a full trigsimp pass is too aggressive or when a test wants to pin one named rewrite rule. The public surface includes fu, futrig, and the individual TR0-style helpers such as tr0, tr2, tr10, tr111, tr22, trpower, and trmorrie. In other words, the root package is not only a convenience layer; it also exposes the rewrite toolkit needed to script deterministic transformations.

///|
test "fu-style helpers are available from the root package" {
let x = Expr::Symbol("x")
let sin_x = @symcore.function("sin", [x])
let cos_x = @symcore.function("cos", [x])
let tan_x = @symcore.function("tan", [x])

inspect(
to_repr(tr2(tan_x)).to_string(),
content="(* (^ (call cos sym:x) -1) (call sin sym:x))",
)
inspect(
pretty_string(tr2i(mul([sin_x, pow(cos_x, integer(-1))]))),
content="tan(x)",
)
}

#Package Guide

The project is organized as a set of focused packages rather than a single monolith. symcore provides the fundamental expression representation and low-level symbolic constructors. symsimplify provides the rewrite engine, algebraic simplifiers, rational simplifiers, denesting, common-subexpression elimination, and the Fu trigonometric rule family; the root package is primarily a curated facade over this layer. symnum provides exact rational arithmetic and related numeric helpers used throughout the rest of the module.

sympolys contains the polynomial stack: dense and sparse polynomial operations, domains, domain matrices, number-field compatibility layers, and AGCA- or series-adjacent helpers. symstats covers random variables, distributions, stochastic processes, matrix ensembles, and symbolic probability queries. symphysics is a large family of packages including quantum mechanics, vector calculus, mechanics, control, optics, and continuum mechanics. symgeometry covers points, linear entities, conics, polygons, curves, convex hulls, and related geometry algorithms. symlogic, symsets, symtensor, symseries, symcombinatorics, symntheory, symcalculus, symconcrete, symliealgebras, symfunctions, symassume, symholonomic, symalgebras, symdiscrete, and symstrategies fill out the rest of the symbolic stack.

The project also ships a test-only oracle layer under src/. Those packages call Python to compare behavior, normalize output, or validate algorithms during regression testing. They are intentionally isolated from runtime implementation code. If you are extending Symbit itself, that separation is not optional: new functionality belongs in src/sym*, and oracle calls belong only in the test-only oracle layer.

#Package Manuals

The root package is intentionally small. The deeper runtime manuals now live with the packages themselves as README.mbt.md, so you do not need to reverse-engineer behavior from the source tree. The most important entry points are:

For contributors, package manuals are synchronized and checked by tools/package_docs.py. That tool enforces package-guide presence, checks for banned placeholder phrasing, and can be used to keep generated package READMEs aligned with the current public package layout.

#Choosing the Right Entry Point

Use the root package when you need a stable, compact API for building expressions and applying simplification passes. Import a specialized package directly when your code depends on deeper semantics such as polynomial domains, statistics, geometry, or physics subsystems. This split is deliberate. It keeps the default API small enough to be teachable while still allowing the module as a whole to grow into a broad symbolic mathematics toolkit.

That distinction matters for maintenance as well. The root package should stay easy to read and hard to misuse. Specialized packages can be larger and more domain-specific. The README is therefore written around the root package first, then points outward to the rest of the module. If you are evaluating the project as a library consumer, start here. If you are looking for a specific mathematical subsystem, read the corresponding package directly under src/.

#Symbit

Symbit is a MoonBit symbolic mathematics library with a broad symbolic algebra surface. The project is not a thin wrapper over Python: the runtime implementation lives in MoonBit packages under src/sym*, while a separate oracle-test tree is reserved for behavior checks and never participates in runtime semantics. In practice this means Symbit is meant to be linked into MoonBit programs as a native symbolic engine: expressions, simplifiers, polynomial domains, statistics, physics, geometry, logic, combinatorics, and related algebraic subsystems are all represented as MoonBit values and manipulated locally.

The module-level root package, CAIMEOX/symbit, is intentionally small and stable. It exposes the core expression type together with a practical facade for expression construction, printing, simplification, rational-function utilities, common-subexpression elimination, and the full Fu trigonometric rewrite family. The deeper mathematical subsystems live in dedicated packages such as sympolys, symstats, symphysics, symgeometry, symlogic, and symtensor. A useful way to read the project is: the root package is the ergonomic entry point for general symbolic rewriting, and the specialized packages are where domain-heavy algorithms and larger APIs live.

The examples in this README are executable mbt check blocks. They are kept intentionally concrete: if the public facade changes, the README should fail under moon test rather than silently drifting out of sync with the code. From another MoonBit package you would import CAIMEOX/symbit in moon.pkg and call the same exported functions shown below.

#QuickStart

The fastest way to start is to build expressions with symbol, integer, rational, add, mul, and pow, then render them with pretty_string or debug_inspect. This keeps the entry path explicit and avoids hiding tree structure behind parser magic. In real use you usually construct a symbolic expression, inspect it in debug or readable form, then apply one of the simplifiers depending on whether you want a general rewrite, a rational-function normalization, or a more specialized transformation.

///|
test "quickstart expression construction and simplification" {
let x = Expr::Symbol("x")
let y = Expr::Symbol("y")

let expr = add([integer(1), mul([x, pow(y, integer(2))])])
debug_inspect(expr, content="(+ (* sym:x (^ sym:y 2)) 1)")
inspect(pretty_string(expr), content="x*y**2 + 1")

let cancelled = simplify(
mul([add([x, integer(1)]), pow(add([x, integer(1)]), integer(-1))]),
)
inspect(pretty_string(cancelled), content="1")
}

Exact rationals are first-class. The root package deliberately exposes rational(num, den) rather than forcing every caller to construct rationals through lower-level numeric packages. This makes it straightforward to write exact algebraic examples without losing track of the fact that the result is symbolic rather than floating-point.

///|
test "quickstart exact rationals remain symbolic" {
let third = try! rational(1, 3)
let expr = add([third, third, third])
inspect(pretty_string(expr), content="1")
}

#Simplification Workflow

Symbit separates general simplification from targeted simplification. simplify runs the broad rewrite pipeline. trigsimp, ratsimp, radsimp, sqrtdenest, powdenest, and related helpers each target one mathematical regime and are often a better engineering choice when you know the shape of the input in advance. This mirrors how symbolic code tends to be maintained in larger systems: broad passes are convenient for interactive work, while explicit targeted passes are easier to reason about in library code and tests.

///|
test "targeted simplification keeps intent explicit" {
let x = Expr::Symbol("x")
let sin_x = @symcore.function("sin", [x])
let cos_x = @symcore.function("cos", [x])
let trig = add([pow(sin_x, integer(2)), pow(cos_x, integer(2))])
inspect(pretty_string(trigsimp(trig)), content="1")

let frac_expr = mul([
add([x, integer(1)]),
pow(Expr::Symbol("y"), integer(-1)),
])
let (num, den) = fraction(frac_expr)
inspect(pretty_string(num), content="x + 1")
inspect(pretty_string(den), content="y")
}

The project also exposes the lower-level pieces that are useful when you need to preserve or inspect structure instead of asking for a full simplification pass. fraction, numer, denom, collect, collect_const, collect_sqrt, collect_abs, epath, and epath_apply all exist for that reason. They let downstream packages and end-user code write deterministic transformations without depending on the entire global simplifier.

#Common-Subexpression Elimination

cse is the root-package entry point for common-subexpression elimination. The returned CseResult keeps both the extracted substitutions and the reduced expressions, and cse_reconstruct lets you rebuild the originals. This matters when you want optimization without losing a simple verification path: tests can assert on the extracted substitution count while still proving that reconstruction recovers the initial expressions exactly.

///|
test "cse extracts and reconstructs shared structure" {
let x = Expr::Symbol("x")
let shared = @symcore.function("sin", [x])
let exprs = [mul([shared, shared]), add([shared, shared])]

let result = cse(exprs)
inspect(result.replacement_count(), content="1")

let replacements = result.replacements_copy()
let (tmp, rhs) = replacements[0]
inspect(tmp, content="x0")
inspect(pretty_string(rhs), content="sin(x)")

let rebuilt = cse_reconstruct(result)
assert_true(pretty_string(rebuilt[0]) == pretty_string(exprs[0]))
assert_true(pretty_string(rebuilt[1]) == pretty_string(exprs[1]))
}

#Trigonometric Rewrites and Fu Rules

For code that wants precise control over trigonometric normalization, the root package exposes the Fu rewrite family directly. This is useful when a full trigsimp pass is too aggressive or when a test wants to pin one named rewrite rule. The public surface includes fu, futrig, and the individual TR0-style helpers such as tr0, tr2, tr10, tr111, tr22, trpower, and trmorrie. In other words, the root package is not only a convenience layer; it also exposes the rewrite toolkit needed to script deterministic transformations.

///|
test "fu-style helpers are available from the root package" {
let x = Expr::Symbol("x")
let sin_x = @symcore.function("sin", [x])
let cos_x = @symcore.function("cos", [x])
let tan_x = @symcore.function("tan", [x])

inspect(
to_repr(tr2(tan_x)).to_string(),
content="(* (^ (call cos sym:x) -1) (call sin sym:x))",
)
inspect(
pretty_string(tr2i(mul([sin_x, pow(cos_x, integer(-1))]))),
content="tan(x)",
)
}

#Package Guide

The project is organized as a set of focused packages rather than a single monolith. symcore provides the fundamental expression representation and low-level symbolic constructors. symsimplify provides the rewrite engine, algebraic simplifiers, rational simplifiers, denesting, common-subexpression elimination, and the Fu trigonometric rule family; the root package is primarily a curated facade over this layer. symnum provides exact rational arithmetic and related numeric helpers used throughout the rest of the module.

sympolys contains the polynomial stack: dense and sparse polynomial operations, domains, domain matrices, number-field compatibility layers, and AGCA- or series-adjacent helpers. symstats covers random variables, distributions, stochastic processes, matrix ensembles, and symbolic probability queries. symphysics is a large family of packages including quantum mechanics, vector calculus, mechanics, control, optics, and continuum mechanics. symgeometry covers points, linear entities, conics, polygons, curves, convex hulls, and related geometry algorithms. symlogic, symsets, symtensor, symseries, symcombinatorics, symntheory, symcalculus, symconcrete, symliealgebras, symfunctions, symassume, symholonomic, symalgebras, symdiscrete, and symstrategies fill out the rest of the symbolic stack.

The project also ships a test-only oracle layer under src/. Those packages call Python to compare behavior, normalize output, or validate algorithms during regression testing. They are intentionally isolated from runtime implementation code. If you are extending Symbit itself, that separation is not optional: new functionality belongs in src/sym*, and oracle calls belong only in the test-only oracle layer.

#Package Manuals

The root package is intentionally small. The deeper runtime manuals now live with the packages themselves as README.mbt.md, so you do not need to reverse-engineer behavior from the source tree. The most important entry points are:

For contributors, package manuals are synchronized and checked by tools/package_docs.py. That tool enforces package-guide presence, checks for banned placeholder phrasing, and can be used to keep generated package READMEs aligned with the current public package layout.

#Choosing the Right Entry Point

Use the root package when you need a stable, compact API for building expressions and applying simplification passes. Import a specialized package directly when your code depends on deeper semantics such as polynomial domains, statistics, geometry, or physics subsystems. This split is deliberate. It keeps the default API small enough to be teachable while still allowing the module as a whole to grow into a broad symbolic mathematics toolkit.

That distinction matters for maintenance as well. The root package should stay easy to read and hard to misuse. Specialized packages can be larger and more domain-specific. The README is therefore written around the root package first, then points outward to the rest of the module. If you are evaluating the project as a library consumer, start here. If you are looking for a specific mathematical subsystem, read the corresponding package directly under src/.

#
BigRational

Root alias for exact rational arithmetic used throughout Symbit.

#
ComplexFloat

Root alias for complex floating-point leaves used in evaluated expressions.

#
CseResult

Root alias for the result returned by common-subexpression elimination.

#
EPath

Root alias for expression-path selectors used by traversal helpers.

#
Expr

Root alias for Symbit's symbolic expression tree.

#
Float

Root alias for machine floating-point values carried by symbolic expressions.

#
Matrix

Root alias for the dense symbolic matrix type.

#
MatrixError

Root alias for matrix-construction and linear-algebra errors.

#
SimplifyPattern

Root alias for one targeted simplify rewrite pattern.

#
SimplifyPlan

Root alias for named simplify pipelines used by simplify.

#
SparseMatrix

Root alias for the sparse symbolic matrix type.

#
SympifyInput

Root alias for values accepted by the high-level sympify facade.

#
add

Build an additive expression from args (empty -> 0).

#
applied_undefined_function

fn applied_undefined_function(name : String, args : Array[
Expr
]) ->
Expr

Apply an undefined function head to arguments while preserving first-class head structure.

#
apply

Apply a first-class function head to arguments. Returns None when head is not a callable core head.

#
apply_at_level

Apply a transformation to nodes at a specific tree depth.

#
besselsimp

Simplify Bessel-function expressions.

#
collect

Collect additive terms by powers of sym.

#
collect_abs

Collect terms containing absolute values.

#
collect_const

Collect numeric constants in additive expressions.

#
collect_sqrt

Collect terms containing square roots.

#
combsimp

Simplify combinatorial and factorial-style expressions.

#
cse

Compute common-subexpression elimination replacements and reduced outputs.

#
cse_reconstruct

Reconstruct original expressions from a cse result.

#
denom

Extract the denominator of a rational expression.

#
denom_expand

Expand only the denominator of a rational expression.

#
diag

Create a diagonal matrix from the supplied diagonal entries.

#
dummy

fn dummy(name? : String) ->
Expr

Create a fresh dummy symbol with identity distinct from its printed name.

#
epath

Traverse an expression by an explicit edit path.

#
epath_apply

Apply a transformation function at a path-selected subexpression.

#
evalf

Numerically evaluate exact numeric leaves and common constants/functions.

#
exptrigsimp

Rewrite between exponential and trigonometric forms for simplification.

#
fraction

Split an expression into numerator and denominator.

#
fraction_expand

Expand numerator and denominator components of a fraction.

Run the full Fu trigonometric rewrite strategy.

#
futrig

Run Fu trigonometric simplification without the full simplify pipeline.

#
gammasimp

Simplify Gamma-family function expressions.

#
hyperexpand

Expand hypergeometric functions into simpler closed forms when possible.

#
hypersimilar

Check whether two terms are hyper-similar in index k.

#
hypersimp

Return the hypergeometric term ratio f(k+1)/f(k) when possible.

#
integer

fn integer(value : Int) ->
Expr

Create an exact integer expression.

#
kroneckersimp

Simplify Kronecker-delta expressions.

#
logcombine

Combine sums/products of logs into compact log forms.

#
mul

Build a multiplicative expression from args (empty -> 1).

#
nsimplify

fn nsimplify(expr :
Expr
, constants? : Array[
Expr
], full? : Bool, rational? : Bool) ->
Expr

Try to recognize a numeric expression as an exact symbolic form.

#
numer

Extract the numerator of a rational expression.

#
numer_expand

Expand only the numerator of a rational expression.

#
ones

Create an all-one matrix.

#
posify

Replace symbols with positivity-assumed dummies and return reverse map.

#
pow

Construct a power expression base**exp.

#
powdenest

Denest powers such as (x**a)**b when safe.

#
powsimp

Simplify powers and exponent combinations.

#
pretty_string

fn pretty_string(expr :
Expr
) -> String

Return the default pretty string form for an expression.

#
rad_rationalize

Rationalize a radical denominator and return transformed (num, den).

#
radsimp

Rationalize and simplify radicals in denominators.

#
rational

Create an exact rational expression num/den.

#
ratsimp

Perform rational function simplification.

#
ratsimpmodprime

Run modular rational simplification over a temporary prime field.

#
rcollect

Recursively collect terms by sym in nested expressions.

#
separatevars

fn separatevars(expr :
Expr
, force? : Bool) ->
Expr

Separate multiplicative factors by symbolic variables.

#
signsimp

Normalize signs in additive and multiplicative expressions.

#
simplify

Run the general simplification pipeline.

#
simplify_with_patterns

Apply a custom simplify plan composed of rewrite patterns.

#
sparse_matrix

Create a sparse symbolic matrix from DOK-style entries.

#
split_surds

Split surd terms into (g, a, b) for denesting-style transformations.

#
sqrtdenest

Denest nested square roots when algebraically possible.

#
sub_post

Apply sub_post rewriting (children before parent).

#
sub_pre

Apply sub_pre rewriting (parent before children).

#
sympify

Coerce a typed MoonBit payload into a core symbolic expression.

#
sympify_dict

Lift key/value pairs into a symbolic dict container.

#
sympify_name

fn sympify_name(name : String) ->
Expr

Coerce a symbolic name through the core sympify pipeline.

#
sympify_tuple

Lift tuple-shaped inputs into a symbolic tuple.

#
tr10i

Apply inverse Fu rule TR10i.

#
tr111

Apply Fu rule TR111.

#
tr12i

Apply inverse Fu rule TR12i.

#
tr15

fn tr15(expr :
Expr
, max? : Int, pow? : Bool) ->
Expr

Apply Fu rule TR15.

#
tr16

fn tr16(expr :
Expr
, max? : Int, pow? : Bool) ->
Expr

Apply Fu rule TR16.

#
tr22

fn tr22(expr :
Expr
, max? : Int, pow? : Bool) ->
Expr

Apply Fu rule TR22.

#
tr2i

Apply inverse Fu rule TR2i.

#
tr5

fn tr5(expr :
Expr
, max? : Int, pow? : Bool) ->
Expr

Apply Fu rule TR5.

#
tr6

fn tr6(expr :
Expr
, max? : Int, pow? : Bool) ->
Expr

Apply Fu rule TR6.

#
trigsimp

Simplify trigonometric expressions.

#
trmorrie

Apply Morrie-style Fu product-to-sum rewrites.

#
trpower

Apply Fu power-focused trigonometric rewrites.

#
zeros

Create an all-zero matrix.

Source Files