Skip to content

Commit d92bac0

Browse files
committed
Finished cyclic hybridisation, experiments implemented
1 parent 2699e50 commit d92bac0

File tree

9 files changed

+236
-1024
lines changed

9 files changed

+236
-1024
lines changed

experiments/hybridisation.ipynb

Lines changed: 61 additions & 1001 deletions
Large diffs are not rendered by default.

experiments/hybridisation.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import json
2+
3+
import pathos
4+
from tqdm import tqdm
5+
6+
import sampo.scheduler
7+
from sampo.backend.multiproc import MultiprocessingComputationalBackend
8+
9+
from sampo.hybrid.population_tabu import TabuPopulationScheduler
10+
11+
from sampo.hybrid.cycle import CycleHybridScheduler
12+
from sampo.api.genetic_api import ScheduleGenerationScheme
13+
from sampo.scheduler import HEFTScheduler, HEFTBetweenScheduler, TopologicalScheduler, GeneticScheduler
14+
from sampo.hybrid.population import HeuristicPopulationScheduler, GeneticPopulationScheduler
15+
16+
from sampo.generator.environment import get_contractor_by_wg
17+
from sampo.generator import SimpleSynthetic
18+
19+
from sampo.base import SAMPO
20+
from sampo.schemas import WorkGraph
21+
22+
def run_experiment(args):
23+
graph_size, iteration = args
24+
25+
heuristics = HeuristicPopulationScheduler([HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()])
26+
# genetic1 = TabuPopulationScheduler()
27+
genetic1 = GeneticPopulationScheduler(GeneticScheduler(mutate_order=0.2,
28+
mutate_resources=0.2,
29+
sgs_type=ScheduleGenerationScheme.Parallel))
30+
genetic2 = GeneticPopulationScheduler(GeneticScheduler(mutate_order=0.001,
31+
mutate_resources=0.001,
32+
sgs_type=ScheduleGenerationScheme.Parallel))
33+
34+
hybrid_combine = CycleHybridScheduler(heuristics, [genetic1, genetic2], max_plateau_size=1)
35+
hybrid_genetic1 = CycleHybridScheduler(heuristics, [genetic1], max_plateau_size=1)
36+
hybrid_genetic2 = CycleHybridScheduler(heuristics, [genetic2], max_plateau_size=1)
37+
38+
wg = WorkGraph.load('wgs', f'{graph_size}_{iteration}')
39+
contractors = [get_contractor_by_wg(wg)]
40+
41+
# SAMPO.backend = MultiprocessingComputationalBackend(n_cpus=10)
42+
SAMPO.backend.cache_scheduler_info(wg, contractors)
43+
SAMPO.backend.cache_genetic_info()
44+
45+
schedule_hybrid_combine = hybrid_combine.schedule(wg, contractors)
46+
schedule_genetic1 = hybrid_genetic1.schedule(wg, contractors)
47+
schedule_genetic2 = hybrid_genetic2.schedule(wg, contractors)
48+
49+
# print(f'Hybrid combine: {schedule_hybrid_combine.execution_time}')
50+
# print(f'Scheduler 1 cycled: {schedule_genetic1.execution_time}')
51+
# print(f'Scheduler 2 cycled: {schedule_genetic2.execution_time}')
52+
return schedule_hybrid_combine.execution_time, schedule_genetic1.execution_time, schedule_genetic2.execution_time
53+
54+
if __name__ == '__main__':
55+
arguments = [(graph_size, iteration) for graph_size in [100, 200, 300, 400, 500] for iteration in range(5)]
56+
results = {graph_size: [] for graph_size in [100, 200, 300, 400, 500]}
57+
58+
with pathos.multiprocessing.Pool(processes=11) as p:
59+
r = p.map(run_experiment, arguments)
60+
61+
for (graph_size, _), (combined_time, time1, time2) in zip(arguments, r):
62+
results[graph_size].append((combined_time / time1, combined_time / time2))
63+
64+
with open('hybrid_results.json', 'w') as f:
65+
json.dump(results, f)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import json
2+
from itertools import chain
3+
from random import Random
4+
5+
import matplotlib.pyplot as plt
6+
7+
with open('hybrid_results.json', 'r') as f:
8+
results = json.load(f)
9+
10+
results = {int(graph_size): [100 * (1 - res) for res in chain(*result)]
11+
for graph_size, result in results.items()}
12+
graph_sizes = results.keys()
13+
14+
rand = Random()
15+
16+
plt.title('Прирост качества планов\nот применения гибридизации', fontsize=16)
17+
plt.xlabel('Размер графа')
18+
plt.ylabel('% прироста качества относительно базового алгоритма')
19+
plt.boxplot(results.values(), labels=graph_sizes)
20+
plt.show()

