Interoperability
Three optional package extensions turn SymbolicUncertainties.jl into a hub for broader Julia scientific-computing workflows. Each activates automatically when the triggering package is loaded.
Latexify.jl — calibration-certificate LaTeX
using Symbolics, SymbolicUncertainties, Latexify
@variables V I σV σI
R = (V ± σV) / (I ± σI)
latex(R)
# e.g. "\\frac{V}{I} \\pm \\sqrt{\\frac{σV^{2}}{I^{2}} + \\frac{V^{2} σI^{2}}{I^{4}}}""\\begin{equation}\n\\frac{V}{I}\n\\end{equation}\n \\pm \\begin{equation}\n\\sqrt{\\mathtt{{\\sigma}V}^{2} ~ \\left( \\frac{1}{I} \\right)^{2} + \\mathtt{{\\sigma}I}^{2} ~ \\left( \\frac{ - V}{I^{2}} \\right)^{2}}\n\\end{equation}\n"The fallback's ArgumentError is replaced by the SymbolicUncertaintiesLatexifyExt extension. Use the returned String directly in your LaTeX calibration-certificate template.
Measurements.jl — convert a substituted measurement
Measurements.jl exports ± as well. using both packages makes the operator ambiguous and unusable — Julia resolves neither. Use import Measurements and qualify the call, the same idiom DynamicQuantities recommends for coexisting with Unitful.
Distributions.jl collides the same way, through IntervalSets, and that one is harder to avoid: a Monte Carlo cross-check requires it, since JCGM 101:2008 §6.4 wants a density per input. In such a session, build measurements with the constructor — SymbolicMeasurement(V, σV) — rather than with ±.
Bridge to the standard Julia uncertainty type for numerical pipelines:
using Symbolics, SymbolicUncertainties, DynamicQuantities
import Measurements
# A mass of 5.000 g with u_c = 0.1 mg. `Measurement` carries the
# number and its dispersion; the unit rides alongside it.
m = 5.0 ± 0.0001
Measurements.Measurement(m) * us"g"5.0 ± 0.0001 gsubstitute does not auto-promote — the conversion is explicit:
@variables V I σV σI
R = (V ± σV) / (I ± σI)
ohm = Dict(V => 5.0us"V", σV => 0.01us"V", I => 0.5us"A", σI => 0.001us"A")
# `Measurement` needs plain numbers, so the values are stripped for the
# conversion — and the unit of the answer is taken from
# [`evaluate`](@ref), which derives it from the model rather than
# leaving it to be asserted here.
R_num = Symbolics.substitute(R, Dict(k => ustrip(v) for (k, v) in ohm))
Measurements.Measurement(R_num) * oneunit(evaluate(R, ohm).val)10.0 ± 0.028 A⁻¹ VConversion fails with ArgumentError when either m.val or m.err is still symbolic — call substitute first.
DataFrames.jl — tabular uncertainty_budget output
Opt into a DataFrame rendering via the as = :dataframe keyword; the default :budget returns the UncertaintyBudget value itself, which already indexes and iterates as a vector of its rows:
using SymbolicUncertainties, Symbolics, DataFrames
@variables V σV R1 σR1 R2 σR2
Vout = propagate(
(v, r1, r2) -> v * r2 / (r1 + r2),
[V ± σV, R1 ± σR1, R2 ± σR2],
)
df = uncertainty_budget(
Vout,
[V, R1, R2],
[σV, σR1, σR2];
as = :dataframe,
)
# df isa DataFrame with 3 rows and columns
# variable, sigma, sensitivity, contribution, relative| Row | variable | sigma | sensitivity | contribution | relative |
|---|---|---|---|---|---|
| Num | Num | Num | Num | Num | |
| 1 | V | σV | R2 / (R1 + R2) | abs(R2 / (R1 + R2))*σV | (((R2*σV) / (R1 + R2))^\e[34m2\e[39m) / ((((-R2*V) / ((R1 + R2)^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR1^\e[34m2\e[39m) + (((R1*V) / (R1^\e[34m2\e[39m + \e[34m2\e[39mR1*R2 + R2^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR2^\e[34m2\e[39m) + ((R2 / (R1 + R2))^\e[34m2\e[39m)*(σV^\e[34m2\e[39m)) |
| 2 | R1 | σR1 | (-R2*V) / ((R1 + R2)^\e[34m2\e[39m) | abs((-R2*V) / ((R1 + R2)^\e[34m2\e[39m))*σR1 | (((-R2*V*σR1) / ((R1 + R2)^\e[34m2\e[39m))^\e[34m2\e[39m) / ((((-R2*V) / ((R1 + R2)^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR1^\e[34m2\e[39m) + (((R1*V) / (R1^\e[34m2\e[39m + \e[34m2\e[39mR1*R2 + R2^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR2^\e[34m2\e[39m) + ((R2 / (R1 + R2))^\e[34m2\e[39m)*(σV^\e[34m2\e[39m)) |
| 3 | R2 | σR2 | V / (R1 + R2) + (-R2*V) / ((R1 + R2)^\e[34m2\e[39m) | abs(V / (R1 + R2) + (-R2*V) / ((R1 + R2)^\e[34m2\e[39m))*σR2 | (((V / (R1 + R2) + (-R2*V) / ((R1 + R2)^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR2^\e[34m2\e[39m)) / ((((-R2*V) / ((R1 + R2)^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR1^\e[34m2\e[39m) + (((R1*V) / (R1^\e[34m2\e[39m + \e[34m2\e[39mR1*R2 + R2^\e[34m2\e[39m))^\e[34m2\e[39m)*(σR2^\e[34m2\e[39m) + ((R2 / (R1 + R2))^\e[34m2\e[39m)*(σV^\e[34m2\e[39m)) |
Export to CSV, join with other tabular data, apply filters — the full DataFrames.jl ergonomics are available. When DataFrames.jl is not loaded, requesting as = :dataframe raises ArgumentError.
Combined workflow
All three extensions compose cleanly:
using Symbolics, SymbolicUncertainties, DynamicQuantities
using Latexify, DataFrames
import Measurements
# Build, propagate, report.
@variables V σV R1 σR1 R2 σR2
Vout = propagate(
(v, r1, r2) -> v * r2 / (r1 + r2),
[V ± σV, R1 ± σR1, R2 ± σR2],
)
# Tabular budget.
df = uncertainty_budget(
Vout,
[V, R1, R2],
[σV, σR1, σR2];
as = :dataframe,
)
# LaTeX for the certificate.
tex = latex(Vout)
# Numeric final result, in volts.
divider = Dict(
V => 5.0us"V", σV => 0.01us"V",
R1 => 1_000.0us"Ω", σR1 => 1.0us"Ω",
R2 => 3_000.0us"Ω", σR2 => 1.0us"Ω",
)
Vout_num = Symbolics.substitute(
Vout,
Dict(k => ustrip(v) for (k, v) in divider),
)
meas = Measurements.Measurement(Vout_num) * oneunit(evaluate(Vout, divider).val)3.75 ± 0.0076 VAPI reference
The extensions overload methods on existing functions — no new exports are introduced at the SymbolicUncertainties public surface (REQ-132 audit).
latex— the extension adds the working method at load time; the fallback keeps its docstring.uncertainty_budget— gains theas = :budget | :dataframekeyword.Measurements.Measurement(m)— a constructor on Measurements.jl's type, visible to users via the external package's namespace.