Expanded Uncertainty

Coverage factor assumptions

The k = 2 shortcut returned by expanded_uncertainty(m) and expanded_uncertainty(m, 2) assumes the output distribution is approximately normal (JCGM 100:2008 §6.3.3) and that the effective degrees of freedom ν_eff ≥ 30. Below that threshold, use the keyword form expanded_uncertainty(m; coverage_probability = …) so that k is derived from m.dof via the Student-t quantile of JCGM 100:2008 §G.4 Table G.2. A numeric ν_eff < 30 also triggers a runtime @warn per REQ-175.

The expanded uncertainty U = k·u_c(y) is the headline number on every calibration certificate. The GUM §6.2 equation (18) defines it in terms of a coverage factor k and the combined standard uncertainty u_c(y).

Positional form — expanded_uncertainty(m, k)

The positional form multiplies m.err by k, preserving m.val and m.dof. The default k = 2 matches EA-4/02 usage and the GUM Annex H worked examples.

using Symbolics
using SymbolicUncertainties
using DynamicQuantities

@variables Vin σVin R1 σR1 R2 σR2
Vout = propagate(
    (vin, r1, r2) -> vin * r2 / (r1 + r2),
    [Vin ± σVin, R1 ± σR1, R2 ± σR2],
)

U = expanded_uncertainty(Vout)       # k = 2 (default)
U3 = expanded_uncertainty(Vout, 3)   # k = 3
U.k, U3.k                            # the field is `U`, not `err`
(2, 3)

U.U is k · u_c in closed form. It is not printed here: the voltage divider's combined uncertainty runs to tens of thousands of characters symbolically, which is a fact about the model rather than about the package — see the Uncertainty Budget page for the structured, per-source view of the same quantity.

k may be symbolic; the multiplication is left in closed form.

Negative numeric k raises ArgumentError. Symbolic k is left in closed form without sign inspection.

Keyword form — expanded_uncertainty(m; coverage_probability)

The keyword form derives k from m.dof via an internal lookup into GUM 100:2008 Table G.2 — the Student-t quantile for the given coverage_probability and effective degrees of freedom.

Supported coverage_probability values: {0.68, 0.90, 0.95, 0.99}. Other values raise ArgumentError.

@variables x σx
m = SymbolicMeasurement(x, σx, Symbolics.Num(4))   # ν_eff = 4

U = expanded_uncertainty(m; coverage_probability = 0.95)
# k = 2.78 (t-quantile at ν = 4, p = 0.95, GUM Table G.2)
# Also emits a @warn because ν_eff < 30 (REQ-175)
x ± 2.78σx (k = 2.78)

Dispatch by m.dof

m.dof valuek sourceSide effects
nothing (not supplied)Normal quantile@info (diagnostic)
Symbolics.Num(Inf)Normal quantilenone
Symbolics.Num(n) with n::Real, n ≥ 30Normal quantilenone
Symbolics.Num(n) with n::Real, 1 ≤ n < 30Student-t quantile from Table G.2@warn per REQ-175
Symbolics.Num(n) with n::Real, n < 1ArgumentError
Any other Num (symbolic)Normal quantile@info (diagnostic)

The normal-quantile values for the four supported coverage probabilities are:

pk (normal)
0.681.00
0.901.645
0.951.96
0.992.576

Welch-Satterthwaite — welch_satterthwaite(contributions, dofs)

Coverage factor assumptions

See the box at the top of this page — the Welch-Satterthwaite pathway exists precisely to handle the ν_eff < 30 case the k = 2 shortcut cannot.

welch_satterthwaite(contributions, dofs) returns the symbolic effective degrees of freedom per JCGM 100:2008 §G.4 equation (G.2b):

\[\nu_{\text{eff}} = \frac{\left(\sum_i u_i^2\right)^2} {\sum_i \frac{u_i^4}{\nu_i}}\]

@variables u1 u2 ν1 ν2

