Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update backward and forward #31

Merged
merged 5 commits into from
Aug 21, 2023
Merged
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
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.
Loading