@File : init.py
@Time : 2023/11/03 10:33:37
@Author : Alejandro Marrero
@Version : 1.0
@Contact : amarrerd@ull.edu.es
@License : (C)Copyright 2023, Alejandro Marrero
@Desc : None
Bases: Mutation
Source code in digneapy/operators/mutation/uniform.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 | class BatchUniformMutation(Mutation):
def __init__(self, seed: Optional[int | np.random.SeedSequence] = None):
super().__init__(seed)
def __call__(
self, population: np.ndarray, lb: np.ndarray, ub: np.ndarray
) -> np.ndarray:
"""Performs uniform one mutation in batches
Args:
population (np.ndarray): Batch of individuals to mutate
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: If the dimension of the individuals do not match the bounds
Returns:
np.ndarray: mutated population
"""
dimension = len(population[0])
n_individuals = len(population)
if len(lb) != len(ub) or dimension != len(lb):
msg = f"len of individuals ({dimension}) and bounds {len(lb)} differs in uniform_one_mutation"
raise ValueError(msg)
mutation_points = self._rng.integers(low=0, high=dimension, size=n_individuals)
new_values = self._rng.uniform(
low=lb[mutation_points], high=ub[mutation_points], size=n_individuals
)
population[np.arange(n_individuals), mutation_points] = new_values
return population
|
Performs uniform one mutation in batches
| Parameters: |
-
population
(ndarray)
–
Batch of individuals to mutate
-
lb
(ndarray)
–
Lower bound for each dimension
-
ub
(ndarray)
–
Upper bound for each dimension
-
seed
(Optional[int | SeedSequence])
–
Seed for the random number generator. Defaults to None.
|
| Raises: |
-
ValueError
–
If the dimension of the individuals do not match the bounds
|
| Returns: |
-
ndarray
–
np.ndarray: mutated population
|
Source code in digneapy/operators/mutation/uniform.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 | def __call__(
self, population: np.ndarray, lb: np.ndarray, ub: np.ndarray
) -> np.ndarray:
"""Performs uniform one mutation in batches
Args:
population (np.ndarray): Batch of individuals to mutate
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: If the dimension of the individuals do not match the bounds
Returns:
np.ndarray: mutated population
"""
dimension = len(population[0])
n_individuals = len(population)
if len(lb) != len(ub) or dimension != len(lb):
msg = f"len of individuals ({dimension}) and bounds {len(lb)} differs in uniform_one_mutation"
raise ValueError(msg)
mutation_points = self._rng.integers(low=0, high=dimension, size=n_individuals)
new_values = self._rng.uniform(
low=lb[mutation_points], high=ub[mutation_points], size=n_individuals
)
population[np.arange(n_individuals), mutation_points] = new_values
return population
|
BinarySelection
Bases: Selection
Source code in digneapy/operators/selection/binary.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | class BinarySelection(Selection):
def __init__(
self, attr: str = "fitness", seed: Optional[int | np.random.SeedSequence] = None
):
super().__init__(seed)
self._attr = attr
def __call__(
self,
population: Sequence[IndType] | np.ndarray,
) -> IndType:
"""Binary Tournament Selection Operator
Args:
population (Sequence): Population of individuals to select a parent from
Raises:
RuntimeError: If the population is empty
Returns:
Instance or Solution: New parent
"""
if not population:
msg = "Trying to selection individuals in an empty population."
raise ValueError(msg)
elif len(population) == 1:
return population[0]
else:
idx1, idx2 = self._rng.integers(low=0, high=len(population), size=2)
return max(population[idx1], population[idx2], key=attrgetter(self._attr))
|
__call__(population)
Binary Tournament Selection Operator
| Parameters: |
-
population
(Sequence)
–
Population of individuals to select a parent from
|
| Raises: |
-
RuntimeError
–
If the population is empty
|
| Returns: |
-
IndType
–
Instance or Solution: New parent
|
Source code in digneapy/operators/selection/binary.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | def __call__(
self,
population: Sequence[IndType] | np.ndarray,
) -> IndType:
"""Binary Tournament Selection Operator
Args:
population (Sequence): Population of individuals to select a parent from
Raises:
RuntimeError: If the population is empty
Returns:
Instance or Solution: New parent
"""
if not population:
msg = "Trying to selection individuals in an empty population."
raise ValueError(msg)
elif len(population) == 1:
return population[0]
else:
idx1, idx2 = self._rng.integers(low=0, high=len(population), size=2)
return max(population[idx1], population[idx2], key=attrgetter(self._attr))
|
Elitist
Bases: Replacement
Source code in digneapy/operators/replacement/elitist.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | class Elitist(Replacement):
def __init__(
self,
hall_of_fame: int = 1,
attr: str = "fitness",
seed: Optional[int | np.random.SeedSequence] = None,
):
super().__init__(seed)
self._hof = hall_of_fame
self._attr = attr
def __call__(
self,
population: Sequence[IndType],
offspring: Sequence[IndType],
) -> Sequence[IndType]:
"""Returns a new population constructed using the Elitist approach.
HoF number of individuals from the current + offspring populations are
kept in the new population. The remaining individuals are selected from
the offspring population.
Args:
population Sequence[IndType],: Current population in the algorithm
offspring Sequence[IndType],: Offspring population
hof (int, optional): _description_. Defaults to 1.
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
list[IndType]:
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in elitist_replacement"
raise ValueError(msg)
combined_population = sorted(
itertools.chain(population, offspring),
key=attrgetter(self._attr),
reverse=True,
)
top = combined_population[: self._hof]
return list(top + offspring[1:])
|
__call__(population, offspring)
Returns a new population constructed using the Elitist approach.
HoF number of individuals from the current + offspring populations are
kept in the new population. The remaining individuals are selected from
the offspring population.
| Parameters: |
-
population Sequence[IndType],
–
Current population in the algorithm
-
offspring Sequence[IndType],
–
-
hof
(int)
–
description. Defaults to 1.
|
| Raises: |
-
ValueError
–
Raises if the sizes of the population are different
|
Source code in digneapy/operators/replacement/elitist.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | def __call__(
self,
population: Sequence[IndType],
offspring: Sequence[IndType],
) -> Sequence[IndType]:
"""Returns a new population constructed using the Elitist approach.
HoF number of individuals from the current + offspring populations are
kept in the new population. The remaining individuals are selected from
the offspring population.
Args:
population Sequence[IndType],: Current population in the algorithm
offspring Sequence[IndType],: Offspring population
hof (int, optional): _description_. Defaults to 1.
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
list[IndType]:
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in elitist_replacement"
raise ValueError(msg)
combined_population = sorted(
itertools.chain(population, offspring),
key=attrgetter(self._attr),
reverse=True,
)
top = combined_population[: self._hof]
return list(top + offspring[1:])
|
Generational
Bases: Replacement
Source code in digneapy/operators/replacement/generational.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | class Generational(Replacement):
def __init__(self, seed: Optional[int | np.random.SeedSequence] = None):
super().__init__(seed)
def __call__(
self, population: Sequence[IndType], offspring: Sequence[IndType]
) -> Sequence[IndType]:
"""Returns the offspring population as the new current population
Args:
population (Sequence[IndType]): Current population in the algorithm
offspring (Sequence[IndType]): Offspring population
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
Sequence[IndType]: New population
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in generational replacement"
raise ValueError(msg)
return offspring[:]
|
__call__(population, offspring)
Returns the offspring population as the new current population
| Parameters: |
-
population
(Sequence[IndType])
–
Current population in the algorithm
-
offspring
(Sequence[IndType])
–
|
| Raises: |
-
ValueError
–
Raises if the sizes of the population are different
|
| Returns: |
-
Sequence[IndType]
–
Sequence[IndType]: New population
|
Source code in digneapy/operators/replacement/generational.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 | def __call__(
self, population: Sequence[IndType], offspring: Sequence[IndType]
) -> Sequence[IndType]:
"""Returns the offspring population as the new current population
Args:
population (Sequence[IndType]): Current population in the algorithm
offspring (Sequence[IndType]): Offspring population
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
Sequence[IndType]: New population
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in generational replacement"
raise ValueError(msg)
return offspring[:]
|
GreedyReplacement
Bases: Replacement
Source code in digneapy/operators/replacement/greedy.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | class GreedyReplacement(Replacement):
def __init__(self, seed: Optional[int | np.random.SeedSequence] = None):
super().__init__(seed)
def __call__(
self,
population: Sequence[IndType],
offspring: Sequence[IndType],
) -> Sequence[IndType]:
"""Returns a new population produced by a greedy operator.
Each individual in the current population is compared with its analogous in the offspring population
and the best survives
Args:
population (Sequence[IndType]): Current population in the algorithm
offspring (Sequence[IndType]): Offspring population
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
Sequence[IndType]: New population
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in first_improve_replacement"
raise ValueError(msg)
return [a if a > b else b for a, b in zip(population, offspring)]
|
__call__(population, offspring)
Returns a new population produced by a greedy operator.
Each individual in the current population is compared with its analogous in the offspring population
and the best survives
| Parameters: |
-
population
(Sequence[IndType])
–
Current population in the algorithm
-
offspring
(Sequence[IndType])
–
|
| Raises: |
-
ValueError
–
Raises if the sizes of the population are different
|
| Returns: |
-
Sequence[IndType]
–
Sequence[IndType]: New population
|
Source code in digneapy/operators/replacement/greedy.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | def __call__(
self,
population: Sequence[IndType],
offspring: Sequence[IndType],
) -> Sequence[IndType]:
"""Returns a new population produced by a greedy operator.
Each individual in the current population is compared with its analogous in the offspring population
and the best survives
Args:
population (Sequence[IndType]): Current population in the algorithm
offspring (Sequence[IndType]): Offspring population
Raises:
ValueError: Raises if the sizes of the population are different
Returns:
Sequence[IndType]: New population
"""
if len(population) != len(offspring):
msg = f"The size of the current population ({len(population)}) != size of the offspring ({len(offspring)}) in first_improve_replacement"
raise ValueError(msg)
return [a if a > b else b for a, b in zip(population, offspring)]
|
ISOLineMutation
Bases: Mutation
Source code in digneapy/operators/mutation/iso.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | class ISOLineMutation(Mutation):
def __init__(
self,
sigma_iso: float,
sigma_line: float,
seed: Optional[int | np.random.SeedSequence] = None,
):
super().__init__(seed)
try:
self._sigma_iso = float(sigma_iso)
self._sigma_line = float(sigma_line)
except ValueError:
raise ValueError("sigma_iso and sigma_line must be float.")
def __call__(
self, population: np.ndarray, lb: np.ndarray, ub: np.ndarray
) -> np.ndarray:
"""Performs ISO+Line mutation from Vassiliades & Mouret 2018
Args:
population (np.ndarray): Batch of individuals to mutate
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
Raises:
ValueError: if dimension != bounds
Returns:
np.ndarray: Newly mutated individuals
"""
dimension = len(population[0])
if len(lb) != len(ub) or dimension != len(lb):
msg = f"The size of individuals ({dimension}) and bounds {len(lb)} is different in iso_line_mutation"
raise ValueError(msg)
indices = np.arange(len(population))
parents_a = np.asarray(
population[self._rng.choice(indices, size=len(population))], copy=True
)
parents_b = np.asarray(
population[self._rng.choice(indices, size=len(population))], copy=True
)
iso_noise = self._rng.normal(0, self._sigma_iso, size=parents_a.shape)
line_steps = self._rng.uniform(0, self._sigma_line, size=(len(parents_a), 1))
direction = parents_b - parents_a
offspring = parents_a + iso_noise + line_steps * direction
offspring = np.clip(offspring, lb, ub)
return offspring
|
__call__(population, lb, ub)
Performs ISO+Line mutation from Vassiliades & Mouret 2018
| Parameters: |
-
population
(ndarray)
–
Batch of individuals to mutate
-
lb
(ndarray)
–
Lower bound for each dimension
-
ub
(ndarray)
–
Upper bound for each dimension
|
| Returns: |
-
ndarray
–
np.ndarray: Newly mutated individuals
|
Source code in digneapy/operators/mutation/iso.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | def __call__(
self, population: np.ndarray, lb: np.ndarray, ub: np.ndarray
) -> np.ndarray:
"""Performs ISO+Line mutation from Vassiliades & Mouret 2018
Args:
population (np.ndarray): Batch of individuals to mutate
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
Raises:
ValueError: if dimension != bounds
Returns:
np.ndarray: Newly mutated individuals
"""
dimension = len(population[0])
if len(lb) != len(ub) or dimension != len(lb):
msg = f"The size of individuals ({dimension}) and bounds {len(lb)} is different in iso_line_mutation"
raise ValueError(msg)
indices = np.arange(len(population))
parents_a = np.asarray(
population[self._rng.choice(indices, size=len(population))], copy=True
)
parents_b = np.asarray(
population[self._rng.choice(indices, size=len(population))], copy=True
)
iso_noise = self._rng.normal(0, self._sigma_iso, size=parents_a.shape)
line_steps = self._rng.uniform(0, self._sigma_line, size=(len(parents_a), 1))
direction = parents_b - parents_a
offspring = parents_a + iso_noise + line_steps * direction
offspring = np.clip(offspring, lb, ub)
return offspring
|
OnePointCrossover
Bases: Crossover
Source code in digneapy/operators/crossover/opoint.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | class OnePointCrossover(Crossover):
def __init__(
self, cxpb: float = 0.5, seed: Optional[int | np.random.SeedSequence] = None
):
super().__init__(cxpb, seed)
def __call__(self, individual: IndType, other: IndType) -> IndType:
"""One point crossover
Args:
individual Instance or Solution: First individual to apply crossover. Returned object
other Instance or Solution: Second individual to apply crossover
cxpb (float64, optional): Crossover probability. Not used in this operator.
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: When the len(ind_1) != len(ind_2)
Returns:
Instance or Solution: New individual
"""
if len(individual) != len(other):
msg = f"Individual of different length in uniform_crossover. len(ind) = {len(individual)} != len(other) = {len(other)}"
raise ValueError(msg)
offspring = individual.clone()
cross_point = self._rng.integers(low=0, high=len(individual))
offspring[cross_point:] = other[cross_point:]
return offspring
|
__call__(individual, other)
One point crossover
| Parameters: |
-
individual Instance or Solution
–
First individual to apply crossover. Returned object
-
other Instance or Solution
–
Second individual to apply crossover
-
cxpb
(float64)
–
Crossover probability. Not used in this operator.
-
seed
(Optional[int | SeedSequence])
–
Seed for the random number generator. Defaults to None.
|
| Raises: |
-
ValueError
–
When the len(ind_1) != len(ind_2)
|
| Returns: |
-
IndType
–
Instance or Solution: New individual
|
Source code in digneapy/operators/crossover/opoint.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 | def __call__(self, individual: IndType, other: IndType) -> IndType:
"""One point crossover
Args:
individual Instance or Solution: First individual to apply crossover. Returned object
other Instance or Solution: Second individual to apply crossover
cxpb (float64, optional): Crossover probability. Not used in this operator.
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: When the len(ind_1) != len(ind_2)
Returns:
Instance or Solution: New individual
"""
if len(individual) != len(other):
msg = f"Individual of different length in uniform_crossover. len(ind) = {len(individual)} != len(other) = {len(other)}"
raise ValueError(msg)
offspring = individual.clone()
cross_point = self._rng.integers(low=0, high=len(individual))
offspring[cross_point:] = other[cross_point:]
return offspring
|
Bases: Crossover
Source code in digneapy/operators/crossover/uniform.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | class UniformCrossover(Crossover):
def __init__(
self, cxpb: float = 0.5, seed: Optional[int | np.random.SeedSequence] = None
):
super().__init__(cxpb, seed)
def __call__(self, individual: IndType, other: IndType) -> IndType:
"""Uniform Crossover Operator for Instances and Solutions
Args:
individual (IndType): First individual to apply crossover. Returned object.
other (IndType): Second individual to apply crossover
cxpb (float64, optional): Crossover probability. Defaults to 0.5.
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: When the len(ind_1) != len(ind_2)
Returns:
ndarray: New individual
"""
if len(individual) != len(other):
msg = f"Individual of different length in uniform_crossover. len(ind) = {len(individual)} != len(other) = {len(other)}"
raise ValueError(msg)
cloned = individual.clone()
probs = self._rng.random(size=len(individual))
genotype = np.empty_like(individual)
genotype = np.where(probs <= self._cxpb, individual, other)
cloned.variables = genotype
return cloned
|
Uniform Crossover Operator for Instances and Solutions
| Parameters: |
-
individual
(IndType)
–
First individual to apply crossover. Returned object.
-
other
(IndType)
–
Second individual to apply crossover
-
cxpb
(float64)
–
Crossover probability. Defaults to 0.5.
-
seed
(Optional[int | SeedSequence])
–
Seed for the random number generator. Defaults to None.
|
| Raises: |
-
ValueError
–
When the len(ind_1) != len(ind_2)
|
Source code in digneapy/operators/crossover/uniform.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | def __call__(self, individual: IndType, other: IndType) -> IndType:
"""Uniform Crossover Operator for Instances and Solutions
Args:
individual (IndType): First individual to apply crossover. Returned object.
other (IndType): Second individual to apply crossover
cxpb (float64, optional): Crossover probability. Defaults to 0.5.
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: When the len(ind_1) != len(ind_2)
Returns:
ndarray: New individual
"""
if len(individual) != len(other):
msg = f"Individual of different length in uniform_crossover. len(ind) = {len(individual)} != len(other) = {len(other)}"
raise ValueError(msg)
cloned = individual.clone()
probs = self._rng.random(size=len(individual))
genotype = np.empty_like(individual)
genotype = np.where(probs <= self._cxpb, individual, other)
cloned.variables = genotype
return cloned
|
Bases: Mutation
Source code in digneapy/operators/mutation/uniform.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | class UniformMutation(Mutation):
def __init__(self, seed: Optional[int | np.random.SeedSequence] = None):
super().__init__(seed)
def __call__(self, individual: IndType, lb: np.ndarray, ub: np.ndarray) -> IndType:
"""Performs Uniform One Mutation on Instances and Solution objects.
Args:
individual (IndType): Instance or Solution to mutate.
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: If bouns != dimension
Returns:
IndType: Newly mutated individual
"""
if len(lb) != len(ub) or len(individual) != len(lb):
msg = f"The size of individual ({len(individual)}) and bounds {len(lb)} is different in uniform_one_mutation."
raise ValueError(msg)
mutation_point = self._rng.integers(low=0, high=len(individual))
new_value = self._rng.uniform(low=lb[mutation_point], high=ub[mutation_point])
individual[mutation_point] = new_value
return individual
|
Performs Uniform One Mutation on Instances and Solution objects.
| Parameters: |
-
individual
(IndType)
–
Instance or Solution to mutate.
-
lb
(ndarray)
–
Lower bound for each dimension
-
ub
(ndarray)
–
Upper bound for each dimension
-
seed
(Optional[int | SeedSequence])
–
Seed for the random number generator. Defaults to None.
|
Source code in digneapy/operators/mutation/uniform.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | def __call__(self, individual: IndType, lb: np.ndarray, ub: np.ndarray) -> IndType:
"""Performs Uniform One Mutation on Instances and Solution objects.
Args:
individual (IndType): Instance or Solution to mutate.
lb (np.ndarray): Lower bound for each dimension
ub (np.ndarray): Upper bound for each dimension
seed (Optional[int | np.random.SeedSequence], optional): Seed for the random number generator. Defaults to None.
Raises:
ValueError: If bouns != dimension
Returns:
IndType: Newly mutated individual
"""
if len(lb) != len(ub) or len(individual) != len(lb):
msg = f"The size of individual ({len(individual)}) and bounds {len(lb)} is different in uniform_one_mutation."
raise ValueError(msg)
mutation_point = self._rng.integers(low=0, high=len(individual))
new_value = self._rng.uniform(low=lb[mutation_point], high=ub[mutation_point])
individual[mutation_point] = new_value
return individual
|