Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,79 @@ If you used PyGAD, please consider adding a citation to the following paper abou
}
```

# New Features

## Environment Switching

PyGAD now supports automatic environment switching to adapt the genetic algorithm parameters based on the generation number:

1. **ENV_STABLE**: Default environment with standard parameters (crossover_probability=0.8, mutation_probability=0.1, parent_selection_type="sss")
2. **ENV_FAST**: Optimized for faster convergence (crossover_probability × 1.2, mutation_probability × 0.5, parent_selection_type="tournament")
3. **ENV_DIVERSE**: Optimized for maintaining population diversity (crossover_probability × 0.7, mutation_probability × 2, parent_selection_type="tournament_nsga2")

By default, the environment cycles every 10 generations: STABLE → FAST → DIVERSE → STABLE...

### Example Usage
```python
import pygad
import numpy as np

def fitness_func(ga_instance, solution, solution_idx):
return np.sum(solution)

# Create GA instance with environment switching enabled
ga_instance = pygad.GA(
num_generations=30,
num_parents_mating=5,
fitness_func=fitness_func,
sol_per_pop=10,
num_genes=5
)

# Run the GA with automatic environment switching
ga_instance.run()
```

## Pareto Front Output

For multi-objective optimization problems, PyGAD now automatically calculates and outputs the Pareto front after each generation. The Pareto front represents the set of non-dominated solutions where no solution can be improved in one objective without worsening at least one other objective.

### Key Features
1. **Automatic Calculation**: The Pareto front is automatically calculated after each generation for multi-objective fitness functions
2. **Serialization**: The Pareto front is included in the generation output dictionary
3. **Visualization**: Use `plot_pareto_front_curve()` method to visualize the Pareto front for 2-objective problems
4. **Access**: Access the latest Pareto front through `ga_instance.pareto_fronts` attribute

### Example Usage
```python
import pygad
import numpy as np

def multi_objective_fitness(ga_instance, solution, solution_idx):
# Two objectives: maximize sum, minimize L2 norm
return [np.sum(solution), -np.sqrt(np.sum(solution**2))]

# Create GA instance for multi-objective optimization
ga_instance = pygad.GA(
num_generations=20,
num_parents_mating=5,
fitness_func=multi_objective_fitness,
sol_per_pop=20,
num_genes=5,
parent_selection_type="nsga2"
)

# Run the GA
ga_instance.run()

# Access the calculated Pareto fronts
print(f"Number of Pareto fronts: {len(ga_instance.pareto_fronts)}")
print(f"Best Pareto front size: {len(ga_instance.pareto_fronts[0])}")

