Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: self-hosted
strategy:
matrix:
python-version: ["3.10"]
Expand All @@ -34,4 +34,4 @@ jobs:
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
python -m pytest tests/ --reruns 5
python -m pytest tests/ --reruns 5 -n 16
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "sampo"
version = "0.1.2"
version = "0.1.2.1"
description = "Open-source framework for adaptive manufacturing processes scheduling"
authors = ["iAirLab <[email protected]>"]
license = "BSD-3-Clause"
Expand Down
37 changes: 19 additions & 18 deletions sampo/scheduler/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,18 @@
from sampo.utilities.validation import validate_schedule


# TODO Кажется, это не работает - лаги не учитываются
def get_finish_time_default(node, worker_team, node2swork, spec, assigned_parent_time, timeline,
work_estimator) -> Time:
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 Кажется, это не работает - лаги не учитываются
return timeline.find_min_start_time_with_additional(node, worker_team, node2swork, spec,
assigned_parent_time, work_estimator)[1]


PRIORITIZATION_F = Callable[
[list[GraphNode], dict[str, set[str]], dict[str, set[str]], WorkTimeEstimator], list[GraphNode]
]
RESOURCE_OPTIMIZE_F = Callable[[GraphNode, list[Contractor], WorkSpec, WorkerContractorPool,
dict[GraphNode, ScheduledWork], Time, Timeline, WorkTimeEstimator],
tuple[Time, Time, Contractor, list[Worker]]]
tuple[Time, Time, dict[GraphNode, Time], Contractor, list[Worker]]]


class GenericScheduler(Scheduler):
Expand Down Expand Up @@ -78,12 +75,12 @@ def ft_getter(worker_team) -> Time:
return get_finish_time(node, worker_team, node2swork, spec,
assigned_parent_time, timeline, work_estimator)

def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker]]:
def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, dict[GraphNode, Time], list[Worker]]:
min_count_worker_team, max_count_worker_team, workers \
= get_worker_borders(worker_pool, contractor, node.work_unit.worker_reqs)

if len(workers) != len(node.work_unit.worker_reqs):
return assigned_parent_time, Time.inf(), []
return assigned_parent_time, Time.inf(), {}, []

workers = [worker.copy() for worker in workers]

Expand All @@ -95,9 +92,10 @@ def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker
min_count_worker_team, max_count_worker_team,
ft_getter))

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
c_st, c_ft, exec_times = timeline.find_min_start_time_with_additional(node, workers, node2swork, spec,
None, assigned_parent_time,
None, work_estimator)
return c_st, c_ft, exec_times, workers

return run_contractor_search(contractors, spec, run_with_contractor)

Expand Down Expand Up @@ -164,11 +162,11 @@ def build_scheduler(self,
work_unit = node.work_unit
work_spec = spec.get_work_spec(work_unit.id)

start_time, finish_time, contractor, best_worker_team = self.optimize_resources(node, contractors,
work_spec, worker_pool,
node2swork,
assigned_parent_time,
timeline, work_estimator)
start_time, finish_time, exec_times, contractor, best_worker_team = self.optimize_resources(node, contractors,
work_spec, worker_pool,
node2swork,
assigned_parent_time,
timeline, work_estimator)

# we are scheduling the work `start of the project`
if index == 0:
Expand All @@ -177,12 +175,15 @@ def build_scheduler(self,
finish_time += start_time

if index == len(ordered_nodes) - 1: # we are scheduling the work `end of the project`
finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
zone_finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
finish_time = max(finish_time, zone_finish_time)
start_time = max(start_time, finish_time)

# apply work to scheduling
# FIXME these (finish_time - start_time) contain lags!
# make Timeline#schedule receive `exec_times` instead of fixed time
timeline.schedule(node, node2swork, best_worker_team, contractor, work_spec,
start_time, work_spec.assigned_time, assigned_parent_time, work_estimator)
start_time, assigned_parent_time, exec_times, work_estimator)

if index == len(ordered_nodes) - 1: # we are scheduling the work `end of the project`
node2swork[node].zones_pre = finalizing_zones
Expand Down
32 changes: 21 additions & 11 deletions sampo/scheduler/genetic/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import numpy as np

from sampo.api.genetic_api import ChromosomeType, ScheduleGenerationScheme
from sampo.base import SAMPO
from sampo.scheduler.base import Scheduler
from sampo.scheduler.timeline import JustInTimeTimeline, MomentumTimeline
from sampo.scheduler.timeline.base import Timeline
from sampo.scheduler.timeline.general_timeline import GeneralTimeline
from sampo.scheduler.utils import WorkerContractorPool
from sampo.scheduler.utils.time_computaion import calculate_working_time_cascade
from sampo.schemas import ZoneReq
from sampo.schemas.contractor import Contractor
from sampo.schemas.graph import GraphNode
Expand All @@ -19,7 +17,7 @@
from sampo.schemas.schedule_spec import ScheduleSpec
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator
from sampo.utilities.collections_util import reverse_dictionary
from sampo.utilities.inseparables import get_exec_times_from_assigned_time_for_chain, find_min_time_slot_size
from sampo.utilities.linked_list import LinkedList


Expand Down Expand Up @@ -174,11 +172,19 @@ def decode(work_index):
.copy().with_count(worker_count)
for worker_index, worker_count in enumerate(cur_resources)
if worker_count > 0]
# apply worker spec
Scheduler.optimize_resources_using_spec(cur_node.work_unit, cur_worker_team, cur_work_spec)

