@File : heuristics.py @Time : 2024/4/11 11:14:36 @Author : Alejandro Marrero @Version : 1.0 @Contact : amarrerd@ull.edu.es @License : (C)Copyright 2024, Alejandro Marrero @Desc : None

EA

Bases: Solver, SupportsSolve[P], RNG

Evolutionary Algorithm from DEAP for digneapy

Source code in digneapy/solvers/evolutionary.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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
class EA(Solver, SupportsSolve[P], RNG):
    """Evolutionary Algorithm from DEAP for digneapy"""

    def __init__(
        self,
        direction: Direction,
        dim: int,
        min_g: int | float,
        max_g: int | float,
        cx=tools.cxUniform,
        mut=tools.mutUniformInt,
        pop_size: int = 10,
        cxpb: float = 0.6,
        mutpb: float = 0.3,
        generations: int = 500,
        n_cores: int = 1,
        seed: int = 42,
    ):
        """Creates a new EA instance with the given parameters.
        Args:
            dir (str): Direction of the evolution process. Min (minimisation) or Max (maximisation).
            dim (int): Number of variables of the problem to solve.
            min_g (int | float): Minimum value of the genome of the solutions.
            max_g (int | float): Maximum value of the genome of the solutions.
            pop_size (int, optional): Population size of the evolutionary algorithm. Defaults to 10.
            cxpb (float, optional): Crossover probability. Defaults to 0.6.
            mutpb (float, optional): Mutation probability. Defaults to 0.3.
            generations (int, optional): Number of generations to perform. Defaults to 500.

        Raises:
            TypeError: If direction is not in digneapy.solvers.DIRECTIONS
        """
        if not isinstance(direction, Direction):
            raise TypeError(
                f"Direction not allowed. Please use a value of the class Direction({Direction.values()})"
            )

        self.direction = direction
        self._cx = cx
        self._mut = mut
        self._pop_size = pop_size
        self._cxpb = cxpb
        self._mutpb = mutpb
        self._generations = generations
        self._n_cores = n_cores if n_cores > 1 else 1
        self._toolbox = base.Toolbox()
        self.initialize_rng(seed=seed)
        if direction == Direction.MINIMISE:
            self._toolbox.register(
                "individual",
                _gen_dignea_ind,
                creator.IndMin,
                self._rng,
                dim,
                min_g,
                max_g,
            )

        else:
            self._toolbox.register(
                "individual",
                _gen_dignea_ind,
                creator.IndMax,
                self._rng,
                dim,
                min_g,
                max_g,
            )

        self._toolbox.register(
            "population", tools.initRepeat, list, self._toolbox.individual
        )
        self._toolbox.register("mate", cx, indpb=0.5)
        self._toolbox.register("mutate", mut, low=min_g, up=max_g, indpb=(1.0 / dim))
        self._toolbox.register("select", tools.selTournament, tournsize=2)

        self._stats = tools.Statistics(key=lambda ind: ind.fitness.values)
        self._stats.register("avg", np.mean)
        self._stats.register("std", np.std)
        self._stats.register("min", np.min)
        self._stats.register("max", np.max)

        self._logbook = None
        self._best_found = Solution()

        self._name = f"EA_PS_{self._pop_size}_CXPB_{self._cxpb}_MUTPB_{self._mutpb}"
        self.__name__ = self._name

        if self._n_cores > 1:
            self._pool = ThreadPoolExecutor(max_workers=self._n_cores)
            self._toolbox.register("map", self._pool.map)

    def __call__(self, problem: P, *args, **kwargs) -> list[Solution]:
        """Call method of the EA solver. It runs the EA to solve the OptProblem given.

        Returns:
            Population (list[Solution]): Final population of the algorithm with the best individual found.
        """
        if problem is None:
            msg = "Problem is None in EA.__call__()"
            raise ValueError(msg)

        self._toolbox.register("evaluate", problem)
        # Reset the algorithm
        self._population = self._toolbox.population(n=self._pop_size)
        self._hof = tools.HallOfFame(1)
        self._logbook = None

        self._population, self._logbook = algorithms.eaSimple(
            self._population,
            self._toolbox,
            cxpb=self._cxpb,
            mutpb=self._mutpb,
            ngen=self._generations,
            halloffame=self._hof,
            stats=self._stats,
            verbose=False,
        )

        # Convert to Solution class
        cast_pop = [
            Solution(
                variables=i,
                objectives=(i.fitness.values[0],),
                fitness=i.fitness.values[0],
            )
            for i in self._population
        ]
        self._population = cast_pop
        self._best_found = Solution(
            variables=self._hof[0],
            objectives=(self._hof[0].fitness.values[0],),
            fitness=self._hof[0].fitness.values[0],
        )
        return [self._best_found, *cast_pop]

__call__(problem, *args, **kwargs)

Call method of the EA solver. It runs the EA to solve the OptProblem given.

Returns:
  • Population( list[Solution] ) –

    Final population of the algorithm with the best individual found.

