Worked Examples

Six canonical electrical worked examples per EARS REQ-162. Each section follows the same skeleton:

  1. Setup — variables, parameters, physical model.
  2. Propagation — symbolic sensitivity analysis.
  3. Uncertainty budget — tabular breakdown per JCGM 100:2008 §5.1.6 / EA-4/02.
  4. Numeric resultevaluate with values that carry their units.

Every quantity carries its unit — in the model annotations, in the values substituted into it, and in the answer, whose unit evaluate derives from the model rather than taking on trust from a comment. Units are written us"V" rather than u"V" so that results print as A⁻¹ V rather than m² kg s⁻³ A⁻²; both notations are accepted everywhere. Every example checks its own dimensions with check_units: a measurement model that is dimensionally wrong is wrong before any uncertainty is propagated, and the check also verifies that u_c carries the dimension of the measurand — the error a budget hides most easily, since y and u live in different fields.

Examples 1 – 5 are executed when this page is built, so the numbers below are generated rather than transcribed. Example 6 is not: it needs ModelingToolkit.jl and a solver, which the documentation environment does not carry. Every example has a test item — under test/examples/, or test/ext_modelingtoolkit/ for the ODE one — asserting its exact numeric output.


1. Ohm's law (GUM §5.1 canonical)

Setup. Voltage V ± σV in volts and current I ± σI in amperes, independent. Measurand: resistance R = V/I in ohms.

using Symbolics, SymbolicUncertainties, DynamicQuantities

@variables V I σV σI

r = (V ± σV) / (I ± σI)
r.err

\[ \begin{equation} \sqrt{\mathtt{{\sigma}V}^{2} ~ \left( \frac{1}{I} \right)^{2} + \mathtt{{\sigma}I}^{2} ~ \left( \frac{ - V}{I^{2}} \right)^{2}} \end{equation} \]

Dimensions. Units annotate the symbols of the model rather than living inside the expression, so the check is a separate call — and it verifies that u_c carries the dimension of the measurand, which is the error a budget hides most easily:

check_units(r, Dict(V => us"V", σV => us"V", I => us"A", σI => us"A"))
UnitReport: consistent

Uncertainty budget.

uncertainty_budget(r)
Uncertainty budget (EA-4/02 §7.3) for y = V / I
  V:  u = σV   c = 1 / I   |c|u = abs(1 / I)*σV
  I:  u = σI   c = (-V) / (I^2)   |c|u = abs((-V) / (I^2))*σI
  u_c = sqrt(((1 / I)^2)*(σV^2) + (((-V) / (I^2))^2)*(σI^2))

Numeric result. V = 5.000 V ± 0.010 V, I = 0.500 A ± 0.001 A:

ohm = Dict(V => 5.0us"V", σV => 0.01us"V", I => 0.5us"A", σI => 0.001us"A")
evaluate(r, ohm)
(val = 10.0 A⁻¹ V, err = 0.0282842712474619 A⁻¹ V)

Verified in test/examples/test_ohms_law.jl.


2. Voltage divider (sensitivity analysis)

Setup. Input voltage Vin ± σVin in volts, resistors R1 ± σR1 and R2 ± σR2 in ohms. Measurand: Vout = Vin·R2/(R1 + R2), in volts — the resistances enter as a ratio and cancel dimensionally.

@variables Vin σVin R1 σR1 R2 σR2

Vout = propagate(
    (v, r1, r2) -> v * r2 / (r1 + r2),
    [Vin ± σVin, R1 ± σR1, R2 ± σR2],
)
check_units(
    Vout,
    Dict(
        Vin => us"V", σVin => us"V",
        R1 => us"Ω", σR1 => us"Ω",
        R2 => us"Ω", σR2 => us"Ω",
    ),
)
UnitReport: consistent

Uncertainty budget.

uncertainty_budget(Vout)
Uncertainty budget (EA-4/02 §7.3) for y = (R2*Vin) / (R1 + R2)
  Vin:  u = σVin   c = R2 / (R1 + R2)   |c|u = abs(R2 / (R1 + R2))*σVin
  R1:  u = σR1   c = (-R2*Vin) / ((R1 + R2)^2)   |c|u = abs((-R2*Vin) / ((R1 + R2)^2))*σR1
  R2:  u = σR2   c = (R1*Vin) / (R1^2 + 2R1*R2 + R2^2)   |c|u = abs((R1*Vin) / (R1^2 + 2R1*R2 + R2^2))*σR2
  u_c = 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))

Numeric result. Vin = 5.000 V ± 0.010 V, R1 = 1.000 kΩ ± 1 Ω, R2 = 3.000 kΩ ± 1 Ω:

divider = Dict(
    Vin => 5.0us"V", σVin => 0.01us"V",
    R1 => 1_000.0us"Ω", σR1 => 1.0us"Ω",
    R2 => 3_000.0us"Ω", σR2 => 1.0us"Ω",
)
evaluate(Vout, divider)
(val = 3.75 V, err = 0.007564824023068878 V)

Verified across test/examples/test_voltage_divider.jl, test_budget_voltage_divider.jl, and test_substitute_voltage_divider.jl.


3. RC time constant (τ = R·C)

Setup. Resistance R ± σR in ohms, capacitance C ± σC in farads. Measurand: τ = R·C in seconds — Ω·F is a second, which the dimensional check confirms rather than takes on trust.