experiments/wg_generate.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from sampo.generator import SimpleSynthetic
2+
3+
from tqdm import tqdm
4+
5+
ss = SimpleSynthetic(rand=231)
6+
7+
for size in range(100, 500 + 1, 100):
8+
for i in tqdm(range(100)):
9+
wg = ss.work_graph(bottom_border=size - 5,
10+
top_border=size)
11+
while not (size - 5 <= wg.vertex_count <= size):
12+
wg = ss.work_graph(bottom_border=size - 20,
13+
top_border=size)
14+
wg.dump('wgs', f'{size}_{i}')

sampo/backend/multiproc.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ def cache_genetic_info(self,
139139
super().cache_genetic_info(population_size, mutate_order, mutate_resources, mutate_zones, deadline,
140140
weights, init_schedules, assigned_parent_time, fitness_weights, sgs_type,
141141
only_lft_initialization, is_multiobjective)
142-
self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, init_schedules, self._landscape)
142+
if init_schedules:
143+
self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, init_schedules,
144+
self._landscape)
145+
else:
146+
self._init_chromosomes = []
143147
self._pool = None
144148

145149
def compute_chromosomes(self, fitness: FitnessFunction, chromosomes: list[ChromosomeType]) -> list[float]:

sampo/hybrid/cycle.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ def run(self,
3939
cur_fitness = Time.inf().value
4040
plateau_steps = 0
4141

42-
while plateau_steps < self._max_plateau_size:
42+
while True:
4343
pop_fitness = self._get_population_fitness(pop)
4444
if pop_fitness == cur_fitness:
4545
plateau_steps += 1
46+
if plateau_steps == self._max_plateau_size:
47+
break
4648
else:
4749
plateau_steps = 0
4850
cur_fitness = pop_fitness
@@ -59,23 +61,7 @@ def schedule(self,
5961
assigned_parent_time: Time = Time(0),
6062
sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel,
6163
landscape: LandscapeConfiguration = LandscapeConfiguration()) -> Schedule:
62-
pop = self._starting_scheduler.schedule([], wg, contractors, spec, assigned_parent_time, landscape)
63-
64-
cur_fitness = Time.inf().value
65-
plateau_steps = 0
66-
67-
while plateau_steps < self._max_plateau_size:
68-
pop_fitness = self._get_population_fitness(pop)
69-
if pop_fitness == cur_fitness:
70-
plateau_steps += 1
71-
else:
72-
plateau_steps = 0
73-
cur_fitness = pop_fitness
74-
75-
for scheduler in self._cycle_schedulers:
76-
pop = scheduler.schedule(pop, wg, contractors, spec, assigned_parent_time, landscape)
77-
78-
best_ind = self._get_best_individual(pop)
64+
best_ind = self.run(wg, contractors, spec, assigned_parent_time, landscape)
7965

8066
toolbox = create_toolbox(wg=wg, contractors=contractors, landscape=landscape,
8167
assigned_parent_time=assigned_parent_time, spec=spec,

sampo/hybrid/population_tabu.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import numpy as np
2+
3+
from sampo.api.genetic_api import ChromosomeType, FitnessFunction
4+
from sampo.base import SAMPO
5+
from sampo.hybrid.population import PopulationScheduler
6+
from sampo.scheduler.genetic import TimeFitness
7+
from sampo.scheduler.utils import get_worker_contractor_pool
8+
from sampo.schemas import WorkGraph, Contractor, Time, LandscapeConfiguration
9+
from sampo.schemas.schedule_spec import ScheduleSpec
10+
11+
from sampo.scheduler.genetic.converter import ChromosomeType
12+
from tabusearch.experiments.scheduling.fixtures import setup_toolbox
13+
14+
from tabusearch.experiments.scheduling.scheduling_utils import get_optimiser, OptimiserLifetime
15+
from tabusearch.utility.chromosome import ChromosomeRW
16+
17+
18+
class TabuPopulationScheduler(PopulationScheduler):
19+
20+
def __init__(self, fitness: FitnessFunction = TimeFitness()):
21+
self._fitness = fitness
22+
23+
def _get_population_fitness(self, pop: list[ChromosomeType]):
24+
# return best chromosome's fitness
25+
return min(SAMPO.backend.compute_chromosomes(self._fitness, pop))
26+
27+
def _get_best_individual(self, pop: list[ChromosomeType], fitness: list[float] | None = None) -> ChromosomeType:
28+
fitness = fitness or SAMPO.backend.compute_chromosomes(self._fitness, pop)
29+
return pop[np.argmin(fitness)]
30+
31+
def schedule(self,
32+
initial_population: list[ChromosomeType],
33+
wg: WorkGraph,
34+
contractors: list[Contractor],
35+
spec: ScheduleSpec = ScheduleSpec(),
36+
assigned_parent_time: Time = Time(0),
37+
landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]:
38+
tabu_toolbox = setup_toolbox(wg, contractors, get_worker_contractor_pool(contractors))
39+
40+
leader = self._get_best_individual(initial_population)
41+
fitness = SAMPO.backend.compute_chromosomes(TimeFitness(), [leader])
42+
print(f'TABU initial fitness: {fitness}')
43+
44+
tabu_leader = ChromosomeRW.from_sampo_chromosome(leader)
45+
46+
opt_ord, opt_res = get_optimiser(tabu_toolbox,
47+
use_vp=True,
48+
optimisers_lifetime=OptimiserLifetime.Short)
49+
tabu_leader = opt_ord.optimize(tabu_leader)
50+
tabu_leader = opt_res.optimize(tabu_leader.position)
51+
52+
chromosome = tabu_leader.position.to_sampo_chromosome()
53+
fitness = SAMPO.backend.compute_chromosomes(TimeFitness(), [chromosome])
54+
print(f'TABU fitness: {fitness}')
55+
56+
return initial_population + [chromosome]

sampo/scheduler/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ def optimize_resources_using_spec(work_unit: WorkUnit, worker_team: list[Worker]
102102
data in its closure and run optimization process when receives `optimize_array`.
103103
Passing None or default value means this function should only apply spec.
104104
"""
105+
worker_reqs = set(wr.kind for wr in work_unit.worker_reqs)
106+
worker_team = [worker for worker in worker_team if worker.name in worker_reqs]
107+
105108
if len(work_spec.assigned_workers) == len(work_unit.worker_reqs):
106109
# all resources passed in spec, skipping optimize_resources step
107110
for worker in worker_team:

sampo/scheduler/genetic/schedule_builder.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def build_schedules_with_cache(wg: WorkGraph,
130130
init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]],
131131
rand: random.Random,
132132
spec: ScheduleSpec,
133-
weights: list[int],
133+
weights: list[int] = None,
134134
pop: list[ChromosomeType] = None,
135135
landscape: LandscapeConfiguration = LandscapeConfiguration(),
136136
fitness_object: FitnessFunction = TimeFitness(),
@@ -176,15 +176,19 @@ def build_schedules_with_cache(wg: WorkGraph,
176176
init_schedules, assigned_parent_time, fitness_weights,
177177
sgs_type, only_lft_initialization, is_multiobjective)
178178

179-
# create population of a given size
180-
pop = pop or SAMPO.backend.generate_first_population(population_size)
181-
182179
SAMPO.logger.info(f'Toolbox initialization & first population took {(time.time() - start) * 1000} ms')
183180

184181
have_deadline = deadline is not None
185182
fitness_f = fitness_object if not have_deadline else TimeFitness()
186183
if have_deadline:
187184
toolbox.register_individual_constructor((-1,))
185+
186+
# create population of a given size
187+
if pop is None:
188+
pop = SAMPO.backend.generate_first_population(population_size)
189+
else:
190+
pop = [toolbox.Individual(chromosome) for chromosome in pop]
191+
188192
evaluation_start = time.time()
189193

190194
hof = tools.ParetoFront(similar=compare_individuals)

0 commit comments

Comments
 (0)