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