Symbolica Playground

A hands-on tour of Symbolica 2.0: exact symbolic manipulation, pattern matching, programmable symbols, polynomial arithmetic, and fast numerical evaluation from Python. Run the cells from top to bottom, then substitute one of your own expressions to see how the pieces fit together.

Setup

Install Symbolica and import the Python API. The temporary namespace keeps this notebook friendly to re-runs when we define custom symbols later.

%pip install -q --upgrade symbolica numpy

from uuid import uuid4
import cmath
import math
from decimal import Decimal

from IPython.display import display
from symbolica import *

set_namespace("playground_" + uuid4().hex[:8])
print("Symbolica", get_version())
error: externally-managed-environment



× This environment is externally managed

╰─> To install Python packages system-wide, try 'pacman -S

    python-xyz', where xyz is the package you are trying to

    install.

    

    If you wish to install a non-Arch-packaged Python package,

    create a virtual environment using 'python -m venv path/to/venv'.

    Then use path/to/venv/bin/python and path/to/venv/bin/pip.

    

    If you wish to install a non-Arch packaged Python application,

    it may be easiest to use 'pipx install xyz', which will manage a

    virtual environment for you. Make sure you have python-pipx

    installed via pacman.



note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.

hint: See PEP 668 for the detailed specification.

Note: you may need to restart the kernel to use updated packages.

Symbolica symbolica-f52eea6

Trying Symbolica

This notebook is meant to be both a tutorial and a small playground. Each section introduces one idea, shows a compact example, and leaves room for you to change the expression or the operation.

1. Build Expressions

Symbols are the atoms of Symbolica. A symbol can be used as a variable, and calling it turns it into a function.

x, y, z, f, g = S("x", "y", "z", "f", "g")

expr = E("f(x, y) + 5*(x + y)^2 + 1_123_456") + x**2
expr
1123456+f(x,y)++(x+y)²

Symbolica keeps exact rational structure by default. You can expand, collect, format, and inspect expressions without leaving Python. In Jupyter, Symbolica expressions use rich HTML output by default; you can also switch the rendered output to LaTeX from Jupyter’s output display controls, or force LaTeX for a specific display call.

expanded = expr.expand()

print("Expanded:")
display(expanded)

poly_part = E("(x + y)^4 + (x - y)^4").expand()

print("\nA polynomial identity:")
display(poly_part)

print("\nCoefficients as a polynomial in x:")
for power, coefficient in poly_part.coefficient_list(x):
    print(f"{power}: {coefficient}")

print("\nNumber of terms:", len(expanded))
Expanded:
1123456+10·x·y+f(x,y)+6·x²+5·y²

A polynomial identity:
12·x²·y²+2·x⁴+2·y⁴

Coefficients as a polynomial in x:
1: 2*y^4
x^2: 12*y^2
x^4: 2

Number of terms: 5

Pretty printing is useful once expressions stop fitting on one line. The rich formatted output wraps long expressions, uses superscripts, and colors brackets and built-in functions in notebooks.

pretty_expr = E("exp((1+x)^7) + (1+y)^10 + f(y) + g(x)").expand()

pretty_expr.formatted(max_line_length=56, terms_on_new_line=True)
1 +10·y +f(y) +g(x) +exp((1+x)) +45·y² +120·y³ +210·y⁴ +252·y⁵ +210·y⁶ +120·y⁷ +45·y⁸ +10·y⁹ +y¹⁰

For papers, slides, and documentation, display the same expression as LaTeX. In Jupyter you can also toggle the output renderer to LaTeX from the output’s display options.

display(pretty_expr, include=["text/latex"])

\[1+10 y+f\!\left(y\right)+g\!\left(x\right)+\exp\!\left(\left(1+x\right)^{7}\right)+45 y^{2}+120 y^{3}+210 y^{4}+252 y^{5}+210 y^{6}+120 y^{7}+45 y^{8}+10 y^{9}+y^{10}\]

2. Collect And Factor

Symbolica can reorganize expressions without changing their value. collect groups terms around a chosen symbol, while collect_by_coefficient groups terms that share the same numerical coefficient.

x, y, z = S("x", "y", "z")

to_collect = E("2*x + 2*x^2 + x^3 + 3*y + 3*x*y")

print("Expression:")
display(to_collect)

print("\nCollected in x:")
display(to_collect.collect(x))

print("\nCollected by numerical coefficient:")
display(to_collect.collect_by_coefficient())

print("\nCoefficient list as a polynomial in x:")
for power, coefficient in to_collect.coefficient_list(x):
    print(f"{power}: {coefficient}")
Expression:
2·x+3·x·y+3·y+2·x²+

Collected in x:
(2+3·y)+3·y+2·x²+

Collected by numerical coefficient:
(x+x²)+(x·y+y)+

Coefficient list as a polynomial in x:
1: 3*y
x: 2+3*y
x^2: 2
x^3: 1

Factoring works directly on expressions. For polynomial-heavy workflows, you can also convert to a polynomial and use the polynomial-specific routines shown later.

small_factor = E("(x + y)^2 - z^2").expand()
larger_factor = E("3*(2*x^2 + y)*(x^3 + y)^2*(1 + 4*y)^2*(1 + x)").expand()

print("Expanded:")
display(small_factor)

print("\nFactored:")
display(small_factor.factor())

print("\nA larger factorization:")
display(larger_factor.factor())
Expanded:
2·x·y++-

Factored:
(x+y+z)·(x+y-z)

A larger factorization:
(1+x)·(1+4·y)²·(y+x³)²·(y+2·x²)

3. Add Algebraic Meaning

Symbol attributes let you teach Symbolica simple algebra. Here dot is symmetric and linear, so sums and scalar factors are distributed automatically.

x, y = S("x", "y")
dot = S("dot", is_symmetric=True, is_linear=True)

dot(3*x + y, x + 2*y)
3·dot(x,x)+7·dot(x,y)+2·dot(y,y)

Built-in special functions already know many exact values and simplifications.

z = S("z")

E("gamma(1/2)")
𝜋^(1/2)
E("polylog(-3, z)")
(z+4·z²+z³)/(1-z)
E("gamma(5/6)").to_float()
1.12878702990813

4. Pattern Matching

Wildcards end in _. They match subexpressions, and you can use them on the left and right side of a replacement.

x, y, f = S("x", "y", "f")
u_ = S("u_")

(f(5) + f(x + y)).replace(f(u_), f(u_ + 1))
f(6)+f(1+x+y)

Sequence wildcards end in __. They match a variable-length list of function arguments.

f, xs__ = S("f", "xs__")

f(1, 2, 3, 4).replace(f(xs__, 4), f(xs__))
f(1,2,3)

Transformers package operations so Symbolica can run them efficiently. This is useful when a replacement should do more than substitute text.

x, f, u_ = S("x", "f", "u_")

f((x + 1)**2).replace(f(u_), f(u_.hold(T().expand())))
f(1+2·x+x²)

5. Series And Derivatives

Symbolica can differentiate and expand exact expressions, including expressions with special functions.

x = S("x")

expr = E("exp(3*x) + x^2*log(1 + x)")

print("Derivative:")
display(expr.derivative(x))

print("\nSeries around x = 0:")
expr.series(x, 0, 5)
Derivative:
2·x·log(1+x)+exp(3·x)+x²/(1+x)

Series around x = 0:
1+3·x+9/2·x^2+11/2·x^3+23/8·x^4+283/120·x^5+𝒪(x^6)

6. Programmable Symbols

Symbolica 2.0 lets users attach hooks to symbols. This toy gamma-like function has a derivative rule and a series rule that regularizes a pole at zero.

set_namespace("playground_hooks_" + uuid4().hex[:8])

x = S("x")

