Skip to main content

Una biblioteca de Algoritmos Genéticos en Python optimizada con Numba para alto rendimiento, con soporte para paralelización y múltiples operadores genéticos.

Project description

PySAG-UdeC

Una librería simple y educativa de Algoritmos Genéticos en Python.

Desarrolladores

John Sebastián Galindo Hernández

Miguel Ángel Moreno Beltrán

Estado del Proyecto

Actualmente PySAG-UdeC se encuentra en la fase de desarrollo.

License: MIT

Características

  • Framework básico para Algoritmos Genéticos.
  • Permite funciones de fitness personalizadas.
  • Operadores genéticos intercambiables (Selección, Cruce, Mutación).
  • Optimización de funciones con Numba donde es aplicable.
  • Manejo de excepciones personalizado.
  • Capacidad para graficar la evolución del fitness.

Instalación

Puedes instalar PySAG-UdeC usando pip (una vez que sea publicado oficialmente en PyPI):

pip install PySAG-UdeC

Para desarrollo se puede clonar el repositorio y realizar la instalación para desarrollo:

git clone https://github.com/SebasGalindo/PySAG-UdeC.git
cd PySAG-UdeC
pip install -e .[dev]

📖 Documentación de la API

La librería PySAG-UdeC ofrece un conjunto de módulos para construir y ejecutar algoritmos genéticos. La documentación en versión extendida se encuentra en: https://pysag-udec.readthedocs.io/es/latest/

🧬 Clase Principal GA

La clase GA es el núcleo de la librería y gestiona el flujo del algoritmo genético.

Parámetros Principales:

  • fitness_func (Callable): Función que evalúa el fitness de un individuo.
  • num_genes (int): Número de genes en cada cromosoma.
  • population_size (int): Tamaño de la población (por defecto: 50).
  • num_generations (int): Número de generaciones a ejecutar (por defecto: 100).
  • num_parents_mating (int): Número de individuos a seleccionar como padres (por defecto: 10).
  • initial_population_func (Callable): Función para crear la población inicial (por defecto: initialization.init_random_uniform).
  • initial_pop_args (Optional[Dict]): Argumentos para la función de inicialización.
  • selection_func (Callable): Función para seleccionar padres (por defecto: selection.selection_roulette_wheel).
  • selection_args (Optional[Dict]): Argumentos para la función de selección.
  • crossover_func (Optional[Callable]): Función para el cruce (por defecto: crossover.crossover_single_point). Puede ser None.
  • crossover_args (Optional[Dict]): Argumentos para la función de cruce.
  • crossover_probability (float): Probabilidad de cruce (0.0-1.0, por defecto: 0.9).
  • mutation_func (Callable): Función para la mutación (por defecto: mutation.mutation_random_gene_uniform).
  • mutation_args (Optional[Dict]): Argumentos para la función de mutación.
  • keep_elitism_percentage (float): Porcentaje de los mejores individuos a pasar a la siguiente generación (0.0-1.0, por defecto: 0.1).
  • random_seed (Optional[int]): Semilla para reproducibilidad (por defecto: None).

Métodos Principales:

  • run() -> Tuple[Optional[IndividualType], float]: Ejecuta el algoritmo genético y devuelve la mejor solución y su fitness.
  • plot_fitness(save_path: Optional[str] = None): Grafica la evolución del mejor fitness por generación.

🚀 Módulo de Inicialización (PySAG.initialization)

Este módulo provee funciones para crear la población inicial.

  • init_random_uniform(pop_size, chromosome_length, low, high, dtype=np.float64): Genera una población con genes de valor real o entero muestreados de una distribución uniforme.
    • dtype: Puede ser np.float32, np.float64, np.int32, np.int64, np.int_.
  • init_random_binary(pop_size, chromosome_length, p_one=0.5): Genera una población con genes binarios (0 o 1). p_one es la probabilidad de que un gen sea 1.
  • init_random_permutation(pop_size, chromosome_length): Genera una población donde cada individuo es una permutación de enteros de 0 a chromosome_length - 1.

👍 Módulo de Selección (PySAG.selection)

Este módulo contiene métodos para seleccionar individuos para la reproducción.

  • selection_roulette_wheel(population, fitness_values, num_parents): Selección proporcional al fitness (los fitness deben ser no negativos).
  • selection_tournament(population, fitness_values, num_parents, tournament_size=3): Se eligen tournament_size individuos al azar, y el mejor de ellos se convierte en padre.
  • selection_rank(population, fitness_values, num_parents): Selección basada en el ranking de fitness de los individuos. La probabilidad de selección es proporcional al rango.
  • selection_stochastic_universal_sampling(SUS)(population, fitness_values, num_parents): Variante de la ruleta que reduce el azar en la asignación de cupos (los fitness deben ser no negativos).
  • selection_random(population, fitness_values, num_parents): Selección aleatoria simple de individuos, no considera el fitness.

↔️ Módulo de Cruce (PySAG.crossover)

Este módulo implementa operadores de cruce.

  • crossover_single_point(parent1, parent2)

    • Descripción: Cruce de un solo punto. Se elige un punto de corte aleatorio. Los hijos intercambian el material genético después de ese punto.
    • Ejemplo:
      • parent1 = np.array([0,0,1,1])
      • parent2 = np.array([1,1,0,0])
      • Si el punto de corte (aleatorio) es 2 (entre el índice 1 y 2):
        • child1 toma [0,0] de parent1 y [0,0] de parent2 -> [0,0,0,0]
        • child2 toma [1,1] de parent2 y [1,1] de parent1 -> [1,1,1,1]
      • Retorna (un posible resultado): (np.array([0,0,0,0]), np.array([1,1,1,1]))
  • crossover_two_points(parent1, parent2)

    • Descripción: Cruce de dos puntos. Se eligen dos puntos de corte aleatorios. Los hijos intercambian el material genético entre esos dos puntos.
    • Ejemplo:
      • parent1 = np.array([0,0,1,1,0,0])
      • parent2 = np.array([1,1,0,0,1,1])
      • Si los puntos de corte (aleatorios) son 2 y 4:
        • child1 toma [0,0] de parent1, [0,0] de parent2, [0,0] de parent1 -> [0,0,0,0,0,0]
        • child2 toma [1,1] de parent2, [1,1] de parent1, [1,1] de parent2 -> [1,1,1,1,1,1]
      • Retorna (un posible resultado): (np.array([0,0,0,0,0,0]), np.array([1,1,1,1,1,1]))
  • crossover_uniform(parent1, parent2, mix_probability=0.5)

    • Descripción: Cruce uniforme. Para cada gen, se decide con mix_probability si los genes de los padres se intercambian.
    • Ejemplo:
      • parent1 = np.array([0,0,1,1])
      • parent2 = np.array([1,1,0,0])
      • mix_probability = 0.5
      • Un posible resultado (el intercambio es aleatorio por gen):
        • Gen 0: no intercambia (child1[0]=0, child2[0]=1)
        • Gen 1: intercambia (child1[1]=1, child2[1]=0)
        • Gen 2: no intercambia (child1[2]=1, child2[2]=0)
        • Gen 3: intercambia (child1[3]=0, child2[3]=1)
        • child1 = [0,1,1,0]
        • child2 = [1,0,0,1]
      • Retorna (un posible resultado): (np.array([0,1,1,0]), np.array([1,0,0,1]))
  • crossover_arithmetic(parent1, parent2, alpha=0.5)

    • Descripción: Cruce aritmético para valores numéricos. child1 = alpha*p1 + (1-alpha)*p2, child2 = (1-alpha)*p1 + alpha*p2.
    • Ejemplo:
      • parent1 = np.array([1.0, 2.0, 10.0])
      • parent2 = np.array([4.0, 6.0, 0.0])
      • alpha = 0.5
        • child1 = 0.5*parent1 + 0.5*parent2 = [0.5, 1.0, 5.0] + [2.0, 3.0, 0.0] = [2.5, 4.0, 5.0]
        • child2 = 0.5*parent2 + 0.5*parent1 = [2.0, 3.0, 0.0] + [0.5, 1.0, 5.0] = [2.5, 4.0, 5.0]
      • Retorna: (np.array([2.5, 4.0, 5.0]), np.array([2.5, 4.0, 5.0]))
  • crossover_order_ox1(parent1, parent2)

    • Descripción: Cruce de orden (OX1) para permutaciones. Un segmento de parent1 se copia a child1. El resto de child1 se llena con genes de parent2 en orden, omitiendo los ya presentes. Proceso similar para child2.
    • Ejemplo:
      • parent1 = np.array([1, 2, 3, 4, 5])
      • parent2 = np.array([5, 4, 1, 2, 3])
      • Si los puntos de corte (aleatorios) para el segmento de parent1 son 1 y 3 (segmento [2,3,4] de parent1):
        • child1 toma [2,3,4] de parent1. child1 = [_, 2, 3, 4, _] (donde _ son posiciones a llenar)
        • Elementos restantes de parent2 en orden, omitiendo 2,3,4: 5, 1.
        • Llenando child1 desde la posición end (índice 4) y envolviendo: child1[4] = 5, child1[0] = 1.
        • child1 final: [1, 2, 3, 4, 5] (En este caso particular, debido a la naturaleza de los padres y los puntos de corte, child1 podría terminar siendo igual a parent1. El proceso es correcto, pero el resultado puede variar significativamente con otros inputs/puntos.)
        • Para child2, si el segmento de parent2 (índices 1 a 3) es [4,1,2]:
        • child2 toma [4,1,2] de parent2. child2 = [_, 4, 1, 2, _]
        • Elementos restantes de parent1 en orden, omitiendo 4,1,2: 3, 5.
        • child2 final: [3, 4, 1, 2, 5]
      • Retorna (un posible resultado para child1 y child2): (np.array([1,2,3,4,5]), np.array([3,4,1,2,5])) (El resultado exacto depende de los puntos aleatorios)

