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

Refactor of scheduling for compatibility with multi-objective optimization #81

Merged
merged 33 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e3fea89
add individual constructor
Timotshak Jan 23, 2024
b039242
Adapt Genetic Algorithm to multi-objective tasks
Timotshak Jan 24, 2024
4aa3f70
refactor build_schedule in genetic algorithm
Timotshak Jan 24, 2024
6a20fc7
fix typing in Individual
Timotshak Jan 24, 2024
18c1049
fix typing in genetic operators
Timotshak Jan 24, 2024
617f8e0
introduce multi-criteria in genetic algorithm
Timotshak Jan 24, 2024
7c6ea6d
introduce multi-criteria in Genetic Scheduler
Timotshak Jan 25, 2024
ef5e0b1
introduce multi-objective fitness function
Timotshak Jan 25, 2024
ace2cff
fix imports
Timotshak Jan 25, 2024
78a2f5e
fix typing
Timotshak Jan 25, 2024
6248ec7
fix typing
Timotshak Jan 25, 2024
2e78808
fix schedule_builder
Timotshak Jan 25, 2024
77b1543
fix schedule_builder
Timotshak Jan 25, 2024
e7bd3f3
add multiobjective tests
Timotshak Jan 25, 2024
1802d1f
refactor resource_usage
Timotshak Jan 25, 2024
16496be
fix resources mutation probability
Timotshak Jan 25, 2024
0fd8033
multiobjective tests
Timotshak Jan 25, 2024
58addd5
fix tests
Timotshak Jan 25, 2024
ed0cfdc
Merge remote-tracking branch 'origin/main' into feature/mo_genetic_al…
Timotshak Feb 6, 2024
3a080cf
refactor generic scheduler
Timotshak Feb 9, 2024
169380a
refactor pipeline, generic and
Timotshak Feb 9, 2024
4968fdb
Merge remote-tracking branch 'origin/main' into feature/mo_genetic_al…
Timotshak Feb 9, 2024
96c2f73
fix tests
Timotshak Feb 12, 2024
45b555d
fix tests
Timotshak Feb 12, 2024
e00f621
fix pipeline
Timotshak Feb 12, 2024
3fe1f51
fix multiobjective test
Timotshak Feb 12, 2024
ff72154
fix backend
Timotshak Feb 12, 2024
3aa8247
fix experiments
Timotshak Feb 14, 2024
e46ce41
fix examples
Timotshak Feb 14, 2024
699b943
fix multi-agency
Timotshak Feb 14, 2024
df2369f
fix readme
Timotshak Feb 14, 2024
03a1a6c
fix notebooks
Timotshak Feb 14, 2024
8d04f7e
fix notebooks
Timotshak Feb 14, 2024
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
26 changes: 13 additions & 13 deletions sampo/backend/multiproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from sampo.scheduler.genetic.operators import Individual
from sampo.scheduler.genetic.utils import create_toolbox_using_cached_chromosomes, init_chromosomes_f
from sampo.scheduler.heft import HEFTScheduler, HEFTBetweenScheduler
from sampo.scheduler.heft.prioritization import prioritization
from sampo.scheduler.resource import AverageReqResourceOptimizer
from sampo.scheduler.resources_in_time import AverageBinarySearchResourceOptimizingScheduler
from sampo.scheduler.topological.base import RandomizedTopologicalScheduler
Expand Down Expand Up @@ -42,7 +41,7 @@ def scheduler_info_initializer(wg: WorkGraph,
only_lft_initialization: bool,
is_multiobjective: bool):
global g_wg, g_contractors, g_landscape, g_spec, g_toolbox, g_work_estimator, g_deadline, g_rand, g_weights, \
g_sgs_type, g_only_lft_initialization, g_is_multiobjective
g_sgs_type, g_only_lft_initialization, g_is_multiobjective

g_wg = wg
g_contractors = contractors
Expand Down Expand Up @@ -157,35 +156,36 @@ def generate_first_population(self, size_population: int) -> list[Individual]:
def mapper(key: str):
def randomized_init():
schedule, _, _, order = RandomizedTopologicalScheduler(g_work_estimator, int(g_rand.random() * 1000000)) \
.schedule_with_cache(g_wg, g_contractors, landscape=g_landscape)
.schedule_with_cache(g_wg, g_contractors, landscape=g_landscape)[0]
return schedule, order, g_spec

