Skip to content

Instantly share code, notes, and snippets.

@rdbuf
Last active March 9, 2019 19:53
Show Gist options
  • Save rdbuf/afedc313ad2048661e0cc75cd1f12bef to your computer and use it in GitHub Desktop.
Save rdbuf/afedc313ad2048661e0cc75cd1f12bef to your computer and use it in GitHub Desktop.
Phystech.Genesis - Кировский завод
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Постановка задачи\n",
"\n",
"*Необходимо оптимизировать план производства изделий на станках.*\n",
"\n",
"Изделия могут состоять из подизделий.\n",
"\n",
"Для пары (типа изделия, тип станка) существует цена наладки. Если наладка уже произведена, то повторной не требуется.\n",
"\n",
"Для пары (типа изделия, тип станка) существует также цена производства.\n",
"\n",
"Для каждого изделия есть последовательность типов станков, по длине не превышающая 5.\n",
"\n",
"67 типов деталей.\n",
"\n",
"2300 деталей изготовить.\n",
"\n",
"10 станков, 7 типов.\n",
"\n",
"Оптимизировать время изготовления всех изделий."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Подход к решению\n",
"\n",
"Соображение: на обработку данной детали на данном станке всегда будет необходимо как минимум столько времени, сколько требуется для наладки и обработки пачкой. Цель: 1. минимизировать паузы между обработкой на разных станках, 2. добиться полного или частичного параллелизма на каком-то из этапов.\n",
"\n",
"Будем рассматривать параллельный станок как вспомогательный и использовать его только в случае, когда это уменьшает время обработки данной партии на данном типе станка. Таким образом, нужно оптимизировать только взаимный порядок.\n",
"\n",
"Задача в литературе известна как job-shop problem и является NP-трудной. В Google OR-Tools есть инструментарий для оптимизации таких задач ([ссылка](https://developers.google.com/optimization/scheduling/job_shop)), нужно посмотреть ближе."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## OR-Tools"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from ortools.sat.python import cp_model\n",
"import collections\n",
"from math import ceil, floor\n",
"import itertools"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"model = cp_model.CpModel()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"source_path = \"Данные.xlsx\"\n",
"[titles, order, machines, technology, timings, setup_costs] = [pd.read_excel(source_path, sheet_name=idx, index_col=0) for idx in range(6)]"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"# 1. Make a structure that holds all sequences of tasks\n",
"\n",
"Task = collections.namedtuple('Task', 'start end duration interval machine parallel')\n",
"\n",
"# Set max bounds of timing to a reasonably large value\n",
"#horizon = sum(task.duration for _, job in store.items() for task in job)\n",
"horizon = 29251\n",
"\n",
"def adjust_if_painter(n, machine):\n",
" return n if machine != 'Окраска' else max(100, n)\n",
"\n",
"store = {} # id, tasks\n",
"for index, _ in titles.iterrows():\n",
" store[index] = []\n",
" count = order.loc[index, 'Кол-во']\n",
" for step in technology.loc[index, 'Тип оборудования':]:\n",
" if type(step) == float: break # that'd mean there is nothing left to process\n",
" setup_cost = setup_costs.loc[index, step]\n",
" production_cost = timings.loc[index, step]\n",
" avail_machs = len(machines[machines['Тип'].str.startswith(step)])\n",
" # setup_cost * n + production_cost * count / n -> minimize\n",
" n, duration = min([(n, ceil(setup_cost * n + production_cost * adjust_if_painter(count / n, step))) for n in range(1, avail_machs+1)], key=lambda x: x[1])\n",
" \n",
" start_var = model.NewIntVar(0, horizon, f'start_{index}_{step}')\n",
" end_var = model.NewIntVar(0, horizon, f'end_{index}_{step}')\n",
" interval_var = model.NewIntervalVar(start_var, duration, end_var, f'interval_{index}_{step}')\n",
" store[index].append(Task(start_var, end_var, duration, interval_var, step, n))\n",
"\n",
"# 2. Apply sequential constraints\n",
"\n",
"for index, tasks in store.items():\n",
" for i in range(len(tasks) - 1):\n",
" model.Add(tasks[i].end <= tasks[i+1].start)\n",
" if index != floor(index):\n",
" model.Add(tasks[-1].end <= store[floor(index)][0].start)\n",
"\n",
"# 2.1 Apply extra constraints\n",
"\n",
"model.Add(store[23.0][-1].end <= 500)\n",
"model.Add(store[34.0][-1].end <= 575)\n",
"model.Add(store[35.0][-1].end <= 900)\n",
" \n",
"# 3. Apply disjunctive constraints\n",
"\n",
"machine_load = {}\n",
"for k, g in itertools.groupby(sorted((task for _, job in store.items() for task in job), key=lambda x: x.machine), key=lambda x: x.machine):\n",
" machine_load[k] = [task for task in g]\n",
" model.AddNoOverlap(task.interval for task in machine_load[k])\n",
" \n",
"# 4. Add objective\n",
"\n",
"obj_var = model.NewIntVar(0, horizon, 'makespan')\n",
"model.AddMaxEquality(obj_var, [task.end for _, job in store.items() for task in job])\n",
"model.Minimize(obj_var)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"29251"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(task.duration for _, job in store.items() for task in job) # horizon must be set to this"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# 5. Solve\n",
"solver = cp_model.CpSolver()\n",
"status = solver.Solve(model)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'optimized = 13085.0, status = OPTIMAL'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f'optimized = {solver.ObjectiveValue()}, status = {solver.StatusName(status)}'"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Визуализация"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"colors = [ # generated with https://mokole.com/palette.html\n",
" '#696969', '#a9a9a9', '#dcdcdc', '#2f4f4f', '#556b2f', '#6b8e23', '#a0522d', '#2e8b57', '#228b22', '#7f0000', '#191970', '#006400', '#808000', '#483d8b', '#b22222', '#5f9ea0', '#778899', '#3cb371', '#bc8f8f', '#663399', '#bdb76b', '#4682b4', '#d2691e', '#9acd32', '#20b2aa', '#00008b', '#4b0082', '#32cd32', '#daa520', '#8fbc8f', '#8b008b', '#b03060', '#d2b48c', '#66cdaa', '#9932cc', '#ff0000', '#ffa500', '#ffd700', '#ffff00', '#c71585', '#0000cd', '#7fff00', '#00ff00', '#ba55d3', '#00ff7f', '#e9967a', '#dc143c', '#00ffff', '#00bfff', '#f4a460', '#9370db', '#0000ff', '#a020f0', '#f08080', '#adff2f', '#d8bfd8', '#ff7f50', '#ff00ff', '#db7093', '#f0e68c', '#ffff54', '#6495ed', '#dda0dd', '#90ee90', '#87ceeb', '#ff1493', '#7b68ee', '#afeeee', '#ee82ee', '#7fffd4', '#ff69b4', '#ffe4c4', '#ffb6c1'\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 1120x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(14, 6), dpi=80)\n",
"plt.grid(True)\n",
"\n",
"for i, (_, tasks) in enumerate(store.items()):\n",
" for task in tasks:\n",
" plt.plot(\n",
" (solver.Value(task.start), solver.Value(task.end)), (task.machine, task.machine),\n",
" color=colors[i], linewidth=10, solid_capstyle=\"butt\"\n",
" )"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment