From d4e37994dc095b9f41050cebd0d6c085b80898d3 Mon Sep 17 00:00:00 2001 From: LongxingTan Date: Fri, 18 Aug 2023 20:45:48 +0800 Subject: [PATCH 1/4] Update operation --- lekin/lekin_struct/operation.py | 106 ++++++++++++++++---------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/lekin/lekin_struct/operation.py b/lekin/lekin_struct/operation.py index d94f9f7..6814831 100644 --- a/lekin/lekin_struct/operation.py +++ b/lekin/lekin_struct/operation.py @@ -5,6 +5,59 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union +class Operation: + def __init__( + self, + operation_id: str, + operation_name: str, + quantity: int, + processing_time: Union[int, List[int]], + pre_time=0, # setup times + post_time=0, + lead_time=0, + route_constraint=None, + available_resource=None, + available_resource_priority=None, + parent_job_id=None, + prev_operation_ids=None, + next_operation_ids=None, + **kwargs, + ): + self.operation_id = operation_id + self.operation_name = operation_name + self.quantity = quantity + self.processing_time = processing_time + self.pre_time = pre_time + self.post_time = post_time + self.lead_time = lead_time + # self.demand_time = demand_time + self.route_constraint = route_constraint + self.available_resource = available_resource + self.available_resource_priority = available_resource_priority + self.parent_job_id = parent_job_id + self.prev_operation_ids = prev_operation_ids # predecessors + self.next_operation_ids = next_operation_ids # successors + + self.earliest_start_time = None + self.latest_start_time = None + self.earliest_end_time = None + self.latest_end_time = None + + self.assigned_resource = None # Track the assigned resource + self.assigned_time_slot = None # Track the assigned time slot + + for key, value in kwargs.items(): + setattr(self, key, value) + + # Add a method to calculate granularity metric based on processing time and available time slot + def calculate_granularity_metric(self, available_time_slot): + # Calculate the granularity metric based on processing time and available time slot + pass + + def __str__(self): + return f"{self.operation_id}-{self.operation_name}" + + class OperationCollector: def __init__(self): self.operation_list = [] # List to store Operation objects @@ -57,56 +110,3 @@ def get_operations_by_job_and_route(self, job_list, route_list): # (op for op in self.operations if op.operation_id == next_operation_id), None # ) # return job_operations - - -class Operation: - def __init__( - self, - operation_id: str, - operation_name: str, - quantity: int, - processing_time: Union[int, List[int]], - pre_time=0, # setup times - post_time=0, - lead_time=0, - route_constraint=None, - available_resource=None, - available_resource_priority=None, - parent_job_id=None, - prev_operation_ids=None, - next_operation_ids=None, - **kwargs, - ): - self.operation_id = operation_id - self.operation_name = operation_name - self.quantity = quantity - self.processing_time = processing_time - self.pre_time = pre_time - self.post_time = post_time - self.lead_time = lead_time - # self.demand_time = demand_time - self.route_constraint = route_constraint - self.available_resource = available_resource - self.available_resource_priority = available_resource_priority - self.parent_job_id = parent_job_id - self.prev_operation_ids = prev_operation_ids # predecessors - self.next_operation_ids = next_operation_ids # successors - - self.earliest_start_time = None - self.latest_start_time = None - self.earliest_end_time = None - self.latest_end_time = None - - self.assigned_resource = None # Track the assigned resource - self.assigned_time_slot = None # Track the assigned time slot - - for key, value in kwargs.items(): - setattr(self, key, value) - - # Add a method to calculate granularity metric based on processing time and available time slot - def calculate_granularity_metric(self, available_time_slot): - # Calculate the granularity metric based on processing time and available time slot - pass - - def __str__(self): - return f"{self.operation_id}-{self.operation_name}" From bf503fd845d7cfe9019acdc82b6c943958c9291b Mon Sep 17 00:00:00 2001 From: LongxingTan Date: Sat, 19 Aug 2023 09:13:42 +0800 Subject: [PATCH 2/4] Update backward --- examples/genetic_example.py | 0 examples/rule_example.py | 5 +-- lekin/lekin_struct/resource.py | 11 ++++--- .../construction_heuristics/backward.py | 32 +++++++++++-------- 4 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 examples/genetic_example.py diff --git a/examples/genetic_example.py b/examples/genetic_example.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/rule_example.py b/examples/rule_example.py index 13c9e41..af16a18 100644 --- a/examples/rule_example.py +++ b/examples/rule_example.py @@ -12,7 +12,7 @@ Route, RouteCollector, ) -from lekin.solver.construction_heuristics import ForwardScheduler, LPSTScheduler, SPTScheduler +from lekin.solver.construction_heuristics import BackwardScheduler, ForwardScheduler, LPSTScheduler, SPTScheduler logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) @@ -35,7 +35,7 @@ def prepare_data(file_path="./data/k1.json"): re_name = re["machineName"] re_id = int(re_name.replace("M", "")) resource = Resource(resource_id=re_id, resource_name=re_name) - resource.available_hours = list(range(1, 100)) + resource.available_hours = list(range(1, 150)) resource_collector.add_resource_dict({re_id: resource}) # print([i.resource_id for i in resource_collector.get_all_resources()]) # print(resource_collector.get_all_resources()[0].available_hours) @@ -87,6 +87,7 @@ def prepare_data(file_path="./data/k1.json"): def run_scheduling(job_collector, resource_collector, route_collector): scheduler = ForwardScheduler(job_collector, resource_collector, route_collector) + # scheduler = BackwardScheduler(job_collector, resource_collector, route_collector) scheduler.run() return diff --git a/lekin/lekin_struct/resource.py b/lekin/lekin_struct/resource.py index 34dfb46..085cfb6 100644 --- a/lekin/lekin_struct/resource.py +++ b/lekin/lekin_struct/resource.py @@ -59,19 +59,20 @@ def get_available_timeslot_for_op(self, start=None, end=None, periods=None, freq else: return [] - def get_earliest_available_time(self, duration=None): + def get_earliest_available_time(self, duration=None, start=None): if len(self.available_hours) > len(self.assigned_hours): return min(set(self.available_hours).difference(set(self.assigned_hours))) else: return None - def get_latest_available_time(self, duration=None): - return + def get_latest_available_time(self, duration=None, end=None): + self.update_continuous_empty_hours() + return max([i for i in self.continuous_empty_hours[:end] if i >= duration]) def update_continuous_empty_hours(self): - if len(self.available_hours) != len(self.available_timeslots): + if len(self.available_hours) != len(self._available_timeslots): pass - if len(self.assigned_hours) != len(self.assigned_time_slot): + if len(self.assigned_hours) != len(self.assigned_time_slots): pass # for hours_list in self.available_hours: diff --git a/lekin/solver/construction_heuristics/backward.py b/lekin/solver/construction_heuristics/backward.py index 92d47be..bf07264 100644 --- a/lekin/solver/construction_heuristics/backward.py +++ b/lekin/solver/construction_heuristics/backward.py @@ -37,11 +37,12 @@ def scheduling_job(self, job, resource_collector, route_collector): job.operations = route.operations_sequence - op_earliest_start = 0 - for operation in job.operations[::-1]: + op_earliest_start = 0 # forward constraint + op_latest_end = 150 # backward constraint + for operation in job.operations[::-1]: # inverse logging.info(f"\tAssign Operation {operation.operation_id} of Job {job.job_id}") chosen_resource, chosen_timeslot_hour = self.find_best_resource_and_timeslot_for_operation( - operation, op_earliest_start + operation, op_latest_end, op_earliest_start ) if chosen_resource and chosen_timeslot_hour: @@ -57,24 +58,27 @@ def scheduling_job(self, job, resource_collector, route_collector): chosen_resource.assigned_operations.append(operation) chosen_resource.assigned_hours += chosen_timeslot_hour - op_earliest_start = chosen_timeslot_hour[-1] + 1 + # op_earliest_start = chosen_timeslot_hour[-1] + 1 + op_latest_end = chosen_timeslot_hour[0] - 1 return - def find_best_resource_and_timeslot_for_operation(self, operation, op_earliest_start, **kwargs): + def find_best_resource_and_timeslot_for_operation( + self, operation, op_latest_end=None, op_earliest_start=None, **kwargs + ): available_resource = operation.available_resource - earliest_index = 0 - resource_earliest_time = float("inf") + latest_index = float("inf") + resource_latest_time = 0 for i, resource in enumerate(available_resource): - resource_time = resource.get_earliest_available_time(duration=operation.processing_time) + resource_time = resource.get_latest_available_time(duration=operation.processing_time, end=op_latest_end) - if resource_time < resource_earliest_time: - earliest_index = i - resource_earliest_time = resource_time + if resource_time > resource_latest_time: + latest_index = i + resource_latest_time = resource_time - chosen_resource = available_resource[earliest_index] - earliest_time = int(max(op_earliest_start, resource_earliest_time)) - chosen_hours = list(range(earliest_time, earliest_time + math.ceil(operation.processing_time))) + chosen_resource = available_resource[latest_index] + latest_time = int(min(op_latest_end, resource_latest_time)) + chosen_hours = list(range(latest_time - math.ceil(operation.processing_time), latest_time + 0)) return chosen_resource, chosen_hours def assign_operation(self, operation, start_time, end_time, resources): From 75081ac190e1a5962bb463f87cc8dba91819c1e4 Mon Sep 17 00:00:00 2001 From: LongxingTan Date: Mon, 21 Aug 2023 11:03:25 +0800 Subject: [PATCH 3/4] Update forward and backward --- .github/workflows/test.yml | 42 +++++++++++ CHANGELOG.md | 5 +- docs/source/application.rst | 2 +- docs/source/rules.rst | 1 + examples/rule_example.py | 2 +- lekin/lekin_struct/resource.py | 3 +- lekin/scheduler.py | 4 +- .../construction_heuristics/backward.py | 15 +++- .../solver/construction_heuristics/forward.py | 9 ++- lekin/solver/meta_heuristics/genetic.py | 73 ++++++++++--------- tests/test_dashboard/test_gantt.py | 0 tests/test_lekin_struct/test_job.py | 0 tests/test_lekin_struct/test_operation.py | 0 tests/test_lekin_struct/test_resource.py | 0 tests/test_lekin_struct/test_route.py | 0 tests/test_lekin_struct/test_timeslot.py | 0 .../test_meta_heuristics/test_genetic.py | 0 .../test_meta_heuristics/test_nsga3.py | 0 .../test_meta_heuristics/test_tabu_search.py | 0 .../test_reinforcement_learning/__init__.py | 0 .../test_reinforcement_learning/test_dqn.py | 0 21 files changed, 110 insertions(+), 46 deletions(-) create mode 100644 tests/test_dashboard/test_gantt.py create mode 100644 tests/test_lekin_struct/test_job.py create mode 100644 tests/test_lekin_struct/test_operation.py create mode 100644 tests/test_lekin_struct/test_resource.py create mode 100644 tests/test_lekin_struct/test_route.py create mode 100644 tests/test_lekin_struct/test_timeslot.py create mode 100644 tests/test_solver/test_meta_heuristics/test_genetic.py create mode 100644 tests/test_solver/test_meta_heuristics/test_nsga3.py create mode 100644 tests/test_solver/test_meta_heuristics/test_tabu_search.py create mode 100644 tests/test_solver/test_reinforcement_learning/__init__.py create mode 100644 tests/test_solver/test_reinforcement_learning/test_dqn.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d7e1b8..b78dac0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,3 +81,45 @@ jobs: flags: cpu, unittest name: CPU-coverage fail_ci_if_error: false + + docs: + name: Test docs build + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements_docs.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + sudo apt-get update && sudo apt-get install -y pandoc + python -m pip install --upgrade pip + pip install -r docs/requirements_docs.txt + shell: bash + + - name: Build sphinx documentation + run: | + cd docs + make clean + make html --debug --jobs 2 SPHINXOPTS="-W" + + - name: Upload built docs + uses: actions/upload-artifact@v2 + with: + name: docs-results-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.requires }} + path: docs/build/html/ + # Use always() to always run this step to publish test results when there are test failures + if: success() diff --git a/CHANGELOG.md b/CHANGELOG.md index 811faaa..4eb3502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,10 @@ - spt - fifo - edd - - heuristics + - forward scheduling + - backward scheduling + - meta heuristics - local search - - shifting_bottle_neck - genetic ### Contributor diff --git a/docs/source/application.rst b/docs/source/application.rst index b42790e..b342da8 100644 --- a/docs/source/application.rst +++ b/docs/source/application.rst @@ -10,7 +10,7 @@ Application ---------------- MRP: Material Requirements Planning -- 每个零件的库存 + BOM: Bill Of Materials diff --git a/docs/source/rules.rst b/docs/source/rules.rst index e3a1825..2acb1be 100644 --- a/docs/source/rules.rst +++ b/docs/source/rules.rst @@ -47,6 +47,7 @@ SPT—EDD规则 顺排和倒排,和其他规则启发式算法一样,一个工序集一个工序集的排。每排一个工序,工序job完成后,更新机器、job状态、后续job状态。 +在顺排中,排的比较紧密的资源往往就是瓶颈资源。 倒排 --------------- diff --git a/examples/rule_example.py b/examples/rule_example.py index af16a18..705b2e7 100644 --- a/examples/rule_example.py +++ b/examples/rule_example.py @@ -35,7 +35,7 @@ def prepare_data(file_path="./data/k1.json"): re_name = re["machineName"] re_id = int(re_name.replace("M", "")) resource = Resource(resource_id=re_id, resource_name=re_name) - resource.available_hours = list(range(1, 150)) + resource.available_hours = list(range(1, 151)) resource_collector.add_resource_dict({re_id: resource}) # print([i.resource_id for i in resource_collector.get_all_resources()]) # print(resource_collector.get_all_resources()[0].available_hours) diff --git a/lekin/lekin_struct/resource.py b/lekin/lekin_struct/resource.py index 085cfb6..1726e1e 100644 --- a/lekin/lekin_struct/resource.py +++ b/lekin/lekin_struct/resource.py @@ -4,6 +4,7 @@ import math +import numpy as np import pandas as pd from lekin.lekin_struct.timeslot import TimeSlot @@ -67,7 +68,7 @@ def get_earliest_available_time(self, duration=None, start=None): def get_latest_available_time(self, duration=None, end=None): self.update_continuous_empty_hours() - return max([i for i in self.continuous_empty_hours[:end] if i >= duration]) + return max([i + 1 for (i, v) in enumerate(self.continuous_empty_hours[:end]) if v >= duration]) def update_continuous_empty_hours(self): if len(self.available_hours) != len(self._available_timeslots): diff --git a/lekin/scheduler.py b/lekin/scheduler.py index 98beea9..4662266 100644 --- a/lekin/scheduler.py +++ b/lekin/scheduler.py @@ -10,12 +10,12 @@ class Scheduler(object): - def __init__(self, objective, solver, max_operations): + def __init__(self, objective, solver, max_operations, **kwargs): self.objective = objective self.solver = solver self.max_operations = max_operations - def solve(self, jobs, machines): + def run(self, jobs, machines): self.solver.solve(jobs, machines) def evaluate(self): diff --git a/lekin/solver/construction_heuristics/backward.py b/lekin/solver/construction_heuristics/backward.py index bf07264..388ec4b 100644 --- a/lekin/solver/construction_heuristics/backward.py +++ b/lekin/solver/construction_heuristics/backward.py @@ -3,12 +3,18 @@ import logging import math -from lekin.lekin_struct.timeslot import TimeSlot +from lekin.lekin_struct import JobCollector, ResourceCollector, RouteCollector, TimeSlot from lekin.solver.construction_heuristics.base import BaseScheduler class BackwardScheduler(object): - def __init__(self, job_collector, resource_collector, route_collector=None, **kwargs): + def __init__( + self, + job_collector: JobCollector, + resource_collector: ResourceCollector, + route_collector: RouteCollector = None, + **kwargs, + ): self.job_collector = job_collector self.resource_collector = resource_collector self.route_collector = route_collector @@ -39,7 +45,7 @@ def scheduling_job(self, job, resource_collector, route_collector): op_earliest_start = 0 # forward constraint op_latest_end = 150 # backward constraint - for operation in job.operations[::-1]: # inverse + for operation in job.operations[::-1]: # inverse for backward logging.info(f"\tAssign Operation {operation.operation_id} of Job {job.job_id}") chosen_resource, chosen_timeslot_hour = self.find_best_resource_and_timeslot_for_operation( operation, op_latest_end, op_earliest_start @@ -76,9 +82,10 @@ def find_best_resource_and_timeslot_for_operation( latest_index = i resource_latest_time = resource_time + # print(operation.operation_id, operation.processing_time, op_latest_end, resource_latest_time) chosen_resource = available_resource[latest_index] latest_time = int(min(op_latest_end, resource_latest_time)) - chosen_hours = list(range(latest_time - math.ceil(operation.processing_time), latest_time + 0)) + chosen_hours = list(range(latest_time - math.ceil(operation.processing_time), latest_time + 1)) return chosen_resource, chosen_hours def assign_operation(self, operation, start_time, end_time, resources): diff --git a/lekin/solver/construction_heuristics/forward.py b/lekin/solver/construction_heuristics/forward.py index eca7f98..aa66e53 100644 --- a/lekin/solver/construction_heuristics/forward.py +++ b/lekin/solver/construction_heuristics/forward.py @@ -3,11 +3,18 @@ import logging import math +from lekin.lekin_struct import JobCollector, ResourceCollector, RouteCollector from lekin.solver.construction_heuristics.base import BaseScheduler class ForwardScheduler(BaseScheduler): - def __init__(self, job_collector, resource_collector, route_collector=None, **kwargs): + def __init__( + self, + job_collector: JobCollector, + resource_collector: ResourceCollector, + route_collector: RouteCollector = None, + **kwargs, + ): super().__init__(job_collector, resource_collector, **kwargs) self.job_collector = job_collector self.resource_collector = resource_collector diff --git a/lekin/solver/meta_heuristics/genetic.py b/lekin/solver/meta_heuristics/genetic.py index 92e26b9..0dba9ba 100644 --- a/lekin/solver/meta_heuristics/genetic.py +++ b/lekin/solver/meta_heuristics/genetic.py @@ -3,24 +3,61 @@ import copy import random +from lekin.lekin_struct import JobCollector, ResourceCollector, RouteCollector + class GeneticScheduler: def __init__( self, - job_collector=None, + job_collector: JobCollector, + resource_collector: ResourceCollector, + route_collector: RouteCollector = None, initial_schedule=None, population_size=50, generations=1000, crossover_rate=0.8, mutation_rate=0.2, + **kwargs, ): self.job_collector = job_collector - self.initial_schedule = initial_schedule # 倒排顺排后的初始结果 + self.initial_schedule = initial_schedule self.population_size = population_size self.generations = generations self.crossover_rate = crossover_rate self.mutation_rate = mutation_rate + def run(self): + population = self.initialize_population() + + for generation in range(self.generations): + selected_individuals = self.selection(population) + new_population = [] + + while len(new_population) < self.population_size: + parent1 = random.choice(selected_individuals) + parent2 = random.choice(selected_individuals) + + if random.random() < self.crossover_rate: + offspring1, offspring2 = self.crossover(parent1, parent2) + else: + offspring1, offspring2 = parent1, parent2 + + if random.random() < self.mutation_rate: + offspring1 = self.mutation(offspring1) + if random.random() < self.mutation_rate: + offspring2 = self.mutation(offspring2) + + new_population.append(offspring1) + new_population.append(offspring2) + + population = new_population + + # Find the best solution in the final population + best_solution = min(population, key=lambda chromosome: self.fitness(chromosome)[0]) + + # Return the best schedule + return self.job_collector.create_schedule_from_operations(best_solution) + def initialize_population(self): population = [] for _ in range(self.population_size): @@ -59,35 +96,3 @@ def mutation(self, chromosome): # Return the mutated chromosome mutated_chromosome = 0 return mutated_chromosome - - def evolve(self): - population = self.initialize_population() - - for generation in range(self.generations): - selected_individuals = self.selection(population) - new_population = [] - - while len(new_population) < self.population_size: - parent1 = random.choice(selected_individuals) - parent2 = random.choice(selected_individuals) - - if random.random() < self.crossover_rate: - offspring1, offspring2 = self.crossover(parent1, parent2) - else: - offspring1, offspring2 = parent1, parent2 - - if random.random() < self.mutation_rate: - offspring1 = self.mutation(offspring1) - if random.random() < self.mutation_rate: - offspring2 = self.mutation(offspring2) - - new_population.append(offspring1) - new_population.append(offspring2) - - population = new_population - - # Find the best solution in the final population - best_solution = min(population, key=lambda chromosome: self.fitness(chromosome)[0]) - - # Return the best schedule - return self.job_collector.create_schedule_from_operations(best_solution) diff --git a/tests/test_dashboard/test_gantt.py b/tests/test_dashboard/test_gantt.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_job.py b/tests/test_lekin_struct/test_job.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_operation.py b/tests/test_lekin_struct/test_operation.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_resource.py b/tests/test_lekin_struct/test_resource.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_route.py b/tests/test_lekin_struct/test_route.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_timeslot.py b/tests/test_lekin_struct/test_timeslot.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_genetic.py b/tests/test_solver/test_meta_heuristics/test_genetic.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_nsga3.py b/tests/test_solver/test_meta_heuristics/test_nsga3.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_tabu_search.py b/tests/test_solver/test_meta_heuristics/test_tabu_search.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_reinforcement_learning/__init__.py b/tests/test_solver/test_reinforcement_learning/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_reinforcement_learning/test_dqn.py b/tests/test_solver/test_reinforcement_learning/test_dqn.py new file mode 100644 index 0000000..e69de29 From 939b8ae0bc6795063cb43741daf5695a5563f7dc Mon Sep 17 00:00:00 2001 From: LongxingTan Date: Mon, 21 Aug 2023 14:47:07 +0800 Subject: [PATCH 4/4] Update forward and backward --- docs/requirements_docs.txt | 3 ++- examples/rule_example.py | 4 ++-- lekin/dashboard/gantt.py | 3 ++- lekin/lekin_struct/job.py | 13 ++----------- lekin/lekin_struct/resource.py | 2 ++ lekin/objective/__init__.py | 2 ++ lekin/objective/makespan.py | 14 +++++++------- 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt index 26cba64..b1acf8e 100644 --- a/docs/requirements_docs.txt +++ b/docs/requirements_docs.txt @@ -9,4 +9,5 @@ pydata_sphinx_theme==0.8.0 docutils sphinx-autobuild -tensorflow==2.10.0 +pandas +numpy diff --git a/examples/rule_example.py b/examples/rule_example.py index 705b2e7..2bfe147 100644 --- a/examples/rule_example.py +++ b/examples/rule_example.py @@ -86,8 +86,8 @@ def prepare_data(file_path="./data/k1.json"): def run_scheduling(job_collector, resource_collector, route_collector): - scheduler = ForwardScheduler(job_collector, resource_collector, route_collector) - # scheduler = BackwardScheduler(job_collector, resource_collector, route_collector) + # scheduler = ForwardScheduler(job_collector, resource_collector, route_collector) + scheduler = BackwardScheduler(job_collector, resource_collector, route_collector) scheduler.run() return diff --git a/lekin/dashboard/gantt.py b/lekin/dashboard/gantt.py index 5511f99..5ecb133 100644 --- a/lekin/dashboard/gantt.py +++ b/lekin/dashboard/gantt.py @@ -23,12 +23,13 @@ def get_scheduling_res_from_all_jobs(job_collector): [ op.operation_id, op.parent_job_id, + op.quantity, op.assigned_resource.resource_id, min(op.assigned_hours), max(op.assigned_hours), ] ) - scheduling_res = pd.DataFrame(scheduling_res, columns=["Operation", "Job", "Resource", "Start", "End"]) + scheduling_res = pd.DataFrame(scheduling_res, columns=["Operation", "Job", "Quantity", "Resource", "Start", "End"]) scheduling_res["Duration"] = scheduling_res["End"] - scheduling_res["Start"] # + 1 return scheduling_res diff --git a/lekin/lekin_struct/job.py b/lekin/lekin_struct/job.py index 4c850d7..5344491 100644 --- a/lekin/lekin_struct/job.py +++ b/lekin/lekin_struct/job.py @@ -2,17 +2,6 @@ Struct Job/订单作业 - a job could finish one product while finished - job/mo/operation/activity - -property - - 已完成活动 - - 待完成活动 - - processing time - - due date - - weight - - slack time remaining - - critical ratio - - priority - - 属于哪个订单 """ from datetime import datetime @@ -46,6 +35,8 @@ def __init__( self.assigned_route_id = assigned_route_id # Route object assigned to this job self.assigned_bom_id = assigned_bom_id self._operations_sequence = [] # List of Operation objects for this job + self.makespan = None # finish of the job + self.tardiness = None # delay of the job for key, value in kwargs.items(): setattr(self, key, value) diff --git a/lekin/lekin_struct/resource.py b/lekin/lekin_struct/resource.py index 1726e1e..c472653 100644 --- a/lekin/lekin_struct/resource.py +++ b/lekin/lekin_struct/resource.py @@ -22,6 +22,8 @@ def __init__(self, resource_id, resource_name=None, max_tasks=1, **kwargs): self.assigned_operations = [] self.assigned_time_slots = [] self.assigned_hours = [] + self.changeover_number = None # number of times + self.changeover_time = None # total time costs for key, value in kwargs.items(): setattr(self, key, value) diff --git a/lekin/objective/__init__.py b/lekin/objective/__init__.py index e69de29..428b4b1 100644 --- a/lekin/objective/__init__.py +++ b/lekin/objective/__init__.py @@ -0,0 +1,2 @@ +from lekin.objective.makespan import calculate_makespan +from lekin.objective.tardiness import calculate_tardiness diff --git a/lekin/objective/makespan.py b/lekin/objective/makespan.py index f4a15c2..c354ece 100644 --- a/lekin/objective/makespan.py +++ b/lekin/objective/makespan.py @@ -1,11 +1,11 @@ -def calculate_makespan(schedule_result): - end_times = [end_time for (_, end_time) in schedule_result.values()] - return max(end_times) +def calculate_makespan(job_collector): + for job in job_collector.job_list: + op = job.operations + job.makespan = op.assigned_hours[-1] - -def calculate_flow_time(schedule_result, job): - start_time, end_time = schedule_result[job.route.operations[-1]] - return end_time - job.release_date + if job.demand_date is not None: + job.tardiness = job.makespan - job.demand_date + return def calculate_changeover_time(schedule_result, job_collector):