def init_k_schedule(scheduler_class, k) -> tuple[Schedule | None, list[GraphNode] | None, ScheduleSpec | None]:
try:
return scheduler_class(work_estimator=g_work_estimator,
resource_optimizer=AverageReqResourceOptimizer(k)) \
.schedule(g_wg, g_contractors,
g_spec,
landscape=g_landscape), list(reversed(prioritization(g_wg, g_work_estimator))), g_spec
schedule, _, _, node_order = (scheduler_class(work_estimator=g_work_estimator,
resource_optimizer=AverageReqResourceOptimizer(k))
.schedule_with_cache(g_wg, g_contractors, g_spec,
landscape=g_landscape))[0]
return schedule, node_order[::-1], g_spec
except NoSufficientContractorError:
return None, None, None

if g_deadline is None:
def init_schedule(scheduler_class) -> tuple[Schedule | None, list[GraphNode] | None, ScheduleSpec | None]:
try:
return scheduler_class(work_estimator=g_work_estimator).schedule(g_wg, g_contractors,
landscape=g_landscape), \
list(reversed(prioritization(g_wg, g_work_estimator))), g_spec
schedule, _, _, node_order = (scheduler_class(work_estimator=g_work_estimator)
.schedule_with_cache(g_wg, g_contractors, g_spec,
landscape=g_landscape))[0]
return schedule, node_order[::-1], g_spec
except NoSufficientContractorError:
return None, None, None

