Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update genetic algorithm #52

Merged
merged 6 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ def __init__(self,
fitness_constructor: Callable[[Toolbox], FitnessFunction] = TimeFitness,
scheduler_type: SchedulerType = SchedulerType.Genetic,
resource_optimizer: ResourceOptimizer = IdentityResourceOptimizer(),
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()):
work_estimator: WorkTimeEstimator = DefaultWorkEstimator(),
optimize_resources: bool = False,
deadline: Time = None,
Timotshak marked this conversation as resolved.
Show resolved Hide resolved
verbose: bool = True):
super().__init__(scheduler_type=scheduler_type,
resource_optimizer=resource_optimizer,
work_estimator=work_estimator)
Expand All @@ -53,11 +56,13 @@ def __init__(self,
self.rand = rand or random.Random(seed)
self.fitness_constructor = fitness_constructor
self.work_estimator = work_estimator
self.optimize_resources = optimize_resources
self._n_cpu = n_cpu
self._weights = weights
self._verbose = verbose

self._time_border = None
self._deadline = None
self._deadline = deadline
Timotshak marked this conversation as resolved.
Show resolved Hide resolved

def __str__(self) -> str:
return f'GeneticScheduler[' \
Expand Down Expand Up @@ -199,6 +204,7 @@ def schedule_with_cache(self,

mutate_order, mutate_resources, size_of_population = self.get_params(wg.vertex_count)
worker_pool = get_worker_contractor_pool(contractors)
deadline = None if self.optimize_resources else self._deadline

scheduled_works, schedule_start_time, timeline, order_nodes = build_schedule(wg,
contractors,
Expand All @@ -213,10 +219,13 @@ def schedule_with_cache(self,
landscape,
self.fitness_constructor,
self.work_estimator,
n_cpu=self._n_cpu,
assigned_parent_time=assigned_parent_time,
timeline=timeline,
time_border=self._time_border)
self._n_cpu,
assigned_parent_time,
timeline,
self._time_border,
self.optimize_resources,
deadline,
self._verbose)
schedule = Schedule.from_scheduled_works(scheduled_works.values(), wg)

if validate:
Expand Down
410 changes: 284 additions & 126 deletions sampo/scheduler/genetic/operators.py

Large diffs are not rendered by default.

332 changes: 209 additions & 123 deletions sampo/scheduler/genetic/schedule_builder.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions sampo/scheduler/native_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self,
contractors: list[Contractor],
worker_name2index: dict[str, int],
worker_pool_indices: dict[int, dict[int, Worker]],
parents: dict[int, list[int]],
parents: dict[int, set[int]],
time_estimator: WorkTimeEstimator):
self.native = native
if not native:
Expand All @@ -59,7 +59,7 @@ def fit(chromosome: ChromosomeType) -> Schedule | None:
self.numeration = numeration
# for each vertex index store list of parents' indices
self.parents = [[rev_numeration[p] for p in numeration[index].parents] for index in range(wg.vertex_count)]
head_parents = [parents[i] for i in range(len(parents))]
head_parents = [list(parents[i]) for i in range(len(parents))]
# for each vertex index store list of whole it's inseparable chain indices
self.inseparables = [[rev_numeration[p] for p in numeration[index].get_inseparable_chain_with_self()]
for index in range(wg.vertex_count)]
Expand Down
2 changes: 1 addition & 1 deletion sampo/scheduler/resources_in_time/average_binary_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def fitness(k: float, inner_spec: ScheduleSpec):

result_min_resources = fitness(k_max, copied_spec)
if result_min_resources < deadline:
print('Can keep deadline at minimum resources')
# print('Can keep deadline at minimum resources')
# we can keep the deadline if pass minimum resources,
# so let's go preventing the works going in parallel
for node in wg.nodes:
Expand Down
2 changes: 1 addition & 1 deletion sampo/scheduler/timeline/just_in_time_timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def update_timeline(self,
worker_timeline = self._timeline[(worker.contractor_id, worker.name)]
count_workers = sum([count for _, count in worker_timeline])
worker_timeline.clear()
worker_timeline.append((finish_time, count_workers))
worker_timeline.append((finish_time + 1, count_workers))
else:
# For each worker type consume the nearest available needed worker amount
# and re-add it to the time when current work should be finished.
Expand Down
2 changes: 1 addition & 1 deletion sampo/structurator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def fill_parents_to_new_nodes(origin_node: GraphNode, id2new_nodes: GraphNodeDic
parents_zero_stage: list[tuple[GraphNode, float, EdgeType]] = []
parents_last_stage: list[tuple[GraphNode, float, EdgeType]] = []
for edge in origin_node.edges_to:
indent = 1 if not edge.start.work_unit.is_service_unit and not edge.finish.work_unit.is_service_unit else 0
indent = 1 if not (edge.start.work_unit.is_service_unit or edge.finish.work_unit.is_service_unit) else 0
if edge.type in [EdgeType.FinishStart, EdgeType.InseparableFinishStart]:
lag = edge.lag if not edge.lag % 1 else ceil(edge.lag)
lag = lag if lag > 0 else indent
Expand Down
19 changes: 16 additions & 3 deletions tests/scheduler/genetic/converter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
def test_convert_schedule_to_chromosome(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)
schedule = HEFTScheduler().schedule(setup_wg, setup_contractors, validate=True,
landscape=setup_landscape_many_holders)

chromosome = tb.schedule_to_chromosome(schedule=schedule)
assert tb.validate(chromosome)
Expand Down Expand Up @@ -44,10 +45,22 @@ def test_converter_with_borders_contractor_accounting(setup_toolbox):
for i in range(len(chromosome[2])):
contractors.append(Contractor(id=setup_contractors[i].id,
name=setup_contractors[i].name,
workers={name: Worker(str(uuid4()), name, count, contractor_id=setup_contractors[i].id)
for name, count in zip(workers, chromosome[2][i])},
workers={
name: Worker(str(uuid4()), name, count, contractor_id=setup_contractors[i].id)
for name, count in zip(workers, chromosome[2][i])},
equipments={}))

schedule = Schedule.from_scheduled_works(schedule.values(), setup_wg)

validate_schedule(schedule, setup_wg, contractors)


def test_converter_with_borders_update(setup_toolbox):
tb, _, setup_wg, setup_contractors, _, setup_landscape_many_holders = setup_toolbox
chromosome = tb.generate_chromosome(landscape=setup_landscape_many_holders)
schedule, _, _, _ = tb.chromosome_to_schedule(chromosome, landscape=setup_landscape_many_holders)
schedule = Schedule.from_scheduled_works(schedule.values(), setup_wg)
updated_chromosome = tb.update_resource_borders(chromosome, schedule)
updated_schedule, _, _, _ = tb.chromosome_to_schedule(updated_chromosome, landscape=setup_landscape_many_holders)
updated_schedule = Schedule.from_scheduled_works(updated_schedule.values(), setup_wg)
assert schedule.execution_time == updated_schedule.execution_time
23 changes: 12 additions & 11 deletions tests/scheduler/genetic/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import numpy as np

from sampo.scheduler.genetic.schedule_builder import create_toolbox
from sampo.scheduler.genetic.schedule_builder import create_toolbox_and_mapping_objects
from sampo.schemas.contractor import get_worker_contractor_pool
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator

Expand Down Expand Up @@ -48,14 +48,15 @@ def setup_toolbox(setup_default_schedules) -> tuple:
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,
return (create_toolbox_and_mapping_objects(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,
verbose=False)[0], resources_border,
setup_wg, setup_contractors, setup_default_schedules, setup_landscape_many_holders)
2 changes: 1 addition & 1 deletion tests/scheduler/genetic/full_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
def test_multiprocessing(setup_scheduler_parameters):
setup_wg, setup_contractors, setup_landscape = setup_scheduler_parameters

genetic = GeneticScheduler(number_of_generation=50,
genetic = GeneticScheduler(number_of_generation=100,
mutate_order=0.05,
mutate_resources=0.005,
size_of_population=50)
Expand Down
23 changes: 18 additions & 5 deletions tests/scheduler/genetic/operators_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ def test_mutate_order(setup_toolbox):

for i in range(TEST_ITERATIONS):
individual = tb.generate_chromosome()
mutant = tb.mutate(individual)
mutant = tb.mutate_order(individual)
order = mutant[0]

# check there are no duplications
assert len(order) == len(set(order))
assert tb.validate(mutant)


def test_mutate_resources(setup_toolbox):
Expand All @@ -36,6 +37,16 @@ def test_mutate_resources(setup_toolbox):
assert tb.validate(mutant)


def test_mutate_resource_borders(setup_toolbox):
tb, _, _, _, _, _ = setup_toolbox

for i in range(TEST_ITERATIONS):
individual = tb.generate_chromosome()
mutant = tb.mutate_resource_borders(individual)

assert tb.validate(mutant)


def test_mate_order(setup_toolbox, setup_wg):
tb, _, _, _, _, _ = setup_toolbox
_, _, population_size = get_params(setup_wg.vertex_count)
Expand All @@ -45,13 +56,15 @@ def test_mate_order(setup_toolbox, setup_wg):
for i in range(TEST_ITERATIONS):
individual1, individual2 = population[:2]

individual1, individual2 = tb.mate(individual1, individual2)
order1 = individual1[0]
order2 = individual2[0]
child1, child2 = tb.mate_order(individual1, individual2)
order1 = child1[0]
order2 = child2[0]

# check there are no duplications
assert len(order1) == len(set(order1))
assert len(order2) == len(set(order2))
assert tb.validate(child1)
assert tb.validate(child2)


def test_mate_resources(setup_toolbox, setup_wg):
Expand All @@ -62,7 +75,7 @@ def test_mate_resources(setup_toolbox, setup_wg):

for i in range(TEST_ITERATIONS):
individual1, individual2 = random.sample(population, 2)
individual1, individual2 = tb.mate_resources(individual1, individual2)
individual1, individual2 = tb.mate_resources(individual1, individual2, optimize_resources=False)

# check there are correct resources at mate positions
assert (resources_border[0] <= individual1[1].T[:-1]).all() and \
Expand Down
50 changes: 48 additions & 2 deletions tests/scheduler/resources_in_time/basic_res_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import math

from sampo.scheduler.genetic.base import GeneticScheduler
from sampo.scheduler.genetic.operators import DeadlineResourcesFitness
Expand Down Expand Up @@ -40,12 +41,12 @@ def test_genetic_deadline_planning(setup_scheduler_parameters):
mutate_resources=0.005,
size_of_population=50,
fitness_constructor=DeadlineResourcesFitness.prepare(deadline))
scheduler.set_deadline(deadline)

try:
schedule = scheduler.schedule(setup_wg, setup_contractors, landscape=landscape)

print(f'Planning for deadline time: {schedule.execution_time}, cost: {schedule_cost(schedule)}')
print(f'Planning for deadline time: {schedule.execution_time}, ' +
f'peaks: {get_absolute_peak_resource_usage(schedule)}, cost: {schedule_cost(schedule)}')
except NoSufficientContractorError:
pytest.skip("Given contractors can't satisfy given work graph")

Expand All @@ -72,3 +73,48 @@ def test_true_deadline_planning(setup_scheduler_parameters):

print(f'Peak with deadline: {peak_deadlined}, time: {deadlined_schedule.execution_time}')
print(f'Peak without deadline: {peak_not_deadlined}, time: {not_deadlined_schedule.execution_time}')


def test_lexicographic_genetic_deadline_planning(setup_scheduler_parameters):
setup_wg, setup_contractors, setup_landscape = setup_scheduler_parameters

scheduler = HEFTScheduler()
schedule, _, _, _ = scheduler.schedule_with_cache(setup_wg, setup_contractors, landscape=setup_landscape)
deadline = schedule.execution_time
deadline -= 10 ** max(0, int(math.log10(deadline.value) - 1))
Timotshak marked this conversation as resolved.
Show resolved Hide resolved

print(f'Deadline time: {deadline}')

scheduler_combined = GeneticScheduler(number_of_generation=100,
mutate_order=0.05,
mutate_resources=0.005,
size_of_population=50,
fitness_constructor=DeadlineResourcesFitness.prepare(deadline),
optimize_resources=True,
deadline=deadline,
verbose=False)

scheduler_lexicographic = GeneticScheduler(number_of_generation=100,
mutate_order=0.05,
mutate_resources=0.005,
size_of_population=50,
deadline=deadline,
verbose=False)

try:
schedule = scheduler_combined.schedule(setup_wg, setup_contractors, landscape=setup_landscape)
time_combined = schedule.execution_time

print(f'\tCombined genetic: time = {time_combined}, ' +
f'peak = {get_absolute_peak_resource_usage(schedule)}')

schedule = scheduler_lexicographic.schedule(setup_wg, setup_contractors, landscape=setup_landscape)
time_lexicographic = schedule.execution_time

print(f'\tLexicographic genetic: time = {time_lexicographic}, ' +
f'peak = {get_absolute_peak_resource_usage(schedule)}')

except NoSufficientContractorError:
pytest.skip("Given contractors can't satisfy given work graph")

print()
File renamed without changes.