Source code for biogeme.tools.serialize_numpy

"""
Tools to transform NaN in numpy arrays in order to be serialized

Michel Bierlaire
Mon May 19 2025, 11:57:38
"""

import numpy as np


[docs] def safe_serialize_array( array: np.ndarray, ) -> list[float | None] | list[list[float | None]]: """ Convert a NumPy array with potential NaN values into a nested or flat list with `None` in place of `np.nan`, making it safe for YAML or JSON serialization. :param array: A NumPy array that may contain np.nan values. :return: A list (1D or 2D) with None in place of np.nan, suitable for serialization. """ if not isinstance(array, np.ndarray): raise TypeError(f'Input must be a NumPy array, not {type(array)}.') if array.ndim == 1: return [None if np.isnan(val) else float(val) for val in array] elif array.ndim == 2: return [[None if np.isnan(val) else float(val) for val in row] for row in array] else: raise ValueError("Only 1D and 2D arrays are supported.")
[docs] def safe_deserialize_array( serialized: list[float | None] | list[list[float | None]], ) -> list[float] | list[list[float]]: """ Convert a flat or nested list with None values (as parsed from YAML or JSON) into a list with None replaced by float('nan'). :param serialized: A list (1D or 2D) containing float or None values. :return: A list (1D or 2D) with None replaced by float('nan'). """ if not isinstance(serialized, list): raise TypeError("Input must be a list.") if all(isinstance(val, (float, int, type(None))) for val in serialized): # 1D case return [float('nan') if val is None else float(val) for val in serialized] elif all( isinstance(row, list) and all(isinstance(val, (float, int, type(None))) for val in row) for row in serialized ): # 2D case return [ [float('nan') if val is None else float(val) for val in row] for row in serialized ] else: raise TypeError("Input must be a 1D or 2D list of numbers or None.")