Skip to content

Commit

Permalink
The true deadline optimizing (#36)
Browse files Browse the repository at this point in the history
Now SAMPO is fully able to optimize resources to deadline.

- If enough large deadline given, resource peaks eliminating and schedule performing sequentially
- Feature integrated to AverageBinarySearchResourceOptimizingScheduler and GeneticScheduler
- Now GeneticScheduler's FitnessFunction is not compatible with NativeWrapper#evaluate because FitnessFunction now has an evaluator that returns Schedule, not Makespan value
  • Loading branch information
StannisMod authored Jul 27, 2023
1 parent 66418e0 commit 52cab05
Show file tree
Hide file tree
Showing 20 changed files with 333 additions and 175 deletions.
2 changes: 1 addition & 1 deletion sampo/scheduler/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def schedule(self,
landscape: LandscapeConfiguration = LandscapeConfiguration()) \
-> Schedule:
"""
Realization of a scheduling process. 'schedule' version returns only Schedule.
Implementation of a scheduling process. 'schedule' version returns only Schedule.
:return: Schedule
"""
Expand Down
16 changes: 8 additions & 8 deletions sampo/scheduler/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@


# TODO Кажется, это не работает - лаги не учитываются
def get_finish_time_default(node, worker_team, node2swork, assigned_parent_time, timeline, work_estimator):
return timeline.find_min_start_time(node, worker_team, node2swork,
def get_finish_time_default(node, worker_team, node2swork, spec, assigned_parent_time, timeline, work_estimator):
return timeline.find_min_start_time(node, worker_team, node2swork, spec,
assigned_parent_time, work_estimator) \
+ calculate_working_time_cascade(node, worker_team,
work_estimator) # TODO Кажется, это не работает - лаги не учитываются
Expand Down Expand Up @@ -51,7 +51,7 @@ def get_default_res_opt_function(self, get_finish_time=get_finish_time_default)
dict[GraphNode, ScheduledWork], Time, Timeline, WorkTimeEstimator],
tuple[Time, Time, Contractor, list[Worker]]]:

def optimize_resources_def(node: GraphNode, contractors: list[Contractor], work_spec: WorkSpec,
def optimize_resources_def(node: GraphNode, contractors: list[Contractor], spec: WorkSpec,
worker_pool: WorkerContractorPool, node2swork: dict[GraphNode, ScheduledWork],
assigned_parent_time: Time, timeline: Timeline, work_estimator: WorkTimeEstimator):
def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker]]:
Expand All @@ -64,18 +64,18 @@ def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker
workers = [worker.copy() for worker in workers]

def ft_getter(worker_team):
return get_finish_time(node, worker_team, node2swork, assigned_parent_time, timeline,
work_estimator)
return get_finish_time(node, worker_team, node2swork, spec,
assigned_parent_time, timeline, work_estimator)

# apply worker team spec
self.optimize_resources_using_spec(node.work_unit, workers, work_spec,
self.optimize_resources_using_spec(node.work_unit, workers, spec,
lambda optimize_array: self.resource_optimizer.optimize_resources(
worker_pool, workers,
optimize_array,
min_count_worker_team, max_count_worker_team,
ft_getter))

c_st, c_ft, _ = timeline.find_min_start_time_with_additional(node, workers, node2swork, None,
c_st, c_ft, _ = timeline.find_min_start_time_with_additional(node, workers, node2swork, spec, None,
assigned_parent_time, work_estimator)
return c_st, c_ft, workers

Expand Down Expand Up @@ -154,7 +154,7 @@ def build_scheduler(self,
finish_time += start_time

# apply work to scheduling
timeline.schedule(node, node2swork, best_worker_team, contractor,
timeline.schedule(node, node2swork, best_worker_team, contractor, work_spec,
start_time, work_spec.assigned_time, assigned_parent_time, work_estimator)

schedule_start_time = min((swork.start_time for swork in node2swork.values() if
Expand Down
61 changes: 28 additions & 33 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,61 +126,56 @@ def set_deadline(self, deadline: Time):
self._deadline = deadline

def generate_first_population(self, wg: WorkGraph, contractors: list[Contractor],
landscape: LandscapeConfiguration = LandscapeConfiguration()):
landscape: LandscapeConfiguration = LandscapeConfiguration(),
spec: ScheduleSpec = ScheduleSpec()):
"""
Heuristic algorithm, that generate first population
:param landscape:
:param wg: graph of works
:param contractors:
:param spec:
:return:
"""

def init_k_schedule(scheduler_class, k):
try:
return (scheduler_class(work_estimator=self.work_estimator,
resource_optimizer=AverageReqResourceOptimizer(k)).schedule(wg, contractors,
landscape=landscape),
list(reversed(prioritization(wg, self.work_estimator))))
return scheduler_class(work_estimator=self.work_estimator,
resource_optimizer=AverageReqResourceOptimizer(k)) \
.schedule(wg, contractors,
spec,
landscape=landscape), list(reversed(prioritization(wg, self.work_estimator))), spec
except NoSufficientContractorError:
return None, None
return None, None, None

if self._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))))
return scheduler_class(work_estimator=self.work_estimator).schedule(wg, contractors,
landscape=landscape), \
list(reversed(prioritization(wg, self.work_estimator))), spec
except NoSufficientContractorError:
return 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)
}
return None, None, None
else:
def init_schedule(scheduler_class):
try:
schedule = AverageBinarySearchResourceOptimizingScheduler(
(schedule, _, _, _), modified_spec = AverageBinarySearchResourceOptimizingScheduler(
scheduler_class(work_estimator=self.work_estimator)
).schedule_with_cache(wg, contractors, self._deadline, landscape=landscape)[0]
return schedule, list(reversed(prioritization(wg, self.work_estimator)))
).schedule_with_cache(wg, contractors, self._deadline, spec, landscape=landscape)
return schedule, list(reversed(prioritization(wg, self.work_estimator))), modified_spec
except NoSufficientContractorError:
return 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)
}

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)
}


