forked from hiive/mlrose
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor all crossover classes (onepoint, tsp, etc.)
- Add type hints for all methods/functions - Refactor file and method/function docstrings - General code style improvements
- Loading branch information
1 parent
c9f7652
commit b456fcd
Showing
14 changed files
with
315 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,71 @@ | ||
"""Crossover implementations for GA.""" | ||
"""Crossover implementations for Genetic Algorithms (GA). | ||
# Author: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
# License: BSD 3 clause | ||
This module defines a base class for crossover operations used in genetic algorithms, | ||
detailing how two parent solutions can be combined to create offspring. | ||
Authors: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
License: BSD 3 Clause | ||
""" | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import Any | ||
|
||
|
||
class _CrossoverBase(ABC): | ||
""" | ||
Base class for crossover operations in a genetic algorithm. | ||
Provides a structured way to define crossover behavior in genetic algorithms. | ||
It should be subclassed to implement specific crossover strategies. | ||
Parameters | ||
---------- | ||
optimization_problem : Any | ||
An instance of the optimization problem related to the genetic algorithm. | ||
This problem instance should provide necessary properties like 'length' | ||
that might be needed for the crossover operation. | ||
class _CrossOverBase(ABC): | ||
"""Base class for crossover operations in a genetic algorithm.""" | ||
Attributes | ||
---------- | ||
optimization_problem : Any | ||
The optimization problem instance. | ||
chromosome_length : int | ||
Length of the chromosome, typically derived from the optimization problem's | ||
'length' property. | ||
""" | ||
|
||
def __init__(self, opt_prob): | ||
def __init__(self, optimization_problem: Any) -> None: | ||
""" | ||
Initialize the CrossoverBase with the given optimization problem. | ||
Parameters | ||
---------- | ||
optimization_problem : Any | ||
An instance of the optimization problem related to the GA. | ||
""" | ||
super().__init__() | ||
self._opt_prob = opt_prob | ||
self._length = opt_prob.length | ||
self.optimization_problem = optimization_problem | ||
self.chromosome_length: int = optimization_problem.length | ||
|
||
@abstractmethod | ||
def mate(self, p1, p2): | ||
def mate(self, parent1: Any, parent2: Any) -> Any: | ||
""" | ||
Perform the crossover (mating) between two parents to produce offspring. | ||
This method must be implemented by subclasses to define specific crossover | ||
behavior based on the genetics of the parents. | ||
Parameters | ||
---------- | ||
parent1 : Any | ||
The first parent participating in the crossover. | ||
parent2 : Any | ||
The second parent participating in the crossover. | ||
Returns | ||
------- | ||
Any | ||
The offspring resulting from the crossover. The type of this result | ||
can vary depending on the specific GA implementation. | ||
""" | ||
pass |
64 changes: 53 additions & 11 deletions
64
mlrose_hiive/algorithms/crossovers/one_point_crossover.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,60 @@ | ||
"""Crossover implementations for GA.""" | ||
"""One Point Crossover implementation for Genetic Algorithms (GA). | ||
# Author: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
# License: BSD 3 clause | ||
This module defines a one-point crossover operation used in genetic algorithms, | ||
where a single crossover point is chosen randomly to combine two parent solutions. | ||
Authors: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
License: BSD 3 Clause | ||
""" | ||
|
||
import numpy as np | ||
from typing import Any, Sequence | ||
|
||
from mlrose_hiive.algorithms.crossovers._crossover_base import _CrossoverBase | ||
|
||
|
||
class OnePointCrossover(_CrossoverBase): | ||
""" | ||
One-point crossover for genetic algorithms. | ||
This class implements a simple one-point crossover, where a single crossover | ||
point on the parent chromosomes is chosen randomly, and the genetic information | ||
is exchanged to create a new offspring. | ||
Inherits from: | ||
_CrossoverBase : Abstract base class for crossover operations. | ||
""" | ||
|
||
def __init__(self, optimization_problem: Any) -> None: | ||
""" | ||
Initialize the OnePointCrossover with the given optimization problem. | ||
Parameters | ||
---------- | ||
optimization_problem : Any | ||
An instance of the optimization problem related to the genetic algorithm. | ||
""" | ||
super().__init__(optimization_problem) | ||
|
||
from mlrose_hiive.algorithms.crossovers._crossover_base import _CrossOverBase | ||
def mate(self, parent1: Sequence[float], parent2: Sequence[float]) -> np.ndarray: | ||
""" | ||
Perform the one-point crossover between two parent sequences to produce offspring. | ||
A single crossover point is selected randomly from the chromosome. All genes before | ||
that point are copied from the first parent and the rest from the second parent. | ||
class OnePointCrossOver(_CrossOverBase): | ||
def __init__(self, opt_prob): | ||
super().__init__(opt_prob) | ||
Parameters | ||
---------- | ||
parent1 : Sequence[float] | ||
The first parent chromosome sequence. | ||
parent2 : Sequence[float] | ||
The second parent chromosome sequence. | ||
def mate(self, p1, p2): | ||
n = 1 + np.random.randint(self._length-1) | ||
child = np.array([*p1[:n], *p2[n:]]) | ||
return child | ||
Returns | ||
------- | ||
np.ndarray | ||
The offspring chromosome resulting from the crossover. | ||
""" | ||
crossover_point = 1 + np.random.randint(self.chromosome_length - 1) | ||
offspring = np.array([*parent1[:crossover_point], *parent2[crossover_point:]]) | ||
return offspring |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,147 @@ | ||
"""TSP Crossover implementation for GA.""" | ||
"""TSP Crossover implementation for Genetic Algorithms (GA). | ||
# Author: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
# License: BSD 3 clause | ||
This module defines a TSP-specific crossover operation used in genetic algorithms, | ||
which handles the mating of parent solutions to produce offspring that respect the TSP | ||
constraints. | ||
Authors: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura) | ||
License: BSD 3 Clause | ||
""" | ||
|
||
import numpy as np | ||
from typing import Any, Sequence | ||
|
||
from mlrose_hiive.algorithms.crossovers._crossover_base import _CrossoverBase | ||
|
||
|
||
class TSPCrossover(_CrossoverBase): | ||
""" | ||
Crossover operation tailored for the Traveling Salesman Problem (TSP) in genetic algorithms. | ||
Implements specific crossover techniques that ensure valid TSP routes in the offspring. | ||
The crossover handles distinct city sequences without repetitions and uses specialized | ||
logic to combine parental genes. | ||
from mlrose_hiive.algorithms.crossovers._crossover_base import _CrossOverBase | ||
Inherits from: | ||
_CrossoverBase : Abstract base class for crossover operations. | ||
""" | ||
|
||
def __init__(self, optimization_problem: Any) -> None: | ||
""" | ||
Initialize the TSPCrossover with the given optimization problem. | ||
class TSPCrossOver(_CrossOverBase): | ||
Parameters | ||
---------- | ||
optimization_problem : Any | ||
An instance of the optimization problem related to the genetic algorithm. | ||
""" | ||
super().__init__(optimization_problem) | ||
|
||
def __init__(self, opt_prob): | ||
super().__init__(opt_prob) | ||
def mate(self, parent1: Sequence[int], parent2: Sequence[int]) -> np.ndarray: | ||
""" | ||
Perform the crossover (mating) between two parent sequences to produce offspring. | ||
def mate(self, p1, p2): | ||
return self._mate_fill(p1, p2) | ||
Chooses between two internal methods to generate offspring based on TSP-specific | ||
constraints and optimizations. | ||
def _mate_fill(self, p1, p2): | ||
if self._length > 1: | ||
n = 1 + np.random.randint(self._length - 1) | ||
child = np.array([0] * self._length) | ||
child[:n] = p1[:n] | ||
Parameters | ||
---------- | ||
parent1 : Sequence[int] | ||
The first parent representing a TSP route. | ||
parent2 : Sequence[int] | ||
The second parent representing a TSP route. | ||
unvisited = [node for node in p2 if node not in p1[:n]] | ||
Returns | ||
------- | ||
np.ndarray | ||
The offspring representing a new TSP route. | ||
""" | ||
return self._mate_fill(parent1, parent2) | ||
|
||
def _mate_fill(self, parent1: Sequence[int], parent2: Sequence[int]) -> np.ndarray: | ||
""" | ||
Perform a fill-based crossover using a segment of the first parent and filling | ||
the rest with non-repeated cities from the second parent. | ||
Parameters | ||
---------- | ||
parent1 : Sequence[int] | ||
The first parent representing a TSP route. | ||
parent2 : Sequence[int] | ||
The second parent representing a TSP route. | ||
Returns | ||
------- | ||
np.ndarray | ||
The offspring TSP route. | ||
""" | ||
if self.chromosome_length > 1: | ||
n = 1 + np.random.randint(self.chromosome_length - 1) | ||
child = np.array([0] * self.chromosome_length) | ||
child[:n] = parent1[:n] | ||
unvisited = [city for city in parent2 if city not in parent1[:n]] | ||
child[n:] = unvisited | ||
elif np.random.randint(2) == 0: | ||
child = np.copy(p1) | ||
else: | ||
child = np.copy(p2) | ||
child = np.copy(parent1 if np.random.randint(2) == 0 else parent2) | ||
return child | ||
|
||
def _mate_traverse(self, parent_1, parent_2): | ||
if self._length > 1: | ||
next_a = np.append(parent_1[1:], parent_1[-1]) | ||
next_b = np.append(parent_2[1:], parent_2[-1]) | ||
|
||
visited = [False] * self._length | ||
child = np.array([0] * self._length) | ||
|
||
v = np.random.randint(len(parent_1)) | ||
child[0] = v | ||
visited[v] = True | ||
for i in range(1, len(child)): | ||
cur = child[i-1] | ||
na = next_a[cur] | ||
nb = next_b[cur] | ||
va = visited[na] | ||
vb = visited[nb] | ||
if va and not vb: | ||
nx = nb | ||
elif not va and vb: | ||
nx = na | ||
elif not va and not vb: | ||
fa = self._opt_prob.fitness_fn.calculate_fitness([cur, na]) | ||
fb = self._opt_prob.fitness_fn.calculate_fitness([cur, nb]) | ||
nx = nb if fa > fb else na # opposite because they're distance and smaller is better | ||
def _mate_traverse(self, parent1: Sequence[int], parent2: Sequence[int]) -> np.ndarray: | ||
""" | ||
Perform a traversal-based crossover using city adjacency considerations from | ||
both parents to construct a viable TSP route. | ||
The method determines the next city to visit based on the adjacency in both | ||
parents' routes, considering previously visited cities and selecting based | ||
on fitness values where applicable. | ||
Parameters | ||
---------- | ||
parent1 : Sequence[int] | ||
The first parent representing a TSP route. | ||
parent2 : Sequence[int] | ||
The second parent representing a TSP route. | ||
Returns | ||
------- | ||
np.ndarray | ||
The offspring TSP route. | ||
""" | ||
if self.chromosome_length > 1: | ||
next_city_parent1 = np.append(parent1[1:], parent1[0]) | ||
next_city_parent2 = np.append(parent2[1:], parent2[0]) | ||
|
||
visited_cities = [False] * self.chromosome_length | ||
offspring_route = np.array([0] * self.chromosome_length) | ||
|
||
starting_city = np.random.randint(len(parent1)) | ||
offspring_route[0] = starting_city | ||
visited_cities[starting_city] = True | ||
|
||
for index in range(1, len(offspring_route)): | ||
current_city = offspring_route[index - 1] | ||
next_city1 = next_city_parent1[current_city] | ||
next_city2 = next_city_parent2[current_city] | ||
|
||
visited_city1 = visited_cities[next_city1] | ||
visited_city2 = visited_cities[next_city2] | ||
|
||
if visited_city1 and not visited_city2: | ||
next_city = next_city2 | ||
elif not visited_city1 and visited_city2: | ||
next_city = next_city1 | ||
elif not visited_city1 and not visited_city2: | ||
fitness1 = self.optimization_problem.fitness_fn.calculate_fitness([current_city, next_city1]) | ||
fitness2 = self.optimization_problem.fitness_fn.calculate_fitness([current_city, next_city2]) | ||
next_city = next_city2 if fitness1 > fitness2 else next_city1 # Choose the smaller distance | ||
else: | ||
while True: | ||
nx = np.random.randint(len(parent_1)) | ||
if not visited[nx]: | ||
next_city = np.random.randint(len(parent1)) | ||
if not visited_cities[next_city]: | ||
break | ||
child[i] = nx | ||
visited[nx] = True | ||
elif np.random.randint(2) == 0: | ||
child = np.copy(parent_1) | ||
|
||
offspring_route[index] = next_city | ||
visited_cities[next_city] = True | ||
else: | ||
child = np.copy(parent_2) | ||
return child | ||
offspring_route = np.copy(parent1 if np.random.randint(2) == 0 else parent2) | ||
|
||
return offspring_route |
Oops, something went wrong.