Skip to content

compare_numeric

elegua.compare_numeric

L4 numeric comparison — sample-point agreement within tolerance.

Factory function that creates a LayerFn closure capturing tolerance and minimum sample count configuration. Register the returned function as Layer 4 in a ComparisonPipeline.

make_numeric_comparator(tol=1e-06, min_samples=1)

Create an L4 numeric comparison function.

The returned function compares numeric_samples from both tokens: matching sample points (by variable values) must agree within tol. Returns MATH_MISMATCH if fewer than min_samples common points exist.

Parameters:

Name Type Description Default
tol float

Absolute tolerance for value comparison.

1e-06
min_samples int

Minimum number of common sample points required.

1
Source code in src/elegua/compare_numeric.py
def make_numeric_comparator(tol: float = 1e-6, min_samples: int = 1) -> LayerFn:
    """Create an L4 numeric comparison function.

    The returned function compares ``numeric_samples`` from both tokens:
    matching sample points (by variable values) must agree within ``tol``.
    Returns MATH_MISMATCH if fewer than ``min_samples`` common points exist.

    Parameters:
        tol: Absolute tolerance for value comparison.
        min_samples: Minimum number of common sample points required.
    """

    def compare(token_a: ValidationToken, token_b: ValidationToken) -> TaskStatus:
        samples_a = _extract_samples(token_a.result)
        samples_b = _extract_samples(token_b.result)

        # Index samples by their variable-value key
        # Finite (numeric) samples
        index_a = {
            _sample_key(s): s["value"]
            for s in samples_a
            if "value" in s and isinstance(s["value"], (int, float))
        }
        index_b = {
            _sample_key(s): s["value"]
            for s in samples_b
            if "value" in s and isinstance(s["value"], (int, float))
        }

        # Non-finite sample keys (flagged as non_finite)
        non_finite_a = {_sample_key(s) for s in samples_a if s.get("non_finite")}
        non_finite_b = {_sample_key(s) for s in samples_b if s.get("non_finite")}

        # Keys present on both sides (either finite or non-finite)
        finite_keys_a = set(index_a)
        finite_keys_b = set(index_b)
        all_keys_a = finite_keys_a | non_finite_a
        all_keys_b = finite_keys_b | non_finite_b
        common_keys = all_keys_a & all_keys_b

        if len(common_keys) < min_samples:
            return TaskStatus.MATH_MISMATCH

        # Check all common points agree
        for key in common_keys:
            in_a_finite = key in index_a
            in_b_finite = key in index_b
            in_a_nf = key in non_finite_a
            in_b_nf = key in non_finite_b

            if in_a_finite and in_b_finite:
                # Both finite — compare within tolerance
                if abs(index_a[key] - index_b[key]) > tol:
                    return TaskStatus.MATH_MISMATCH
            elif in_a_nf and in_b_nf:
                # Both non-finite — agreement
                continue
            elif (in_a_finite or in_a_nf) and (in_b_finite or in_b_nf):
                # One finite, one non-finite at same point — mismatch
                return TaskStatus.MATH_MISMATCH
            else:
                # Point exists on one side only — insufficient to disagree
                continue

        return TaskStatus.OK

    return compare