Source code for snaptol.compare

from collections.abc import Collection, Iterable, Mapping
from itertools import zip_longest
from typing import Any

import numpy as np

DEFAULT_RTOL = 1e-05
DEFAULT_ATOL = 1e-08


[docs] def compare_intelligent( # noqa: PLR0911, PLR0912 actual: Any, expected: Any, rtol: float = DEFAULT_RTOL, atol: float = DEFAULT_ATOL, equal_nan: bool = False, ) -> bool: """ Intelligently compare two values of any type for equality. Returns ``True`` if the values are considered equal, and ``False`` otherwise. This function handles various Python data types and performs appropriate comparisons: - ``Floats``: Uses numpy's ``isclose`` with relative and absolute tolerances - ``Integers``: Exact equality - ``Complex``: Exact equality - ``Strings``: Exact equality - ``Booleans``: Exact equality - ``Bytes``/``Bytearray``/``Memoryview``: Byte-wise equality - ``None``: Identity comparison - NumPy arrays: Uses numpy's allclose - ``Mapping`` (dict-like): Deep comparison of keys and values - ``Iterable``: Order-sensitive element-wise comparison - ``Collection``: Length and element-wise comparison - ``Set``: Order-insensitive comparison of string representations - Other types: Standard equality comparison Parameters ---------- actual The value to compare expected The expected value to compare against rtol : optional Relative tolerance for floating point comparisons, by default 1e-05 atol : optional Absolute tolerance for floating point comparisons, by default 1e-08 equal_nan : optional Whether to consider NaN values equal to each other, by default False """ if isinstance(actual, complex | float | int) and isinstance( expected, complex | float | int ): return np.isclose(actual, expected, rtol=rtol, atol=atol, equal_nan=equal_nan) if isinstance(actual, str) and isinstance(expected, str): return actual == expected if isinstance(actual, bool) and isinstance(expected, bool): return actual == expected if isinstance(actual, bytes | bytearray | memoryview) and isinstance( expected, bytes | bytearray | memoryview ): return bytes(actual) == bytes(expected) if actual is None and expected is None: return True if isinstance(actual, np.ndarray) and isinstance(expected, np.ndarray): return np.allclose(actual, expected, rtol=rtol, atol=atol, equal_nan=equal_nan) if isinstance(actual, Mapping) and isinstance(expected, Mapping): ak, ek = set(actual.keys()), set(expected.keys()) if ak != ek: return False for k in sorted(ak, key=str): if not compare_intelligent(actual[k], expected[k], rtol, atol, equal_nan): return False return True if isinstance(actual, Iterable) and isinstance(expected, Iterable): sentinel = object() for a, e in zip_longest(actual, expected, fillvalue=sentinel): if a is sentinel or e is sentinel: return False # Length mismatch. if not compare_intelligent(a, e, rtol, atol, equal_nan): return False return True if isinstance(actual, Collection) and isinstance(expected, Collection): if len(actual) != len(expected): return False for a, e in zip(actual, expected): if not compare_intelligent(a, e, rtol, atol, equal_nan): return False return True if isinstance(actual, set) and isinstance(expected, set): a = sorted(map(repr, actual)) b = sorted(map(repr, expected)) return a == b if type(actual) is type(expected): return actual == expected return False