Skip to content

Strain & rosette math

strain_from_volts / bridge_value_from_volts turn a measured bridge voltage into engineering units via the SDK conversions; the rosette helpers in dtollib.utils do pure application-side rosette reduction. See the TEDS guide for context.

Volts → strain / bridge value

dtollib.strain

Strain / bridge volts→engineering conversion helpers — §8.B6.

The strain/bridge read path is two stages: a normal voltage acquisition (poll / record) yields the bridge output voltage, then these helpers turn that voltage into engineering units (strain ε, or the bridge transducer's rated quantity) via the SDK's olDaVoltsToStrain / olDaVoltsToBridgeBasedSensor.

The helpers read the gage/bridge parameters straight off the channel spec, so a caller only supplies the measured unstrained/strained voltages. The conversions are pure SDK math (no HDASS, no capability gate) — the capability gate lives upstream at configure time (:func:dtollib.tasks.builder._require_io_type_supported).

Pure application-side rosette transforms (rectangular / delta) live in :mod:dtollib.utils for callers who prefer not to round-trip the SDK.

bridge_value_from_volts

bridge_value_from_volts(
    backend,
    spec,
    *,
    v_unstrained,
    v_strained,
    v_excitation=None,
    temperature_coefficient=0.0,
)

Convert a measured bridge-transducer voltage to its engineering value.

Reads the bridge parameters (nominal resistance, lead resistance, rated sensitivity) from spec; v_excitation defaults to the spec's configured excitation voltage.

Source code in src/dtollib/strain.py
def bridge_value_from_volts(
    backend: DtolBackend,
    spec: BridgeInput,
    *,
    v_unstrained: float,
    v_strained: float,
    v_excitation: float | None = None,
    temperature_coefficient: float = 0.0,
) -> float:
    """Convert a measured bridge-transducer voltage to its engineering value.

    Reads the bridge parameters (nominal resistance, lead resistance,
    rated sensitivity) from ``spec``; ``v_excitation`` defaults to the
    spec's configured excitation voltage.
    """
    return backend.volts_to_bridge_based_sensor(
        v_unstrained,
        v_strained,
        spec.excitation_voltage if v_excitation is None else v_excitation,
        temperature_coefficient,
        spec.nominal_resistance_ohms,
        spec.lead_resistance_ohms,
        spec.sensitivity_mv_per_v,
        0.0,
    )

strain_from_volts

strain_from_volts(
    backend,
    spec,
    *,
    v_unstrained,
    v_strained,
    v_excitation=None,
)

Convert a measured strain-bridge voltage to strain (ε).

Reads the gage parameters (configuration, gage factor, resistance, lead resistance, Poisson ratio) from spec; only the two measured voltages are supplied per reading. v_excitation defaults to the spec's configured excitation voltage.

Source code in src/dtollib/strain.py
def strain_from_volts(
    backend: DtolBackend,
    spec: StrainInput,
    *,
    v_unstrained: float,
    v_strained: float,
    v_excitation: float | None = None,
) -> float:
    """Convert a measured strain-bridge voltage to strain (ε).

    Reads the gage parameters (configuration, gage factor, resistance,
    lead resistance, Poisson ratio) from ``spec``; only the two measured
    voltages are supplied per reading.  ``v_excitation`` defaults to the
    spec's configured excitation voltage.
    """
    return backend.volts_to_strain(
        _STRAIN_CONFIG_TO_OL[spec.configuration],
        v_unstrained,
        v_strained,
        spec.excitation_voltage if v_excitation is None else v_excitation,
        spec.gage_factor,
        spec.gage_resistance_ohms,
        spec.lead_resistance_ohms,
        spec.poisson_ratio,
        0.0,
    )

Rosette and thermocouple helpers

dtollib.utils

Pure-Python helpers: NIST ITS-90 thermocouple math + rosette transforms.

These helpers do not depend on the SDK and are testable on any platform without hardware. They exist for two use cases:

  1. Client-side validation in :class:~dtollib.tasks.ThermocoupleInput __post_init__ — reject TC ranges outside the operating envelope before the SDK rejects them, with a precise error message.
  2. Application-side TC linearisation (deferred-by-default). When OLSSC_SUP_LINEARIZE_TC is False on a subsystem, the wrapper applies these polynomials itself.

Coefficient source: NIST Monograph 175 (Burns et al., 1993), the ITS-90 reference functions for letter-designated thermocouples.

Status by type:

  • Type K — full forward + inverse polynomial.
  • Type J — full forward + inverse polynomial.
  • Other types — operating range only; the forward/inverse polynomials are not implemented.

Calling an unsupported type at the polynomial layer raises :class:~dtollib.errors.DtolValidationError naming the supported set (J, K) and the NIST reference needed to add others.

compute_delta_rosette

compute_delta_rosette(eps_0, eps_60, eps_120)

Compute principal strains from a 0/60/120 delta rosette.

Parameters:

Name Type Description Default
eps_0 float

Strain reading on the 0° gauge.

required
eps_60 float

Strain reading on the 60° gauge.

required
eps_120 float

Strain reading on the 120° gauge.

required

Returns:

Type Description
float

(eps_max, eps_min, theta_p_rad) — principal strains and

float

the angle of the principal axis from the 0° gauge.

Source code in src/dtollib/utils.py
def compute_delta_rosette(
    eps_0: float,
    eps_60: float,
    eps_120: float,
) -> tuple[float, float, float]:
    """Compute principal strains from a 0/60/120 delta rosette.

    Args:
        eps_0: Strain reading on the 0° gauge.
        eps_60: Strain reading on the 60° gauge.
        eps_120: Strain reading on the 120° gauge.

    Returns:
        ``(eps_max, eps_min, theta_p_rad)`` — principal strains and
        the angle of the principal axis from the 0° gauge.
    """
    import math  # noqa: PLC0415

    mean = (eps_0 + eps_60 + eps_120) / 3.0
    a = eps_0 - mean
    b = (eps_60 - eps_120) / math.sqrt(3.0)
    radius = math.hypot(a, b)
    eps_max = mean + radius
    eps_min = mean - radius
    theta_p = 0.5 * math.atan2(b, a) if a != 0.0 or b != 0.0 else 0.0
    return (eps_max, eps_min, theta_p)

compute_rectangular_rosette

compute_rectangular_rosette(eps_0, eps_45, eps_90)

Compute principal strains from a 0/45/90 rectangular rosette.

Parameters:

Name Type Description Default
eps_0 float

Strain reading on the 0° gauge.

required
eps_45 float

Strain reading on the 45° gauge.

required
eps_90 float

Strain reading on the 90° gauge.

required

Returns:

Type Description
float

(eps_max, eps_min, theta_p_rad) — principal strains and

float

the angle of the principal axis from the 0° gauge, in

float

radians.

Source code in src/dtollib/utils.py
def compute_rectangular_rosette(
    eps_0: float,
    eps_45: float,
    eps_90: float,
) -> tuple[float, float, float]:
    """Compute principal strains from a 0/45/90 rectangular rosette.

    Args:
        eps_0: Strain reading on the 0° gauge.
        eps_45: Strain reading on the 45° gauge.
        eps_90: Strain reading on the 90° gauge.

    Returns:
        ``(eps_max, eps_min, theta_p_rad)`` — principal strains and
        the angle of the principal axis from the 0° gauge, in
        radians.
    """
    import math  # noqa: PLC0415

    mean = (eps_0 + eps_90) / 2.0
    diff = (eps_0 - eps_90) / 2.0
    shear_half = (eps_0 + eps_90) / 2.0 - eps_45
    radius = math.hypot(diff, shear_half)
    eps_max = mean + radius
    eps_min = mean - radius
    theta_p = 0.5 * math.atan2(-2.0 * shear_half, eps_0 - eps_90) if eps_0 != eps_90 else 0.0
    return (eps_max, eps_min, theta_p)

convert_temperature_to_volts

convert_temperature_to_volts(tc_type, temperature_c)

NIST ITS-90 forward polynomial: temperature → thermo-emf.

Parameters:

Name Type Description Default
tc_type str

Single-letter TC type ("K", "J", ...).

required
temperature_c float

Temperature in degrees Celsius.

required

Returns:

Type Description
float

Thermo-emf in volts (not millivolts — the SDK's

float

olDaSetTriggerThresholdLevel and friends use volts).

Raises:

Type Description
DtolValidationError

temperature_c outside the type's NIST-documented operating range, or tc_type is not yet implemented.

Source code in src/dtollib/utils.py
def convert_temperature_to_volts(tc_type: str, temperature_c: float) -> float:
    """NIST ITS-90 forward polynomial: temperature → thermo-emf.

    Args:
        tc_type: Single-letter TC type (``"K"``, ``"J"``, ...).
        temperature_c: Temperature in degrees Celsius.

    Returns:
        Thermo-emf in **volts** (not millivolts — the SDK's
        ``olDaSetTriggerThresholdLevel`` and friends use volts).

    Raises:
        DtolValidationError: ``temperature_c`` outside the type's
            NIST-documented operating range, or ``tc_type`` is not
            yet implemented.
    """
    key = tc_type.upper()
    lo, hi = get_thermocouple_range(key)
    if not (lo <= temperature_c <= hi):
        raise DtolValidationError(
            f"temperature {temperature_c} °C outside Type {key} range [{lo}, {hi}]",
            context=ErrorContext(operation="convert_temperature_to_volts"),
        )

    mv = _temperature_to_mv(key, temperature_c)
    return mv * 1e-3

convert_volts_to_temperature

convert_volts_to_temperature(
    tc_type, volts, *, cjc_temperature_c=0.0
)

NIST ITS-90 inverse polynomial + CJC: thermo-emf → temperature.

The thermocouple emf is measured relative to the cold junction; to recover the absolute hot-junction temperature we add the emf the cold junction would emit at its measured temperature, then invert the polynomial.

Parameters:

Name Type Description Default
tc_type str

Single-letter TC type.

required
volts float

Measured thermo-emf in volts.

required
cjc_temperature_c float

Cold-junction temperature in degrees Celsius. Defaults to 0.0 for the (uncommon) ice-bath case.

0.0

Returns:

Type Description
float

Hot-junction temperature in degrees Celsius.

Raises:

Type Description
DtolValidationError

tc_type is unsupported (only J and K have polynomials) or a temperature/voltage is outside range.

Source code in src/dtollib/utils.py
def convert_volts_to_temperature(
    tc_type: str,
    volts: float,
    *,
    cjc_temperature_c: float = 0.0,
) -> float:
    """NIST ITS-90 inverse polynomial + CJC: thermo-emf → temperature.

    The thermocouple emf is measured **relative to** the cold
    junction; to recover the absolute hot-junction temperature we add
    the emf the cold junction would emit at its measured temperature,
    then invert the polynomial.

    Args:
        tc_type: Single-letter TC type.
        volts: Measured thermo-emf in **volts**.
        cjc_temperature_c: Cold-junction temperature in degrees
            Celsius.  Defaults to ``0.0`` for the (uncommon) ice-bath
            case.

    Returns:
        Hot-junction temperature in degrees Celsius.

    Raises:
        DtolValidationError: ``tc_type`` is unsupported (only J and K
            have polynomials) or a temperature/voltage is outside range.
    """
    key = tc_type.upper()
    lo, hi = get_thermocouple_range(key)
    if not (lo <= cjc_temperature_c <= hi):
        raise DtolValidationError(
            f"CJC temperature {cjc_temperature_c} °C outside Type {key} range [{lo}, {hi}]",
            context=ErrorContext(operation="convert_volts_to_temperature"),
        )

    measured_mv = volts * 1e3
    cjc_mv = _temperature_to_mv(key, cjc_temperature_c)
    total_mv = measured_mv + cjc_mv
    return _mv_to_temperature(key, total_mv)

get_thermocouple_range

get_thermocouple_range(tc_type)

Operating temperature range for a thermocouple type.

Parameters:

Name Type Description Default
tc_type str

Single-letter type designation ("J", "K", "T", "E", "R", "S", "B", "N"). Case-insensitive.

required

Returns:

Type Description
tuple[float, float]

(min_degc, max_degc) per NIST Monograph 175.

Raises:

Type Description
DtolValidationError

tc_type is not one of the supported letter designations.

Source code in src/dtollib/utils.py
def get_thermocouple_range(tc_type: str) -> tuple[float, float]:
    """Operating temperature range for a thermocouple type.

    Args:
        tc_type: Single-letter type designation (``"J"``, ``"K"``,
            ``"T"``, ``"E"``, ``"R"``, ``"S"``, ``"B"``, ``"N"``).
            Case-insensitive.

    Returns:
        ``(min_degc, max_degc)`` per NIST Monograph 175.

    Raises:
        DtolValidationError: ``tc_type`` is not one of the supported
            letter designations.
    """
    key = tc_type.upper()
    rng = _TC_RANGES.get(key)
    if rng is None:
        raise DtolValidationError(
            f"unknown thermocouple type {tc_type!r}; expected one of {sorted(_TC_RANGES.keys())}",
            context=ErrorContext(operation="get_thermocouple_range"),
        )
    return rng