Source code for metaheuristic_designer.utils

"""Utility functions, type aliases, and a JSON encoder used across the library."""

from typing import Optional
import json
import numbers
from enum import Enum
import numpy as np

null_aliases = {"null", "nothing", "identity", "passthrough"}

RNGLike = int | np.random.Generator

RealVector = np.ndarray[tuple[int], np.floating]
RealMatrix = np.ndarray[tuple[int, int], np.floating]
RealTensor = np.ndarray[tuple[int, ...], np.floating]

IntVector = np.ndarray[tuple[int], np.integer]
IntMatrix = np.ndarray[tuple[int, int], np.integer]
IntTensor = np.ndarray[tuple[int, ...], np.integer]

BinVector = np.ndarray[tuple[int], np.uint8 | np.bool]
BinMatrix = np.ndarray[tuple[int, int], np.uint8 | np.bool]
BinTensor = np.ndarray[tuple[int, ...], np.uint8 | np.bool]

ScalarLike = np.number | float | int
VectorLike = RealVector | IntVector | BinVector
MatrixLike = RealMatrix | IntMatrix | BinMatrix
TensorLike = RealTensor | IntTensor | BinTensor

MaskLike = IntTensor | BinTensor


[docs] class TerminationException(Exception): """ Custom exception to handle SIGTERM """
[docs] class NumpyEncoder(json.JSONEncoder): """JSON encoder that can serialize NumPy scalars, arrays, and Enums. Use this encoder with ``json.dumps`` or ``json.dump`` when your data structure contains NumPy integers, floats, or arrays, or when you need to serialize Enum values as their string names. """
[docs] def default(self, o): if isinstance(o, np.integer): return int(o) elif isinstance(o, np.floating): return float(o) elif isinstance(o, np.ndarray): return o.tolist() elif isinstance(o, Enum): return str(o) return json.JSONEncoder.default(self, o)
[docs] def check_random_state(seed: Optional[RNGLike]) -> np.random.Generator: """Turn seed into an np.random.Generator instance. Original implementation adapted from: https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/utils/validation.py BSD 3-Clause License, Copyright (c) 2007-2025 The scikit-learn developers. Parameters ---------- seed : None, int or instance of Generator If seed is None, return the Generator singleton used by np.random. If seed is an int, return a new Generator instance seeded with seed. If seed is already a Generator instance, return it. Otherwise raise ValueError. Returns ------- :class:`numpy:numpy.random.Generator` The random state object based on `seed` parameter. """ if seed is None or seed is np.random: return np.random.default_rng() if isinstance(seed, numbers.Integral): return np.random.default_rng(seed) if isinstance(seed, np.random.Generator): return seed raise ValueError("%r cannot be used to seed a numpy.random.Generator instance" % seed)
[docs] def per_individual(func): """Decorator that applies a function to each row of a 2-D array. The wrapped function receives a single row (a 1-D array) and any keyword arguments, and must return a 1-D array of the same length. The decorator loops over rows and stacks the results back into a 2-D array. Parameters ---------- func : callable A function ``(row, **kwargs) -> 1-D array``. Returns ------- callable A function that accepts a 2-D matrix and returns a 2-D array. """ def wrapper(matrix, **kwargs): return np.array([func(row, **kwargs) for row in matrix]) return wrapper
[docs] def per_individual_list(func): """Decorator that applies a function to each element of a list. The wrapped function receives a single element and any keyword arguments, and returns a transformed element. The decorator loops over the list and returns a new list of the results. Parameters ---------- func : callable A function ``(value, **kwargs) -> Any``. Returns ------- callable A function that accepts a list and returns a list. """ def wrapper(values, **kwargs): return [func(value, **kwargs) for value in values] return wrapper