Symbolica is a fast computer algebra system built in Rust with an extensive Python API. Version 3.0 brings it to the browser: you can edit and run the examples in this post without installing anything!
The release also adds particle physics tools, exact nonlinear equation solving, algebraic numbers, precision-aware Python scalars, and a Typst plugin for computing directly inside documents. Below are the highlights, with examples you can try.
For changes to existing code, see the migration guide. The detailed changelog is at the end.
Run Symbolica in your browser
Symbolica now compiles to WebAssembly (WASM). Combined with Pyodide, this lets Python notebooks run entirely in your browser. No Python installation or remote computation server is needed.
Click the Run button for a demonstration (downloads 15 MB):
from symbolica import *
x = S('x')
E('exp(x^2) * sin(x)').integrate(x)You can edit the code as you please and run it again. Hovering over integrate reveals its signature
and documentation (through a WASM version of ty).
The output is MathML, a markup language for math that
all modern browsers can render.
Rendering a 1GB expression would freeze the browser. Therefore, large expressions are automatically wrapped in a custom pager that shows a bounded preview. The pager lets you inspect or expand individual parts. Try it on this product:
from symbolica import E, S
x, y, z, w, a = S('x', 'y', 'z', 'w', 'a')
((x+y+z+w)**20).expand() * (1+a)Click a numbered marker, then Show more to reveal more of the expression. The full expression stays in the browser's Rust/WASM runtime and only parts are streamed to the browser. The output adapts to the available width on your device, and reacts to resizing.
Most examples in the Symbolica documentation are now editable and runnable. On the newly created Gallery page you will find full Marimo notebooks with interactive controls, and some examples for tensor manipulation and particle physics.
I expect these notebooks to be especially useful for teaching and sharing research results that readers can explore themselves.
Typst plugin
WebAssembly also makes Symbolica available inside Typst, a modern typesetter. The plugin is free to use, including commercially, and exposes most of Symbolica's API for symbolic algebra, equation solving, and numerical evaluation.
For example, factor a polynomial and differentiate it while typesetting:
#import "@preview/symbolica:0.1.0": *
#let x = symbol("x")
#let f = math($x^4 - 5 x^2 + 4$)
$
f(x) &= #to-typst(f) \
&= #to-typst(factor(f)) \
f'(x) &= #to-typst(derivative(f, x))
$This produces
The computation and its typeset result stay together, avoiding a separate calculation and a manual copy into the document. See the manual for the full API.
HEPkit: particle-physics tools that work together
Particle-physics calculations often involve several specialised programs, each with its own installation, input language, conventions and quirks. HEPkit brings these tools into a shared framework built around Symbolica. Together with Lucien Huber, Valentin Hirschi, and other community members, we are rebuilding established tools in Rust with a typed Python interface.
HEPkit connects model definitions, Feynman diagrams, and tensor expressions. Its Rust components share a Symbolica kernel, allowing the stages of a calculation to exchange mathematical objects directly.
The toolkit covers graph generation, tensor and integration-by-parts reductions, UV expansions, auxiliary mass flow, and more. These components can work together in one self-contained notebook.
Choose a two-loop QED diagram and inspect its numerator in the notebook below.
Diagrams have interactive SVG views, and tensor expressions use compact mathematical notation.
The HEPkit website has more examples and a fuller list of capabilities. This is an ongoing effort: we are porting existing tools to Rust with AI assistance, checking results against their original implementations, improving performance and addressing shortcomings.
HEPkit also collects citations for the methods used in your calculation.
Exact algebra and solving
Symbolica 3.0 can represent polynomial roots exactly and use them to solve nonlinear systems, including systems with symbolic parameters. You can keep a result exact during algebraic manipulations and evaluate it numerically when needed.
The new root object selects a root of a polynomial. Real roots come first,
in ascending order, followed by complex roots ordered by real and then
imaginary part.
Automatic simplification
Constructing a root simplifies its defining polynomial and returns an integer or radical when possible. For example:
from symbolica import S
x = S("x")
polynomial = (x - 3) * (x**2 - 2)
roots = [polynomial.root(k) for k in range(3)]
rootsThe result is
A root that has no simpler form remains exact. You can still evaluate it at the precision you need:
from symbolica import S
x = S("x")
alpha = (x**3 - x - 1).root(0)
display(alpha)
alpha.to_float(80)Nonlinear systems with parameters
Exact roots also make it possible to express solutions that cannot be written with radicals. The solver keeps track of parameter conditions and supports algebraic-number coefficients.
For example, we can solve the coupled system
for
from symbolica import Expression, S
x, y, a, b = S("x", "y", "a", "b")
solutions = Expression.solve([
(x**2 + y**2).eq(a),
(x*y).eq(b),
], [x, y])
solutionsFor generic parameter values, the four solution branches can be written as:
The returned solutions record the conditions on
The default domain is complex; you can also ask for real or rational solutions.
Algebraic-number coefficients
Algebraic numbers can now be coefficients of polynomials, rather than being
treated as extra symbolic variables. For example, this conversion discovers
the extension containing
from symbolica import N, S
x = S("x")
expr = x + N(2).sqrt()
expr.to_polynomial(extensions=[])**2You can also choose an algebraic extension explicitly when factoring:
from symbolica import N, S
x = S("x")
expr = x**2 - 6
expr.factor(extension=[N(2).sqrt(), N(3).sqrt()])The result is
Numerical evaluation
High-precision results now come back to Python as Float
and ComplexFloat objects. They carry precision through arithmetic, elementary
functions, and formatting, so you can continue calculating with a numerical
result without converting it to another library's type. Python's Decimal class inputs remain
supported.
We have also added a benchmark page that compares Symbolica's numerical evaluation to other backends. Below we showcase an example that has nested polynomials, exponentials, sines, and cosines. Symbolica generates code faster, uses less build memory, and evaluates faster than the other tested backends.
Creating the browser build
Most of Symbolica's code is pure Rust and most algebra is done in-house. The exception is arbitrary-precision arithmetic. Native Symbolica uses GMP and MPFR, two reliable and fast C libraries.
By introducing an abstraction layer over the integer and floating-point backends, we could swap out GMP and MPFR with Malachite for integers and Astro-float for floating-point numbers. These libraries are currently a bit slower than GMP/MPFR, but they are written in pure Rust and can more easily be compiled to WASM.
The browser build has some limitations: there is little support for 64-bit WASM compilation in Rust, so the whole build is 32-bit (a 4 GB addressing space). Large expressions may exceed this limit, but Symbolica is optimized for a small memory footprint, so you will likely not run into this problem very soon (see also the benchmarks). On top, Pyodide does not support threading.
Fortunately, it is easy to deploy a notebook locally. Just run
uvx --refresh --python 3.14 --with symbolica marimo edit yournotebook.pyWASM is surprisingly fast for parsing and algebra: these workloads took about 1.4–2 times as long as native after warmup:
| Case | Native | WASM | WASM / native |
|---|---|---|---|
| Parse a 496-term expression | 0.511 ms | 0.699 ms | 1.37× |
Expand (1+x+y+z)^30 (5,456 terms) |
6.2 ms | 9.41 ms | 1.52× |
Factor x^12-y^12 |
0.284 ms | 0.44 ms | 1.55× |
| Differentiate a 5,456-term expression | 6.2 ms | 8.79 ms | 1.42× |
| Multiply dense polynomials (455 × 364 terms) | 0.8 ms | 1.6 ms | 2.00× |
| Exact polynomial division (1,540 ÷ 120 terms) | 0.776 ms | 1.26 ms | 1.63× |
| Evaluate 496 terms (one point) | 1.01 µs | 3.93 µs | 3.87× |
| Evaluate 496 terms (batch of 256; per point) | 0.0751 µs | 2.54 µs | 33.77× |
Evaluation uses prebuilt evaluators and preallocated NumPy inputs; Python-call and output-allocation costs are included. The batch row reports time per point. Evaluator construction and the first call took a combined median of 6.63 ms natively and 55 ms in WASM, excluded from the warmed timings. See the methodology and raw measurements for the matching Symbolica 3.0.2 builds, setup breakdown, and all samples.
Numerical evaluation shows a larger gap, especially for batches. Native evaluators use JIT compilation; the WASM build currently interprets the optimized evaluation instructions. In the next Symbolica version, WASM will also be supported as a JIT target (using symjit-wasm).
Closing
Symbolica is now accessible through multiple platforms: you can run it directly in your browser and inside Typst documents. We are very excited about the new use cases that this unlocks.
Try the live examples in the gallery!
Appendix Detailed changelog All changes since Symbolica 2.2
Exact algebra and solving
- Added exact polynomial-system solving with parameters and solution families.
Expression.solve()returns aSolutionSetcontaining variable assignments, free variables, validity conditions, and complete or generic coverage. Solving supports complex, real, rational, and integer domains; unsupported problems raise an error. The oldsolve_linear_system()entry point was removed. - Added canonical algebraic roots, certified root embeddings, and algebraic contexts for combining generators. Algebraic coefficients can be retained through polynomial conversion and factorization, including factorization in a specified algebraic extension.
- Root indices put real roots first, in ascending order, followed by complex roots ordered by real and then imaginary part. Root isolation and numerical root finding handle difficult and degenerate cases more reliably.
- Extended partial fractions to multiple variables with
expr.apart(x, y)and automatic variable selection withexpr.apart(). - Improved polynomial multiplication, homogeneous multiplication, modular GCD reconstruction, factor reconstruction, resultants, and Gröbner-basis reduction. Polynomial storage is more compact, and coefficient domains can supply specialized arithmetic kernels.
- Unified checked polynomial division and improved failure handling for non-exact quotients. Fixed rational-polynomial constants, variable contexts, and arithmetic edge cases; derivatives cancel common factors, and powers use binary exponentiation.
- Fixed resultant signs and vanishing subresultants, zero generators in F4 reduction, algebraic constant coefficients, finite-field factor descent, and inconsistent sparse linear systems.
- Added Rust polynomial and rational reconstruction algorithms, with shared resource budgets and propagation of errors from black-box evaluations.
Expressions and pattern matching
- Python
==and!=now return Booleans. Use.eq()and.ne()to construct deferred conditions; ordered comparisons also construct conditions. Unknown conditions evaluate toNone. - Mathematical property queries distinguish a proven false result from an
unknown one. Python's
is_real(),is_integer(),is_scalar(),is_positive(), andis_nonnegative()returnTrue,False, orNone. - Match callbacks use
True,False, andNonefor accept, reject, and undecidable. Matching levels now use separatemin_levelandmax_levelbounds in place oflevel_range. - Added the
Flatfunction attribute (is_flat=Truein Python), which flattens nested calls to the same function, and lookup of existing symbols throughExpression.get()in Python andSymbol::get()in Rust. - Extended expression indexing with negative indices and Python slices of immediate children. Arithmetic can defer to a custom right operand's reflected operators, supporting Community types without discarding them.
- Added configurable
check_zero()identity tests with finite-field and numerical sampling, precision limits, exact fallback, and an explicit choice to treat stored floating coefficients as exact. Results can be undecidable. - Pruned impossible pattern matches earlier while preserving required wildcard minima in partial matches, and improved selective expansion and normalization.
- Series coefficient access distinguishes known zero coefficients from terms outside the computed order. Fixed fractional-order conversions and truncation of empty series; expansions can also use wildcard variables.
- Added Python access to further transcendental functions, including reciprocal trigonometric and hyperbolic functions and their inverses.
- Improved implicit multiplication before parentheses, Mathematica built-in parsing, fraction formatting, and canonical strings. Canonical strings bypass custom printers.
- Symbol printers accept per-mode dictionaries as well as callbacks. Symbols with attached data can be parsed, and cyclic printing is canonicalized.
- Expression export includes only the state needed by the expression. Import preserves symbol metadata and variable mappings more reliably, and tensor sums retain completed contractions.
- Stateful serialization rebuilds imported symbol identities and preserves expression-valued tags. Evaluator saves include the symbolic state needed for loading in another thread or process; external callbacks remain separate.
Breaking change: antisymmetric function matching
Rules that rely on permuting the arguments of an antisymmetric function must be updated. In 2.2, the matcher considered argument permutations. In 3.0, antisymmetric functions match argument by argument in the order stored in the expression, just like ordinary functions.
Antisymmetry still applies during normalization: f(2, 1) becomes
-f(1, 2), and f(1, 1) becomes zero. However, calls containing wildcards
retain their argument order, so the position of each wildcard in a pattern
now matters. For example, f(w_, 1) does not match f(1, 2); use
f(1, w_) to match that stored order.
Review replacements, match iterators, and restrictions that depend on a particular wildcard receiving an argument. If a rule needs several argument orders, express them explicitly and account for the permutation sign in the replacement. See the migration example.
Numerical evaluation
- Exposed Numerica's immutable
FloatandComplexFloatin Python. Arbitrary- precision evaluation, numerical solving, and high-precision polynomial roots return these scalars. Decimal inputs remain supported. - Added precision-aware arithmetic, elementary functions, constants, formatting, Decimal conversion, and random sampling on the new scalars.
- Added non-inlined sub-evaluators, configurable through
FunctionDefinition(..., inlining="always" | "never" | "auto").get_instructions()now exports an object with dimensions, constants, instructions, and nested function bodies. - Added
Evaluator.from_instructions()to reconstruct an evaluator, including retained function bodies. Function names and tags remainExpressionobjects.EvaluatorInstructions.constant_functionsexposes registered constants and fixed-argument calls asEvaluatorConstantFunctionobjects, so code generators can evaluate them at their target precision. - NumPy is optional for Python evaluation. Machine-precision batch results are NumPy arrays when it is installed and nested Python lists otherwise.
- Compiled evaluator libraries export their input and output dimensions. Loading reads this metadata, and evaluation validates buffer and batch sizes. Replacing a compiled library at the same path is detected.
- Precision evaluators reject invalid input counts and unsupported precision with Python exceptions. Improved preservation of constant precision, evaluation at integer Bessel orders, and transcendental calls in generated C++.
- Fixed constant-function handling, complex and SIMD code generation, register reuse, deserialization, and deeply nested evaluator failures. Retained function bodies survive differentiation and receive shared optimizations.
- Added Rust tracked numerical evaluation and harmonic/generalized polylogarithm periods, including sixth-root and endpoint-one evaluations.
- Updated the SymJIT dependency to 2.25. Direct Rust expression evaluation now
also accepts standard-library
HashMapvalues.
Notebook output and Community APIs
- Added shared bounded MathML output for expressions, polynomials, series,
rational functions, and matrices.
MathMLViewer,MathMLPageSource,MathMLPage, andMathMLMarkersupport custom providers and a standalone browser renderer without requiring widget packages for page generation. formatted()uses paged MathML by default;mathml=Falseselects the highlighted text view. Custom printers can return bounded MathML fragments forPrintMode.MathML.- Added responsive wrapping, inspection, expansion/collapse, and keyboard zoom. Omission markers open at the hidden items, compact products can fit completely in automatic previews, and explicit page sizes remain bounded.
- Corrected signs, fraction sizing, continuation operators, matrix paging, radical grouping, and omission placement. Compact tensor indices and trailing factors remain visible when they fit; cached pages and fewer layout passes reduce repeated rendering work.
- Community tensor expressions preserve ordered external axes through their
tensor-aware operations. Use
outeror explicit port contractions to control how axes combine,to_expression()to drop the tensor interface, andpaged()to retain tensor notation in interactive output. - HEPkit graph output supports native SVG views and isolated interactive notebook display. Tensor, graph, and integration components share the same Symbolica kernel and typed extension registration.
Numerica 3.0
- Integer and floating-point backends can be selected independently:
integer-gmporinteger-malachite, andfloat-mpfrorfloat-astro. GMP integers and MPFR floats remain the defaults. - Redesigned compact integer storage, improved integer factorization, and added faster integer, rational, and finite-field polynomial kernels, including checked division kernels.
- Added certified real and complex interval arithmetic for enclosures and root certification.
- Separated mathematical ordering (
OrderedRing,RealEmbedding) and random sampling (SampleableRing) from general ring operations. Added explicit integer-range sampling and compatible RNG re-exports. - Improved complex arithmetic accuracy, decimal parsing, precision validation, and NaN construction that preserves numeric shape. Corrected zero powers in finite fields.
- Corrected matrix inversion and sparse identity checks, validated dense and sparse matrix structure, and added approximate integer lattice-basis reduction.
- Integration grids validate dimensions, bin counts, and sampling settings.
Rust continuous and discrete grid constructors now return
Result. - Fixed determinant row-swap signs, signed integer shifts and serialization, finite-field sampling, and complex powers and division. Improved transcendental precision and error estimates, preserving uncertainty across working-precision changes and mixed-precision arithmetic.
Builds and licensing
- Symbolica uses Numerica 3.0 and Graphica 3.0. The minimum Rust version remains 1.89.
- Added WebAssembly builds with the
wasmfeature. Native compilation and JIT support are controlled bynative_code_generation, enabled by default. - Introduced the Symbolica Source-Available License 1.0. Numerica remains MIT licensed. See the license information and the license supplied with each package.
- Updated signed offline license keys and moved Rust licensing APIs into
symbolica::license. Added signed library keys for scoped multicore unlocking in Python packages and Rust libraries. - Legacy license keys are processed with a migration warning. Dependent Rust
libraries can capture and activate
InheritedLibraryUnlockhandles in their workers, avoiding Python GIL deadlocks during license checks. - Added typed extension points for optional Python implementations linked into
one native host, and the separate
c_apifeature gate for the C API.
Discussion