masque/masque/builder/_tolerances.py

48 lines
1.5 KiB
Python

"""Shared numeric tolerances for builder geometry and parameter comparisons."""
from math import isclose, remainder, tau
from typing import Any
import numpy
GEOMETRY_RTOL = 1e-5
GEOMETRY_ATOL = 1e-8
DOMAIN_RTOL = 1e-9
DOMAIN_ATOL = 1e-12
MANHATTAN_ANGLE_RTOL = 1e-9
MANHATTAN_ANGLE_ATOL = 1e-9
def scalar_close(a: float, b: float) -> bool:
"""Match the solver's existing scalar-comparison behavior."""
return isclose(float(a), float(b), rel_tol=GEOMETRY_RTOL, abs_tol=GEOMETRY_ATOL)
def array_close(a: Any, b: Any) -> bool:
"""Match NumPy's historical builder geometry-comparison behavior."""
return bool(numpy.allclose(a, b, rtol=GEOMETRY_RTOL, atol=GEOMETRY_ATOL))
def angles_equal(a: float, b: float) -> bool:
"""Return true when two rotations are equal modulo one full turn."""
delta = remainder(float(a) - float(b), tau)
return isclose(delta, 0.0, rel_tol=GEOMETRY_RTOL, abs_tol=GEOMETRY_ATOL)
def manhattan_axis(rotation: float) -> int | None:
"""Return 0 for horizontal, 1 for vertical, or None for a non-cardinal angle."""
angle = float(rotation) % (numpy.pi / 2)
if isclose(
angle,
0.0,
rel_tol=MANHATTAN_ANGLE_RTOL,
abs_tol=MANHATTAN_ANGLE_ATOL,
) or isclose(
angle,
numpy.pi / 2,
rel_tol=MANHATTAN_ANGLE_RTOL,
abs_tol=MANHATTAN_ANGLE_ATOL,
):
quarter_turn = round(float(rotation) / (numpy.pi / 2))
return quarter_turn % 2
return None