Source code in digneapy/solvers/evolutionary.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def __call__(self, problem: P, *args, **kwargs) -> list[Solution]:
    """Call method of the EA solver. It runs the EA to solve the OptProblem given.

    Returns:
        Population (list[Solution]): Final population of the algorithm with the best individual found.
    """
    if problem is None:
        msg = "Problem is None in EA.__call__()"
        raise ValueError(msg)

    self._toolbox.register("evaluate", problem)
    # Reset the algorithm
    self._population = self._toolbox.population(n=self._pop_size)
    self._hof = tools.HallOfFame(1)
    self._logbook = None

    self._population, self._logbook = algorithms.eaSimple(
        self._population,
        self._toolbox,
        cxpb=self._cxpb,
        mutpb=self._mutpb,
        ngen=self._generations,
        halloffame=self._hof,
        stats=self._stats,
        verbose=False,
    )

    # Convert to Solution class
    cast_pop = [
        Solution(
            variables=i,
            objectives=(i.fitness.values[0],),
            fitness=i.fitness.values[0],
        )
        for i in self._population
    ]
    self._population = cast_pop
    self._best_found = Solution(
        variables=self._hof[0],
        objectives=(self._hof[0].fitness.values[0],),
        fitness=self._hof[0].fitness.values[0],
    )
    return [self._best_found, *cast_pop]

__init__(direction, dim, min_g, max_g, cx=tools.cxUniform, mut=tools.mutUniformInt, pop_size=10, cxpb=0.6, mutpb=0.3, generations=500, n_cores=1, seed=42)

Creates a new EA instance with the given parameters. Args: dir (str): Direction of the evolution process. Min (minimisation) or Max (maximisation). dim (int): Number of variables of the problem to solve. min_g (int | float): Minimum value of the genome of the solutions. max_g (int | float): Maximum value of the genome of the solutions. pop_size (int, optional): Population size of the evolutionary algorithm. Defaults to 10. cxpb (float, optional): Crossover probability. Defaults to 0.6. mutpb (float, optional): Mutation probability. Defaults to 0.3. generations (int, optional): Number of generations to perform. Defaults to 500.

Raises:
  • TypeError

    If direction is not in digneapy.solvers.DIRECTIONS

Source code in digneapy/solvers/evolutionary.py
 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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def __init__(
    self,
    direction: Direction,
    dim: int,
    min_g: int | float,
    max_g: int | float,
    cx=tools.cxUniform,
    mut=tools.mutUniformInt,
    pop_size: int = 10,
    cxpb: float = 0.6,
    mutpb: float = 0.3,
    generations: int = 500,
    n_cores: int = 1,
    seed: int = 42,
):
    """Creates a new EA instance with the given parameters.
    Args:
        dir (str): Direction of the evolution process. Min (minimisation) or Max (maximisation).
        dim (int): Number of variables of the problem to solve.
        min_g (int | float): Minimum value of the genome of the solutions.
        max_g (int | float): Maximum value of the genome of the solutions.
        pop_size (int, optional): Population size of the evolutionary algorithm. Defaults to 10.
        cxpb (float, optional): Crossover probability. Defaults to 0.6.
        mutpb (float, optional): Mutation probability. Defaults to 0.3.
        generations (int, optional): Number of generations to perform. Defaults to 500.

    Raises:
        TypeError: If direction is not in digneapy.solvers.DIRECTIONS
    """
    if not isinstance(direction, Direction):
        raise TypeError(
            f"Direction not allowed. Please use a value of the class Direction({Direction.values()})"
        )

    self.direction = direction
    self._cx = cx
    self._mut = mut
    self._pop_size = pop_size
    self._cxpb = cxpb
    self._mutpb = mutpb
    self._generations = generations
    self._n_cores = n_cores if n_cores > 1 else 1
    self._toolbox = base.Toolbox()
    self.initialize_rng(seed=seed)
    if direction == Direction.MINIMISE:
        self._toolbox.register(
            "individual",
            _gen_dignea_ind,
            creator.IndMin,
            self._rng,
            dim,
            min_g,
            max_g,
        )

    else:
        self._toolbox.register(
            "individual",
            _gen_dignea_ind,
            creator.IndMax,
            self._rng,
            dim,
            min_g,
            max_g,
        )

    self._toolbox.register(
        "population", tools.initRepeat, list, self._toolbox.individual
    )
    self._toolbox.register("mate", cx, indpb=0.5)
    self._toolbox.register("mutate", mut, low=min_g, up=max_g, indpb=(1.0 / dim))
    self._toolbox.register("select", tools.selTournament, tournsize=2)

    self._stats = tools.Statistics(key=lambda ind: ind.fitness.values)
    self._stats.register("avg", np.mean)
    self._stats.register("std", np.std)
    self._stats.register("min", np.min)
    self._stats.register("max", np.max)

    self._logbook = None
    self._best_found = Solution()

    self._name = f"EA_PS_{self._pop_size}_CXPB_{self._cxpb}_MUTPB_{self._mutpb}"
    self.__name__ = self._name

    if self._n_cores > 1:
        self._pool = ThreadPoolExecutor(max_workers=self._n_cores)
        self._toolbox.register("map", self._pool.map)