270 lines
9.1 KiB
Plaintext
270 lines
9.1 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "google",
|
|
"metadata": {},
|
|
"source": [
|
|
"##### Copyright 2025 Google LLC."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "apache",
|
|
"metadata": {},
|
|
"source": [
|
|
"Licensed under the Apache License, Version 2.0 (the \"License\");\n",
|
|
"you may not use this file except in compliance with the License.\n",
|
|
"You may obtain a copy of the License at\n",
|
|
"\n",
|
|
" http://www.apache.org/licenses/LICENSE-2.0\n",
|
|
"\n",
|
|
"Unless required by applicable law or agreed to in writing, software\n",
|
|
"distributed under the License is distributed on an \"AS IS\" BASIS,\n",
|
|
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
|
|
"See the License for the specific language governing permissions and\n",
|
|
"limitations under the License.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "basename",
|
|
"metadata": {},
|
|
"source": [
|
|
"# permutation_flow_shop"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "link",
|
|
"metadata": {},
|
|
"source": [
|
|
"<table align=\"left\">\n",
|
|
"<td>\n",
|
|
"<a href=\"https://colab.research.google.com/github/google/or-tools/blob/main/examples/notebook/contrib/permutation_flow_shop.ipynb\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/colab_32px.png\"/>Run in Google Colab</a>\n",
|
|
"</td>\n",
|
|
"<td>\n",
|
|
"<a href=\"https://github.com/google/or-tools/blob/main/examples/contrib/permutation_flow_shop.py\"><img src=\"https://raw.githubusercontent.com/google/or-tools/main/tools/github_32px.png\"/>View source on GitHub</a>\n",
|
|
"</td>\n",
|
|
"</table>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "doc",
|
|
"metadata": {},
|
|
"source": [
|
|
"First, you must install [ortools](https://pypi.org/project/ortools/) package in this colab."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "install",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%pip install ortools"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "description",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n",
|
|
"This model implements the permutation flow shop problem (PFSP).\n",
|
|
"\n",
|
|
"In the PFSP, a set of jobs has to be processed on a set of machines. Each job\n",
|
|
"must be processed on each machine in sequence and all jobs have to be processed\n",
|
|
"in the same order on every machine. The objective is to minimize the makespan.\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "code",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from typing import Sequence\n",
|
|
"from dataclasses import dataclass\n",
|
|
"from itertools import product\n",
|
|
"\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"from ortools.sat.colab import flags\n",
|
|
"from ortools.sat.python import cp_model\n",
|
|
"\n",
|
|
"_PARAMS = flags.define_string(\n",
|
|
" \"params\",\n",
|
|
" \"num_search_workers:16\",\n",
|
|
" \"Sat solver parameters.\",\n",
|
|
")\n",
|
|
"\n",
|
|
"_TIME_LIMIT = flags.define_float(\n",
|
|
" \"time_limit\",\n",
|
|
" 60.0,\n",
|
|
" \"Time limit in seconds. Default is 60s.\",\n",
|
|
")\n",
|
|
"\n",
|
|
"_LOG = flags.define_boolean(\n",
|
|
" \"log\",\n",
|
|
" False,\n",
|
|
" \"Whether to log the solver output.\",\n",
|
|
")\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class TaskType:\n",
|
|
" \"\"\"\n",
|
|
" Small wrapper to hold the start, end, and interval variables of a task.\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" start: cp_model.IntVar\n",
|
|
" end: cp_model.IntVar\n",
|
|
" interval: cp_model.IntervalVar\n",
|
|
"\n",
|
|
"\n",
|
|
"def permutation_flow_shop(\n",
|
|
" processing_times: np.ndarray,\n",
|
|
" time_limit: float,\n",
|
|
" log: bool,\n",
|
|
" params: str\n",
|
|
"):\n",
|
|
" \"\"\"\n",
|
|
" Solves the given permutation flow shop problem instance with OR-Tools.\n",
|
|
"\n",
|
|
" Parameters\n",
|
|
" ----------\n",
|
|
" processing_times\n",
|
|
" An n-by-m matrix of processing times of the jobs on the machines.\n",
|
|
" time_limit\n",
|
|
" The time limit in seconds. If not set, the solver runs until an\n",
|
|
" optimal solution is found.\n",
|
|
" log\n",
|
|
" Whether to log the solver output. Default is False.\n",
|
|
"\n",
|
|
" Raises\n",
|
|
" ------\n",
|
|
" ValueError\n",
|
|
" If the number of lines is greater than 1, i.e., the instance is a\n",
|
|
" distributed permutation flow shop problem.\n",
|
|
" \"\"\"\n",
|
|
" m = cp_model.CpModel()\n",
|
|
" num_jobs, num_machines = processing_times.shape\n",
|
|
" horizon = processing_times.sum()\n",
|
|
"\n",
|
|
" # Create interval variables for all tasks (each job/machine pair).\n",
|
|
" tasks = {}\n",
|
|
" for job, machine in product(range(num_jobs), range(num_machines)):\n",
|
|
" start = m.new_int_var(0, horizon, \"\")\n",
|
|
" end = m.new_int_var(0, horizon, \"\")\n",
|
|
" duration = processing_times[job][machine]\n",
|
|
" interval = m.new_interval_var(start, duration, end, \"\")\n",
|
|
" tasks[job, machine] = TaskType(start, end, interval)\n",
|
|
"\n",
|
|
" # No overlap for all job intervals on this machine.\n",
|
|
" for machine in range(num_machines):\n",
|
|
" intervals = [tasks[job, machine].interval for job in range(num_jobs)]\n",
|
|
" m.add_no_overlap(intervals)\n",
|
|
"\n",
|
|
" # Add precedence constraints between tasks of the same job.\n",
|
|
" for job, machine in product(range(num_jobs), range(num_machines - 1)):\n",
|
|
" pred = tasks[job, machine]\n",
|
|
" succ = tasks[job, machine + 1]\n",
|
|
" m.add(pred.end <= succ.start)\n",
|
|
"\n",
|
|
" # Create arcs for circuit constraints.\n",
|
|
" arcs = []\n",
|
|
" for idx1 in range(num_jobs):\n",
|
|
" arcs.append((0, idx1 + 1, m.new_bool_var(\"start\")))\n",
|
|
" arcs.append((idx1 + 1, 0, m.new_bool_var(\"end\")))\n",
|
|
"\n",
|
|
" lits = {}\n",
|
|
" for idx1, idx2 in product(range(num_jobs), repeat=2):\n",
|
|
" if idx1 != idx2:\n",
|
|
" lit = m.new_bool_var(f\"{idx1} -> {idx2}\")\n",
|
|
" lits[idx1, idx2] = lit\n",
|
|
" arcs.append((idx1 + 1, idx2 + 1, lit))\n",
|
|
"\n",
|
|
" m.add_circuit(arcs)\n",
|
|
"\n",
|
|
" # Enforce that the permutation of jobs is the same on all machines.\n",
|
|
" for machine in range(num_machines):\n",
|
|
" starts = [tasks[job, machine].start for job in range(num_jobs)]\n",
|
|
" ends = [tasks[job, machine].end for job in range(num_jobs)]\n",
|
|
"\n",
|
|
" for idx1, idx2 in product(range(num_jobs), repeat=2):\n",
|
|
" if idx1 == idx2:\n",
|
|
" continue\n",
|
|
"\n",
|
|
" # Since all machines share the same arc literals, if the literal\n",
|
|
" # i -> j is True, this enforces that job i is always scheduled\n",
|
|
" # before job j on all machines.\n",
|
|
" lit = lits[idx1, idx2]\n",
|
|
" m.add(ends[idx1] <= starts[idx2]).only_enforce_if(lit)\n",
|
|
"\n",
|
|
" # Set minimizing makespan as objective.\n",
|
|
" obj_var = m.new_int_var(0, horizon, \"makespan\")\n",
|
|
" completion_times = [\n",
|
|
" tasks[(job, num_machines - 1)].end for job in range(num_jobs)\n",
|
|
" ]\n",
|
|
" m.add_max_equality(obj_var, completion_times)\n",
|
|
" m.minimize(obj_var)\n",
|
|
"\n",
|
|
" solver = cp_model.CpSolver()\n",
|
|
" if params:\n",
|
|
" solver.parameters.parse_text_format(params)\n",
|
|
" solver.parameters.log_search_progress = log\n",
|
|
" solver.parameters.max_time_in_seconds = time_limit\n",
|
|
"\n",
|
|
" status_code = solver.Solve(m)\n",
|
|
" status = solver.StatusName(status_code)\n",
|
|
"\n",
|
|
" print(f\"Status: {status}\")\n",
|
|
" print(f\"Makespan: {solver.ObjectiveValue()}\")\n",
|
|
"\n",
|
|
" if status in [\"OPTIMAL\", \"FEASIBLE\"]:\n",
|
|
" start = [solver.Value(tasks[job, 0].start) for job in range(num_jobs)]\n",
|
|
" solution = np.argsort(start) + 1\n",
|
|
" print(f\"Solution: {solution}\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def main(argv: Sequence[str]) -> None:\n",
|
|
" \"\"\"Creates the data and calls the solving procedure.\"\"\"\n",
|
|
" # VRF_10_5_2 instance from http://soa.iti.es/problem-instances.\n",
|
|
" # Optimal makespan is 698.\n",
|
|
" processing_times = [\n",
|
|
" [79, 67, 10, 48, 52],\n",
|
|
" [40, 40, 57, 21, 54],\n",
|
|
" [48, 93, 49, 11, 79],\n",
|
|
" [16, 23, 19, 2, 38],\n",
|
|
" [38, 90, 57, 73, 3],\n",
|
|
" [76, 13, 99, 98, 55],\n",
|
|
" [73, 85, 40, 20, 85],\n",
|
|
" [34, 6, 27, 53, 21],\n",
|
|
" [38, 6, 35, 28, 44],\n",
|
|
" [32, 11, 11, 34, 27],\n",
|
|
" ]\n",
|
|
"\n",
|
|
" permutation_flow_shop(\n",
|
|
" np.array(processing_times), _TIME_LIMIT.value, _LOG.value, _PARAMS.value\n",
|
|
" )\n",
|
|
"\n",
|
|
"\n",
|
|
"app.run(main) \n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|