Skip to content

Commit

Permalink
Update backward and forward (#31)
Browse files Browse the repository at this point in the history
* Update operation

* Update backward

* Update forward and backward

* Update forward and backward

---------

Co-authored-by: yuetan1988 <[email protected]>
  • Loading branch information
LongxingTan and yuetan1988 committed Aug 21, 2023
1 parent ad63841 commit b6dbc6f
Show file tree
Hide file tree
Showing 26 changed files with 132 additions and 68 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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()
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
- spt
- fifo
- edd
- heuristics
- forward scheduling
- backward scheduling
- meta heuristics
- local search
- shifting_bottle_neck
- genetic

### Contributor
Expand Down
3 changes: 2 additions & 1 deletion docs/requirements_docs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pydata_sphinx_theme==0.8.0
docutils
sphinx-autobuild

tensorflow==2.10.0
pandas
numpy
2 changes: 1 addition & 1 deletion docs/source/application.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Application
----------------

MRP: Material Requirements Planning
- 每个零件的库存


BOM: Bill Of Materials

Expand Down
1 change: 1 addition & 0 deletions docs/source/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ SPT—EDD规则

顺排和倒排,和其他规则启发式算法一样,一个工序集一个工序集的排。每排一个工序,工序job完成后,更新机器、job状态、后续job状态。

在顺排中,排的比较紧密的资源往往就是瓶颈资源。

倒排
---------------
Expand Down
6 changes: 3 additions & 3 deletions examples/rule_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ 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_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)
Expand Down Expand Up @@ -86,8 +85,9 @@ 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

Expand Down
3 changes: 2 additions & 1 deletion lekin/dashboard/gantt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 2 additions & 11 deletions lekin/lekin_struct/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion lekin/lekin_struct/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import math

import numpy as np
import pandas as pd

from lekin.lekin_struct.timeslot import TimeSlot
Expand All @@ -21,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)
Expand Down Expand Up @@ -67,7 +70,8 @@ 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):
Expand Down
2 changes: 2 additions & 0 deletions lekin/objective/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from lekin.objective.makespan import calculate_makespan
from lekin.objective.tardiness import calculate_tardiness
14 changes: 7 additions & 7 deletions lekin/objective/makespan.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
4 changes: 2 additions & 2 deletions lekin/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
17 changes: 13 additions & 4 deletions lekin/solver/construction_heuristics/backward.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -39,7 +45,8 @@ 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
Expand Down Expand Up @@ -76,9 +83,11 @@ 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):
Expand Down
9 changes: 8 additions & 1 deletion lekin/solver/construction_heuristics/forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 39 additions & 34 deletions lekin/solver/meta_heuristics/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.

0 comments on commit b6dbc6f

Please sign in to comment.