import logging
import numpy as np
logger_ = logging.getLogger(__name__)
# TODO: refactor or replace redundant functions here
[docs]
def contains_same_elements(obj1, obj2):
"""
Check if two objects contain the same elements.
Parameters
----------
obj1 : array_like
First object to compare.
obj2 : array_like
Second object to compare.
Returns
-------
bool
True if both objects contain the same elements, False otherwise.
"""
return set(obj1) == set(obj2)
[docs]
def is_numeric_scalar(item):
"""
Check if an item is a numeric scalar.
Parameters
----------
item : object
Item to check.
Returns
-------
bool
True if the item is a numeric scalar, False otherwise.
"""
return isinstance(item, (int, float, complex, np.number))
[docs]
def is_array_like(obj):
"""
Check if an object is array-like.
Parameters
----------
obj : object
Object to check.
Returns
-------
bool
True if the object is array-like, False otherwise.
"""
return isinstance(obj, (list, np.ndarray))
[docs]
def is_list_math_equal(list1, list2, tol=1e-10):
"""
Recursively compares two nested lists or arrays to check if they are equal.
Parameters
----------
list1 : array_like
First list or array to compare.
list2 : array_like
Second list or array to compare.
tol : float, optional
Tolerance for floating-point comparison. Default is 1e-10.
Returns
-------
bool
True if the lists or arrays are equal within the specified tolerance, False otherwise.
Raises
------
AssertionError
If the lists or arrays are not equal.
"""
def convert_to_array_like(obj):
"""
Convert an object to an array-like object.
Parameters
----------
obj : object
Object to convert.
Returns
-------
np.ndarray
Converted array-like object.
Raises
------
ValueError
If the object type is unsupported.
"""
if isinstance(obj, np.ndarray):
return obj
elif isinstance(obj, list):
return np.array(obj, dtype=object) # Use dtype=object to handle inhomogeneous arrays
else:
raise ValueError(f"Unsupported type: {type(obj)}")
def is_element_equal(elem1, elem2, tol):
"""
Check if two elements are equal within a specified tolerance.
Parameters
----------
elem1 : object
First element to compare.
elem2 : object
Second element to compare.
tol : float
Tolerance for floating-point comparison.
Returns
-------
bool
True if the elements are equal within the specified tolerance, False otherwise.
"""
if is_numeric_scalar(elem1) and is_numeric_scalar(elem2):
if not np.isclose(elem1, elem2, atol=tol, rtol=0):
logger_.warning(f"Scalars are not equal: {elem1} != {elem2}")
return False
elif np.isscalar(elem1) and np.isscalar(elem2):
if not elem1 == elem2:
logger_.warning(f"Scalars are not equal: {elem1} != {elem2} with types {type(elem1)}, {type(elem2)}")
return False
elif is_array_like(elem1) and is_array_like(elem2):
return is_list_math_equal(elem1, elem2, tol)
else:
logger_.warning(f"Types are not the same: {type(elem1)} != {type(elem2)}")
return False
if not is_array_like(list1) or not is_array_like(list2):
logger_.warning(f"Both inputs must be lists or NumPy arrays: {type(list1)} != {type(list2)}")
return False
if isinstance(list1, np.ndarray) and isinstance(list2, np.ndarray):
if list1.shape != list2.shape:
# try again as lists
logger_.warning(f"Arrays have different shapes: {list1.shape} != {list2.shape}")
return is_list_math_equal(list1.tolist(), list2.tolist())
for sub_elem1, sub_elem2 in zip(list1, list2):
if is_element_equal(sub_elem1, sub_elem2, tol) == False:
return False
elif isinstance(list1, list) and isinstance(list2, list):
if len(list1) != len(list2):
logger_.warning(f"Lists have different lengths: {len(list1)} != {len(list2)}")
return False
for sub_elem1, sub_elem2 in zip(list1, list2):
if is_element_equal(sub_elem1, sub_elem2, tol) == False:
return False
else:
# Convert both to NumPy arrays for comparison
array1 = convert_to_array_like(list1)
array2 = convert_to_array_like(list2)
return is_list_math_equal(array1, array2, tol)
return True
# TODO: replace np.number checks with more flexible isnumeric ones
[docs]
def is_number_or_list_of_numbers_nonnegative(obj):
"""
Check if an object is a non-negative number or a list of non-negative numbers.
Parameters
----------
obj : object
Object to check.
Returns
-------
bool
True if the object is a non-negative number or a list of non-negative numbers, False otherwise.
"""
return hasattr(obj, '__iter__') == False and np.issubdtype(type(obj), np.number) and obj >= 0 or \
hasattr(obj, '__iter__') and np.all([np.issubdtype(type(k), np.number) and k >= 0 for k in np.array(obj).flatten()])
[docs]
def is_number_or_nparray_of_numbers_nonnegative(obj):
"""
Check if an object is a non-negative number or a NumPy array of non-negative numbers.
Parameters
----------
obj : object
Object to check.
Returns
-------
bool
True if the object is a non-negative number or a NumPy array of non-negative numbers, False otherwise.
"""
return np.issubdtype(type(obj), np.number) and obj >= 0 \
or isinstance(obj, np.ndarray) and np.all([np.issubdtype(type(k), np.number) and k >= 0 for k in obj.flatten()])
[docs]
def is_nparray_of_numbers(obj):
"""
Check if an object is a NumPy array of numbers.
Parameters
----------
obj : object
Object to check.
Returns
-------
bool
True if the object is a NumPy array of numbers, False otherwise.
"""
return isinstance(obj, np.ndarray) and np.all([np.issubdtype(type(k), np.number) for k in obj.flatten()])
[docs]
def is_number_or_nparray_of_numbers(obj):
"""
Check if an object is a number or a NumPy array of numbers.
Parameters
----------
obj : object
Object to check.
Returns
-------
bool
True if the object is a number or a NumPy array of numbers, False otherwise.
"""
return np.issubdtype(type(obj), np.number) or is_nparray_of_numbers(obj)