def regularize_toy_gamma(args):
    if args[0].get_coefficient(0) == 0:
        a = args[0].to_expression()
        return (1 / a, S("toy_gamma")(a + 1))

toy_gamma = S(
    "toy_gamma",
    derivative=lambda f, _index: f * E("digamma")(f[0]),
    series=regularize_toy_gamma,
)

display(toy_gamma(x).derivative(x))
toy_gamma(x).series(x, 0, 0)
toy_gamma(x)·digamma(x)
toy_gamma(1)·x^-1+toy_gamma(1)·digamma(1)+𝒪(x^1)

Evaluation hooks teach a custom symbol how to become a number. The same symbolic expression can remain exact until you ask for a floating-point approximation.

set_namespace("playground_eval_" + uuid4().hex[:8])

x = S("x")
my_cosh = S(
    "my_cosh",
    eval={
        "float": lambda args: math.cosh(args[0]),
        "complex": lambda args: cmath.cosh(args[0]),
    },
)

expr = my_cosh(1/2) + x

display(expr)
print("Float conversion:", expr.to_float())
print("Direct evaluation at x = 3:", expr.evaluate({x: 3.0, my_cosh(1/2): math.cosh(0.5)}))
x+my_cosh(5.00000000000000e-1)
Float conversion: 1.12762596520638+x
Direct evaluation at x = 3: (4.127625965206381+0j)

7. From Formula To Numerical Kernel

One of Symbolica’s strengths is moving between exact formulas and fast numerical evaluation. You can derive expressions symbolically, then evaluate them many times in optimization, fitting, simulation, or integration loops.

x, y = S("x", "y")

objective = E("(sin(x*y) + gamma(x + 1)/(1 + y^2))^2 + (x - y)^2")
gradient = [objective.derivative(v) for v in (x, y)]

print("Objective:")
display(objective)

print("\nExact gradient:")
for variable, derivative in zip((x, y), gradient):
    print(f"d/d{variable} = {derivative}")
Objective:
(x-y)²+(sin(x·y)+gamma(1+x)/(1+y²))²

Exact gradient:
d/dx = 2*(x-y)+2*(y*cos(x*y)+gamma(1+x)*polygamma(0,1+x)/(1+y^2))*(sin(x*y)+gamma(1+x)/(1+y^2))
d/dy = -2*(x-y)+2*(x*cos(x*y)-2*y*gamma(1+x)/(1+y^2)^2)*(sin(x*y)+gamma(1+x)/(1+y^2))

Direct evaluation is convenient for one-off computations and quick checks.

x, f = S("x", "f")
expr = E("3*cos(x)") + f(2)

expr.evaluate({x: 1.0, f(2): 4.0})
(5.620906917604419+0j)

Optimized evaluators are better when the same expression is evaluated many times. The evaluator builds a compact instruction program and, in Python, will use a JIT-compiled backend by default.

import numpy as np

ev = objective.evaluator([x, y])

ev.evaluate(np.array([
    [1.2, 0.7],
    [2.0, 0.4],
    [3.0, 1.5],
]))
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
File /usr/lib/python3.14/site-packages/numpy/_core/__init__.py:24
     23 try:
---> 24     from . import multiarray
     25 except ImportError as exc:

File /usr/lib/python3.14/site-packages/numpy/_core/multiarray.py:11
      9 import functools
---> 11 from . import _multiarray_umath, overrides
     12 from ._multiarray_umath import *  # noqa: F403

ImportError: /usr/lib/libgomp.so.1: cannot allocate memory in static TLS block

The above exception was the direct cause of the following exception:

ImportError                               Traceback (most recent call last)
Cell In[30], line 1
----> 1 import numpy as np
      2 
      3 ev = objective.evaluator([x, y])
      4 

File /usr/lib/python3.14/site-packages/numpy/__init__.py:112
    109 from . import _distributor_init
    111 try:
