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.

FunctionPurpose
build_evaluatorCompile (val, err) to a Julia callable or C source
to_exprExtract the (val, err) tuple for downstream symbolic tooling
latexLaTeX 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)
223

Writes 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:

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. :bogus or FortranTarget) → ArgumentError listing JuliaTarget / CTarget and noting the FortranTarget deferral per UB-004.
  • Empty variables + non-numeric measurementSymbolics.build_function error propagates.
  • latex(m) called without Latexify.jlArgumentError naming the package to load.

API reference

SymbolicUncertainties.build_evaluatorFunction
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, default JuliaTarget()) — code- generation target. Supported: JuliaTarget(), CTarget(). Any other value raises ArgumentError.
  • fname (keyword, default :evaluate_measurement) — name of the generated C function. Ignored for JuliaTarget.

Returns

  • target = JuliaTarget() — a Julia callable g(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() — a String containing a C function definition. Begins with #include <math.h> and defines void <fname>(double *out, const double arg1, …).

Special cases

  • When variables is empty and m is fully numeric (both m.val and m.err unwrap to a plain Real), returns a zero-argument closure yielding the numeric (val, err) pair.

Errors

  • Unsupported target (including FortranTarget — Symbolics.jl 7 does not export it, see upstream-bugs.md UB-004) → ArgumentError listing supported targets.
  • Symbolics.build_function errors (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).

source
SymbolicUncertainties.to_exprFunction
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.

source
SymbolicUncertainties.latexFunction
latex(m::SymbolicMeasurement) -> String
M7 stub

The 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).

source
latex(c::CalibrationCertificate) -> String

Render 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.

source