diff --git a/examples/field_development/field_development_scheduling.py b/examples/field_development/field_development_scheduling.py index 803a5580..f61b4fd2 100644 --- a/examples/field_development/field_development_scheduling.py +++ b/examples/field_development/field_development_scheduling.py @@ -1,18 +1,16 @@ -from matplotlib import pyplot as plt - -from sampo.utilities.schedule import remove_service_tasks +import warnings -from sampo.utilities.visualization.base import VisualizationMode +from matplotlib import pyplot as plt -from sampo.generator import get_contractor_by_wg # Warning!!! sampo~=0.1.1.77 +from sampo.generator.environment.contractor_by_wg import get_contractor_by_wg from sampo.scheduler.heft.base import HEFTScheduler -from sampo.schemas.graph import WorkGraph from sampo.schemas.contractor import Contractor +from sampo.schemas.graph import WorkGraph from sampo.structurator.base import graph_restructuring -from sampo.utilities.visualization.work_graph import work_graph_fig +from sampo.utilities.schedule import remove_service_tasks +from sampo.utilities.visualization.base import VisualizationMode from sampo.utilities.visualization.schedule import schedule_gant_chart_fig - -import warnings +from sampo.utilities.visualization.work_graph import work_graph_fig warnings.filterwarnings("ignore") # for matplotlib warning suppression diff --git a/experiments/algorithms_2_multi_agency.py b/experiments/algorithms_2_multi_agency.py index 54e3603a..8f9afeef 100644 --- a/experiments/algorithms_2_multi_agency.py +++ b/experiments/algorithms_2_multi_agency.py @@ -38,7 +38,7 @@ def log(message: str, logfile: IO): with open(f'algorithms_2_multi_agency_comparison.txt', 'w') as logfile: logger = partial(log, logfile=logfile) - bg = generate_block_graph(SyntheticBlockGraphType.RANDOM, 10, [1, 1, 1], lambda x: (30, 50), 0.5, + bg = generate_block_graph(SyntheticBlockGraphType.RANDOM, 10, [0, 1, 1], lambda x: (None, 50), 0.5, rand, obstruction_getter, 2, [3, 4], [3, 4], logger=logger) conjuncted = bg.to_work_graph() @@ -57,4 +57,4 @@ def log(message: str, logfile: IO): schedule = best_algo.schedule(conjuncted, contractors) - print(f'Best algo res res: {schedule.execution_time}') + print(f'Best algo res: {schedule.execution_time}') diff --git a/experiments/algorithms_efficiency.py b/experiments/algorithms_efficiency.py new file mode 100644 index 00000000..e6d26573 --- /dev/null +++ b/experiments/algorithms_efficiency.py @@ -0,0 +1,69 @@ +from functools import partial +from random import Random +from typing import IO + +from pathos.multiprocessing import ProcessingPool + +from sampo.generator import SimpleSynthetic +from sampo.scheduler.heft.base import HEFTBetweenScheduler +from sampo.scheduler.heft.base import HEFTScheduler +from sampo.scheduler.multi_agency.block_generator import SyntheticBlockGraphType, generate_block_graph +from sampo.scheduler.multi_agency.multi_agency import Agent, Manager +from sampo.scheduler.topological.base import TopologicalScheduler + +r_seed = Random().randint(0, 100000) +p_rand = SimpleSynthetic(rand=r_seed) +rand = Random(r_seed) + + +def obstruction_getter(i: int): + return None + # return OneInsertObstruction.from_static_graph(0.5, rand, p_rand.work_graph(SyntheticGraphType.SEQUENTIAL, 10)) + + +def log(message: str, logfile: IO): + # print(message) + logfile.write(message + '\n') + + +def run_iteration(args): + iteration = args[0] + schedulers = [HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()] + + blocks_received = {str(scheduler): 0 for scheduler in schedulers} + for i in range(1, 5): + with open(f'algorithms_efficiency_block_size_{50 * i}_iteration_{iteration}.txt', 'w') as logfile: + logger = partial(log, logfile=logfile) + logger(f'block_size ~ {50 * i}') + + for graph_type in SyntheticBlockGraphType: + contractors = [p_rand.contractor(10) for _ in range(len(schedulers))] + + agents = [Agent(f'Agent {i}', schedulers[i % len(schedulers)], [contractor]) + for i, contractor in enumerate(contractors)] + manager = Manager(agents) + + bg = generate_block_graph(graph_type, 10, [1, 1, 1], lambda x: (None, 50 * i), 0.5, + rand, obstruction_getter, 2, [3, 4], [3, 4], logger=logger) + + scheduled_blocks = manager.manage_blocks(bg, logger=logger) + + # aggregate statistics + for sblock in scheduled_blocks.values(): + blocks_received[str(sblock.agent.scheduler)] += 1 + + # downtimes + logger(' '.join([str(agent.downtime.value) for agent in agents])) + + logger('') + + print('Received blocks statistics:') + for scheduler, blocks in blocks_received.items(): + print(f'{scheduler} {blocks}') + + +if __name__ == '__main__': + pool = ProcessingPool(10) + args = [[i] for i in range(10)] + + pool.map(run_iteration, args) diff --git a/experiments/genetic_2_multi_agency.py b/experiments/genetic_2_multi_agency.py index cf2f0db7..0e2d6723 100644 --- a/experiments/genetic_2_multi_agency.py +++ b/experiments/genetic_2_multi_agency.py @@ -30,7 +30,7 @@ def obstruction_getter(i: int): for i, contractor in enumerate(contractors)] manager = Manager(agents) - bg = generate_block_graph(SyntheticBlockGraphType.Sequential, 100, [1, 0, 0], lambda x: (30, 50), 0.2, + bg = generate_block_graph(SyntheticBlockGraphType.SEQUENTIAL, 100, [1, 0, 0], lambda x: (None, 50), 0.2, rand, obstruction_getter, 2, [3, 4] * 1, [3, 4] * 1, logger=print) conjuncted = bg.to_work_graph() diff --git a/experiments/genetic_init.py b/experiments/genetic_init.py new file mode 100644 index 00000000..7c4271ad --- /dev/null +++ b/experiments/genetic_init.py @@ -0,0 +1,61 @@ +from pathos.multiprocessing import ProcessingPool + +from sampo.generator.base import SimpleSynthetic +from sampo.generator.environment.contractor_by_wg import get_contractor_by_wg +from sampo.scheduler.genetic.base import GeneticScheduler +from sampo.schemas.time import Time + + +def run_test(args) -> list[tuple[Time, Time]]: + graph_size, iterations = args + + result = [] + for i in range(iterations): + ss = SimpleSynthetic() + wg = ss.work_graph(top_border=graph_size) + contractors = [get_contractor_by_wg(wg)] + + baseline_genetic = GeneticScheduler(mutate_order=1.0, + mutate_resources=1.0, + size_selection=200, + size_of_population=200) + + optimized_genetic = GeneticScheduler(mutate_order=1.0, + mutate_resources=1.0, + size_selection=200, + size_of_population=200) + optimized_genetic.set_weights([14, 11, 1, 1, 1, 1, 10]) + + baseline_result = baseline_genetic.schedule(wg, contractors) + my_result = optimized_genetic.schedule(wg, contractors) + + result.append((baseline_result.execution_time, my_result.execution_time)) + + return result + + +if __name__ == '__main__': + num_iterations = 50 + + sizes = [200 * i for i in range(1, num_iterations + 1)] + # iterations = [5 - i for i in range(1, num_iterations + 1)] + iterations = [10 for i in range(1, num_iterations + 1)] + + with ProcessingPool(10) as p: + results_by_size = p.map(run_test, zip(sizes, iterations)) + + print('-------------------------------------------------------------') + print('-------------------------| Results |-------------------------') + print('-------------------------------------------------------------') + with open('genetic_init_res.txt', 'w') as f: + for graph_size, result_list in zip(sizes, results_by_size): + global_ratio = 0 + for baseline_result, my_result in result_list: + ratio = baseline_result / my_result + global_ratio += ratio + global_ratio /= len(result_list) + + res_string = f'Size: {graph_size}, upgrade ratio: {global_ratio}' + print(res_string) + f.write(res_string) + f.write('\n') diff --git a/experiments/modular_examples/comparison_plots.py b/experiments/modular_examples/comparison_plots.py index 446d3b43..7e840ed8 100644 --- a/experiments/modular_examples/comparison_plots.py +++ b/experiments/modular_examples/comparison_plots.py @@ -36,9 +36,10 @@ # parsing raw data -def parse_raw_data(mode_index: int, iterations: int, algos: list[str], algo_labels: list[str]): +def parse_raw_data(block_size: int, iterations: int, algos: list[str], algo_labels: list[str]): for launch_index in range(iterations): - with open(f'algorithms_comparison_block_size_{mode_index}_{launch_index}.txt', 'r') as f: + # with open(f'algorithms_comparison_block_size_{mode_index}_{launch_index}.txt', 'r') as f: + with open(f'algorithms_efficiency_block_size_{block_size}_iteration_{launch_index}.txt', 'r') as f: mode = None finished = True bg_info_read = False @@ -72,7 +73,7 @@ def parse_raw_data(mode_index: int, iterations: int, algos: list[str], algo_labe bg_info = line.split(' ') bg_info_read = True continue - if i == 10 or (i == 7 and bg_info[0] == 'Queues'): + if i == 10 or (i == 7 and bg_info[0] == 'QUEUES'): finished = True downtimes = [int(downtime) for downtime in line.split(' ')] for algo_ind, algo in enumerate(algos): @@ -199,11 +200,11 @@ def compare_algos_block_type(title: str, compare_dict: Dict, algo_labels: list[s # boxplot block type comparison def boxplot_compare_algos_block_type(title: str, compare_dict: Dict, algo_labels: list[str]): - fig = plt.figure(figsize=(14, 10)) + fig = plt.figure(figsize=(15, 5)) fig.suptitle(title, fontsize=32) for i, freq_by_step in enumerate(compare_dict.items()): values = np.array([[f for f in freq.values()] for freq in freq_by_step[1].values()]) - ax = fig.add_subplot(33 * 10 + i + 1) + ax = fig.add_subplot(13 * 10 + i + 1) ax.set(title=algo_labels[i]) ax.set_xlabel('Block type') ax.set_ylabel('Count') @@ -220,13 +221,11 @@ def boxplot_compare_algos_block_type(title: str, compare_dict: Dict, algo_labels # all algorithms -# algos = ['HEFTAddEnd', 'HEFTAddBetween', 'Topological', -# 'Genetic[generations=50,size_selection=None,mutate_order=None,mutate_resources=None]'] -# algo_labels = ['HEFTAddEnd', 'HEFTAddBetween', 'Topological', -# 'Genetic[\ngenerations=50,size_selection=None,\nmutate_order=None,mutate_resources=None]'] -# +algos = ['HEFTAddEnd', 'HEFTAddBetween', 'Topological'] +algo_labels = ['HEFTAddEnd', 'HEFTAddBetween', 'Topological'] + # parse_raw_data(0, 10, algos, algo_labels) -# + # compare_algos_general('Algorithms block receive count - average', global_algo_frequencies, algos, algo_labels) # compare_algos_general('Algorithms downtimes - average', global_algo_downtimes, algos, algo_labels) # @@ -238,53 +237,60 @@ def boxplot_compare_algos_block_type(title: str, compare_dict: Dict, algo_labels # global_algo_block_type_frequencies, algo_labels) # genetics -algos = ['Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.5]', - 'Genetic[generations=50,size_selection=100,mutate_order=0.25,mutate_resources=0.5]', - 'Genetic[generations=50,size_selection=100,mutate_order=0.5,mutate_resources=0.75]', - 'Genetic[generations=50,size_selection=100,mutate_order=0.75,mutate_resources=0.75]', - 'Genetic[generations=50,size_selection=50,mutate_order=0.9,mutate_resources=0.9]', - 'Genetic[generations=50,size_selection=100,mutate_order=0.5,mutate_resources=0.5]', - 'Genetic[generations=50,size_selection=200,mutate_order=0.25,mutate_resources=0.5]', - 'Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.75]', - 'Genetic[generations=50,size_selection=100,mutate_order=0.75,mutate_resources=0.75]', - 'Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.9]'] -# algo_labels = ['first_population=100,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.5', -# 'first_population=100,\nsize_selection=100,\nmutate_order=0.25,\nmutate_resources=0.5', -# 'first_population=100,\nsize_selection=100,\nmutate_order=0.5,\nmutate_resources=0.75', -# 'first_population=100,\nsize_selection=100,\nmutate_order=0.75,\nmutate_resources=0.75', -# 'first_population=100,\nsize_selection=50,\nmutate_order=0.9\n,mutate_resources=0.9', -# 'first_population=500,\nsize_selection=100,\nmutate_order=0.5,\nmutate_resources=0.5]', -# 'first_population=500,\nsize_selection=200,\nmutate_order=0.25,\nmutate_resources=0.5]', -# 'first_population=500,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.75]', -# 'first_population=500,\nsize_selection=100,\nmutate_order=0.75,\nmutate_resources=0.75]', -# 'first_population=500,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.9]'] -algo_labels = [str(i) for i in range(1, 10 + 1)] - -# clear previous data -global_algo_frequencies.clear() -global_algo_downtimes.clear() -global_algo_bg_type_frequencies.clear() -global_algo_bg_type_downtimes.clear() -global_algo_block_type_frequencies.clear() -global_algo_block_type_downtimes.clear() -launch_algo_frequencies.clear() -launch_algo_downtimes.clear() -launch_algo_bg_type_frequencies.clear() -launch_algo_bg_type_downtimes.clear() -launch_algo_block_type_frequencies.clear() -launch_algo_block_type_downtimes.clear() - -parse_raw_data(1, 1, algos, algo_labels) - -boxplot_compare_algos_general('Received blocks - genetics comparison', launch_algo_frequencies, algos, algo_labels) -boxplot_compare_algos_general('Downtimes blocks - genetics comparison', launch_algo_downtimes, algos, algo_labels) - -boxplot_compare_algos_bg_type('Received blocks - genetics with graph types comparison', +# algos = ['Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.5]', +# 'Genetic[generations=50,size_selection=100,mutate_order=0.25,mutate_resources=0.5]', +# 'Genetic[generations=50,size_selection=100,mutate_order=0.5,mutate_resources=0.75]', +# 'Genetic[generations=50,size_selection=100,mutate_order=0.75,mutate_resources=0.75]', +# 'Genetic[generations=50,size_selection=50,mutate_order=0.9,mutate_resources=0.9]', +# 'Genetic[generations=50,size_selection=100,mutate_order=0.5,mutate_resources=0.5]', +# 'Genetic[generations=50,size_selection=200,mutate_order=0.25,mutate_resources=0.5]', +# 'Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.75]', +# 'Genetic[generations=50,size_selection=100,mutate_order=0.75,mutate_resources=0.75]', +# 'Genetic[generations=50,size_selection=50,mutate_order=0.5,mutate_resources=0.9]'] +# # algo_labels = ['first_population=100,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.5', +# # 'first_population=100,\nsize_selection=100,\nmutate_order=0.25,\nmutate_resources=0.5', +# # 'first_population=100,\nsize_selection=100,\nmutate_order=0.5,\nmutate_resources=0.75', +# # 'first_population=100,\nsize_selection=100,\nmutate_order=0.75,\nmutate_resources=0.75', +# # 'first_population=100,\nsize_selection=50,\nmutate_order=0.9\n,mutate_resources=0.9', +# # 'first_population=500,\nsize_selection=100,\nmutate_order=0.5,\nmutate_resources=0.5]', +# # 'first_population=500,\nsize_selection=200,\nmutate_order=0.25,\nmutate_resources=0.5]', +# # 'first_population=500,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.75]', +# # 'first_population=500,\nsize_selection=100,\nmutate_order=0.75,\nmutate_resources=0.75]', +# # 'first_population=500,\nsize_selection=50,\nmutate_order=0.5,\nmutate_resources=0.9]'] +# algo_labels = [str(i) for i in range(1, 10 + 1)] +# +# # clear previous data +# global_algo_frequencies.clear() +# global_algo_downtimes.clear() +# global_algo_bg_type_frequencies.clear() +# global_algo_bg_type_downtimes.clear() +# global_algo_block_type_frequencies.clear() +# global_algo_block_type_downtimes.clear() +# launch_algo_frequencies.clear() +# launch_algo_downtimes.clear() +# launch_algo_bg_type_frequencies.clear() +# launch_algo_bg_type_downtimes.clear() +# launch_algo_block_type_frequencies.clear() +# launch_algo_block_type_downtimes.clear() +# +parse_raw_data(200, 10, algos, algo_labels) + +comparison_class = 'algorithms' + +boxplot_compare_algos_general(f'Received blocks - {comparison_class} comparison', launch_algo_frequencies, algos, algo_labels) +boxplot_compare_algos_general(f'Downtimes blocks - {comparison_class} comparison', launch_algo_downtimes, algos, algo_labels) + +boxplot_compare_algos_bg_type(f'Received blocks - {comparison_class} with graph types comparison', launch_algo_bg_type_frequencies, algo_labels) -boxplot_compare_algos_bg_type('Downtimes - genetics with graph types comparison', +boxplot_compare_algos_bg_type(f'Downtimes - {comparison_class} with graph types comparison', launch_algo_bg_type_downtimes, algo_labels) -boxplot_compare_algos_block_type('Received blocks - genetics with block types comparison', +for v in launch_algo_block_type_frequencies.values(): + for vv in v.values(): + for block_type in ['GENERAL', 'SEQUENTIAL', 'PARALLEL']: + temp = vv[block_type] + +boxplot_compare_algos_block_type(f'Received blocks - {comparison_class} with block types comparison', launch_algo_block_type_frequencies, algo_labels) diff --git a/sampo/scheduler/genetic/base.py b/sampo/scheduler/genetic/base.py index 7d20c2b9..6487e73b 100644 --- a/sampo/scheduler/genetic/base.py +++ b/sampo/scheduler/genetic/base.py @@ -40,6 +40,7 @@ def __init__(self, rand: Optional[random.Random] = None, seed: Optional[float or None] = None, n_cpu: int = 1, + weights: list[int] = None, fitness_constructor: Callable[[Toolbox], FitnessFunction] = TimeFitness, scheduler_type: SchedulerType = SchedulerType.Genetic, resource_optimizer: ResourceOptimizer = IdentityResourceOptimizer(), @@ -56,6 +57,7 @@ def __init__(self, self.fitness_constructor = fitness_constructor self.work_estimator = work_estimator self._n_cpu = n_cpu + self._weights = None self._time_border = None self._deadline = None @@ -125,11 +127,18 @@ def set_deadline(self, deadline: Time): """ self._deadline = deadline + def set_weights(self, weights: list[int]): + self._weights = weights + + @classmethod def generate_first_population(self, wg: WorkGraph, contractors: list[Contractor], landscape: LandscapeConfiguration = LandscapeConfiguration(), - spec: ScheduleSpec = ScheduleSpec()): + spec: ScheduleSpec = ScheduleSpec(), + work_estimator: WorkTimeEstimator = None, + deadline: Time = None, + weights=None): """ - Heuristic algorithm, that generate first population + Algorithm, that generate first population :param landscape: :param wg: graph of works @@ -138,44 +147,47 @@ def generate_first_population(self, wg: WorkGraph, contractors: list[Contractor] :return: """ + if weights is None: + weights = [2, 2, 1, 1, 1, 1] + def init_k_schedule(scheduler_class, k): try: - return scheduler_class(work_estimator=self.work_estimator, + return scheduler_class(work_estimator=work_estimator, resource_optimizer=AverageReqResourceOptimizer(k)) \ .schedule(wg, contractors, spec, - landscape=landscape), list(reversed(prioritization(wg, self.work_estimator))), spec + landscape=landscape), list(reversed(prioritization(wg, work_estimator))), spec except NoSufficientContractorError: return None, None, None - if self._deadline is None: + if deadline is None: def init_schedule(scheduler_class): try: - return scheduler_class(work_estimator=self.work_estimator).schedule(wg, contractors, - landscape=landscape), \ - list(reversed(prioritization(wg, self.work_estimator))), spec + return scheduler_class(work_estimator=work_estimator).schedule(wg, contractors, + landscape=landscape), \ + list(reversed(prioritization(wg, work_estimator))), spec except NoSufficientContractorError: return None, None, None + else: def init_schedule(scheduler_class): try: (schedule, _, _, _), modified_spec = AverageBinarySearchResourceOptimizingScheduler( - scheduler_class(work_estimator=self.work_estimator) - ).schedule_with_cache(wg, contractors, self._deadline, spec, landscape=landscape) - return schedule, list(reversed(prioritization(wg, self.work_estimator))), modified_spec + scheduler_class(work_estimator=work_estimator) + ).schedule_with_cache(wg, contractors, deadline, spec, landscape=landscape) + return schedule, list(reversed(prioritization(wg, work_estimator))), modified_spec except NoSufficientContractorError: return None, None, None return { - "heft_end": init_schedule(HEFTScheduler), - "heft_between": init_schedule(HEFTBetweenScheduler), - "12.5%": init_k_schedule(HEFTScheduler, 8), - "25%": init_k_schedule(HEFTScheduler, 4), - "75%": init_k_schedule(HEFTScheduler, 4 / 3), - "87.5%": init_k_schedule(HEFTScheduler, 8 / 7) + "heft_end": (*init_schedule(HEFTScheduler), weights[0]), + "heft_between": (*init_schedule(HEFTBetweenScheduler), weights[1]), + "12.5%": (*init_k_schedule(HEFTScheduler, 8), weights[2]), + "25%": (*init_k_schedule(HEFTScheduler, 4), weights[3]), + "75%": (*init_k_schedule(HEFTScheduler, 4 / 3), weights[4]), + "87.5%": (*init_k_schedule(HEFTScheduler, 8 / 7), weights[5]) } - def schedule_with_cache(self, wg: WorkGraph, contractors: list[Contractor], @@ -198,7 +210,8 @@ def schedule_with_cache(self, :param timeline: :return: """ - init_schedules = self.generate_first_population(wg, contractors, landscape) + 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) worker_pool = get_worker_contractor_pool(contractors) diff --git a/sampo/scheduler/genetic/operators.py b/sampo/scheduler/genetic/operators.py index f2dfb6f8..87665272 100644 --- a/sampo/scheduler/genetic/operators.py +++ b/sampo/scheduler/genetic/operators.py @@ -1,3 +1,4 @@ +import math import random from abc import ABC, abstractmethod from copy import deepcopy @@ -6,6 +7,7 @@ import numpy as np from deap import creator, base, tools +from deap.tools import initRepeat from sampo.scheduler.genetic.converter import convert_chromosome_to_schedule from sampo.scheduler.genetic.converter import convert_schedule_to_chromosome, ChromosomeType @@ -129,7 +131,7 @@ def init_toolbox(wg: WorkGraph, worker_name2index: dict[str, int], index2contractor: dict[int, str], index2contractor_obj: dict[int, Contractor], - init_chromosomes: dict[str, ChromosomeType], + init_chromosomes: dict[str, tuple[ChromosomeType, float]], mutate_order: float, mutate_resources: float, selection_size: int, @@ -138,6 +140,7 @@ def init_toolbox(wg: WorkGraph, 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]], assigned_parent_time: Time = Time(0), @@ -158,7 +161,10 @@ def init_toolbox(wg: WorkGraph, # 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', tools.initRepeat, list, toolbox.individual) + toolbox.register('population', generate_population, wg=wg, contractors=contractors, spec=spec, + work_id2index=work_id2index, worker_name2index=worker_name2index, + contractor2index=contractor2index, contractor_borders=contractor_borders, + init_chromosomes=init_chromosomes, rand=rand, work_estimator=work_estimator, landscape=landscape) # crossover for order toolbox.register('mate', mate_scheduling_order, rand=rand) # mutation for order. Coefficient luke one or two mutation in individual @@ -172,7 +178,8 @@ def init_toolbox(wg: WorkGraph, 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) + probability_mutate_contractors=mutate_resources, rand=rand, + contractors_capacity=contractor_borders, resources_min_border=resources_min_border) # crossover for resources toolbox.register('mate_resources', mate_for_resources, rand=rand) # crossover for resource borders @@ -195,13 +202,73 @@ 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, + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec, + work_id2index: dict[str, int], + worker_name2index: dict[str, int], + contractor2index: dict[str, int], + contractor_borders: np.ndarray, + init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]], + rand: random.Random, + work_estimator: WorkTimeEstimator = None, + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + """ + Generates population using chromosome weights. + Do not use `generate_chromosome` function. + """ + def randomized_init() -> ChromosomeType: + schedule = RandomizedTopologicalScheduler(work_estimator, + int(rand.random() * 1000000)) \ + .schedule(wg, contractors, spec, 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) + + weights_multiplier = math.ceil(size_population / sum(weights)) + + for i in range(len(weights)): + weights[i] *= weights_multiplier + + all_types = ['heft_end', 'heft_between', '12.5%', '25%', '75%', '87.5%', 'topological'] + chromosome_types = rand.sample(all_types, k=size_population, counts=weights) + + chromosomes = [init_chromosomes[generated_type][0] if generated_type != 'topological' else None + 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] + + def generate_chromosome(wg: WorkGraph, contractors: list[Contractor], work_id2index: dict[str, int], worker_name2index: dict[str, int], contractor2index: dict[str, int], contractor_borders: np.ndarray, - init_chromosomes: dict[str, ChromosomeType], + init_chromosomes: dict[str, tuple[ChromosomeType, float]], spec: ScheduleSpec, rand: random.Random, work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), @@ -222,21 +289,20 @@ 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'] + chromosome = init_chromosomes['heft_end'][0] elif chance < 0.4: - chromosome = init_chromosomes['heft_between'] + chromosome = init_chromosomes['heft_between'][0] elif chance < 0.5: - chromosome = init_chromosomes['12.5%'] + chromosome = init_chromosomes['12.5%'][0] elif chance < 0.6: - chromosome = init_chromosomes['25%'] + chromosome = init_chromosomes['25%'][0] elif chance < 0.7: - chromosome = init_chromosomes['75%'] + chromosome = init_chromosomes['75%'][0] elif chance < 0.8: - chromosome = init_chromosomes['87.5%'] - else: - chromosome = randomized_init() + chromosome = init_chromosomes['87.5%'][0] if chromosome is None: chromosome = randomized_init() diff --git a/sampo/scheduler/genetic/schedule_builder.py b/sampo/scheduler/genetic/schedule_builder.py index ea12efa0..e20c72cb 100644 --- a/sampo/scheduler/genetic/schedule_builder.py +++ b/sampo/scheduler/genetic/schedule_builder.py @@ -6,6 +6,7 @@ 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 @@ -26,6 +27,108 @@ from sampo.utilities.collections_util import reverse_dictionary +def create_toolbox(wg: WorkGraph, + contractors: list[Contractor], + worker_pool: WorkerContractorPool, + selection_size: int, + mutate_order: float, + mutate_resources: float, + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, int]], + rand: random.Random, + spec: ScheduleSpec = ScheduleSpec(), + work_estimator: WorkTimeEstimator = None, + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> tuple[Toolbox, np.ndarray]: + start = time.time() + + # preparing access-optimized data structures + nodes = [node for node in wg.nodes if not node.is_inseparable_son()] + + 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) + 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))) + for work_index, node in index2node.items(): + 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 + resources_min_border[worker_index] = max(resources_min_border[worker_index], req.min_count) + + contractor_borders = np.zeros((len(contractor2index), len(worker_name2index)), dtype=int) + for ind, contractor in enumerate(contractors): + for ind_worker, worker in enumerate(contractor.workers.values()): + contractor_borders[ind, ind_worker] = worker.count + + # construct inseparable_child -> inseparable_parent mapping + inseparable_parents = {} + for node in nodes: + for child in node.get_inseparable_chain_with_self(): + inseparable_parents[child] = node + + # here we aggregate information about relationships from the whole inseparable chain + children = {work_id2index[node.id]: [work_id2index[inseparable_parents[child].id] + for inseparable in node.get_inseparable_chain_with_self() + for child in inseparable.children] + for node in nodes} + + parents = {work_id2index[node.id]: [] for node in nodes} + for node, node_children in children.items(): + for child in node_children: + parents[child].append(node) + + init_chromosomes: dict[str, tuple[ChromosomeType, int]] = \ + {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 + for name, (schedule, order, chromosome_spec, importance) in init_schedules.items()} + + for name, chromosome in init_chromosomes.items(): + if chromosome is not None: + if not is_chromosome_correct(chromosome[0], node_indices, parents): + raise NoSufficientContractorError('HEFTs are deploying wrong chromosomes') + + print(f'Genetic optimizing took {(time.time() - start) * 1000} ms') + + return init_toolbox(wg, + contractors, + worker_pool, + landscape, + index2node, + work_id2index, + worker_name2index, + index2contractor, + index2contractor_obj, + init_chromosomes, + mutate_order, + mutate_resources, + selection_size, + rand, + spec, + worker_pool_indices, + contractor2index, + contractor_borders, + resources_min_border, + node_indices, + parents, + Time(0), + work_estimator), resources_border + + def build_schedule(wg: WorkGraph, contractors: list[Contractor], worker_pool: WorkerContractorPool, @@ -34,12 +137,12 @@ def build_schedule(wg: WorkGraph, selection_size: int, mutate_order: float, mutate_resources: float, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec]], + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, int]], rand: random.Random, spec: ScheduleSpec, landscape: LandscapeConfiguration = LandscapeConfiguration(), fitness_constructor: Callable[[Callable[[list[ChromosomeType]], list[int]]], - FitnessFunction] = TimeFitness, + FitnessFunction] = TimeFitness, work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), show_fitness_graph: bool = False, n_cpu: int = 1, @@ -68,39 +171,15 @@ def build_schedule(wg: WorkGraph, global_start = time.time() - start = time.time() # preparing access-optimized data structures nodes = [node for node in wg.nodes if not node.is_inseparable_son()] - index2node: dict[int, GraphNode] = dict(enumerate(nodes)) - work_id2index: dict[str, int] = {node.id: index for index, node in index2node.items()} + work_id2index: dict[str, int] = {node.id: index for index, node in enumerate(nodes)} 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 = dict(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))) - for work_index, node in index2node.items(): - 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 - resources_min_border[worker_index] = max(resources_min_border[worker_index], req.min_count) - - contractor_borders = np.zeros((len(contractor2index), len(worker_name2index)), dtype=int) - for ind, contractor in enumerate(contractors): - for ind_worker, worker in enumerate(contractor.workers.values()): - contractor_borders[ind, ind_worker] = worker.count # construct inseparable_child -> inseparable_parent mapping inseparable_parents = {} @@ -119,33 +198,16 @@ def build_schedule(wg: WorkGraph, for child in node_children: parents[child].append(node) - print(f'Genetic optimizing took {(time.time() - start) * 1000} ms') - start = time.time() - # initial chromosomes construction - init_chromosomes: dict[str, ChromosomeType] = \ - {name: convert_schedule_to_chromosome(wg, work_id2index, worker_name2index, - contractor2index, contractor_borders, schedule, spec, order) - if schedule is not None else None - for name, (schedule, order, spec) in init_schedules.items()} - - toolbox = init_toolbox(wg, contractors, worker_pool, landscape, index2node, - work_id2index, worker_name2index, index2contractor, - index2contractor_obj, init_chromosomes, mutate_order, - mutate_resources, selection_size, rand, spec, worker_pool_indices, - contractor2index, contractor_borders, node_indices, parents, - assigned_parent_time, work_estimator) - - for name, chromosome in init_chromosomes.items(): - if chromosome is not None: - if not is_chromosome_correct(chromosome, node_indices, parents): - raise NoSufficientContractorError('HEFTs are deploying wrong chromosomes') + toolbox, resources_border = create_toolbox(wg, contractors, worker_pool, selection_size, + mutate_order, mutate_resources, 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(n=population_size) + pop = toolbox.population(size_population=population_size) print(f'Toolbox initialization & first population took {(time.time() - start) * 1000} ms') @@ -199,7 +261,9 @@ def build_schedule(wg: WorkGraph, prev_best_fitness = best_fitness # select individuals of next generation - offspring = toolbox.select(pop, int(math.sqrt(len(pop)))) + # 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] @@ -253,8 +317,6 @@ def build_schedule(wg: WorkGraph, for mutant in offspring: if rand.random() < mutpb_res: ind = toolbox.mutate_resource_borders(mutant[0], - contractors_capacity=contractors_capacity, - resources_min_border=resources_min_border, type_of_worker=worker) # add to population cur_generation.append(wrap(ind)) diff --git a/sampo/scheduler/heft/base.py b/sampo/scheduler/heft/base.py index d96c114e..7818bbf2 100644 --- a/sampo/scheduler/heft/base.py +++ b/sampo/scheduler/heft/base.py @@ -4,7 +4,7 @@ from sampo.scheduler.generic import GenericScheduler from sampo.scheduler.heft.prioritization import prioritization from sampo.scheduler.resource.base import ResourceOptimizer -from sampo.scheduler.resource.coordinate_descent import CoordinateDescentResourceOptimizer +from sampo.scheduler.resource.full_scan import FullScanResourceOptimizer from sampo.scheduler.timeline.just_in_time_timeline import JustInTimeTimeline from sampo.scheduler.timeline.momentum_timeline import MomentumTimeline from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator @@ -19,7 +19,7 @@ class HEFTScheduler(GenericScheduler): def __init__(self, scheduler_type: SchedulerType = SchedulerType.HEFTAddEnd, - resource_optimizer: ResourceOptimizer = CoordinateDescentResourceOptimizer(dichotomy_int), + resource_optimizer: ResourceOptimizer = FullScanResourceOptimizer(), timeline_type: Type = JustInTimeTimeline, work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), prioritization_f: Callable = prioritization, @@ -39,7 +39,7 @@ class HEFTBetweenScheduler(HEFTScheduler): def __init__(self, scheduler_type: SchedulerType = SchedulerType.HEFTAddBetween, - resource_optimizer: ResourceOptimizer = CoordinateDescentResourceOptimizer(dichotomy_int), + resource_optimizer: ResourceOptimizer = FullScanResourceOptimizer(), work_estimator: WorkTimeEstimator = DefaultWorkEstimator()): super().__init__(scheduler_type, resource_optimizer, MomentumTimeline, resource_optimize_f=self.get_default_res_opt_function(self.get_finish_time), diff --git a/sampo/scheduler/multi_agency/block_generator.py b/sampo/scheduler/multi_agency/block_generator.py index 82533f76..0e841854 100644 --- a/sampo/scheduler/multi_agency/block_generator.py +++ b/sampo/scheduler/multi_agency/block_generator.py @@ -82,18 +82,18 @@ def generate_wg(mode, i): rev_edge_prob = int(1 / edge_prob) for idx, start in enumerate(bg.nodes): for end in bg.nodes[idx:]: - if start.wg.vertex_count > EMPTY_GRAPH_VERTEX_COUNT \ - and end.wg.vertex_count > EMPTY_GRAPH_VERTEX_COUNT: + if not start.is_service() and not end.is_service(): if start == end or rand.randint(0, rev_edge_prob) != 0: continue bg.add_edge(start, end) for node in bg.nodes: - if node.wg.vertex_count > EMPTY_GRAPH_VERTEX_COUNT: + if not node.is_service(): bg.add_edge(global_start, node) bg.add_edge(node, global_end) - logger(f'{graph_type.name} ' + ' '.join([str(mode.name) for mode in modes])) + logger(f'{graph_type.name} ' + ' '.join([str(mode.name) for i, mode in enumerate(modes) + if nodes[i].vertex_count != EMPTY_GRAPH_VERTEX_COUNT])) return bg @@ -182,6 +182,6 @@ def generate_wg(mode, i): nodes_all.append(parent) # logger(f'Generated queue {queue}: blocks={n_blocks}, edges={generated_edges}') - logger('Queues') + logger('QUEUES') return BlockGraph(nodes_all) diff --git a/sampo/scheduler/multi_agency/block_graph.py b/sampo/scheduler/multi_agency/block_graph.py index ceb8c308..ecf6b1c4 100644 --- a/sampo/scheduler/multi_agency/block_graph.py +++ b/sampo/scheduler/multi_agency/block_graph.py @@ -22,6 +22,9 @@ def __init__(self, wg: WorkGraph, obstruction: Obstruction | None = None): def id(self): return self.wg.start.id + def is_service(self): + return self.wg.vertex_count == 2 + def __hash__(self): return hash(self.id) diff --git a/sampo/scheduler/multi_agency/multi_agency.py b/sampo/scheduler/multi_agency/multi_agency.py index d26e5663..f70ae929 100644 --- a/sampo/scheduler/multi_agency/multi_agency.py +++ b/sampo/scheduler/multi_agency/multi_agency.py @@ -139,8 +139,8 @@ def manage_blocks(self, bg: BlockGraph, logger: Callable[[str], None] = None) -> assert start_time >= max_parent_time, f'Scheduler {agent._scheduler} does not handle parent_time!' - if logger: - logger(f'Scheduled {i} block: {agent._scheduler}') + if logger and not block.is_service(): + logger(f'{agent._scheduler}') sblock = ScheduledBlock(wg=block.wg, agent=agent, schedule=agent_schedule, start_time=start_time, end_time=end_time) diff --git a/sampo/scheduler/timeline/momentum_timeline.py b/sampo/scheduler/timeline/momentum_timeline.py index 2bf61f3b..2dd8053b 100644 --- a/sampo/scheduler/timeline/momentum_timeline.py +++ b/sampo/scheduler/timeline/momentum_timeline.py @@ -250,7 +250,7 @@ def _find_earliest_time_slot(state: SortedList[ScheduleEvent], # print(f'Warning! Probably cycle in looking for earliest time slot: {i} iteration') # print(f'Current start time: {current_start_time}, current start idx: {current_start_idx}') i += 1 - end_idx = state.bisect_right(current_start_time + exec_time + 1) + end_idx = state.bisect_right(current_start_time + exec_time) if spec.is_independent: if end_idx - current_start_idx > 1: @@ -265,7 +265,7 @@ def _find_earliest_time_slot(state: SortedList[ScheduleEvent], # that influence amount of available for us workers not_enough_workers_found = False for idx in range(end_idx - 1, current_start_idx - 2, -1): - if state[idx].available_workers_count < required_worker_count or state[idx].time <= parent_time: + if state[idx].available_workers_count < required_worker_count or state[idx].time < parent_time: # we're trying to find a new slot that would start with # either the last index passing the quantity check # or the index after the execution interval @@ -307,7 +307,7 @@ def update_timeline(self, swork = node2swork[node] # masking the whole chain ScheduleEvent with the first node start = swork.start_time - end = node2swork[node.get_inseparable_chain_with_self()[-1]].finish_time + 1 + end = node2swork[node.get_inseparable_chain_with_self()[-1]].finish_time for w in worker_team: state = self._timeline[w.contractor_id][w.name] start_idx = state.bisect_right(start) @@ -362,14 +362,14 @@ def _schedule_with_inseparables(self, start_time: Time, exec_times: dict[GraphNode, tuple[Time, Time]]): # 6. create a schedule entry for the task - nodes_start_times = {ins_node: ins_node.min_start_time(node2swork) for ins_node in inseparable_chain} + # nodes_start_times = {ins_node: ins_node.min_start_time(node2swork) for ins_node in inseparable_chain} curr_time = start_time for i, chain_node in enumerate(inseparable_chain): - _, node_time = exec_times[chain_node] + node_lag, node_time = exec_times[chain_node] - lag_req = nodes_start_times[chain_node] - curr_time - node_lag = lag_req if lag_req > 0 else 0 + # lag_req = nodes_start_times[chain_node] - curr_time + # node_lag = lag_req if lag_req > 0 else 0 start_work = curr_time + node_lag swork = ScheduledWork( diff --git a/sampo/schemas/resources.py b/sampo/schemas/resources.py index 43c6acde..a0a6eff4 100644 --- a/sampo/schemas/resources.py +++ b/sampo/schemas/resources.py @@ -24,6 +24,24 @@ class Resource(AutoJSONSerializable['Equipment'], Identifiable): count: int contractor_id: Optional[str] = "" + def __init__(self, + id: str, + name: str, + count: int, + contractor_id: Optional[str] = ""): + self.id = id + self.name = name + self._count = count + self.contractor_id = contractor_id + + @property + def count(self): + return self._count + + @count.setter + def count(self, value): + self._count = int(value) + # TODO: describe the function (description, return type) def get_agent_id(self) -> AgentId: return self.contractor_id, self.name @@ -47,7 +65,7 @@ def __init__(self, contractor_id: Optional[str] = "", productivity: Optional[IntervalGaussian] = IntervalGaussian(1, 0, 1, 1), cost_one_unit: Optional[float] = None): - super(Worker, self).__init__(id, name, count, contractor_id) + super(Worker, self).__init__(id, name, int(count), contractor_id) self.productivity = productivity if productivity is not None else IntervalGaussian(1, 0, 1, 1) self.cost_one_unit = cost_one_unit if cost_one_unit is not None else self.productivity.mean * 10 @@ -72,7 +90,7 @@ def with_count(self, count: int) -> 'Worker': :param count: amount of current type of worker :return: upgraded current object """ - self.count = count + self.count = int(count) return self def get_cost(self) -> float: diff --git a/tests/conftest.py b/tests/conftest.py index 3f61dd7f..3e09660e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,10 +9,7 @@ from sampo.generator.pipeline.project import get_start_stage, get_finish_stage from sampo.scheduler.base import SchedulerType from sampo.scheduler.generate import generate_schedule -from sampo.scheduler.heft.base import HEFTBetweenScheduler -from sampo.scheduler.heft.base import HEFTScheduler -from sampo.scheduler.resource.average_req import AverageReqResourceOptimizer -from sampo.scheduler.resource.full_scan import FullScanResourceOptimizer +from sampo.scheduler.genetic.base import GeneticScheduler from sampo.schemas.contractor import Contractor from sampo.schemas.exceptions import NoSufficientContractorError from sampo.schemas.graph import WorkGraph, EdgeType @@ -192,26 +189,11 @@ def setup_empty_contractors(setup_wg) -> list[Contractor]: def setup_default_schedules(setup_scheduler_parameters): work_estimator: WorkTimeEstimator = DefaultWorkEstimator() - setup_wg, setup_contractors, setup_landscape_many_holders = setup_scheduler_parameters - - def init_schedule(scheduler_class): - return scheduler_class(work_estimator=work_estimator, - resource_optimizer=FullScanResourceOptimizer()).schedule(setup_wg, setup_contractors, - landscape=setup_landscape_many_holders) - - def init_k_schedule(scheduler_class, k): - return scheduler_class(work_estimator=work_estimator, - resource_optimizer=AverageReqResourceOptimizer(k)).schedule(setup_wg, setup_contractors, - landscape=setup_landscape_many_holders) - - return setup_scheduler_parameters, { - 'heft_end': init_schedule(HEFTScheduler), - 'heft_between': init_schedule(HEFTBetweenScheduler), - '12.5%': init_k_schedule(HEFTScheduler, 8), - '25%': init_k_schedule(HEFTScheduler, 4), - '75%': init_k_schedule(HEFTScheduler, 4 / 3), - '87.5%': init_k_schedule(HEFTScheduler, 8 / 7) - } + setup_wg, setup_contractors, setup_landscape = setup_scheduler_parameters + + return setup_scheduler_parameters, GeneticScheduler.generate_first_population(setup_wg, setup_contractors, + setup_landscape, + work_estimator=work_estimator) @fixture(scope='session', diff --git a/tests/scheduler/genetic/fixtures.py b/tests/scheduler/genetic/fixtures.py index 535071cc..c8ab1540 100644 --- a/tests/scheduler/genetic/fixtures.py +++ b/tests/scheduler/genetic/fixtures.py @@ -7,6 +7,7 @@ from sampo.scheduler.genetic.converter import ChromosomeType, convert_schedule_to_chromosome from sampo.scheduler.genetic.operators import init_toolbox +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 @@ -42,96 +43,6 @@ def get_params(works_count: int) -> Tuple[int, float, float, int]: return size_selection, mutate_order, mutate_resources, size_of_population -def create_toolbox(wg: WorkGraph, - contractors: List[Contractor], - worker_pool: WorkerContractorPool, - selection_size: int, - mutate_order: float, - mutate_resources: float, - init_schedules: Dict[str, Schedule], - rand: Random, - spec: ScheduleSpec = ScheduleSpec(), - work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), - landscape: LandscapeConfiguration = LandscapeConfiguration()) -> Tuple[Toolbox, np.ndarray]: - nodes = [node for node in wg.nodes if not node.is_inseparable_son()] - - 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) - index2node_list = [(index, node) for index, node in enumerate(nodes)] - 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))) - for work_index, node in index2node.items(): - 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 - resources_min_border[worker_index] = max(resources_min_border[worker_index], req.min_count) - - contractor_borders = np.zeros((len(contractor2index), len(worker_name2index)), dtype=int) - for ind, contractor in enumerate(contractors): - for ind_worker, worker in enumerate(contractor.workers.values()): - contractor_borders[ind, ind_worker] = worker.count - - # construct inseparable_child -> inseparable_parent mapping - inseparable_parents = {} - for node in nodes: - for child in node.get_inseparable_chain_with_self(): - inseparable_parents[child] = node - - # here we aggregate information about relationships from the whole inseparable chain - children = {work_id2index[node.id]: [work_id2index[inseparable_parents[child].id] - for inseparable in node.get_inseparable_chain_with_self() - for child in inseparable.children] - for node in nodes} - - parents = {work_id2index[node.id]: [] for node in nodes} - for node, node_children in children.items(): - for child in node_children: - parents[child].append(node) - - init_chromosomes: Dict[str, ChromosomeType] = \ - {name: convert_schedule_to_chromosome(wg, work_id2index, worker_name2index, - contractor2index, contractor_borders, schedule, spec) - for name, schedule in init_schedules.items()} - - return init_toolbox(wg, - contractors, - worker_pool, - landscape, - index2node, - work_id2index, - worker_name2index, - index2contractor, - index2contractor_obj, - init_chromosomes, - mutate_order, - mutate_resources, - selection_size, - rand, - spec, - worker_pool_indices, - contractor2index, - contractor_borders, - node_indices, - parents, - Time(0), - work_estimator), resources_border - - @fixture def setup_toolbox(setup_default_schedules) -> tuple: (setup_wg, setup_contractors, setup_landscape_many_holders), setup_default_schedules = setup_default_schedules diff --git a/tests/scheduler/genetic/full_scheduling.py b/tests/scheduler/genetic/full_scheduling.py index 41df205a..193f1d6b 100644 --- a/tests/scheduler/genetic/full_scheduling.py +++ b/tests/scheduler/genetic/full_scheduling.py @@ -10,4 +10,4 @@ def test_multiprocessing(setup_scheduler_parameters): size_of_population=100, size_selection=500) - genetic.schedule(setup_wg, setup_contractors) + 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 3e87ff85..a8e49287 100644 --- a/tests/scheduler/genetic/operators_test.py +++ b/tests/scheduler/genetic/operators_test.py @@ -45,7 +45,7 @@ def test_mate_order(setup_toolbox, setup_wg): (tb, resources_border), _, _, _, _ = setup_toolbox _, _, _, population_size = get_params(setup_wg.vertex_count) - population = tb.population(n=population_size) + population = tb.population(size_population=population_size) for i in range(TEST_ITERATIONS): individual1, individual2 = tb.select(population, 2) @@ -63,7 +63,7 @@ def test_mate_resources(setup_toolbox, setup_wg): (tb, resources_border), _, _, _, _ = setup_toolbox _, _, _, population_size = get_params(setup_wg.vertex_count) - population = tb.population(n=population_size) + population = tb.population(size_population=population_size) rand = Random() for i in range(TEST_ITERATIONS): diff --git a/tests/utils/validation_test.py b/tests/utils/validation_test.py index 99b9fbfd..4b72f442 100644 --- a/tests/utils/validation_test.py +++ b/tests/utils/validation_test.py @@ -28,7 +28,7 @@ def is_resources_break(self) -> bool: def test_check_order_validity_right(setup_default_schedules): (setup_wg, _, _), setup_default_schedules = setup_default_schedules - for scheduler, schedule in setup_default_schedules.items(): + for scheduler, (schedule, _, _, _) in setup_default_schedules.items(): try: _check_all_tasks_scheduled(schedule, setup_wg) _check_parent_dependencies(schedule, setup_wg) @@ -39,7 +39,7 @@ def test_check_order_validity_right(setup_default_schedules): def test_check_order_validity_wrong(setup_default_schedules): (setup_wg, _, _), setup_default_schedules = setup_default_schedules - for schedule in setup_default_schedules.values(): + for (schedule, _, _, _) in setup_default_schedules.values(): for break_type in BreakType: if break_type.is_order_break(): broken = break_schedule(break_type, schedule, setup_wg) @@ -56,7 +56,7 @@ def test_check_order_validity_wrong(setup_default_schedules): def test_check_resources_validity_right(setup_default_schedules): (setup_wg, setup_contractors, _), setup_default_schedules = setup_default_schedules - for scheduler, schedule in setup_default_schedules.items(): + for scheduler, (schedule, _, _, _) in setup_default_schedules.items(): try: _check_all_workers_correspond_to_worker_reqs(schedule) _check_all_allocated_workers_do_not_exceed_capacity_of_contractors(schedule, setup_contractors) @@ -68,7 +68,7 @@ def test_check_resources_validity_wrong(setup_default_schedules): (setup_wg, setup_contractors, _), setup_default_schedules = setup_default_schedules setup_worker_pool = get_worker_contractor_pool(setup_contractors) - for schedule in setup_default_schedules.values(): + for (schedule, _, _, _) in setup_default_schedules.values(): for break_type in BreakType: if break_type.is_resources_break(): broken = break_schedule(break_type, schedule, setup_wg, setup_worker_pool)