Working with Formulae

This guide demonstrates how to use the dcclib.extraction.formulae module to extract and evaluate formulas from Digital Calibration Certificates (DCCs).

A DCC can embed measurement models as MathML. dcclib parses these into SymPy expressions, resolves their variables to the referenced quantity values, and evaluates them — optionally with unit-aware arithmetic and uncertainty propagation via dccQuantities.

Note

Evaluation automatically selects unit-aware arithmetic (via dccQuantities) when it is installed, including uncertainty propagation. Without it, plain numeric evaluation via mpmath is used. See the installation notes for the optional quantities extra.

1. Extracting formulas from a DCC

The FormulaExtractor reads all MathML formulas from a DCC and resolves their variables to the quantity values referenced via xref. Each formula is returned as a DCCFunction.

from dcclib.extraction.formulae import FormulaExtractor, format_quantity

extractor = FormulaExtractor.from_file(formula_xml_path)
functions = extractor.extract()

print(f"Extracted {len(functions)} formula(s).")
for i, func in enumerate(functions):
    print(f"\nFormula {i}: {func.name}")
    print(f"  Expression: {func.expression}")
    print(f"  Variables:  {', '.join(func.variables) or '(resolved via dccQuantities)'}")
    for key, val in func.variables.items():
        val_str, unit_str = format_quantity(val)
        print(f"    {key} = {val_str} {unit_str}")

Note

Variables extracted from a DCC are resolved to dccQuantities objects. When the optional quantities extra is not installed, func.variables is empty because the referenced quantities cannot be materialised — extraction of the expression itself still works.

2. Evaluating an extracted formula

A DCCFunction is evaluated with its evaluate() method. When the variables were resolved during extraction, it can be called without arguments to use those embedded values. evaluate() returns one result per evaluation step (variables holding lists are zipped, not combined cartesian-style).

from dcclib.extraction.formulae import FormulaExtractor, format_quantity

extractor = FormulaExtractor.from_file(formula_xml_path)
functions = extractor.extract()

for func in functions:
    if not func.variables:
        # Variables were not resolved (dccQuantities not installed); skip evaluation.
        continue
    print(f"Results for {func.name}:")
    for res in func.evaluate():
        inputs_str = ", ".join(f"{k}={format_quantity(v)[0]}" for k, v in res.inputs.items())
        val_str, unit_str = format_quantity(res.output)
        print(f"  f({inputs_str}) = {val_str} {unit_str}")

3. Creating a DCCFunction manually

You can also build a DCCFunction yourself from a name, a SymPy expression (or string), and a dictionary of variable values. Plain numeric values work without any optional dependencies:

from sympy import symbols

from dcclib.extraction.formulae import format_quantity, wrap_variable_value
from dcclib.extraction.formulae.formula import DCCFunction

x, y = symbols("x y")
expr = x * y + 1

func = DCCFunction(name="MyFunc", expression=expr, variables={"x": 2.0, "y": 3.0}, bound_variables={"x", "y"})

for res in func.evaluate():
    val_str, unit_str = format_quantity(res.output)
    print(f"Result: {val_str} {unit_str}")

# Override a variable, keeping the original variable's unit metadata (as the CLI does):
overrides = {"y": 10.0}
eval_vars = func.variables | {k: wrap_variable_value(v, func.variables.get(k)) for k, v in overrides.items()}

for res in func.evaluate(variables=eval_vars):
    val_str, unit_str = format_quantity(res.output)
    print(f"Overridden result: {val_str} {unit_str}")

Evaluating over a list of inputs

A variable holding a list is evaluated element-wise, producing one result per entry:

from sympy import symbols

from dcclib.extraction.formulae import format_quantity
from dcclib.extraction.formulae.formula import DCCFunction

t = symbols("t")
func = DCCFunction(name="Ramp", expression=2 * t + 1, variables={"t": [0.0, 1.0, 2.0]}, bound_variables={"t"})

for res in func.evaluate():
    val_str, _ = format_quantity(res.output)
    print(f"t={res.inputs['t']} -> {val_str}")

4. Unit-aware quantities

When the optional quantities extra is installed, variables can be DccQuantityType objects carrying a value, an uncertainty and a unit. Evaluation then propagates units and uncertainties automatically. The following snippet only runs the unit-aware path when dccQuantities is available, so it is safe to execute in any environment:

from sympy import symbols

from dcclib.extraction.formulae import format_quantity
from dcclib.extraction.formulae.formula import DCCFunction
from dcclib.utils.quantities_check import HAS_DCC_QUANTITIES

if HAS_DCC_QUANTITIES:
    from dcc_quantities.dcc_quantity_type import DccQuantityType

    x, y = symbols("x y")
    qx = DccQuantityType.from_single_quantity_value(value=2.0, uncertainty=0.1, name="x", unit="\\metre")
    qy = DccQuantityType.from_single_quantity_value(value=3.0, uncertainty=0.2, name="y", unit="\\metre")

    func = DCCFunction(name="Area", expression=x * y, variables={"x": qx, "y": qy}, bound_variables={"x", "y"})
    for res in func.evaluate():
        val_str, unit_str = format_quantity(res.output)
        # e.g. "6.0 ± 0.36 \metre\tothe{2}"
        print(f"Result: {val_str} {unit_str}")
else:
    print("dccQuantities is not installed; skipping unit-aware evaluation.")

See also