Skip to content

Commit

Permalink
fix: lekin examples with forward and backward scheduling
Browse files Browse the repository at this point in the history
  • Loading branch information
yuetan1988 authored Aug 2, 2024
1 parent 609deba commit 0e82863
Show file tree
Hide file tree
Showing 18 changed files with 165 additions and 33 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[license-url]: https://opensource.org/licenses/Apache-2.0
[pypi-image]: https://badge.fury.io/py/lekin.svg
[pypi-url]: https://pypi.python.org/pypi/lekin
[pepy-image]: https://pepy.tech/badge/lekin/month
[pepy-image]: https://pepy.tech/badge/lekin
[pepy-url]: https://pepy.tech/project/lekin
[build-image]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml/badge.svg?branch=master
[build-url]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml?query=branch%3Amaster
Expand All @@ -29,7 +29,8 @@

**[Documentation](https://python-lekin.readthedocs.io)** | **[Tutorials](https://python-lekin.readthedocs.io/en/latest/tutorials.html)** | **[Release Notes](https://python-lekin.readthedocs.io/en/latest/CHANGELOG.html)** | **[中文](https://github.com/LongxingTan/python-lekin/blob/master/README_zh_CN.md)**

**python-lekin** is a rapid-to-implement and easy-to-use Flexible Job Shop Scheduler Library, named after and inspired by [Lekin](https://web-static.stern.nyu.edu/om/software/lekin/). As a core function in **APS (advanced planning and scheduler)**, it helps manufacturers optimize the allocation of materials and production capacity optimally to balance demand and capacity.
**python-lekin** is a Flexible Job Shop Scheduler Library, named after [Lekin](https://web-static.stern.nyu.edu/om/software/lekin/).
As a core function in **APS (advanced planning and scheduler)**, it helps manufacturers optimize the allocation of materials and production capacity optimally to balance demand and capacity.

- Changeover Optimization
- Ready for demo, research and maybe production
Expand All @@ -50,6 +51,8 @@

## Tutorial

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1H3o6tqJKr1yTvPNI9t0yggbb7BzE_iPz?usp=sharing)

**Installation**

``` shell
Expand Down
5 changes: 3 additions & 2 deletions README_zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@

**[文档](https://python-lekin.readthedocs.io)** | **[教程](https://python-lekin.readthedocs.io/en/latest/tutorials.html)** | **[发布日志](https://python-lekin.readthedocs.io/en/latest/CHANGELOG.html)** | **[English](https://github.com/LongxingTan/python-lekin/blob/master/README.md)**

**python-lekin**是一个APS智能排产调度工具,名字来源于[Lekin](https://web-static.stern.nyu.edu/om/software/lekin/)。在考虑实际约束的前提下,实现动态调整计划排程,高效响应客户订单承诺。

**python-lekin**是一个APS智能排产调度工具。考虑实际约束的前提下,实现动态调整计划排程,高效响应客户订单承诺。

- 支持工艺路线约束
- 支持产能约束
Expand All @@ -41,6 +40,8 @@

## 快速入门

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1H3o6tqJKr1yTvPNI9t0yggbb7BzE_iPz?usp=sharing)

### 安装

``` shell
Expand Down
7 changes: 6 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ coverage:
status:
project:
default:
threshold: 2%
threshold: 3%

patch:
default:
enabled: false
changes: no
26 changes: 24 additions & 2 deletions docs/source/application.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Application
===========
应用
============

基本概念
----------------
Expand Down Expand Up @@ -198,3 +198,25 @@ Material_op一开始,解析爬坡配置, 得到按小时或按数量的map,
计算最终详细排产结果时,根据一个op的初始时间和结束时间,划分落在每个班次的时长,和数量

[修正: 不能在结果生成时,才产出数量。结果时,每个op在资源那里拆成了按单班产能,结果生成时已经不知道具体的详细爬坡了?.在生成时就确定数量. 但最后一个的数量,可以在最后矫正]


车间排产
------------------

1. 准备环境

2. 定义领域模型
Entity
- Job
- Resource
- Timeslot

Planning entity
- JobAssignment

Solution
- Jobschedule

3. 约束

4. Solver
2 changes: 0 additions & 2 deletions docs/source/demand.rst

This file was deleted.

6 changes: 3 additions & 3 deletions docs/source/heuristics.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
heuristics
============
启发式排产
===============


禁忌搜索
Expand All @@ -9,7 +9,7 @@ heuristics
遗传算法
-------------

遗传算法应用在排产中的关键就是如何将排产结果进行编码、以及如何计算fitness。
遗传算法应用在排产中的关键就是如何将排产结果进行编码、以及如何计算fitness。简单理解就是: 把序列的permutation优化一下

每一个可行解被称为一个染色体,一个染色体由多个元素构成,这个元素称为基因。

Expand Down
1 change: 0 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ Finite Capacity Planning
rules
heuristics
application
demand
GitHub <https://github.com/LongxingTan/python-lekin>


Expand Down
13 changes: 12 additions & 1 deletion docs/source/rules.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Rules
规则排产
============

分为rule-based、event-based、resource-based两种思路。
Expand Down Expand Up @@ -72,3 +72,14 @@ SPT—EDD规则
.. code-block:: python
forward(operations, next_op_start_until, with_material_kitting_constraint, align_with_same_production_line, earliest_start_time, earliest_end_time)
终局
----------------------

规则启发在排产中的应用,进行足够的抽象后,灵活使用多种方法的结合。

一批次可开始的。剩余未开始的
- 那么,可开始的是否一定要比未开始的先开始呢?其实不是
- 那么通过工序图的依赖关系,其实给定了每个工序的最早开始时间约束。甚至不是具体的时间,而是一个变量之间的
18 changes: 18 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Examples

forward scheduling
```shell

```


backward scheduling
```shell

```


genetic
```shell

```
2 changes: 2 additions & 0 deletions examples/genetic_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def __init__(self):
if selection_rand[i] > qk[j] and selection_rand[i] <= qk[j + 1]:
population_list[i] = copy.deepcopy(total_chromosome[j + 1])
break

"""----------comparison----------"""
for i in range(population_size * 2):
if chrom_fit[i] < Tbest_now:
Expand All @@ -201,6 +202,7 @@ def __init__(self):
sequence_best = copy.deepcopy(sequence_now)

makespan_record.append(Tbest)

"""----------result----------"""
print("optimal sequence", sequence_best)
print("optimal value:%f" % Tbest)
Expand Down
2 changes: 0 additions & 2 deletions examples/pymoo_demo.py → examples/pymoo_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
每个任务有自己的需求日期
目标为延误日期最少,同时换型最少
做的事情就是把这个序列的permutation优化一下
"""

from typing import Union
Expand Down
19 changes: 14 additions & 5 deletions examples/rule_example.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import argparse
import json
import logging

Expand Down Expand Up @@ -25,7 +26,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, 200))
resource_collector.add_resource_dict(resource)

print([i.resource_id for i in resource_collector.get_all_resources()])
Expand Down Expand Up @@ -73,17 +74,25 @@ def prepare_data(file_path="./data/k1.json"):
return job_collector, resource_collector, route_collector


def run_scheduling(job_collector, resource_collector, route_collector):
# scheduler = BackwardScheduler(job_collector, resource_collector, route_collector)
scheduler = ForwardScheduler(job_collector, resource_collector, route_collector)
def run_scheduling(job_collector, resource_collector, route_collector, use_model="forward"):
if use_model == "forward":
scheduler = ForwardScheduler(job_collector, resource_collector, route_collector)
elif use_model == "backward":
scheduler = BackwardScheduler(job_collector, resource_collector, route_collector)
else:
raise ValueError

scheduler.run()
return


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--use_model", type=str, default="backward")
args = parser.parse_args()

job_collector, resource_collector, route_collector = prepare_data(file_path="./data/k1.json")
run_scheduling(job_collector, resource_collector, route_collector)
run_scheduling(job_collector, resource_collector, route_collector, use_model=args.use_model)

scheduling_res = get_scheduling_res_from_all_jobs(job_collector)
print(scheduling_res)
Expand Down
6 changes: 1 addition & 5 deletions lekin/lekin_struct/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ def __str__(self):

class JobCollector:
def __init__(self):
self.job_list = [] # List to store Job objects
self.job_list = []
self.color_dict = dict() # List to store colors for job
self.index = -1
# self.route_list = []
# self.operation_list = []
# self.resource_list = []
# self.time_slot_list = []

def __iter__(self):
return self
Expand Down
3 changes: 0 additions & 3 deletions lekin/lekin_struct/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ def __init__(
operation_id: str,
operation_name: str,
quantity: int,
# beat_time: Union[int, List[int], float, List[float]],
processing_time: Union[int, List[int], float, List[float]],
pre_time: float = 0, # setup times
post_time: float = 0,
Expand All @@ -31,13 +30,11 @@ def __init__(
self.operation_id = operation_id
self.operation_name = operation_name
self.quantity = quantity
# self.beat_time = beat_time
self.processing_time = processing_time
self.pre_time = pre_time
self.post_time = post_time
self.lead_time = lead_time
self.lag_time = lag_time
# self.demand_time = demand_time
self.route_constraint = route_constraint
self.available_resource = available_resource
self.available_resource_priority = available_resource_priority
Expand Down
2 changes: 1 addition & 1 deletion lekin/lekin_struct/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def find_first_index_larger(self, input_value, lists):
for j, value in enumerate(lists):
if value > input_value:
return j
return None # If no value is larger than the input
return None

def find_last_index_larger(self, input_value, lists):
lists = lists[::-1]
Expand Down
11 changes: 11 additions & 0 deletions lekin/solver/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class SolverConfig:
def __init__(self, entity_selector=None, move_selector=None, termination=None):
self.entity_selector = entity_selector
self.move_selector = move_selector
self.termination = termination


class TerminationConfig:
def __init__(self, seconds_spent_limit=None, max_iterations=None):
self.seconds_spent_limit = seconds_spent_limit
self.max_iterations = max_iterations
65 changes: 65 additions & 0 deletions lekin/solver/solver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import copy
import random
import time


def check_constraints(job_assignments):
for i, assignment1 in enumerate(job_assignments):
for j, assignment2 in enumerate(job_assignments):
if i < j:
# Check resource conflict
if assignment1.resource == assignment2.resource:
if not (
assignment1.timeslot.end_time <= assignment2.timeslot.start_time
or assignment2.timeslot.end_time <= assignment1.timeslot.start_time
):
return False
# Check timeslot conflict
if assignment1.timeslot is None or assignment2.timeslot is None:
return False
return True


class LekinSolver(object):
def __init__(self, config):
self.config = config
self.best_solution = None

def solve(self, schedule):
start_time = time.time()
current_solution = copy.deepcopy(schedule)
self.best_solution = current_solution
tabu_list = []
iterations = 0

while not self._is_termination_reached(start_time, iterations):
neighbors = self.config.move_selector.generate_neighbors(current_solution)
feasible_neighbors = [neighbor for neighbor in neighbors if check_constraints(neighbor.job_assignments)]

if not feasible_neighbors:
continue

feasible_neighbors.sort(key=self.config.entity_selector.evaluate)
current_solution = feasible_neighbors[0]
current_cost = self.config.entity_selector.evaluate(current_solution)
best_cost = self.config.entity_selector.evaluate(self.best_solution)

if current_cost < best_cost:
self.best_solution = current_solution

tabu_list.append(current_solution)
if len(tabu_list) > self.config.entity_selector.tabu_tenure:
tabu_list.pop(0)

iterations += 1

return self.best_solution

def _is_termination_reached(self, start_time, iterations):
if self.config.termination.seconds_spent_limit:
if time.time() - start_time > self.config.termination.seconds_spent_limit:
return True
if self.config.termination.max_iterations:
if iterations >= self.config.termination.max_iterations:
return True
return False
3 changes: 0 additions & 3 deletions lekin/solver/solver_auto.py

This file was deleted.

0 comments on commit 0e82863

Please sign in to comment.