else:
def init_schedule(scheduler_class) -> tuple[Schedule | None, list[GraphNode] | None, ScheduleSpec | None]:
try:
(schedule, _, _, _), modified_spec = AverageBinarySearchResourceOptimizingScheduler(
(schedule, _, _, node_order), modified_spec = AverageBinarySearchResourceOptimizingScheduler(
scheduler_class(work_estimator=g_work_estimator)
).schedule_with_cache(g_wg, g_contractors, g_deadline, g_spec, landscape=g_landscape)
return schedule, list(reversed(prioritization(g_wg, g_work_estimator))), modified_spec
return schedule, node_order[::-1], modified_spec
except NoSufficientContractorError:
return None, None, None

Expand Down
2 changes: 1 addition & 1 deletion sampo/pipeline/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,5 @@ def optimize_local(self, optimizer: ScheduleLocalOptimizer, area: range) -> 'Sch
...

@abstractmethod
def finish(self) -> ScheduledProject:
def finish(self) -> list[ScheduledProject]:
...
100 changes: 56 additions & 44 deletions sampo/pipeline/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(self):
self._contractors: list[Contractor] | pd.DataFrame | str | tuple[ContractorGenerationMethod, int] | None \
= ContractorGenerationMethod.AVG, 1
self._work_estimator: WorkTimeEstimator = DefaultWorkEstimator()
self._node_order: list[GraphNode] | None = None
self._node_orders: list[list[GraphNode]] | None = None
self._lag_optimize: LagOptimizationStrategy = LagOptimizationStrategy.NONE
self._spec: ScheduleSpec | None = ScheduleSpec()
self._assigned_parent_time: Time | None = Time(0)
Expand Down Expand Up @@ -180,12 +180,12 @@ def work_estimator(self, work_estimator: WorkTimeEstimator) -> 'InputPipeline':
self._work_estimator = work_estimator
return self

def node_order(self, node_order: list[GraphNode]) -> 'InputPipeline':
self._node_order = node_order
def node_order(self, node_orders: list[list[GraphNode]]) -> 'InputPipeline':
self._node_orders = node_orders
return self

def optimize_local(self, optimizer: OrderLocalOptimizer, area: range) -> 'InputPipeline':
self._local_optimize_stack.add(optimizer.optimize, (area,))
self._local_optimize_stack.add(optimizer.optimize, area)
return self

def schedule(self, scheduler: Scheduler) -> 'SchedulePipeline':
Expand Down Expand Up @@ -230,72 +230,84 @@ def prioritization(wg: WorkGraph, work_estimator: WorkTimeEstimator):
match self._lag_optimize:
case LagOptimizationStrategy.NONE:
wg = self._wg
schedule, _, _, node_order = scheduler.schedule_with_cache(wg, self._contractors,
self._landscape_config,
self._spec,
assigned_parent_time=self._assigned_parent_time)
self._node_order = node_order
schedules = scheduler.schedule_with_cache(wg, self._contractors,
self._spec,
landscape=self._landscape_config,
assigned_parent_time=self._assigned_parent_time)
node_orders = [node_order for _, _, _, node_order in schedules]
schedules = [schedule for schedule, _, _, _ in schedules]
self._node_orders = node_orders

case LagOptimizationStrategy.AUTO:
# Searching the best
wg1 = graph_restructuring(self._wg, False)
schedule1, _, _, node_order1 = scheduler.schedule_with_cache(wg1, self._contractors,
self._landscape_config,
self._spec,
assigned_parent_time=self._assigned_parent_time)
wg2 = graph_restructuring(self._wg, True)
schedule2, _, _, node_order2 = scheduler.schedule_with_cache(wg2, self._contractors,
self._landscape_config,
self._spec,
assigned_parent_time=self._assigned_parent_time)
schedules = scheduler.schedule_with_cache(wg1, self._contractors,
self._spec,
landscape=self._landscape_config,
assigned_parent_time=self._assigned_parent_time)
node_orders1 = [node_order for _, _, _, node_order in schedules]
schedules1 = [schedule for schedule, _, _, _ in schedules]
min_time1 = min([schedule.execution_time for schedule in schedules1])

if schedule1.execution_time < schedule2.execution_time:
self._node_order = node_order1
wg2 = graph_restructuring(self._wg, True)
schedules = scheduler.schedule_with_cache(wg2, self._contractors,
self._spec,
landscape=self._landscape_config,
assigned_parent_time=self._assigned_parent_time)
node_orders2 = [node_order for _, _, _, node_order in schedules]
schedules2 = [schedule for schedule, _, _, _ in schedules]
min_time2 = min([schedule.execution_time for schedule in schedules2])

if min_time1 < min_time2:
self._node_orders = node_orders1
wg = wg1
schedule = schedule1
schedules = schedules1
else:
self._node_order = node_order2
self._node_orders = node_orders2
wg = wg2
schedule = schedule2
schedules = schedules2

case _:
wg = graph_restructuring(self._wg, self._lag_optimize.value)
schedule, _, _, node_order = scheduler.schedule_with_cache(wg, self._contractors,
self._landscape_config,
self._spec,
assigned_parent_time=self._assigned_parent_time)
self._node_order = node_order
schedules = scheduler.schedule_with_cache(wg, self._contractors,
self._spec,
landscape=self._landscape_config,
assigned_parent_time=self._assigned_parent_time)
node_orders = [node_order for _, _, _, node_order in schedules]
schedules = [schedule for schedule, _, _, _ in schedules]
self._node_orders = node_orders

return DefaultSchedulePipeline(self, wg, schedule)
return DefaultSchedulePipeline(self, wg, schedules)


# noinspection PyProtectedMember
class DefaultSchedulePipeline(SchedulePipeline):

def __init__(self, s_input: DefaultInputPipeline, wg: WorkGraph, schedule: Schedule):
def __init__(self, s_input: DefaultInputPipeline, wg: WorkGraph, schedules: list[Schedule]):
self._input = s_input
self._wg = wg
self._worker_pool = get_worker_contractor_pool(s_input._contractors)
self._schedule = schedule
self._scheduled_works = {wg[swork.id]:
swork for swork in schedule.to_schedule_work_dict.values()}
self._schedules = schedules
self._scheduled_works = [{wg[swork.id]: swork for swork in schedule.to_schedule_work_dict.values()}
for schedule in schedules]
self._local_optimize_stack = ApplyQueue()
self._start_date = None

def optimize_local(self, optimizer: ScheduleLocalOptimizer, area: range) -> 'SchedulePipeline':
self._local_optimize_stack.add(optimizer.optimize,
(
self._input._node_order, self._input._contractors,
self._input._landscape_config,
self._input._spec, self._worker_pool, self._input._work_estimator,
self._input._assigned_parent_time, area))
self._input._contractors, self._input._landscape_config,
self._input._spec, self._worker_pool, self._input._work_estimator,
self._input._assigned_parent_time, area)
return self

