Coverage for /builds/ase/ase/ase/ga/offspring_creator.py: 81.97%
61 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3"""Base module for all operators that create offspring."""
4import numpy as np
6from ase import Atoms
9class OffspringCreator:
10 """Base class for all procreation operators
12 Parameters:
14 verbose: Be verbose and print some stuff
16 rng: Random number generator
17 By default numpy.random.
18 """
20 def __init__(self, verbose=False, num_muts=1, rng=np.random):
21 self.descriptor = 'OffspringCreator'
22 self.verbose = verbose
23 self.min_inputs = 0
24 self.num_muts = num_muts
25 self.rng = rng
27 def get_min_inputs(self):
28 """Returns the number of inputs required for a mutation,
29 this is to know how many candidates should be selected
30 from the population."""
31 return self.min_inputs
33 def get_new_individual(self, parents):
34 """Function that returns a new individual.
35 Overwrite in subclass."""
36 raise NotImplementedError
38 def finalize_individual(self, indi):
39 """Call this function just before returning the new individual"""
40 indi.info['key_value_pairs']['origin'] = self.descriptor
42 return indi
44 @classmethod
45 def initialize_individual(cls, parent, indi=None):
46 """Initializes a new individual that inherits some parameters
47 from the parent, and initializes the info dictionary.
48 If the new individual already has more structure it can be
49 supplied in the parameter indi."""
50 if indi is None:
51 indi = Atoms(pbc=parent.get_pbc(), cell=parent.get_cell())
52 else:
53 indi = indi.copy()
54 # key_value_pairs for numbers and strings
55 indi.info['key_value_pairs'] = {'extinct': 0}
56 # data for lists and the like
57 indi.info['data'] = {}
59 return indi
62class OperationSelector:
63 """Class used to randomly select a procreation operation
64 from a list of operations.
66 Parameters:
68 probabilities: A list of probabilities with which the different
69 mutations should be selected. The norm of this list
70 does not need to be 1.
72 oplist: The list of operations to select from.
74 rng: Random number generator
75 By default numpy.random.
76 """
78 def __init__(self, probabilities, oplist, rng=np.random):
79 assert len(probabilities) == len(oplist)
80 self.oplist = oplist
81 self.rho = np.cumsum(probabilities)
82 self.rng = rng
84 def __get_index__(self):
85 v = self.rng.random() * self.rho[-1]
86 for i in range(len(self.rho)):
87 if self.rho[i] > v:
88 return i
90 def get_new_individual(self, candidate_list):
91 """Choose operator and use it on the candidate. """
92 to_use = self.__get_index__()
93 return self.oplist[to_use].get_new_individual(candidate_list)
95 def get_operator(self):
96 """Choose operator and return it."""
97 to_use = self.__get_index__()
98 return self.oplist[to_use]
101class CombinationMutation(OffspringCreator):
102 """Combine two or more mutations into one operation.
104 Parameters:
106 mutations: Operator instances
107 Supply two or more mutations that will applied one after the other
108 as one mutation operation. The order of the supplied mutations prevail
109 when applying the mutations.
111 """
113 def __init__(self, *mutations, verbose=False):
114 super().__init__(verbose=verbose)
115 self.descriptor = 'CombinationMutation'
117 # Check that a combination mutation makes sense
118 msg = "Too few operators supplied to a CombinationMutation"
119 assert len(mutations) > 1, msg
121 self.operators = mutations
123 def get_new_individual(self, parents):
124 f = parents[0]
126 indi = self.mutate(f)
127 if indi is None:
128 return indi, f'mutation: {self.descriptor}'
130 indi = self.initialize_individual(f, indi)
131 indi.info['data']['parents'] = [f.info['confid']]
133 return (self.finalize_individual(indi),
134 f'mutation: {self.descriptor}')
136 def mutate(self, atoms):
137 """Perform the mutations one at a time."""
138 for op in self.operators:
139 if atoms is not None:
140 atoms = op.mutate(atoms)
141 return atoms