# Plot the Pareto front curve (for 2 objectives)
ga_instance.plot_pareto_front_curve()
```

# Contact Us

* E-mail: ahmed.f.gad@gmail.com
Expand Down
Binary file added pygad/__init__.pyc
Binary file not shown.
Binary file added pygad/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file added pygad/__pycache__/pygad.cpython-313.pyc
Binary file not shown.
Binary file added pygad/helper/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file added pygad/helper/__pycache__/misc.cpython-313.pyc
Binary file not shown.
Binary file not shown.
324 changes: 318 additions & 6 deletions pygad/pygad.py

Large diffs are not rendered by default.

Binary file added pygad/utils/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file added pygad/utils/__pycache__/crossover.cpython-313.pyc
Binary file not shown.
Binary file added pygad/utils/__pycache__/mutation.cpython-313.pyc
Binary file not shown.
Binary file added pygad/utils/__pycache__/nsga2.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file added pygad/visualize/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file not shown.
38 changes: 38 additions & 0 deletions test_multi_objective.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
import pygad
import numpy
import time

def fitness_func(ga_instance, solution, solution_idx):
# Simple test fitness function
fitness_score = numpy.sum(solution * numpy.array([0.2, 0.3, 0.3, 0.2]))
# Simulate time cost
time_cost = numpy.sum(solution) * 0.1
# Simulate diversity score
diversity_score = numpy.random.uniform(0, 1)

# Return a composite fitness score for compatibility with existing GA implementation
# You can adjust the weights based on your priorities
composite_score = fitness_score + diversity_score - time_cost
return composite_score

ga_instance = pygad.GA(num_generations=30,
num_parents_mating=4,
fitness_func=fitness_func,
sol_per_pop=8,
num_genes=4,
parent_selection_type="sss",
crossover_type="single_point",
mutation_type="random",
mutation_percent_genes=10)

ga_instance.run()

# 打印最终结果
solution, solution_fitness, solution_idx = ga_instance.best_solution()
print("\nFinal solution:", solution)
print("\nFinal solution fitness:", solution_fitness)

# 输出最后一代的Pareto前沿
final_pareto = ga_instance.calculate_pareto_front(ga_instance.last_generation_fitness)
print("\nFinal Pareto front size: {}".format(len(final_pareto)))
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
251 changes: 251 additions & 0 deletions tests/test_environment_switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
"""
This file contains tests for the environment switching feature in PyGAD.

The environment switching feature allows PyGAD to automatically adjust genetic algorithm parameters
based on different predefined environments:
- ENV_STABLE: Default environment with standard parameters
- ENV_FAST: Optimized for faster convergence
- ENV_DIVERSE: Optimized for maintaining population diversity

The tests verify:
1. The default environment switching pattern (cycles every 10 generations)
2. Correct parameter adjustments for each environment
3. Proper handling of adaptive mutation parameters
4. Multi-objective optimization with environment switching
"""
import pygad
import numpy as np

def test_environment_switch_pattern():
"""
Test that the default environment switching pattern cycles correctly
every 10 generations: STABLE → FAST → DIVERSE → STABLE...
"""
def fitness_func(ga_instance, solution, solution_idx):
return np.sum(solution)

ga_instance = pygad.GA(num_generations=1,
num_parents_mating=2,
fitness_func=fitness_func,
sol_per_pop=5,
num_genes=3)

# 测试环境切换模式
expected_pattern = [pygad.GA.ENV_STABLE, # generation 0
pygad.GA.ENV_STABLE, # generations 1-9
pygad.GA.ENV_FAST, # generations 10-19
pygad.GA.ENV_DIVERSE, # generations 20-29
pygad.GA.ENV_STABLE, # generations 30-39
pygad.GA.ENV_FAST] # generations 40-49

test_generations = [0, 5, 10, 20, 30, 40]

for gen, expected_env in zip(test_generations, expected_pattern):
env = ga_instance.get_environment_state(gen)
assert env == expected_env, f"Generation {gen}: expected {expected_env}, got {env}"

def test_environment_parameters_adjustment():
"""
Test that genetic algorithm parameters are adjusted correctly
when switching between different environments:
- FAST: Increase crossover probability by 20%, decrease mutation by 50%
- DIVERSE: Decrease crossover probability by 30%, increase mutation by 100%
- STABLE: Restore original parameters
"""
def fitness_func(ga_instance, solution, solution_idx):
return np.sum(solution)

# 测试FAST环境
ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
fitness_func=fitness_func,
sol_per_pop=5,
num_genes=3,
crossover_probability=0.8,
mutation_probability=0.1)

# 保存原始参数
original_crossover_rate = ga_instance.crossover_probability
original_mutation_rate = ga_instance.mutation_probability
ga_instance.original_selection_type = ga_instance.parent_selection_type

# 模拟FAST环境
ga_instance.current_environment = pygad.GA.ENV_FAST

# 应用FAST环境参数调整,对应run()方法中的内嵌逻辑
if isinstance(original_crossover_rate, (int, float)):
ga_instance.crossover_probability = original_crossover_rate * 1.2
else:
ga_instance.crossover_probability = original_crossover_rate

if isinstance(original_mutation_rate, (int, float)):
ga_instance.mutation_probability = original_mutation_rate * 0.5
else:
ga_instance.mutation_probability = original_mutation_rate

ga_instance.parent_selection_type = ga_instance.original_selection_type if ga_instance.original_selection_type not in ['nsga2', 'tournament_nsga2'] else 'tournament'

# 检查参数调整是否正确
assert ga_instance.crossover_probability == original_crossover_rate * 1.2, f"FAST environment crossover rate adjustment failed"
assert ga_instance.mutation_probability == original_mutation_rate * 0.5, f"FAST environment mutation rate adjustment failed"
assert ga_instance.parent_selection_type == "sss", f"FAST environment selection type adjustment failed"

# 模拟DIVERSE环境
ga_instance.current_environment = pygad.GA.ENV_DIVERSE

# 应用DIVERSE环境参数调整,对应run()方法中的内嵌逻辑
if isinstance(original_crossover_rate, (int, float)):
ga_instance.crossover_probability = original_crossover_rate * 0.7
else:
ga_instance.crossover_probability = original_crossover_rate

if isinstance(original_mutation_rate, (int, float)):
ga_instance.mutation_probability = original_mutation_rate * 2
else:
ga_instance.mutation_probability = original_mutation_rate

ga_instance.parent_selection_type = 'tournament_nsga2'

# 检查参数调整是否正确
assert ga_instance.crossover_probability == original_crossover_rate * 0.7, f"DIVERSE environment crossover rate adjustment failed"
assert ga_instance.mutation_probability == original_mutation_rate * 2, f"DIVERSE environment mutation rate adjustment failed"
assert ga_instance.parent_selection_type == "tournament_nsga2", f"DIVERSE environment selection type adjustment failed"

# 模拟回到STABLE环境
ga_instance.current_environment = pygad.GA.ENV_STABLE

# 恢复原始参数,对应run()方法中的内嵌逻辑
ga_instance.crossover_probability = original_crossover_rate
ga_instance.mutation_probability = original_mutation_rate
ga_instance.parent_selection_type = ga_instance.original_selection_type

# 检查参数是否恢复原值
assert ga_instance.crossover_probability == original_crossover_rate, f"STABLE environment crossover rate should remain unchanged"
assert ga_instance.mutation_probability == original_mutation_rate, f"STABLE environment mutation rate should remain unchanged"
assert ga_instance.parent_selection_type == "sss", f"STABLE environment selection type should remain unchanged"

def test_sequence_parameters_in_adaptive_mutation():
"""
Test that sequence parameters (like adaptive mutation rates) remain unchanged
when switching environments. Adaptive mutation uses a sequence of rates
that should not be overwritten by environment adjustments.
"""
def fitness_func(ga_instance, solution, solution_idx):
return np.sum(solution)

ga_instance = pygad.GA(num_generations=10,
num_parents_mating=2,
fitness_func=fitness_func,
sol_per_pop=5,
num_genes=3,
mutation_probability=[0.2, 0.05],
mutation_type="adaptive")

original_mutation_rate = ga_instance.mutation_probability.copy()

# 在FAST环境下测试,自适应突变参数应该保持不变
ga_instance.current_environment = pygad.GA.ENV_FAST

# 应用参数调整,对应run()方法中的内嵌逻辑
original_mutation_rate = ga_instance.mutation_probability
if isinstance(original_mutation_rate, (int, float)):
ga_instance.mutation_probability = original_mutation_rate * 0.5
else:
ga_instance.mutation_probability = original_mutation_rate

# 验证自适应突变参数未被修改
assert ga_instance.mutation_probability == original_mutation_rate, "Adaptive sequence mutation rate should remain unchanged in FAST environment"

# 在DIVERSE环境下测试
ga_instance.current_environment = pygad.GA.ENV_DIVERSE

# 应用参数调整,对应run()方法中的内嵌逻辑
if isinstance(original_mutation_rate, (int, float)):
ga_instance.mutation_probability = original_mutation_rate * 2
else:
ga_instance.mutation_probability = original_mutation_rate

# 验证自适应突变参数仍未被修改
assert ga_instance.mutation_probability == original_mutation_rate, "Adaptive sequence mutation rate should remain unchanged in DIVERSE environment"

def test_multi_objective_environment_switch():
"""
Test environment switching with multi-objective optimization.
Verifies that the environment switching works correctly with
non-single objective fitness functions and proper Pareto front handling.
"""
def fitness_func(ga_instance, solution, solution_idx):
# Multi-objective fitness: maximize sum, minimize L2 norm
return [np.sum(solution), -np.sqrt(np.sum(solution**2))]

ga_instance = pygad.GA(num_generations=30,
num_parents_mating=2,
fitness_func=fitness_func,
sol_per_pop=10,
num_genes=3)

# 运行遗传算法,验证环境切换正常工作且不报错
ga_instance.run()

# 验证Pareto前沿被正确计算
assert hasattr(ga_instance, 'pareto_fronts'), "Pareto fronts attribute should exist"
assert len(ga_instance.pareto_fronts) > 0, "Pareto fronts should not be empty"

def test_three_dimensional_multi_objective():
"""
Test multi-objective optimization with three-dimensional fitness vectors
to verify proper Pareto front calculation for higher-dimensional problems.
"""
def fitness_func(ga_instance, solution, solution_idx):
# Three objectives:
# 1. Maximize sum of solution
# 2. Minimize L2 norm
# 3. Maximize product of non-zero elements
sum_obj = np.sum(solution)
norm_obj = -np.sqrt(np.sum(solution**2))
product_obj = np.prod(solution[solution != 0]) if np.any(solution != 0) else 0
return [sum_obj, norm_obj, product_obj]

ga_instance = pygad.GA(
num_generations=10,
num_parents_mating=5,
fitness_func=fitness_func,
sol_per_pop=20,
num_genes=5,
parent_selection_type="nsga2",
crossover_probability=0.8,
mutation_probability=0.1
)

# Run for a few generations
ga_instance.run()

# Verify Pareto front was calculated correctly for 3 objectives
assert hasattr(ga_instance, 'pareto_fronts'), "Pareto fronts attribute should exist"
assert len(ga_instance.pareto_fronts) > 0, "Pareto fronts should not be empty"

# Check that each solution in the Pareto front has 3 objectives
for front in ga_instance.pareto_fronts:
for solution in front:
if len(solution) >= 2:
# solution array contains [solution_idx, fitness_values]
fitness = solution[1]
assert len(fitness) == 3, "3-objective fitness expected"

if __name__ == "__main__":
test_environment_switch_pattern()
print("✓ test_environment_switch_pattern passed")

test_environment_parameters_adjustment()
print("✓ test_environment_parameters_adjustment passed")

test_sequence_parameters_in_adaptive_mutation()
print("✓ test_sequence_parameters_in_adaptive_mutation passed")

test_multi_objective_environment_switch()
print("✓ test_multi_objective_environment_switch passed")

test_three_dimensional_multi_objective()
print("✓ test_three_dimensional_multi_objective passed")

print("\nAll tests passed!")