def schedule_with_cache(self,
wg: WorkGraph,
contractors: list[Contractor],
Expand Down
26 changes: 17 additions & 9 deletions sampo/scheduler/genetic/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator

ChromosomeType = tuple[np.ndarray, np.ndarray, np.ndarray]
ChromosomeType = tuple[np.ndarray, np.ndarray, np.ndarray, ScheduleSpec]


def convert_schedule_to_chromosome(wg: WorkGraph,
work_id2index: dict[str, int], worker_name2index: dict[str, int],
contractor2index: dict[str, int], contractor_borders: np.ndarray,
schedule: Schedule, order: list[GraphNode] | None = None) -> ChromosomeType:
work_id2index: dict[str, int],
worker_name2index: dict[str, int],
contractor2index: dict[str, int],
contractor_borders: np.ndarray,
schedule: Schedule,
spec: ScheduleSpec,
order: list[GraphNode] | None = None) -> ChromosomeType:
"""
Receive a result of scheduling algorithm and transform it to chromosome
:param wg:
:param work_id2index:
:param worker_name2index:
:param contractor2index:
:param contractor_borders:
:param schedule:
:param spec:
:param order: if passed, specify the node order that should appear in the chromosome
:return:
"""
Expand Down Expand Up @@ -58,15 +64,14 @@ def convert_schedule_to_chromosome(wg: WorkGraph,

resource_border_chromosome = np.copy(contractor_borders)

return order_chromosome, resource_chromosome, resource_border_chromosome
return order_chromosome, resource_chromosome, resource_border_chromosome, spec


def convert_chromosome_to_schedule(chromosome: ChromosomeType,
worker_pool: WorkerContractorPool,
index2node: dict[int, GraphNode],
index2contractor: dict[int, Contractor],
worker_pool_indices: dict[int, dict[int, Worker]],
spec: ScheduleSpec,
worker_name2index: dict[str, int],
contractor2index: dict[str, int],
landscape: LandscapeConfiguration = LandscapeConfiguration(),
Expand All @@ -83,12 +88,14 @@ def convert_chromosome_to_schedule(chromosome: ChromosomeType,
works_order = chromosome[0]
works_resources = chromosome[1]
border = chromosome[2]
spec = chromosome[3]
worker_pool = copy.deepcopy(worker_pool)

# use 3rd part of chromosome in schedule generator
for worker_index in worker_pool:
for contractor_index in worker_pool[worker_index]:
worker_pool[worker_index][contractor_index].with_count(border[contractor2index[contractor_index], worker_name2index[worker_index]])
worker_pool[worker_index][contractor_index].with_count(border[contractor2index[contractor_index],
worker_name2index[worker_index]])

if not isinstance(timeline, JustInTimeTimeline):
timeline = JustInTimeTimeline(index2node.values(), index2contractor.values(), worker_pool, landscape)
Expand All @@ -114,13 +121,14 @@ def convert_chromosome_to_schedule(chromosome: ChromosomeType,
# apply worker spec
Scheduler.optimize_resources_using_spec(node.work_unit, worker_team, work_spec)

st = timeline.find_min_start_time(node, worker_team, node2swork, assigned_parent_time, work_estimator)
st = timeline.find_min_start_time(node, worker_team, node2swork, work_spec,
assigned_parent_time, work_estimator)

if order_index == 0: # we are scheduling the work `start of the project`
st = assigned_parent_time # this work should always have st = 0, so we just re-assign it

# finish using time spec
timeline.schedule(node, node2swork, worker_team, contractor,
timeline.schedule(node, node2swork, worker_team, contractor, work_spec,
st, work_spec.assigned_time, assigned_parent_time, work_estimator)

schedule_start_time = min((swork.start_time for swork in node2swork.values() if
Expand Down
Loading

0 comments on commit 52cab05

Please sign in to comment.