Source code for metaheuristic_designer.initializers.latin_hypercube_initializer
"""Initializer that implements Latin Hypercube Sampling as an initialization technique."""
from __future__ import annotations
from typing import Optional
import numpy as np
from ..objective_function import ObjectiveFunc
from ..population import Population
from .uniform_initializer import UniformInitializer
from ..initializer import Initializer
[docs]
class LatinHypercubeInitializer(Initializer):
"""
Initializer that generates individuals using the Latin Hypercube Sampling (LHS)
technique, in which values are drawn from a stratified uniform distribution that
more efficiently samples the search space than naive uniform sampling.
Parameters
----------
dimension : int
Length of the genotype vector.
lower_bound : float or array
Lower bound(s) of the distribution. If an array is given,
it must have length `dimension`.
upper_bound : float or array
Upper bound(s) of the distribution. Must match the shape
of `lower_bound`.
population_size : int, optional
Number of individuals to generate (default 1).
encoding : Encoding, optional
Encoding that will be passed to each individual.
dtype : type, optional
Desired NumPy dtype of the generated vectors (default ``float``).
rng : RNGLike, optional
Random number generator.
"""
def __init__(self, dimension, lower_bound, upper_bound, population_size=1, encoding=None, dtype=float, rng=None):
super().__init__(dimension=dimension, population_size=population_size, encoding=encoding, rng=rng)
if type(lower_bound) in [list, tuple, np.ndarray]:
if len(lower_bound) != dimension:
raise ValueError(f"If lower_bound is a sequence it must be of length {dimension}.")
self.lower_bound = lower_bound
else:
self.lower_bound = np.repeat(lower_bound, self.dimension)
if type(upper_bound) in [list, tuple, np.ndarray]:
if len(upper_bound) != dimension:
raise ValueError(f"If upper_bound is a sequence it must be of length {dimension}.")
self.upper_bound = upper_bound
else:
self.upper_bound = np.repeat(upper_bound, self.dimension)
self.dtype = dtype
self.fallback = UniformInitializer(dimension, lower_bound, upper_bound, dtype=dtype, rng=rng)
[docs]
def generate_random(self):
return self.fallback.generate_random()
[docs]
def generate_population(self, n_individuals: Optional[int] = None) -> Population:
"""
Create a fully formed population of *n_individuals* individuals.
Parameters
----------
objfunc: ObjectiveFunc
Objective function that will be propagated to each individual.
n_individual: int, optional
Number of individuals to generate
Returns
-------
generated_population: Population
Newly generated population.
"""
if n_individuals is None:
n_individuals = self.population_size
idx_matrix = np.tile(np.arange(n_individuals), reps=(self.dimension, 1)).T
perm_matrix = self.rng.permuted(idx_matrix, axis=0)
unif_samples = self.rng.random((n_individuals, self.dimension))
norm_pop_matrix = (perm_matrix + unif_samples) / n_individuals
population_matrix = (self.upper_bound - self.lower_bound) * norm_pop_matrix + self.lower_bound
return Population(genotype_matrix=population_matrix, encoding=self.encoding)