"""
Módulo de Inicialización de Poblaciones para Algoritmos Genéticos.
Este módulo proporciona funciones para crear la población inicial de individuos
(cromosomas) para un algoritmo genético. Las funciones están diseñadas para ser
flexibles y eficientes, utilizando Numba para la optimización cuando es aplicable.
Funciones de Inicialización Disponibles:
- init_random_uniform:
Genera una población con genes de valor real o entero muestreados de una
distribución uniforme, especificado mediante el parámetro `dtype`.
- init_random_binary:
Genera una población con genes binarios (0 o 1).
- init_random_permutation:
Genera una población donde cada individuo es una permutación de enteros
(útil para problemas de ordenamiento).
"""
from typing import Any, List, Type, Union
import numpy as np
from numba import njit
from numpy.typing import NDArray
from .exceptions import (
InitializationError,
RangeError,
TypeValidationError,
validate_parameter,
)
@njit(cache=True)
def _generate_float_uniform_population_impl(
pop_size: int, chromosome_length: int, low: float, high: float
) -> List[NDArray[np.float64]]:
"""
Generar población uniforme de flotantes.
Implementación Numba para generar población uniforme de flotantes.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Número de genes en cada cromosoma (individuo).
low: Límite inferior del rango para los valores de los genes.
high: Límite superior del rango para los valores de los genes.
(Exclusivo para flotantes, inclusivo para enteros).
Returns:
Una lista de individuos (arrays de NumPy) del `dtype` especificado.
"""
population = []
for _ in range(pop_size):
# np.random.uniform genera valores en [low, high)
individual = np.random.uniform(low, high, size=chromosome_length)
population.append(individual) # Por defecto es float64
return population
@njit(cache=True)
def _generate_integer_uniform_population_impl(
pop_size: int, chromosome_length: int, low: int, high: int
) -> List[NDArray[np.int_]]: # Numba puede usar np.int_ o np.int64
"""
Generar población uniforme de enteros.
Implementación Numba para generar población uniforme de enteros.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Número de genes en cada cromosoma (individuo).
low: Límite inferior del rango para los valores de los genes.
high: Límite superior del rango para los valores de los genes.
(Exclusivo para flotantes, inclusivo para enteros).
Returns:
Una lista de individuos (arrays de NumPy) del `dtype` especificado.
"""
population = []
# np.random.randint(low, high_exclusive, size=...)
# Para rango [low, high] inclusivo, el segundo parámetro debe ser high + 1
for _ in range(pop_size):
individual = np.random.randint(low, high + 1, size=chromosome_length)
population.append(
individual.astype(np.int_)
) # Asegurar tipo int_ (plataforma-dependiente int)
return population
@njit(cache=True)
def _generate_binary_population_impl(
pop_size: int, chromosome_length: int, p_one: float
) -> List[NDArray[np.int_]]:
"""
Generar población binaria.
Implementación Numba para generar población binaria.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Número de genes en cada cromosoma (individuo).
p_one: Probabilidad de que un gen sea 1.
Returns:
Una lista de individuos (arrays de NumPy) del `dtype` especificado.
"""
population = []
for _ in range(pop_size):
individual = np.random.random(size=chromosome_length) < p_one
population.append(
individual.astype(np.int_)
) # Convertir booleano a int (0 o 1)
return population
@njit(cache=True)
def _generate_permutation_population_impl(
pop_size: int, chromosome_length: int
) -> List[NDArray[np.int_]]:
"""
Generar población de permutaciones de enteros.
Implementación Numba para generar población de permutaciones de enteros.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Número de genes en cada cromosoma (individuo).
Returns:
Una lista de individuos (arrays de NumPy) del `dtype` especificado.
"""
population = []
base_array = np.arange(
chromosome_length, dtype=np.int_
) # Especificar dtype para claridad
for _ in range(pop_size):
individual = np.random.permutation(base_array)
population.append(individual) # Ya es del tipo correcto (np.int_)
return population
[documentos]
def init_random_binary(
pop_size: int,
chromosome_length: int,
p_one: float = 0.5,
**kwargs: Any,
) -> List[NDArray[np.int_]]:
"""
Genera una población con genes binarios (0 o 1).
Cada gen se establece en 1 con probabilidad `p_one`
y en 0 con probabilidad `1 - p_one`.
Los genes son de tipo `np.int_`.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Número de genes en cada cromosoma.
p_one: Probabilidad de que un gen individual sea 1. Por defecto es 0.5.
**kwargs: Argumentos adicionales (no utilizados).
Returns:
Una lista de individuos (arrays de NumPy de tipo `np.int_`), donde cada
individuo representa un cromosoma binario.
Raises:
TypeValidationError: Si los tipos de los parámetros son incorrectos.
RangeError: Si `pop_size` o `chromosome_length` no son positivos,
o si `p_one` no está en el rango [0, 1].
InitializationError: Si ocurre un error inesperado durante la inicialización.
Example:
>>> pop_bin = init_random_binary(10, 8)
>>> len(pop_bin)
10
>>> pop_bin[0].shape
(8,)
>>> pop_bin[0].dtype # np.int_ es int32 o int64 según la plataforma
dtype('int...')
>>> np.all((pop_bin[0] == 0) | (pop_bin[0] == 1))
True
"""
validate_parameter(pop_size, "pop_size", int, min_val=1)
validate_parameter(chromosome_length, "chromosome_length", int, min_val=1)
validate_parameter(p_one, "p_one", float, min_val=0.0, max_val=1.0) # type: ignore
try:
# _generate_binary_population_impl devuelve una lista de NDArray[np.int_]
population = _generate_binary_population_impl(
pop_size, chromosome_length, p_one
)
except Exception as e:
raise InitializationError(
f"Error durante la inicialización binaria: {str(e)}",
details={ # type: ignore
"pop_size": pop_size,
"chromosome_length": chromosome_length,
"p_one": p_one,
"original_error": str(e),
},
) from e
return population
[documentos]
def init_random_permutation(
pop_size: int,
chromosome_length: int,
**kwargs: Any,
) -> List[NDArray[np.int_]]:
"""
Genera una población donde cada individuo es una permutación de enteros.
Cada cromosoma será una permutación de los enteros
`0, 1, ..., chromosome_length - 1`.
Este método está diseñado para problemas de optimización combinatoria (e.g., TSP)
donde los individuos representan un orden o secuencia de elementos discretos.
Los genes son de tipo `np.int_`.
Args:
pop_size: Número de individuos en la población.
chromosome_length: Longitud de la permutación (número de elementos a permutar).
**kwargs: Argumentos adicionales (no utilizados).
Returns:
Una lista de individuos (arrays de NumPy de tipo `np.int_`), donde cada
individuo es una permutación de enteros.
Raises:
TypeValidationError: Si los tipos de los parámetros son incorrectos.
RangeError: Si `pop_size` o `chromosome_length` no son positivos.
InitializationError: Si ocurre un error inesperado durante la inicialización.
Example:
>>> pop_perm = init_random_permutation(5, 4)
>>> len(pop_perm)
5
>>> pop_perm[0].shape
(4,)
>>> pop_perm[0].dtype # np.int_ es int32 o int64 según la plataforma
dtype('int...')
>>> import numpy as np
>>> np.array_equal(np.sort(pop_perm[0]), np.arange(4))
True
"""
validate_parameter(pop_size, "pop_size", int, min_val=1)
validate_parameter(chromosome_length, "chromosome_length", int, min_val=1)
try:
population = _generate_permutation_population_impl(pop_size, chromosome_length)
except Exception as e:
raise InitializationError(
f"Error durante la inicialización de permutaciones: {str(e)}",
details={ # type: ignore
"pop_size": pop_size,
"chromosome_length": chromosome_length,
"original_error": str(e),
},
) from e
return population