def get_individuals(): individuals = [ Individual(params=[1.0, 0.1, -0.5, 4.3, 2.4], reax_energies=[43.2, 10.6, -174.2, 1008.4]), Individual(params=[0.5, 0.05, -0.76, 8.6, 2.1], reax_energies=[56.9, 11.2, -164.1, 994.8]), Individual(params=[1.24, 0.17, -0.07, 6.3, 2.24], reax_energies=[102.4, 5.5, -155.5, 1008.12]), Individual(params=[0.73, 0.25, -0.25, 10.2, 1.7], reax_energies=[40.2, 11.2, -104.1, 1018.32]), ] return individuals
def crossover(parent1: Individual, parent2: Individual, root_individual: RootIndividual, **kwargs) -> Tuple[Individual, Individual]: """Execute uniform crossover. Take the ith row of parent1 and randomly swap bits with the ith row of p2.""" sieve = np.random.randint(2, size=len( parent1.params)) # Array of 0's and 1's not_sieve = sieve ^ 1 # Complement of sieve child1 = Individual(list(parent1.params * sieve + parent2.params * not_sieve), root_individual=root_individual) child2 = Individual(list(parent1.params * not_sieve + parent2.params * sieve), root_individual=root_individual) return child1, child2
def test_individual_from_root_individual(root_individual): individual_from_root = Individual.from_root_individual(root_individual) assert isinstance(individual_from_root, Individual) assert individual_from_root.params == root_individual.root_params assert individual_from_root.reax_energies is None assert individual_from_root.ffield is not None assert individual_from_root.cost is None
def get_population(self, generation_number: int) -> Tuple[List[Individual], List[int]]: generation_dir_path = os.path.join(self.population_path, self.GENERATION_FOLDER_PREFIX + str(generation_number)) root_individual = self.get_root_individual() population = [] successfully_retrieved_case_numbers = [] for case_number in range(self.population_size): child_dir = os.path.join(generation_dir_path, self.INDIVIDUAL_FOLDER_PREFIX + str(case_number)) self.population_reax_reader.dir_path = child_dir try: child_fort99_data = self.population_reax_reader.read_fort99() child_ffield, _ = self.population_reax_reader.read_ffield() child_params = [get_param(key, child_ffield) for key in self.param_keys] fort99_extractor = Fort99Extractor(child_fort99_data) child_reax_energies = fort99_extractor.get_reax_energies() # noinspection PyTypeChecker child = Individual(child_params, child_reax_energies, root_individual, error_calculator=ReaxError) population.append(child) successfully_retrieved_case_numbers.append(case_number) except FileNotFoundError: # 'fort.99' file does not exist print("fort.99 not found in '{}'...continuing to next case".format(child_dir)) continue except ValueError: # Invalid number of columns in fort.99 -> skip and continue to next case # TODO: Log continue return population, successfully_retrieved_case_numbers
def crossover(parent1: Individual, parent2: Individual, root_individual: RootIndividual, **kwargs) -> Tuple[Individual, Individual]: """Execute single-point crossover. Cut the parameter vectors from two children at random positions and join to yield two new vectors (children). :param parent1: The first individual participating in crossover. :param parent2: The second individual participating in crossover. :return: Two new Children generated from the mating/crossover. """ choice = random.randrange(0, len(parent1.params)) child1 = Individual(parent1.params[:choice] + parent2.params[choice:], root_individual=root_individual) child2 = Individual(parent2.params[:choice] + parent1.params[choice:], root_individual=root_individual) return child1, child2
def crossover(parent1: Individual, parent2: Individual, root_individual: RootIndividual, **kwargs) -> Tuple[Individual, Individual]: """Execute two-point crossover.""" idx = random.sample(len(parent1.params), 2) smaller_id = min(idx) bigger_id = max(idx) child1 = Individual(parent1.params[:smaller_id] + parent2.params[smaller_id:bigger_id] + parent1.params[bigger_id:], root_individual=root_individual) child2 = Individual(parent2.params[:smaller_id] + parent1.params[smaller_id:bigger_id] + parent2.params[bigger_id:], root_individual=root_individual) return child1, child2
def crossover(parent1: Individual, parent2: Individual, root_individual: RootIndividual, **kwargs) -> Tuple[Individual, Individual]: """Double Pareto crossover from Thakur's 2014 - "A new GA for global optimization of multimodal continuous functions." NOTE: Thakur is unclear about modified beta if/then command. I have ASSUMED the following: if u >= 1/2: >> use first expression else: (u < 1/2): >> use second expression This SHOULD be correct, as x = 0 corresponds to f(x), which is the Pareto density function, equal to 0.5! """ alpha = kwargs.get('dpx_alpha', 10) beta = kwargs.get('dpx_beta', 1) child1_params = [] child2_params = [] for parent1_param, parent2_param in zip(parent1.params, parent2.params): u = random.uniform(0, 1) if u >= 1 / 2: modified_beta = alpha * beta * (1 - (2 * u)**(-1 / alpha)) else: modified_beta = alpha * beta * ((1 - (2 * u))**(-1 / alpha) - 1) child1_param = ( (parent1_param + parent2_param) + modified_beta * abs(parent1_param - parent2_param)) / 2 child2_param = ( (parent1_param + parent2_param) - modified_beta * abs(parent1_param - parent2_param)) / 2 child1_params.append(child1_param) child2_params.append(child2_param) child1 = Individual(child1_params, root_individual=root_individual) child2 = Individual(child2_params, root_individual=root_individual) return child1, child2
def mutation(parent: Individual, root_individual: RootIndividual, **kwargs) -> Individual: """Inspired by Monte Carlo/GA guidelines for ReaxFF paper. Use a random number (determined by uniform distribution) in the central segment for each parameter range. So, if a parameter has a range of [p_min, p_max], a uniform random number will be generated in the bounds [p_min + (p_max - p_min)/4, p_max - (p_max - p_min)/4]. Mutates all parameters (doesn't consider `FRAC_PARAMS_MUTATE`). """ param_bounds = kwargs['param_bounds'] new_params = [] for (lower_bound, upper_bound) in param_bounds: delta = (upper_bound - lower_bound) / 4 new_param = random.uniform(lower_bound + delta, upper_bound - delta) new_params.append(new_param) return Individual(new_params, root_individual=root_individual)
def mutation(parent: Individual, root_individual: RootIndividual, **kwargs) -> Individual: """Polynomial mutation adapted from Deb and Agrawal's paper. ADAPTED TO FUNCTION WITHOUT UPPER/LOWER BOUNDS FOR PARAMETERS. """ eta = kwargs.get('polynomial_eta', 60) poly_degree = 1 / (1 + eta) new_params = [] for param in parent.params: u = random.random() if u <= 0.5: delta_l = (2 * u)**poly_degree - 1 param = param + delta_l * param else: delta_r = 1 - (2 * (1 - u))**poly_degree param = param + delta_r * param new_params.append(param) return Individual(new_params, root_individual=root_individual)
def mutation(parent: Individual, root_individual: RootIndividual, **kwargs) -> Individual: """Mutate Child's `params` using Nakata's methodology. new_param = old_param + (scale * rand_num * old_param). `scale` can be float or List with len(`scale`) = len(self.params), e.g., when using param_increments is desired. param_bounds are currently being used as (min, max) conditions for params, if they exist. if param is outside param_bounds, param is set using uniform distribution with (min, max) bounds. :return: New Individual after mutation. """ scale = kwargs.get('nakata_scale', 0.1) low = kwargs.get('nakata_rand_lower', -1.0) high = kwargs.get('nakata_rand_higher', 1.0) new_params = [ param + (scale * random.uniform(low, high) * param) for param in parent.params ] return Individual(new_params, root_individual=root_individual)
def mutation(parent: Individual, root_individual: RootIndividual, **kwargs) -> Individual: """UNCONSTRAINED Gaussian mutation. Upper and lower bounds are not required for this version. Allows usage of multiple scaling factors for the normal distribution for mutation. Each scaling factor needs a corresponding probability (gauss_frac) of using that given factor. """ stds = kwargs.get('gauss_std', [0.1]) probabilities = kwargs.get('gauss_frac', [1.0]) u = random.uniform(0, 1) cumulative_probability = 0 for std, probability in zip(stds, probabilities): if cumulative_probability < u <= (cumulative_probability + probability): new_params = [ param + param * random.gauss(0, std) for param in parent.params ] break cumulative_probability += probability return Individual(new_params, root_individual=root_individual)
def test_individual_init(): params = [0.5, 0.4, 1.0, -4.7, 8.6] reax_energies = [43.2, 10.6, -174.2, 1008.4] individual = Individual(params=params, reax_energies=reax_energies) assert individual.params == params assert individual.reax_energies == reax_energies
def individual(): params = [0.5, 0.4, 1.0, -4.7, 8.6] reax_energies = [43.2, 10.6, -174.2, 1008.4] individual = Individual(params=params, reax_energies=reax_energies, error_calculator=ReaxError) return individual
def execute(self) -> List[Individual]: root_individual = self.population_repository.get_root_individual() individual = Individual.from_root_individual(root_individual) population = [self.strategy.mutation(individual, root_individual, **self.mutation_settings_dict) for _ in range(self.population_size)] return population