cur_inseparable_chain = cur_node.get_inseparable_chain_with_self()
if cur_work_spec.assigned_time is not None:
cur_exec_time = cur_work_spec.assigned_time
cur_exec_times = get_exec_times_from_assigned_time_for_chain(cur_inseparable_chain,
cur_work_spec.assigned_time)
else:
cur_exec_time = calculate_working_time_cascade(cur_node, cur_worker_team, work_estimator)
return cur_node, cur_worker_team, cur_contractor, cur_exec_time, cur_work_spec
cur_exec_times = {}
for dep_node in cur_inseparable_chain:
cur_exec_times[dep_node] = work_estimator.estimate_time(dep_node.work_unit, cur_worker_team)

return cur_node, cur_worker_team, cur_contractor, cur_exec_times, cur_work_spec

# account the remaining works
enumerated_works_remaining = LinkedList(iterable=enumerate(
Expand All @@ -191,23 +197,27 @@ def decode(work_index):
prev_start_time = start_time - 1

def work_scheduled(args) -> bool:
idx, (work_idx, node, worker_team, contractor, exec_time, work_spec) = args
idx, (work_idx, node, worker_team, contractor, exec_times, work_spec) = args

exec_time = find_min_time_slot_size(node.get_inseparable_chain_with_self(), node2swork, exec_times, start_time)

if timeline.can_schedule_at_the_moment(node, worker_team, work_spec, node2swork, start_time, exec_time):
# apply worker spec
Scheduler.optimize_resources_using_spec(node.work_unit, worker_team, work_spec)
finish_time = start_time + exec_time

st = start_time
if idx == 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

if idx == len(works_order) - 1: # we are scheduling the work `end of the project`
finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
zone_finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
finish_time = max(finish_time, zone_finish_time)
st = max(start_time, finish_time)

# assert timeline.can_schedule_at_the_moment(node, worker_team, work_spec, node2swork, st, exec_time)

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

if idx == len(works_order) - 1: # we are scheduling the work `end of the project`
node2swork[node].zones_pre = finalizing_zones
Expand Down
2 changes: 1 addition & 1 deletion sampo/scheduler/heft/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ def __init__(self,
@staticmethod
def get_finish_time(node, worker_team, node2swork, spec, assigned_parent_time, timeline, work_estimator) -> Time:
return timeline.find_min_start_time_with_additional(node, worker_team, node2swork, spec, None,
assigned_parent_time, work_estimator)[1]
assigned_parent_time, None, work_estimator)[1]
16 changes: 9 additions & 7 deletions sampo/scheduler/lft/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,30 +158,32 @@ def build_scheduler(self,
timeline = self._timeline_type(worker_pool, landscape)

for index, node in enumerate(ordered_nodes):
work_unit = node.work_unit
work_spec = spec[node.id]

# get assigned contractor and workers
contractor, best_worker_team = self._node_id2workers[node.id]

# find start time
start_time, finish_time, _ = timeline.find_min_start_time_with_additional(node, best_worker_team,
node2swork, work_spec, None,
assigned_parent_time,
work_estimator)
start_time, finish_time, exec_times = timeline.find_min_start_time_with_additional(node, best_worker_team,
node2swork, work_spec,
None,
assigned_parent_time,
None,
work_estimator)
# we are scheduling the work `start of the project`
if index == 0:
# this work should always have start_time = 0, so we just re-assign it
start_time = assigned_parent_time
finish_time += start_time

if index == len(ordered_nodes) - 1: # we are scheduling the work `end of the project`
finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
zone_finish_time, finalizing_zones = timeline.zone_timeline.finish_statuses()
finish_time = max(finish_time, zone_finish_time)
start_time = max(start_time, finish_time)

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

if index == len(ordered_nodes) - 1: # we are scheduling the work `end of the project`
node2swork[node].zones_pre = finalizing_zones
Expand Down
18 changes: 10 additions & 8 deletions sampo/scheduler/timeline/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@ class Timeline(ABC):
def schedule(self,
node: GraphNode,
node2swork: dict[GraphNode, ScheduledWork],
passed_agents: list[Worker],
workers: list[Worker],
contractor: Contractor,
spec: WorkSpec,
assigned_start_time: Time | None = None,
assigned_time: Time | None = None,
assigned_start_time: Optional[Time] = None,
assigned_parent_time: Time = Time(0),
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()) -> Time:
exec_times: Optional[dict[GraphNode, Time]] = None,
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()):
"""
Schedules the given `GraphNode` using passed agents, spec and times.
Schedules the given `GraphNode` using passed workers, spec and times.
If start time not passed, it should be computed as minimum work start time.
:return: scheduled finish time of given work
"""
...

Expand All @@ -41,6 +40,7 @@ def find_min_start_time(self,
node2swork: dict[GraphNode, ScheduledWork],
spec: WorkSpec,
parent_time: Time = Time(0),
exec_times: dict[GraphNode, Time] = None,
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()) -> Time:
"""
Computes start time, max parent time, contractor and exec times for given node.
Expand All @@ -50,11 +50,12 @@ def find_min_start_time(self,
:param node2swork: dictionary, that match GraphNode to ScheduleWork respectively
:param spec: specification for given `GraphNode`
:param parent_time: the minimum start time
:param exec_times:
:param work_estimator: function that calculates execution time of the GraphNode
:return: minimum time
"""
return self.find_min_start_time_with_additional(node, worker_team, node2swork, spec, None,
parent_time, work_estimator)[0]
parent_time, exec_times, work_estimator)[0]

@abstractmethod
def find_min_start_time_with_additional(self,
Expand All @@ -64,8 +65,9 @@ def find_min_start_time_with_additional(self,
spec: WorkSpec,
assigned_start_time: Optional[Time] = None,
assigned_parent_time: Time = Time(0),
exec_times: dict[GraphNode, Time] = None,
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()) \
-> tuple[Time, Time, dict[GraphNode, tuple[Time, Time]]]:
-> tuple[Time, Time, dict[GraphNode, Time]]:
...

@abstractmethod
Expand Down
Loading