Code Generation
This is where the symbolic pipeline becomes deployable: compiled Julia evaluators for hot-loop Monte Carlo and calibration-batch workflows, and C source strings for embedded metrology systems.
| Function | Purpose |
|---|---|
build_evaluator | Compile (val, err) to a Julia callable or C source |
to_expr | Extract the (val, err) tuple for downstream symbolic tooling |
latex | LaTeX rendering; requires Latexify.jl to be loaded |
build_evaluator — Julia target
using Symbolics, SymbolicUncertainties, DynamicQuantities
@variables V I σV σI
R_m = (V ± σV) / (I ± σI)
ohm = Dict(V => 5.0us"V", σV => 0.01us"V", I => 0.5us"A", σI => 0.001us"A")
g = build_evaluator(R_m, [V, I, σV, σI])
# A compiled evaluator takes plain numbers — that is what compiling it
# is for, and a unit check has no place in an inner loop. The unit of
# what it returns still belongs to the model, so it is taken from
# [`evaluate`](@ref) once, outside the loop, rather than asserted here.
unit_R = oneunit(evaluate(R_m, ohm).val)
val, err = g(5.0, 0.5, 0.01, 0.001) # volts, amperes
(val * unit_R, err * unit_R)(10.0 A⁻¹ V, 0.0282842712474619 A⁻¹ V)The returned callable is compiled via Symbolics.build_function with expression = Val{false} — runtime-compiled, sub-100 ns per call on typical GUM measurements. Use it in Monte Carlo loops, calibration- batch processing, and laboratory-automation inner loops where the substitute + toexpr + eval round-trip is too slow.
Empty-variables fast path
For fully-numeric measurements, pass an empty variable vector:
# A reference resistor: 5.000 Ω with u_c = 0.1 Ω.
m = 5.0 ± 0.1
g = build_evaluator(m, Symbolics.Num[])
g() .* us"Ω"(5.0 Ω, 0.1 Ω)build_evaluator — C source for embedded deployment
c_source = build_evaluator(
R_m,
[V, I, σV, σI];
target = CTarget(),
fname = :ohms_law_evaluator,
)
write("ohms_law.c", c_source)223Writes a C file containing:
#include <math.h>
void ohms_law_evaluator(double *du,
const double V, const double I,
const double σV, const double σI)
{
du[0] = V / I;
du[1] = hypot(σV / I, (V * σI) / (I * I));
}Compile it into firmware, PLC toolchains, or regulated- industry deployment builds where the audit trail requires the GUM formula in an approved artefact.
Why the emitted code says hypot, not sqrt
The symbolic form of u_c is a square root of a sum of squares — the form every GUM text writes, and the readable one. Evaluated in double precision it is fragile: the squares overflow once a contribution passes about 1e154 and underflow to zero below about 1e-150, so u_c comes back as Inf or 0 where hypot returns the right number. hypot is in math.h and in Julia's Base, so the fix costs nothing.
Only the emitted code changes; m.err keeps its sqrt form. The rewrite applies exactly when every addend under the root is a square, which is the uncorrelated regime. Under a declared correlation the variance carries 2·cᵢcⱼ·u(xᵢ,xⱼ) (JCGM 100:2008 eq. 13) — not a square, and possibly negative — and hypot has nowhere to put it, so the emitter falls back to sqrt rather than dropping the cross term.
Scope note — FortranTarget is not supported
EARS REQ-071 mentions "CTarget or FortranTarget when available". Symbolics 7 does not export FortranTarget — see upstream-bugs.md UB-004. Only JuliaTarget() and CTarget() are supported. Requests for other targets raise ArgumentError listing the supported set.
Users who need Fortran bindings can wrap the C output via iso_c_binding or translate the emitted source manually.
to_expr — escape hatch for downstream pipelines
(v, e) = to_expr(R_m)
# v, e are Symbolics.Num — pipe to ModelingToolkit.jl, a
# hand-written LaTeX template, or your own code generator.(V / I, sqrt(((1 / I)^2)*(σV^2) + (((-V) / (I^2))^2)*(σI^2)))to_expr returns the (m.val, m.err) pair. The m.dof field is not included — users who need it read m.dof directly.
latex(m)
latex(m) renders a measurement for a calibration certificate. It is provided by the SymbolicUncertaintiesLatexifyExt package extension, so it needs Latexify.jl in the session:
using Latexify
latex(R_m)Called without Latexify.jl loaded it raises an ArgumentError naming the package to load, rather than an UndefVarError on a name that would otherwise not exist.
Two alternatives need no extension at all:
Base.show(io, MIME"text/latex", m)— emits$val \pm err$automatically in Jupyter / Pluto.to_expr(m)plus a handwritten LaTeX template.
Calibration-certificate snippet (hand-rolled example)
@variables V I σV σI
R = (V ± σV) / (I ± σI)
# building blocks:
(v, e) = to_expr(R)
g_compiled = build_evaluator(R, [V, I, σV, σI])
c_source = build_evaluator(R, [V, I, σV, σI]; target = CTarget())
# Hand-rolled certificate line:
certificate_line = "\\[ R = $(v) \\pm $(e) \\]"
# (with Latexify.jl loaded: certificate_line = latex(R))"\\[ R = V / I \\pm sqrt(((1 / I)^2)*(σV^2) + (((-V) / (I^2))^2)*(σI^2)) \\]"Performance
The compiled Julia evaluator achieves sub-100 ns per call on typical GUM measurements — at least two orders of magnitude faster than the substitute + toexpr + eval path. The speedup is documented but not CI-gated; users verify with @btime from BenchmarkTools.jl.
Error paths
- Unsupported target (e.g.
:bogusorFortranTarget) →ArgumentErrorlistingJuliaTarget/CTargetand noting the FortranTarget deferral per UB-004. - Empty variables + non-numeric measurement →
Symbolics.build_functionerror propagates. latex(m)called without Latexify.jl →ArgumentErrornaming the package to load.
API reference
SymbolicUncertainties.build_evaluator — Function
build_evaluator(m, variables; target = JuliaTarget(), fname = :evaluate_measurement)Compile the (m.val, m.err) computation of the SymbolicMeasurement m into a Julia callable (default) or emit C source as a String, depending on target.
Arguments
m::SymbolicMeasurement— the measurement to compile.variables::AbstractVector{<:Num}— the symbolic input variables in positional order.target(keyword, defaultJuliaTarget()) — code- generation target. Supported:JuliaTarget(),CTarget(). Any other value raisesArgumentError.fname(keyword, default:evaluate_measurement) — name of the generated C function. Ignored forJuliaTarget.
Returns
target = JuliaTarget()— a Julia callableg(args...) -> Tuple{Float64, Float64}accepting the variable values positionally and returning(val, err). Runtime-compiled (expression = Val{false}), sub-100 ns per call on typical GUM measurements.target = CTarget()— aStringcontaining a C function definition. Begins with#include <math.h>and definesvoid <fname>(double *out, const double arg1, …).
Special cases
- When
variablesis empty andmis fully numeric (bothm.valandm.errunwrap to a plainReal), returns a zero-argument closure yielding the numeric(val, err)pair.
Errors
- Unsupported
target(includingFortranTarget— Symbolics.jl 7 does not export it, seeupstream-bugs.mdUB-004) →ArgumentErrorlisting supported targets. Symbolics.build_functionerrors (e.g. variables missing from the expression) are re-raised unchanged.
Implements the deployment pathway of JCGM 100:2008 §9. Traces REQ-070 (Julia target) and REQ-071 (C target).
SymbolicUncertainties.to_expr — Function
to_expr(m::SymbolicMeasurement) -> Tuple{Num, Num}Return the (m.val, m.err) pair for downstream symbolic manipulation. Destructurable via (v, e) = to_expr(m).
The m.dof field is not included — users who need it read m.dof directly. Export utility for downstream reporting per JCGM 100:2008 §7.
Traces REQ-073.
SymbolicUncertainties.latex — Function
latex(m::SymbolicMeasurement) -> StringThe full latex(m) implementation ships as an SymbolicUncertaintiesLatexifyExt package extension in Milestone M8. Until then, this function raises ArgumentError with a message pointing at Latexify.jl and the M4 Base.show(io, MIME"text/latex", m) method as the current notebook-rendering alternative.
Return a LaTeX-formatted String representation of the measurement suitable for direct inclusion in a calibration certificate (planned M8 behaviour). Matches the reporting posture of JCGM 100:2008 §7 (reporting uncertainty).
Traces REQ-072 (deferred to M8).
latex(c::CalibrationCertificate) -> StringRender a CalibrationCertificate as a complete, compilable LaTeX document — \documentclass through \end{document}.
The specimen watermark is applied with eso-pic to every shipped page, not only the first, and is repeated as text in the header and the footer. It cannot be switched off.
Special characters in the supplied fields are escaped, so a laboratory name containing & or % still compiles.
Traces REQ-241.