🔄 Módulo de Mutación (PySAG.mutation)

Este módulo proporciona operadores de mutación.

  • mutation_bit_flip(individual, mutation_rate=0.01)

    • Descripción: Invierte bits aleatorios en un individuo binario. Cada bit tiene una probabilidad mutation_rate de ser invertido.
    • Ejemplo:
      • individual = np.array([0,1,0,1])
      • mutation_rate = 0.5
      • Un posible resultado (cada bit muta con probabilidad 0.5):
        • Bit 0 (0) no muta.
        • Bit 1 (1) muta a 0.
        • Bit 2 (0) muta a 1.
        • Bit 3 (1) no muta.
        • mutated_individual = [0,0,1,1]
      • Retorna (un posible resultado): np.array([0,0,1,1])
  • mutation_random_gene_uniform(individual, gene_low, gene_high, mutation_rate=0.01)

    • Descripción: Reemplaza genes con valores de una distribución uniforme entre gene_low y gene_high. Cada gen tiene mutation_rate de probabilidad de ser mutado.
    • Ejemplo (enteros):
      • individual = np.array([10, 20, 30, 40])
      • gene_low = 0, gene_high = 5 (inclusive para enteros)
      • mutation_rate = 0.5
      • Un posible resultado:
        • Gen 0 (10) muta (ej. a 3).
        • Gen 1 (20) no muta.
        • Gen 2 (30) muta (ej. a 1).
        • Gen 3 (40) no muta.
        • mutated_individual = [3, 20, 1, 40]
      • Retorna (un posible resultado): np.array([3, 20, 1, 40])
  • mutation_gaussian(individual, mu=0.0, sigma=1.0, mutation_rate=0.01, clip_low=None, clip_high=None)

    • Descripción: Añade ruido gaussiano (N(mu, sigma)) a genes numéricos. Cada gen tiene mutation_rate de probabilidad de ser mutado. Los valores pueden ser recortados.
    • Ejemplo:
      • individual = np.array([1.0, 2.5, 3.0])
      • mu = 0.0, sigma = 0.1, mutation_rate = 0.6
      • Un posible resultado:
        • Gen 0 (1.0) muta: 1.0 + ruido (ej. ruido = 0.05) -> 1.05
        • Gen 1 (2.5) no muta.
        • Gen 2 (3.0) muta: 3.0 + ruido (ej. ruido = -0.02) -> 2.98
        • mutated_individual = [1.05, 2.5, 2.98]
      • Retorna (un posible resultado): np.array([1.05, 2.5, 2.98])
  • mutation_swap(individual, mutation_rate=0.01)

    • Descripción: Intercambia dos genes aleatorios en el individuo. La operación ocurre con mutation_rate de probabilidad.
    • Ejemplo:
      • individual = np.array([1,2,3,4,5])
      • mutation_rate = 1.0 (para asegurar que ocurra la mutación para el ejemplo)
      • Si los índices aleatorios elegidos son 0 y 3:
        • mutated_individual = [4,2,3,1,5] (el gen en la posición 0 y 3 se intercambian)
      • Retorna (un posible resultado): np.array([4,2,3,1,5])
  • mutation_inversion(individual, mutation_rate=0.01)

    • Descripción: Invierte un segmento aleatorio del cromosoma. La operación ocurre con mutation_rate de probabilidad.
    • Ejemplo:
      • individual = np.array([1,2,3,4,5,6])
      • mutation_rate = 1.0 (para asegurar que ocurra la mutación para el ejemplo)
      • Si los índices aleatorios para el segmento son 1 y 4 (segmento [2,3,4,5]):
        • El segmento [2,3,4,5] se invierte a [5,4,3,2]
        • mutated_individual = [1,5,4,3,2,6]
      • Retorna (un posible resultado): np.array([1,5,4,3,2,6])

💡 Ejemplo de Uso: Maximización de una Función Matemática

A continuación, se muestra un ejemplo básico de cómo utilizar la clase GA para maximizar la función $f(x, y) = \sin(x) \cdot \cos(y) + (x+y)/10$.

