symeval
Write a sympy expression, fill in pint quantities, and get the full derivation, that is (1) formula, (2) substituted values with units, and (3) the result with unit — rendered as LaTeX in your marimo or Jupyter notebook.
- ✨ Crystal-clear — shows the full derivation: formula, values with units, and result
- 🐍 Pure Python — drop into your interactive notebooks and other Python code, no special syntax, no cell magic, no Domain-Specific Language (DSL)
- 📏 Unit-aware —
pintquantities carry units through every step and convert to your chosen output unit - 🧮 Sympy-native — rearrange or simplify your formula symbolically first, then evaluate
- 📊 DataFrame-ready — use
quantity_evalf()to compute a new unit-aware column on aDataFrame
pip install symeval
Axial stress under a compressive force
from pint import Quantity
from sympy import Symbol
from symeval import sym_evalf
sigma = sym_evalf(
expr=Symbol("F") / Symbol("A"),
subs={Symbol("F"): Quantity(-680, "kN"), Symbol("A"): Quantity(10_580, "mm^2")},
output_symbol=r"\sigma",
output_unit="MPa",
decimals=2,
)
$$\begin{align*} \sigma &= \frac{F}{A} \ &= \frac{,-680\ \mathrm{kN}}{,10580\ \mathrm{mm}^{2}} \ \sigma &= -6.43\times 10^{7}\ \mathrm{Pa} = -64.27\ \mathrm{MPa} \end{align*}$$
You can also build the sympy expression first and call .sym_evalf() as a method — useful when you want to do symbolic math before filling in numbers. Pass mode= to choose the rendering style; mode="verbose" adds an extra line showing all values converted to SI base units:
f_sym, a_sym = Symbol("F"), Symbol("A")
sigma_expr = f_sym / a_sym
sigma_expr.sym_evalf(
subs={f_sym: Quantity(-680, "kN"), a_sym: Quantity(10_580, "mm^2")},
output_symbol=r"\sigma",
output_unit="MPa",
decimals=2,
mode="verbose",
)
$$\begin{align*} \sigma &= \frac{F}{A} \ &= \frac{,-680\ \mathrm{kN}}{,10580\ \mathrm{mm}^{2}} \ &= \frac{,-6.800\times 10^{5}\ \mathrm{N}}{,1.058\times 10^{-2}\ \mathrm{m}^{2}} \ \sigma &= -6.43\times 10^{7}\ \mathrm{Pa} = -64.27\ \mathrm{MPa} \end{align*}$$
mode="one_line" collapses the derivation onto a single line:
sigma_expr.sym_evalf(
subs={f_sym: Quantity(-680, "kN"), a_sym: Quantity(10_580, "mm^2")},
output_symbol=r"\sigma",
output_unit="MPa",
decimals=1,
mode="one_line",
)
$$\sigma = \frac{F}{A} = \frac{,-680\ \mathrm{kN}}{,10580\ \mathrm{mm}^{2}} = -64.3\ \mathrm{MPa}$$
quantity_evalf() on a DataFrame
quantity_evalf is the numeric-only sibling of sym_evalf — same unit-aware evaluation, no LaTeX overhead. It's useful for applying a formula across every row of a DataFrame:
import polars as pl
from pint import Quantity
from sympy import Symbol
from symeval import quantity_evalf
f_sym, a_sym = Symbol("F"), Symbol("A")
sigma_expr = f_sym / a_sym
members = pl.DataFrame({
"member_type": ["column", "column", "brace", "strut", "tie"],
"section": ["W14x90", "HSS8x8x5/8", "HSS6x6x3/8", "L4x4", "C8x11.5"],
"F_kN": [-720.0, -680.0, 340.0, -110.0, 250.0],
"A_mm2": [17_100.0, 10_580.0, 4_890.0, 1_870.0, 2_168.0],
})
def stress_MPa(row):
return quantity_evalf(
sigma_expr,
subs={f_sym: Quantity(row["F_kN"], "kN"), a_sym: Quantity(row["A_mm2"], "mm^2")},
output_unit="MPa",
).magnitude
members_with_stress = members.with_columns(
pl.struct(["F_kN", "A_mm2"])
.map_elements(stress_MPa, return_dtype=pl.Float64)
.alias("sigma_MPa")
)
| member_type | section | F_kN | A_mm2 | sigma_MPa |
|---|---|---|---|---|
| column | W14x90 | -720.00 | 17100.00 | -42.11 |
| column | HSS8x8x5/8 | -680.00 | 10580.00 | -64.27 |
| brace | HSS6x6x3/8 | 340.00 | 4890.00 | 69.53 |
| strut | L4x4 | -110.00 | 1870.00 | -58.82 |
| tie | C8x11.5 | 250.00 | 2168.00 | 115.31 |
Then use sym_evalf to show the full derivation for any row you want to inspect:
sigma_expr.sym_evalf(
subs={f_sym: Quantity(-680, "kN"), a_sym: Quantity(10_580, "mm^2")},
output_symbol=r"\sigma",
output_unit="MPa",
decimals=1,
)
$$\begin{align*} \sigma &= \frac{F}{A} \ &= \frac{,-680\ \mathrm{kN}}{,10580\ \mathrm{mm}^{2}} \ \sigma &= -6.4\times 10^{7}\ \mathrm{Pa} = -64.3\ \mathrm{MPa} \end{align*}$$
Axial resistance of a steel HSS member
A worked example from CSA S16-17. Each sym_evalf result feeds into the next — F_e into $\lambda$, $\lambda$ into $C_r$, $C_r$ into $DCR$ — so the LaTeX rendering captures the full audit trail of a multi-step engineering check:
$$F_{e} = \frac{\pi^{2} E r_{y}^{2}}{L^{2} k^{2}} = \frac{\pi^{2} ,200\ \mathrm{GPa} ,\left(76.1\ \mathrm{mm}\right)^{2}}{,\left(6.5\ \mathrm{m}\right)^{2} ,1^{2}} = 0.271\ \mathrm{GPa}$$
$$\lambda = \left(\frac{F_{y}}{F_{e}}\right)^{n} = \left(\frac{,400\ \mathrm{MPa}}{,0.2706\ \mathrm{GPa}}\right)^{,1.34} = 1.689$$
$$C_{r} = A F_{y} \phi_{s} \left(\lambda + 1\right)^{- \frac{1}{n}} = ,10580\ \mathrm{mm}^{2} ,400\ \mathrm{MPa} ,0.85 \left(,1.6886 + 1\right)^{- \frac{1}{,1.34}} = 1.720\ \mathrm{MN}$$
$$DCR = \frac{C_{f}}{C_{r}} = \frac{,680\ \mathrm{kN}}{,1.7196\ \mathrm{MN}} = 0.395$$
See symeval_mo.py for the full reactive marimo notebook with input UIs.
Ideal Gas Law: symbolic rearrangement
Starting from $PV = nRT$, sympy.solve rearranges the equation symbolically for any variable, then the resulting expression feeds straight into sym_evalf:
$$\begin{align*} P &= \frac{R T n}{V} \ &= \frac{,8.314\ \frac{\mathrm{J}}{\left(\mathrm{K} \cdot \mathrm{mol}\right)} ,273.15\ \mathrm{K} ,1\ \mathrm{mol}}{,22.4\ \mathrm{l}} \ P &= 1.01\times 10^{5}\ \mathrm{Pa} = 101.39\ \mathrm{kPa} \end{align*}$$
See symeval_mo.py for the full reactive marimo notebook with input UIs.
Author
Built and maintained by Joost Gevaert at Bedrock.
Feedback & contributing
Found a bug or have a feature request? Open an issue — pull requests are welcome too. The package is a single marimo notebook (symeval_mo.py) with ## EXPORT-marked cells extracted into src/symeval/ via mobuild; see CLAUDE.md for the project layout and RELEASING.md for the release workflow.
Inspiration
- handcalcs — renders Python calculation code as LaTeX in Jupyter
- CalcPad — engineering calculations DSL with symbolic/numeric workflow
- Bret Victor's Explorable Explanations
License
Apache License 2.0 — see LICENSE.
Release files for symeval 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| symeval-0.4.0.tar.gz | 17.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| symeval-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.5 kB
Release files / symeval-0.4.0.tar.gz
| Download URL | symeval-0.4.0.tar.gz |
|---|---|
| Size | 17.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e0395782f61140f68a87b2622df9c61f5c2e9ecfdcc31ab9a5ca03450977951
|
|
BLAKE2b-256 checksum How to use checksums |
d47fe1c92d976db792f2be1d34af6b0bfa85e0ebc3dc86de232f60bbf140213a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / symeval-0.4.0-py3-none-any.whl
| Download URL | symeval-0.4.0-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
088f8f09f217feaddc9d4beb43ef19b77c9b1f004049eb2c7e4643d98658ab73
|
|
BLAKE2b-256 checksum How to use checksums |
f8574e7a5b58075e95233aaf53f357140d3e07c5e7cf4b42404938f59c4ba5a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|