def test_filter_individuals(self): set_db() environment_params = { 'tree_height': 1.0, 'temperature': 20.0, 'predators_speed': 10.0, 'food_animals_speed': 10.0, 'food_animals_strength': 1.0 } environment = Environment('Bla', environment_params) population = DataWrapper() collection = [] for i in range(0, get_population_size() + 50): ind = { 'height': 1, 'arm_length': 0, 'speed': 10, 'strength': i, 'jump': 0, 'skin_thickness': 0.05, 'total_reach': 2 } collection.append(ind) population['Bla_1'] = collection filtered_individuals = filter_individuals(population, environment, 1) self.assertEqual(len(filtered_individuals['Bla_1_filtered']), int(get_population_size() * 0.6)) rand_pos = random.randint(0, 49) self.assertGreater(filtered_individuals['Bla_1'][rand_pos+1]['strength'], filtered_individuals['Bla_1'][rand_pos]['strength'])
def test_obtain_randomized_individuals(self): my_indivs = obtain_randomized_pairs(int(get_population_size() * 0.6)) self.assertEqual(len(my_indivs), int(get_population_size() * 0.3)) for ind1, i in enumerate(my_indivs): for ind2, j in enumerate(my_indivs): if ind1 != ind2 and i == j: raise Exception
async def pair_individuals(current_population, list_of_pairs, iteration, filtered_coll): """ This method creates the asynchronous tasks to obtain two childs for each pair of parents :param current_population: individuals to reproduce :type current_population: list or DBWrapper :param list_of_pairs: list of randomized pairs :type list_of_pairs: list of tuples of 2 ints :param iteration: current iteration :type iteration: int :param filtered_coll: name of the filtered collection :type filtered_coll: str :return: list of the obtained children or list of _id's of the new children :rtype: list of dicts or list of ints """ tasks = [] for ind, pair in enumerate(list_of_pairs): for step in [0, int(get_population_size() * 0.3)]: tasks.append( asyncio.ensure_future( obtain_children(ind + int(get_population_size() * 0.6) + step, iteration, pair[0], pair[1], current_population, filtered_coll ) ) ) responses = await asyncio.gather(*tasks) return responses
def reproduction_stage(iteration, current_population, environment_name): """ This method pairs random individuals, obtains their children, and adds them to the population :param iteration: current iteration :type iteration: int :param current_population: set of individuals to reproduce :type current_population: list or MongoWrapper :param environment_name: :type environment_name: :return: resulting set of individuals after reproduction stage :rtype: list or DBWrapper """ filtered_coll = f'{environment_name}_{iteration}_filtered' reproduce_coll = f'{environment_name}_{iteration}_reproduction' if is_elitist(): individuals_pairs = obtain_randomized_pairs(int(get_population_size() * 0.6)) else: individuals_pairs = obtain_ordered_pairs(int(get_population_size() * 0.6)) new_iter_individuals = obtain_pairs_of_individuals( current_population, individuals_pairs, filtered_coll ) newly_created_individuals = asyncio.run(pair_individuals(current_population, individuals_pairs, iteration, filtered_coll)) logging.debug(f"Created {len(newly_created_individuals)} new individuals") current_population[reproduce_coll].extend(new_iter_individuals) return current_population
def test_natural_death(self): set_db() population = DataWrapper() collection = [] for i in range(0, get_population_size() + 100): ind = { 'age': i % 5, 'height': 1, 'arm_length': 0, 'speed': 10, 'strength': 1, 'jump': 0, 'skin_thickness': 0.05 } collection.append(ind) population['Bla_1_reproduction'] = collection new_pop = natural_death(1, population, 'Bla') self.assertEqual(len(new_pop['Bla_2']), get_population_size())
async def obtain_children(index, iteration, individual1_ind, individual2_ind, current_population, filtered_coll): """ This method takes the indexes of the new child parents, obtains the parents parameters, and creates the new child parameters from the average of each of the parents parameters, adding a small mutation factor :param index: _id of the new children :type index: int :param iteration: current iteration :type iteration: int :param individual1_ind: parent 1 _id :type individual1_ind: int :param individual2_ind: parent 1 _id :type individual2_ind: int :param current_population: individuals to reproduce :type current_population: list or DBWrapper :param filtered_coll: name of the filtered collection :type filtered_coll: str :return: obtained child or _id of the new child :rtype: dict or int """ ind1 = current_population[filtered_coll][individual1_ind] ind2 = current_population[filtered_coll][individual2_ind] child = dict() child['_id'] = index child['age'] = int((index - int(get_population_size()*0.6)) / int(get_population_size()*0.6 / 5)) \ + iteration \ + 1 mutation_factor = get_mutation_factor() PARAMS_TO_READ = GENERIC_PARAMS if is_generic() else HUMAN_PARAMS for parameter, value in ind1.items(): if parameter != '_id' and parameter != 'age' and parameter != 'value': child[parameter] = round( float(np.clip( (ind1[parameter] + ind2[parameter]) / 2 * random.uniform(1 - mutation_factor, 1 + mutation_factor), PARAMS_TO_READ[parameter][0] * 0.8, PARAMS_TO_READ[parameter][1] * 1.2)), 3 ) child['value'] = 0 reproduction_collection = f'{"_".join(filtered_coll.split("_")[:-1])}_{"reproduction"}' current_population[reproduction_collection].append(child) return index
def check_converged(self, prev_analysis): """ This method checks if the current iteration individuals have changed much compared to the previous iteration individuals :param prev_analysis: the previous iteration individuals average parameters :type prev_analysis: PopulationAnalysis object :return: true if the current iteration individual parameters are really similar to the previous one. false otherwise :rtype: boolean """ if self.averages['value'] >= 0.7: if self.averages['fitting'] >= int(get_population_size() * 0.80): total_dif = 0 for key, val in self.averages.items(): total_dif += abs(val - prev_analysis.averages[key]) if total_dif < 1: return True if self.averages['fitting'] >= int(get_population_size() * 0.95): return True return False
def test_obtain_randomized_pair_of_individuals(self): num_inds = 6000 individuals = [] for i in range(0, num_inds): indiv = { 'height': 1, 'arm_length': 2, 'speed': 3, 'strength': 4, 'jump': 5, 'skin_thickness': 6 } individuals.append(indiv) curr_pop = {'bla_1_filtered': individuals} my_pairs = obtain_randomized_pairs(num_inds) new_indivs = obtain_pairs_of_individuals(curr_pop, my_pairs, 'bla_1_filtered') self.assertEquals(len(my_pairs), int(get_population_size() * 0.3)) self.assertEquals(len(new_indivs), int(get_population_size() * 0.6)) self.assertTrue('_id' in new_indivs[0]) for pair in my_pairs: self.assertTrue(len(pair) == 2)
def filter_individuals(current_population, environment, iteration): """ Main filter method :param current_population: set of individuals to test against the environment :type current_population: list or DBWrapper :param environment: the current parameters against which the individuals are being tested :type environment: Environment :param iteration: current iteration :type iteration: int :return: resulting set of individuals after filtering stage :rtype: list or DBWrapper """ filtered_collection = f'{environment.name}_{iteration}_{"filtered"}' _ = asyncio.run(create_evaluation_tasks(current_population, environment, iteration)) current_population[filtered_collection].sort(key=operator.itemgetter('value'), reverse=True) deleted = 0 for idx, item in enumerate(current_population[filtered_collection]): if idx < int(get_population_size() * 0.4): current_population[filtered_collection].pop(get_population_size() - idx - 1) deleted += 1 logging.debug(f"Deleted {int(get_population_size() * 0.4)} individuals") return current_population
def create_individuals(environment_name): """ This method creates the initial set of individuals that will be tested against the environment :param environment_name: name of the environment :type environment_name: str :return: resulting set of individuals :rtype: list or DBWrapper """ current_population = return_db() for index in range(0, get_population_size()): params = obtain_params(index) current_population[f'{environment_name}_{1}'].append(params) logging.debug( f"Created a starting set of {get_population_size()} individuals") return current_population
def obtain_randomized_pairs(current_population_len): """ This method obtains a randomized pairs list containing all current individuals :param current_population_len: total number of individuals to randomize :type current_population_len: int :return: the resulting list of randomized pairs :rtype: list of tuples of 2 ints """ random_individuals = [i for i in range(0, current_population_len)] random.shuffle(random_individuals) random_pairs = [] for i in range(0, int(get_population_size() * 0.3)): ind1 = random_individuals.pop() ind2 = random_individuals.pop() random_pairs.append((ind1, ind2)) return random_pairs
def set_plots(self, environment): if not is_generic(): self.ax1 = self.fig.add_subplot(3, 2, 1) self.ax1.set_title('Average Speed') self.ax1.set_ylim([ HUMAN_PARAMS['speed'][0] * 0.8, HUMAN_PARAMS['speed'][1] * 1.2 ]) self.ax2 = self.fig.add_subplot(3, 2, 2) self.ax2.set_title('Average Strength') self.ax2.set_ylim([ HUMAN_PARAMS['strength'][0] * 0.8, HUMAN_PARAMS['strength'][1] * 1.2 ]) self.ax3 = self.fig.add_subplot(3, 2, 3) self.ax3.set_title('Average Skin thickness') self.ax3.set_ylim([ HUMAN_PARAMS['skin_thickness'][0] * 0.8, HUMAN_PARAMS['skin_thickness'][1] * 1.2 ]) self.ax4 = self.fig.add_subplot(3, 2, 4) self.ax4.set_title('Average Total Reach') self.ax4.set_ylim([1 * 0.8, 2.5 * 1.2]) self.ax5 = self.fig.add_subplot(3, 2, 5) self.ax5.set_title('Individuals fitting') self.ax5.set_ylim([ (get_population_size() * 0.9) - get_population_size(), get_population_size() * 1.1 ]) self.ax6 = self.fig.add_subplot(3, 2, 6) else: ind = 1 num_attrs = len(environment.data.keys()) num_rows = int((num_attrs + 2) / 2) + 1 num_cols = 2 for key, val in environment.data.items(): self.__setattr__(key, self.fig.add_subplot(num_rows, num_cols, ind)) self.__getattribute__(key).set_title(key) self.__getattribute__(key).set_ylim([ GENERIC_PARAMS[key][0] * 0.8, GENERIC_PARAMS[key][1] * 1.2 ]) ind += 1 self.fitting = self.fig.add_subplot(num_rows, num_cols, ind) self.fitting.set_title('Individuals fitting') self.fitting.set_ylim([ (get_population_size() * 0.9) - get_population_size(), get_population_size() * 1.1 ]) self.data = self.fig.add_subplot(num_rows, num_cols, ind + 1) plt.xlabel("Iterations") plt.tight_layout() plt.gcf().subplots_adjust(bottom=0.08, right=0.9, left=0.1, top=0.9)
async def create_evaluation_tasks(current_population, environment, iteration): """ Creates a task for each individual and waits for the result :param current_population: set of individuals to test against the environment :type current_population: list or DBWrapper :param environment: the current parameters against which the individuals are being tested :type environment: dict :param iteration: current iteration :type iteration: int :return: pairs of individuals with their values :rtype: list of tuples of floats """ tasks = [ asyncio.ensure_future(evaluate_individual(current_population, individual_id, environment, iteration)) for individual_id in range(0, get_population_size()) ] responses = await asyncio.gather(*tasks) return responses
def mongo_obtain_pairs_of_individuals(current_population, filtered_population, individuals_pairs): """ This method inserts the reproduction stage 'parents' into the new collection :param current_population: individuals to reproduce :type current_population: DBWrapper instance :param filtered_population: the specific population to be reproduced :type filtered_population: MongoCollectionWrapper instance :param individuals_pairs: list of pairs :type individuals_pairs: list of tuples of 2 ints :return: list of randomized pairs :rtype: list of tuples of 2 ints """ for index, individual_pair in enumerate(individuals_pairs): for index2, step in enumerate([0, int(get_population_size() * 0.3)]): individual = filtered_population[individual_pair[index2]] individual['_id'] = index current_population.current_collection.insert_one(individual) return individuals_pairs
def obtain_params(index): """ This method creates an individual for the first iteration, by randomly obtaining values in a defined range for each parameters, and assigning an age and initial iteration :param index: individual index :type index: int :return: the individual containing all its parameters :rtype: dict """ params = { '_id': index, 'age': int(index / (get_population_size() / 5)) + 1, 'value': 0 } individual_params = dict(GENERIC_PARAMS) if is_generic() else dict( HUMAN_PARAMS) for k, v in individual_params.items(): params[k] = round(random.uniform(v[0], v[1]), 3) return params
def natural_death(iteration, current_population, environment_name): """ Kill all individuals which have as age the current iteration. :param iteration: current iteration :type iteration: int :param current_population: Object containing the individuals collections :type current_population: DBWrapper :param environment_name: the current parameters against which the individuals are being tested :type environment_name: str :return: the individuals that survived the filtering stage :rtype: list or DBWrapper """ coll_to_trim = current_population[f'{environment_name}_{iteration}_{"reproduction"}'] new_col = current_population[f'{environment_name}_{iteration + 1}'] coll_to_trim.sort(key=operator.itemgetter('age'), reverse=True) for idx, individual in enumerate(coll_to_trim): if idx < get_population_size(): individual['_id'] = idx new_col.append(individual) return current_population
def obtain_pairs_of_individuals(current_population, individuals_pairs, filtered_coll): """ This method inserts the reproduction stage 'parents' into the new collection :param current_population: individuals to reproduce :type current_population: list :param individuals_pairs: list of pairs :type individuals_pairs: list of tuples of 2 ints :param filtered_coll: name of the filtered collection :type filtered_coll: str :return: parents added to a new list :rtype: list of dicts """ new_iter_individuals = [] for index, individual_pair in enumerate(individuals_pairs): ind1 = current_population[filtered_coll][individual_pair[0]] ind1['_id'] = index new_iter_individuals.append(ind1) ind2 = current_population[filtered_coll][individual_pair[1]] ind2['_id'] = index + int(get_population_size() * 0.3) new_iter_individuals.append(ind2) return new_iter_individuals
def analyze_population(self, current_population, environment_name, iteration): """ This method obtains the averages for the current iteration individuals and saves the results to be plot :param current_population: current set of individuals :type current_population: list or DBWrapper :param environment_name: the current parameters against which the individuals are being tested :type environment_name: str :param iteration: current iteration :type iteration: int """ coll_name = f'{environment_name}_{iteration + 1}' if not is_generic(): total_speed = 0 total_strength = 0 total_skin = 0 total_reach = 0 total_value = 0 to_evaluate = 0 total_fitting = 0 for individual in current_population[coll_name]: total_speed += individual['speed'] total_strength += individual['strength'] total_skin += individual['skin_thickness'] if 'value' in individual: total_value += individual['value'] to_evaluate += 1 reach = individual['height'] + individual['arm_length'] + individual['jump'] total_reach += reach if (reach > self.environment['tree_height'] or (individual['speed'] > self.environment['food_animals_speed'] and individual['strength'] > self.environment['food_animals_strength'])) and \ is_fast_enough(individual, self.environment) and \ is_warm_enough(individual, self.environment): total_fitting += 1 self.averages = { 'speed': total_speed / get_population_size(), 'strength': total_strength / get_population_size(), 'skin': total_skin / get_population_size(), 'total_reach': total_reach / get_population_size(), 'value': total_value / to_evaluate, 'fitting': total_fitting } else: total_value = 0 evaluated = 0 for individual in current_population[coll_name]: fits = True for param, value in individual.items(): if param != '_id' and param != 'age' and param != 'value': self.averages[param] += value if fits and \ not (GENERIC_ENVIRONMENT_DEFAULT[param]-2 < value < GENERIC_ENVIRONMENT_DEFAULT[param]+2): fits = False if param == 'value': total_value += value evaluated += 1 if fits: self.averages['fitting'] += 1 self.averages['value'] = total_value / max(evaluated, 1) for param, value in GENERIC_PARAMS.items(): self.averages[param] = self.averages[param] / get_population_size() my_plot.add_data(self.averages, self.iteration)
async def async_obtain_children(self, indiv1_ind, indiv2_ind): set_db() rand_idx = random.randint(0, get_population_size()) rand_iter = random.randint(0, get_population_size()) indiv1_rand_height = random.uniform(HUMAN_PARAMS['height'][0], HUMAN_PARAMS['height'][1]) indiv2_rand_height = random.uniform(HUMAN_PARAMS['height'][0], HUMAN_PARAMS['height'][1]) indiv1_arm_length = random.uniform(HUMAN_PARAMS['arm_length'][0], HUMAN_PARAMS['arm_length'][1]) indiv2_arm_length = random.uniform(HUMAN_PARAMS['arm_length'][0], HUMAN_PARAMS['arm_length'][1]) indiv1_speed = random.uniform(HUMAN_PARAMS['speed'][0], HUMAN_PARAMS['speed'][1]) indiv2_speed = random.uniform(HUMAN_PARAMS['speed'][0], HUMAN_PARAMS['speed'][1]) indiv1_strength = random.uniform(HUMAN_PARAMS['strength'][0], HUMAN_PARAMS['strength'][1]) indiv2_strength = random.uniform(HUMAN_PARAMS['strength'][0], HUMAN_PARAMS['strength'][1]) indiv1_jump = random.uniform(HUMAN_PARAMS['jump'][0], HUMAN_PARAMS['jump'][1]) indiv2_jump = random.uniform(HUMAN_PARAMS['jump'][0], HUMAN_PARAMS['jump'][1]) indiv1_skin_thickness = random.uniform( HUMAN_PARAMS['skin_thickness'][0], HUMAN_PARAMS['skin_thickness'][1]) indiv2_skin_thickness = random.uniform( HUMAN_PARAMS['skin_thickness'][0], HUMAN_PARAMS['skin_thickness'][1]) indiv1 = { 'height': indiv1_rand_height, 'arm_length': indiv1_arm_length, 'speed': indiv1_speed, 'strength': indiv1_strength, 'jump': indiv1_jump, 'skin_thickness': indiv1_skin_thickness } indiv2 = { 'height': indiv2_rand_height, 'arm_length': indiv2_arm_length, 'speed': indiv2_speed, 'strength': indiv2_strength, 'jump': indiv2_jump, 'skin_thickness': indiv2_skin_thickness } current_population = DataWrapper() current_population["test_pop"] = [indiv1, indiv2] new_child = [ asyncio.ensure_future( obtain_children(rand_idx, rand_iter, indiv1_ind, indiv2_ind, current_population, "test_pop")) ] _ = await asyncio.gather(*new_child) new_child = current_population["test_reproduction"][0] self.assertTrue( new_child['height'] > (indiv1['height'] + indiv2['height']) / 2 - ((indiv1['height'] + indiv2['height']) * mutation_factor)) self.assertTrue( new_child['height'] < (indiv1['height'] + indiv2['height']) / 2 + ((indiv1['height'] + indiv2['height']) * mutation_factor)) self.assertTrue( new_child['arm_length'] > (indiv1['arm_length'] + indiv2['arm_length']) / 2 - ((indiv1['arm_length'] + indiv2['arm_length']) * mutation_factor)) self.assertTrue( new_child['arm_length'] < (indiv1['arm_length'] + indiv2['arm_length']) / 2 + ((indiv1['arm_length'] + indiv2['arm_length']) * mutation_factor)) self.assertTrue( new_child['speed'] > (indiv1['speed'] + indiv2['speed']) / 2 - ((indiv1['speed'] + indiv2['speed']) * mutation_factor)) self.assertTrue( new_child['speed'] < (indiv1['speed'] + indiv2['speed']) / 2 + ((indiv1['speed'] + indiv2['speed']) * mutation_factor)) self.assertTrue( new_child['strength'] > (indiv1['strength'] + indiv2['strength']) / 2 - ((indiv1['strength'] + indiv2['strength']) * mutation_factor)) self.assertTrue( new_child['strength'] < (indiv1['strength'] + indiv2['strength']) / 2 + ((indiv1['strength'] + indiv2['strength']) * mutation_factor)) self.assertTrue( new_child['jump'] > (indiv1['jump'] + indiv2['jump']) / 2 - ((indiv1['jump'] + indiv2['jump']) * mutation_factor)) self.assertTrue( new_child['jump'] < (indiv1['jump'] + indiv2['jump']) / 2 + ((indiv1['jump'] + indiv2['jump']) * mutation_factor)) self.assertTrue( new_child['skin_thickness'] > (indiv1['skin_thickness'] + indiv2['skin_thickness']) / 2 - ((indiv1['skin_thickness'] + indiv2['skin_thickness']) * mutation_factor)) self.assertTrue( new_child['skin_thickness'] < (indiv1['skin_thickness'] + indiv2['skin_thickness']) / 2 + ((indiv1['skin_thickness'] + indiv2['skin_thickness']) * mutation_factor)) return 1