Inverse Inference
The inverse-inference layer answers the central instrument- design question of metrology: what input precision do I need so that my combined standard uncertainty hits a target?
Four closed-form operations cover the common workflow:
infer_precision(m, σᵢ, target_uc)— solvesm.err = target_ucforσᵢ.infer_all_precisions(m, variables, sigmas, target_uc)— the worst-case single-source precision table.required_precision(m, σᵢ, target_uc)— inequality framing (delegates toinfer_precision).
infer_precision — headline solve
using Symbolics, SymbolicUncertainties, DynamicQuantities
@variables V I σV σI
V_m = V ± σV
I_m = I ± σI
R = V_m / I_m # Ohm's-law resistance
σV_needed = infer_precision(R, σV, 0.01)
# Closed-form σV* such that R.err = 0.01.\[ \begin{equation} \sqrt{I^{2} ~ \left( 0.0001 + \frac{ - \mathtt{{\sigma}I}^{2} ~ V^{2}}{I^{4}} \right)} \end{equation} \]
Substitute concrete numerics to read off the target:
# I = 0.500 A with u(I) = 1 mA. The target σV* comes back in volts.
step1 = Symbolics.substitute(
σV_needed,
Dict(k => ustrip(v) for (k, v) in Dict(I => 0.5us"A", σI => 0.001us"A")),
)\[ \begin{equation} \sqrt{0.25 ~ \left( 0.0001 - 1.6 \cdot 10^{-5} ~ V^{2} \right)} \end{equation} \]
Why the library does not use Symbolics.solve_for or Symbolics.symbolic_solve
Symbolics.solve_for only handles linear equations; Ohm's law is quadratic in σV. Symbolics.symbolic_solve is more capable but requires Nemo.jl for polynomial solving beyond the most trivial case, and even with Nemo it can fail on rational-coefficient systems. SymbolicUncertainties.jl uses the closed-form algebraic split that works for every GUM-standard quadratic measurement:
m.err² = cᵢ²·σᵢ² + Σⱼ≠ᵢ (cⱼ·σⱼ)²
⇒ σᵢ* = sqrt((target_uc² − Σⱼ≠ᵢ(cⱼ·σⱼ)²) / cᵢ²)extracted by subtracting the σᵢ → 0 substituted form from m.err² and dividing by σᵢ². No solver dependency; no Nemo.jl requirement. See specs/007-protocol-and-inference/research.md R1/R2 for the design rationale.
Measurements whose err is not quadratic in σᵢ (e.g. a transcendental sin(σᵢ) term) raise ArgumentError per REQ-091, with a message recommending using Nemo (if you want to try Symbolics.symbolic_solve directly) or numerical root-finding via an external package.
infer_all_precisions — worst-case table
table = infer_all_precisions(R, [V, I], [σV, σI], 0.01)
# table[σV] == 0.01 / |1/I|
# table[σI] == 0.01 / |V/I²|Dict{Symbolics.Num, Symbolics.Num} with 2 entries:
σI => 0.01 / abs((-V) / (I^2))
σV => 0.01 / abs(1 / I)Each entry is the precision that input would need alone to drive u_c(y) = target_uc. Uses the closed-form shortcut σᵢ* = target_uc / |cᵢ| (research R4) where cᵢ comes from the sensitivity_coefficient helper.
RLC resonance
@variables L C σL σC
f0 = propagate(
(l, c) -> 1 / (2π * sqrt(l * c)),
[L ± σL, C ± σC],
)
ε = 0.001
σL_star = infer_precision(f0, σL, ε * f0.val)
# Closed-form σL* such that f0.err == 0.001 · f0.val.\[ \begin{equation} \sqrt{157.91 ~ L^{3} ~ C ~ \left( \frac{ - 2.4612 \cdot 10^{5} ~ \mathtt{{\sigma}C}^{2}}{3.8865 \cdot 10^{7} ~ C^{3} ~ L} + \left( \frac{0.001}{6.2832 ~ \sqrt{C ~ L}} \right)^{2} \right)} \end{equation} \]
Asserted numerically by test/examples/test_rlc_resonance.jl via two-pass substitution through the AsFloat helper (no Nemo needed).
Error paths (REQ-091)
ArgumentError fires in the following cases:
target_uc isa Real && target_uc < 0— standard uncertainty must be non-negative (GUM §4.3.1).σᵢnot inm.err— the measurand does not depend on the supplied input.m.errnot quadratic in σᵢ — the algebraic split fails. Message recommendsusing Nemoor numerical root-finding.
API reference
SymbolicUncertainties.infer_precision — Function
infer_precision(m::SymbolicMeasurement, σᵢ, target_uc) -> NumSolve m.err == target_uc symbolically for σᵢ and return the positive root.
Assumes the GUM-standard quadratic form m.err² = cᵢ²·σᵢ² + (contribution-of-other-σⱼ)² — true for any measurement built via propagate / propagate_correlated / the M1 binary operators. Under this assumption the closed- form σᵢ* is:
σᵢ* = sqrt((target_uc² − Σⱼ≠ᵢ(cⱼ·σⱼ)²) / cᵢ²)Implementation (no general-purpose polynomial solver required — sidesteps the Symbolics.symbolic_solve ⇒ Nemo.jl dependency chain):
- Squares
m.errand simplifies (the sqrt wrapper cancels). - Substitutes σᵢ → 0 to obtain the sum of other-input contributions.
- Subtracts to isolate the σᵢ contribution
cᵢ²·σᵢ². - Extracts
cᵢ² = contribution / σᵢ²via symbolic division. - Computes
σᵢ* = sqrt((target² − others²) / cᵢ²).
Errors:
target_uc isa Real && target_uc < 0→ArgumentError.σᵢnot inm.err→ArgumentError.m.erris not quadratic in σᵢ (the σᵢ-contribution does not factor ascᵢ²·σᵢ²) →ArgumentErrorper REQ-091.
Implements the methodology of JCGM 100:2008 §5.2 inverse. Traces REQ-090, REQ-091.
SymbolicUncertainties.infer_all_precisions — Function
infer_all_precisions(m, variables, sigmas, target_uc) -> Dict{Num, Num}Produce the worst-case single-source precision table. For each pair (xᵢ, σᵢ), compute the σᵢ* that would alone drive m.err = target_uc.
Uses the closed-form shortcut (research R4):
σᵢ* = target_uc / abs(sensitivity_coefficient(m, xᵢ))Requires length(variables) == length(sigmas) (DimensionMismatch otherwise). Propagates any sensitivity_coefficient failure unchanged. Inverse form of JCGM 100:2008 §5.2 applied per-input.
Traces REQ-092.