ν_eff = welch_satterthwaite([u1, u2], [ν1, ν2])
# = (u1² + u2²)² / (u1⁴/ν1 + u2⁴/ν2)
((u1^2 + u2^2)^2) / ((u1^4) / ν1 + (u2^4) / ν2)

Mismatched list lengths raise DimensionMismatch. Empty or all-zero inputs return the formal 0/0 symbolic expression unchanged.

End-to-end: from contributions to expanded uncertainty

Combine welch_satterthwaite with the keyword form of expanded_uncertainty to derive the coverage-probability-based expanded uncertainty for a small-sample calibration:

using Symbolics
using SymbolicUncertainties

@variables Vin σVin R1 σR1 R2 σR2
@variables ν_Vin ν_R1 ν_R2

Vout = propagate(
    (vin, r1, r2) -> vin * r2 / (r1 + r2),
    [Vin ± σVin, R1 ± σR1, R2 ± σR2],
)

u_Vin = uncertainty_contribution(Vout, Vin, σVin)
u_R1  = uncertainty_contribution(Vout, R1,  σR1)
u_R2  = uncertainty_contribution(Vout, R2,  σR2)

# Evaluated rather than printed: `Symbolics.substitute` inserts the
# numbers but does not reduce them (see upstream-bugs.md UB-001), so
# the closed form stays enormous. `build_evaluator` is the tool that
# actually produces numbers — see the Code Generation page.
# Volts and ohms; stripped because the evaluation below works on
# plain numbers.
vals = Dict(
    k => ustrip(v) for (k, v) in Dict(
        Vin => 12.0us"V", σVin => 0.1us"V",
        R1 => 1_000.0us"Ω", σR1 => 10.0us"Ω",
        R2 => 2_000.0us"Ω", σR2 => 20.0us"Ω",
    )
)
[
    Float64(eval(Symbolics.toexpr(Symbolics.substitute(u, vals)))) for
    u in (u_Vin, u_R1, u_R2)
]

ν_eff = welch_satterthwaite(
    [u_Vin, u_R1, u_R2],
    [ν_Vin, ν_R1, ν_R2],
)

