Skip to content

Commit

Permalink
Update (#25)
Browse files Browse the repository at this point in the history
Co-authored-by: LongxingTan <[email protected]>
  • Loading branch information
yuetan1988 and LongxingTan authored Jul 21, 2023
1 parent 5a443c2 commit 0036690
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 13 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ coverage.xml
/reference/
/data/
/weights/
/examples/jobshoppro/*
/reference/jobshoppro/*
/business/*
/examples/*
/examples/*.xlsx
49 changes: 49 additions & 0 deletions examples/basic_aps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
backward + forward + push
"""

import pandas as pd

from lekin.lekin_struct import Job, JobCollector, Resource, ResourceCollector, Route
from lekin.solver.construction_heuristics.rule import BackwardScheduler

mo = pd.read_excel("./MOInput.xlsx", sheet_name="2.1.工单")
resource_date = pd.read_excel("./MOInput.xlsx", sheet_name="5.1.资源可用列表")

# print(mo)
print(resource_date)


job_collector = JobCollector()
for index, row in mo.iterrows():
job_id = row["产品ID"]
priority = row["优先级"]
earliest_start_date = row["任务开始控制"]
demand_date = row["交付时间"]
assigned_route = map()
# route_id = row['route_id']
job_collector.add_job(
Job(
job_id=job_id,
priority=priority,
demand_time=demand_date,
earliest_start_time=earliest_start_date,
assigned_route=assigned_route,
)
)


resource_collector = ResourceCollector()
for group, row in resource_date.groupby("资源ID"):
resource_id = group
start_time = row["开始时间"]
end_time = row["结束时间"]

resource = Resource(resource_id=resource_id)
for s, d in zip(start_time, end_time):
resource.add_timeslot(start_time, end_time)
resource_collector.add_resource(resource)


# scheduler = BackwardScheduler(job_collector, resource_collector)
# scheduler.run()
4 changes: 2 additions & 2 deletions lekin/lekin_struct/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"""

from lekin.lekin_struct.job import Job # 成品需求
from lekin.lekin_struct.job import Job, JobCollector # 成品需求
from lekin.lekin_struct.operation import Operation # 工序
from lekin.lekin_struct.resource import Machine # 机器
from lekin.lekin_struct.resource import Resource, ResourceCollector # 机器
from lekin.lekin_struct.route import Route
from lekin.lekin_struct.timeslot import TimeSlot
31 changes: 28 additions & 3 deletions lekin/lekin_struct/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,38 @@
class JobCollector:
def __init__(self):
self.jobs = [] # List to store Job objects
self.routes = [] # List to store route with sequence of jobs
self.operations = []
self.routes = []
self.resources = []
self.time_slots = []

def add_job(self, job):
self.jobs.append(job)
if job.assigned_route is not None:
self.routes.append(job.assigned_route)
else:
self.routes.clear()

def add_route(self, route):
self.routes.append(route)

def add_operation(self, operation):
self.operations.append(operation)

def add_resource(self, resource):
self.resources.append(resource)

def add_time_slot(self, time_slot):
self.time_slots.append(time_slot)

def get_job_by_id(self, job_id):
for job in self.jobs:
if job.job_id == job_id:
return job
return None

def get_operations_by_job_and_route(self, job_id, route_id):
assert len(job_id) == len(route_id)
job_operations = []
for operation in self.operations:
if operation.parent_operation_id is None and operation.route_id == route_id:
Expand Down Expand Up @@ -87,13 +101,24 @@ def get_schedule(self):

return schedule

def get_all_operations(self):
return self.operations

def get_all_resources(self):
return self.resources

def get_all_time_slots(self):
return self.time_slots


class Job(object):
def __init__(self, job_id, priority, demand_time, assigned_route, assigned_tree):
def __init__(self, job_id, priority, demand_time, earliest_start_time=None, assigned_route=None, assigned_bom=None):
self.job_id = job_id
self.priority = priority
self.demand_time = demand_time
self.assigned_route = None # Route object assigned to this job
self.earliest_start_time = earliest_start_time # Material constraint
self.assigned_route = assigned_route # Route object assigned to this job
self.assigned_bom = assigned_bom
self.assigned_operations = [] # List of Operation objects assigned to this job

def assign_route(self, route):
Expand Down
2 changes: 1 addition & 1 deletion lekin/lekin_struct/material.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
物料检查
物料
- 物料可以锁定,可以占用,可以空闲
"""
3 changes: 3 additions & 0 deletions lekin/lekin_struct/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@ def __init__(self, operation_id, operation_name, processing_time, demand_time, r
self.earliest_end_time = None
self.latest_end_time = None

self.assigned_resource = None # To store the assigned resource
self.assigned_time_slot = None # To store the assigned time slot

def __str__(self):
pass
7 changes: 4 additions & 3 deletions lekin/lekin_struct/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class ResourceCollector:
def __init__(self):
self.resources = {}

def add_resource(self, resource):
def add_resource(self, resource, priority=1, time_slots=None):
self.resources[resource.resource_id] = resource

def get_resource(self, resource_id):
Expand All @@ -67,13 +67,14 @@ def get_unoccupied_time_slots(self):


class Resource:
def __init__(self, resource_id, resource_name):
def __init__(self, resource_id, resource_name=None):
self.resource_id = resource_id
self.resource_name = resource_name
self.available_timeslots = []

def add_timeslot(self, start_time, end_time):
self.available_timeslots.append(TimeSlot(self.resource_id, start_time, end_time))
# self.available_timeslots.append(TimeSlot(self.resource_id, start_time, end_time))
self.available_timeslots.append(TimeSlot(start_time, end_time))

def get_unoccupied_time_slots(self):
unoccupied_slots = []
Expand Down
5 changes: 3 additions & 2 deletions lekin/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@


class Scheduler(object):
def __init__(self, objective, solver):
def __init__(self, objective, solver, max_operations):
self.objective = objective
self.solver = solver
self.max_operations = max_operations

def solve(self, jobs, machines, max_operations):
def solve(self, jobs, machines):
self.solver.solve(jobs, machines)

def evaluate(self):
Expand Down
13 changes: 13 additions & 0 deletions lekin/solver/construction_heuristics/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,16 @@ def get_unoccupied_time_slots(self):
if not slot.is_occupied():
unoccupied_slots.append(slot)
return unoccupied_slots

# 便于保存结果
def assign_operation(self, operation, resource):
# Check if the resource has any available time slots for the operation
resource_time_slots = resource.get_time_slots()
for time_slot in resource_time_slots:
if time_slot.start_time <= operation.get_max_begin():
# Assign the operation to the resource within the available time slot
operation.assign_resource(resource, time_slot)
operation.assigned_resource = resource # Update the assigned resource
operation.assigned_time_slot = time_slot # Update the assigned time slot
return True
return False
Empty file.
57 changes: 57 additions & 0 deletions lekin/solver/meta_heuristics/branch_and_bound.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from datetime import datetime, timedelta
from itertools import permutations

from lekin.lekin_struct import TimeSlot


class BranchAndBoundScheduler:
def __init__(self, job_list, resource_list):
self.job_list = job_list
self.resource_list = resource_list
self.best_schedule = None
self.best_cost = float("inf")

def find_available_time_slots(self, operation, resource, current_time):
# Implement the logic to find available time slots for a given operation and resource
available_time_slots = []
for slot in resource.available_time_slots:
if slot.start_time >= current_time + operation.processing_time:
available_time_slots.append(slot)
return available_time_slots

def assign_operation(self, job, operation, resource, start_time):
# Implement the logic to assign an operation to a resource at a specific start time
end_time = start_time + operation.processing_time
resource.available_time_slots.append(TimeSlot(end_time, float("inf")))
return end_time

def calculate_cost(self, schedule):
# Implement the logic to calculate the cost of the current schedule
# For example, you can calculate makespan or any other relevant metric
makespan = max(slot.end_time for slot in schedule)
return makespan

def branch_and_bound(self, current_job_idx, current_time, schedule):
# Implement the Branch and Bound algorithm for scheduling
if current_job_idx == len(self.job_list):
cost = self.calculate_cost(schedule)
if cost < self.best_cost:
self.best_cost = cost
self.best_schedule = schedule.copy()
return

job = self.job_list[current_job_idx]
route = self.resource_list[job.route_id]

for resource_id in route.operations[0].resource_ids:
resource = self.resource_list[resource_id]
time_slots = self.find_available_time_slots(route.operations[0], resource, current_time)
for slot in time_slots:
new_schedule = schedule + [slot]
new_time = self.assign_operation(job, route.operations[0], resource, slot.start_time)
self.branch_and_bound(current_job_idx + 1, new_time, new_schedule)

def get_schedule(self):
# Implement the main function to get the final schedule using Branch and Bound
self.branch_and_bound(0, datetime(2023, 1, 1), [])
return self.best_schedule
File renamed without changes.
27 changes: 27 additions & 0 deletions tests/test_solver/test_meta_heuristics/test_branch_and_bound.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from datetime import datetime, timedelta
import unittest

from lekin.lekin_struct import Job, Operation, Resource, Route, TimeSlot
from lekin.solver.meta_heuristics.branch_and_bound import BranchAndBoundScheduler


class BranchAndBoundSchedulerTest(unittest.TestCase):
def test_schedule(self):
job1 = Job(1, datetime(2023, 1, 10), 2, 1)
job2 = Job(2, datetime(2023, 1, 20), 1, 1)

op1 = Operation(1, timedelta(hours=2), 2, None, [1])
op2 = Operation(2, timedelta(hours=3), None, 1, [1])

route1 = Route(1, [op1, op2])
print(route1)

resource1 = Resource(1, [TimeSlot(datetime(2023, 1, 1), datetime(2023, 1, 3))])

job_list = [job1, job2]
resource_list = [resource1]

scheduler = BranchAndBoundScheduler(job_list, resource_list)
schedule = scheduler.get_schedule()
for idx, slot in enumerate(schedule):
print(f"Job {idx + 1} will start at {slot.start_time} and end at {slot.end_time}")

0 comments on commit 0036690

Please sign in to comment.