--> 112     from numpy.__config__ import show_config
    113 except ImportError as e:
    114     if isinstance(e, ModuleNotFoundError) and e.name == "numpy.__config__":
    115         # The __config__ module itself was not found, so add this info:

File /usr/lib/python3.14/site-packages/numpy/__config__.py:4
      1 # This file is generated by numpy's build process
      2 # It contains system_info results at the time of building this package.
      3 from enum import Enum
----> 4 from numpy._core._multiarray_umath import (
      5     __cpu_features__,
      6     __cpu_baseline__,
      7     __cpu_dispatch__,
      8 )
     10 __all__ = ["show_config"]
     11 _built_with_meson = True

File /usr/lib/python3.14/site-packages/numpy/_core/__init__.py:85
     58     major, minor, *_ = sys.version_info
     59     msg = f"""
     60 
     61 IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!
   (...)     82 Original error was: {exc}
     83 """
---> 85     raise ImportError(msg) from exc
     86 finally:
     87     for envkey in env_added:

ImportError: 

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python 3.14 from "/bin/python"
  * The NumPy version is: "2.4.6"

and make sure that they are the versions you expect.

Please carefully study the information and documentation linked above.
This is unlikely to be a NumPy issue but will be caused by a bad install
or environment on your machine.

Original error was: /usr/lib/libgomp.so.1: cannot allocate memory in static TLS block

For higher precision, pass Decimal inputs and the number of decimal digits you want.

x = S("x")
value, _ = ev.evaluate_with_prec([1.2, 0.7], 50)

print(value)

thread '<unnamed>' (20650) panicked at /root/.cargo/git/checkouts/symbolica-db3dbb7e8d40efb5/f52eea6/src/evaluate/evaluator.rs:292:21:
External function 'symbolica_gamma' does not have an implementation for numerica::domains::float::multiprecision::Float
---------------------------------------------------------------------------
PanicException                            Traceback (most recent call last)
Cell In[35], line 2
      1 x = S("x")
----> 2 value, _ = ev.evaluate_with_prec([1.2, 0.7], 50)
      3 
      4 print(value)

PanicException: External function 'symbolica_gamma' does not have an implementation for numerica::domains::float::multiprecision::Float

8. Polynomials

Symbolica has fast exact polynomial arithmetic. Convert an expression to a polynomial when you want polynomial-specific operations such as factorization, GCDs, Groebner bases, or exact parameter elimination.

x, y = S("x", "y")

p = E("3*(2*x^2 + y)*(x^3 + y)^2*(1 + 4*y)^2*(1 + x)").expand().to_polynomial()

print("Terms:", p.nterms())
print("\nSquare-free factorization:")
for factor, exponent in p.factor_square_free():
    print(f"({factor})^{exponent}")
Terms: 30

Square-free factorization:
(1+4*y)^2
(1+x)^1
(y+2*x^2)^1
(y+x^3)^2
(3)^1

You can solve symbolic linear systems with parameters just as naturally as numeric ones.

x, y, c = S("x", "y", "c")

Expression.solve_linear_system(
    [x + y - c, 2*x - y - 3],
    [x, y],
)
[1/3·(3+c), 1/3·(-3+2·c)]

9. Stream Terms

When expressions get large, many operations can be applied term by term. A TermStreamer lets Symbolica process those terms without keeping the full intermediate expression in memory.

x, x_, f = S("x", "x_", "f")
expr = E("x + f(x) + 2*f(y) + 7*f(z)")

stream = TermStreamer(n_cores=2)
stream.push(expr)
stream = stream.map(T().replace(f(x_), f(x_) + x_))
stream.normalize()

stream.to_expression()

Where To Go Next

To keep exploring, replace one expression in this notebook with something from your own work. Try a symbolic preprocessing step, a factorization, a pattern rewrite, or a numerical evaluator, then compare the result with your current approach.

For commercial use, production use, unrestricted cores, or organizational support, see https://symbolica.io/license/.

Useful next stops: the getting-started guide or the Python API docs on the website.