def finish(self) -> ScheduledProject:
processed_sworks = self._local_optimize_stack.apply(self._scheduled_works)
schedule = Schedule.from_scheduled_works(processed_sworks.values(), self._wg)
return ScheduledProject(self._input._wg, self._wg, self._input._contractors, schedule)
def finish(self) -> list[ScheduledProject]:
scheduled_projects = []
for scheduled_works, node_order in zip(self._scheduled_works, self._input._node_orders):
processed_sworks = self._local_optimize_stack.apply(scheduled_works, node_order)
schedule = Schedule.from_scheduled_works(processed_sworks.values(), self._wg)
scheduled_projects.append(ScheduledProject(self._input._wg, self._wg, self._input._contractors, schedule))
return scheduled_projects

def visualization(self, start_date: str) -> 'Visualization':
def visualization(self, start_date: str) -> list['Visualization']:
from sampo.utilities.visualization import Visualization
return Visualization.from_project(self.finish(), start_date)
return [Visualization.from_project(project, start_date) for project in self.finish()]
13 changes: 7 additions & 6 deletions sampo/scheduler/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def schedule(self,
start_time: Time = Time(0),
timeline: Timeline | None = None,
landscape: LandscapeConfiguration = LandscapeConfiguration()) \
-> Schedule:
-> list[Schedule]:
"""
Implementation of a scheduling process. 'schedule' version returns only Schedule.

Expand All @@ -63,20 +63,21 @@ def schedule(self,
raise ValueError('None or empty WorkGraph')
if contractors is None or len(contractors) == 0:
raise ValueError('None or empty contractor list')
schedule = self.schedule_with_cache(wg, contractors, landscape, spec, validate, start_time, timeline)[0]
schedules = self.schedule_with_cache(wg, contractors, spec, validate, start_time, timeline, landscape)
schedules = [schedule[0] for schedule in schedules]
# print(f'Schedule exec time: {schedule.execution_time} days')
return schedule
return schedules

@abstractmethod
def schedule_with_cache(self,
wg: WorkGraph,
contractors: list[Contractor],
landscape: LandscapeConfiguration = LandscapeConfiguration(),
spec: ScheduleSpec = ScheduleSpec(),
validate: bool = False,
assigned_parent_time: Time = Time(0),
timeline: Timeline | None = None) \
-> tuple[Schedule, Time, Timeline, list[GraphNode]]:
timeline: Timeline | None = None,
landscape: LandscapeConfiguration = LandscapeConfiguration()) \
-> list[tuple[Schedule, Time, Timeline, list[GraphNode]]]:
"""
Extended version of 'schedule' method. Returns much inner info
about a scheduling process, not only Schedule.
Expand Down
8 changes: 4 additions & 4 deletions sampo/scheduler/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker
def schedule_with_cache(self,
wg: WorkGraph,
contractors: list[Contractor],
landscape: LandscapeConfiguration() = LandscapeConfiguration(),
spec: ScheduleSpec = ScheduleSpec(),
validate: bool = False,
assigned_parent_time: Time = Time(0),
timeline: Timeline | None = None) \
-> tuple[Schedule, Time, Timeline, list[GraphNode]]:
timeline: Timeline | None = None,
landscape: LandscapeConfiguration() = LandscapeConfiguration()) \
-> list[tuple[Schedule, Time, Timeline, list[GraphNode]]]:
ordered_nodes = self.prioritization(wg, self.work_estimator)

schedule, schedule_start_time, timeline = \
Expand All @@ -120,7 +120,7 @@ def schedule_with_cache(self,
if validate:
validate_schedule(schedule, wg, contractors)

return schedule, schedule_start_time, timeline, ordered_nodes
return [(schedule, schedule_start_time, timeline, ordered_nodes)]

def build_scheduler(self,
ordered_nodes: list[GraphNode],
Expand Down
Loading
Loading