Examples

This page shows small, complete examples for common scuq workflows.

Quantities and Units

Quantity combines a numerical value with a physical unit. Arithmetic keeps track of the resulting unit.

from scuq.quantities import Quantity
from scuq.si import METER, SECOND, VOLT, AMPERE

voltage = Quantity(VOLT, 12.0)
current = Quantity(AMPERE, 0.25)
resistance = voltage / current

distance = Quantity(METER, 10.0)
time = Quantity(SECOND, 2.0)
speed = distance / time

print(resistance)  # 48.0 V*A^(-1)
print(speed)       # 5.0 m*s^(-1)

Custom Units and Strict Mode

Strict mode is enabled by default. In strict mode, compatible but different units are not converted implicitly for operations such as addition and explicit value access. Disable strict mode when automatic conversion is desired.

from scuq.quantities import Quantity, set_strict
from scuq.si import VOLT
from scuq.units import AlternateUnit

mV = AlternateUnit("mV", VOLT / 1000)

set_strict(False)
signal = Quantity(VOLT, 2.0) + Quantity(mV, 500.0)

print(signal)                 # 2.5 V
print(signal.get_value(mV))   # 2500.0

The result of an addition or subtraction is expressed in the unit of the left operand.

Uncertainty Propagation

Uncertain input values are modeled first. They can then be wrapped in Quantity objects. This keeps uncertainty propagation separate from unit handling while still preserving units in the final result.

from scuq.quantities import Quantity
from scuq.si import METER
from scuq.ucomponents import Context, UncertainInput

length = Quantity(METER, UncertainInput(2.0, 0.02))
width = Quantity(METER, UncertainInput(3.0, 0.03))

area = length * width

context = Context()
area_uncertainty = context.uncertainty(area)

print(area)              # 6.0 +/- 0.08485281374238571 [NC] m^(2)
print(area_uncertainty)  # 0.08485281374238571 m^(2)

The uncertainty result has the matching unit of the evaluated model. In this example, the uncertainty of the area is expressed in square meters.

The [NC] marker in the string representation means “no context”. It is shown when an uncertain expression depends on more than one input, but the object itself has no assigned Context for printing. In that case, __str__ evaluates the expression with a temporary default context and assumes no correlations. Explicit calls such as context.uncertainty(area) use the context supplied by the caller and therefore do not carry this marker.

Physical Constants

The curated scuq.constants module contains exact SI defining constants and selected measured CODATA constants as protected SCUQ quantities. It is independent of SciPy.

from scuq.constants import SPEED_OF_LIGHT, alpha, epsilon_0, mu_0, Z_0
from scuq.ucomponents import Context

context = Context()

assert context.value_uncertainty_unit(SPEED_OF_LIGHT)[1] == 0
print(context.value_uncertainty_unit(alpha))
print(context.value_uncertainty_unit(mu_0))
print(context.value_uncertainty_unit(epsilon_0))
print(context.value_uncertainty_unit(Z_0))

alpha is the single measured input in this group. The three electromagnetic constants are derived from it, so their uncertainty expressions share that same input rather than modeling independent measurements. Augmented assignment to a public constant raises TypeError; expressions such as 2 * SPEED_OF_LIGHT return an ordinary Quantity.

Correlated Real Inputs

Use a Context to define correlations between uncertain input values. The same context is then used to evaluate the model uncertainty.

from scuq.ucomponents import Context, UncertainInput

context = Context()

voltage = UncertainInput(5.0, 0.05)
current = UncertainInput(0.1, 0.002)

context.set_correlation(voltage, current, -0.4)

resistance = voltage / current
print(resistance.get_value())          # 50.0
print(context.uncertainty(resistance))  # 1.2845232578665127

Complex Measurement Example

scuq also supports complex uncertainty propagation. The following compact example computes a complex impedance from voltage, current, and phase.

import numpy

from scuq import cucomponents
from scuq.quantities import Quantity
from scuq.si import AMPERE, OHM, RADIAN, VOLT
from scuq.units import ONE

context = cucomponents.Context()

j = Quantity(ONE, context.gaussian(1j, 0.0, 0.0))
voltage = Quantity(VOLT, context.gaussian(4.9990, 0.003209, 0.0))
current = Quantity(AMPERE, context.gaussian(19.661e-3, 0.00947e-3, 0.0))
phase = Quantity(RADIAN, context.gaussian(1.04446, 0.0007521, 0.0))

impedance = voltage / current * numpy.exp(j * phase)

assert impedance.get_default_unit().is_compatible(OHM)
print(impedance)
# Value = (127.73216992810208+219.8465119126384j) ; Uncertainty =
#  [[0.03784785 0.00220209]
#  [0.00220209 0.04035856]] [NC] V*A^(-1)
print(context.uncertainty(impedance))
# [[0.03784785 0.00220209]
#  [0.00220209 0.04035856]] V^(2)*A^(-2)

This pattern is useful for measurement models where the numerical model is ordinary Python or NumPy code, while scuq carries units and uncertainty information through the calculation.

As with real uncertain components, [NC] in the printed complex impedance marks a string conversion without an assigned context. The explicit context.uncertainty(impedance) call above evaluates the uncertainty with the given context.