@variables R C σR σC

τ = (R ± σR) * (C ± σC)
check_units(τ, Dict(R => us"Ω", σR => us"Ω", C => us"F", σC => us"F"))
UnitReport: consistent

Numeric result. R = 1.000 kΩ ± 10 Ω, C = 1.000 µF ± 10 nF:

rc = Dict(R => 1_000.0us"Ω", σR => 10.0us"Ω", C => 1e-6us"F", σC => 1e-8us"F")
evaluate(τ, rc)
(val = 0.001 F Ω, err = 1.4142135623730951e-5 F Ω)

The two contributions are equal here — 10 ms of relative error on each input — so u(τ) is √2 times either one.


4. Dissipated power — two measurement models, two budgets

Setup. Measurand: dissipated power in watts, from either P = V·I or P = V²/R.

P = (V ± σV) * (I ± σI)
check_units(P, Dict(V => us"V", σV => us"V", I => us"A", σI => us"A"))
UnitReport: consistent
@variables R2ₚ σR2ₚ
P_alt = propagate((v, r) -> v^2 / r, [V ± σV, R2ₚ ± σR2ₚ])
check_units(
    P_alt,
    Dict(V => us"V", σV => us"V", R2ₚ => us"Ω", σR2ₚ => us"Ω"),
)
UnitReport: consistent

Both are watts, and both are correct — yet they give different uncertainties, because they descend from different measurements. The budget depends on which quantities were measured, not on the physical quantity itself. That is JCGM GUM-6:2020's subject, and it is upstream of everything this package does.

power = Dict(V => 5.0us"V", σV => 0.01us"V", I => 0.5us"A", σI => 0.001us"A")
evaluate(P, power)
(val = 2.5 A V, err = 0.007071067811865475 A V)

5. RLC resonance (f₀ = 1/(2π√(LC)))

Setup. Inductance L ± σL in henries, capacitance C ± σC in farads. Measurand: resonant frequency in hertz.

@variables L σL

f₀ = propagate((l, c) -> 1 / (2π * sqrt(l * c)), [L ± σL, C ± σC])
check_units(f₀, Dict(L => us"H", σL => us"H", C => us"F", σC => us"F"))
UnitReport: consistent

Propagation. The sensitivity coefficients are ∂f₀/∂L = −f₀/(2L) and ∂f₀/∂C = −f₀/(2C), so the relative uncertainty is half the quadrature sum of the relative input uncertainties — a square root halves relative errors, which is why resonant methods are forgiving.

Numeric result. L = 10.00 mH ± 50 µH, C = 1.000 µF ± 5 nF:

rlc = Dict(L => 10e-3us"H", σL => 50e-6us"H", C => 1e-6us"F", σC => 5e-9us"F")
evaluate(f₀, rlc)
(val = 1591.5494309189535 s⁻¹, err = 5.626976975981912 s⁻¹)

Verified in test/examples/test_rlc_resonance.jl.


6. RC-charge ODE (du/dt = (Vin − u) / (R·C))

Setup. A first-order dynamic measurement — the standard RC charge transient. Parameters R ± σR, C ± σC, Vin ± σVin are treated as constants with uncertainty. This example requires the ModelingToolkit.jl package extension.

using ModelingToolkit
using ModelingToolkit: t_nounits as t, D_nounits as D, System
using SymbolicUncertainties
using SymbolicUncertainties: ±

@variables u(t)
@parameters R C Vin
eqs = [D(u) ~ (Vin - u) / (R * C)]
@named rc_sys = System(eqs, t)

R_m   = R   ± 0.01R
C_m   = C   ± 0.01C
Vin_m = Vin ± 0.001Vin

Symbolic snapshot via propagate_ode:

result = propagate_ode(rc_sys, [R_m, C_m, Vin_m])
# result[1].val = u(t)
# result[1].err = sqrt((∂u/∂R)²·σR² + (∂u/∂C)²·σC² +
#                      (∂u/∂Vin)²·σVin²)

Augmented integration via uncertainty_ode:

using OrdinaryDiffEq

augmented = uncertainty_ode(rc_sys, [R_m, C_m, Vin_m])
compiled  = mtkcompile(augmented)

u0 = Dict(u => 0.0,
          (s => 0.0 for s in setdiff(unknowns(augmented),
                                     unknowns(rc_sys)))...)
p  = Dict(R => 1.0, C => 1e-3, Vin => 5.0)
prob = ODEProblem(compiled, merge(u0, p), (0.0, 50e-3))
sol  = solve(prob, Tsit5(); abstol = 1e-10, reltol = 1e-10)

# sol[u, end] ≈ 5.0  (converged to Vin)
# sol[∂u_∂Vin(t), end] ≈ 1.0

Verified in test/ext_modelingtoolkit/test_rc_charge_endpoint.jl. For a full walkthrough of propagate_ode and uncertainty_ode, see ODE Integration.


Keeping these examples honest

Examples 1 – 5 are executed at build time, so they cannot drift: if a Symbolics.jl release changes a user-visible output, the page changes with it and a wrong claim in the surrounding prose shows up as a mismatch with the printed result.

Example 6 is the exception, and its code block is transcribed. It is covered by test/ext_modelingtoolkit/, which is authoritative; if that test needs updating for output drift, mirror the change here.