From d704780c618f9a04fd4f844a03dad88784c14842 Mon Sep 17 00:00:00 2001 From: Oleg Petrov Date: Tue, 12 Mar 2024 15:47:54 +0300 Subject: [PATCH] add operations history --- gefest/core/configs/optimization_params.py | 11 ++++++++++- gefest/core/geometry/datastructs/point.py | 3 +++ gefest/core/geometry/datastructs/polygon.py | 3 +++ gefest/core/geometry/datastructs/structure.py | 17 ++++++++++++++++- gefest/core/opt/operators/crossovers.py | 14 ++++++++++---- gefest/core/opt/operators/mutations.py | 6 +++++- gefest/tools/optimizers/GA/GA.py | 4 +++- 7 files changed, 50 insertions(+), 8 deletions(-) diff --git a/gefest/core/configs/optimization_params.py b/gefest/core/configs/optimization_params.py index 88f161780..131f38e16 100644 --- a/gefest/core/configs/optimization_params.py +++ b/gefest/core/configs/optimization_params.py @@ -5,6 +5,7 @@ from golem.core.optimisers.adaptive.operator_agent import MutationAgentTypeEnum from golem.core.optimisers.genetic.operators.inheritance import GeneticSchemeTypesEnum from golem.core.optimisers.genetic.operators.selection import SelectionTypesEnum +from loguru import logger from pydantic import BaseModel, ConfigDict, model_validator from gefest.core.configs.tuner_params import TunerParams @@ -96,7 +97,7 @@ class OptimizationParams(BaseModel): moead_multi_objective_selector_neighbors: int = 2 """Parameter used in moead selector.""" - optimizer: ValidOptimizer = 'gefest_ga' + optimizer: Optional[ValidOptimizer] = 'gefest_ga' """Optimizer.""" pair_selector: Callable = panmixis @@ -207,6 +208,14 @@ def create_classes_instances(self): """Selects and initializes specified modules.""" if isinstance(self.optimizer, str): self.optimizer = getattr(OptimizerTypes, self.optimizer).value + elif self.optimizer is None: + logger.warning('Optimizer not provided.') + + if len(self.crossover_each_prob) != len(self.crossovers): + raise ValueError('Mismatch number of crossovers and probs.') + + if len(self.mutation_each_prob) != len(self.mutations): + raise ValueError('Mismatch number of mutations and probs.') if isinstance(self.postprocess_rules[0], str): self.postprocess_rules = [getattr(Rules, name).value for name in self.postprocess_rules] diff --git a/gefest/core/geometry/datastructs/point.py b/gefest/core/geometry/datastructs/point.py index 813727b05..4e16f7436 100644 --- a/gefest/core/geometry/datastructs/point.py +++ b/gefest/core/geometry/datastructs/point.py @@ -9,6 +9,9 @@ class Point: x: float y: float + def __hash__(self) -> int: + return hash((self.x, self.y)) + @computed_field(repr=False) def coords(self) -> list[float]: """List coordinates representation.""" diff --git a/gefest/core/geometry/datastructs/polygon.py b/gefest/core/geometry/datastructs/polygon.py index 60052addd..ab08bfde9 100644 --- a/gefest/core/geometry/datastructs/polygon.py +++ b/gefest/core/geometry/datastructs/polygon.py @@ -27,6 +27,9 @@ class Polygon: points: list[Point] = Field(default_factory=list) id_: Optional[Union[UUID, PolyID]] = Field(default_factory=uuid4) + def __hash__(self) -> int: + return hash((self.id_, *self.points)) + def __len__(self) -> int: return len(self.points) diff --git a/gefest/core/geometry/datastructs/structure.py b/gefest/core/geometry/datastructs/structure.py index b34882d4d..6e179fa6b 100644 --- a/gefest/core/geometry/datastructs/structure.py +++ b/gefest/core/geometry/datastructs/structure.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Any, Union from uuid import UUID, uuid4 from pydantic import Field @@ -17,6 +17,9 @@ class Structure: extra_characteristics: dict = Field(default_factory=dict) id_: UUID = Field(default_factory=uuid4) + def __hash__(self) -> int: + return hash((self.id_, *self.polygons, *self.fitness)) + def __len__(self): return len(self.polygons) @@ -35,6 +38,7 @@ def __setitem__(self, key, value): if polygons[key] != value: polygons[key] = value self.polygons = tuple(polygons) + self.id_ = uuid4() def __getitem__(self, key): return self.polygons[key] @@ -57,3 +61,14 @@ def remove(self, value): polygons = list(self.polygons) polygons.remove(value) self.polygons = tuple(polygons) + + def set_extra(self, field: str, data: Any): + """Add any extra information.""" + if field in self.extra_characteristics.keys(): + self.extra_characteristics[field].append(data) + else: + self.extra_characteristics[field] = [data] + + def clear_extra(self): + """Clear all extra information.""" + self.extra_characteristics = {} diff --git a/gefest/core/opt/operators/crossovers.py b/gefest/core/opt/operators/crossovers.py index 6f8403ac1..b938d055f 100644 --- a/gefest/core/opt/operators/crossovers.py +++ b/gefest/core/opt/operators/crossovers.py @@ -41,11 +41,16 @@ def crossover_structures( size=1, p=operations_probs, ) - new_structure = chosen_crossover[0](s1, s2, domain) - if not new_structure: + new_structures = chosen_crossover[0](s1, s2, domain) + if not new_structures: logger.warning(f'None out: {chosen_crossover[0].__name__}') + else: + for child in new_structures: + child.clear_extra() + child.set_extra('crossover', (chosen_crossover[0].__name__)) + child.set_extra('parents', (structure1.id_, structure2.id_)) - return new_structure + return new_structures # pairs for crossover selection @@ -83,7 +88,7 @@ def structure_level_crossover( result = list(copy.deepcopy(part_1)) result.extend(copy.deepcopy(part_2)) - new_structure = Structure(polygons=result) + new_structure = Structure(polygons=result, parent='structure_level_crossover') return (new_structure,) @@ -142,6 +147,7 @@ def polygon_level_crossover( idx_ = where(s1.polygons, lambda p: p == poly_1)[0] s1[idx_] = new_poly + s1.parent = 'polygon_level_crossover' return (s1,) diff --git a/gefest/core/opt/operators/mutations.py b/gefest/core/opt/operators/mutations.py index 52d0966ed..2f27655c1 100644 --- a/gefest/core/opt/operators/mutations.py +++ b/gefest/core/opt/operators/mutations.py @@ -47,10 +47,14 @@ def mutate_structure( size=1, p=operations_probs, ) - new_structure = chosen_mutation[0](new_structure, domain, idx_) + new_structure = chosen_mutation[0](copy.deepcopy(new_structure), domain, idx_) + if not new_structure: logger.warning(f'None out: {chosen_mutation[0].__name__}') + else: + new_structure.set_extra('mutations', (idx_, chosen_mutation[0].__name__)) + new_structure.set_extra('parents', structure.id_) return new_structure diff --git a/gefest/tools/optimizers/GA/GA.py b/gefest/tools/optimizers/GA/GA.py index 517e5b62d..0ce2f322b 100644 --- a/gefest/tools/optimizers/GA/GA.py +++ b/gefest/tools/optimizers/GA/GA.py @@ -47,7 +47,7 @@ def __init__( if len(self.opt_params.objectives) > 1: if self.opt_params.multiobjective_selector.__name__ == 'MOEAD': self.opt_params.extra = 0 - logger.warning('For moead extra not available.') + logger.warning('Extra not available for moead.') self.selector = self.opt_params.multiobjective_selector( single_demention_selection=self.selector, @@ -72,8 +72,10 @@ def optimize(self) -> list[Structure]: mutated_child = self.mutation(child) self._pop.extend(mutated_child) self._pop.extend(self.sampler(self.opt_params.extra)) + self._pop = list(set(self._pop)) self._pop = self.objectives_evaluator(self._pop) self.log_dispatcher.log_pop(self._pop, str(step + 1)) + self._pop = self.selector(self._pop, self.pop_size) pbar.set_description(f'Best fitness: {self._pop[0].fitness}') return self._pop