import numpy as np
from PySAG import GA, crossover, initialization, mutation, selection

# 1. Definir la función de Fitness
GENE_LOW = -10.0
GENE_HIGH = 10.0
NUM_GENES = 2  # Dos variables: x, y

def fitness_function(individual: np.ndarray) -> float:
    """
    Función de fitness para el problema de maximización de una función matemática.
    f(x, y) = sin(x) * cos(y) + (x+y)/10
    """
    if len(individual) != NUM_GENES: #
        raise ValueError(f"El individuo debe tener {NUM_GENES} genes.") #
    x = individual[0] #
    y = individual[1] #
    x = np.clip(x, GENE_LOW, GENE_HIGH) #
    y = np.clip(y, GENE_LOW, GENE_HIGH) #
    return np.sin(x) * np.cos(y) + (x + y) / 10.0 #

# 2. Configurar y Instanciar la clase GA
population_size = 100 #
num_generations = 150 #
num_parents_mating = 20 #
crossover_prob = 0.85 #
elitism_percentage = 0.05 #
mutation_rate_for_gaussian = 0.1 #

print("Configurando el Algoritmo Genético para maximizar f(x,y)...") #

ga_instance_math = GA(
    fitness_func=fitness_function, #
    num_genes=NUM_GENES, #
    population_size=population_size, #
    num_generations=num_generations, #
    num_parents_mating=num_parents_mating, #
    initial_population_func=initialization.init_random_uniform, #
    initial_pop_args={"low": GENE_LOW, "high": GENE_HIGH, "dtype": np.float64}, #
    selection_func=selection.selection_tournament, #
    selection_args={"tournament_size": 5}, #
    crossover_func=crossover.crossover_uniform, #
    crossover_args={"mix_probability": 0.5}, #
    crossover_probability=crossover_prob, #
    mutation_func=mutation.mutation_gaussian, #
    mutation_args={
        "mu": 0.0, #
        "sigma": 0.5, #
        "mutation_rate": mutation_rate_for_gaussian, #
        "clip_low": GENE_LOW,
        "clip_high": GENE_HIGH,
    },
    keep_elitism_percentage=elitism_percentage, #
    random_seed=42, #
)

# 3. Ejecutar el AG
print("Ejecutando el Algoritmo Genético...") #
best_solution, best_fitness = ga_instance_math.run() #

# 4. Mostrar Resultados
if best_solution is not None: #
    print(f"\nMejor solución encontrada: {best_solution}") #
    print(f"Valor de la función (fitness): {best_fitness:.6f}") #
    recalculated_fitness = fitness_function(best_solution) #
    print(f"Fitness recalculado para la mejor solución: {recalculated_fitness:.6f}") #
    ga_instance_math.plot_fitness(save_path="math_function_maximization_fitness.png") #
else:
    print("No se encontró una solución.") #

print("\nEjemplo de maximización de función matemática completado.") #

Extracto del ejemplo example_math_function_maximization.py. Los argumentos gene_low y gene_high en mutation_args para mutation_gaussian han sido renombrados a clip_low y clip_high para consistencia con la documentación de la función.


⚙️ Herramientas de Desarrollo

Este proyecto utiliza las siguientes herramientas para asegurar la calidad del código:

  • Black: Para formateo de código.
  • isort: Para organizar las importaciones.
  • Flake8: Para el linting de código, con los plugins:
    • flake8-docstrings
    • flake8-import-order

Configuradas a través de .pre-commit-config.yaml.


📜 Licencia

Este proyecto está bajo la Licencia MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pysag_udec-0.1.2.tar.gz (37.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pysag_udec-0.1.2-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file pysag_udec-0.1.2.tar.gz.

File metadata

  • Download URL: pysag_udec-0.1.2.tar.gz
  • Upload date:
  • Size: 37.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for pysag_udec-0.1.2.tar.gz
Algorithm Hash digest
SHA256 8b2d3225f7f91f2e1b8e87f26ba41167e92b82164931dd5507d462b82c6ee84c
MD5 421d7d2859610636443aeec44aed5841
BLAKE2b-256 54b905e95dcefe4d4352411d9efb65c6d31626648a41dd4386955ea8975b2cd6

See more details on using hashes here.

File details

Details for the file pysag_udec-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: pysag_udec-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.3

File hashes

Hashes for pysag_udec-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7e1a730d9959e265c2545bf289d105b3d7ffacebb006a54459ed804b3db1e5a1
MD5 7204f73deda5836e9b3f1f227e9098d8
BLAKE2b-256 7d4d81d9c31de4a5c1e624f628d0a993f60fc4ba375550759d65895b80d8968f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page