Vout_with_dof = SymbolicMeasurement(Vout.val, Vout.err, ν_eff)
Vout_U = expanded_uncertainty(
    Vout_with_dof;
    coverage_probability = 0.95,
)
(R2*Vin) / (R1 + R2) ± sqrt((((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (((R1*Vin) / (R1^2 + 2R1*R2 + R2^2))^2)*(σR2^2) + ((R2 / (R1 + R2))^2)*(σVin^2))*(1.96 + (2.372384(((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin)) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2) + 2.8227073279999995(((((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2))^2) + 2.5561097948467197(((((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2))^3)) (k = 1.96 + (2.372384(((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin)) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2) + 2.8227073279999995(((((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2))^2) + 2.5561097948467197(((((abs((-R2*Vin) / ((R1 + R2)^2))^4)*(σR1^4)) / ν_R1 + ((abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^4)*(σR2^4)) / ν_R2 + ((abs(R2 / (R1 + R2))^4)*(σVin^4)) / ν_Vin) / (((abs((-R2*Vin) / ((R1 + R2)^2))^2)*(σR1^2) + (abs(Vin / (R1 + R2) + (-R2*Vin) / ((R1 + R2)^2))^2)*(σR2^2) + (abs(R2 / (R1 + R2))^2)*(σVin^2))^2))^3))

Because ν_eff here is a purely symbolic expression (not a concrete Real), the keyword form falls back to the normal quantile and emits an @info message. Supply a concrete numeric substitution via Symbolics.substitute to obtain the t-quantile path and the REQ-175 @warn when appropriate.

API reference

U is not a standard uncertainty

expanded_uncertainty returns an ExpandedUncertainty, not a SymbolicMeasurement. This is deliberate.

U = k · u_c is the half-width of a coverage interval. It is a reporting quantity, not an input to further propagation: a value carrying U where u_c is expected inflates every downstream result by the coverage factor, silently and by exactly k. Returning a SymbolicMeasurement would have made that substitution type-correct and therefore easy to make by accident.

The distinct type carries the estimate, U, the coverage factor k that produced it, and the effective degrees of freedom k was derived from — everything a calibration certificate has to state under JCGM 100:2008 §7.2.3, and nothing that invites reuse in a computation.

SymbolicUncertainties.expanded_uncertaintyFunction
expanded_uncertainty(m::SymbolicMeasurement, k = 2) -> ExpandedUncertainty
Coverage factor assumptions

expanded_uncertainty(m, k) returns U = k·u_c(y) under the GUM §6.3.3 assumption that the output distribution is approximately normal. When the effective degrees of freedom ν_eff < 30, the shortcut k = 2 for 95 % coverage is inadequate; use the keyword form expanded_uncertainty(m; coverage_probability = …) so that k is derived from m.dof via the Student-t quantile of JCGM 100:2008 §G.4.

Return a measurement with val = m.val, err = k·m.err, and dof = m.dof (preserved). The default k = 2 matches EA-4/02 usage and the GUM Annex H worked examples.

Negative numeric k raises ArgumentError. Symbolic k is left in closed form with no sign inspection.

Implements the methodology of JCGM 100:2008 §6.2 equation (18). Traces REQ-050.

source
expanded_uncertainty(m; coverage_probability=nothing) -> ExpandedUncertainty

Single-argument convenience wrapper. With no keyword argument it applies the default k = 2, matching EA-4/02 usage. With an explicit coverage_probability, it derives k from m.dof via the Student-t quantile of JCGM 100:2008 §G.4 Table G.2.

Coverage factor assumptions

The keyword form derives k from m.dof via the Student-t quantile of JCGM 100:2008 §G.4 Table G.2. When m.dof is nothing or a symbolic expression, the normal-distribution quantile is used instead (per GUM §6.3.3); a numeric ν_eff < 30 emits a runtime @warn.

Supported coverage_probability values: {0.68, 0.90, 0.95, 0.99} — the four entries of GUM Table G.2. Other values raise ArgumentError.

See expanded_uncertainty(m, k) for the positional form.

Implements the methodology of JCGM 100:2008 §6.2 / §G.4. Traces REQ-050, REQ-051, REQ-174, REQ-175.

source
SymbolicUncertainties.ExpandedUncertaintyType
ExpandedUncertainty

Result of an expanded-uncertainty calculation: the estimate val, the expanded uncertainty U = k · u_c, the coverage factor k, and the effective degrees of freedom the factor was derived from.

Deliberately not a SymbolicMeasurement. U is a coverage interval half-width, not a standard uncertainty, so a quantity carrying it is not propagatable: feeding U back into a computation would silently inflate every downstream result by k. Returning a distinct type makes that mistake impossible to make by accident (JCGM 100:2008 §6.2).

Traces REQ-050, REQ-051.

source
SymbolicUncertainties.welch_satterthwaiteFunction
welch_satterthwaite(contributions, dofs) -> Num
Coverage factor assumptions

The Welch-Satterthwaite effective degrees of freedom support the Student-t pathway of expanded_uncertainty(m; coverage_probability). The k = 2 shortcut of expanded_uncertainty(m, 2) assumes ν_eff ≥ 30; below that threshold, JCGM 100:2008 §6.3.3 requires the t-quantile path this function enables.

Return the symbolic effective degrees of freedom ν_eff per JCGM 100:2008 §G.4 equation (G.2b):

ν_eff = (Σᵢ uᵢ²)² / Σᵢ (uᵢ⁴ / νᵢ)

where uᵢ are the per-variable uncertainty contributions (typically from uncertainty_contribution) and νᵢ the associated degrees of freedom.

Raises DimensionMismatch when the two lists differ in length.

Edge case: empty lists or all-zero contributions produce the formal 0/0 symbolic expression unchanged — no special-case substitution to Inf.

Implements the methodology of JCGM 100:2008 §G.4. Traces REQ-052, REQ-174.

source