diff --git a/experiments/algorithms_2_multi_agency.py b/experiments/algorithms_2_multi_agency.py index 8f9afeef..057c8917 100644 --- a/experiments/algorithms_2_multi_agency.py +++ b/experiments/algorithms_2_multi_agency.py @@ -27,7 +27,7 @@ def log(message: str, logfile: IO): schedulers = [HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()] - # GeneticScheduler(50, 50, 0.5, 0.5, 20)] + # GeneticScheduler(50, 0.5, 0.5, 20)] contractors = [p_rand.contractor(10) for _ in range(len(schedulers))] diff --git a/experiments/genetic_2_multi_agency.py b/experiments/genetic_2_multi_agency.py index 0e2d6723..75daaffb 100644 --- a/experiments/genetic_2_multi_agency.py +++ b/experiments/genetic_2_multi_agency.py @@ -18,11 +18,11 @@ def obstruction_getter(i: int): if __name__ == '__main__': - schedulers = [GeneticScheduler(50, 50, 0.5, 0.5, 100),] - # GeneticScheduler(50, 50, 0.5, 0.5, 100), - # GeneticScheduler(50, 50, 0.5, 0.5, 100), - # GeneticScheduler(50, 50, 0.5, 0.5, 100), - # GeneticScheduler(50, 50, 0.5, 0.5, 100)] + schedulers = [GeneticScheduler(50, 0.5, 0.5, 100),] + # GeneticScheduler(50, 0.5, 0.5, 100), + # GeneticScheduler(50, 0.5, 0.5, 100), + # GeneticScheduler(50, 0.5, 0.5, 100), + # GeneticScheduler(50, 0.5, 0.5, 100)] contractors = [p_rand.contractor(10) for _ in range(len(schedulers))] diff --git a/experiments/modular_examples/multi_agency_comparison.py b/experiments/modular_examples/multi_agency_comparison.py index 4f6f2cb3..94f933e4 100644 --- a/experiments/modular_examples/multi_agency_comparison.py +++ b/experiments/modular_examples/multi_agency_comparison.py @@ -74,16 +74,16 @@ def run_iteration(args): if mode == 0: schedulers = [HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()] else: - schedulers = [GeneticScheduler(50, 50, 0.5, 0.5, 100), - GeneticScheduler(50, 100, 0.25, 0.5, 100), - GeneticScheduler(50, 100, 0.5, 0.75, 100), - GeneticScheduler(50, 100, 0.75, 0.75, 100), - GeneticScheduler(50, 50, 0.9, 0.9, 100), - GeneticScheduler(50, 100, 0.5, 0.5, 500), - GeneticScheduler(50, 200, 0.25, 0.5, 500), - GeneticScheduler(50, 50, 0.5, 0.75, 500), - GeneticScheduler(50, 100, 0.75, 0.75, 500), - GeneticScheduler(50, 50, 0.5, 0.9, 500)] + schedulers = [GeneticScheduler(50, 0.5, 0.5, 100), + GeneticScheduler(50, 0.25, 0.5, 100), + GeneticScheduler(50, 0.5, 0.75, 100), + GeneticScheduler(50, 0.75, 0.75, 100), + GeneticScheduler(50, 0.9, 0.9, 100), + GeneticScheduler(50, 0.5, 0.5, 500), + GeneticScheduler(50, 0.25, 0.5, 500), + GeneticScheduler(50, 0.5, 0.75, 500), + GeneticScheduler(50, 0.75, 0.75, 500), + GeneticScheduler(50, 0.5, 0.9, 500)] variate_block_size(logfile, schedulers) diff --git a/sampo/scheduler/genetic/base.py b/sampo/scheduler/genetic/base.py index 6487e73b..9a7343e7 100644 --- a/sampo/scheduler/genetic/base.py +++ b/sampo/scheduler/genetic/base.py @@ -1,4 +1,3 @@ -import math import random from typing import Optional, Callable @@ -33,7 +32,6 @@ class GeneticScheduler(Scheduler): def __init__(self, number_of_generation: Optional[int] = 50, - size_selection: Optional[int or None] = None, mutate_order: Optional[float or None] = None, mutate_resources: Optional[float or None] = None, size_of_population: Optional[float or None] = None, @@ -49,7 +47,6 @@ def __init__(self, resource_optimizer=resource_optimizer, work_estimator=work_estimator) self.number_of_generation = number_of_generation - self.size_selection = size_selection self.mutate_order = mutate_order self.mutate_resources = mutate_resources self.size_of_population = size_of_population @@ -57,7 +54,7 @@ def __init__(self, self.fitness_constructor = fitness_constructor self.work_estimator = work_estimator self._n_cpu = n_cpu - self._weights = None + self._weights = weights self._time_border = None self._deadline = None @@ -65,48 +62,35 @@ def __init__(self, def __str__(self) -> str: return f'GeneticScheduler[' \ f'generations={self.number_of_generation},' \ - f'size_selection={self.size_selection},' \ + f'population_size={self.size_of_population},' \ f'mutate_order={self.mutate_order},' \ f'mutate_resources={self.mutate_resources}' \ f']' - def get_params(self, works_count: int) -> tuple[int, float, float, int]: + def get_params(self, works_count: int) -> tuple[float, float, int]: """ Return base parameters for model to make new population :param works_count: :return: """ - size_selection = self.size_selection - if size_selection is None: - if works_count < 300: - size_selection = 20 - else: - size_selection = works_count // 15 - mutate_order = self.mutate_order if mutate_order is None: - if works_count < 300: - mutate_order = 0.05 - else: - mutate_order = 2 / math.sqrt(works_count) + mutate_order = 0.05 mutate_resources = self.mutate_resources if mutate_resources is None: - if works_count < 300: - mutate_resources = 0.1 - else: - mutate_resources = 6 / math.sqrt(works_count) + mutate_resources = 0.005 size_of_population = self.size_of_population if size_of_population is None: if works_count < 300: - size_of_population = 20 - elif 1500 > works_count >= 300: size_of_population = 50 + elif 1500 > works_count >= 300: + size_of_population = 100 else: - size_of_population = works_count // 50 - return size_selection, mutate_order, mutate_resources, size_of_population + size_of_population = works_count // 25 + return mutate_order, mutate_resources, size_of_population def set_use_multiprocessing(self, n_cpu: int): """ @@ -130,8 +114,8 @@ def set_deadline(self, deadline: Time): def set_weights(self, weights: list[int]): self._weights = weights - @classmethod - def generate_first_population(self, wg: WorkGraph, contractors: list[Contractor], + @staticmethod + def generate_first_population(wg: WorkGraph, contractors: list[Contractor], landscape: LandscapeConfiguration = LandscapeConfiguration(), spec: ScheduleSpec = ScheduleSpec(), work_estimator: WorkTimeEstimator = None, @@ -213,7 +197,7 @@ def schedule_with_cache(self, init_schedules = GeneticScheduler.generate_first_population(wg, contractors, landscape, spec, self.work_estimator, self._deadline, self._weights) - size_selection, mutate_order, mutate_resources, size_of_population = self.get_params(wg.vertex_count) + mutate_order, mutate_resources, size_of_population = self.get_params(wg.vertex_count) worker_pool = get_worker_contractor_pool(contractors) scheduled_works, schedule_start_time, timeline, order_nodes = build_schedule(wg, @@ -221,7 +205,6 @@ def schedule_with_cache(self, worker_pool, size_of_population, self.number_of_generation, - size_selection, mutate_order, mutate_resources, init_schedules, diff --git a/sampo/scheduler/genetic/operators.py b/sampo/scheduler/genetic/operators.py index 87665272..26aaf840 100644 --- a/sampo/scheduler/genetic/operators.py +++ b/sampo/scheduler/genetic/operators.py @@ -1,13 +1,13 @@ -import math import random +import math from abc import ABC, abstractmethod from copy import deepcopy from functools import partial from typing import Iterable, Callable +from operator import attrgetter import numpy as np -from deap import creator, base, tools -from deap.tools import initRepeat +from deap import creator, base from sampo.scheduler.genetic.converter import convert_chromosome_to_schedule from sampo.scheduler.genetic.converter import convert_schedule_to_chromosome, ChromosomeType @@ -129,20 +129,20 @@ def init_toolbox(wg: WorkGraph, index2node: dict[int, GraphNode], work_id2index: dict[str, int], worker_name2index: dict[str, int], - index2contractor: dict[int, str], index2contractor_obj: dict[int, Contractor], - init_chromosomes: dict[str, tuple[ChromosomeType, float]], + init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]], mutate_order: float, mutate_resources: float, - selection_size: int, + population_size: int, rand: random.Random, spec: ScheduleSpec, worker_pool_indices: dict[int, dict[int, Worker]], contractor2index: dict[str, int], contractor_borders: np.ndarray, - resources_min_border: np.ndarray, node_indices: list[int], parents: dict[int, list[int]], + resources_border: np.ndarray, + resources_min_border: np.ndarray, assigned_parent_time: Time = Time(0), work_estimator: WorkTimeEstimator = DefaultWorkEstimator()) -> base.Toolbox: """ @@ -152,38 +152,34 @@ def init_toolbox(wg: WorkGraph, :return: Object, included tools for genetic """ toolbox = base.Toolbox() - # generate initial population + # generate chromosome toolbox.register('generate_chromosome', generate_chromosome, wg=wg, contractors=contractors, work_id2index=work_id2index, worker_name2index=worker_name2index, contractor2index=contractor2index, contractor_borders=contractor_borders, spec=spec, init_chromosomes=init_chromosomes, rand=rand, work_estimator=work_estimator, landscape=landscape) - # create from generate_chromosome function one individual - toolbox.register('individual', tools.initRepeat, Individual, toolbox.generate_chromosome, n=1) - # create population from individuals - toolbox.register('population', generate_population, wg=wg, contractors=contractors, spec=spec, + # create population + # toolbox.register('population', tools.initRepeat, list, lambda: toolbox.generate_chromosome()) + toolbox.register('population', generate_population, wg=wg, contractors=contractors, work_id2index=work_id2index, worker_name2index=worker_name2index, - contractor2index=contractor2index, contractor_borders=contractor_borders, + contractor2index=contractor2index, contractor_borders=contractor_borders, spec=spec, init_chromosomes=init_chromosomes, rand=rand, work_estimator=work_estimator, landscape=landscape) + # selection + toolbox.register('select', select_new_population, pop_size=population_size) # crossover for order toolbox.register('mate', mate_scheduling_order, rand=rand) - # mutation for order. Coefficient luke one or two mutation in individual - toolbox.register('mutate', tools.mutShuffleIndexes, indpb=mutate_order) - # selection. Some random individuals and arranges a battle between them as a result in a continuing genus, - # this is the best among these it - toolbox.register('select', tools.selTournament, tournsize=selection_size) - - # mutation for resources - toolbox.register('mutate_resources', mut_uniform_int, probability_mutate_resources=mutate_resources, - contractor_count=len(index2contractor), rand=rand) - # mutation for resource borders - toolbox.register('mutate_resource_borders', mutate_resource_borders, - probability_mutate_contractors=mutate_resources, rand=rand, - contractors_capacity=contractor_borders, resources_min_border=resources_min_border) + # mutation for order + toolbox.register('mutate', mutate_scheduling_order, mutpb=mutate_order, rand=rand) # crossover for resources toolbox.register('mate_resources', mate_for_resources, rand=rand) + # mutation for resources + toolbox.register('mutate_resources', mutate_for_resources, resources_border=resources_border, + mutpb=mutate_resources, rand=rand) # crossover for resource borders toolbox.register('mate_resource_borders', mate_for_resource_borders, rand=rand) + # mutation for resource borders + toolbox.register('mutate_resource_borders', mutate_resource_borders, resources_min_border=resources_min_border, + mutpb=mutate_resources, rand=rand) toolbox.register('validate', is_chromosome_correct, node_indices=node_indices, parents=parents) toolbox.register('schedule_to_chromosome', convert_schedule_to_chromosome, wg=wg, @@ -202,20 +198,7 @@ def copy_chromosome(chromosome: ChromosomeType) -> ChromosomeType: return chromosome[0].copy(), chromosome[1].copy(), chromosome[2].copy(), deepcopy(chromosome[3]) -def wrap(chromosome: ChromosomeType) -> Individual: - """ - Created an individual from chromosome. - """ - - def ind_getter(): - return chromosome - - ind = initRepeat(Individual, ind_getter, n=1) - ind.fitness.invalid_steps = 0 - return ind - - -def generate_population(size_population: int, +def generate_population(n: int, wg: WorkGraph, contractors: list[Contractor], spec: ScheduleSpec, @@ -226,40 +209,37 @@ def generate_population(size_population: int, init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]], rand: random.Random, work_estimator: WorkTimeEstimator = None, - landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[Individual]: """ - Generates population using chromosome weights. + Generates population. Do not use `generate_chromosome` function. """ + def randomized_init() -> ChromosomeType: - schedule = RandomizedTopologicalScheduler(work_estimator, - int(rand.random() * 1000000)) \ - .schedule(wg, contractors, spec, landscape=landscape) + schedule = RandomizedTopologicalScheduler(work_estimator, int(rand.random() * 1000000)) \ + .schedule(wg, contractors, landscape=landscape) return convert_schedule_to_chromosome(wg, work_id2index, worker_name2index, contractor2index, contractor_borders, schedule, spec) - # chromosome types' weights - # these numbers are the probability weights: prob = norm(weights), sum(prob) = 1 - weights = [2, 2, 1, 1, 1, 1, 2] - - for i, (_, importance, _) in enumerate(init_chromosomes.values()): - weights[i] = int(weights[i] * importance) + count_for_specified_types = (n // 3) // len(init_chromosomes) + count_for_specified_types = count_for_specified_types if count_for_specified_types > 0 else 1 + sum_counts_for_specified_types = count_for_specified_types * len(init_chromosomes) + counts = [count_for_specified_types * importance for _, importance, _ in init_chromosomes.values()] - weights_multiplier = math.ceil(size_population / sum(weights)) + weights_multiplier = math.ceil(sum_counts_for_specified_types / sum(counts)) + counts = [count * weights_multiplier for count in counts] - for i in range(len(weights)): - weights[i] *= weights_multiplier + count_for_topological = n - sum_counts_for_specified_types + count_for_topological = count_for_topological if count_for_topological > 0 else 1 + counts += [count_for_topological] - all_types = ['heft_end', 'heft_between', '12.5%', '25%', '75%', '87.5%', 'topological'] - chromosome_types = rand.sample(all_types, k=size_population, counts=weights) + chromosome_types = rand.sample(list(init_chromosomes.keys()) + ['topological'], k=n, counts=counts) - chromosomes = [init_chromosomes[generated_type][0] if generated_type != 'topological' else None + chromosomes = [Individual(init_chromosomes[generated_type][0]) + if generated_type != 'topological' else Individual(randomized_init()) for generated_type in chromosome_types] - for i, chromosome in enumerate(chromosomes): - if chromosome is None: - chromosomes[i] = randomized_init() - return [wrap(chromosome) for chromosome in chromosomes] + return chromosomes def generate_chromosome(wg: WorkGraph, @@ -268,11 +248,11 @@ def generate_chromosome(wg: WorkGraph, worker_name2index: dict[str, int], contractor2index: dict[str, int], contractor_borders: np.ndarray, - init_chromosomes: dict[str, tuple[ChromosomeType, float]], + init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]], spec: ScheduleSpec, rand: random.Random, work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), - landscape: LandscapeConfiguration = LandscapeConfiguration()) -> ChromosomeType: + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> Individual: """ It is necessary to generate valid scheduling, which are satisfied to current dependencies That's why will be used the approved order of works (HEFT order and Topological sorting) @@ -289,7 +269,6 @@ def randomized_init() -> ChromosomeType: return convert_schedule_to_chromosome(wg, work_id2index, worker_name2index, contractor2index, contractor_borders, schedule, spec) - chromosome = None chance = rand.random() if chance < 0.2: chromosome = init_chromosomes['heft_end'][0] @@ -303,11 +282,19 @@ def randomized_init() -> ChromosomeType: chromosome = init_chromosomes['75%'][0] elif chance < 0.8: chromosome = init_chromosomes['87.5%'][0] - - if chromosome is None: + else: chromosome = randomized_init() - return chromosome + return Individual(chromosome) + + +def select_new_population(population: list[ChromosomeType], pop_size: int) -> list[ChromosomeType]: + """ + Selection operator for genetic algorithm. + Selecting top n individuals in population. + """ + population = sorted(population, key=attrgetter('fitness'), reverse=True) + return population[:pop_size] def is_chromosome_correct(chromosome: ChromosomeType, @@ -317,7 +304,7 @@ def is_chromosome_correct(chromosome: ChromosomeType, Check order of works and contractors. """ return is_chromosome_order_correct(chromosome, parents) and \ - is_chromosome_contractors_correct(chromosome, node_indices) + is_chromosome_contractors_correct(chromosome, node_indices) def is_chromosome_order_correct(chromosome: ChromosomeType, parents: dict[int, list[int]]) -> bool: @@ -369,14 +356,15 @@ def mate_scheduling_order(ind1: ChromosomeType, ind2: ChromosomeType, rand: rand :return: two cross individuals """ - ind1 = copy_chromosome(ind1) - ind2 = copy_chromosome(ind2) + child1 = Individual(copy_chromosome(ind1)) + child2 = Individual(copy_chromosome(ind2)) - order1 = ind1[0] - order2 = ind2[0] + order1 = child1[0] + order2 = child2[0] + border = len(order1) // 4 # randomly select the point where the crossover will take place - crossover_point = rand.randint(1, len(ind1)) + crossover_point = rand.randint(border, len(order1) - border) ind1_new_tail = get_order_tail(order1[:crossover_point], order2) ind2_new_tail = get_order_tail(order2[:crossover_point], order1) @@ -384,118 +372,118 @@ def mate_scheduling_order(ind1: ChromosomeType, ind2: ChromosomeType, rand: rand order1[crossover_point:] = ind1_new_tail order2[crossover_point:] = ind2_new_tail - return ind1, ind2 - - -def mut_uniform_int(ind: ChromosomeType, low: np.ndarray, up: np.ndarray, type_of_worker: int, - probability_mutate_resources: float, contractor_count: int, rand: random.Random) -> ChromosomeType: - """ - Mutation function for resources. - It changes selected numbers of workers in random work in a certain interval for this work. - - :param low: lower bound specified by `WorkUnit` - :param up: upper bound specified by `WorkUnit` - :return: mutate individual - """ - ind = copy_chromosome(ind) - - # select random number from interval from min to max from uniform distribution - size = len(ind[1]) - workers_count = len(ind[1][0]) - - if type_of_worker == workers_count - 1: - # print('Contractor mutation!') - for i in range(size): - if rand.random() < probability_mutate_resources: - ind[1][i][type_of_worker] = rand.randint(0, contractor_count - 1) - return ind - - # change in this interval in random number from interval - for i, xl, xu in zip(range(size), low, up): - if rand.random() < probability_mutate_resources: - # borders - contractor = ind[1][i][-1] - border = ind[2][contractor][type_of_worker] - # TODO Debug why min(xu, border) can be lower than xl - ind[1][i][type_of_worker] = rand.randint(xl, max(xl, min(xu, border))) - - return ind + return child1, child2 -def mutate_resource_borders(ind: ChromosomeType, contractors_capacity: np.ndarray, resources_min_border: np.ndarray, - type_of_worker: int, probability_mutate_contractors: float, rand: random.Random) \ - -> ChromosomeType: +def mutate_scheduling_order(ind: ChromosomeType, mutpb: float, rand: random.Random) -> ChromosomeType: """ - Mutation for contractors' resource borders. + Mutation operator for order. + Swap neighbors. """ - ind = copy_chromosome(ind) - - num_resources = len(resources_min_border) - num_contractors = len(ind[2]) - for contractor in range(num_contractors): - if rand.random() < probability_mutate_contractors: - ind[2][contractor][type_of_worker] -= rand.randint(resources_min_border[type_of_worker] + 1, - max(resources_min_border[type_of_worker] + 1, - ind[2][contractor][type_of_worker] // 10)) - if ind[2][contractor][type_of_worker] <= 0: - ind[2][contractor][type_of_worker] = 1 - - # find and correct all invalidated resource assignments - for work in range(len(ind[0])): - if ind[1][work][num_resources] == contractor: - ind[1][work][type_of_worker] = min(ind[1][work][type_of_worker], - ind[2][contractor][type_of_worker]) + order = ind[0] + for i in range(1, len(order) - 2): + if rand.random() < mutpb: + order[i], order[i + 1] = order[i + 1], order[i] return ind -def mate_for_resources(ind1: ChromosomeType, ind2: ChromosomeType, mate_positions: np.ndarray, +def mate_for_resources(ind1: ChromosomeType, ind2: ChromosomeType, rand: random.Random) -> (ChromosomeType, ChromosomeType): """ CxOnePoint for resources. :param ind1: first individual :param ind2: second individual - :param mate_positions: an array of positions that should be mate :param rand: the rand object used for exchange point selection :return: first and second individual """ - ind1 = copy_chromosome(ind1) - ind2 = copy_chromosome(ind2) + child1 = Individual(copy_chromosome(ind1)) + child2 = Individual(copy_chromosome(ind2)) - # exchange work resources - res1 = ind1[1][:, mate_positions] - res2 = ind2[1][:, mate_positions] - cxpoint = rand.randint(1, len(res1)) + res1 = child1[1] + res2 = child2[1] + num_works = len(res1) + border = num_works // 4 + cxpoint = rand.randint(border, num_works - border) - mate_positions = rand.sample(list(range(len(res1))), cxpoint) + mate_positions = rand.sample(range(num_works), cxpoint) res1[mate_positions], res2[mate_positions] = res2[mate_positions], res1[mate_positions] - return ind1, ind2 + return child1, child2 + + +def mutate_for_resources(ind: ChromosomeType, resources_border: np.ndarray, + mutpb: float, rand: random.Random) -> ChromosomeType: + """ + Mutation function for resources. + It changes selected numbers of workers in random work in a certain interval for this work. + + :return: mutate individual + """ + # select random number from interval from min to max from uniform distribution + res = ind[1] + res_count = len(res[0]) + for i, work_res in enumerate(res): + for type_of_res in range(res_count - 1): + if rand.random() < mutpb: + xl = resources_border[0, type_of_res, i] + xu = resources_border[1, type_of_res, i] + contractor = work_res[-1] + border = ind[2][contractor, type_of_res] + # TODO Debug why min(xu, border) can be lower than xl + work_res[type_of_res] = rand.randint(xl, max(xl, min(xu, border))) + if rand.random() < mutpb: + work_res[-1] = rand.randint(0, len(ind[2]) - 1) + + return ind def mate_for_resource_borders(ind1: ChromosomeType, ind2: ChromosomeType, - mate_positions: np.ndarray, rand: random.Random) -> (ChromosomeType, ChromosomeType): + rand: random.Random) -> (ChromosomeType, ChromosomeType): """ Crossover for contractors' resource borders. """ - ind1 = copy_chromosome(ind1) - ind2 = copy_chromosome(ind2) + child1 = Individual(copy_chromosome(ind1)) + child2 = Individual(copy_chromosome(ind2)) - num_contractors = len(ind1[2]) - contractors_to_mate = rand.sample(list(range(num_contractors)), rand.randint(1, num_contractors)) + borders1 = child1[2] + borders2 = child2[2] + num_contractors = len(borders1) + contractors = rand.sample(range(num_contractors), rand.randint(1, num_contractors)) - if rand.randint(0, 2) == 0: - # trying to mate whole contractors - border1 = ind1[2][contractors_to_mate] - border2 = ind2[2][contractors_to_mate] - border1[:], border2[:] = border2[:], border1[:] - else: - # trying to mate part of selected contractors - border1 = ind1[2][contractors_to_mate] - border2 = ind2[2][contractors_to_mate] - for c_border1, c_border2 in zip(border1, border2): - # mate_positions = rand.sample(list(range(len(c_border1))), rand.randint(1, len(c_border1))) - c_border1[mate_positions], c_border2[mate_positions] = c_border2[mate_positions], c_border1[mate_positions] - - return ind1, ind2 + num_res = len(borders1[0]) + res_indices = list(range(num_res)) + border = num_res // 4 + mate_positions = rand.sample(res_indices, rand.randint(border, num_res - border)) + + (borders1[contractors, mate_positions], + borders2[contractors, mate_positions]) = (borders2[contractors, mate_positions], + borders1[contractors, mate_positions]) + + return child1, child2 + + +def mutate_resource_borders(ind: ChromosomeType, resources_min_border: np.ndarray, + mutpb: float, rand: random.Random) -> ChromosomeType: + """ + Mutation for contractors' resource borders. + """ + num_resources = len(resources_min_border) + num_contractors = len(ind[2]) + type_of_res = rand.randint(0, len(ind[2][0]) - 1) + for contractor in range(num_contractors): + if rand.random() < mutpb: + ind[2][contractor][type_of_res] -= rand.randint(resources_min_border[type_of_res] + 1, + max(resources_min_border[type_of_res] + 1, + ind[2][contractor][type_of_res] // 10)) + if ind[2][contractor][type_of_res] <= 0: + ind[2][contractor][type_of_res] = 1 + + # find and correct all invalidated resource assignments + for work in range(len(ind[0])): + if ind[1][work][num_resources] == contractor: + ind[1][work][type_of_res] = min(ind[1][work][type_of_res], + ind[2][contractor][type_of_res]) + + return ind diff --git a/sampo/scheduler/genetic/schedule_builder.py b/sampo/scheduler/genetic/schedule_builder.py index e20c72cb..e4246222 100644 --- a/sampo/scheduler/genetic/schedule_builder.py +++ b/sampo/scheduler/genetic/schedule_builder.py @@ -1,18 +1,13 @@ -import math import random import time from typing import Callable import numpy as np -import seaborn as sns from deap import tools from deap.base import Toolbox -from deap.tools import initRepeat -from matplotlib import pyplot as plt -from pandas import DataFrame from sampo.scheduler.genetic.converter import convert_schedule_to_chromosome -from sampo.scheduler.genetic.operators import init_toolbox, ChromosomeType, Individual, copy_chromosome, \ +from sampo.scheduler.genetic.operators import init_toolbox, ChromosomeType, \ FitnessFunction, TimeFitness, is_chromosome_correct from sampo.scheduler.native_wrapper import NativeWrapper from sampo.scheduler.timeline.base import Timeline @@ -24,20 +19,20 @@ from sampo.schemas.schedule_spec import ScheduleSpec from sampo.schemas.time import Time from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator -from sampo.utilities.collections_util import reverse_dictionary def create_toolbox(wg: WorkGraph, contractors: list[Contractor], worker_pool: WorkerContractorPool, - selection_size: int, + population_size: int, mutate_order: float, mutate_resources: float, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, int]], + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], rand: random.Random, spec: ScheduleSpec = ScheduleSpec(), work_estimator: WorkTimeEstimator = None, - landscape: LandscapeConfiguration = LandscapeConfiguration()) -> tuple[Toolbox, np.ndarray]: + landscape: LandscapeConfiguration = LandscapeConfiguration()) \ + -> Toolbox: start = time.time() # preparing access-optimized data structures @@ -46,21 +41,15 @@ def create_toolbox(wg: WorkGraph, index2node: dict[int, GraphNode] = {index: node for index, node in enumerate(nodes)} work_id2index: dict[str, int] = {node.id: index for index, node in index2node.items()} worker_name2index = {worker_name: index for index, worker_name in enumerate(worker_pool)} - index2contractor = {ind: contractor.id for ind, contractor in enumerate(contractors)} index2contractor_obj = {ind: contractor for ind, contractor in enumerate(contractors)} - contractor2index = reverse_dictionary(index2contractor) + contractor2index = {contractor.id: ind for ind, contractor in enumerate(contractors)} worker_pool_indices = {worker_name2index[worker_name]: { contractor2index[contractor_id]: worker for contractor_id, worker in workers_of_type.items() } for worker_name, workers_of_type in worker_pool.items()} node_indices = list(range(len(nodes))) - contractors_capacity = np.zeros((len(contractors), len(worker_pool))) - for w_ind, cont2worker in worker_pool_indices.items(): - for c_ind, worker in cont2worker.items(): - contractors_capacity[c_ind][w_ind] = worker.count - resources_border = np.zeros((2, len(worker_pool), len(index2node))) - resources_min_border = np.zeros((len(worker_pool))) + resources_min_border = np.zeros(len(worker_pool)) for work_index, node in index2node.items(): for req in node.work_unit.worker_reqs: worker_index = worker_name2index[req.kind] @@ -90,11 +79,11 @@ def create_toolbox(wg: WorkGraph, for child in node_children: parents[child].append(node) - init_chromosomes: dict[str, tuple[ChromosomeType, int]] = \ + init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]] = \ {name: (convert_schedule_to_chromosome(wg, work_id2index, worker_name2index, contractor2index, contractor_borders, schedule, chromosome_spec, order), importance, chromosome_spec) - if schedule is not None else None + if schedule is not None else None for name, (schedule, order, chromosome_spec, importance) in init_schedules.items()} for name, chromosome in init_chromosomes.items(): @@ -111,22 +100,22 @@ def create_toolbox(wg: WorkGraph, index2node, work_id2index, worker_name2index, - index2contractor, index2contractor_obj, init_chromosomes, mutate_order, mutate_resources, - selection_size, + population_size, rand, spec, worker_pool_indices, contractor2index, contractor_borders, - resources_min_border, node_indices, parents, + resources_border, + resources_min_border, Time(0), - work_estimator), resources_border + work_estimator) def build_schedule(wg: WorkGraph, @@ -134,41 +123,36 @@ def build_schedule(wg: WorkGraph, worker_pool: WorkerContractorPool, population_size: int, generation_number: int, - selection_size: int, - mutate_order: float, - mutate_resources: float, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, int]], + mutpb_order: float, + mutpb_res: float, + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], rand: random.Random, spec: ScheduleSpec, landscape: LandscapeConfiguration = LandscapeConfiguration(), - fitness_constructor: Callable[[Callable[[list[ChromosomeType]], list[int]]], - FitnessFunction] = TimeFitness, + fitness_constructor: Callable[ + [Callable[[list[ChromosomeType]], list[int]]], FitnessFunction] = TimeFitness, work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), - show_fitness_graph: bool = False, n_cpu: int = 1, assigned_parent_time: Time = Time(0), timeline: Timeline | None = None, - time_border: int = None) \ + time_border: int = None, + optimize_resources: bool = False) \ -> tuple[ScheduleWorkDict, Time, Timeline, list[GraphNode]]: """ Genetic algorithm. Structure of chromosome: - [[order of job], [numbers of workers types 1 for each job], [numbers of workers types 2], ... ] + [[order of job], + [[numbers of workers types 1 for each job], [numbers of workers types 2], ... ], + [[border of workers types 1 for each contractor], [border of workers types 2], ...] + ] Different mate and mutation for order and for workers. Generate order of job by prioritization from HEFTs and from Topological. Generate resources from min to max. Overall initial population is valid. - :param spec: spec for current scheduling - :param n_cpu: number or parallel workers to use in computational process - :param assigned_parent_time: start time of the whole schedule(time shift) - :return: scheduler + :return: schedule """ - - if show_fitness_graph: - fitness_history = [] - global_start = time.time() # preparing access-optimized data structures @@ -200,35 +184,32 @@ def build_schedule(wg: WorkGraph, start = time.time() - toolbox, resources_border = create_toolbox(wg, contractors, worker_pool, selection_size, - mutate_order, mutate_resources, init_schedules, - rand, spec, work_estimator, landscape) + toolbox = create_toolbox(wg, contractors, worker_pool, population_size, mutpb_order, mutpb_res, init_schedules, + rand, spec, work_estimator, landscape) native = NativeWrapper(toolbox, wg, contractors, worker_name2index, worker_pool_indices, parents, work_estimator) # create population of a given size - pop = toolbox.population(size_population=population_size) + pop = toolbox.population(n=population_size) print(f'Toolbox initialization & first population took {(time.time() - start) * 1000} ms') - if not native.native: + if native.native: + native_start = time.time() + best_chromosome = native.run_genetic(pop, mutpb_order, mutpb_order, mutpb_res, mutpb_res, mutpb_res, mutpb_res, + population_size) + print(f'Native evaluated in {(time.time() - native_start) * 1000} ms') + else: # save best individuals hof = tools.HallOfFame(1, similar=compare_individuals) - # probability to participate in mutation and crossover for each individual - cxpb, mutpb = mutate_order, mutate_order - mutpb_res, cxpb_res = mutate_resources, mutate_resources - - # def evaluate_chromosomes(chromosomes: list[ChromosomeType]): - # return native.evaluate([chromo for chromo in chromosomes if toolbox.validate(chromo)]) - fitness_f = fitness_constructor(native.evaluate) start = time.time() # map to each individual fitness function - pop = [ind for ind in pop if toolbox.validate(ind[0])] - fitness = fitness_f.evaluate([ind[0] for ind in pop]) + pop = [ind for ind in pop if toolbox.validate(ind)] + fitness = fitness_f.evaluate(pop) evaluation_time = time.time() - start @@ -238,10 +219,7 @@ def build_schedule(wg: WorkGraph, hof.update(pop) best_fitness = hof[0].fitness.values[0] - if show_fitness_graph: - fitness_history.append(sum(fitness) / len(fitness)) - - generation = 0 + generation = 1 # the best fitness, track to increase performance by stopping evaluation when not decreasing prev_best_fitness = Time.inf() @@ -249,9 +227,9 @@ def build_schedule(wg: WorkGraph, start = time.time() plateau_steps = 0 - max_plateau_steps = generation_number # 8 + max_plateau_steps = generation_number - while generation < generation_number and plateau_steps < max_plateau_steps \ + while generation <= generation_number and plateau_steps < max_plateau_steps \ and (time_border is None or time.time() - global_start < time_border): print(f'-- Generation {generation}, population={len(pop)}, best time={best_fitness} --') if best_fitness == prev_best_fitness: @@ -260,178 +238,72 @@ def build_schedule(wg: WorkGraph, plateau_steps = 0 prev_best_fitness = best_fitness - # select individuals of next generation - # offspring = toolbox.select(pop, int(math.sqrt(len(pop)))) - offspring = toolbox.select(pop, selection_size) - - # clone selected individuals - # offspring = [toolbox.clone(ind) for ind in offspring] - - # operations for ORDER - # crossover - # take 2 individuals as input 1 modified individuals - # take after 1: (1,3,5) and (2,4,6) and get pairs 1,2; 3,4; 5,6 + rand.shuffle(pop) cur_generation = [] - for child1, child2 in zip(offspring[::2], offspring[1::2]): - if rand.random() < cxpb: - ind1, ind2 = toolbox.mate(child1[0], child2[0]) - # add to population - cur_generation.append(wrap(ind1)) - cur_generation.append(wrap(ind2)) - - # mutation - # take 1 individuals as input and return 1 individuals as output - for mutant in offspring: - if rand.random() < mutpb: - ind_order = toolbox.mutate(mutant[0][0]) - ind = copy_chromosome(mutant[0]) - ind = (ind_order[0], ind[1], ind[2], ind[3]) - # add to population - cur_generation.append(wrap(ind)) + for ind1, ind2 in zip(pop[::2], pop[1::2]): + # mate order + cur_generation.extend(toolbox.mate(ind1, ind2)) if worker_name2index: # operations for RESOURCES - # mutation - # select types for mutation - # numbers of changing types - number_of_type_for_changing = rand.randint(1, len(worker_name2index) - 1) - # workers type for changing(+1 means contractor 'resource') - workers = rand.sample(range(len(worker_name2index) + 1), number_of_type_for_changing) - - # resources mutation - for worker in workers: - low = resources_border[0, worker] if worker != len(worker_name2index) else 0 - up = resources_border[1, worker] if worker != len(worker_name2index) else 0 - for mutant in offspring: - if rand.random() < mutpb_res: - ind = toolbox.mutate_resources(mutant[0], low=low, up=up, type_of_worker=worker) - # add to population - cur_generation.append(wrap(ind)) - - # resource borders mutation - for worker in workers: - if worker == len(worker_name2index): - continue - for mutant in offspring: - if rand.random() < mutpb_res: - ind = toolbox.mutate_resource_borders(mutant[0], - type_of_worker=worker) - # add to population - cur_generation.append(wrap(ind)) - - # for the crossover, we use those types that did not participate - # in the mutation(+1 means contractor 'resource') - # workers_for_mate = list(set(list(range(len(worker_name2index) + 1))) - set(workers)) - # crossover - # take 2 individuals as input 1 modified individuals - - workers = rand.sample(range(len(worker_name2index) + 1), number_of_type_for_changing) - - for child1, child2 in zip(offspring[::2], offspring[1::2]): - for ind_worker in workers: - # mate resources - if rand.random() < cxpb_res: - ind1, ind2 = toolbox.mate_resources(child1[0], child2[0], ind_worker) - # add to population - cur_generation.append(wrap(ind1)) - cur_generation.append(wrap(ind2)) + for ind1, ind2 in zip(pop[:len(pop) // 2], pop[len(pop) // 2:]): + # mate resources + cur_generation.extend(toolbox.mate_resources(ind1, ind2)) + if optimize_resources: # mate resource borders - if rand.random() < cxpb_res: - if ind_worker == len(worker_name2index): - continue - ind1, ind2 = toolbox.mate_resource_borders(child1[0], child2[0], ind_worker) + cur_generation.extend(toolbox.mate_resource_borders(ind1, ind2)) + + for mutant in cur_generation: + # resources mutation + toolbox.mutate_resources(mutant) + if optimize_resources: + # resource borders mutation + toolbox.mutate_resource_borders(mutant) - # add to population - cur_generation.append(wrap(ind1)) - cur_generation.append(wrap(ind2)) + for mutant in cur_generation: + # order mutation + toolbox.mutate(mutant) evaluation_start = time.time() # Gather all the fitness in one list and print the stats - invalid_ind = [ind for ind in cur_generation if toolbox.validate(ind[0])] - # for each individual - evaluation - # print(pool.map(lambda x: x + 2, range(10))) + offspring = [ind for ind in cur_generation if toolbox.validate(ind)] - invalid_fit = fitness_f.evaluate([ind[0] for ind in invalid_ind]) - for fit, ind in zip(invalid_fit, invalid_ind): + offspring_fitness = fitness_f.evaluate(offspring) + for fit, ind in zip(offspring_fitness, offspring): ind.fitness.values = [fit] - evaluation_time += time.time() - evaluation_start - - # add mutant part of generation to offspring - offspring.extend(invalid_ind) - cur_generation.clear() - if show_fitness_graph: - _ftn = [f for f in fitness if not math.isinf(f)] - if len(_ftn) > 0: - fitness_history.append(sum(_ftn) / len(_ftn)) - - # pop_size = len(pop) - # pop = [ind for ind in pop if valid(ind)] - # print(f'----| Filtered out {pop_size - len(pop)} invalid individuals') + evaluation_time += time.time() - evaluation_start # renewing population - pop[:] = offspring - hof.update(pop) + pop += offspring + pop = toolbox.select(pop) + hof.update([pop[0]]) best_fitness = hof[0].fitness.values[0] - # best = hof[0] - # fits = [ind.fitness.values[0] for ind in pop] - # evaluation = chromosome_evaluation(best, index2node, resources_border, work_id2index, worker_name2index, - # parent2inseparable_son, agents) - # print('fits: ', fits) - # print(evaluation) generation += 1 native.close() - chromosome = hof[0][0] + best_chromosome = hof[0] # assert that we have valid chromosome assert hof[0].fitness.values[0] != Time.inf() - print(f'Final time: {hof[0].fitness.values[0]}') + print(f'Final time: {best_fitness}') print(f'Generations processing took {(time.time() - start) * 1000} ms') print(f'Evaluation time: {evaluation_time * 1000}') - else: - native_start = time.time() - chromosome = native.run_genetic(list([ind[0] for ind in pop]), - mutate_order, mutate_order, mutate_resources, mutate_resources, - mutate_resources, mutate_resources, selection_size) - print(f'Native evaluated in {(time.time() - native_start) * 1000} ms') - scheduled_works, schedule_start_time, timeline, order_nodes = toolbox.chromosome_to_schedule(chromosome, + scheduled_works, schedule_start_time, timeline, order_nodes = toolbox.chromosome_to_schedule(best_chromosome, landscape=landscape, timeline=timeline) - if show_fitness_graph: - sns.lineplot( - data=DataFrame.from_records([(generation * 4, v) for generation, v in enumerate(fitness_history)], - columns=['Поколение', 'Функция качества']), - x='Поколение', - y='Функция качества', - palette='r') - plt.show() - return {node.id: work for node, work in scheduled_works.items()}, schedule_start_time, timeline, order_nodes def compare_individuals(first: tuple[ChromosomeType], second: tuple[ChromosomeType]): - return (first[0][0] == second[0][0]).all() and (first[0][1] == second[0][1]).all() - - -def wrap(chromosome: ChromosomeType) -> Individual: - """ - Creates an individual from chromosome. - """ - - def ind_getter(): - return chromosome - - ind = initRepeat(Individual, ind_getter, n=1) - ind.fitness.invalid_steps = 0 - return ind + return (first[0] == second[0]).all() and (first[1] == second[1]).all() and (first[2] == second[2]).all() diff --git a/tests/scheduler/genetic/converter_test.py b/tests/scheduler/genetic/converter_test.py index ae926fe9..ef3ffd6b 100644 --- a/tests/scheduler/genetic/converter_test.py +++ b/tests/scheduler/genetic/converter_test.py @@ -1,13 +1,15 @@ from uuid import uuid4 from tests.scheduler.genetic.fixtures import * +from sampo.schemas.schedule import Schedule +from sampo.schemas.contractor import Contractor from sampo.scheduler.heft.base import HEFTScheduler from sampo.schemas.resources import Worker from sampo.utilities.validation import validate_schedule def test_convert_schedule_to_chromosome(setup_toolbox): - (tb, _), setup_wg, setup_contractors, _, setup_landscape_many_holders = setup_toolbox + tb, _, setup_wg, setup_contractors, _, setup_landscape_many_holders = setup_toolbox schedule = HEFTScheduler().schedule(setup_wg, setup_contractors, validate=True, landscape=setup_landscape_many_holders) @@ -16,7 +18,7 @@ def test_convert_schedule_to_chromosome(setup_toolbox): def test_convert_chromosome_to_schedule(setup_toolbox): - (tb, _), setup_wg, setup_contractors, _, _ = setup_toolbox + tb, _, setup_wg, setup_contractors, _, _ = setup_toolbox chromosome = tb.generate_chromosome() schedule, _, _, _ = tb.chromosome_to_schedule(chromosome) @@ -26,7 +28,7 @@ def test_convert_chromosome_to_schedule(setup_toolbox): def test_converter_with_borders_contractor_accounting(setup_toolbox): - (tb, _), setup_wg, setup_contractors, _, setup_landscape_many_holders = setup_toolbox + tb, _, setup_wg, setup_contractors, _, setup_landscape_many_holders = setup_toolbox chromosome = tb.generate_chromosome(landscape=setup_landscape_many_holders) diff --git a/tests/scheduler/genetic/fixtures.py b/tests/scheduler/genetic/fixtures.py index c8ab1540..14516e22 100644 --- a/tests/scheduler/genetic/fixtures.py +++ b/tests/scheduler/genetic/fixtures.py @@ -1,29 +1,16 @@ from random import Random -from typing import List, Dict, Tuple +from typing import Tuple -import numpy as np -from deap.base import Toolbox from pytest import fixture -from sampo.scheduler.genetic.converter import ChromosomeType, convert_schedule_to_chromosome -from sampo.scheduler.genetic.operators import init_toolbox +import numpy as np + from sampo.scheduler.genetic.schedule_builder import create_toolbox -from sampo.schemas.contractor import Contractor, WorkerContractorPool, get_worker_contractor_pool -from sampo.schemas.graph import WorkGraph, GraphNode -from sampo.schemas.landscape import LandscapeConfiguration -from sampo.schemas.schedule import Schedule -from sampo.schemas.schedule_spec import ScheduleSpec -from sampo.schemas.time import Time +from sampo.schemas.contractor import get_worker_contractor_pool from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator -from sampo.utilities.collections_util import reverse_dictionary -def get_params(works_count: int) -> Tuple[int, float, float, int]: - if works_count < 300: - size_selection = 20 - else: - size_selection = works_count // 15 - +def get_params(works_count: int) -> Tuple[float, float, int]: if works_count < 300: mutate_order = 0.006 else: @@ -40,7 +27,7 @@ def get_params(works_count: int) -> Tuple[int, float, float, int]: size_of_population = 50 else: size_of_population = works_count // 50 - return size_selection, mutate_order, mutate_resources, size_of_population + return mutate_order, mutate_resources, size_of_population @fixture @@ -48,18 +35,27 @@ def setup_toolbox(setup_default_schedules) -> tuple: (setup_wg, setup_contractors, setup_landscape_many_holders), setup_default_schedules = setup_default_schedules setup_worker_pool = get_worker_contractor_pool(setup_contractors) - selection_size, mutate_order, mutate_resources, size_of_population = get_params(setup_wg.vertex_count) + mutate_order, mutate_resources, size_of_population = get_params(setup_wg.vertex_count) rand = Random(123) work_estimator: WorkTimeEstimator = DefaultWorkEstimator() - return create_toolbox(setup_wg, - setup_contractors, - setup_worker_pool, - selection_size, - mutate_order, - mutate_resources, - setup_default_schedules, - rand, - work_estimator=work_estimator, - landscape=setup_landscape_many_holders), setup_wg, setup_contractors, setup_default_schedules, \ - setup_landscape_many_holders + nodes = [node for node in setup_wg.nodes if not node.is_inseparable_son()] + worker_name2index = {worker_name: index for index, worker_name in enumerate(setup_worker_pool)} + resources_border = np.zeros((2, len(setup_worker_pool), len(nodes))) + for work_index, node in enumerate(nodes): + for req in node.work_unit.worker_reqs: + worker_index = worker_name2index[req.kind] + resources_border[0, worker_index, work_index] = req.min_count + resources_border[1, worker_index, work_index] = req.max_count + + return (create_toolbox(setup_wg, + setup_contractors, + setup_worker_pool, + size_of_population, + mutate_order, + mutate_resources, + setup_default_schedules, + rand, + work_estimator=work_estimator, + landscape=setup_landscape_many_holders), resources_border, + setup_wg, setup_contractors, setup_default_schedules, setup_landscape_many_holders) diff --git a/tests/scheduler/genetic/full_scheduling.py b/tests/scheduler/genetic/full_scheduling.py index 193f1d6b..4eddc9d7 100644 --- a/tests/scheduler/genetic/full_scheduling.py +++ b/tests/scheduler/genetic/full_scheduling.py @@ -4,10 +4,9 @@ def test_multiprocessing(setup_scheduler_parameters): setup_wg, setup_contractors, setup_landscape = setup_scheduler_parameters - genetic = GeneticScheduler(number_of_generation=500, - mutate_order=1.0, - mutate_resources=1.0, - size_of_population=100, - size_selection=500) + genetic = GeneticScheduler(number_of_generation=50, + mutate_order=0.05, + mutate_resources=0.005, + size_of_population=50) genetic.schedule(setup_wg, setup_contractors, landscape=setup_landscape) diff --git a/tests/scheduler/genetic/operators_test.py b/tests/scheduler/genetic/operators_test.py index a8e49287..d6f582db 100644 --- a/tests/scheduler/genetic/operators_test.py +++ b/tests/scheduler/genetic/operators_test.py @@ -1,12 +1,13 @@ from tests.scheduler.genetic.fixtures import * from sampo.scheduler.genetic.converter import ChromosomeType +import random TEST_ITERATIONS = 10 def test_generate_individual(setup_toolbox): - (tb, _), _, _, _, _ = setup_toolbox + tb, _, _, _, _, _ = setup_toolbox for i in range(TEST_ITERATIONS): chromosome: ChromosomeType = tb.generate_chromosome() @@ -14,11 +15,11 @@ def test_generate_individual(setup_toolbox): def test_mutate_order(setup_toolbox): - (tb, _), _, _, _, _ = setup_toolbox + tb, _, _, _, _, _ = setup_toolbox for i in range(TEST_ITERATIONS): individual = tb.generate_chromosome() - mutant = tb.mutate(individual[0]) + mutant = tb.mutate(individual) order = mutant[0] # check there are no duplications @@ -26,31 +27,25 @@ def test_mutate_order(setup_toolbox): def test_mutate_resources(setup_toolbox): - (tb, resources_border), _, _, _, _ = setup_toolbox - - rand = Random() + tb, _, _, _, _, _ = setup_toolbox for i in range(TEST_ITERATIONS): individual = tb.generate_chromosome() - type_of_resource = rand.randint(0, len(resources_border[0]) - 1) - mutant = tb.mutate_resources(individual, - resources_border[0][type_of_resource], - resources_border[1][type_of_resource], - type_of_resource) + mutant = tb.mutate_resources(individual) assert tb.validate(mutant) def test_mate_order(setup_toolbox, setup_wg): - (tb, resources_border), _, _, _, _ = setup_toolbox - _, _, _, population_size = get_params(setup_wg.vertex_count) + tb, _, _, _, _, _ = setup_toolbox + _, _, population_size = get_params(setup_wg.vertex_count) - population = tb.population(size_population=population_size) + population = tb.population(n=population_size) for i in range(TEST_ITERATIONS): - individual1, individual2 = tb.select(population, 2) + individual1, individual2 = population[:2] - individual1, individual2 = tb.mate(individual1[0], individual2[0]) + individual1, individual2 = tb.mate(individual1, individual2) order1 = individual1[0] order2 = individual2[0] @@ -60,23 +55,20 @@ def test_mate_order(setup_toolbox, setup_wg): def test_mate_resources(setup_toolbox, setup_wg): - (tb, resources_border), _, _, _, _ = setup_toolbox - _, _, _, population_size = get_params(setup_wg.vertex_count) + tb, resources_border, _, _, _, _ = setup_toolbox + _, _, population_size = get_params(setup_wg.vertex_count) - population = tb.population(size_population=population_size) - rand = Random() + population = tb.population(n=population_size) for i in range(TEST_ITERATIONS): - individual1, individual2 = tb.select(population, 2) - - worker = rand.sample(list(range(len(resources_border) + 1)), 1)[0] - individual1, individual2 = tb.mate_resources(individual1[0], individual2[0], worker) + individual1, individual2 = random.sample(population, 2) + individual1, individual2 = tb.mate_resources(individual1, individual2) # check there are correct resources at mate positions - assert (resources_border[0][worker] <= individual1[1][:, worker]).all() and \ - (individual1[1][:, worker] <= resources_border[1][worker]).all() - assert (resources_border[0][worker] <= individual1[1][:, worker]).all() and \ - (individual1[1][:, worker] <= resources_border[1][worker]).all() + assert (resources_border[0] <= individual1[1].T[:-1]).all() and \ + (individual1[1].T[:-1] <= resources_border[1]).all() + assert (resources_border[0] <= individual1[1].T[:-1]).all() and \ + (individual1[1].T[:-1] <= resources_border[1]).all() # check the whole chromosomes assert tb.validate(individual1) diff --git a/tests/scheduler/resources_in_time/basic_res_test.py b/tests/scheduler/resources_in_time/basic_res_test.py index ad85e57f..dda836a3 100644 --- a/tests/scheduler/resources_in_time/basic_res_test.py +++ b/tests/scheduler/resources_in_time/basic_res_test.py @@ -35,7 +35,11 @@ def test_genetic_deadline_planning(setup_scheduler_parameters): setup_wg, setup_contractors, landscape = setup_scheduler_parameters deadline = Time.inf() // 2 - scheduler = GeneticScheduler(fitness_constructor=DeadlineResourcesFitness.prepare(deadline)) + scheduler = GeneticScheduler(number_of_generation=50, + mutate_order=0.05, + mutate_resources=0.005, + size_of_population=50, + fitness_constructor=DeadlineResourcesFitness.prepare(deadline)) scheduler.set_deadline(deadline) try: