Source code for porcupy.utils.helpers

import numpy as np

[docs]def initialize_population(pop_size, dim, lb, ub): """ Initialize population within the specified bounds. Generates random positions for the porcupine population using uniform distribution. Supports both single bounds (same for all dimensions) and per-dimension bounds. Parameters ---------- pop_size : int Number of search agents (porcupines). dim : int Number of dimensions in the search space. lb : ndarray Lower bounds for each dimension (or single value for all dimensions). ub : ndarray Upper bounds for each dimension (or single value for all dimensions). Returns ------- positions : ndarray Initialized population of shape (pop_size, dim). Raises ------ ValueError If `pop_size` or `dim` is non-positive, or `lb` and `ub` are incompatible. """ if pop_size <= 0 or dim <= 0: raise ValueError("Population size and dimension must be positive") # Convert bounds to arrays, ensuring they're at least 1D lb = np.atleast_1d(np.array(lb, dtype=float)) ub = np.atleast_1d(np.array(ub, dtype=float)) # Check if bounds are compatible if len(lb) != len(ub) or (len(lb) > 1 and len(lb) != dim): raise ValueError("Bounds must match dimension or be single values") # Check if lower bounds are less than upper bounds if np.any(lb > ub): raise ValueError("Lower bounds must be less than or equal to upper bounds") if len(lb) == 1: # Single bound for all dimensions return np.random.uniform(lb[0], ub[0], (pop_size, dim)) else: # Per-dimension bounds positions = np.zeros((pop_size, dim)) for i in range(dim): positions[:, i] = np.random.uniform(lb[i], ub[i], pop_size) return positions
[docs]def clip_to_bounds(positions, lb, ub): """ Clip positions to stay within the specified bounds. Ensures all positions remain within the search space by clipping values that exceed the lower or upper bounds. Parameters ---------- positions : ndarray Population positions of shape (pop_size, dim). lb : ndarray Lower bounds for each dimension (or single value). ub : ndarray Upper bounds for each dimension (or single value). Returns ------- clipped_positions : ndarray Positions clipped to stay within bounds. """ # Convert bounds to arrays, ensuring they're at least 1D lb = np.atleast_1d(np.array(lb, dtype=float)) ub = np.atleast_1d(np.array(ub, dtype=float)) # Check if bounds are compatible dim = positions.shape[1] if len(lb) != len(ub) or (len(lb) > 1 and len(lb) != dim): raise ValueError("Bounds must match dimension or be single values") # Perform clipping if len(lb) == 1: return np.clip(positions, lb[0], ub[0]) else: return np.clip(positions, lb, ub)