Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feature/nn
Browse files Browse the repository at this point in the history
# Conflicts:
#	pyproject.toml
#	sampo/scheduler/generic.py
#	sampo/scheduler/timeline/just_in_time_timeline.py
#	sampo/scheduler/timeline/momentum_timeline.py
  • Loading branch information
vanoha01 committed Oct 24, 2023
2 parents be86e94 + c6602b1 commit 05e51a9
Show file tree
Hide file tree
Showing 33 changed files with 1,415 additions and 479 deletions.
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.1.205"
version = "0.1.1.220"
description = "Open-source framework for adaptive manufacturing processes scheduling"
authors = ["iAirLab <[email protected]>"]
license = "BSD-3-Clause"
Expand Down
1 change: 1 addition & 0 deletions sampo/scheduler/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def generate_schedule(scheduling_algorithm_type: SchedulerType,
scheduler = get_scheduler_ctor(scheduling_algorithm_type)(work_estimator=work_time_estimator)
start_time = time.time()
if isinstance(scheduler, GeneticScheduler):
scheduler.number_of_generation = 5
scheduler.set_use_multiprocessing(n_cpu=4)

schedule = scheduler.schedule(work_graph,
Expand Down
2 changes: 1 addition & 1 deletion sampo/scheduler/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def build_scheduler(self,
node2swork: dict[GraphNode, ScheduledWork] = {}
# list for support the queue of workers
if not isinstance(timeline, self._timeline_type):
timeline = self._timeline_type(contractors, worker_pool, landscape)
timeline = self._timeline_type(contractors, landscape)

for index, node in enumerate(reversed(ordered_nodes)): # the tasks with the highest rank will be done first
work_unit = node.work_unit
Expand Down
47 changes: 34 additions & 13 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import random
from typing import Optional, Callable

from deap.base import Toolbox

from sampo.scheduler.base import Scheduler, SchedulerType
from sampo.scheduler.genetic.operators import FitnessFunction, TimeFitness
from sampo.scheduler.genetic.schedule_builder import build_schedule
Expand Down Expand Up @@ -34,27 +32,33 @@ def __init__(self,
number_of_generation: Optional[int] = 50,
mutate_order: Optional[float or None] = None,
mutate_resources: Optional[float or None] = None,
mutate_zones: Optional[float or None] = None,
size_of_population: Optional[float or None] = None,
rand: Optional[random.Random] = None,
seed: Optional[float or None] = None,
n_cpu: int = 1,
weights: list[int] = None,
fitness_constructor: Callable[[Toolbox], FitnessFunction] = TimeFitness,
fitness_constructor: Callable[[Time | None], FitnessFunction] = TimeFitness,
scheduler_type: SchedulerType = SchedulerType.Genetic,
resource_optimizer: ResourceOptimizer = IdentityResourceOptimizer(),
work_estimator: WorkTimeEstimator = DefaultWorkEstimator()):
work_estimator: WorkTimeEstimator = DefaultWorkEstimator(),
optimize_resources: bool = False,
verbose: bool = True):
super().__init__(scheduler_type=scheduler_type,
resource_optimizer=resource_optimizer,
work_estimator=work_estimator)
self.number_of_generation = number_of_generation
self.mutate_order = mutate_order
self.mutate_resources = mutate_resources
self.mutate_zones = mutate_zones
self.size_of_population = size_of_population
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
Expand All @@ -67,7 +71,7 @@ def __str__(self) -> str:
f'mutate_resources={self.mutate_resources}' \
f']'

def get_params(self, works_count: int) -> tuple[float, float, int]:
def get_params(self, works_count: int) -> tuple[float, float, float, int]:
"""
Return base parameters for model to make new population
Expand All @@ -82,6 +86,10 @@ def get_params(self, works_count: int) -> tuple[float, float, int]:
if mutate_resources is None:
mutate_resources = 0.005

mutate_zones = self.mutate_zones
if mutate_zones is None:
mutate_zones = 0.05

size_of_population = self.size_of_population
if size_of_population is None:
if works_count < 300:
Expand All @@ -90,11 +98,12 @@ def get_params(self, works_count: int) -> tuple[float, float, int]:
size_of_population = 100
else:
size_of_population = works_count // 25
return mutate_order, mutate_resources, size_of_population
return mutate_order, mutate_resources, mutate_zones, size_of_population

def set_use_multiprocessing(self, n_cpu: int):
"""
Set the number of CPU cores
Set the number of CPU cores.
DEPRECATED, NOT WORKING
:param n_cpu:
"""
Expand All @@ -114,6 +123,12 @@ def set_deadline(self, deadline: Time):
def set_weights(self, weights: list[int]):
self._weights = weights

def set_optimize_resources(self, optimize_resources: bool):
self._optimize_resources = optimize_resources

def set_verbose(self, verbose: bool):
self._verbose = verbose

@staticmethod
def generate_first_population(wg: WorkGraph, contractors: list[Contractor],
landscape: LandscapeConfiguration = LandscapeConfiguration(),
Expand Down Expand Up @@ -197,8 +212,10 @@ def schedule_with_cache(self,
init_schedules = GeneticScheduler.generate_first_population(wg, contractors, landscape, spec,
self.work_estimator, self._deadline, self._weights)

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

scheduled_works, schedule_start_time, timeline, order_nodes = build_schedule(wg,
contractors,
Expand All @@ -207,16 +224,20 @@ def schedule_with_cache(self,
self.number_of_generation,
mutate_order,
mutate_resources,
mutate_zones,
init_schedules,
self.rand,
spec,
landscape,
self.fitness_constructor,
fitness_object,
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
35 changes: 26 additions & 9 deletions sampo/scheduler/genetic/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from sampo.schemas.contractor import WorkerContractorPool, Contractor
from sampo.schemas.graph import GraphNode, WorkGraph
from sampo.schemas.landscape import LandscapeConfiguration
from sampo.schemas.requirements import ZoneReq
from sampo.schemas.resources import Worker
from sampo.schemas.schedule import ScheduledWork, Schedule
from sampo.schemas.schedule_spec import ScheduleSpec
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator

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


def convert_schedule_to_chromosome(wg: WorkGraph,
Expand All @@ -24,6 +25,7 @@ def convert_schedule_to_chromosome(wg: WorkGraph,
contractor_borders: np.ndarray,
schedule: Schedule,
spec: ScheduleSpec,
landscape: LandscapeConfiguration,
order: list[GraphNode] | None = None) -> ChromosomeType:
"""
Receive a result of scheduling algorithm and transform it to chromosome
Expand All @@ -35,6 +37,7 @@ def convert_schedule_to_chromosome(wg: WorkGraph,
:param contractor_borders:
:param schedule:
:param spec:
:param landscape:
:param order: if passed, specify the node order that should appear in the chromosome
:return:
"""
Expand All @@ -52,6 +55,9 @@ def convert_schedule_to_chromosome(wg: WorkGraph,
# +1 stores contractors line
resource_chromosome = np.zeros((len(order_chromosome), len(worker_name2index) + 1), dtype=int)

# zone status changes after node executing
zone_changes_chromosome = np.zeros((len(order_chromosome), len(landscape.zone_config.start_statuses)), dtype=int)

for node in order:
node_id = node.work_unit.id
index = work_id2index[node_id]
Expand All @@ -64,13 +70,14 @@ def convert_schedule_to_chromosome(wg: WorkGraph,

resource_border_chromosome = np.copy(contractor_borders)

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


def convert_chromosome_to_schedule(chromosome: ChromosomeType,
worker_pool: WorkerContractorPool,
index2node: dict[int, GraphNode],
index2contractor: dict[int, Contractor],
index2zone: dict[int, str],
worker_pool_indices: dict[int, dict[int, Worker]],
worker_name2index: dict[str, int],
contractor2index: dict[str, int],
Expand All @@ -89,6 +96,7 @@ def convert_chromosome_to_schedule(chromosome: ChromosomeType,
works_resources = chromosome[1]
border = chromosome[2]
spec = chromosome[3]
zone_statuses = chromosome[4]
worker_pool = copy.deepcopy(worker_pool)

# use 3rd part of chromosome in schedule generator
Expand All @@ -98,15 +106,13 @@ def convert_chromosome_to_schedule(chromosome: ChromosomeType,
worker_name2index[worker_index]])

if not isinstance(timeline, JustInTimeTimeline):
timeline = JustInTimeTimeline(index2node.values(), index2contractor.values(), worker_pool, landscape)
timeline = JustInTimeTimeline(index2contractor.values(), landscape)

order_nodes = []

for order_index, work_index in enumerate(works_order):
node = index2node[work_index]
order_nodes.append(node)
# if node.id in node2swork and not node.is_inseparable_son():
# continue

work_spec = spec.get_work_spec(node.id)

Expand All @@ -121,15 +127,26 @@ 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, work_spec,
assigned_parent_time, work_estimator)
st, ft, exec_times = timeline.find_min_start_time_with_additional(node, worker_team, node2swork, work_spec,
assigned_parent_time,
work_estimator=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, work_spec,
st, work_spec.assigned_time, assigned_parent_time, work_estimator)
ft = timeline.schedule(node, node2swork, worker_team, contractor, work_spec,
st, work_spec.assigned_time, assigned_parent_time, work_estimator)
# process zones
zone_reqs = [ZoneReq(index2zone[i], zone_status) for i, zone_status in enumerate(zone_statuses[work_index])]
zone_start_time = timeline.zone_timeline.find_min_start_time(zone_reqs, ft, 0)

# we should deny scheduling
# if zone status change can be scheduled only in delayed manner
if zone_start_time != ft:
node2swork[node].zones_post = timeline.zone_timeline.update_timeline(order_index,
[z.to_zone() for z in zone_reqs],
zone_start_time, 0)

schedule_start_time = min((swork.start_time for swork in node2swork.values() if
len(swork.work_unit.worker_reqs) != 0), default=assigned_parent_time)
Expand Down
Loading

0 comments on commit 05e51a9

Please sign in to comment.