├── .gitignore ├── LICENSE ├── README.md ├── demo_heuristic_model.py ├── demo_math_model.py ├── gantt_plot.py ├── heuristic_model ├── Heuristic_modeling.ipynb ├── Makefile ├── c_utils.cpp ├── jsp_bbs.cpp ├── jsp_bbs.pyx └── setup.py └── mathematic_model ├── BigM modeling.ipynb ├── Integer time modeling.ipynb ├── PMSP_bigM.md ├── Predictive_Scheduling_FRAILBots_SAA.md ├── gantt_plot.py ├── pmsp_TimeIntModel.py ├── pmsp_gurobi.py ├── pmsp_gurobi2.py ├── pmsp_gurobi_TIM.py └── pmsp_milp.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Chen Peng 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Parallel machine scheduling problems 2 | 3 | This repository is to solve the parallel machine scheduling problems with job release constraints in the objective of sum of completion times. Two methods are proposed. One method is to use heuristic idea to model the problem and solve the modeled problem with branch and bound algorithm. The algorithm is implemented in C to get fast enough speed for online case. Another method is to use Pyomo to model the problem into a mixed integer programming and solve it with the solver (CPLEX, GUROBI or GLPK). 4 | 5 | - Heuristic model: 6 | - Release the constraints to convert the problem to a P problem and solve the converted problem to get bounds; 7 | - Search the optimal solution with Branch and Bound algorithm; 8 | - A fast and sub-optimal approximate solution is also implemented. 9 | - Mixed integer programming: 10 | - Use Pyomo/Guropi to mathematically model the problem; 11 | - Solve the problem with solver; 12 | - Interpret the solution to machine-jobs schedule; 13 | 14 | The result is visualized and expressed with Gantt chart. 15 | -------------------------------------------------------------------------------- /demo_heuristic_model.py: -------------------------------------------------------------------------------- 1 | from heapq import * 2 | import numpy as np 3 | import math 4 | import sys 5 | import os 6 | sys.path.append(os.path.abspath('./bin')) 7 | import matplotlib.pyplot as plt 8 | from jsp_bbs import * 9 | from gantt_plot import * 10 | 11 | def BAB_search(reqs_id, request_times, process_intervals, robot_properties): 12 | num_req = len(reqs_id) 13 | search_depth = 10 14 | 15 | if num_req <= search_depth: 16 | wait_times = np.zeros(num_req, dtype=np.int32) 17 | order = np.zeros(num_req, dtype=np.int32) 18 | robot_properties = np.array(robot_properties, dtype=np.int32) 19 | job_scheduling(reqs_id, request_times, process_intervals, robot_properties, wait_times, order, search_depth) 20 | # check if the order effective 21 | if len(set(order))!=len(order): 22 | print(order) 23 | else: 24 | # get first search_depth requests by earliest due date 25 | num_req = search_depth 26 | JSP_wait_times = np.zeros(num_req, dtype=np.int32) 27 | JSP_order = np.zeros(num_req, dtype=np.int32) 28 | robot_properties = np.array(robot_properties, dtype=np.int32) 29 | 30 | # get order of index by srpt heuristics 31 | srpt_sort = SRPT_heuristics(reqs_id, request_times, process_intervals, robot_properties) 32 | reqs_id = list(reqs_id) 33 | # get index sort of reqs_id 34 | arg_srpt_sort = np.array([reqs_id.index(x) for x in srpt_sort]) 35 | reqs_id = np.array(reqs_id, dtype=np.int32) 36 | job_scheduling(reqs_id[arg_srpt_sort[:search_depth]], request_times[arg_srpt_sort[:search_depth]], 37 | process_intervals[arg_srpt_sort[:search_depth]],robot_properties, 38 | JSP_wait_times, JSP_order, search_depth-2) 39 | order = np.concatenate((JSP_order, reqs_id[arg_srpt_sort[search_depth:]])) 40 | # check if the order effective 41 | if len(set(order))!=len(order): 42 | print(order) 43 | 44 | return order 45 | 46 | def SRPT_heuristics(reqs_id, request_times, process_intervals, robot_properties): 47 | num_req = len(reqs_id) 48 | wait_times = np.zeros(num_req, dtype=np.int32) 49 | order = np.zeros(num_req, dtype=np.int32) 50 | robot_properties = np.array(robot_properties, dtype=np.int32) 51 | SRPT_Preemptive_Bound(reqs_id, request_times, process_intervals, robot_properties, wait_times, order) 52 | 53 | if len(set(order))!=len(order): 54 | print(order) 55 | return order 56 | 57 | 58 | if __name__=='__main__': 59 | 60 | # job scheduling 61 | # data_record = {} 62 | job_num = 10 63 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 64 | # np.random.seed(0) 65 | 66 | release_times = np.random.randint(0, 20, size=(job_num), dtype=np.int32) 67 | process_intervals = np.random.randint(10, 120, size=(job_num), dtype=np.int32) 68 | machine_available_times = np.random.randint(0, 5, size=(3,), dtype=np.int32) 69 | 70 | 71 | # SRPT-CONVERT 72 | # print("SRPT-CONVERT") 73 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 74 | SRPT_order = np.zeros(job_num, dtype=np.int32) 75 | SRPT_machines_order = np.zeros(job_num, dtype=np.int32) 76 | # output for wait time and serve order with convert SRPT 77 | SRPT_Preemptive_Bound(job_ids,release_times,process_intervals, 78 | machine_available_times, SRPT_wait, SRPT_order) 79 | 80 | sequence_schedules(job_ids, release_times, process_intervals, machine_available_times, 81 | SRPT_order, SRPT_wait, SRPT_machines_order) 82 | # formulate job properties 83 | # 'id':{'release', release_time, 'duration', process_time} 84 | JOBS = formulate_jobs_dict(job_ids, release_times, process_intervals, SRPT_wait) 85 | # formulate schedules 86 | # 'id':{'start':**, 'finish':**} 87 | SCHEDULE_SRPT = formulate_schedule_dict(job_ids, release_times, process_intervals, SRPT_wait, SRPT_machines_order) 88 | gantt_chart_plot(JOBS, SCHEDULE_SRPT, machine_available_times, "SRPT_CONVERT") 89 | 90 | # Branch and Bound Search 91 | # print("Branch and Bound Search") 92 | optimal_wait_time = np.zeros(job_num, dtype=np.int32) 93 | optimal_machine_order = np.zeros(job_num, dtype=np.int32) 94 | optimal_order = np.zeros(job_num, dtype=np.int32) 95 | job_scheduling(job_ids, release_times, process_intervals, machine_available_times, optimal_wait_time, optimal_order, job_num-2) 96 | sequence_eval(job_ids,release_times,process_intervals, 97 | machine_available_times, optimal_order, optimal_wait_time) 98 | sequence_schedules(job_ids, release_times, process_intervals, machine_available_times, 99 | optimal_order, optimal_wait_time, optimal_machine_order) 100 | 101 | JOBS = formulate_jobs_dict(job_ids, release_times, process_intervals, optimal_wait_time) 102 | # formulate schedules 103 | SCHEDULE_BAB = formulate_schedule_dict(job_ids, release_times, process_intervals, optimal_wait_time, optimal_machine_order) 104 | 105 | gantt_chart_plot(JOBS, SCHEDULE_BAB, machine_available_times, "BAB_search") 106 | 107 | plt.show() 108 | 109 | -------------------------------------------------------------------------------- /demo_math_model.py: -------------------------------------------------------------------------------- 1 | from pyomo.environ import * 2 | from pyomo.gdp import * 3 | import matplotlib.pyplot as plt 4 | import random 5 | import numpy as np 6 | import os 7 | import sys 8 | sys.path.append(os.path.abspath("./bin/")) 9 | # heuristic method provides some search boundary 10 | sys.path.append(os.path.abspath("./heuristic_model")) 11 | sys.path.append(os.path.abspath("./mathematic_model")) 12 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, sequence_schedules 13 | from pmsp_milp import PMSP_MILP 14 | from pmsp_TimeIntModel import PMSP_MIP_TIM 15 | from gantt_plot import * 16 | 17 | 18 | if __name__ == '__main__': 19 | job_num = 20 20 | machine_num = 3 21 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 22 | np.random.seed(15) # 13 is not feasible solution 23 | release_times = np.random.randint(0, 20, size=(job_num), dtype=np.int32) 24 | process_intervals = np.random.randint(10, 20, size=(job_num), dtype=np.int32) 25 | # machine_available_times = np.random.randint(0, 3, size=(machine_num), dtype=np.int32) 26 | machine_available_times = np.zeros(3, dtype=np.int32) 27 | # solving from modeling 28 | # pmsp_solver = PMSP_MILP() 29 | pmsp_solver = PMSP_MIP_TIM() 30 | 31 | # solver of PMSP 32 | sum_wts, optimal_order = pmsp_solver.solve(job_ids, release_times, process_intervals, machine_available_times) 33 | opt_wait = np.zeros(job_num, dtype=np.int32) 34 | opt_machine_order = np.zeros(job_num, dtype=np.int32) 35 | sequence_eval(job_ids,release_times,process_intervals, 36 | machine_available_times, optimal_order, opt_wait) 37 | sequence_schedules(job_ids, release_times, process_intervals, machine_available_times, 38 | optimal_order, opt_wait, opt_machine_order) 39 | 40 | JOBS = formulate_jobs_dict(job_ids, release_times, process_intervals, opt_wait) 41 | # formulate schedules 42 | SCHEDULE_BAB = formulate_schedule_dict(job_ids, release_times, process_intervals, opt_wait, opt_machine_order) 43 | 44 | gantt_chart_plot(JOBS, SCHEDULE_BAB, machine_available_times, "BAB_search") 45 | 46 | plt.show() -------------------------------------------------------------------------------- /gantt_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | def gantt_chart_plot(JOBS, SCHEDULE, Machine_available, Title): 4 | 5 | bw = 0.3 6 | plt.figure(figsize=(12, 0.7*(len(JOBS.keys())))) 7 | idx = 0 8 | for j in sorted(JOBS.keys()): 9 | x = 0 10 | y = JOBS[j]['release'] 11 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='cyan', alpha=0.6, label="release constraint") 12 | x = SCHEDULE[j]['start'] 13 | y = SCHEDULE[j]['finish'] 14 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='red', alpha=0.5, label="process interval") 15 | plt.plot([x,y,y,x,x], [idx-bw,idx-bw,idx+bw,idx+bw,idx-bw],color='k') 16 | plt.text((SCHEDULE[j]['start'] + SCHEDULE[j]['finish'])/2.0,idx, 17 | 'Job ' + str(j), color='white', weight='bold', 18 | horizontalalignment='center', verticalalignment='center') 19 | idx += 1 20 | 21 | plt.ylim(-0.5, idx-0.5) 22 | plt.title('Job Schedule '+ Title) 23 | plt.xlabel('Time') 24 | plt.ylabel('Jobs') 25 | plt.yticks(range(len(JOBS)), JOBS.keys()) 26 | plt.grid() 27 | xlim = plt.xlim() 28 | 29 | # order machine for plotting nicely 30 | MACHINES = sorted(set([SCHEDULE[j]['machine'] for j in JOBS.keys()])) 31 | 32 | plt.figure(figsize=(12, 0.7*len(MACHINES))) 33 | for j in sorted(JOBS.keys()): 34 | idx = MACHINES.index(SCHEDULE[j]['machine']) 35 | x = 0 36 | y = Machine_available[idx] 37 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='green', alpha=0.5) 38 | x = SCHEDULE[j]['start'] 39 | y = SCHEDULE[j]['finish'] 40 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='red', alpha=0.5) 41 | plt.plot([x,y,y,x,x], [idx-bw,idx-bw,idx+bw,idx+bw,idx-bw],color='k') 42 | plt.text((SCHEDULE[j]['start'] + SCHEDULE[j]['finish'])/2.0,idx, 43 | 'Job ' + str(j), color='white', weight='bold', 44 | horizontalalignment='center', verticalalignment='center') 45 | plt.xlim(xlim) 46 | plt.ylim(-0.5, len(MACHINES)-0.5) 47 | plt.title('Machine Schedule '+ Title) 48 | plt.yticks(range(len(MACHINES)), MACHINES) 49 | plt.ylabel('Machines') 50 | plt.grid() 51 | 52 | def formulate_jobs_dict(job_ids, release_times, process_intervals, wait_times): 53 | job_dict = {} 54 | for idx, j_id in enumerate(job_ids): 55 | job_dict[j_id] = {} 56 | job_dict[j_id]['release'] = release_times[idx] 57 | job_dict[j_id]['duration'] = process_intervals[idx] 58 | job_dict[j_id]['waiting'] = wait_times[idx] 59 | return job_dict 60 | 61 | def formulate_schedule_dict(job_ids, release_times, process_intervals, wait_times, machine_dispatches): 62 | schedule_dict = {} 63 | for idx, j_id in enumerate(job_ids): 64 | schedule_dict[j_id] = {} 65 | schedule_dict[j_id]['start'] = release_times[idx]+wait_times[idx] 66 | schedule_dict[j_id]['finish'] = release_times[idx]+process_intervals[idx]+wait_times[idx] 67 | schedule_dict[j_id]['machine'] = machine_dispatches[idx] 68 | 69 | return schedule_dict -------------------------------------------------------------------------------- /heuristic_model/Heuristic_modeling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Parallel Machine Scheduling Heuristic modeling\n", 8 | "### Introduction\n", 9 | "\n", 10 | "Parallel machine scheduling problems with release constraints, $Pm|r_j|\\sum C_j$, has been proved NP-hard in strong sense. This repository is to use Branch and Bound algorithm to solve it, as well as an approximate algorithm, SRPT-CONVERT. The code is implemented in C and Python-API is wrapped with Cython package.\n", 11 | "\n", 12 | "### Problem definition\n", 13 | "\n", 14 | "$M$ machines needs to process $N$ jobs. Each job is associated with a release constraint and a process time interval. Each machine is not available to start processing jobs until the available time is up to (processing some jobs not in $N$ jobs). The objective is to have the machines finish processing all jobs. The result is to have the Gantt chart to express the schedule of the whole machines. \n", 15 | "\n", 16 | "- Input: \n", 17 | " - request_ids: ids of jobs to be processed;\n", 18 | " - release_times: time interval of jobs released to be processed;\n", 19 | " - process_intervals: time interval to process a job, non preemptive is assumed;\n", 20 | " - machine_available_times: time interval of machines available to start processing jobs;\n", 21 | "- Output:\n", 22 | " - Gantt Chart of jobs and machines;\n", 23 | "\n", 24 | "### Approximate algorithm\n", 25 | "\n", 26 | "- SRPT-CONVERT:\n", 27 | " - Delete the non-preemptive constraint to change the problem to a P-solvable problem, $Pm|r_j, pmtm|\\sum C_i$, by releasing the non-preemptive constraints.\n", 28 | " - CONVERT algorithm is used to transform the preemptive schedules to non-preemptive one.\n", 29 | "\n", 30 | "### Exact algorithm\n", 31 | "\n", 32 | "- Branch and bound search\n", 33 | " - Bounds I:\n", 34 | " - $Pm||\\sum C_i$, delete release constraints;\n", 35 | " - Solution: Shortest-Process-Time (SPT)\n", 36 | " - Bounds II:\n", 37 | " - $Pm|r_j, pmtm|\\sum C_i$, delete non-preemptive constraints;\n", 38 | " - Solution: Shortest-Remaining-Process-Time (SRPT);\n", 39 | "\n", 40 | "### Running the code\n", 41 | "\n", 42 | "- Compile the code: make all;\n", 43 | "- Try the demo: python demo.py;" 44 | ] 45 | } 46 | ], 47 | "metadata": { 48 | "kernelspec": { 49 | "display_name": "Python [conda env:py36]", 50 | "language": "python", 51 | "name": "conda-env-py36-py" 52 | }, 53 | "language_info": { 54 | "codemirror_mode": { 55 | "name": "ipython", 56 | "version": 3 57 | }, 58 | "file_extension": ".py", 59 | "mimetype": "text/x-python", 60 | "name": "python", 61 | "nbconvert_exporter": "python", 62 | "pygments_lexer": "ipython3", 63 | "version": "3.6.4" 64 | } 65 | }, 66 | "nbformat": 4, 67 | "nbformat_minor": 2 68 | } 69 | -------------------------------------------------------------------------------- /heuristic_model/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | # make dynamic library for cython package 3 | python setup.py build_ext --inplace 4 | 5 | clean: 6 | rm ../bin/*.so jsp_bbs.cpp -------------------------------------------------------------------------------- /heuristic_model/c_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern "C" 8 | { 9 | 10 | struct Job 11 | { 12 | int job_property; 13 | int job_id; 14 | }; 15 | struct Machine 16 | { 17 | int machine_property; 18 | int job_id; 19 | int machine_id; 20 | }; 21 | // The jobs number is set to 20 22 | struct Node 23 | { 24 | int level; 25 | int sequence[30]; 26 | int bound_seq[30]; 27 | int bounder; 28 | Node(int jobs_num, int _level=0) 29 | { 30 | level = _level; 31 | for(int i=0;i j2.job_property; 48 | } 49 | }; 50 | // operator for machine heap 51 | struct CompareMachine 52 | { 53 | bool operator()(Machine const& m1, Machine const& m2) const 54 | { 55 | return m1.machine_property > m2.machine_property; 56 | } 57 | }; 58 | // operator for Node heap 59 | struct CompareNode 60 | { 61 | bool operator()(Node const& n1, Node const& n2) const 62 | { 63 | return n1.bounder > n2.bounder; 64 | } 65 | }; 66 | /* vector.sum() */ 67 | int vectorSum(int *vector, int length) 68 | { 69 | int vector_sum = 0; 70 | for(int i=0;i request_queue; 163 | // initialize priority queue for machines in the size of num_machines 164 | std::priority_queue, CompareJob> machine_process_heap; 165 | int process_heap_size; 166 | todo_jobs_num = seq_size; 167 | t = 0; 168 | int t_seq = 0, gap_to_process, unfinish_num = 0; 169 | // unfinished jobs after each gap processing 170 | Job unfinished_jobs[num_machines]; 171 | Job machine_process; 172 | // push sequence job ids into a queue 173 | for(int i=0;i 0) 179 | { 180 | todo_jobs_num += 1; 181 | machine_process_heap.push(Job{machine_states[i], i+30}); 182 | } 183 | } 184 | 185 | while(todo_jobs_num > 0) 186 | { 187 | if(!request_queue.empty()) 188 | { 189 | while(machine_process_heap.size()<(unsigned int)num_machines) 190 | { 191 | j_id = request_queue.front(); 192 | request_queue.pop(); // pop out the first job_id in sequence 193 | j_id_idx = get_1d_vector_index(sequence, j_id, seq_size); 194 | job_process_time = process_intervals[j_id_idx]; 195 | job_request_time = request_times[j_id_idx]; 196 | // available interval of that machine 197 | machine_process_interval = std::max(job_request_time-t, 0) + job_process_time; 198 | machine_process_heap.push(Job{machine_process_interval, j_id}); 199 | // get transformed machine state when all requests have been pushed into process heap 200 | if (request_queue.empty()) 201 | { 202 | t_seq = t; // record sequence responded time 203 | process_heap_size = machine_process_heap.size(); 204 | // save doing jobs into machine states 205 | for(int i=0;i < process_heap_size;i++) 206 | { 207 | machine_process = machine_process_heap.top(); 208 | machine_process_heap.pop(); 209 | machine_states[i] = machine_process.job_property; 210 | } 211 | transform_finish = true; 212 | break; 213 | } 214 | } 215 | } 216 | // break out when state transform has been finished 217 | if (transform_finish) 218 | break; 219 | else 220 | { 221 | machine_process = machine_process_heap.top(); 222 | machine_process_heap.pop(); // pop out first process jobs 223 | gap_to_process = machine_process.job_property; 224 | j_id = machine_process.job_id; 225 | 226 | t += gap_to_process; // update time to the instant when it finishes 227 | 228 | // if (j_id in sequence) 229 | if(isvalueinarray(j_id, sequence, seq_size)) 230 | { 231 | j_id_idx = get_1d_vector_index(sequence, j_id, seq_size); 232 | transform_wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 233 | } 234 | // # the job has been done 235 | todo_jobs_num--; 236 | // # batch size: depend on number of machine and jobs to do 237 | N = machine_process_heap.size(); 238 | unfinish_num = 0; 239 | for(int i=0; i < N; i++) 240 | { 241 | machine_process = machine_process_heap.top(); 242 | machine_process_heap.pop(); 243 | job_process_time = machine_process.job_property; 244 | j_id = machine_process.job_id; 245 | job_process_time -= gap_to_process; 246 | // in case the process time is the same 247 | if (job_process_time == 0) 248 | { 249 | todo_jobs_num--; 250 | if (isvalueinarray(j_id, sequence, seq_size)) 251 | { 252 | j_id_idx = get_1d_vector_index(sequence, j_id, seq_size); 253 | transform_wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 254 | } 255 | } 256 | else 257 | unfinished_jobs[unfinish_num++] = Job{job_process_time, j_id}; 258 | } 259 | // put uncompleted jobs back to process queue 260 | if (unfinish_num > 0) 261 | { 262 | for(int i=0; i request_queue; 300 | // initialize process queue for machines 301 | std::priority_queue, CompareJob> machine_process_heap; 302 | // int process_heap_size; 303 | todo_jobs_num = num_jobs; 304 | t = 0; 305 | int gap_to_process, unfinish_num = 0; 306 | Job unfinished_jobs[num_machines]; 307 | Job machine_process; 308 | // push sequence job_id into a queue 309 | for(int i=0;i 0) 316 | { 317 | todo_jobs_num += 1; 318 | machine_process_heap.push(Job{machine_properties[i], i+30}); 319 | } 320 | } 321 | 322 | while(todo_jobs_num > 0) 323 | { 324 | if(!request_queue.empty()) 325 | { 326 | while(machine_process_heap.size()<(unsigned int)num_machines) 327 | { 328 | j_id = request_queue.front(); 329 | request_queue.pop(); // pop out request job_id 330 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 331 | job_process_time = process_intervals[j_id_idx]; 332 | job_request_time = request_times[j_id_idx]; 333 | // available interval of that machine after being assigned 334 | machine_process_interval = std::max(job_request_time-t, 0) + job_process_time; 335 | machine_process_heap.push(Job{machine_process_interval, j_id}); 336 | // get transformed machine state 337 | if (request_queue.empty()) 338 | break; 339 | } 340 | } 341 | 342 | machine_process = machine_process_heap.top(); 343 | machine_process_heap.pop(); 344 | gap_to_process = machine_process.job_property; 345 | j_id = machine_process.job_id; 346 | t += gap_to_process; // update time to the instant when it finishes 347 | 348 | if(isvalueinarray(j_id, job_ids, num_jobs)) 349 | { 350 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 351 | wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 352 | } 353 | // the job has been done 354 | todo_jobs_num--; 355 | // batch size: depend on number of machine and jobs to do 356 | N = machine_process_heap.size(); 357 | unfinish_num = 0; 358 | for(int i=0; i < N; i++) 359 | { 360 | machine_process = machine_process_heap.top(); 361 | machine_process_heap.pop(); 362 | job_process_time = machine_process.job_property; 363 | j_id = machine_process.job_id; 364 | job_process_time -= gap_to_process; 365 | // in case the process time is the same 366 | if (job_process_time == 0) 367 | { 368 | todo_jobs_num--; 369 | if (isvalueinarray(j_id, job_ids, num_jobs)) 370 | { 371 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 372 | wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 373 | } 374 | } 375 | else 376 | unfinished_jobs[unfinish_num++] = Job{job_process_time, j_id}; 377 | } 378 | 379 | // put uncompleted jobs back to process queue 380 | if (unfinish_num > 0) 381 | { 382 | for(int i=0; i request_queue; 419 | // initialize process queue for machines 420 | std::priority_queue, CompareJob> machine_process_heap; 421 | int machine_jobs[num_machines]; 422 | // int process_heap_size; 423 | todo_jobs_num = num_jobs; 424 | t = 0; 425 | int gap_to_process, unfinish_num = 0; 426 | Job unfinished_jobs[num_machines]; 427 | Job machine_process; 428 | // push sequence job_id into a queue 429 | for(int i=0;i 0) 436 | { 437 | todo_jobs_num += 1; 438 | machine_process_heap.push(Job{machine_properties[i], i+num_jobs}); 439 | machine_jobs[i] = i+num_jobs; 440 | } 441 | else 442 | { 443 | machine_jobs[i] = -1; 444 | } 445 | } 446 | 447 | while(todo_jobs_num > 0) 448 | { 449 | if(!request_queue.empty()) 450 | { 451 | while(machine_process_heap.size()<(unsigned int)num_machines) 452 | { 453 | j_id = request_queue.front(); 454 | request_queue.pop(); // pop out request job_id 455 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 456 | job_process_time = process_intervals[j_id_idx]; 457 | job_request_time = request_times[j_id_idx]; 458 | // available interval of that machine after being assigned 459 | machine_process_interval = std::max(job_request_time-t, 0) + job_process_time; 460 | machine_process_heap.push(Job{machine_process_interval, j_id}); 461 | m_id = get_1d_vector_index(machine_jobs, -1, num_machines); 462 | machine_jobs[m_id] = j_id; 463 | // get transformed machine state 464 | if (request_queue.empty()) 465 | break; 466 | } 467 | } 468 | 469 | machine_process = machine_process_heap.top(); 470 | machine_process_heap.pop(); 471 | gap_to_process = machine_process.job_property; 472 | j_id = machine_process.job_id; 473 | t += gap_to_process; // update time to the instant when it finishes 474 | m_id = get_1d_vector_index(machine_jobs, j_id, num_machines); 475 | // std::cout << "machine id available " << m_id << std::endl; 476 | if(isvalueinarray(j_id, job_ids, num_jobs)) 477 | { 478 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 479 | wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 480 | machine_orders[j_id_idx] = m_id; 481 | // std::cout << "machine id " << m_id << " is done for job " 482 | // << j_id << " at " << t << std::endl; 483 | } 484 | machine_jobs[m_id] = -1; 485 | // the job has been done 486 | todo_jobs_num--; 487 | // batch size: depend on number of machine and jobs to do 488 | N = machine_process_heap.size(); 489 | unfinish_num = 0; 490 | for(int i=0; i < N; i++) 491 | { 492 | machine_process = machine_process_heap.top(); 493 | machine_process_heap.pop(); 494 | job_process_time = machine_process.job_property; 495 | j_id = machine_process.job_id; 496 | job_process_time -= gap_to_process; 497 | // in case the process time is the same 498 | if (job_process_time == 0) 499 | { 500 | todo_jobs_num--; 501 | m_id = get_1d_vector_index(machine_jobs, j_id, num_machines); 502 | if (isvalueinarray(j_id, job_ids, num_jobs)) 503 | { 504 | j_id_idx = get_1d_vector_index(job_ids, j_id, num_jobs); 505 | wait_times[j_id_idx] = t - request_times[j_id_idx] - process_intervals[j_id_idx]; 506 | machine_orders[j_id_idx] = m_id; 507 | std::cout << "machine id " << m_id << " is done for job " << j_id << " at " << t << std::endl; 508 | } 509 | machine_jobs[m_id] = -1; 510 | } 511 | else 512 | unfinished_jobs[unfinish_num++] = Job{job_process_time, j_id}; 513 | } 514 | 515 | // put uncompleted jobs back to process queue 516 | if (unfinish_num > 0) 517 | { 518 | for(int i=0; i, CompareJob> request_heap; 571 | std::priority_queue, CompareJob> process_heap; 572 | 573 | todo_jobs_num = ji_size; 574 | int serve_num = 0; 575 | Job request_job; // instance for pop of heap 576 | Job process_job; 577 | t = time; 578 | int num_avail; 579 | 580 | // initialize serve order with -1 581 | for (int i=0; i 0) 588 | { 589 | // get the earliest request jobs 590 | if (!request_heap.empty()) 591 | { 592 | while (true) 593 | { 594 | // put requested jobs into process queue 595 | request_job = request_heap.top(); 596 | request_heap.pop(); // pop it into process queue 597 | request_time = request_job.job_property; 598 | j_id = request_job.job_id; 599 | if (request_time <= t) 600 | { 601 | // move to process queue 602 | j_id_idx = get_1d_vector_index(job_ids, j_id, ji_size); 603 | process_heap.push(Job{process_intervals[j_id_idx], j_id}); 604 | } 605 | else 606 | { 607 | request_heap.push(request_job); 608 | break; 609 | } 610 | 611 | // all of requests have been put into process, break the loop 612 | if (request_heap.empty()) 613 | break; 614 | } 615 | } 616 | // std::cout << "t is " << t << std::endl; 617 | // # gap_to_process = max(gap_to_process//num_machines, 1) 618 | gap_to_process = 1; 619 | // check the availability of machines 620 | num_avail = check_available_machines(machine_states, t, num_machines); 621 | // std::cout << "num_avail is " << num_avail << std::endl; 622 | t += gap_to_process; 623 | // batch operation for machines 624 | if(num_avail>0) 625 | { 626 | for(int i=0;i, CompareJob> process_heap; 713 | std::priority_queue, CompareJob> machine_process_heap; 714 | 715 | Job process_job; 716 | Job machine_process; 717 | Job unfinished_jobs[num_machines]; 718 | int unfinish_num = 0; 719 | todo_jobs_num = ji_size; 720 | t = time; 721 | // push all todo jobs into process queue 722 | for(int i=0;i 0) 729 | { 730 | process_heap.push(Job{machine_properties[i], 30+i}); 731 | todo_jobs_num += 1; 732 | } 733 | } 734 | while(todo_jobs_num > 0) 735 | { 736 | // std::cout << "todo_jobs_num " << todo_jobs_num << std::endl; 737 | // std::cout << "process_heap empty" << process_heap.empty() << std::endl; 738 | if (!process_heap.empty()) 739 | { 740 | // put smallest process time jobs into machine heap 741 | while(machine_process_heap.size() < (unsigned int)num_machines) 742 | { 743 | process_job = process_heap.top(); 744 | process_heap.pop(); 745 | process_time = process_job.job_property; 746 | j_id = process_job.job_id; 747 | machine_interval = process_time; 748 | machine_process_heap.push(Job{machine_interval, j_id}); 749 | if (process_heap.empty()) 750 | break; 751 | } 752 | } 753 | 754 | machine_process = machine_process_heap.top(); 755 | machine_process_heap.pop(); 756 | gap_to_process = machine_process.job_property; 757 | j_id = machine_process.job_id; 758 | t += gap_to_process; 759 | // std::cout << "process_heap " << process_heap.size() << std::endl; 760 | // std::cout << "machine_process_heap " << machine_process_heap.size() << std::endl; 761 | // std::cout << "job id pop out " << j_id << std::endl; 762 | if (isvalueinarray(j_id, job_ids, ji_size)) 763 | { 764 | // double check 765 | j_id_idx = get_1d_vector_index(job_ids, j_id, ji_size); 766 | SPT_wait[j_id_idx] = (t - process_intervals[j_id_idx] - 767 | request_times[j_id_idx]); 768 | SPT_order[serve_num++] = j_id; 769 | // std::cout << "job id in list " << j_id << std::endl; 770 | } 771 | // # the job has been done 772 | todo_jobs_num -= 1; 773 | // # batch size: depend on number of machine and jobs to do 774 | N = machine_process_heap.size(); 775 | unfinish_num = 0; 776 | for(int i=0; i < N; i++) 777 | { 778 | machine_process = machine_process_heap.top(); 779 | machine_process_heap.pop(); 780 | process_time = machine_process.job_property; 781 | j_id = machine_process.job_id; 782 | 783 | process_time -= gap_to_process; 784 | // in case of same process time 785 | if (process_time == 0) 786 | { 787 | todo_jobs_num -= 1; 788 | if (isvalueinarray(j_id, job_ids, ji_size)) 789 | { 790 | j_id_idx = get_1d_vector_index(job_ids, j_id, ji_size); 791 | SPT_wait[j_id_idx] = (t - request_times[j_id_idx] - process_intervals[j_id_idx]); 792 | SPT_order[serve_num++] = j_id; 793 | // std::cout << "job id " << j_id << std::endl; 794 | } 795 | } 796 | else 797 | // unfinished_jobs.append((process_time, j_id)) 798 | unfinished_jobs[unfinish_num++] = Job{process_time, j_id}; 799 | } 800 | 801 | if (unfinish_num > 0) 802 | { 803 | // std::cout << "unfinished number " << unfinish_num << std::endl; 804 | for(int i=0; i 0) 862 | { 863 | // seq_indices = [job_ids.index(j_id) for j_id in job_ids] 864 | // bseq_indices = list(filter(lambda x: x not in seq_indices, range(num_jobs)) 865 | get_sequence_indices(sequence, job_ids, num_jobs, seq_size, seq_indices, bseq_indices); 866 | // seq_request_times = request_times[seq_indices] 867 | // seq_process_intervals = process_intervals[seq_indices] 868 | get_sequence_values(seq_indices, request_times, seq_size, seq_request_times); 869 | get_sequence_values(seq_indices, process_intervals, seq_size, seq_process_intervals); 870 | 871 | sequence_time = state_transform_utils(sequence, seq_process_intervals, seq_request_times, 872 | machine_states, transform_wait_times, 873 | seq_size, num_machines); 874 | } 875 | else 876 | { 877 | for(int i=0;i< num_jobs;i++) 878 | bseq_indices[i] = i; 879 | } 880 | 881 | // bseq_job_ids = job_ids[bseq_indices] 882 | get_sequence_values(bseq_indices, job_ids, bseq_size, bseq_job_ids); 883 | // bseq_request_times = request_times[bseq_indices] 884 | get_sequence_values(bseq_indices, request_times, bseq_size, bseq_request_times); 885 | // bseq_process_intervals = process_intervals[bseq_indices] 886 | get_sequence_values(bseq_indices, process_intervals, bseq_size, bseq_process_intervals); 887 | 888 | // get two bounds for remaining part of jobs 889 | SRPT_Preemptive_Bound_utils(bseq_job_ids, bseq_request_times, bseq_process_intervals, machine_states, 890 | sequence_time, bseq_size, num_machines, 891 | bseq_SRPT_wait, SRPT_bseq_order); 892 | 893 | // get no release bounder for remaining part of jobs 894 | SPT_No_Release_Bound_utils(bseq_job_ids, bseq_request_times, bseq_process_intervals, machine_states, 895 | sequence_time, bseq_size, num_machines, 896 | bseq_SPT_wait, SPT_bseq_order); 897 | 898 | // std::cout << "SRPT " << vectorSum(bseq_SRPT_wait, bseq_size) << std::endl; 899 | // std::cout << "SPT " << vectorSum(bseq_SPT_wait, bseq_size) << std::endl; 900 | 901 | if (vectorSum(bseq_SRPT_wait, bseq_size) >= vectorSum(bseq_SPT_wait, bseq_size)) 902 | // bseq_wait_times = bseq_SRPT_wait; 903 | std::copy(bseq_SRPT_wait, bseq_SRPT_wait+bseq_size, 904 | bseq_wait_times); 905 | else 906 | // bseq_wait_times = bseq_SPT_wait; 907 | std::copy(bseq_SPT_wait, bseq_SPT_wait+bseq_size, bseq_wait_times); 908 | 909 | // bound_seq = SRPT_bseq_order; 910 | std::copy(SRPT_bseq_order, SRPT_bseq_order+bseq_size, bound_seq); 911 | 912 | if (seq_size > 0) 913 | bounder = vectorSum(bseq_wait_times, bseq_size) + vectorSum(transform_wait_times, seq_size); 914 | else 915 | bounder = vectorSum(bseq_wait_times, bseq_size); 916 | 917 | return bounder; 918 | } 919 | 920 | /* remain_job_ids = list(filter(lambda x: x in node.sequence, job_ids)) */ 921 | int filter_job_ids(Node node, int* job_ids, int* remain_job_ids, int jobs_num) 922 | { 923 | int remain_jobs_num = jobs_num - node.level; 924 | int j = 0; 925 | for(int i=0; ilevel; 943 | for(int i=0;i < u->level;i++) 944 | sequence[i] = u->sequence[i]; 945 | for(int i=0;i<(num_jobs-u->level+1);i++) 946 | { 947 | if (u->bound_seq[i]!=u->sequence[u->level-1]) 948 | sequence[level++] = u->bound_seq[i]; 949 | } 950 | } 951 | 952 | void job_scheduling_utils(int* job_ids, int* request_times, int* process_intervals, 953 | int* machine_properties, int* optimal_wait_times, 954 | int* optimal_order, int num_jobs, 955 | int num_machines, int search_depth) 956 | { 957 | 958 | int depth_check, tree_depth = num_jobs; 959 | int total_wait_time; 960 | int nodes_num = 1, evals_num = 0, prunes_num = 0; 961 | // if not tree_depth or not NUM_machines: 962 | // raise ValueError("Invalid scheduling tasks!") 963 | 964 | // heap for nodes 965 | std::priority_queue, CompareNode> node_pool_heap; 966 | int min_wait_sum; 967 | int remain_jobs_num; 968 | 969 | int wait_times[num_jobs]; 970 | int wait_times_SRPT[num_jobs]; 971 | int sequence[num_jobs]; 972 | int remain_job_ids[num_jobs]; 973 | 974 | Node vTemp = Node(num_jobs); 975 | Node uTemp = Node(num_jobs); 976 | 977 | vTemp.bounder = bounder_cal_utils(job_ids, num_jobs, request_times, process_intervals, 978 | machine_properties, vTemp.sequence, vTemp.level, 979 | num_machines, vTemp.bound_seq); 980 | 981 | sequence_eval_utils( job_ids, process_intervals, request_times, 982 | machine_properties, wait_times_SRPT, vTemp.bound_seq, 983 | num_jobs, num_machines); 984 | 985 | min_wait_sum = vectorSum(wait_times_SRPT, num_jobs); 986 | depth_check = std::min(search_depth, tree_depth); 987 | 988 | // copy(src, src+length, dest) 989 | std::copy(wait_times_SRPT, wait_times_SRPT+num_jobs, optimal_wait_times); 990 | std::copy(vTemp.bound_seq, vTemp.bound_seq+num_jobs, optimal_order); 991 | // std::cout << "sum of wait time for SRPT " << min_wait_sum << std::endl; 992 | 993 | node_pool_heap.push(vTemp); 994 | 995 | while (!node_pool_heap.empty()) 996 | { 997 | vTemp = node_pool_heap.top(); 998 | node_pool_heap.pop(); 999 | if (vTemp.bounder < min_wait_sum) 1000 | { 1001 | // get remain job_ids except sequence 1002 | remain_jobs_num = filter_job_ids(vTemp, job_ids, remain_job_ids, num_jobs); 1003 | for(int i=0; i=t^r_i \\ \\forall i \\in J$ (C1)\n", 36 | "- One job do once: $\\sum^M_k x_{ki} = 1$ (C2)\n", 37 | "- Disjunctive constraint:\n", 38 | " - $t^d_{j}\\geq t^d_{i} + t^P_i - BigM((1-y_{ij})+(1-z_{jk})+(1-z_{ik})), k\\in M, (i,j) \\in \\mathcal{S}_{PAIRS}$ (C3)\n", 39 | " - $t^d_{i}\\geq t^d_{j} + t^P_j - BigM( (y_{ij})+(1-z_{jk})+(1-z_{ik})), k\\in M, (i,j) \\in \\mathcal{S}_{PAIRS}$ (C4)\n", 40 | "- Consecutive constraint: $y_{ij}+1\\geq y_{il}+y_{lj}, (i,j), (i,l)\\ and\\ (j,l) \\in \\mathcal{S}_{PAIRS}$ (C5)\n", 41 | "- Machine available constraint:\n", 42 | " \n", 43 | " - $t^d_j\\geq t^A_k - BigM(1-z_{ik})$\n", 44 | " \n", 45 | " " 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [] 54 | } 55 | ], 56 | "metadata": { 57 | "kernelspec": { 58 | "display_name": "Python [conda env:py36]", 59 | "language": "python", 60 | "name": "conda-env-py36-py" 61 | }, 62 | "language_info": { 63 | "codemirror_mode": { 64 | "name": "ipython", 65 | "version": 3 66 | }, 67 | "file_extension": ".py", 68 | "mimetype": "text/x-python", 69 | "name": "python", 70 | "nbconvert_exporter": "python", 71 | "pygments_lexer": "ipython3", 72 | "version": "3.6.4" 73 | } 74 | }, 75 | "nbformat": 4, 76 | "nbformat_minor": 2 77 | } 78 | -------------------------------------------------------------------------------- /mathematic_model/Integer time modeling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Parallel machine scheduling problem (PMSP)-II\n", 8 | "\n", 9 | "### Summary\n", 10 | "There are M parallel machines and N to-do jobs. Each job is integrated with a release time and process time. The goal is to minimize the total completion time of all jobs. The (integer) discrete time model is used.\n", 11 | "\n", 12 | "### Sets\n", 13 | "- $\\mathcal{S}_M$ = set of machines, initialize with machine ids; \n", 14 | "- $\\mathcal{S}_J$ = set of jobs, initialize with job ids;\n", 15 | "- $\\mathcal{S}_{time}$ = set of discrete time in integer: $TB\\times 1$;\n", 16 | " - $TB$ can be estimated from approximate algorithm;\n", 17 | "\n", 18 | "### Parameters\n", 19 | "- $t^r_i$ : Release time of jobs $N\\times1$;\n", 20 | "- $t^p_i$ : Process time of jobs $N\\times1$; \n", 21 | "- $t^A_k$ : Available time of machines $M\\times1$;\n", 22 | "- $N$ number of jobs;\n", 23 | "- $M$ number of machines;\n", 24 | "### Variables\n", 25 | "- $t^c_{i}$ complete time of job $i\\in \\mathcal{S}_J$;\n", 26 | "- $\\chi_{ikt}$: a binary value if job $i$ is scheduled on machine $k$ and processed at time $t$, $N\\times M\\times TB$;\n", 27 | "\n", 28 | "\n", 29 | "### Objectives\n", 30 | "- minimize total completion time:\n", 31 | " $\\sum_i^N t^c_{i}$\n", 32 | "\n", 33 | "\n", 34 | "### Constraints\n", 35 | "- One job can only be processed by one machine: $\\sum_{k=0}^M\\sum_{max(0, t-t_i^p)}^t \\chi_{ikt}\\leq1, \\forall i\\in N$(C1)\n", 36 | "- One job do once: $\\sum^M_{k=0}\\sum^{TB}_{t=0} \\chi_{ikt} = 1, \\forall i \\in J$ (C2)\n", 37 | "- Release constraint: $\\sum_k^M\\sum_t^{t^r_i-1}\\chi_{ikt}=0 \\ \\forall i \\in J$ (C3)\n", 38 | "- Machine available time constraint: $\\sum_i^N\\sum_t^{t^r_i-1}\\chi_{ikt}=0, \\forall k\\in M$ (C4)\n", 39 | "- Completion time requirements: $\\sum_k^M\\sum_t^{TB}(t+t_i^p+t_k^A)\\chi_{ikt}= t^C_i, \\forall i\\in J$ (C5)." 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [] 48 | } 49 | ], 50 | "metadata": { 51 | "kernelspec": { 52 | "display_name": "Python [conda env:py36]", 53 | "language": "python", 54 | "name": "conda-env-py36-py" 55 | }, 56 | "language_info": { 57 | "codemirror_mode": { 58 | "name": "ipython", 59 | "version": 3 60 | }, 61 | "file_extension": ".py", 62 | "mimetype": "text/x-python", 63 | "name": "python", 64 | "nbconvert_exporter": "python", 65 | "pygments_lexer": "ipython3", 66 | "version": "3.6.4" 67 | } 68 | }, 69 | "nbformat": 4, 70 | "nbformat_minor": 2 71 | } 72 | -------------------------------------------------------------------------------- /mathematic_model/PMSP_bigM.md: -------------------------------------------------------------------------------- 1 | ### Formulation of Predictive Scheduling of FRAIL-Bots under stochastic request prediction (Time-Index-Model) 2 | 3 | #### 1. Description 4 | 5 | Predictive scheduling of FRAIL-Bots under deterministic requests prediction has been modeled into parallel machine scheduling problem with job release time. The job release time and job process time are calculated based on predictive requests, including full tray location and full tray time. In practice, the predicted requests are not deterministic but with some uncertainty. Consequently, the job release time and process time is stochastic following the know probability distribution. Hence, a stochastic program is formulated to achieve the optimal solution considering the uncertain information. 6 | 7 | #### 2. Formulation 8 | 9 | The predictive scheduling of the crop-transporting robots can be formulated as stochastic program. The predictive transporting requests are generated when the fill ratio of the tray is up to certain value. The request includes full tray time of harvesting tray $\Delta t^f_i$and full tray location $L^f_i$, where $\Delta t^f_i \sim \xi^f_i$, $\xi_i^f$ is the estimated probability distribution of full tray time prediction and $L^f_i \sim \xi^{L_f}_i$, $\xi_i^f$ is the estimated probability distribution of full tray location. Given $\Delta \xi^f_i$ and $\xi^{L^f}_i$, we can sample the deterministic $\Delta t^f_i(\omega)$ and $L^f_i(\omega)$ in each scenario $\omega$, from which we can generate the request time $\Delta t^ r_i(\omega)$ and $\Delta t^p_i(\omega)$ in that scenario. The sampled scenarios are formulated as a finite set $\Omega$. The whole problem can be formulated as below: 10 | 11 | $1.\ Parameters$ 12 | 13 | $i,j$: index of jobs (transporting requests); 14 | 15 | $k$: index of machines (FRAIL-Bots); 16 | 17 | $\Delta t^r_i (\omega)$: job release time of the request i in the scenario $\omega$; 18 | 19 | $\Delta t^p_i (\omega)$: job process time of the request i in the scenario $\omega$; 20 | 21 | $\Delta t^a_i (\omega)$: machine available time of the request i in the scenario $\omega$; 22 | 23 | $I$: set of jobs ; 24 | 25 | $J$: $J=I\cup\{0\}$, where $\{0\}$ is a dummy job serving as the predecessor of the first job and the successor of the last job processed on a machine. The release time and process time of the dummy job is equal to 0; 26 | 27 | $M$: set of machines (FRAIL-Bots); 28 | 29 | $\Omega$: set of scenarios, which consists of a finite number of scenarios; 30 | 31 | $bigM$: a very large number; 32 | 33 | $2.\ Decision\ variables$ 34 | 35 | $t^C_i(\omega)$: complete time of job $i\in J$ 36 | 37 | $x_{ij}^k$: a binary value if job $i$ is scheduled on the machine $k$ and processed at time $t$; 38 | 39 | $3.\ Stochastic\ Program\ Formulations$ 40 | 41 | $min\ g_{stoch} = \mathbb{E}_{\Omega}\sum\limits_{i\in J}\Delta t^C_i(\omega)$ 42 | 43 | $s.t.\ $ 44 | 45 | $\sum\limits_{k\in M}\sum\limits_{t\in TB} \chi_{ik}^t(\omega) \leq 1, i\in J, \omega\in \Omega\ \ \ (2)$ 46 | 47 | $\sum\limits_{k\in M}\sum\limits_{i\in J,i\neq j}x_{ij}^k=1, i\in J\ \ \ (3)$ 48 | 49 | $\sum\limits_{l\in J,l\neq i}x^k_{il}-\sum\limits_{l\in J,l\neq i}x^k_{li}=0 \ \ \ (4)$ 50 | 51 | $C_i(\omega)\geq \sum\limits_{k\in M}(\Delta t^a_k(\omega)\times\sum\limits_{j\in J,j\ne i}x_{ij}^k) + \Delta t_i^r + \Delta t^p_i, i\in I, j\in J, j\ne i,$ 52 | 53 | The objective function $g_{stoch}$ is to minimize the expectation of the total completion time. Constraint (1) restricts that one job can only be processed by one machine. Constraint (2) states that one job can only be done once. Constraint (3) ensures that the job can only be processed after the release constraint. Constraint (4) implies that the job cannot be processed until the machine is available. Constraint (5) expresses the completion time of each job $i$. 54 | 55 | #### 3. SAA method 56 | 57 | SAA (Sampling Average Approximation) is an approach for solving stochastic optimization problem by deterministic techniques using Monte Carlo simulation. It is a popular and easy to implement approach to address this stochastic problems, and it performs well under sufficient number of scenarios. After sampling multiple scenarios $\Omega$, the expectation of the total completion time can be approximated by the average, $\hat{g}_{SAA}$ of all sampled scenarios, where $\hat{g}_{SAA}=\frac{1}{N}\sum\limits_{\omega=1}^N\sum\limits_{i\in J}\Delta t^C_i(\omega)$. Given this, the formulated problem can be solved by the commercial solver Gurobi for the small number of scenarios. 58 | 59 | The average optimal value $\hat{g}_{SAA}$ can also be written as $\hat{g}_{SAA}=\sum\limits_{i\in J}\frac{1}{N}\sum\limits_{\omega=1}^N\Delta t^C_i(\omega)$. We use $\hat{g}_j$ to express the value of $\frac{1}{N}\sum\limits_{\omega=1}^N\Delta t^C_i(\omega)$, so $\hat{g}_{SAA}=\sum\limits_{i\in J}\hat{g}_{j}$. When we calculate $\hat{g}_{SAA}$, the value of $\hat{\mathbf{g}}=\{\hat{g}_1, \hat{g}_2, \ldots, \hat{g}_J\}$ is also figured out and given the $\mathbf{g}$, we can obtain the scheduling policy $\hat{x}=f(\hat{\mathbf{g}})$. For the optimal expression of the objective function $g_{stoch} = \sum\limits_{i\in J}\mathbb{E}_{\Omega}\Delta t^C_i(\omega)=\sum\limits_{i\in J}g_j$, when $\hat{g}_{SAA}$ converges to $g_{stoch}$, $\hat{\mathbf{g}}$ converges to $\mathbf{g}$. Therefore the scheduling result $\hat{x}$ will converge to $x_{opt}$. -------------------------------------------------------------------------------- /mathematic_model/Predictive_Scheduling_FRAILBots_SAA.md: -------------------------------------------------------------------------------- 1 | ### Two-stage stochastic program of Predictive Scheduling of FRAIL-Bots under stochastic request prediction (Time-Index-Model) 2 | 3 | #### 1. Description 4 | 5 | Predictive scheduling of FRAIL-Bots under deterministic requests prediction has been modeled into parallel machine scheduling problem with job release time. The job release time and job process time are calculated based on predictive transport requests, including full tray location and full tray time. However, in practice, the predicted requests are not deterministic but with some uncertainty. Consequently, the job release time and process time is stochastic distributed expressed as multiple possible scenarios. A stochastic program is formulated to achieve the optimal solution considering all the estimated scenarios. 6 | 7 | #### 2. Formulation 8 | 9 | The predictive scheduling of the crop-transporting robots can be formulated as stochastic program. The predictive transporting requests are generated when the fill ratio of the tray is up to certain value. The request includes full tray time of harvesting tray $\Delta t^f_i$and full tray location $L^f_i$, where $\Delta t^f_i \sim \Xi^f$ and $\xi_i^f$ is the predictive full tray time in the scenario $\omega_i$ and $L^f_i \sim \Xi^{L_f}$ and $\xi_i^{L_f}$ is the predictive full tray location in scenario $\omega_i$. In each scenario $\omega$, we can generate the request time $\Delta t^ r_i(\omega)$ and $\Delta t^p_i(\omega)$ given $\xi_i^f$ and $\xi_i^{L_f}$. The sampled scenarios are formulated as a finite set $\Omega$. The whole problem can be formulated as below: 10 | 11 | ##### 1. Parameters 12 | 13 | $i,j$: index of jobs (transporting requests); 14 | 15 | $k$: index of machines (FRAIL-Bots); 16 | 17 | $t$: index of the duration; 18 | 19 | $\Delta t^r_i (\omega)$: job release time of the request i in the scenario $\omega$; 20 | 21 | $\Delta t^p_i (\omega)$: job process time of the request i in the scenario $\omega$; 22 | 23 | $\Delta t^a_i (\omega)$: machine available time of the request i in the scenario $\omega$; 24 | 25 | $J$: set of jobs (requests); 26 | 27 | $M$: set of machines (FRAIL-Bots); 28 | 29 | $TB$: upper bound for the makespan of machines, this can be estimated by heuristic policy; 30 | 31 | $\Omega$: set of scenarios, which consists of a finite number of scenarios; 32 | 33 | ##### 2. Decision\ variables 34 | 35 | $t^C_i(\omega)$: complete time of job $i\in J$ 36 | 37 | $\chi_{ik}^t(\omega)$: a binary value if job $i$ is scheduled on the machine $k$ and starts processing at time $t$; 38 | 39 | $z_{ik}$: a binary value if job $i$ is scheduled on the machine $k$. Notice that this one does not relate to the scenario. 40 | 41 | ##### 3. Two-Stage Stochastic Program Formulations 42 | 43 | This problem can be modeled as two-stage stochastic programming. The first stage is to solve the assignment variable $z_{ik}$. When $z_{ik}$ is decided, the problem is changed to a one machine scheduling problem, which can be solved with a heuristic rule Earliest Release Time. 44 | 45 | $min\ g_{stoch} = \mathbb{E}_{\Omega}\sum\limits_{i\in J}\Delta t^C_i(\omega)$ 46 | 47 | $s.t.\ $ 48 | 49 | $\sum\limits_{k\in M}\sum\limits_{t\in TB} \chi_{ik}^t(\omega) = 1, i\in J, \omega\in \Omega\ \ \ (1)$ 50 | 51 | $\sum\limits_{j\in J}\sum\limits_{h=max(0,t-t^p_i)}^{t-1} \chi_{ik}^h(\omega) \leq 1,\ k\in M, \omega\in \Omega\ \ \ (2)$ 52 | 53 | $\sum\limits_{k\in M}\sum\limits_{t=0}^{\Delta t^r_i-1} \chi_{ik}^t(\omega)=0, i\in J, \omega\in \Omega \ \ \ (3)$ 54 | 55 | $\sum\limits_{i\in J}\sum\limits_{t}^{\Delta t^a_i-1} \chi_{ik}^t(\omega) = 0, k\in M, \omega\in \Omega \ \ \ (4)$ 56 | 57 | $\sum\limits_{k\in M}\sum\limits_{t\in TB} \chi_{ik}^t(\omega)(t+\Delta t_i^p(\omega)+\Delta t_i^A(\omega)) = t_i^C(\omega), i\in J, \omega\in \Omega \ \ \ (5)$ 58 | 59 | $\sum\limits_{i\in J}\sum\limits_{k\in M}\chi_{ik}^t(\omega) \leq z_{ik},\ \ t\in TB\ \ \ (6)$ 60 | 61 | The objective function $g_{stoch}$ is to minimize the expectation of the total completion time. Constraint (1) restricts that one job can only be processed by one machine. Constraint (2) states that one job can only be done once. Constraint (3) ensures that the job can only be processed after the release constraint. Constraint (4) implies that the job cannot be processed until the machine is available. Constraint (5) expresses the completion time of each job $i$. Constraint (6) is to guarantee that the the job processed by machine $k$ must be assigned on that machine. 62 | 63 | #### 3. SAA method 64 | 65 | SAA (Sampling Average Approximation) is an approach for solving stochastic optimization problem by deterministic techniques using Monte Carlo simulation. It is a popular and easy to implement approach to address this stochastic problems, and it performs well under sufficient number of scenarios. After sampling multiple scenarios $\Omega$, the expectation of the total completion time can be approximated by the average, $\hat{g}_{SAA}$ of all sampled scenarios, where $\hat{g}_{SAA}=\frac{1}{N}\sum\limits_{\omega=1}^N\sum\limits_{i\in J}\Delta t^C_i(\omega)$. Given this, the formulated problem can be solved by the commercial solver Gurobi for the finite number of scenarios. Then the assignment of $z_{ik}$ can be solved from the stochastic program. Given the results of $z_{ik}$, then under each scenario, jobs are scheduled according to the modified SPT method: the job is firstly sorted in the ascending order based on the processing times, the shortest job among those not yet processed but released will be put on the machine. The order on different scenarios are combined to formulate a consensus order for multiple scenarios. 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /mathematic_model/gantt_plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | def gantt_chart_plot(JOBS, SCHEDULE, Machine_available, Title): 4 | 5 | bw = 0.3 6 | plt.figure(figsize=(12, 0.7*(len(JOBS.keys())))) 7 | idx = 0 8 | for j in sorted(JOBS.keys()): 9 | x = 0 10 | y = JOBS[j]['release'] 11 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='cyan', alpha=0.6, label="release constraint") 12 | x = SCHEDULE[j]['start'] 13 | y = SCHEDULE[j]['finish'] 14 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='red', alpha=0.5, label="process interval") 15 | plt.plot([x,y,y,x,x], [idx-bw,idx-bw,idx+bw,idx+bw,idx-bw],color='k') 16 | plt.text((SCHEDULE[j]['start'] + SCHEDULE[j]['finish'])/2.0,idx, 17 | 'Job ' + str(j), color='grey', weight='bold', 18 | horizontalalignment='center', verticalalignment='center') 19 | idx += 1 20 | 21 | plt.ylim(-0.5, idx-0.5) 22 | plt.title('Job Schedule '+ Title) 23 | plt.xlabel('Time') 24 | plt.ylabel('Jobs') 25 | plt.yticks(range(len(JOBS)), JOBS.keys()) 26 | plt.grid() 27 | xlim = plt.xlim() 28 | 29 | # # order machine for plotting nicely 30 | MACHINES = sorted(set([SCHEDULE[j]['machine'] for j in JOBS.keys()])) 31 | 32 | plt.figure(figsize=(12, 0.7*len(MACHINES))) 33 | for j in sorted(JOBS.keys()): 34 | idx = MACHINES.index(SCHEDULE[j]['machine']) 35 | x = 0 36 | y = Machine_available[idx] 37 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='green', alpha=0.5) 38 | x = SCHEDULE[j]['start'] 39 | y = SCHEDULE[j]['finish'] 40 | plt.fill_between([x,y],[idx-bw,idx-bw],[idx+bw,idx+bw], color='red', alpha=0.5) 41 | plt.plot([x,y,y,x,x], [idx-bw,idx-bw,idx+bw,idx+bw,idx-bw],color='k') 42 | plt.text((SCHEDULE[j]['start'] + SCHEDULE[j]['finish'])/2.0,idx, 43 | 'Job ' + str(j), color='grey', weight='bold', 44 | horizontalalignment='center', verticalalignment='center') 45 | plt.xlim(xlim) 46 | plt.ylim(-0.5, len(MACHINES)-0.5) 47 | plt.title('Machine Schedule '+ Title) 48 | plt.yticks(range(len(MACHINES)), MACHINES) 49 | plt.ylabel('Machines') 50 | plt.grid() 51 | 52 | def formulate_jobs_dict(job_ids, release_times, process_intervals): 53 | job_dict = {} 54 | for idx, j_id in enumerate(job_ids): 55 | job_dict[j_id] = {} 56 | job_dict[j_id]['release'] = release_times[idx] 57 | job_dict[j_id]['duration'] = process_intervals[idx] 58 | return job_dict 59 | 60 | def formulate_schedule_dict(job_ids, release_times, process_intervals, wait_times, machine_dispatches): 61 | schedule_dict = {} 62 | for idx, j_id in enumerate(job_ids): 63 | schedule_dict[j_id] = {} 64 | schedule_dict[j_id]['start'] = release_times[idx]+wait_times[idx] 65 | schedule_dict[j_id]['finish'] = release_times[idx]+process_intervals[idx]+wait_times[idx] 66 | schedule_dict[j_id]['machine'] = machine_dispatches[idx] 67 | 68 | return schedule_dict -------------------------------------------------------------------------------- /mathematic_model/pmsp_TimeIntModel.py: -------------------------------------------------------------------------------- 1 | from pyomo.environ import * 2 | from pyomo.gdp import * 3 | import matplotlib.pyplot as plt 4 | import random 5 | import numpy as np 6 | import os 7 | import sys 8 | sys.path.append(os.path.abspath("../bin/")) 9 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, job_scheduling 10 | 11 | # input to solver: 12 | ## job_ids (N*1), request_times, process_times; 13 | # output of solver: 14 | ## serving order of jobs and predicted wait times 15 | class PMSP_MIP_TIM(object): 16 | def __init__(self, objective_type="min_sum"): 17 | self.obj_type = objective_type 18 | ## solver settings 19 | # self.optimizer = SolverFactory("cbc") 20 | # self.optimizer = SolverFactory("cplex", executable="/home/pengchen/ibm/ILOG/CPLEX_Studio129/cplex/bin/x86-64_linux/cplex") 21 | self.optimizer = SolverFactory("gurobi") 22 | # self.optimizer.options['tol'] = 1e-1 23 | # self.optimizer.options['MIPgap'] = 5 24 | # self.optimizer.options['Heuristics'] = 1 25 | # self.optimizer.options['Threads'] = 4 26 | # self.optimizer.options['Timelimit'] = 2 27 | def solve(self, job_ids, request_times, process_times, available_times): 28 | # creat concrete model 29 | if request_times.min()<0 or process_times.min()<0 or available_times.min()<0: 30 | print("request_times", request_times) 31 | print("process_times", process_times) 32 | print("available_times", available_times) 33 | machine_ids = range(len(available_times)) 34 | model = self._create_model(job_ids, request_times, process_times, machine_ids, available_times) 35 | results = self.optimizer.solve(model) 36 | 37 | # if ((results.solver.status == SolverStatus.ok) and 38 | # (results.solver.termination_condition == TerminationCondition.optimal)): 39 | if ((results.solver.status == SolverStatus.ok) or 40 | ((results.solver.termination_condition == TerminationCondition.maxTimeLimit))): 41 | # if results.solver.status == SolverStatus.ok: 42 | # model.start.pprint() 43 | # model.y.pprint() 44 | # model.z.pprint() 45 | optimal_wt_sum, optimal_order = self._result_process(model, job_ids, request_times, process_times) 46 | # model.complete.pprint() 47 | else: 48 | print("The problem is not solved!") 49 | print("Solver Status: ", results.solver.status) 50 | print("Solution status: ", results.solver.termination_condition) 51 | print(results) 52 | exit(1) 53 | 54 | return optimal_wt_sum, optimal_order 55 | 56 | def _create_model(self, job_ids, request_times, process_times, machine_ids, available_times, continuous=False): 57 | # create model 58 | m = ConcreteModel() 59 | # index set to simplify notation 60 | m.J = Set(initialize=range(len(job_ids))) 61 | m.M = Set(initialize=machine_ids) 62 | # TimeBound is the possible largest makespan of one machine 63 | # TimeBound = complete_time_max + np.max(request_times) 64 | self._start_up_bound(job_ids, request_times, process_times, available_times) 65 | # TimeBound = self.complete_time_max + np.max(request_times) 66 | # TimeBound = np.max(request_times) + np.max(process_times) 67 | TimeBound = np.sum(request_times) + np.sum(process_times) + np.sum(available_times) 68 | m.T = Set(initialize=range(TimeBound)) 69 | 70 | # decision variables: completion time 71 | m.complete = Var(m.J, within=NonNegativeReals) 72 | # decision variable: m.CHI[i,j,t] job j is processed on machine i and processed at time t 73 | m.CHI = Var(m.M, m.J, m.T, domain=Binary) 74 | # minmax 75 | m.max_complete = Var(range(1), domain=NonNegativeReals) 76 | times = np.array(range(TimeBound)) 77 | 78 | # C1: each job starts processing on only one machine at only one point in time 79 | m.C1 = Constraint(m.J, rule=lambda m, j: 80 | sum(m.CHI[i,j,t] for i in m.M for t in m.T)==1) 81 | 82 | # C2: at most one job to be processed at any time on any machine 83 | def C2_rule_(model, i, t): 84 | if t == 0: 85 | return Constraint.Skip 86 | sum_chi = 0 87 | for j in model.J: 88 | h = max(0, t-process_times[j]) 89 | for t_idx in range(h, t): 90 | sum_chi += model.CHI[i, j, t_idx] 91 | return sum_chi <= 1 92 | m.C2 = Constraint(m.M, m.T, rule=C2_rule_) 93 | 94 | # C3: completion time must meet requirements 95 | def C3_rule_(model, j): 96 | return model.complete[j] == sum((t+process_times[j]+available_times[i])*model.CHI[i,j,t] for i in m.M for t in m.T) 97 | m.C3 = Constraint(m.J, rule=C3_rule_) 98 | 99 | # C4: release times constraint 100 | def C4_rule_(model, j): 101 | if request_times[j] == 0: 102 | return Constraint.Skip 103 | else: 104 | sum_chi = 0 105 | for i in model.M: 106 | for t in range(request_times[j]): 107 | sum_chi += model.CHI[i,j,t] 108 | return sum_chi == 0 109 | m.C4 = Constraint(m.J, rule=C4_rule_) 110 | 111 | # C5: machine available constraint 112 | def C5_rule_(model, i): 113 | sum_chi = 0 114 | if available_times[i] == 0: 115 | return Constraint.Skip 116 | else: 117 | for j in model.J: 118 | for t in range(available_times[i]): 119 | sum_chi += model.CHI[i,j,t] 120 | return sum_chi == 0 121 | m.C5 = Constraint(m.M, rule=C5_rule_) 122 | 123 | m.C6 = Constraint(m.J, rule=lambda m, j: 124 | m.complete[j] <= m.max_complete[0]) 125 | # objective: min sum without weights 126 | m.OBJ = Objective(expr = sum(m.complete[j] for j in m.J), sense=minimize) 127 | # m.OBJ = Objective(expr = m.max_complete[0], sense=minimize) 128 | 129 | return m 130 | 131 | # get the bounded start time from heuristic SRPT 132 | def _start_up_bound(self, job_ids, request_times, process_times, available_times): 133 | job_num = len(job_ids) 134 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 135 | SRPT_order = np.zeros(job_num, dtype=np.int32) 136 | # output for wait time and serve order with convert SRPT 137 | SRPT_Preemptive_Bound(job_ids,request_times,process_times, 138 | available_times, SRPT_wait, SRPT_order) 139 | self.low_bound = max((request_times + SRPT_wait).sum(),0) 140 | # print("low bound of SRPT", SRPT_wait.sum()) 141 | # get wait time for nonpreemptive 142 | sequence_eval(job_ids,request_times,process_times, 143 | available_times, SRPT_order, SRPT_wait) 144 | # print("********solve by SRPT heuristic********") 145 | # print("SRPT_wait sum", SRPT_wait.sum()) 146 | # print("SRPT order", SRPT_order) 147 | 148 | start_times = request_times + SRPT_wait 149 | self.start_time_max = start_times.max() 150 | self.complete_time_max = (request_times + process_times + SRPT_wait).max() 151 | self.up_bound = start_times.sum() 152 | 153 | # print(self.start_time_max, self.complete_time_max, self.up_bound) 154 | 155 | def _result_process(self, model, job_ids, request_times, process_times): 156 | complete = [] 157 | for j in model.J: 158 | complete.append(model.complete[j]()) 159 | print("total complete time: ", np.sum(complete)) 160 | print("max complete time: ", model.max_complete[0]()) 161 | start_times = np.array(complete) - process_times 162 | arg_start = np.argsort(start_times) 163 | order = job_ids[arg_start] 164 | # wait time = start time - request time 165 | wait_sum = np.sum(start_times - request_times) 166 | # print(start_times) 167 | 168 | return wait_sum, order 169 | 170 | 171 | if __name__ == '__main__': 172 | job_num = 25 173 | machine_num = 5 174 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 175 | # machine_ids = np.arange(0, machine_num, 1, dtype=np.int32) 176 | np.random.seed(15) # 13 is not feasible solution 177 | request_times = np.random.randint(0, 20, size=(job_num), dtype=np.int32) 178 | process_intervals = np.random.randint(1, 50, size=(job_num), dtype=np.int32) 179 | # machine_properties = np.random.randint(10, 30, size=(machine_num), dtype=np.int32) 180 | machine_properties = np.zeros(machine_num, dtype=np.int32) 181 | 182 | # # solving from modeling 183 | pmsp_solver = PMSP_MIP_TIM() 184 | pmsp_solver._start_up_bound(job_ids, request_times, process_intervals, machine_properties) 185 | # solver of PMSP 186 | optimal_wt, optimal_order = pmsp_solver.solve(job_ids, request_times, process_intervals, machine_properties) 187 | print("******solving by MILP*******") 188 | print("optimal wt sum ", optimal_wt) 189 | print("optimal order ", optimal_order) 190 | opt_wait = np.zeros(job_num, dtype=np.int32) 191 | sequence_eval(job_ids,request_times,process_intervals, 192 | machine_properties, optimal_order, opt_wait) 193 | print("evaluate optimal order", opt_wait.sum()) 194 | -------------------------------------------------------------------------------- /mathematic_model/pmsp_gurobi.py: -------------------------------------------------------------------------------- 1 | from gurobipy import Model, GRB, GurobiError, quicksum, max_ 2 | import os 3 | import sys 4 | import numpy as np 5 | # from gantt_plot import * 6 | sys.path.append(os.path.abspath("../bin/")) 7 | # heuristic method provides some search boundary 8 | sys.path.append(os.path.abspath("../heuristic_model")) 9 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, job_scheduling, sequence_schedules 10 | StatusDict = {getattr(GRB.Status, s): s for s in dir(GRB.Status) if s.isupper()} 11 | 12 | 13 | class PMSP_Gurobi(object): 14 | """docstring for PMSP_Gurobi""" 15 | def __init__(self): 16 | # setting the solver attributes; 17 | self.schedules = {} 18 | self.order = [] 19 | 20 | def solve(self, job_ids, request_times, process_intervals, machine_properties): 21 | solved = False 22 | pmsp_model, assign, order, startTime = self._create_model(job_ids, request_times, process_intervals, machine_properties) 23 | # self._set_model_parms(pmsp_model) 24 | # solve the model 25 | try: 26 | pmsp_model.optimize() 27 | if pmsp_model.status == GRB.Status.OPTIMAL: 28 | solved = True 29 | self._formulate_schedules(job_ids, request_times, process_intervals, 30 | machine_properties, assign, order, startTime) 31 | 32 | else: 33 | statstr = StatusDict[pmsp_model.status] 34 | print('Optimization was stopped with status %s' %statstr) 35 | self._formulate_schedules(job_ids, request_times, process_intervals, 36 | machine_properties, assign, order, startTime) 37 | except GurobiError as e: 38 | print('Error code '+str(e.errno)+': '+str(e)) 39 | 40 | return solved 41 | 42 | def _set_model_parms(self, m): 43 | # permittable gap 44 | m.setParam('MIPGap',0.2) 45 | # time limit 46 | m.setParam('TimeLimit',10) 47 | # percentage of time on heuristics 48 | m.setParam('Heuristics',0.5) 49 | 50 | def _formulate_schedules(self, job_ids, request_times, process_intervals, 51 | machines, assign, order, startTime): 52 | # print("variables: ", startTime.keys) 53 | start_times = np.zeros(len(job_ids)) 54 | assign_list = [] 55 | order_list = [] 56 | j_ids = list(job_ids) 57 | for i, j_id in enumerate(job_ids): 58 | self.schedules[j_id] = {} 59 | self.schedules[j_id]['start'] = startTime[j_id].x 60 | self.schedules[j_id]['finish'] = startTime[j_id].x + process_intervals[j_id] 61 | for m in range(len(machines)): 62 | if assign[j_id, m].x == 1: 63 | self.schedules[j_id]['machine'] = m 64 | assign_list.append((j_id, m)) 65 | start_times[i] = startTime[j_id].x 66 | 67 | for i_id in job_ids: 68 | for j_id in job_ids: 69 | if i_id < j_id: 70 | if order[i_id, j_id].x > 0.5: 71 | order_list.append((i_id, j_id)) 72 | print("start_times: ", start_times) 73 | print("assign_list: ", assign_list) 74 | print("order_list: ", order_list) 75 | self.order = job_ids[np.argsort(start_times)] 76 | 77 | return 78 | def _create_model(self, job_ids, r_times, p_intervals, m_availabe): 79 | ## prepare the index for decision variables 80 | # start time of process 81 | jobs = tuple(job_ids) 82 | machines = tuple(range(len(machine_properties))) 83 | # order of executing jobs: tuple list 84 | jobPairs = [(i,j) for i in jobs for j in jobs if i= release_time[i] for i in jobs),'job release constraint') 116 | # 2. machine available constraint 117 | m.addConstrs((startTime[i] >= machine_time[k]*z[i,k] for (i,k) in job_machinePairs), 'machine available constraint') 118 | # 3. disjunctive constraint 119 | m.addConstrs((startTime[j] >= startTime[i] + process_time[i] - BigM*((1-y[i,j]) + (1-z[j,k]) + (1-z[i,k])) 120 | for k in machines for (i,j) in jobPairs), 'temporal disjunctive order1') 121 | m.addConstrs((startTime[i] >= startTime[j] + process_time[j] - BigM*(y[i,j] + (1-z[j,k])+(1-z[i,k])) 122 | for k in machines for (i,j) in jobPairs), 'temporal disjunctive order2') 123 | # 4. one job is assigned to one and only one machine 124 | m.addConstrs((quicksum([z[i,k] for k in machines])==1 for i in jobs), 'job non-splitting') 125 | 126 | # set initial solution 127 | # for (i,k) in job_machinePairs: 128 | # if (i,k) in assign_list: 129 | # z[(i,k)].start = 1 130 | # else: 131 | # z[(i,k)].start = 0 132 | 133 | # for (i,j) in jobPairs: 134 | # if (i,j) in order_list: 135 | # y[(i,j)].start = 1 136 | # else: 137 | # y[(i,j)].start = 0 138 | 139 | # for i in job_ids: 140 | # startTime[i].start = start_times[i] 141 | 142 | 143 | return m, z, y, startTime 144 | 145 | 146 | if __name__ == '__main__': 147 | job_num = 10 148 | machine_num = 3 149 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 150 | # machine_ids = np.arange(0, machine_num, 1, dtype=np.int32) 151 | np.random.seed(15) # 13 is not feasible solution 152 | request_times = np.random.randint(0, 60, size=(job_num), dtype=np.int32) 153 | process_intervals = np.random.randint(10, 130, size=(job_num), dtype=np.int32) 154 | # machine_properties = np.zeros(machine_num, dtype=np.int32) 155 | machine_properties = np.random.randint(0, 60, size=(machine_num), dtype=np.int32) 156 | 157 | pmsp_solver = PMSP_Gurobi() 158 | 159 | # solver of PMSP 160 | solved = pmsp_solver.solve(job_ids, request_times, process_intervals, machine_properties) 161 | if solved: 162 | print("schedules", pmsp_solver.schedules) 163 | print("order", pmsp_solver.order) 164 | 165 | # job_dict = formulate_jobs_dict(job_ids, request_times, process_intervals) 166 | # gantt_chart_plot(job_dict, pmsp_solver.schedules, machine_properties, "gurobi solver") 167 | # plt.show() 168 | 169 | # get SRPT solution 170 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 171 | SRPT_order = np.zeros(job_num, dtype=np.int32) 172 | machine_orders = np.zeros(job_num, dtype=np.int32) 173 | # output for wait time and serve order with convert SRPT 174 | SRPT_Preemptive_Bound(job_ids,request_times,process_intervals, 175 | machine_properties, SRPT_wait, SRPT_order) 176 | sequence_schedules(job_ids, request_times, process_intervals, machine_properties, 177 | SRPT_order, SRPT_wait, machine_orders) 178 | print("SRPT order", SRPT_order) 179 | print("machine assigns", machine_orders) 180 | print("Start time", request_times+SRPT_wait) -------------------------------------------------------------------------------- /mathematic_model/pmsp_gurobi2.py: -------------------------------------------------------------------------------- 1 | from gurobipy import Model, GRB, GurobiError, quicksum, max_ 2 | import os 3 | import sys 4 | import numpy as np 5 | # from gantt_plot import * 6 | sys.path.append(os.path.abspath("../bin/")) 7 | # heuristic method provides some search boundary 8 | sys.path.append(os.path.abspath("../heuristic_model")) 9 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, job_scheduling, sequence_schedules 10 | StatusDict = {getattr(GRB.Status, s): s for s in dir(GRB.Status) if s.isupper()} 11 | 12 | 13 | class PMSP_Gurobi(object): 14 | """docstring for PMSP_Gurobi""" 15 | def __init__(self): 16 | # setting the solver attributes; 17 | self.schedules = {} 18 | self.order = [] 19 | 20 | def solve(self, job_ids, request_times, process_intervals, machine_properties): 21 | solved = False 22 | pmsp_model, assign, completeTime = self._create_model(job_ids, request_times, process_intervals, machine_properties) 23 | # self._set_model_parms(pmsp_model) 24 | # solve the model 25 | try: 26 | pmsp_model.optimize() 27 | if pmsp_model.status == GRB.Status.OPTIMAL: 28 | solved = True 29 | print(assign.values()) 30 | self._formulate_schedules(job_ids, request_times, process_intervals, 31 | machine_properties, assign, completeTime) 32 | 33 | # else: 34 | # statstr = StatusDict[pmsp_model.status] 35 | # print('Optimization was stopped with status %s' %statstr) 36 | # self._formulate_schedules(job_ids, request_times, process_intervals, 37 | # machine_properties, assign, order, completeTime) 38 | except GurobiError as e: 39 | print('Error code '+str(e.errno)+': '+str(e)) 40 | 41 | return solved 42 | 43 | def _set_model_parms(self, m): 44 | # permittable gap 45 | m.setParam('MIPGap',0.2) 46 | # time limit 47 | m.setParam('TimeLimit',10) 48 | # percentage of time on heuristics 49 | m.setParam('Heuristics',0.5) 50 | 51 | def _formulate_schedules(self, job_ids, request_times, process_intervals, 52 | machines, assign, completeTime): 53 | # print("variables: ", completeTime.keys) 54 | complete_time = np.zeros(len(job_ids)) 55 | assign_list = [] 56 | order_list = [] 57 | j_ids = list(job_ids) 58 | for i, j_id in enumerate(job_ids): 59 | self.schedules[j_id] = {} 60 | self.schedules[j_id]['start'] = completeTime[j_id].x - process_intervals[i] 61 | self.schedules[j_id]['finish'] = completeTime[j_id].x 62 | for m in range(len(machines)): 63 | for i_id in job_ids: 64 | if j_id != i_id: 65 | if assign[j_id, i_id, m].x == 1: 66 | self.schedules[j_id]['machine'] = m 67 | assign_list.append((j_id, m)) 68 | complete_time[i] = completeTime[j_id].x 69 | 70 | # for i_id in job_ids: 71 | # for j_id in job_ids: 72 | # if i_id < j_id: 73 | # if order[i_id, j_id].x > 0.5: 74 | # order_list.append((i_id, j_id)) 75 | print("complete_times: ", complete_time) 76 | print("assign_list: ", assign_list) 77 | # print("order_list: ", order_list) 78 | self.order = job_ids[np.argsort(complete_time)] 79 | 80 | return 81 | 82 | def _create_model(self, job_ids, r_times, p_intervals, m_availabe): 83 | ## prepare the index for decision variables 84 | # start time of process 85 | # add a dummy job with the id of -1 86 | dummy_Job = tuple([-1]) 87 | J = dummy_Job + tuple(job_ids) 88 | I = tuple(job_ids) 89 | r_times = (0,)+tuple(r_times) 90 | p_intervals = (0,)+tuple(p_intervals) 91 | 92 | machines = tuple(range(len(machine_properties))) 93 | # order of executing jobs: tuple list 94 | # jobPairs = [(i,j) for i in jobs for j in jobs if i= release_time[i]+process_time[i]+ 132 | quicksum(x[i,j,k]*machine_time[k] for k in machines for j in J if i!=j) for i in I), 'machine available constraint') 133 | # 4. disjunctive constraint 134 | m.addConstrs((completeTime[i] >= completeTime[j] + process_time[i] - BigM*(1 - x[j,i,k]) for i in I for j in J for k in machines if i!=j), 'temporal disjunctive order1') 135 | # 5. completion time of dummy job 136 | m.addConstr((completeTime[J[0]] == 0), 'first job completion time') 137 | # 6. completion time of the other jobs 138 | m.addConstrs((completeTime[i] >= np.min(m_availabe) for i in I), 'other job completion time') 139 | # 7. all the machine start from dummy job and end with the dummy job 140 | m.addConstrs((quicksum(x[d,j,k] for d in dummy_Job for j in I ) <= z[k] for k in machines),'machine opened') 141 | # 8.machien opens 142 | m.addConstr((quicksum(z[k] for k in machines)<=len(m_availabe)),'machine number') 143 | 144 | return m, x, completeTime 145 | 146 | 147 | if __name__ == '__main__': 148 | job_num = 10 149 | machine_num = 2 150 | job_ids = np.arange(0, job_num, 1, dtype=np.int32)+1 151 | # machine_ids = np.arange(0, machine_num, 1, dtype=np.int32) 152 | np.random.seed(15) # 13 is not feasible solution 153 | request_times = np.random.randint(0, 60, size=(job_num), dtype=np.int32) 154 | process_intervals = np.random.randint(10, 130, size=(job_num), dtype=np.int32) 155 | # machine_properties = np.zeros(machine_num, dtype=np.int32) 156 | machine_properties = np.random.randint(0, 60, size=(machine_num), dtype=np.int32) 157 | 158 | pmsp_solver = PMSP_Gurobi() 159 | 160 | # solver of PMSP 161 | solved = pmsp_solver.solve(job_ids, request_times, process_intervals, machine_properties) 162 | if solved: 163 | print("schedules", pmsp_solver.schedules) 164 | print("order", pmsp_solver.order) 165 | 166 | # job_dict = formulate_jobs_dict(job_ids, request_times, process_intervals) 167 | # gantt_chart_plot(job_dict, pmsp_solver.schedules, machine_properties, "gurobi solver") 168 | # plt.show() 169 | 170 | # get SRPT solution 171 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 172 | SRPT_order = np.zeros(job_num, dtype=np.int32) 173 | machine_orders = np.zeros(job_num, dtype=np.int32) 174 | # output for wait time and serve order with convert SRPT 175 | SRPT_Preemptive_Bound(job_ids,request_times,process_intervals, 176 | machine_properties, SRPT_wait, SRPT_order) 177 | sequence_schedules(job_ids, request_times, process_intervals, machine_properties, 178 | SRPT_order, SRPT_wait, machine_orders) 179 | print("SRPT order", SRPT_order) 180 | print("machine assigns", machine_orders) 181 | print("Start time", request_times+SRPT_wait) -------------------------------------------------------------------------------- /mathematic_model/pmsp_gurobi_TIM.py: -------------------------------------------------------------------------------- 1 | from gurobipy import Model, GRB, GurobiError, quicksum, max_ 2 | import numpy as np 3 | from gantt_plot import * 4 | import os 5 | import time 6 | import sys 7 | sys.path.append(os.path.abspath("../bin/")) 8 | # heuristic method provides some search boundary 9 | sys.path.append(os.path.abspath("../heuristic_model")) 10 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, job_scheduling, sequence_schedules 11 | 12 | StatusDict = {getattr(GRB.Status, s): s for s in dir(GRB.Status) if s.isupper()} 13 | 14 | class PMSP_Gurobi(object): 15 | """docstring for PMSP_Gurobi""" 16 | def __init__(self): 17 | # setting the solver attributes; 18 | self.schedules = {} 19 | self.order = [] 20 | self.bigM = 0 21 | def solve(self, job_ids, request_times, process_intervals, machine_properties): 22 | solved = False 23 | init_time = time.time() 24 | SPRT_TIME = self.SRPT_bound(job_ids, request_times, process_intervals, machine_properties) 25 | pmsp_model = self._create_model(job_ids, request_times, process_intervals, machine_properties, SPRT_TIME) 26 | self.bigM = SPRT_TIME 27 | print("model time: ", time.time()-init_time) 28 | model_instant = time.time() 29 | self._set_model_parms(pmsp_model) 30 | # solve the model 31 | try: 32 | pmsp_model.optimize() 33 | if pmsp_model.status == GRB.Status.OPTIMAL: 34 | solved = True 35 | self._formulate_schedules(job_ids, request_times, process_intervals, 36 | machine_properties, pmsp_model) 37 | else: 38 | statstr = StatusDict[pmsp_model.status] 39 | print('Optimization was stopped with status %s' %statstr) 40 | self._formulate_schedules(job_ids, request_times, process_intervals, 41 | machine_properties, pmsp_model) 42 | except GurobiError as e: 43 | print('Error code '+str(e.errno)+': '+str(e)) 44 | print("solving time: ", time.time()-model_instant) 45 | return solved 46 | 47 | def SRPT_bound(self, job_ids, request_times, process_intervals, machine_properties): 48 | job_num = len(job_ids) 49 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 50 | SRPT_order = np.zeros(job_num, dtype=np.int32) 51 | SRPT_Preemptive_Bound(job_ids,request_times,process_intervals, 52 | machine_properties, SRPT_wait, SRPT_order) 53 | BigM_bound = max(request_times + process_intervals + SRPT_wait) + 5 54 | 55 | return BigM_bound 56 | 57 | def _set_model_parms(self, m): 58 | # permittable gap 59 | # m.setParam('MIPGap',0.2) 60 | # # time limit 61 | # m.setParam('TimeLimit',10) 62 | # # percentage of time on heuristics 63 | # m.setParam('Heuristics',0.5) 64 | m.setParam('Threads', 8) 65 | m.setParam('OutputFlag', 1) 66 | 67 | def _formulate_schedules(self, job_ids, request_times, process_intervals, 68 | machines, model): 69 | # print("variables: ", startTime.keys) 70 | start_times = np.zeros(len(job_ids)) 71 | j_ids = list(job_ids) 72 | complete = [] 73 | for i, j_id in enumerate(job_ids): 74 | self.schedules[j_id] = {} 75 | self.schedules[j_id]['start'] = model._completeTime[j_id].x - process_intervals[j_id] 76 | self.schedules[j_id]['finish'] = model._completeTime[j_id].x 77 | complete.append(model._completeTime[j_id].x) 78 | for m in range(len(machines)): 79 | for t in range(int(self.bigM)): 80 | if model._CHI[m, j_id, t].x == 1: 81 | self.schedules[j_id]['machine'] = m 82 | start_times[i] = model._completeTime[j_id].x - process_intervals[j_id] 83 | 84 | self.order = job_ids[np.argsort(start_times)] 85 | print("total complete time: ", np.sum(complete)) 86 | print("max complete time: ", model._max_complete.x) 87 | 88 | return 89 | 90 | def _create_model(self, job_ids, r_times, p_intervals, m_availabe, SRPT_MAX): 91 | ## prepare the index for decision variables 92 | # start time of process 93 | jobs = tuple(job_ids) 94 | machines = tuple(range(len(machine_properties))) 95 | # define BigM 96 | # BigM = np.sum(r_times) + np.sum(p_intervals) + np.sum(m_availabe) 97 | # BigM = np.max(r_times) + np.max(p_intervals) 98 | # define possible largest time duration 99 | # TIME = range(int(BigM)) 100 | TIME = range(int(SRPT_MAX)) 101 | 102 | ## parameters model (dictionary) 103 | # 1. release time 104 | release_time = dict(zip(jobs, tuple(r_times))) 105 | # 2. process time 106 | process_time = dict(zip(jobs, tuple(p_intervals))) 107 | # 3. machiane available time 108 | machine_time = dict(zip(machines, tuple(m_availabe))) 109 | 110 | ## create model 111 | m = Model('PMSP') 112 | 113 | # job machine time 114 | m._MachineJobTime = [(k,j,t) for k in machines for j in jobs for t in TIME] 115 | 116 | ## create decision variables 117 | m._max_complete = m.addVar(1, name='max_complete_time') 118 | # 1. Chi: job j is processed on Machine i and processed at time t 119 | m._CHI = m.addVars(m._MachineJobTime, vtype=GRB.BINARY, name='order') 120 | # 2. complete time of executing each job 121 | m._completeTime = m.addVars(jobs, name='completeTime') 122 | 123 | ## create objective 124 | m.setObjective(quicksum(m._completeTime), GRB.MINIMIZE) # TOTRY 125 | # m.setObjective(m._max_complete, GRB.MINIMIZE) # TOTRY 126 | ## create constraints 127 | # 1. Each job starts processing on only one machine at only one point in time 128 | for j in jobs: 129 | expr = 0 130 | for k in machines: 131 | for t in TIME: 132 | expr += m._CHI[k,j,t] 133 | m.addConstr((expr == 1), 'time nonsplitting') 134 | m.update() 135 | # m.addConstrs((quicksum([m._CHI[k,i,t] for k in machines for t in TIME])==1 for i in jobs),'job machine match') 136 | # 2. At most one job is processed at any time on any machine 137 | for k in machines: 138 | for t in TIME[1:]: 139 | expr = 0 140 | for j_id in jobs: 141 | h = max(0, t - process_time[j_id]) 142 | for t_id in range(h, t): 143 | expr += m._CHI[k,j_id,t_id] 144 | m.addConstr(expr <= 1) 145 | m.update() 146 | # 3. Completion time requirement 147 | # m.addConstrs((quicksum([(t+process_time[j]+machine_time[i])*m._CHI[i,j,t] for i in machines for t in TIME]) <= m._completeTime[j] for j in jobs), "CT") 148 | for j in jobs: 149 | expr = 0 150 | for i in machines: 151 | for t in TIME: 152 | expr += (t + process_time[j])*m._CHI[i,j,t] 153 | m.addConstr(expr == m._completeTime[j]) 154 | m.update() 155 | # 4. release time constraint 156 | # m.addConstrs((quicksum([m._CHI[i,j_id,t] for i in machines for t in range(release_time[j_id])])==0 for j_id in jobs if release_time[j_id]>0),'release') 157 | for j in jobs: 158 | if release_time[j] > 0: 159 | expr = 0 160 | for i in machines: 161 | for t in range(release_time[j]): 162 | expr += m._CHI[i,j,t] 163 | m.addConstr(expr==0) 164 | m.update() 165 | # 5. machine available time 166 | # m.addConstrs((quicksum([m._CHI[m_id,j,t] for j in jobs for t in range(machine_time[m_id])])==0 for m_id in machines if machine_time[m_id]>0),'available') 167 | for i in machines: 168 | if machine_time[i]>0: 169 | expr = 0 170 | for j in jobs: 171 | for t in range(machine_time[i]): 172 | expr += m._CHI[i,j,t] 173 | m.addConstr(expr== 0) 174 | m.update() 175 | # 6. for minimax 176 | m.addConstrs((m._max_complete>=m._completeTime[j] for j in jobs),'minimax') 177 | 178 | return m 179 | 180 | 181 | if __name__ == '__main__': 182 | 183 | start_time = time.time() 184 | job_num = 20 185 | machine_num = 5 186 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 187 | np.random.seed(15) # 13 is not feasible solution 188 | request_times = np.random.randint(0, 60, size=(job_num), dtype=np.int32) 189 | process_intervals = np.random.randint(10, 130, size=(job_num), dtype=np.int32) 190 | # machine_properties = np.random.randint(0, 3, size=(machine_num), dtype=np.int32) 191 | # machine_properties = np.zeros(machine_num) 192 | machine_properties = np.random.randint(0, 60, size=(machine_num), dtype=np.int32) 193 | print("request_times", request_times) 194 | 195 | 196 | pmsp_solver = PMSP_Gurobi() 197 | # solver of PMSP 198 | solved = pmsp_solver.solve(job_ids, request_times, process_intervals, machine_properties) 199 | # print("solve time: ", time.time()-model_time) 200 | if solved: 201 | print("schedules", pmsp_solver.schedules) 202 | print("order", pmsp_solver.order) 203 | print("solving time(s) is: ", (time.time() - start_time)) 204 | job_dict = formulate_jobs_dict(job_ids, request_times, process_intervals) 205 | gantt_chart_plot(job_dict, pmsp_solver.schedules, machine_properties, "gurobi solver") 206 | plt.show() -------------------------------------------------------------------------------- /mathematic_model/pmsp_milp.py: -------------------------------------------------------------------------------- 1 | from pyomo.environ import * 2 | from pyomo.gdp import * 3 | import matplotlib.pyplot as plt 4 | import random 5 | import numpy as np 6 | import os 7 | import sys 8 | sys.path.append(os.path.abspath("../bin/")) 9 | # heuristic method provides some search boundary 10 | sys.path.append(os.path.abspath("../heuristic_model")) 11 | from jsp_bbs import SRPT_Preemptive_Bound, sequence_eval, job_scheduling, sequence_schedules 12 | 13 | # input to solver: 14 | ## job_ids (N*1), request_times, process_times; 15 | # output of solver: 16 | ## serving order of jobs and predicted wait times 17 | class PMSP_MILP(object): 18 | def __init__(self, objective_type="min_sum"): 19 | self.obj_type = objective_type 20 | # self.optimizer = SolverFactory("cplex", executable="/home/pengchen/ibm/ILOG/CPLEX_Studio129/cplex/bin/x86-64_linux/cplex") 21 | self.optimizer = SolverFactory("gurobi") 22 | # self.optimizer.options['tol'] = 1e-1 23 | # self.optimizer.options['MIPgap'] = 5 24 | # self.optimizer.options['Heuristics'] = 1 25 | # self.optimizer.options['Threads'] = 4 26 | # self.optimizer.options['Timelimit'] = 2 27 | def solve(self, job_ids, request_times, process_times, available_times): 28 | # creat concrete model 29 | machine_ids = range(len(available_times)) 30 | model = self._create_model(job_ids, request_times, process_times, machine_ids, available_times) 31 | results = self.optimizer.solve(model) 32 | if ((results.solver.status == SolverStatus.ok) and 33 | (results.solver.termination_condition == TerminationCondition.optimal)): 34 | # if ((results.solver.status == SolverStatus.ok) or 35 | # ((results.solver.termination_condition == TerminationCondition.maxTimeLimit))): 36 | optimal_wt_sum, optimal_order = self._result_process(model, job_ids, request_times, process_times) 37 | else: 38 | print("The problem is not solved!") 39 | print("Solver Status: ", results.solver.status) 40 | print("Solution status: ", results.solver.termination_condition) 41 | print(results) 42 | exit(1) 43 | 44 | return optimal_wt_sum, optimal_order 45 | 46 | def _create_model(self, job_ids, request_times, process_times, machine_ids, available_times): 47 | # create model 48 | m = ConcreteModel() 49 | # index set to simplify notation 50 | m.J = Set(initialize=range(len(job_ids))) 51 | m.M = Set(initialize=machine_ids) 52 | # 2d matrix to represent orders 53 | m.PAIRS = Set(initialize = m.J * m.J, dimen=2, filter=lambda m, j, k : j < k) 54 | 55 | # decision variables: as tight as possible, provided by heuristic model 56 | self._start_up_bound(job_ids, request_times, process_times, available_times) # can be calculated from heuristic idea 57 | bigM = np.sum(request_times) + np.max(process_times) + np.max(available_times) 58 | # for getting tight boundary 59 | start_min = request_times.min() 60 | m.start = Var(m.J, bounds=(start_min, self.start_time_max), within=NonNegativeReals) 61 | 62 | # assignment of jobs to machines 63 | m.z = Var(m.J, m.M, domain=Binary) 64 | # disjunctive constraints 65 | m.y = Var(m.PAIRS, domain=Binary) 66 | # release constraints 67 | m.c1 = Constraint(m.J, rule=lambda m, j: m.start[j] >= request_times[j]) 68 | # one job for one machine 69 | m.c2 = Constraint(m.J, rule=lambda m, j: 70 | sum(m.z[j,mach] for mach in m.M) == 1) 71 | # consecutive constraint 72 | def c3_rule_(model, l, k, i, j): 73 | if l>i and l= model.y[i,l] + model.y[l,j] 75 | else: 76 | return Constraint.Skip 77 | m.c3 = Constraint(m.J, m.M, m.PAIRS, rule=c3_rule_) 78 | # machine cannot process until available 79 | def c4_rule_(model, j, k): 80 | return model.start[j] >= available_times[k] - bigM*(1 - model.z[j,k]) 81 | m.c4 = Constraint(m.J, m.M, rule=c4_rule_) 82 | 83 | # disjunctive constraints 84 | m.d1 = Constraint(m.M, m.PAIRS, rule = lambda m, mach, j, k: 85 | m.start[j] + process_times[j] <= m.start[k] + bigM*((1-m.y[j,k]) + (1-m.z[j,mach]) + (1-m.z[k,mach]))) 86 | m.d2 = Constraint(m.M, m.PAIRS, rule = lambda m, mach, j, k: 87 | m.start[k] + process_times[k] <= m.start[j] + bigM*((m.y[j,k]) + (1-m.z[j,mach]) + (1-m.z[k,mach]))) 88 | 89 | # objective 90 | if self.obj_type == "min_sum": 91 | m.OBJ = Objective(expr = sum(m.start[j] for j in m.J), sense=minimize) 92 | # lower bound for result 93 | # if not continuous: 94 | # def rule_up_bound_(model): 95 | # return sum(m.start[j] for j in m.J) <= self.up_bound + 2 96 | # m.c_up = Constraint(rule = rule_up_bound_) 97 | # search tighter bound 98 | def rule_low_bound_(model): 99 | return sum(m.start[j] for j in m.J) >= self.low_bound 100 | m.c_low = Constraint(rule = rule_low_bound_) 101 | # minimax 102 | if self.obj_type == "minimax": 103 | m.maxstart = Var(domain=NonNegativeReals) 104 | m.OBJ = Objective(expr = m.maxstart, sense=minimize) 105 | m.c_minmax = Constraint(m.J, rule= lambda m, i: m.start[i] <= m.maxstart) 106 | 107 | # from Chu's paper 108 | # domain knowledge: number of jobs schedule on one machine cannot be more than N-M+1 109 | # def c_domain1_rule_(model, k): 110 | # return sum(m.z[j,k] for j in m.J) <= len(job_ids) - len(available_times) + 1 111 | # m.c_domain1 = Constraint(m.M, rule = c_domain1_rule_) 112 | # # domain knowledge2: PRTF dominaint 113 | # PRTF = 2*request_times + process_times 114 | # PRTF_max = PRTF.max() 115 | # m.c_domain2 = Constraint(m.M, m.PAIRS, rule = lambda m, k, i, j: 116 | # PRTF[j] + PRTF_max*(1 - m.y[i,j]) >= PRTF[i]) 117 | 118 | return m 119 | 120 | # get the bounded start time from heuristic SRPT 121 | def _start_up_bound(self, job_ids, request_times, process_times, available_times): 122 | job_num = len(job_ids) 123 | SRPT_wait = np.zeros(job_num, dtype=np.int32) 124 | SRPT_order = np.zeros(job_num, dtype=np.int32) 125 | # output for wait time and serve order with convert SRPT 126 | SRPT_Preemptive_Bound(job_ids,request_times,process_times, 127 | available_times, SRPT_wait, SRPT_order) 128 | self.low_bound = max((request_times + SRPT_wait).sum(),0) 129 | # print("low bound of SRPT", SRPT_wait.sum()) 130 | # get wait time for nonpreemptive 131 | sequence_eval(job_ids,request_times,process_times, 132 | available_times, SRPT_order, SRPT_wait) 133 | 134 | start_times = request_times + SRPT_wait 135 | self.start_time_max = start_times.max() 136 | self.up_bound = start_times.sum() 137 | 138 | def _result_process(self, model, job_ids, request_times, process_times): 139 | start = [] 140 | for j in model.J: 141 | start.append(model.start[j]()) 142 | 143 | start_times = np.array(start) 144 | arg_start = np.argsort(start_times) 145 | order = job_ids[arg_start] 146 | print(start_times) 147 | # wait time = start time - request time 148 | wait_sum = np.sum(start_times - request_times) 149 | 150 | return wait_sum, order 151 | 152 | 153 | if __name__ == '__main__': 154 | job_num = 8 155 | machine_num = 3 156 | job_ids = np.arange(0, job_num, 1, dtype=np.int32) 157 | # machine_ids = np.arange(0, machine_num, 1, dtype=np.int32) 158 | machine_orders = np.zeros(job_num, dtype=np.int32) 159 | np.random.seed(15) # 13 is not feasible solution 160 | request_times = np.random.randint(0, 20, size=(job_num), dtype=np.int32) 161 | process_intervals = np.random.randint(1, 20, size=(job_num), dtype=np.int32) 162 | machine_properties = np.random.randint(10, 30, size=(machine_num), dtype=np.int32) 163 | 164 | pmsp_solver = PMSP_MILP() 165 | 166 | # solver of PMSP 167 | optimal_wt, optimal_order = pmsp_solver.solve(job_ids, request_times, process_intervals, machine_properties) 168 | print("******solving by MILP*******") 169 | print("optimal wt sum ", optimal_wt) 170 | print("optimal order ", optimal_order) 171 | opt_wait = np.zeros(job_num, dtype=np.int32) 172 | sequence_eval(job_ids,request_times,process_intervals, 173 | machine_properties, optimal_order, opt_wait) 174 | sequence_schedules(job_ids, request_times, process_intervals, machine_properties, optimal_order, opt_wait, 175 | machine_orders) 176 | print("evaluate optimal order", opt_wait.sum()) 177 | print("machine_orders", machine_orders) 178 | --------------------------------------------------------------------------------