Symbolic evaluation for engineering calculations — renders expression → numbers with units → result as LaTeX
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file symeval-0.3.2.tar.gz.
File metadata
- Download URL: symeval-0.3.2.tar.gz
- Upload date:
- Size: 15.9 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
582c4c21584bb17ecdc747df976f0c6bdffdeeaf0685ad7b06a0a708c6e0a2c6
|
|
| MD5 |
52654b1c5e88fc4b31256243c83917e8
|
|
| BLAKE2b-256 |
412edfc6038384fe017c3cbdb6004ccea1a47eb0d39b8f22f013f850d4c14327
|
File details
Details for the file symeval-0.3.2-py3-none-any.whl.
File metadata
- Download URL: symeval-0.3.2-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5b5eed2649bdf7159c5e5bb7c38dc225de614169579c63f0d076ccbc123c116
|
|
| MD5 |
0e4e4945e45715973e2dfe79d2bcabc5
|
|
| BLAKE2b-256 |
cb65be7ff66cee9cd37dbae82dabcc7b815110608e0a06dd11d5a507a5fc5cc9
|