From 1454d630f2de4d9dc82df92314b8164fdb53c121 Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Tue, 7 Nov 2023 17:41:32 +0300 Subject: [PATCH 1/9] Correct condition --- .../optimisers/genetic/operators/mutation.py | 90 +++++++++---------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index 2005377e..e34997c7 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -81,43 +81,42 @@ def __call__(self, population: Union[Individual, PopulationT]) -> Union[Individu if isinstance(population, Individual): population = [population] - final_population, mutations_applied, application_attempts = tuple(zip(*map(self._mutation, population))) + final_population, application_attempts = tuple(zip(*map(self._mutation, population))) # drop individuals to which mutations could not be applied final_population = [ind for ind, init_ind, attempt in zip(final_population, population, application_attempts) - if not attempt or ind.graph != init_ind.graph] + if not(attempt and ind.graph == init_ind.graph)] if len(population) == 1: return final_population[0] if final_population else final_population return final_population - def _mutation(self, individual: Individual) -> Tuple[Individual, Optional[MutationIdType], bool]: + def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: """ Function applies mutation operator to graph """ - application_attempt = False - mutation_applied = None - for _ in range(self.parameters.max_num_of_operator_attempts): - new_graph = deepcopy(individual.graph) - - new_graph, mutation_applied = self._apply_mutations(new_graph) - if mutation_applied is None: - continue - application_attempt = True - is_correct_graph = self.graph_generation_params.verifier(new_graph) - if is_correct_graph: - parent_operator = ParentOperator(type_='mutation', - operators=mutation_applied, - parent_individuals=individual) - individual = Individual(new_graph, parent_operator, - metadata=self.requirements.static_individual_metadata) - break + graph = deepcopy(individual.graph) + mutation_type = self._operator_agent.choose_action(graph) + is_applied = self._will_mutation_be_applied(mutation_type) + if is_applied: + for _ in range(self.parameters.max_num_of_operator_attempts): + new_graph = deepcopy(individual.graph) + + new_graph = self._apply_mutations(new_graph, mutation_type) + is_correct_graph = self.graph_generation_params.verifier(new_graph) + if is_correct_graph and graph != new_graph: + parent_operator = ParentOperator(type_='mutation', + operators=mutation_type, + parent_individuals=individual) + individual = Individual(new_graph, parent_operator, + metadata=self.requirements.static_individual_metadata) + break else: # Collect invalid actions - self.agent_experience.collect_experience(individual, mutation_applied, reward=-1.0) - else: - self.log.debug('Number of mutation attempts exceeded. ' - 'Please check optimization parameters for correctness.') - return individual, mutation_applied, application_attempt + self.agent_experience.collect_experience(individual, mutation_type, reward=-1.0) + + self.log.debug(f'Number of attempts for {mutation_type} mutation application exceeded. ' + 'Please check optimization parameters for correctness.') + return individual, is_applied def _sample_num_of_mutations(self) -> int: # most of the time returns 1 or rarely several mutations @@ -127,33 +126,28 @@ def _sample_num_of_mutations(self) -> int: num_mut = 1 return num_mut - def _apply_mutations(self, new_graph: Graph) -> Tuple[Graph, Optional[MutationIdType]]: + def _apply_mutations(self, new_graph: Graph, mutation_type: MutationIdType) -> Graph: """Apply mutation 1 or few times iteratively""" - mutation_type = self._operator_agent.choose_action(new_graph) - mutation_applied = None for _ in range(self._sample_num_of_mutations()): - new_graph, applied = self._adapt_and_apply_mutation(new_graph, mutation_type) - if applied: - mutation_applied = mutation_type - is_custom_mutation = isinstance(mutation_type, Callable) - if is_custom_mutation: # custom mutation occurs once - break - return new_graph, mutation_applied - - def _adapt_and_apply_mutation(self, new_graph: Graph, mutation_type) -> Tuple[Graph, bool]: - applied = self._will_mutation_be_applied(mutation_type) - if applied: - # get the mutation function and adapt it - mutation_func = self._get_mutation_func(mutation_type) - new_graph = mutation_func(new_graph, requirements=self.requirements, - graph_gen_params=self.graph_generation_params, - parameters=self.parameters) - return new_graph, applied - - def _will_mutation_be_applied(self, mutation_type: Union[MutationTypesEnum, Callable]) -> bool: + new_graph = self._adapt_and_apply_mutation(new_graph, mutation_type) + + is_custom_mutation = isinstance(mutation_type, Callable) + if is_custom_mutation: # custom mutation occurs once + break + return new_graph + + def _adapt_and_apply_mutation(self, new_graph: Graph, mutation_type: MutationIdType) -> Graph: + # get the mutation function and adapt it + mutation_func = self._get_mutation_func(mutation_type) + new_graph = mutation_func(new_graph, requirements=self.requirements, + graph_gen_params=self.graph_generation_params, + parameters=self.parameters) + return new_graph + + def _will_mutation_be_applied(self, mutation_type: MutationIdType) -> bool: return random() <= self.parameters.mutation_prob and mutation_type is not MutationTypesEnum.none - def _get_mutation_func(self, mutation_type: Union[MutationTypesEnum, Callable]) -> Callable: + def _get_mutation_func(self, mutation_type: MutationIdType) -> Callable: if isinstance(mutation_type, Callable): mutation_func = mutation_type else: From a55dfe926e9687d22cd7a29894c059156de9abf9 Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Wed, 8 Nov 2023 12:32:51 +0300 Subject: [PATCH 2/9] Fix min mutation proba --- golem/core/optimisers/genetic/parameters/mutation_prob.py | 2 +- test/integration/test_genetic_schemes.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/golem/core/optimisers/genetic/parameters/mutation_prob.py b/golem/core/optimisers/genetic/parameters/mutation_prob.py index 2ea40c5c..fc8a2096 100644 --- a/golem/core/optimisers/genetic/parameters/mutation_prob.py +++ b/golem/core/optimisers/genetic/parameters/mutation_prob.py @@ -9,7 +9,7 @@ class AdaptiveMutationProb(AdaptiveParameter[float]): def __init__(self, default_prob: float = 0.5): self._current_std = 0. self._max_std = 0. - self._min_proba = 0.05 + self._min_proba = 0.1 self._default_prob = default_prob @property diff --git a/test/integration/test_genetic_schemes.py b/test/integration/test_genetic_schemes.py index f68b971b..d7391085 100644 --- a/test/integration/test_genetic_schemes.py +++ b/test/integration/test_genetic_schemes.py @@ -36,7 +36,8 @@ def test_genetic_scheme_types(genetic_type): num_iterations=num_iterations) assert found_graph is not None # at least 20% more generation than early_stopping_iterations were evaluated - assert history.generations_count >= num_iterations // 3 * 1.2 + # (+2 gen for initial assumption and final choice) + assert history.generations_count >= num_iterations // 3 * 1.2 + 2 # metric improved assert np.mean([ind.fitness.value for ind in history.generations[0].data]) > \ np.mean([ind.fitness.value for ind in history.generations[-1].data]) From dc9000bd79a92869c3cd11400b5aafb4d2a41959 Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Tue, 28 Nov 2023 14:19:30 +0300 Subject: [PATCH 3/9] Review fixes --- .../optimisers/genetic/operators/mutation.py | 24 +++++++------------ test/integration/test_genetic_schemes.py | 16 +++++++------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index e34997c7..9f47f8b1 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -118,9 +118,10 @@ def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: 'Please check optimization parameters for correctness.') return individual, is_applied - def _sample_num_of_mutations(self) -> int: + def _sample_num_of_mutations(self, mutation_type: MutationIdType) -> int: # most of the time returns 1 or rarely several mutations - if self.parameters.variable_mutation_num: + is_custom_mutation = isinstance(mutation_type, Callable) + if self.parameters.variable_mutation_num and not is_custom_mutation: num_mut = max(int(round(np.random.lognormal(0, sigma=0.5))), 1) else: num_mut = 1 @@ -128,20 +129,11 @@ def _sample_num_of_mutations(self) -> int: def _apply_mutations(self, new_graph: Graph, mutation_type: MutationIdType) -> Graph: """Apply mutation 1 or few times iteratively""" - for _ in range(self._sample_num_of_mutations()): - new_graph = self._adapt_and_apply_mutation(new_graph, mutation_type) - - is_custom_mutation = isinstance(mutation_type, Callable) - if is_custom_mutation: # custom mutation occurs once - break - return new_graph - - def _adapt_and_apply_mutation(self, new_graph: Graph, mutation_type: MutationIdType) -> Graph: - # get the mutation function and adapt it - mutation_func = self._get_mutation_func(mutation_type) - new_graph = mutation_func(new_graph, requirements=self.requirements, - graph_gen_params=self.graph_generation_params, - parameters=self.parameters) + for _ in range(self._sample_num_of_mutations(mutation_type)): + mutation_func = self._get_mutation_func(mutation_type) + new_graph = mutation_func(new_graph, requirements=self.requirements, + graph_gen_params=self.graph_generation_params, + parameters=self.parameters) return new_graph def _will_mutation_be_applied(self, mutation_type: MutationIdType) -> bool: diff --git a/test/integration/test_genetic_schemes.py b/test/integration/test_genetic_schemes.py index d7391085..f720ae5c 100644 --- a/test/integration/test_genetic_schemes.py +++ b/test/integration/test_genetic_schemes.py @@ -1,9 +1,8 @@ -from functools import partial +from typing import Sequence import numpy as np import pytest -from examples.synthetic_graph_evolution.experiment_setup import run_trial from examples.synthetic_graph_evolution.generators import generate_labeled_graph from examples.synthetic_graph_evolution.tree_search import tree_search_setup from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters @@ -17,7 +16,7 @@ def set_up_params(genetic_scheme: GeneticSchemeTypesEnum): multi_objective=False, mutation_types=[ MutationTypesEnum.single_add, - MutationTypesEnum.single_drop, + MutationTypesEnum.single_drop ], crossover_types=[CrossoverTypesEnum.none], genetic_scheme_type=genetic_scheme @@ -27,13 +26,16 @@ def set_up_params(genetic_scheme: GeneticSchemeTypesEnum): @pytest.mark.parametrize('genetic_type', GeneticSchemeTypesEnum) def test_genetic_scheme_types(genetic_type): - target_graph = generate_labeled_graph('tree', 4, node_labels=['x']) + target_graph = generate_labeled_graph('tree', 30, node_labels=['x']) num_iterations = 30 gp_params = set_up_params(genetic_type) - found_graph, history = run_trial(target_graph=target_graph, - optimizer_setup=partial(tree_search_setup, algorithm_parameters=gp_params), - num_iterations=num_iterations) + optimizer, objective = tree_search_setup(target_graph, + num_iterations=num_iterations, + algorithm_parameters=gp_params) + found_graphs = optimizer.optimise(objective) + found_graph = found_graphs[0] if isinstance(found_graphs, Sequence) else found_graphs + history = optimizer.history assert found_graph is not None # at least 20% more generation than early_stopping_iterations were evaluated # (+2 gen for initial assumption and final choice) From 156567c5e770b37f60b3339f77561e3ed27384fd Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Mon, 4 Dec 2023 16:37:19 +0300 Subject: [PATCH 4/9] Fix typing --- golem/core/optimisers/genetic/operators/mutation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index 9f47f8b1..33dfe883 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -118,7 +118,7 @@ def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: 'Please check optimization parameters for correctness.') return individual, is_applied - def _sample_num_of_mutations(self, mutation_type: MutationIdType) -> int: + def _sample_num_of_mutations(self, mutation_type: Union[MutationTypesEnum, Callable]) -> int: # most of the time returns 1 or rarely several mutations is_custom_mutation = isinstance(mutation_type, Callable) if self.parameters.variable_mutation_num and not is_custom_mutation: @@ -127,7 +127,7 @@ def _sample_num_of_mutations(self, mutation_type: MutationIdType) -> int: num_mut = 1 return num_mut - def _apply_mutations(self, new_graph: Graph, mutation_type: MutationIdType) -> Graph: + def _apply_mutations(self, new_graph: Graph, mutation_type: Union[MutationTypesEnum, Callable]) -> Graph: """Apply mutation 1 or few times iteratively""" for _ in range(self._sample_num_of_mutations(mutation_type)): mutation_func = self._get_mutation_func(mutation_type) @@ -136,10 +136,10 @@ def _apply_mutations(self, new_graph: Graph, mutation_type: MutationIdType) -> G parameters=self.parameters) return new_graph - def _will_mutation_be_applied(self, mutation_type: MutationIdType) -> bool: + def _will_mutation_be_applied(self, mutation_type: Union[MutationTypesEnum, Callable]) -> bool: return random() <= self.parameters.mutation_prob and mutation_type is not MutationTypesEnum.none - def _get_mutation_func(self, mutation_type: MutationIdType) -> Callable: + def _get_mutation_func(self, mutation_type: Union[MutationTypesEnum, Callable]) -> Callable: if isinstance(mutation_type, Callable): mutation_func = mutation_type else: From a365f11a0fece274f13fdbfa2d95e3a2588230aa Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Fri, 8 Dec 2023 12:52:40 +0300 Subject: [PATCH 5/9] Fix mutation retries --- .../genetic/operators/base_mutations.py | 184 ++++++++++-------- .../optimisers/genetic/operators/mutation.py | 2 +- golem/core/optimisers/random_graph_factory.py | 2 +- .../optimizers/gp_operators/test_mutation.py | 13 +- 4 files changed, 110 insertions(+), 91 deletions(-) diff --git a/golem/core/optimisers/genetic/operators/base_mutations.py b/golem/core/optimisers/genetic/operators/base_mutations.py index 9dc24d7f..1677359f 100644 --- a/golem/core/optimisers/genetic/operators/base_mutations.py +++ b/golem/core/optimisers/genetic/operators/base_mutations.py @@ -1,7 +1,10 @@ +from copy import deepcopy from functools import partial -from random import choice, randint, random, sample +from random import choice, randint, random, sample, shuffle from typing import TYPE_CHECKING, Optional +import numpy as np + from golem.core.adapter import register_native from golem.core.dag.graph import ReconnectType from golem.core.dag.graph_node import GraphNode @@ -69,7 +72,6 @@ def simple_mutation(graph: OptGraph, :param graph: graph to mutate """ - exchange_node = graph_gen_params.node_factory.exchange_node visited_nodes = set() @@ -138,56 +140,67 @@ def nodes_not_cycling(source_node: OptNode, target_node: OptNode): @register_native def add_intermediate_node(graph: OptGraph, - node_to_mutate: OptNode, node_factory: OptNodeFactory) -> OptGraph: - # add between node and parent - new_node = node_factory.get_parent_node(node_to_mutate, is_primary=False) - if not new_node: - return graph - - # rewire old children to new parent - new_node.nodes_from = node_to_mutate.nodes_from - node_to_mutate.nodes_from = [new_node] - - # add new node to graph - graph.add_node(new_node) + nodes_with_parents = [node for node in graph.nodes if node.nodes_from] + if len(nodes_with_parents) > 0: + shuffle(nodes_with_parents) + for node_to_mutate in nodes_with_parents: + # add between node and parent + new_node = node_factory.get_parent_node(node_to_mutate, is_primary=False) + if not new_node: + continue + + # rewire old children to new parent + new_node.nodes_from = node_to_mutate.nodes_from + node_to_mutate.nodes_from = [new_node] + + # add new node to graph + graph.add_node(new_node) + break return graph @register_native def add_separate_parent_node(graph: OptGraph, - node_to_mutate: OptNode, node_factory: OptNodeFactory) -> OptGraph: - # add as separate parent - new_node = node_factory.get_parent_node(node_to_mutate, is_primary=True) - if not new_node: - # there is no possible operators - return graph - if node_to_mutate.nodes_from: - node_to_mutate.nodes_from.append(new_node) - else: - node_to_mutate.nodes_from = [new_node] - graph.nodes.append(new_node) + node_idx = np.arange(len(graph.nodes)) + shuffle(node_idx) + for idx in node_idx: + node_to_mutate = graph.nodes[idx] + # add as separate parent + new_node = node_factory.get_parent_node(node_to_mutate, is_primary=True) + if not new_node: + # there is no possible operators + continue + if node_to_mutate.nodes_from: + node_to_mutate.nodes_from.append(new_node) + else: + node_to_mutate.nodes_from = [new_node] + graph.nodes.append(new_node) + break return graph @register_native def add_as_child(graph: OptGraph, - node_to_mutate: OptNode, node_factory: OptNodeFactory) -> OptGraph: - # add as child - old_node_children = graph.node_children(node_to_mutate) - new_node_child = choice(old_node_children) if old_node_children else None - new_node = node_factory.get_node(is_primary=False) - if not new_node: - return graph - graph.add_node(new_node) - graph.connect_nodes(node_parent=node_to_mutate, node_child=new_node) - if new_node_child: - graph.connect_nodes(node_parent=new_node, node_child=new_node_child) - graph.disconnect_nodes(node_parent=node_to_mutate, node_child=new_node_child, - clean_up_leftovers=True) - + node_idx = np.arange(len(graph.nodes)) + shuffle(node_idx) + for idx in node_idx: + node_to_mutate = graph.nodes[idx] + # add as child + old_node_children = graph.node_children(node_to_mutate) + new_node_child = choice(old_node_children) if old_node_children else None + new_node = node_factory.get_node(is_primary=False) + if not new_node: + continue + graph.add_node(new_node) + graph.connect_nodes(node_parent=node_to_mutate, node_child=new_node) + if new_node_child: + graph.connect_nodes(node_parent=new_node, node_child=new_node_child) + graph.disconnect_nodes(node_parent=node_to_mutate, node_child=new_node_child, + clean_up_leftovers=True) + break return graph @@ -202,20 +215,20 @@ def single_add_mutation(graph: OptGraph, :param graph: graph to mutate """ - if graph.depth >= requirements.max_depth: # add mutation is not possible return graph - node_to_mutate = choice(graph.nodes) - - single_add_strategies = [add_as_child, add_separate_parent_node] - if node_to_mutate.nodes_from: - single_add_strategies.append(add_intermediate_node) - strategy = choice(single_add_strategies) - - result = strategy(graph, node_to_mutate, graph_gen_params.node_factory) - return result + new_graph = deepcopy(graph) + single_add_strategies = [add_as_child, add_separate_parent_node, add_intermediate_node] + shuffle(single_add_strategies) + for strategy in single_add_strategies: + new_graph = strategy(new_graph, graph_gen_params.node_factory) + # maximum three equality check + if new_graph == graph: + continue + break + return new_graph @register_native @@ -229,11 +242,15 @@ def single_change_mutation(graph: OptGraph, :param graph: graph to mutate """ - node = choice(graph.nodes) - new_node = graph_gen_params.node_factory.exchange_node(node) - if not new_node: - return graph - graph.update_node(node, new_node) + node_idx = np.arange(len(graph.nodes)) + shuffle(node_idx) + for idx in node_idx: + node = graph.nodes[idx] + new_node = graph_gen_params.node_factory.exchange_node(node) + if not new_node: + continue + graph.update_node(node, new_node) + break return graph @@ -289,22 +306,27 @@ def tree_growth(graph: OptGraph, selected random node, if false then previous depth of selected node doesn't affect to new subtree depth, maximal depth of new subtree just should satisfy depth constraint in parent tree """ - node_from_graph = choice(graph.nodes) - if local_growth: - max_depth = distance_to_primary_level(node_from_graph) - is_primary_node_selected = (not node_from_graph.nodes_from) or (node_from_graph != graph.root_node and - randint(0, 1)) - else: - max_depth = requirements.max_depth - distance_to_root_level(graph, node_from_graph) - is_primary_node_selected = \ - distance_to_root_level(graph, node_from_graph) >= requirements.max_depth and randint(0, 1) - if is_primary_node_selected: - new_subtree = graph_gen_params.node_factory.get_node(is_primary=True) - if not new_subtree: - return graph - else: - new_subtree = graph_gen_params.random_graph_factory(requirements, max_depth).root_node - graph.update_subtree(node_from_graph, new_subtree) + node_idx = np.arange(len(graph.nodes)) + shuffle(node_idx) + for idx in node_idx: + node_from_graph = graph.nodes[idx] + if local_growth: + max_depth = distance_to_primary_level(node_from_graph) + is_primary_node_selected = (not node_from_graph.nodes_from) or (node_from_graph != graph.root_node and + randint(0, 1)) + else: + max_depth = requirements.max_depth - distance_to_root_level(graph, node_from_graph) + is_primary_node_selected = \ + distance_to_root_level(graph, node_from_graph) >= requirements.max_depth and randint(0, 1) + if is_primary_node_selected: + new_subtree = graph_gen_params.node_factory.get_node(is_primary=True) + if not new_subtree: + continue + else: + new_subtree = graph_gen_params.random_graph_factory(requirements, max_depth).root_node + + graph.update_subtree(node_from_graph, new_subtree) + break return graph @@ -349,16 +371,18 @@ def reduce_mutation(graph: OptGraph, return graph nodes = [node for node in graph.nodes if node is not graph.root_node] - node_to_del = choice(nodes) - children = graph.node_children(node_to_del) - is_possible_to_delete = all([len(child.nodes_from) - 1 >= requirements.min_arity for child in children]) - if is_possible_to_delete: - graph.delete_subtree(node_to_del) - else: - primary_node = graph_gen_params.node_factory.get_node(is_primary=True) - if not primary_node: - return graph - graph.update_subtree(node_to_del, primary_node) + shuffle(nodes) + for node_to_del in nodes: + children = graph.node_children(node_to_del) + is_possible_to_delete = all([len(child.nodes_from) - 1 >= requirements.min_arity for child in children]) + if is_possible_to_delete: + graph.delete_subtree(node_to_del) + else: + primary_node = graph_gen_params.node_factory.get_node(is_primary=True) + if not primary_node: + continue + graph.update_subtree(node_to_del, primary_node) + break return graph diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index 33dfe883..d93f815e 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -103,7 +103,7 @@ def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: new_graph = self._apply_mutations(new_graph, mutation_type) is_correct_graph = self.graph_generation_params.verifier(new_graph) - if is_correct_graph and graph != new_graph: + if is_correct_graph: parent_operator = ParentOperator(type_='mutation', operators=mutation_type, parent_individuals=individual) diff --git a/golem/core/optimisers/random_graph_factory.py b/golem/core/optimisers/random_graph_factory.py index 8d46e609..6b644f70 100644 --- a/golem/core/optimisers/random_graph_factory.py +++ b/golem/core/optimisers/random_graph_factory.py @@ -67,4 +67,4 @@ def graph_growth(graph: OptGraph, if not is_max_depth_exceeded: # lower proba of further growth reduces time of graph generation if choices([0, 1], weights=[1 - growth_proba, growth_proba])[0]: - graph_growth(graph, node, node_factory, requirements, max_depth, growth_proba) + graph_growth(graph, node, node_factory, requirements, max_depth, growth_proba / max_depth) diff --git a/test/unit/optimizers/gp_operators/test_mutation.py b/test/unit/optimizers/gp_operators/test_mutation.py index fe392c3a..d049a41e 100644 --- a/test/unit/optimizers/gp_operators/test_mutation.py +++ b/test/unit/optimizers/gp_operators/test_mutation.py @@ -79,13 +79,12 @@ def test_add_as_parent_node(graph): Test correctness of adding as a parent """ new_graph = deepcopy(graph) - node_to_mutate = new_graph.nodes[1] params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory - add_separate_parent_node(new_graph, node_to_mutate, node_factory) + add_separate_parent_node(new_graph, node_factory) - assert len(node_to_mutate.nodes_from) > len(graph.nodes[1].nodes_from) + assert new_graph.length > graph.length @pytest.mark.parametrize('graph', [simple_linear_graph(), tree_graph(), simple_cycled_graph()]) @@ -94,14 +93,12 @@ def test_add_as_child_node(graph): Test correctness of adding as a child """ new_graph = deepcopy(graph) - node_to_mutate = new_graph.nodes[1] params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory - add_as_child(new_graph, node_to_mutate, node_factory) + add_as_child(new_graph, node_factory) assert new_graph.length > graph.length - assert new_graph.node_children(node_to_mutate) != graph.node_children(node_to_mutate) @pytest.mark.parametrize('graph', [simple_linear_graph(), tree_graph(), simple_cycled_graph()]) @@ -110,14 +107,12 @@ def test_add_as_intermediate_node(graph): Test correctness of adding as an intermediate node """ new_graph = deepcopy(graph) - node_to_mutate = new_graph.nodes[1] params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory - add_intermediate_node(new_graph, node_to_mutate, node_factory) + add_intermediate_node(new_graph, node_factory) assert new_graph.length > graph.length - assert node_to_mutate.nodes_from[0] != graph.nodes[1].nodes_from[0] @pytest.mark.parametrize('graph', [simple_linear_graph(), tree_graph(), simple_cycled_graph()]) From daf56a67df33da02438fc9580c1d1185826add8b Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Fri, 8 Dec 2023 14:30:06 +0300 Subject: [PATCH 6/9] Fix random graph factory --- golem/core/optimisers/random_graph_factory.py | 2 +- test/unit/optimizers/test_random_graph_factory.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/golem/core/optimisers/random_graph_factory.py b/golem/core/optimisers/random_graph_factory.py index 6b644f70..8d46e609 100644 --- a/golem/core/optimisers/random_graph_factory.py +++ b/golem/core/optimisers/random_graph_factory.py @@ -67,4 +67,4 @@ def graph_growth(graph: OptGraph, if not is_max_depth_exceeded: # lower proba of further growth reduces time of graph generation if choices([0, 1], weights=[1 - growth_proba, growth_proba])[0]: - graph_growth(graph, node, node_factory, requirements, max_depth, growth_proba / max_depth) + graph_growth(graph, node, node_factory, requirements, max_depth, growth_proba) diff --git a/test/unit/optimizers/test_random_graph_factory.py b/test/unit/optimizers/test_random_graph_factory.py index 1acea237..67214144 100644 --- a/test/unit/optimizers/test_random_graph_factory.py +++ b/test/unit/optimizers/test_random_graph_factory.py @@ -11,7 +11,7 @@ from golem.core.optimisers.random_graph_factory import RandomGrowthGraphFactory -@pytest.mark.parametrize('max_depth', [1, 5, 10, 30]) +@pytest.mark.parametrize('max_depth', [1, 5, 10, 30, 50]) def test_gp_composer_random_graph_generation_looping(max_depth): """ Test checks DefaultRandomOptGraphFactory valid generation. """ available_node_types = ['a', 'b', 'c', 'd', 'e'] @@ -31,4 +31,4 @@ def test_gp_composer_random_graph_generation_looping(max_depth): assert verifier(graph) is True assert graph.depth <= requirements.max_depth # at least one graph has depth greater than a max_depth quarter - assert np.any([graph.depth >= math.ceil(max_depth / 4) for graph in graphs]) + assert np.any([graph.depth >= math.ceil(max_depth * 0.25) for graph in graphs]) From 0e07e0fd3b5692671a9b856f77dc16a04af52d94 Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Fri, 8 Dec 2023 15:04:12 +0300 Subject: [PATCH 7/9] Fix tests --- .../core/optimisers/genetic/operators/mutation.py | 3 +-- .../unit/optimizers/gp_operators/test_mutation.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index d93f815e..8c52e751 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -94,8 +94,7 @@ def __call__(self, population: Union[Individual, PopulationT]) -> Union[Individu def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: """ Function applies mutation operator to graph """ - graph = deepcopy(individual.graph) - mutation_type = self._operator_agent.choose_action(graph) + mutation_type = self._operator_agent.choose_action(individual.graph) is_applied = self._will_mutation_be_applied(mutation_type) if is_applied: for _ in range(self.parameters.max_num_of_operator_attempts): diff --git a/test/unit/optimizers/gp_operators/test_mutation.py b/test/unit/optimizers/gp_operators/test_mutation.py index d049a41e..f89bd757 100644 --- a/test/unit/optimizers/gp_operators/test_mutation.py +++ b/test/unit/optimizers/gp_operators/test_mutation.py @@ -82,8 +82,13 @@ def test_add_as_parent_node(graph): params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory + prev_nodes = new_graph.nodes[:] add_separate_parent_node(new_graph, node_factory) + new_nodes = [node for node in new_graph.nodes if node not in prev_nodes] + assert len(new_nodes) == 1 + assert not new_nodes[0].nodes_from + assert new_graph.node_children(new_nodes[0]) assert new_graph.length > graph.length @@ -96,8 +101,12 @@ def test_add_as_child_node(graph): params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory + prev_nodes = new_graph.nodes[:] add_as_child(new_graph, node_factory) + new_nodes = [node for node in new_graph.nodes if node not in prev_nodes] + assert len(new_nodes) == 1 + assert new_nodes[0].nodes_from assert new_graph.length > graph.length @@ -109,9 +118,13 @@ def test_add_as_intermediate_node(graph): new_graph = deepcopy(graph) params = get_mutation_params() node_factory = params['graph_gen_params'].node_factory - + prev_nodes = new_graph.nodes[:] add_intermediate_node(new_graph, node_factory) + new_nodes = [node for node in new_graph.nodes if node not in prev_nodes] + assert len(new_nodes) == 1 + assert new_nodes[0].nodes_from + assert new_graph.node_children(new_nodes[0]) assert new_graph.length > graph.length From 87351294f1bf5486b6574510f34f95f038c75129 Mon Sep 17 00:00:00 2001 From: Lyubov Yamshchikova Date: Fri, 8 Dec 2023 15:17:40 +0300 Subject: [PATCH 8/9] Minor --- test/unit/optimizers/test_random_graph_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/optimizers/test_random_graph_factory.py b/test/unit/optimizers/test_random_graph_factory.py index 67214144..44669c91 100644 --- a/test/unit/optimizers/test_random_graph_factory.py +++ b/test/unit/optimizers/test_random_graph_factory.py @@ -11,7 +11,7 @@ from golem.core.optimisers.random_graph_factory import RandomGrowthGraphFactory -@pytest.mark.parametrize('max_depth', [1, 5, 10, 30, 50]) +@pytest.mark.parametrize('max_depth', [1, 5, 10, 30]) def test_gp_composer_random_graph_generation_looping(max_depth): """ Test checks DefaultRandomOptGraphFactory valid generation. """ available_node_types = ['a', 'b', 'c', 'd', 'e'] From 80d51b4d53ff149c782067017836cdace8e7e704 Mon Sep 17 00:00:00 2001 From: kasyanovse Date: Wed, 27 Dec 2023 13:58:14 +0300 Subject: [PATCH 9/9] change `mutation_type` to `mutation_name` in `parent_operator` --- golem/core/optimisers/genetic/operators/mutation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/golem/core/optimisers/genetic/operators/mutation.py b/golem/core/optimisers/genetic/operators/mutation.py index 8c52e751..4b0c3f9c 100644 --- a/golem/core/optimisers/genetic/operators/mutation.py +++ b/golem/core/optimisers/genetic/operators/mutation.py @@ -103,8 +103,12 @@ def _mutation(self, individual: Individual) -> Tuple[Individual, bool]: new_graph = self._apply_mutations(new_graph, mutation_type) is_correct_graph = self.graph_generation_params.verifier(new_graph) if is_correct_graph: + if isinstance(mutation_type, MutationTypesEnum): + mutation_name = mutation_type.name + else: + mutation_name = mutation_type.__name__ parent_operator = ParentOperator(type_='mutation', - operators=mutation_type, + operators=mutation_name, parent_individuals=individual) individual = Individual(new_graph, parent_operator, metadata=self.requirements.static_individual_metadata)