├── requirements.txt ├── or-tutorial ├── linear-programming │ ├── __pycache__ │ │ └── utils.cpython-36.pyc │ ├── product-mix.ipynb │ ├── sensitivity-analysis.ipynb │ ├── unbalanced-transportation.ipynb │ ├── workforce-scheduling.ipynb │ ├── production-planning.ipynb │ └── transportation.ipynb ├── inventory │ └── environment.yml └── integer-programming │ ├── Untitled.ipynb │ ├── vehicle-routing.ipynb │ ├── knapsack.ipynb │ ├── set-covering.ipynb │ ├── traveling-salesman.ipynb │ ├── capital-budgeting.ipynb │ └── integer-programming.ipynb ├── .gitignore ├── environment.yml └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.16.5 2 | pandas==0.25.1 3 | gurobi==8.1.1 4 | pulp==1.6.1 5 | deap==1.3 6 | 7 | 8 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/__pycache__/utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unerue/or-tutorial/HEAD/or-tutorial/linear-programming/__pycache__/utils.cpython-36.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files 2 | .DS_Store 3 | .git 4 | *.mps 5 | *.log 6 | copy.md 7 | 8 | # Folders 9 | .ipynb_checkpoints/ 10 | ortools/ 11 | scheduling/ 12 | queueing/ 13 | pert/ 14 | dynamic-programming/ 15 | examples/ 16 | __pycache__/ -------------------------------------------------------------------------------- /or-tutorial/inventory/environment.yml: -------------------------------------------------------------------------------- 1 | name: optimization 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - python=3.7.4 7 | - pip=19.2.3 8 | - numpy=1.16.5 9 | - pandas=0.25.1 10 | - pip: 11 | - pulp>=1.6.1,<=1.7.0 12 | - deap>=1.3,<=1.4 13 | 14 | 15 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: optimization 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - python=3.7.4 7 | - pip=19.2.3 8 | - numpy=1.16.5 9 | - pandas=0.25.1 10 | - gurobi=8.1.1 11 | - pip: 12 | - pulp>=1.6.1,<=1.7.0 13 | - deap>=1.3,<=1.4 14 | - tabulate>=0.8.5,<=0.9.0 15 | 16 | 17 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/Untitled.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Integer Programming\n", 8 | "\n", 9 | "### Plant Location Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). EXCEL 2010 경영과학. 한경사, 409.**\n", 12 | "\n", 13 | "

B전자는 최근 자사 제품의 수요가 급증하여 새로운 제품을 생산할 공장을 신축하기 위하여 3개의 공장 후보지를 선정하였다. B전자는 현재 각 공장에서 생산된 제품을 4개의 물류센터에 공급하고 있으므로 새로 신축할 공장에서 생산될 제품도 4개의 물류센터에 공급하려고 한다. 3개의 공장 후보지의 월 공급능력(개), 월 고정비(천원), 각 공장 후보지에서 4개의 물류센터까지의 수송단가(천원) 및 각 물류센터의 월 수요량(개)은 Table과 같다. B전자의 총비용을 최소로 하는 공장 후보지와 각 신축 공장에서 각 물류센터로의 최적 수송계획을 수립하기 위한 정수계획모형을 작성하고 최적해와 총비용을 구하라.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | "
Table 1. 파출소 후보지역별 설치비용과 8분 이내 도착가능 후보지역
파출소 후보지역파출소 설치비용(천만원)8분 이내 도착가능 후보지역
161, 2, 3, 4, 5
271, 2, 4, 5, 9
351, 3, 4, 5, 6, 7, 8
471, 2, 3, 4, 5, 6, 9
591, 2, 3, 4, 5, 9
6103, 4, 6, 7, 8
783, 5, 7, 8, 9
853, 6, 7, 8, 9
9112, 4, 5, 7, 8, 9
\n" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 3", 81 | "language": "python", 82 | "name": "python3" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 3 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython3", 94 | "version": "3.7.3" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 4 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python37 EXCEL2016 Version Kyungsu 2 | 3 | # or-tutorial 4 | 5 |

6 | 7 | 8 | 9 |

10 | 11 | `or-tutorial`는 경영과학(OR/MS) 튜토리얼입니다. 본 강좌의 목적은 수리 모형을 파이썬으로 구현하는데 있습니다. 따라서, 이론적인 내용 및 수리 모형으로 정식화 과정 생략하였습니다. 자세한 내용은 참고한 서적을 참고하시기 바랍니다. 12 | 13 | `or-tutorial` is an operations research tutorial. The purpose of this tutorial is to implement mathematical models in Python. So, I omitted the mathematical formulation. For more information, please read the references. 14 | 15 | #### Dependencies 16 | 17 | `or-tutorial` requires: 18 | 19 | * Python (>= 3.7.4) 20 | * NumPy (>= 1.16.5) 21 | * pandas (>= 0.25.1) 22 | * PuLP (>= 1.6.9) 23 | * GUROBI (>= 8.1.1) 24 | * DEAP (>= 1.3.1) 25 | * tabulate (>= 0.8.5) 26 | 27 | # Operations Research Tutorial with Python 28 | 29 | ## 선형계획법
Linear Programming 30 | 31 | 1. **Product Mix Problem** - [product-mix.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/linear-programming/product-mix.ipynb) 32 | 2. **Sensitivity Analysis** - [sensitivity-analysis.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/linear-programming/sensitivity-analysis.ipynb) 33 | 3. **Production Planning Problem** - [production-planning.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/linear-programming/production-planning.ipynb) 34 | 4. **Portfolio Selection Problem** - [portfolio-selection.ipynb]() 35 | 5. **Workforce Scheduling** - [workforce-scheduling.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/linear-programming/workforce-scheduling.ipynb) 36 | 6. **Transportation Problem** - [transportation.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/linear-programming/transportation.ipynb) 37 | 38 | --- 39 | 40 | ## 정수계획법
Integer Programming 41 | 42 | 1. **Knapsack Problem** - [knapsack.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/integer-programming/knapsack.ipynb) 43 | 2. **Traveling Salesman Problem** - [traveling-salesman.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/integer-programming/traveling-salesman.ipynb) 44 | 3. **Capital Budgeting Problem** - [capital-budgeting.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/integer-programming/capital-budgeting.ipynb) 45 | 4. **Set Covering Problem** - []() 46 | 5. **Plant Location Problem** -[]() 47 | 6. **Vehicle Routing Problem** - [vehicle-routing.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/integer-programming/vehicle-routing.ipynb) 48 | 49 | --- 50 | 51 | ## 동적계획법
Dynamic Programming 52 | 53 | 1. **Shortest Path Problem** - []() 54 | 2. **Knapsack Problem** - []() 55 | 56 | --- 57 | 58 | ## 네트워크모형
Network Model 59 | 60 | 1. **Shortest Path Problem** - []() 61 | 2. **Minimum Spanning Tree Problem** -[]() 62 | 3. **Maximum Flow Problem** - []() 63 | 4. **Minimum Cost Flow Problem** - []() 64 | 65 | --- 66 | 67 | ## 재고관리
Inventory Management 68 | 69 | 1. **News Vendor Problem** - []() 70 | 2. **Economic Order Quantity (EOQ)** - []() 71 | 3. **Lot-Sizing Problem** - []() 72 | 4. **Joint Replenishment Problem (JRP)** - []() 73 | 74 | --- 75 | 76 | ## 일정계획
Scheduling 77 | 78 | 1. **Single Machine** - []() 79 | 2. **Flow-Shop Scheduling** - []() 80 | 3. **Job-Shop Scheduling** - [job-shop.ipynb](https://nbviewer.jupyter.org/github/unerue/or-tutorial/blob/master/or-tutorial/scheduling/job-shop.ipynb) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/vehicle-routing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Vehicle Routing Problem\n", 8 | "\n", 9 | "

차량 경로 문제(vehicle routing problem, VRP)는 Dantzig and Ramser (1959 )에 의해 제안되었으며 이후 다양한 연구가 진행되어 왔습니다. VRP의 기본 가정은 $n$개의 목적지에 $k$대 차량이 단일 물류 창고를 중심으로 최소비용 또는 최소거리로 운송할 수 있는 경로를 구하는 문제입니다.

\n", 10 | "\n", 11 | "## SOVLING A VEHICLE ROUTING PROBLEM\n", 12 | "\n", 13 | "### Mathematical Modeling\n", 14 | "\n", 15 | "

\n", 16 | "\n", 17 | "$$x_{ij} = \n", 18 | "\\begin{cases}\n", 19 | " 1, \\; \\text{if $i$ is supplied before $j$ by vehicle $k$,}\\\\\n", 20 | " 0, \\; \\text{otherwise}\n", 21 | "\\end{cases}$$\n", 22 | "\n", 23 | "

위의 의사결정변수와 파라미터를 이용하여 총 거리의 합을 최소화하는 정수 계획법의 목적함수는 식(1)과 같습니다.

\n", 24 | "\n", 25 | "$$\\begin{align*}\n", 26 | " & \\text{minimize } & & \\sum_{i=1}^{n} \\sum_{j=1}^{n} c_{ij}x_{ij} \\\\[1ex]\n", 27 | " & \\text{subject to } & \\, & \\sum_{i=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex]\n", 28 | " & & \\, & \\sum_{j=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex] \n", 29 | " & & \\, & \\sum_{i=1}^{n} x_{i0} = K, & \\quad \\\\[1ex] \n", 30 | " & & \\, & \\sum_{j=1}^{n} x_{0j} = K, & \\quad \\\\[1ex] \n", 31 | "\\end{align*}$$\n", 32 | "\n", 33 | "

" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 1, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/plain": [ 44 | "array([[ 0, 42, 73, 87, 66, 46, 91, 35, 62, 55],\n", 45 | " [ 42, 0, 108, 99, 84, 85, 98, 13, 72, 64],\n", 46 | " [ 73, 108, 0, 60, 46, 29, 71, 96, 60, 61],\n", 47 | " [ 87, 99, 60, 0, 21, 71, 11, 86, 27, 35],\n", 48 | " [ 66, 84, 46, 21, 0, 50, 29, 70, 16, 21],\n", 49 | " [ 46, 85, 29, 71, 50, 0, 80, 74, 58, 56],\n", 50 | " [ 91, 98, 71, 11, 29, 80, 0, 86, 29, 36],\n", 51 | " [ 35, 13, 96, 86, 70, 74, 86, 0, 59, 51],\n", 52 | " [ 62, 72, 60, 27, 16, 58, 29, 59, 0, 8],\n", 53 | " [ 55, 64, 61, 35, 21, 56, 36, 51, 8, 0]], dtype=int32)" 54 | ] 55 | }, 56 | "execution_count": 1, 57 | "metadata": {}, 58 | "output_type": "execute_result" 59 | } 60 | ], 61 | "source": [ 62 | "from utils import GenerateCities\n", 63 | "\n", 64 | "coords, cities = GenerateCities(100, 100, 10, 42).generate()\n", 65 | "cities" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "### Mixed Integer Programming using PuLP\n", 73 | "\n", 74 | "

이제 파이썬을 활용하여 외판원 문제의 최적해를 구해보겠습니다. 파이썬을 활용한 경영과학(MS/OR)은 추후 기초부터 포스팅을 할 예정입니다.

" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 2, 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "name": "stdout", 84 | "output_type": "stream", 85 | "text": [ 86 | "296.0\n", 87 | "u_1 = 9.0\n", 88 | "u_2 = 1.0\n", 89 | "u_3 = 3.0\n", 90 | "u_4 = 2.0\n", 91 | "u_5 = 0.0\n", 92 | "u_6 = 4.0\n", 93 | "u_7 = 8.0\n", 94 | "u_8 = 6.0\n", 95 | "u_9 = 7.0\n", 96 | "x_(0,_5) = 1.0\n", 97 | "x_(1,_0) = 1.0\n", 98 | "x_(2,_4) = 1.0\n", 99 | "x_(3,_6) = 1.0\n", 100 | "x_(4,_3) = 1.0\n", 101 | "x_(5,_2) = 1.0\n", 102 | "x_(6,_8) = 1.0\n", 103 | "x_(7,_1) = 1.0\n", 104 | "x_(8,_9) = 1.0\n", 105 | "x_(9,_7) = 1.0\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "from pulp import *\n", 111 | "\n", 112 | "# Initialize vehicle routing problem\n", 113 | "prob = LpProblem('Vehicle Routing', LpMinimize)\n", 114 | "\n", 115 | "K = 4\n", 116 | "n = len(cities)\n", 117 | "indices = [(i, j) for i in range(n) for j in range(n) if i != j]\n", 118 | "\n", 119 | "# Creating decision variables\n", 120 | "x = LpVariable.dicts('x', indices, cat='Binary')\n", 121 | "u = LpVariable.dicts('u', list(range(n)), lowBound=0, upBound=n-1, cat='Continuous')\n", 122 | "\n", 123 | "# Objective function\n", 124 | "prob += lpSum([cities[i][j] * x[(i,j)] for i, j in indices])\n", 125 | "\n", 126 | "# Constraints\n", 127 | "for i in range(n):\n", 128 | " if i != 0:\n", 129 | " prob += lpSum([x[(i,j)] for j in range(n) if i != j]) == 1\n", 130 | " \n", 131 | "for j in range(n):\n", 132 | " if j != 0:\n", 133 | " prob += lpSum([x[(i,j)] for i in range(n) if i != j]) == 1\n", 134 | " \n", 135 | "for i in range(1, n):\n", 136 | " for j in range(1, n):\n", 137 | " if i != j:\n", 138 | " prob += u[i] - u[j] + len(cities) * x[(i,j)] <= len(cities) - 1\n", 139 | "\n", 140 | "# Solve problem\n", 141 | "prob.solve()\n", 142 | "print(value(prob.objective))\n", 143 | "\n", 144 | "for i in prob.variables():\n", 145 | " if i.name[0] == 'u':\n", 146 | " print(i.name, '=', i.varValue)\n", 147 | " elif i.varValue != 0:\n", 148 | " print(i.name, '=', i.varValue)" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "metadata": {}, 154 | "source": [ 155 | "## CONCLUSION\n", 156 | "\n", 157 | "

차량 경로 문제는

\n", 158 | "\n", 159 | "

해당 포스트는 지속적으로 업데이트 할 예정이니 오타, 틀린부분, 파이써닉(pythonic)하지 못한 코드가 있을 경우 지적해주시면 수정하도록 하겠습니다.

\n", 160 | "\n", 161 | "## REFERENCES\n", 162 | "\n", 163 | "1. 이강우 & 김정자 (2010). _경영과학_, 한경사.\n", 164 | "2. Rasmussen, R. (2011). TSP in Spreadsheets–a Guided Tour. _International Review of Economics Education_, 10(1), 94-116.\n", 165 | "3. Hillier, F. S. & Lieberman, G. J. (2013). _Introduction to Operations Research_. McGraw-Hill Science, Engineering & Mathematics.\n", 166 | "4. Bello, I., Pham, H., Le, Q. V., Norouzi, M., & Bengio, S. (2016). Neural Combinatorial Optimization with Reinforcement Learning. _arXiv preprint arXiv:1611.09940_." 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [] 175 | } 176 | ], 177 | "metadata": { 178 | "kernelspec": { 179 | "display_name": "Python 3", 180 | "language": "python", 181 | "name": "python3" 182 | }, 183 | "language_info": { 184 | "codemirror_mode": { 185 | "name": "ipython", 186 | "version": 3 187 | }, 188 | "file_extension": ".py", 189 | "mimetype": "text/x-python", 190 | "name": "python", 191 | "nbconvert_exporter": "python", 192 | "pygments_lexer": "ipython3", 193 | "version": "3.7.3" 194 | } 195 | }, 196 | "nbformat": 4, 197 | "nbformat_minor": 4 198 | } 199 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/product-mix.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programming\n", 8 | "\n", 9 | "### Product Mix Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 32.**\n", 12 | "\n", 13 | "

D기업은 두 종류의 원료와 한 종류의 기계를 사용하여 제품 A와 제품 B를 생산하여 판매하고 있다. 각 제품을 1단위 생산할 때 필요한 원료의 사용량과 기계사용시간은 다음 Table 1과 같다.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | "
Table 1. 제품조합문제의 자료
자원AB월 가용량
원료 1(kg)105300
원료 2(kg)44160
기계사용시간(시간)26180
단위당 이익(만원/개)32
\n", 48 | " \n", 49 | "

한편 D기업이 보유하고 있는 원료 1과 원료 2의 월 가용량은 각각 300kg, 160kg이고 기계사용시간의 월 가용시간은 180시간이며 생산된 제품 A와 제품 B를 판매하면 각각 단위당 3만원과 2만원의 이익이 발생한다고 한다. D기업에 주어진 한정된 자원을 사용하여 D기업의 월 이익을 최대로 하는 제품 A와 제품 B의 월 생산량을 구하기 위한 선형계획모형을 작성하라.


\n", 50 | "\n", 51 | "$$\\begin{align*}\n", 52 | " & \\text{maximize } & Z=3&X_{1}+2X_{2} & \\text{(Objective function)} &\\quad(1.1)\\\\[1ex]\n", 53 | " & \\text{subject to } & \\, 10&X_{1}+5X_{2} \\le 300 & \\text{(Constraint 1)} &\\quad(1.2)\\\\[1ex]\n", 54 | " & & \\, 4&X_{1}+4X_{2} \\le 160 & \\text{(Constraint 2)} &\\quad(1.3)\\\\[1ex] \n", 55 | " & & \\, 2&X_{1}+6X_{2} \\le 180 & \\text{(Constraint 3)} &\\quad(1.4)\\\\[1ex] \n", 56 | " & \\text{and} & \\, &X_{1},X_{2} \\ge 0 & \\text{(Non-negative)} &\\quad(1.5)\\\\[1ex] \n", 57 | "\\end{align*}$$" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "#### Optimization with PuLP" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 1, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "name": "stdout", 74 | "output_type": "stream", 75 | "text": [ 76 | "Status Optimal\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "from pulp import *\n", 82 | "\n", 83 | "# Define problem\n", 84 | "prob = LpProblem(name='Product Mix Problem', sense=LpMaximize)\n", 85 | "\n", 86 | "# Create decision variables and non-negative constraint\n", 87 | "x1 = LpVariable(name='X1', lowBound=0, upBound=None, cat='Continuous')\n", 88 | "x2 = LpVariable(name='X2', lowBound=0, upBound=None, cat='Continuous')\n", 89 | "\n", 90 | "# Set objective function\n", 91 | "prob += 3*x1 + 2*x2\n", 92 | "\n", 93 | "# Set constraints\n", 94 | "prob += 10*x1 + 5*x2 <= 300\n", 95 | "prob += 4*x1 + 4*x2 <= 160\n", 96 | "prob += 2*x1 + 6*x2 <= 180\n", 97 | "\n", 98 | "# Solving problem\n", 99 | "prob.solve()\n", 100 | "print('Status', LpStatus[prob.status])" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 2, 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "name": "stdout", 110 | "output_type": "stream", 111 | "text": [ 112 | "Z = 100.0\n", 113 | "X1 = 20.0\n", 114 | "X2 = 20.0\n" 115 | ] 116 | } 117 | ], 118 | "source": [ 119 | "print('Z = {}'.format(value(prob.objective)))\n", 120 | "for v in prob.variables():\n", 121 | " print('{} = {}'.format(v.name, v.varValue))" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "metadata": {}, 127 | "source": [ 128 | "#### Optimization with GUROBI" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": 3, 134 | "metadata": {}, 135 | "outputs": [ 136 | { 137 | "name": "stdout", 138 | "output_type": "stream", 139 | "text": [ 140 | "Optimize a model with 3 rows, 2 columns and 6 nonzeros\n", 141 | "Coefficient statistics:\n", 142 | " Matrix range [2e+00, 1e+01]\n", 143 | " Objective range [2e+00, 3e+00]\n", 144 | " Bounds range [0e+00, 0e+00]\n", 145 | " RHS range [2e+02, 3e+02]\n", 146 | "Presolve time: 0.00s\n", 147 | "Presolved: 3 rows, 2 columns, 6 nonzeros\n", 148 | "\n", 149 | "Iteration Objective Primal Inf. Dual Inf. Time\n", 150 | " 0 5.0000000e+30 4.875000e+30 5.000000e+00 0s\n", 151 | " 2 1.0000000e+02 0.000000e+00 0.000000e+00 0s\n", 152 | "\n", 153 | "Solved in 2 iterations and 0.01 seconds\n", 154 | "Optimal objective 1.000000000e+02\n" 155 | ] 156 | } 157 | ], 158 | "source": [ 159 | "from gurobipy import *\n", 160 | "\n", 161 | "# Create a new model\n", 162 | "m = Model(name='Product Mix Problem')\n", 163 | "\n", 164 | "# Create variables\n", 165 | "x1 = m.addVar(lb=0, ub=1e+100, vtype=GRB.CONTINUOUS, name='X1')\n", 166 | "x2 = m.addVar(lb=0, ub=1e+100, vtype=GRB.CONTINUOUS, name='X2')\n", 167 | "\n", 168 | "# Set objective\n", 169 | "m.setObjective(3*x1 + 2*x2, sense=GRB.MAXIMIZE)\n", 170 | "\n", 171 | "# Add constraint\n", 172 | "m.addConstr(10*x1 + 5*x2 <= 300)\n", 173 | "m.addConstr(4*x1 + 4*x2 <= 160)\n", 174 | "m.addConstr(2*x1 + 6*x2 <= 180)\n", 175 | "\n", 176 | "# Optimize model\n", 177 | "m.optimize()" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 4, 183 | "metadata": {}, 184 | "outputs": [ 185 | { 186 | "name": "stdout", 187 | "output_type": "stream", 188 | "text": [ 189 | "Z = 100.0\n", 190 | "X1 = 20.0\n", 191 | "X2 = 20.0\n" 192 | ] 193 | } 194 | ], 195 | "source": [ 196 | "print('Z = {}'.format(m.objVal))\n", 197 | "for v in m.getVars():\n", 198 | " print('{} = {}'.format(v.varName, v.x))" 199 | ] 200 | } 201 | ], 202 | "metadata": { 203 | "kernelspec": { 204 | "display_name": "Python 3", 205 | "language": "python", 206 | "name": "python3" 207 | }, 208 | "language_info": { 209 | "codemirror_mode": { 210 | "name": "ipython", 211 | "version": 3 212 | }, 213 | "file_extension": ".py", 214 | "mimetype": "text/x-python", 215 | "name": "python", 216 | "nbconvert_exporter": "python", 217 | "pygments_lexer": "ipython3", 218 | "version": "3.7.3" 219 | }, 220 | "latex_envs": { 221 | "LaTeX_envs_menu_present": true, 222 | "autocomplete": true, 223 | "bibliofile": "biblio.bib", 224 | "cite_by": "apalike", 225 | "current_citInitial": 1, 226 | "eqLabelWithNumbers": true, 227 | "eqNumInitial": 1, 228 | "hotkeys": { 229 | "equation": "Ctrl-E", 230 | "itemize": "Ctrl-I" 231 | }, 232 | "labels_anchors": false, 233 | "latex_user_defs": false, 234 | "report_style_numbering": false, 235 | "user_envs_cfg": false 236 | }, 237 | "varInspector": { 238 | "cols": { 239 | "lenName": 16, 240 | "lenType": 16, 241 | "lenVar": 40 242 | }, 243 | "kernels_config": { 244 | "python": { 245 | "delete_cmd_postfix": "", 246 | "delete_cmd_prefix": "del ", 247 | "library": "var_list.py", 248 | "varRefreshCmd": "print(var_dic_list())" 249 | }, 250 | "r": { 251 | "delete_cmd_postfix": ") ", 252 | "delete_cmd_prefix": "rm(", 253 | "library": "var_list.r", 254 | "varRefreshCmd": "cat(var_dic_list()) " 255 | } 256 | }, 257 | "types_to_exclude": [ 258 | "module", 259 | "function", 260 | "builtin_function_or_method", 261 | "instance", 262 | "_Feature" 263 | ], 264 | "window_display": false 265 | } 266 | }, 267 | "nbformat": 4, 268 | "nbformat_minor": 4 269 | } 270 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/knapsack.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Integer Programming\n", 8 | "\n", 9 | "### Knapsack Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). EXCEL 2010 경영과학. 한경사, 380.**\n", 12 | "\n", 13 | "

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | "
Table 1. Example
ItemsWeights (kg)ValuesMax weight (kg)
1366
248
323
\n", 40 | "\n", 41 | "

단일품목 배낭문제의 최적 정수해를 구하기 위한 정수계획모형을 정식화하기 위하여 품목 1, 품목2, 품목 3을 각각 결정변수 $X_{1}$, $X_{2}$, $X_{3}$이라고 정의하고 다음과 같이 $0-1$변수로 정의하자.

\n", 42 | "\n", 43 | "$$X_{j} = \\begin{cases}1: \\text{if item $j$ is selected } (j=1,2,3)\\\\\n", 44 | "0: \\text{if item $j$ is not selected } (j=1,2,3)\n", 45 | "\\end{cases}$$\n", 46 | "\n", 47 | "

위에서 정의한 결정변수를 이용하여 배낭에 넣어 갖고 갈 수 있는 품목의 효용을 최대화하는 목적함수와 배낭의 보관능력에 대한 제약식을 정식화하면 다음과 같다.

\n", 48 | "\n", 49 | "$$\\begin{align*}\n", 50 | " & \\text{maximize } & Z=7&X_{1} + 8X_{2} + 3X_{3} \\\\\n", 51 | " & \\text{subject to } & \\, 3&X_{1} + 4X_{2} + 2X_{3} \\le 6 \\\\\n", 52 | " & \\text{and} & \\, &X_{j} = 0 \\text{ or } 1 (j=1,2,3)\n", 53 | "\\end{align*}$$" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 13, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "import os\n", 63 | "import sys\n", 64 | "\n", 65 | "# Add the parent directory for importing custom library\n", 66 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "#### Optimization with PuLP" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 14, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "Status: Optimal\n", 86 | "Objective value: 11.0\n", 87 | "\n", 88 | "Variables Values\n", 89 | "----------- --------\n", 90 | "X1 0\n", 91 | "X2 1\n", 92 | "X3 1\n", 93 | "\n", 94 | "Statistics:\n", 95 | "- Number of variables: 3\n", 96 | "- Number of constraints: 1\n", 97 | "- Solve time: 0.006s\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "from pulp import *\n", 103 | "from ortools.utils import output\n", 104 | "\n", 105 | "# Define problem\n", 106 | "prob = LpProblem('Knapsack Problem', LpMaximize)\n", 107 | "\n", 108 | "# Create decision variables and non-negative constraint\n", 109 | "x1 = LpVariable(name='X1', lowBound=0, cat='Binary')\n", 110 | "x2 = LpVariable(name='X2', lowBound=0, cat='Binary')\n", 111 | "x3 = LpVariable(name='X3', lowBound=0, cat='Binary')\n", 112 | "\n", 113 | "# Set objective function\n", 114 | "prob += 7*x1 + 8*x2 + 3*x3\n", 115 | "\n", 116 | "# Set constraints\n", 117 | "prob += 3*x1 + 4*x2 + 2*x3 <= 6\n", 118 | "\n", 119 | "# Solving problem\n", 120 | "prob.solve()\n", 121 | "output(prob)" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 15, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "name": "stdout", 131 | "output_type": "stream", 132 | "text": [ 133 | "Status: Optimal\n", 134 | "Objective value: 11.0\n", 135 | "\n", 136 | "Variables Values\n", 137 | "----------- --------\n", 138 | "x_0 0\n", 139 | "x_1 1\n", 140 | "x_2 1\n", 141 | "\n", 142 | "Statistics:\n", 143 | "- Number of variables: 3\n", 144 | "- Number of constraints: 1\n", 145 | "- Solve time: 0.006s\n" 146 | ] 147 | } 148 | ], 149 | "source": [ 150 | "from pulp import *\n", 151 | "from ortools.utils import output\n", 152 | "\n", 153 | "n_items = 3\n", 154 | "weights = [3, 4, 2]\n", 155 | "values = [7, 8, 3]\n", 156 | "max_weight = 6\n", 157 | "\n", 158 | "# Define problem\n", 159 | "prob = LpProblem('Knapsack Problem', LpMaximize)\n", 160 | "\n", 161 | "# Create decision variables\n", 162 | "x = LpVariable.dicts('x', [i for i in range(n_items)], lowBound=0, cat='Binary')\n", 163 | "\n", 164 | "# Set objective function\n", 165 | "prob += lpSum([values[i]*x[i] for i in range(n_items)])\n", 166 | "\n", 167 | "# Set constraints\n", 168 | "prob += lpSum([weights[i]*x[i] for i in range(n_items)]) <= max_weight\n", 169 | "\n", 170 | "# Solving problem\n", 171 | "prob.solve()\n", 172 | "output(prob)" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "#### Optimization with GUROBI" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 16, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "name": "stdout", 189 | "output_type": "stream", 190 | "text": [ 191 | "Status: Optimal\n", 192 | "Objective value: 11.0\n", 193 | "\n", 194 | "Variables Values\n", 195 | "----------- --------\n", 196 | "X0 0\n", 197 | "X1 1\n", 198 | "X2 1\n", 199 | "\n", 200 | "Statistics:\n", 201 | "- Number of variables: 3\n", 202 | "- Number of constraints: 1\n", 203 | "- Solve time: 0.000s\n" 204 | ] 205 | } 206 | ], 207 | "source": [ 208 | "from gurobipy import *\n", 209 | "from ortools.utils import set_gurobi, custom_callback, output\n", 210 | "\n", 211 | "n_items = 3\n", 212 | "weights = [3, 4, 2]\n", 213 | "values = [7, 8, 3]\n", 214 | "max_weight = 6\n", 215 | "\n", 216 | "# Define problem\n", 217 | "m = Model('Knapsack Problem')\n", 218 | "\n", 219 | "# Create decision variables\n", 220 | "x = [m.addVar(vtype=GRB.BINARY, name='X{}'.format(i)) for i in range(n_items)]\n", 221 | "m.update()\n", 222 | "\n", 223 | "# Set objective function\n", 224 | "m.setObjective(quicksum(values[i]*x[i] for i in range(n_items)), GRB.MAXIMIZE)\n", 225 | "\n", 226 | "# Set constraints\n", 227 | "m.addConstr(quicksum(weights[i]*x[i] for i in range(n_items)) <= max_weight)\n", 228 | "\n", 229 | "set_gurobi(m, verbose=False)\n", 230 | "\n", 231 | "# Optimize model\n", 232 | "m.optimize(custom_callback)\n", 233 | "output(m)" 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.7.3" 254 | }, 255 | "latex_envs": { 256 | "LaTeX_envs_menu_present": true, 257 | "autocomplete": true, 258 | "bibliofile": "biblio.bib", 259 | "cite_by": "apalike", 260 | "current_citInitial": 1, 261 | "eqLabelWithNumbers": true, 262 | "eqNumInitial": 1, 263 | "hotkeys": { 264 | "equation": "Ctrl-E", 265 | "itemize": "Ctrl-I" 266 | }, 267 | "labels_anchors": false, 268 | "latex_user_defs": false, 269 | "report_style_numbering": false, 270 | "user_envs_cfg": false 271 | }, 272 | "varInspector": { 273 | "cols": { 274 | "lenName": 16, 275 | "lenType": 16, 276 | "lenVar": 40 277 | }, 278 | "kernels_config": { 279 | "python": { 280 | "delete_cmd_postfix": "", 281 | "delete_cmd_prefix": "del ", 282 | "library": "var_list.py", 283 | "varRefreshCmd": "print(var_dic_list())" 284 | }, 285 | "r": { 286 | "delete_cmd_postfix": ") ", 287 | "delete_cmd_prefix": "rm(", 288 | "library": "var_list.r", 289 | "varRefreshCmd": "cat(var_dic_list()) " 290 | } 291 | }, 292 | "types_to_exclude": [ 293 | "module", 294 | "function", 295 | "builtin_function_or_method", 296 | "instance", 297 | "_Feature" 298 | ], 299 | "window_display": false 300 | } 301 | }, 302 | "nbformat": 4, 303 | "nbformat_minor": 4 304 | } 305 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/set-covering.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Integer Programming\n", 8 | "\n", 9 | "### Set Covering Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). EXCEL 2010 경영과학. 한경사, 405.**\n", 12 | "\n", 13 | "

해운대구는 최근 신시가지 개발의 완료와 더불어 각종 범죄가 급속도로 지능화, 신속화, 흉악화 되어가고 있다. 따라서 부산시경에서는 수사의 신속성과 기동성을 확보함으로써 수사의 효율성을 제고하기 이ㅜ하여 해운대구에 파출소를 설치하기로 결정하고 해운대구에서 범죄 발생빈도가 높은 파출소의 후보지역을 조사한 결과 9개 지역으로 나타났다. 부산시경에서는 해운대구에서 조사된 범죄 발생빈도가 높은 9개 지역을 파출소의 설치대상 후보지역으로 선정하고 범죄가 발생할 때 최대로 8분 이내에 범죄 현장에 도착할 수 있도록 이 후보지역 중에서 특정 지역을 선정하여 파출소를 설치하려고 한다. Table은 각 후보지역의 파출소 설치비용과 각 후보지역을 기준으로 8분 이내에 범죄현장에 도착할 수 있는 후보지역을 조사한 결과이다.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | "
Table 1. 파출소 후보지역별 설치비용과 8분 이내 도착가능 후보지역
파출소 후보지역파출소 설치비용(천만원)8분 이내 도착가능 후보지역
161, 2, 3, 4, 5
271, 2, 4, 5, 9
351, 3, 4, 5, 6, 7, 8
471, 2, 3, 4, 5, 6, 9
591, 2, 3, 4, 5, 9
6103, 4, 6, 7, 8
783, 5, 7, 8, 9
853, 6, 7, 8, 9
9112, 4, 5, 7, 8, 9
\n", 68 | "\n", 69 | "* 파출소를 최소의 수로 설치하여 9개의 지역에서 버모지가 발생할 때 8분 이내에 범죄현장에 도착하도록 하고자 할 때 어느 후보지역에 파출소를 설치하면 되겠는가?\n", 70 | "* 최소의 파출소 설치비용으로 파출소를 설치하여 9개의 지역에서 범죄가 발생할 때 8분 이내에 범죄현장에 도착하도록 하고자 할 때 어느 후보지역에 파출소를 설치하면 되겠는가?" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "#### 최소의 파출소 후보지역을 구하기 위한 0-1정수계획모형\n", 78 | "\n", 79 | "$$\\begin{align*}\n", 80 | " & \\text{minimize } & Z=7&X_{1} + 8X_{2} + 3X_{3} \\\\\n", 81 | " & \\text{subject to } & \\, 3&X_{1} + 4X_{2} + 2X_{3} \\le 6 \\\\\n", 82 | " & \\text{and} & \\, &X_{j} = 0 \\text{ or } 1 (j=1,2,3)\n", 83 | "\\end{align*}$$" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 1, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "import sys\n", 93 | "\n", 94 | "# Add the parent directory for importing custom library\n", 95 | "sys.path.append('../')" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 2, 101 | "metadata": {}, 102 | "outputs": [ 103 | { 104 | "name": "stdout", 105 | "output_type": "stream", 106 | "text": [ 107 | "Status: Optimal\n", 108 | "Objective value: 2.0\n", 109 | "\n", 110 | "Variables Values\n", 111 | "----------- --------\n", 112 | "X1 1\n", 113 | "X2 0\n", 114 | "X3 0\n", 115 | "X4 0\n", 116 | "X5 0\n", 117 | "X6 0\n", 118 | "X7 1\n", 119 | "X8 0\n", 120 | "X9 0\n", 121 | "\n", 122 | "Statistics:\n", 123 | "- Number of variables: 9\n", 124 | "- Number of constraints: 8\n", 125 | "- Solve time: 0.005s\n" 126 | ] 127 | } 128 | ], 129 | "source": [ 130 | "from pulp import *\n", 131 | "from ortools.utils import output\n", 132 | "\n", 133 | "# Define problem\n", 134 | "prob = LpProblem('Set Covering Problem', LpMinimize)\n", 135 | "\n", 136 | "# Create decision variables and non-negative constraint\n", 137 | "x1 = LpVariable(name='X1', lowBound=0, cat='Binary')\n", 138 | "x2 = LpVariable(name='X2', lowBound=0, cat='Binary')\n", 139 | "x3 = LpVariable(name='X3', lowBound=0, cat='Binary')\n", 140 | "x4 = LpVariable(name='X4', lowBound=0, cat='Binary')\n", 141 | "x5 = LpVariable(name='X5', lowBound=0, cat='Binary')\n", 142 | "x6 = LpVariable(name='X6', lowBound=0, cat='Binary')\n", 143 | "x7 = LpVariable(name='X7', lowBound=0, cat='Binary')\n", 144 | "x8 = LpVariable(name='X8', lowBound=0, cat='Binary')\n", 145 | "x9 = LpVariable(name='X9', lowBound=0, cat='Binary')\n", 146 | "\n", 147 | "# Set objective function\n", 148 | "prob += x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9\n", 149 | "\n", 150 | "# Set constraints\n", 151 | "prob += x1 + x2 + x3 + x4 + x5 >= 1\n", 152 | "prob += x1 + x2 + x4 + x5 + x9 >= 1\n", 153 | "prob += x1 + x3 + x4 + x5 + x6 + x7 + x8 >= 1\n", 154 | "prob += x1 + x2 + x3 + x4 + x5 + x6 + x9 >= 1\n", 155 | "prob += x3 + x4 + x6 + x7 + x8 >= 1\n", 156 | "prob += x3 + x5 + x7 + x8 + x9 >= 1\n", 157 | "prob += x3 + x6 + x7 + x8 + x9 >= 1\n", 158 | "prob += x2 + x4 + x5 + x7 + x8 + x9 >= 1\n", 159 | "\n", 160 | "# Solving problem\n", 161 | "prob.solve()\n", 162 | "output(prob)" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 3, 168 | "metadata": {}, 169 | "outputs": [ 170 | { 171 | "name": "stdout", 172 | "output_type": "stream", 173 | "text": [ 174 | "Status: Optimal\n", 175 | "Objective value: 11.0\n", 176 | "\n", 177 | "Variables Values\n", 178 | "----------- --------\n", 179 | "X1 1\n", 180 | "X2 0\n", 181 | "X3 0\n", 182 | "X4 0\n", 183 | "X5 0\n", 184 | "X6 0\n", 185 | "X7 0\n", 186 | "X8 1\n", 187 | "X9 0\n", 188 | "\n", 189 | "Statistics:\n", 190 | "- Number of variables: 9\n", 191 | "- Number of constraints: 8\n", 192 | "- Solve time: 0.008s\n" 193 | ] 194 | } 195 | ], 196 | "source": [ 197 | "from pulp import *\n", 198 | "from ortools.utils import output\n", 199 | "\n", 200 | "# Define problem\n", 201 | "prob = LpProblem('Set Covering Problem', LpMinimize)\n", 202 | "\n", 203 | "# Create decision variables and non-negative constraint\n", 204 | "x1 = LpVariable(name='X1', lowBound=0, cat='Binary')\n", 205 | "x2 = LpVariable(name='X2', lowBound=0, cat='Binary')\n", 206 | "x3 = LpVariable(name='X3', lowBound=0, cat='Binary')\n", 207 | "x4 = LpVariable(name='X4', lowBound=0, cat='Binary')\n", 208 | "x5 = LpVariable(name='X5', lowBound=0, cat='Binary')\n", 209 | "x6 = LpVariable(name='X6', lowBound=0, cat='Binary')\n", 210 | "x7 = LpVariable(name='X7', lowBound=0, cat='Binary')\n", 211 | "x8 = LpVariable(name='X8', lowBound=0, cat='Binary')\n", 212 | "x9 = LpVariable(name='X9', lowBound=0, cat='Binary')\n", 213 | "\n", 214 | "# Set objective function\n", 215 | "prob += 6*x1 + 7*x2 + 5*x3 + 7*x4 + 9*x5 + 10*x6 + 8*x7 + 5*x8 + 11*x9\n", 216 | "\n", 217 | "# Set constraints\n", 218 | "prob += x1 + x2 + x3 + x4 + x5 >= 1\n", 219 | "prob += x1 + x2 + x4 + x5 + x9 >= 1\n", 220 | "prob += x1 + x3 + x4 + x5 + x6 + x7 + x8 >= 1\n", 221 | "prob += x1 + x2 + x3 + x4 + x5 + x6 + x9 >= 1\n", 222 | "prob += x3 + x4 + x6 + x7 + x8 >= 1\n", 223 | "prob += x3 + x5 + x7 + x8 + x9 >= 1\n", 224 | "prob += x3 + x6 + x7 + x8 + x9 >= 1\n", 225 | "prob += x2 + x4 + x5 + x7 + x8 + x9 >= 1\n", 226 | "\n", 227 | "# Solving problem\n", 228 | "prob.solve()\n", 229 | "output(prob)" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": null, 235 | "metadata": {}, 236 | "outputs": [], 237 | "source": [] 238 | } 239 | ], 240 | "metadata": { 241 | "kernelspec": { 242 | "display_name": "Python 3", 243 | "language": "python", 244 | "name": "python3" 245 | }, 246 | "language_info": { 247 | "codemirror_mode": { 248 | "name": "ipython", 249 | "version": 3 250 | }, 251 | "file_extension": ".py", 252 | "mimetype": "text/x-python", 253 | "name": "python", 254 | "nbconvert_exporter": "python", 255 | "pygments_lexer": "ipython3", 256 | "version": "3.7.3" 257 | } 258 | }, 259 | "nbformat": 4, 260 | "nbformat_minor": 4 261 | } 262 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/sensitivity-analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programming\n", 8 | "\n", 9 | "### Sensitivity Analysis\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 225.**\n", 12 | "\n", 13 | "

D전자는 통신장비를 생산하여 판매하고 있는 회사이며 최근 개발한 신제품의 판매시장을 국내, 아시아지역, 유럽지역으로 확장하려고 한다. 이를 위하여 각 지역별 시장조사를 한 결과 수집된 자료는 와 같다.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | "
Table 1. D전자의 마케팅자료
지역단위당 판매이익(만원)단위당 광고비용(만원)단위당 판매활동시간(시간)
국내61.01.5
아시아40.82.0
유럽71.53.0
\n", 42 | "\n", 43 | "

D전자의 신제품 생산능력은 1개월에 3,500개이고 1개월 동안 아시아 지역에 최소한 500개를 공급할 계획을 가지고 있다. 그리고 D전자의 판매원의 판매활동시간은 1개월에 총 5,000시간이며 1개월 광고비 예산은 3,000만원이다. 한편 D전자는 주문생산을 원칙으로 하고 있기 대문에 재고는 보유하지 않는다. D전자의 월 판매이익을 최대로 하는 지역별 신제품의 판매량을 구하기 위한 선형계획 모형을 작성하여 지역별 최적 판매계획을 수립하고 민감도분석을 수행하라.

" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 1, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "import os\n", 53 | "import sys\n", 54 | "\n", 55 | "# Add the parent directory for importing custom library\n", 56 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "#### Optimization with PuLP" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 2, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "Status: Optimal\n" 76 | ] 77 | } 78 | ], 79 | "source": [ 80 | "from pulp import *\n", 81 | "\n", 82 | "# Define problem\n", 83 | "prob = LpProblem(name='Marketing', sense=LpMaximize)\n", 84 | "\n", 85 | "# Create decision variables and non-negative constraint\n", 86 | "x1 = LpVariable(name='X1', lowBound=0, upBound=None, cat='Continuous')\n", 87 | "x2 = LpVariable(name='X2', lowBound=0, upBound=None, cat='Continuous')\n", 88 | "x3 = LpVariable(name='X3', lowBound=0, upBound=None, cat='Continuous')\n", 89 | "\n", 90 | "# Set objective function\n", 91 | "prob += 6*x1 + 4*x2 + 7*x3\n", 92 | "\n", 93 | "# Set constraints\n", 94 | "prob += 1.0*x1 + 0.8*x2 + 1.5*x3 <= 3000\n", 95 | "prob += 1.5*x1 + 2.0*x2 + 3.0*x3 <= 5000\n", 96 | "prob += x1 + x2 + x3 <= 3500\n", 97 | "prob += x2 >= 500\n", 98 | "\n", 99 | "# Solving problem\n", 100 | "prob.solve()\n", 101 | "print('Status: {}'.format(LpStatus[prob.status]))" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 3, 107 | "metadata": {}, 108 | "outputs": [ 109 | { 110 | "name": "stdout", 111 | "output_type": "stream", 112 | "text": [ 113 | "Z = 17600.0\n", 114 | "X1 = 2600.0\tReduced cost 0.0\n", 115 | "X2 = 500.0\tReduced cost -8.8817842e-16\n", 116 | "X3 = 0.0\tReduced cost -2.0\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "print('Z = {}'.format(value(prob.objective)))\n", 122 | "for v in prob.variables():\n", 123 | " print('{} = {}\\tReduced cost {}'.format(v.name, v.varValue, v.dj))" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": 4, 129 | "metadata": {}, 130 | "outputs": [ 131 | { 132 | "name": "stdout", 133 | "output_type": "stream", 134 | "text": [ 135 | "Sensitivity Analysis\n", 136 | "Constraint\tShadow Price\tSlack\n", 137 | "_C1: \t\t6.0\t\t-0.0\n", 138 | "_C2: \t\t-0.0\t\t100.0\n", 139 | "_C3: \t\t-0.0\t\t400.0\n", 140 | "_C4: \t\t-0.8\t\t-0.0\n" 141 | ] 142 | } 143 | ], 144 | "source": [ 145 | "print('Sensitivity Analysis\\nConstraint\\tShadow Price\\tSlack')\n", 146 | "for name, c in prob.constraints.items():\n", 147 | " print('{}: \\t\\t{}\\t\\t{}'.format(name, c.pi, c.slack))" 148 | ] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "metadata": {}, 153 | "source": [ 154 | "#### Optimization with GUROBI" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 5, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "Status: Optimal\n" 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "from gurobipy import *\n", 172 | "from ortools.utils import set_gurobi, custom_callback\n", 173 | "\n", 174 | "# Create a new model\n", 175 | "m = Model(name='Marketing')\n", 176 | "\n", 177 | "# Create variables\n", 178 | "x1 = m.addVar(lb=0, ub=1e+100, vtype=GRB.CONTINUOUS, name='X1')\n", 179 | "x2 = m.addVar(lb=0, ub=1e+100, vtype=GRB.CONTINUOUS, name='X2')\n", 180 | "x3 = m.addVar(lb=0, ub=1e+100, vtype=GRB.CONTINUOUS, name='X3')\n", 181 | "\n", 182 | "# Set objective\n", 183 | "m.setObjective(6*x1 + 4*x2 + 7*x3, sense=GRB.MAXIMIZE)\n", 184 | "\n", 185 | "# Add constraint\n", 186 | "m.addConstr(1.0*x1 + 0.8*x2 + 1.5*x3 <= 3000)\n", 187 | "m.addConstr(1.5*x1 + 2.0*x2 + 3.0*x3 <= 5000)\n", 188 | "m.addConstr(x1 + x2 + x3 <= 3500)\n", 189 | "m.addConstr(x2 >= 500)\n", 190 | "\n", 191 | "set_gurobi(m, verbose=False)\n", 192 | "\n", 193 | "# Optimize model\n", 194 | "m.optimize(custom_callback)\n", 195 | "if m.status == GRB.Status.OPTIMAL:\n", 196 | " print('Status: Optimal')" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 6, 202 | "metadata": {}, 203 | "outputs": [ 204 | { 205 | "name": "stdout", 206 | "output_type": "stream", 207 | "text": [ 208 | "Z = 17600.0\n", 209 | "X1 = 2600.0\tReduced cost 0.0\n", 210 | "X2 = 500.0\tReduced cost 0.0\n", 211 | "X3 = 0.0\tReduced cost -2.0\n" 212 | ] 213 | } 214 | ], 215 | "source": [ 216 | "print('Z = {}'.format(m.objVal))\n", 217 | "for v in m.getVars():\n", 218 | " print('{} = {}\\tReduced cost {}'.format(v.varName, v.x, v.RC))" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 7, 224 | "metadata": {}, 225 | "outputs": [ 226 | { 227 | "name": "stdout", 228 | "output_type": "stream", 229 | "text": [ 230 | "Sensitivity Analysis\n", 231 | "Constraint\tShadow Price\tSlack\n", 232 | "R0: \t\t6.0\t\t0.0\n", 233 | "R1: \t\t0.0\t\t100.0\n", 234 | "R2: \t\t0.0\t\t400.0\n", 235 | "R3: \t\t-0.8000000000000007\t\t0.0\n" 236 | ] 237 | } 238 | ], 239 | "source": [ 240 | "print('Sensitivity Analysis\\nConstraint\\tShadow Price\\tSlack')\n", 241 | "for c in m.getConstrs():\n", 242 | " print('{}: \\t\\t{}\\t\\t{}'.format(c.ConstrName, c.Pi, c.Slack))" 243 | ] 244 | } 245 | ], 246 | "metadata": { 247 | "kernelspec": { 248 | "display_name": "Python 3", 249 | "language": "python", 250 | "name": "python3" 251 | }, 252 | "language_info": { 253 | "codemirror_mode": { 254 | "name": "ipython", 255 | "version": 3 256 | }, 257 | "file_extension": ".py", 258 | "mimetype": "text/x-python", 259 | "name": "python", 260 | "nbconvert_exporter": "python", 261 | "pygments_lexer": "ipython3", 262 | "version": "3.7.3" 263 | }, 264 | "latex_envs": { 265 | "LaTeX_envs_menu_present": true, 266 | "autocomplete": true, 267 | "bibliofile": "biblio.bib", 268 | "cite_by": "apalike", 269 | "current_citInitial": 1, 270 | "eqLabelWithNumbers": true, 271 | "eqNumInitial": 1, 272 | "hotkeys": { 273 | "equation": "Ctrl-E", 274 | "itemize": "Ctrl-I" 275 | }, 276 | "labels_anchors": false, 277 | "latex_user_defs": false, 278 | "report_style_numbering": false, 279 | "user_envs_cfg": false 280 | }, 281 | "varInspector": { 282 | "cols": { 283 | "lenName": 16, 284 | "lenType": 16, 285 | "lenVar": 40 286 | }, 287 | "kernels_config": { 288 | "python": { 289 | "delete_cmd_postfix": "", 290 | "delete_cmd_prefix": "del ", 291 | "library": "var_list.py", 292 | "varRefreshCmd": "print(var_dic_list())" 293 | }, 294 | "r": { 295 | "delete_cmd_postfix": ") ", 296 | "delete_cmd_prefix": "rm(", 297 | "library": "var_list.r", 298 | "varRefreshCmd": "cat(var_dic_list()) " 299 | } 300 | }, 301 | "types_to_exclude": [ 302 | "module", 303 | "function", 304 | "builtin_function_or_method", 305 | "instance", 306 | "_Feature" 307 | ], 308 | "window_display": false 309 | } 310 | }, 311 | "nbformat": 4, 312 | "nbformat_minor": 4 313 | } 314 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/unbalanced-transportation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programmign Model\n", 8 | "\n", 9 | "### Unbalanced Transportation Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 306.**\n", 12 | "\n", 13 | "

현실의 수송문제는 총 공급량과 총 수요량이 일치하는 경우는 거의 없으며 이들이 서로 일치하지 않는 공급과잉이나 수요과잉이 발생하는 것이 일반적인 현상이다. 이와 같이 총 공급량과 총 수요량이 서로 일치하지 않는 수송문제를 불균형 수송문제라고 한다.

" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 4, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "import os\n", 23 | "import sys\n", 24 | "\n", 25 | "# Add the parent directory for importing custom library\n", 26 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "#### Optimization with PuLP" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "from pulp import *\n", 43 | "\n", 44 | "prob = LpProblem('Unbalaced Transportation Problem', LpMinimize)\n", 45 | "\n", 46 | "n_suppliers = 3\n", 47 | "n_buyers = 4\n", 48 | "\n", 49 | "costs = [\n", 50 | " [4, 5, 6, 8],\n", 51 | " [4, 7, 9, 2], \n", 52 | " [5, 8, 7, 6]\n", 53 | "]\n", 54 | "\n", 55 | "supply = [120, 150, 200]\n", 56 | "demand = [150, 60, 130, 180]\n", 57 | "\n", 58 | "routes = [(i, j) for i in range(n_suppliers) for j in range(n_buyers)]\n", 59 | "\n", 60 | "x = LpVariable.dicts('X', routes, lowBound=0)\n", 61 | "\n", 62 | "prob += lpSum([x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)])\n", 63 | "\n", 64 | "for i in range(n_suppliers):\n", 65 | " prob += lpSum([x[i, j] for j in range(n_buyers)]) == supply[i]\n", 66 | " \n", 67 | "for j in range(n_buyers):\n", 68 | " prob += lpSum([x[i, j] for i in range(n_suppliers)]) <= demand[j]\n", 69 | " \n", 70 | "# Solving problem\n", 71 | "prob.solve()\n", 72 | "print('Status', LpStatus[prob.status])\n", 73 | "\n", 74 | "print('Z = {}'.format(value(prob.objective)))\n", 75 | "for v in prob.variables():\n", 76 | " print('{} = {}'.format(v.name, v.varValue))" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "from gurobipy import *\n", 86 | "\n", 87 | "n_suppliers = 3\n", 88 | "n_buyers = 4\n", 89 | "\n", 90 | "costs = [\n", 91 | " [4, 5, 6, 8],\n", 92 | " [4, 7, 9, 2], \n", 93 | " [5, 8, 7, 6]\n", 94 | "]\n", 95 | "\n", 96 | "supply = [120, 150, 200]\n", 97 | "demand = [150, 60, 130, 180]\n", 98 | "\n", 99 | "routes = tuplelist([(i, j) for i in range(n_suppliers) for j in range(n_buyers)])\n", 100 | "\n", 101 | "m = Model('Unbalaced Transportation Problem')\n", 102 | "\n", 103 | "x = m.addVars(routes, lb=0, vtype=GRB.CONTINUOUS, name='X')\n", 104 | "\n", 105 | "m.update()\n", 106 | "\n", 107 | "m.setObjective(quicksum(x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)), GRB.MINIMIZE)\n", 108 | "\n", 109 | "for i in range(n_suppliers):\n", 110 | " m.addConstr(quicksum(x[i, j] for j in range(n_buyers)) == supply[i])\n", 111 | " \n", 112 | "for j in range(n_buyers):\n", 113 | " m.addConstr(quicksum(x[i, j] for i in range(n_suppliers)) <= demand[j])\n", 114 | "\n", 115 | "# Optimize model\n", 116 | "m.optimize()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "print('Z = {}'.format(m.objVal))\n", 126 | "for v in m.getVars():\n", 127 | " print('{} = {}'.format(v.varName, v.x))" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "metadata": {}, 133 | "source": [ 134 | "## Transportation Problem with Prohibited Transport Routes" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "import numpy as np\n", 144 | "from pulp import *\n", 145 | "\n", 146 | "prob = LpProblem('Transportation', LpMinimize)\n", 147 | "\n", 148 | "costs = [\n", 149 | " [4, 3, 2, 0],\n", 150 | " [4, 1, 1000, 6], \n", 151 | " [3, 0, 1, 2]\n", 152 | "]\n", 153 | "\n", 154 | "supply = [200, 300, 500]\n", 155 | "demand = [100, 60, 130, 300]\n", 156 | "\n", 157 | "n_suppliers = 3\n", 158 | "n_buyers = 4\n", 159 | "\n", 160 | "routes = [(i, j) for i in range(n_suppliers) for j in range(n_buyers)]\n", 161 | "\n", 162 | "x = LpVariable.dicts('x', routes, lowBound=0)\n", 163 | "\n", 164 | "prob += lpSum([x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)])\n", 165 | "\n", 166 | "for i in range(n_suppliers):\n", 167 | " prob += lpSum([x[i, j] for j in range(n_buyers)]) <= supply[i]\n", 168 | " \n", 169 | "for j in range(n_buyers):\n", 170 | " prob += lpSum([x[i, j] for i in range(n_suppliers)]) == demand[j]\n", 171 | " \n", 172 | "# Solving problem\n", 173 | "prob.solve()\n", 174 | "print('Status', LpStatus[prob.status])\n", 175 | "\n", 176 | "print('Z = {}'.format(value(prob.objective)))\n", 177 | "for v in prob.variables():\n", 178 | " print('{} = {}'.format(v.name, v.varValue))" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": null, 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "## Transshipment Problem" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": null, 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "prob = LpProblem(\"Transportation\", LpMinimize)\n", 209 | "\n", 210 | "S = [\"A\", \"B\", \"C\"]\n", 211 | "D = [\"1\", \"2\", \"3\", \"4\"]\n", 212 | "costs = [[21, 12, 25, 11],\n", 213 | " [13, 10, 21, 17],\n", 214 | " [12, 14, 19, 12]]\n", 215 | "\n", 216 | "supply = dict(zip(S, [80, 60, 50]))\n", 217 | "demand = dict(zip(D, [50, 40, 70, 30]))\n", 218 | "demand\n", 219 | "\n", 220 | "opt_costs = makeDict([S, D], costs, 0)\n", 221 | "opt_costs\n", 222 | "\n", 223 | "x = LpVariable.dicts('Route', (S, D), lowBound=0)\n", 224 | "x\n", 225 | "\n", 226 | "routes = [(i, j) for i in S for j in D]\n", 227 | "routes\n", 228 | "\n", 229 | "prob += lpSum([x[s][d]*opt_costs[s][d] for (s, d) in routes]) # objective function\n", 230 | "\n", 231 | "# constraints\n", 232 | "for i in S:\n", 233 | " prob += lpSum([x[i][j] for j in D]) == supply[i]\n", 234 | "\n", 235 | "for j in D:\n", 236 | " prob += lpSum([x[i][j] for i in S]) == demand[j]\n", 237 | " \n", 238 | "prob.solve()\n", 239 | "\n", 240 | "for i in prob.variables():\n", 241 | " print(i.name, \"=\", i.varValue)\n", 242 | "print('Status', LpStatus[prob.status])\n", 243 | "print(pulp.value(prob.objective))" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [] 259 | }, 260 | { 261 | "cell_type": "code", 262 | "execution_count": null, 263 | "metadata": {}, 264 | "outputs": [], 265 | "source": [ 266 | "prob = LpProblem(\"Unbalanced Transportation\", LpMinimize)\n", 267 | "\n", 268 | "S = [\"A\", \"B\", \"C\"]\n", 269 | "D = [\"1\", \"2\", \"3\", \"4\"]\n", 270 | "costs = [[21, 12, 25, 11],\n", 271 | " [13, 10, 21, 17],\n", 272 | " [12, 14, 19, 12]]\n", 273 | "\n", 274 | "supply = dict(zip(S, [80, 60, 50]))\n", 275 | "demand = dict(zip(D, [60, 50, 80, 40]))\n", 276 | "\n", 277 | "opt_costs = makeDict([S, D], costs, 0)\n", 278 | "\n", 279 | "x = LpVariable.dicts('Route', (S, D), lowBound=0)\n", 280 | "x\n", 281 | "\n", 282 | "routes = [(i, j) for i in S for j in D]\n", 283 | "\n", 284 | "prob += lpSum([x[s][d]*opt_costs[s][d] for (s, d) in routes]) # objective function\n", 285 | "\n", 286 | "# constraints\n", 287 | "for i in S:\n", 288 | " prob += lpSum([x[i][j] for j in D]) == supply[i]\n", 289 | "\n", 290 | "for j in D:\n", 291 | " prob += lpSum([x[i][j] for i in S]) <= demand[j]\n", 292 | " \n", 293 | "prob.solve()\n", 294 | "\n", 295 | "for i in prob.variables():\n", 296 | " print(i.name, \"=\", i.varValue)\n", 297 | "print('Status', LpStatus[prob.status])\n", 298 | "print(pulp.value(prob.objective))" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "metadata": {}, 305 | "outputs": [], 306 | "source": [] 307 | } 308 | ], 309 | "metadata": { 310 | "kernelspec": { 311 | "display_name": "Python 3", 312 | "language": "python", 313 | "name": "python3" 314 | }, 315 | "language_info": { 316 | "codemirror_mode": { 317 | "name": "ipython", 318 | "version": 3 319 | }, 320 | "file_extension": ".py", 321 | "mimetype": "text/x-python", 322 | "name": "python", 323 | "nbconvert_exporter": "python", 324 | "pygments_lexer": "ipython3", 325 | "version": "3.7.3" 326 | }, 327 | "latex_envs": { 328 | "LaTeX_envs_menu_present": true, 329 | "autocomplete": true, 330 | "bibliofile": "biblio.bib", 331 | "cite_by": "apalike", 332 | "current_citInitial": 1, 333 | "eqLabelWithNumbers": true, 334 | "eqNumInitial": 1, 335 | "hotkeys": { 336 | "equation": "Ctrl-E", 337 | "itemize": "Ctrl-I" 338 | }, 339 | "labels_anchors": false, 340 | "latex_user_defs": false, 341 | "report_style_numbering": false, 342 | "user_envs_cfg": false 343 | }, 344 | "varInspector": { 345 | "cols": { 346 | "lenName": 16, 347 | "lenType": 16, 348 | "lenVar": 40 349 | }, 350 | "kernels_config": { 351 | "python": { 352 | "delete_cmd_postfix": "", 353 | "delete_cmd_prefix": "del ", 354 | "library": "var_list.py", 355 | "varRefreshCmd": "print(var_dic_list())" 356 | }, 357 | "r": { 358 | "delete_cmd_postfix": ") ", 359 | "delete_cmd_prefix": "rm(", 360 | "library": "var_list.r", 361 | "varRefreshCmd": "cat(var_dic_list()) " 362 | } 363 | }, 364 | "types_to_exclude": [ 365 | "module", 366 | "function", 367 | "builtin_function_or_method", 368 | "instance", 369 | "_Feature" 370 | ], 371 | "window_display": false 372 | } 373 | }, 374 | "nbformat": 4, 375 | "nbformat_minor": 4 376 | } 377 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/traveling-salesman.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Integer Programming\n", 8 | "\n", 9 | "### Travelling Salesman Problem\n", 10 | "\n", 11 | "

외판원 문제(travelling salesman problem, TSP)에 대한 자세한 내용은 블로그에서 참고해보시기 바랍니다.

\n", 12 | "\n", 13 | "**이강우 & 김정자. (2012). EXCEL 2010 경영과학. 한경사, 390.**\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | "
Table 1. Example
1(Yard)2(A)3(B)4(C)5(D)
1(Yard)-4.04.04.75.0
2(A)0.0-1.32.51.7
3(B)0.01.3-1.02.0
4(C)0.02.51.0-2.0
5(D)0.01.71.42.0-
\n", 66 | "\n", 67 | "\n", 68 | "

외판원 문제를 정식화하면 $n$개의 도시가 있을 때 인덱스 $i$와 $j$는 $n$개 만큼있으며 파리미터는 거리 $c_{ij}$, 의사결정변수로 $x_{ij}$와 $u_{i}$가 있으며 Miller-Tucker-Zemlin (MTZ) 공식은 다음과 같습니다.

\n", 69 | "\n", 70 | "$$x_{ij} = \n", 71 | "\\begin{cases}\n", 72 | " 1, \\; \\text{if the edge $(i,j)$ is included in the Hamilton cycle}\\\\\n", 73 | " 0, \\; \\text{otherwise}\n", 74 | "\\end{cases}$$\n", 75 | "\n", 76 | "$$u_{i} = \\text{order city $i$ is visited}$$\n", 77 | "\n", 78 | "

위의 의사결정변수와 파라미터를 이용하여 총 거리의 합을 최소화하는 정수 계획법은 다음과 같습니다.

\n", 79 | "\n", 80 | "$$\\begin{align*}\n", 81 | " & \\text{minimize } & & \\sum_{i=1}^{n} \\sum_{j=1}^{n} c_{ij}x_{ij} \\\\[1ex]\n", 82 | " & \\text{subject to } & \\, & \\sum_{i=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex]\n", 83 | " & & \\, & \\sum_{j=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex] \n", 84 | " & & \\, & u_{i} - u_{j} \\le N(1-x_{ij})-1 & \\quad & \\forall i = 2, \\dots, n, j=2, \\dots, n\\\\[1ex] \n", 85 | "\\end{align*}$$\n", 86 | "\n" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 27, 92 | "metadata": {}, 93 | "outputs": [], 94 | "source": [ 95 | "import os\n", 96 | "import sys\n", 97 | "\n", 98 | "# Add the parent directory for importing custom library\n", 99 | "sys.path.append('../')" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 28, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "name": "stdout", 109 | "output_type": "stream", 110 | "text": [ 111 | "7.7\n", 112 | "x_(0,_1) = 1.0\n", 113 | "x_(1,_4) = 1.0\n", 114 | "x_(2,_3) = 1.0\n", 115 | "x_(3,_2) = 1.0\n", 116 | "x_(4,_0) = 1.0\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "from pulp import *\n", 122 | "from ortools.utils import output\n", 123 | "\n", 124 | "n = 5\n", 125 | "\n", 126 | "costs = [\n", 127 | " [100, 4.0, 4.0, 4.7, 5.0],\n", 128 | " [0.0, 1000, 1.3, 2.5, 1.7],\n", 129 | " [0.0, 1.3, 100, 1.0, 1.4],\n", 130 | " [0.0, 2.5, 1.0, 100, 2.0],\n", 131 | " [0.0, 1.7, 1.4, 2.0, 100],\n", 132 | "]\n", 133 | "\n", 134 | "prob = LpProblem('Traveling Salesman Problem', LpMinimize)\n", 135 | "\n", 136 | "indexs = [(i, j) for i in range(n) for j in range(n)]\n", 137 | "\n", 138 | "x = LpVariable.dicts('x', indexs, lowBound=0, cat='Binary')\n", 139 | "\n", 140 | "prob += lpSum([costs[i][j]*x[i, j] for i, j in indexs])\n", 141 | "\n", 142 | "for j in range(n):\n", 143 | " prob += lpSum([x[i, j] for i in range(n)]) == 1\n", 144 | " \n", 145 | "for i in range(n):\n", 146 | " prob += lpSum([x[i, j] for j in range(n)]) == 1\n", 147 | " \n", 148 | "prob.solve()\n", 149 | "print(value(prob.objective))\n", 150 | "\n", 151 | "for i in prob.variables():\n", 152 | " if i.varValue == 1:\n", 153 | " print(i.name, '=', i.varValue)" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": null, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 12, 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "ename": "NameError", 184 | "evalue": "name 'cities' is not defined", 185 | "output_type": "error", 186 | "traceback": [ 187 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 188 | "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", 189 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprob\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mLpProblem\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Travelling Salesman'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mLpMinimize\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcities\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0mindexs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mj\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mi\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mj\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 190 | "\u001b[0;31mNameError\u001b[0m: name 'cities' is not defined" 191 | ] 192 | } 193 | ], 194 | "source": [ 195 | "from pulp import *\n", 196 | "\n", 197 | "# Initialize travelling salesman problem\n", 198 | "prob = LpProblem('Travelling Salesman', LpMinimize)\n", 199 | "\n", 200 | "n = len(cities)\n", 201 | "indexs = [(i, j) for i in range(n) for j in range(n) if i != j]\n", 202 | "\n", 203 | "# Creating decision variables\n", 204 | "x = LpVariable.dicts('x', indexs, cat='Binary')\n", 205 | "u = LpVariable.dicts('u', list(range(n)), lowBound=0, upBound=n-1, cat='Continuous')\n", 206 | "\n", 207 | "# Objective function\n", 208 | "prob += lpSum([cities[i][j] * x[(i,j)] for i, j in indexs])\n", 209 | "\n", 210 | "# Constraints\n", 211 | "for i in range(n):\n", 212 | " prob += lpSum([x[(i,j)] for j in range(n) if i != j]) == 1\n", 213 | " \n", 214 | "for j in range(n):\n", 215 | " prob += lpSum([x[(i,j)] for i in range(n) if i != j]) == 1\n", 216 | " \n", 217 | "for i in range(1, n):\n", 218 | " for j in range(1, n):\n", 219 | " if i != j:\n", 220 | " prob += u[i] - u[j] + n * x[(i,j)] <= n - 1\n", 221 | "\n", 222 | "# Solve problem\n", 223 | "prob.solve()\n", 224 | "print(value(prob.objective))\n", 225 | "\n", 226 | "for i in prob.variables():\n", 227 | " if i.name[0] == 'u':\n", 228 | " print(i.name, '=', i.varValue)\n", 229 | " elif i.varValue != 0:\n", 230 | " print(i.name, '=', i.varValue)" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": null, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "\n", 245 | "$$x_{ij} = \n", 246 | "\\begin{cases}\n", 247 | " 1, \\; \\text{if the edge $(i,j)$ is included in the Hamilton cycle}\\\\\n", 248 | " 0, \\; \\text{otherwise}\n", 249 | "\\end{cases}$$\n", 250 | "\n", 251 | "$$u_{i} = \\text{order city $i$ is visited}$$\n", 252 | "\n", 253 | "

위의 의사결정변수와 파라미터를 이용하여 총 거리의 합을 최소화하는 정수 계획법은 다음과 같습니다.

\n", 254 | "\n", 255 | "\n", 256 | "\n", 257 | "$$\\begin{align*}\n", 258 | " & \\text{minimize } & & \\sum_{i=1}^{n} \\sum_{j=1}^{n} c_{ij}x_{ij} \\\\[1ex]\n", 259 | " & \\text{subject to } & \\, & \\sum_{i=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex]\n", 260 | " & & \\, & \\sum_{j=1}^{n} x_{ij} = 1, & \\quad & \\forall j = 1, \\dots, n\\\\[1ex] \n", 261 | " & & \\, & u_{i} - u_{j} \\le N(1-x_{ij})-1 & \\quad & \\forall i = 2, \\dots, n, j=2, \\dots, n\\\\[1ex] \n", 262 | "\\end{align*}$$\n", 263 | "\n", 264 | "

목적함수는 식(1)과 같습니다. 제약식은 다음과 같습니다. 제약식(2)과 (3)은 각 도시 $j$로 들어오는 도시는 1개가 되어야만 하고 각 도시 $i$에서 출발하는 도시는 1개가 되어야 한다는 걸 나타냅니다. 제약식(3)은 부등식 제약조건(inequality constraints)으로 MTZ 공식입니다. $x_{ij}=1$일 때, $(i,j)$에 대한 두 도시 사이는 순서는 $u_{j} \\ge u_{i}$를 뜻합니다. 제약식(4)와 (5)는 의사결정변수 $x_{ij}$는 $0$과 $1$ 값만 가질 수 있는 이진변수이고, $u_{i}$는 $0$보다 크거나 같고 $n-1$보다 작거나 같은 값을 가질 수 있는 변수를 나타냅니다.

\n" 265 | ] 266 | } 267 | ], 268 | "metadata": { 269 | "kernelspec": { 270 | "display_name": "Python 3", 271 | "language": "python", 272 | "name": "python3" 273 | }, 274 | "language_info": { 275 | "codemirror_mode": { 276 | "name": "ipython", 277 | "version": 3 278 | }, 279 | "file_extension": ".py", 280 | "mimetype": "text/x-python", 281 | "name": "python", 282 | "nbconvert_exporter": "python", 283 | "pygments_lexer": "ipython3", 284 | "version": "3.7.3" 285 | } 286 | }, 287 | "nbformat": 4, 288 | "nbformat_minor": 4 289 | } 290 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/workforce-scheduling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programming\n", 8 | "\n", 9 | "### Workforce Scheduling\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 233.**\n", 12 | "\n", 13 | "

K항공에서는 최근 여행객의 증가로 인천국제공항에 기항하거나 출항하는 비행기를 증편시키려고 하며 이에 따라서 추가로 서비스 직원을 고용하려고 한다. Table 1은 새로 증편된 운항계획에 따라서 각 교대조별 근무시간, 시간대별 필요한 직원의 소요인원 및 교대조별 1명당 1일 노무비용을 조사한 결과이다. 한편 K항공의 서비스 직원은 1일 8시간 단위로 근무하며 각 교대조의 근무시간은 다음과 같다.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | "
Table 1. 교대조별 근무시간대와 근무시간대별 소요인원
근무시간대교대조1교대조2교대조3교대조4교대조5소요인원(명)
06:00 ~ 08:0048
08:00 ~ 10:0079
10:00 ~ 12:0065
12:00 ~ 14:0087
14:00 ~ 16:0064
16:00 ~ 18:0073
18:00 ~ 20:0082
20:00 ~ 22:0043
22:00 ~ 24:0052
24:00 ~ 06:0015
1인당 임금(만원)767.589.5
" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 1, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "import os\n", 135 | "import sys\n", 136 | "\n", 137 | "# Add the parent directory for importing custom library\n", 138 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "#### Optimization with PuLP" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 2, 151 | "metadata": {}, 152 | "outputs": [ 153 | { 154 | "name": "stdout", 155 | "output_type": "stream", 156 | "text": [ 157 | "Status: Optimal\n", 158 | "Objective value: 1301.0\n", 159 | "\n", 160 | "Variables Values\n", 161 | "----------- --------\n", 162 | "X1 48\n", 163 | "X2 31\n", 164 | "X3 39\n", 165 | "X4 43\n", 166 | "X5 15\n", 167 | "\n", 168 | "Statistics:\n", 169 | "- Number of variables: 5\n", 170 | "- Number of constraints: 10\n", 171 | "- Solve time: 0.018s\n" 172 | ] 173 | } 174 | ], 175 | "source": [ 176 | "from pulp import *\n", 177 | "from ortools.utils import output\n", 178 | "\n", 179 | "# Define problem\n", 180 | "prob = LpProblem(name='Workforce Scheduling', sense=LpMinimize)\n", 181 | "\n", 182 | "# Create decision variables and non-negative constraint\n", 183 | "x1 = LpVariable(name='X1', lowBound=0, cat='Continuous')\n", 184 | "x2 = LpVariable(name='X2', lowBound=0, cat='Continuous')\n", 185 | "x3 = LpVariable(name='X3', lowBound=0, cat='Continuous')\n", 186 | "x4 = LpVariable(name='X4', lowBound=0, cat='Continuous')\n", 187 | "x5 = LpVariable(name='X5', lowBound=0, cat='Continuous')\n", 188 | "\n", 189 | "# Set objective function\n", 190 | "prob += 7*x1 + 6*x2 + 7.5*x3 + 8*x4 + 9.5*x5\n", 191 | "\n", 192 | "# Set constraints\n", 193 | "prob += x1 >= 48\n", 194 | "prob += x1 + x2 >= 79\n", 195 | "prob += x1 + x2 >= 65\n", 196 | "prob += x1 + x2 + x3 >= 87\n", 197 | "prob += x2 + x3 >= 64\n", 198 | "prob += x3 + x4 >= 73\n", 199 | "prob += x3 + x4 >= 82\n", 200 | "prob += x4 >= 43\n", 201 | "prob += x4 + x5 >= 52\n", 202 | "prob += x5 >= 15\n", 203 | "\n", 204 | "# Solving problem\n", 205 | "prob.solve()\n", 206 | "output(prob, sensitivity=False)" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 3, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "name": "stdout", 216 | "output_type": "stream", 217 | "text": [ 218 | "Status: Optimal\n", 219 | "Objective value: 1301.0\n", 220 | "\n", 221 | "Variables Values\n", 222 | "----------- --------\n", 223 | "X0 48\n", 224 | "X1 31\n", 225 | "X2 39\n", 226 | "X3 43\n", 227 | "X4 15\n", 228 | "\n", 229 | "Statistics:\n", 230 | "- Number of variables: 5\n", 231 | "- Number of constraints: 10\n", 232 | "- Solve time: 0.017s\n" 233 | ] 234 | } 235 | ], 236 | "source": [ 237 | "from pulp import *\n", 238 | "from ortools.utils import output\n", 239 | "\n", 240 | "n_shifts = 5\n", 241 | "costs = [7, 6, 7.5, 8, 9.5]\n", 242 | "needs = [48, 79, 65, 87, 64, 73, 82, 43, 52, 15]\n", 243 | "\n", 244 | "table = [\n", 245 | " [1, 0, 0, 0, 0],\n", 246 | " [1, 1, 0, 0, 0],\n", 247 | " [1, 1, 0, 0, 0],\n", 248 | " [1, 1, 1, 0, 0],\n", 249 | " [0, 1, 1, 0, 0],\n", 250 | " [0, 0, 1, 1, 0],\n", 251 | " [0, 0, 1, 1, 0],\n", 252 | " [0, 0, 0, 1, 0],\n", 253 | " [0, 0, 0, 1, 1],\n", 254 | " [0, 0, 0, 0, 1]\n", 255 | "]\n", 256 | "\n", 257 | "# Define problem\n", 258 | "prob = LpProblem(name='Workforce Scheduling', sense=LpMinimize)\n", 259 | "\n", 260 | "# Create decision variables and non-negative constraint\n", 261 | "x = [LpVariable('X{}'.format(i), lowBound=0) for i in range(n_shifts)]\n", 262 | "\n", 263 | "# Set objective function\n", 264 | "prob += lpSum([costs[i]*x[i] for i in range(n_shifts)])\n", 265 | "\n", 266 | "for i in range(len(table)):\n", 267 | " prob += lpSum([x[j] for j in range(n_shifts) if table[i][j] == 1]) >= needs[i]\n", 268 | " \n", 269 | "# Solving problem\n", 270 | "prob.solve()\n", 271 | "output(prob, sensitivity=False)" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "#### Optimization with GUROBI" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": 4, 284 | "metadata": {}, 285 | "outputs": [ 286 | { 287 | "name": "stdout", 288 | "output_type": "stream", 289 | "text": [ 290 | "Status: Optimal\n", 291 | "Objective value: 1301.0\n", 292 | "\n", 293 | "Variables Values\n", 294 | "----------- --------\n", 295 | "X0 48\n", 296 | "X1 31\n", 297 | "X2 39\n", 298 | "X3 43\n", 299 | "X4 15\n", 300 | "\n", 301 | "Statistics:\n", 302 | "- Number of variables: 5\n", 303 | "- Number of constraints: 10\n", 304 | "- Solve time: 0.000s\n" 305 | ] 306 | } 307 | ], 308 | "source": [ 309 | "from gurobipy import *\n", 310 | "from ortools.utils import set_gurobi, custom_callback, output\n", 311 | "\n", 312 | "n_shifts = 5\n", 313 | "costs = [7, 6, 7.5, 8, 9.5]\n", 314 | "needs = [48, 79, 65, 87, 64, 73, 82, 43, 52, 15]\n", 315 | "\n", 316 | "table = [\n", 317 | " [1, 0, 0, 0, 0],\n", 318 | " [1, 1, 0, 0, 0],\n", 319 | " [1, 1, 0, 0, 0],\n", 320 | " [1, 1, 1, 0, 0],\n", 321 | " [0, 1, 1, 0, 0],\n", 322 | " [0, 0, 1, 1, 0],\n", 323 | " [0, 0, 1, 1, 0],\n", 324 | " [0, 0, 0, 1, 0],\n", 325 | " [0, 0, 0, 1, 1],\n", 326 | " [0, 0, 0, 0, 1]\n", 327 | "]\n", 328 | "\n", 329 | "m = Model('Workforce Scheduling')\n", 330 | "\n", 331 | "x = [m.addVar(vtype=GRB.CONTINUOUS, name='X{}'.format(i)) for i in range(n_shifts)]\n", 332 | "\n", 333 | "m.update()\n", 334 | "\n", 335 | "m.setObjective(quicksum(costs[i]*x[i] for i in range(n_shifts)), GRB.MINIMIZE)\n", 336 | "\n", 337 | "for i in range(len(table)):\n", 338 | " m.addConstr(quicksum(x[j] for j in range(n_shifts) if table[i][j] == 1) >= needs[i])\n", 339 | "\n", 340 | "set_gurobi(m, verbose=False)\n", 341 | "\n", 342 | "# Optimize model\n", 343 | "m.optimize(custom_callback)\n", 344 | "output(m)" 345 | ] 346 | } 347 | ], 348 | "metadata": { 349 | "kernelspec": { 350 | "display_name": "Python 3", 351 | "language": "python", 352 | "name": "python3" 353 | }, 354 | "language_info": { 355 | "codemirror_mode": { 356 | "name": "ipython", 357 | "version": 3 358 | }, 359 | "file_extension": ".py", 360 | "mimetype": "text/x-python", 361 | "name": "python", 362 | "nbconvert_exporter": "python", 363 | "pygments_lexer": "ipython3", 364 | "version": "3.7.3" 365 | } 366 | }, 367 | "nbformat": 4, 368 | "nbformat_minor": 4 369 | } 370 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/production-planning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programming\n", 8 | "\n", 9 | "### Production Planning Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 217.**\n", 12 | "\n", 13 | "

D부품회사는 자동차 부품을 생산하여 A자동차회사에 납품하고 있는 회사이다. A자동차회사는 분기별로 다음 분기에 사용할 부품 1과 부품 2를 D부품회사에 주문하고 있다. D부품회사는가 A자동차회사로부터 받은 이번 분기의 부품 1과 부품 2의 월별 주문수량은 Table 1과 같다.

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | "
Table 1. Order quantity
PartsJanuaryFebruaryMarch
Part 1300025003000
Part 2250020002000
\n", 36 | "\n", 37 | "

현재 D부품회사에서 보유하고 있는 부품 1과 부품 2의 재고는 두 부품 모두 500개씩이고 D부품회사는 3월말에도 두 부품 모두 500개 이상의 재고를 보유하려고 한다. 한편 D부품회사의 재고보관능력은 부품의 종류에 상관없이 월 2,000개이다. 그리고 부품 1과 부품 2의 1단위 생산에 소요되는 기계가공시간과 노동시간은 Table 2와 같으며 생산자원인 기계가공시간과 노동시간의 월별 가용시간은 Table 3과 같다.

\n", 38 | "\n", 39 | "\n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | "
Table 2. Production time of parts per unit
PartsMachining times per unitLabor hours per unit
Part 10.20.05
Part 20.10.04
\n", 57 | "\n", 58 | "\n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | "
Table 3. Monthly availability of production resources
MonthAvailability of machining timesAvailability of labor hours
January1000300
February1000300
March1000300
\n", 81 | "\n", 82 | "

한편 부품 1과 부품 2의 총 생산비용은 생산비용, 재고유지비용, 생산량 변동에 따른 비용으로 구성되어 있으며 부품 1과 부품 2의 단위당 생산비용과 단위당 월간 재고유지비용은 Table 4와 같다.

\n", 83 | "\n", 84 | "\n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | "
Table 4. Monthly production cost and inventory holding per unit per unit by part
Costs by partJanuaryFebruaryMarch
Part 1Production cost per unit303530
Inventory holding cost per unit0.30.30.3
Part 2Production cost per unit202520
Inventory holding cost per unit0.150.150.15
\n", 119 | "\n", 120 | "

또한 월별 총 생산량의 변동에 따른 비용은 부품 1과 부품 2의 월 생산량의 합이 직전 월의 생산량에 비해 1개 증가할 경우에는 500원, 1개 감소할 경우에는 200원의 비용이 발생한다고 한다. 전년도 12월에는 부품 1과 부품 2를 5,000개 생산하였다고 한다. D부품회사의 총 생산비용을 최소로 하는 1월, 2월 3월의 생산계획을 수립하라.

" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 1, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "import os\n", 130 | "import sys\n", 131 | "\n", 132 | "# Add the parent directory for importing custom library\n", 133 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "#### Optimization with PuLP\n", 141 | "\n", 142 | "

각 의사결정변수 및 매개변수를 선언할 때, stringdictionary를 사용하면 전체적인 분위기가 다음과 같아진다. 다음은 기말재고량을 구하는 공식을 표현한 것이다. 두 개의 코드를 보고 앞으로 수리적 모형을 파이썬으로 구현할 때 참고하도록 하자. 본인은 후자를 좋아한다.

" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 7, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "from pulp import *\n", 152 | "\n", 153 | "parts = ['P1', 'P2']\n", 154 | "month = ['Jan', 'Feb', 'Mar']\n", 155 | "\n", 156 | "demand = {'P1': [3000, 2500, 3000], \n", 157 | " 'P2': [2500, 2000, 2000]}\n", 158 | "\n", 159 | "prob = LpProblem('Production Planning', LpMinimize)\n", 160 | "\n", 161 | "indexs = [(p, m) for p in parts for m in month]\n", 162 | "\n", 163 | "X = LpVariable.dicts('X', indexs, lowBound=0)\n", 164 | "Y = LpVariable.dicts('Y', indexs, lowBound=0)\n", 165 | "\n", 166 | "# Inital inventory level\n", 167 | "for p in parts:\n", 168 | " prob += X[p, 'Jan'] - Y[p, 'Jan'] == demand[p][month.index('Jan')] - 500\n", 169 | "\n", 170 | "# Expected ending inventory level\n", 171 | "for p in parts:\n", 172 | " for m in month:\n", 173 | " if m != 'Jan':\n", 174 | " prob += X[p, m] + Y[p, month[month.index(m)-1]] - Y[p, m] == demand[p][month.index(m)]" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 3, 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "data": { 184 | "text/plain": [ 185 | "Production Planning:\n", 186 | "MINIMIZE\n", 187 | "None\n", 188 | "SUBJECT TO\n", 189 | "_C1: X_('P1',_'Jan') - Y_('P1',_'Jan') = 2500\n", 190 | "\n", 191 | "_C2: X_('P2',_'Jan') - Y_('P2',_'Jan') = 2000\n", 192 | "\n", 193 | "_C3: X_('P1',_'Feb') - Y_('P1',_'Feb') + Y_('P1',_'Jan') = 2500\n", 194 | "\n", 195 | "_C4: X_('P1',_'Mar') + Y_('P1',_'Feb') - Y_('P1',_'Mar') = 3000\n", 196 | "\n", 197 | "_C5: X_('P2',_'Feb') - Y_('P2',_'Feb') + Y_('P2',_'Jan') = 2000\n", 198 | "\n", 199 | "_C6: X_('P2',_'Mar') + Y_('P2',_'Feb') - Y_('P2',_'Mar') = 2000\n", 200 | "\n", 201 | "VARIABLES\n", 202 | "X_('P1',_'Feb') Continuous\n", 203 | "X_('P1',_'Jan') Continuous\n", 204 | "X_('P1',_'Mar') Continuous\n", 205 | "X_('P2',_'Feb') Continuous\n", 206 | "X_('P2',_'Jan') Continuous\n", 207 | "X_('P2',_'Mar') Continuous\n", 208 | "Y_('P1',_'Feb') Continuous\n", 209 | "Y_('P1',_'Jan') Continuous\n", 210 | "Y_('P1',_'Mar') Continuous\n", 211 | "Y_('P2',_'Feb') Continuous\n", 212 | "Y_('P2',_'Jan') Continuous\n", 213 | "Y_('P2',_'Mar') Continuous" 214 | ] 215 | }, 216 | "execution_count": 3, 217 | "metadata": {}, 218 | "output_type": "execute_result" 219 | } 220 | ], 221 | "source": [ 222 | "prob" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": 4, 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "from pulp import *\n", 232 | "\n", 233 | "parts = 2\n", 234 | "month = 3\n", 235 | "\n", 236 | "demand = [[3000, 2500, 3000], \n", 237 | " [2500, 2000, 2000]]\n", 238 | "\n", 239 | "prob = LpProblem('Production Planning', LpMinimize)\n", 240 | "\n", 241 | "indexs = [(i, j) for i in range(parts) for j in range(month)]\n", 242 | "\n", 243 | "X = LpVariable.dicts('X', indexs, lowBound=0)\n", 244 | "Y = LpVariable.dicts('Y', indexs, lowBound=0)\n", 245 | "\n", 246 | "# Inital inventory level\n", 247 | "for i in range(parts):\n", 248 | " prob += X[i, 0] - Y[i, 0] == demand[i][0] - 500\n", 249 | "\n", 250 | "# Expected ending inventory level\n", 251 | "for i in range(parts):\n", 252 | " for j in range(1, month):\n", 253 | " prob += X[i, j] + Y[i, j-1] - Y[i, j] == demand[i][j]" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 5, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "data": { 263 | "text/plain": [ 264 | "Production Planning:\n", 265 | "MINIMIZE\n", 266 | "None\n", 267 | "SUBJECT TO\n", 268 | "_C1: X_(0,_0) - Y_(0,_0) = 2500\n", 269 | "\n", 270 | "_C2: X_(1,_0) - Y_(1,_0) = 2000\n", 271 | "\n", 272 | "_C3: X_(0,_1) + Y_(0,_0) - Y_(0,_1) = 2500\n", 273 | "\n", 274 | "_C4: X_(0,_2) + Y_(0,_1) - Y_(0,_2) = 3000\n", 275 | "\n", 276 | "_C5: X_(1,_1) + Y_(1,_0) - Y_(1,_1) = 2000\n", 277 | "\n", 278 | "_C6: X_(1,_2) + Y_(1,_1) - Y_(1,_2) = 2000\n", 279 | "\n", 280 | "VARIABLES\n", 281 | "X_(0,_0) Continuous\n", 282 | "X_(0,_1) Continuous\n", 283 | "X_(0,_2) Continuous\n", 284 | "X_(1,_0) Continuous\n", 285 | "X_(1,_1) Continuous\n", 286 | "X_(1,_2) Continuous\n", 287 | "Y_(0,_0) Continuous\n", 288 | "Y_(0,_1) Continuous\n", 289 | "Y_(0,_2) Continuous\n", 290 | "Y_(1,_0) Continuous\n", 291 | "Y_(1,_1) Continuous\n", 292 | "Y_(1,_2) Continuous" 293 | ] 294 | }, 295 | "execution_count": 5, 296 | "metadata": {}, 297 | "output_type": "execute_result" 298 | } 299 | ], 300 | "source": [ 301 | "prob" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 6, 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "from pulp import *\n", 311 | "\n", 312 | "parts = 2\n", 313 | "month = 3\n", 314 | "\n", 315 | "demand = [[3000, 2500, 3000], \n", 316 | " [2500, 2000, 2000]]\n", 317 | "\n", 318 | "prod = [0.2, 0.1]\n", 319 | "labo = [0.05, 0.04]\n", 320 | "\n", 321 | "avail_prod = [1000, 1000, 1000]\n", 322 | "avail_labo = [300, 300, 300]\n", 323 | "\n", 324 | "p1_prod = [30, 35, 30]\n", 325 | "p1_inve = [0.3, 0.3, 0.3]\n", 326 | "\n", 327 | "prob = LpProblem('Production Planning', LpMinimize)\n", 328 | "\n", 329 | "indexs = [(i, j) for i in range(parts) for j in range(month)]\n", 330 | "\n", 331 | "X = LpVariable.dicts('X', indexs, lowBound=0)\n", 332 | "Y = LpVariable.dicts('Y', indexs, lowBound=0)\n", 333 | "\n", 334 | "# Inital inventory level\n", 335 | "for i in range(parts):\n", 336 | " prob += X[i, 0] - Y[i, 0] == demand[i][0] - 500\n", 337 | "\n", 338 | "# Expected ending inventory level\n", 339 | "for i in range(parts):\n", 340 | " for j in range(1, month):\n", 341 | " prob += X[i, j] + Y[i, j-1] - Y[i, j] == demand[i][j]" 342 | ] 343 | }, 344 | { 345 | "cell_type": "code", 346 | "execution_count": null, 347 | "metadata": {}, 348 | "outputs": [], 349 | "source": [] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": null, 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [] 357 | } 358 | ], 359 | "metadata": { 360 | "kernelspec": { 361 | "display_name": "Python 3", 362 | "language": "python", 363 | "name": "python3" 364 | }, 365 | "language_info": { 366 | "codemirror_mode": { 367 | "name": "ipython", 368 | "version": 3 369 | }, 370 | "file_extension": ".py", 371 | "mimetype": "text/x-python", 372 | "name": "python", 373 | "nbconvert_exporter": "python", 374 | "pygments_lexer": "ipython3", 375 | "version": "3.7.3" 376 | }, 377 | "latex_envs": { 378 | "LaTeX_envs_menu_present": true, 379 | "autocomplete": true, 380 | "bibliofile": "biblio.bib", 381 | "cite_by": "apalike", 382 | "current_citInitial": 1, 383 | "eqLabelWithNumbers": true, 384 | "eqNumInitial": 1, 385 | "hotkeys": { 386 | "equation": "Ctrl-E", 387 | "itemize": "Ctrl-I" 388 | }, 389 | "labels_anchors": false, 390 | "latex_user_defs": false, 391 | "report_style_numbering": false, 392 | "user_envs_cfg": false 393 | }, 394 | "varInspector": { 395 | "cols": { 396 | "lenName": 16, 397 | "lenType": 16, 398 | "lenVar": 40 399 | }, 400 | "kernels_config": { 401 | "python": { 402 | "delete_cmd_postfix": "", 403 | "delete_cmd_prefix": "del ", 404 | "library": "var_list.py", 405 | "varRefreshCmd": "print(var_dic_list())" 406 | }, 407 | "r": { 408 | "delete_cmd_postfix": ") ", 409 | "delete_cmd_prefix": "rm(", 410 | "library": "var_list.r", 411 | "varRefreshCmd": "cat(var_dic_list()) " 412 | } 413 | }, 414 | "types_to_exclude": [ 415 | "module", 416 | "function", 417 | "builtin_function_or_method", 418 | "instance", 419 | "_Feature" 420 | ], 421 | "window_display": false 422 | } 423 | }, 424 | "nbformat": 4, 425 | "nbformat_minor": 4 426 | } 427 | -------------------------------------------------------------------------------- /or-tutorial/linear-programming/transportation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Linear Programming\n", 8 | "\n", 9 | "### Transportation Problem\n", 10 | "\n", 11 | "수송계획법은 선형게획법의 특수한 형태로서, 일반적으로 목적함수는 비용의 최소화이다. 수송계획 법의 수식형태와 적용변수들은 다음과 같으며, 수요와 공급이 일치할 경우, 제약조건은 등식이나, 수요와 공급이 불일치할 경우, 제약조건은 부등식이 된다.\n", 12 | "\n", 13 | "* 목적함수(objective function): $\\min \\sum_{i=1}^n\\sum_{j=1}^mc_{ij}x_{ij}$\n", 14 | "* 제약조건(constraints): $\\sum_{i=1}^nx_{ij}=S_i,\\quad\\sum_{i=1}^mx_{ij}=D_j$\n", 15 | "* Decision variable: $x_{ij}$, 결정변수로서 공급지역 i에서 수요지역 j로 이동하는 물동량으로 기저변수(basic variable)일 경우 물동량 할당이 된다.\n", 16 | "\n", 17 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 302.**\n", 18 | "\n", 19 | "

P전자는 3개의 공장에서 생산된 제품을 4개의 수요지로 수송하고 있다. 공장 1, 2, 3의 공급량은 각각 120, 150, 200개이고 수요지 1, 2, 3, 4의 수요량은 각각 100, 60, 130, 180개이며 각 공장과 각 수요지 사이의 제품 단위당 수송비용은 표와 같다. 여기서 공장 $i(i=1,2,3)$에서 각 수요지로 수송되는 수송량의 합계는 공장 $i$의 공급량과 일치해야 하며 각 공장에서 수요지 $j(j=1,2,3,4)$로 수송되는 수송량은 수요지 $j$의 수요량과 일치해야 한다. P전자의 총 수송비용을 최소로 하는 각 공장에서 각 수요지로의 최적 수송량을 결정하는 문제를 선혱계획모형으로 정식화하고 Python을 이용하여 최적 수송량을 구하라.

\n", 20 | "\n", 21 | "\n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | "
Table 1. P전자의 수송표
구분1234공급량(개)
14568120
24792150
35876200
수요량(개)10060130180
\n", 64 | "\n", 65 | "

$X_{ij}=\\text{공급지 $i$에서 수요지 $j$로의 수송량(개)}$

" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 2, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "import os\n", 75 | "import sys\n", 76 | "\n", 77 | "# Add the parent directory for importing custom library\n", 78 | "sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), os.pardir)))" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "#### Optimization with PuLP" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 3, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "Status: Optimal\n", 98 | "Objective value: 2130.0\n", 99 | "\n", 100 | "Variables Values\n", 101 | "----------- --------\n", 102 | "X11 60\n", 103 | "X12 60\n", 104 | "X13 0\n", 105 | "X14 0\n", 106 | "X21 0\n", 107 | "X22 0\n", 108 | "X23 0\n", 109 | "X24 150\n", 110 | "X31 40\n", 111 | "X32 0\n", 112 | "X33 130\n", 113 | "X34 30\n", 114 | "\n", 115 | "Statistics:\n", 116 | "- Number of variables: 12\n", 117 | "- Number of constraints: 7\n", 118 | "- Solve time: 0.004s\n" 119 | ] 120 | } 121 | ], 122 | "source": [ 123 | "from pulp import *\n", 124 | "from ortools.utils import output\n", 125 | "\n", 126 | "prob = LpProblem('Transportation Problem', LpMinimize)\n", 127 | "\n", 128 | "x11 = LpVariable('X11', lowBound=0)\n", 129 | "x12 = LpVariable('X12', lowBound=0)\n", 130 | "x13 = LpVariable('X13', lowBound=0)\n", 131 | "x14 = LpVariable('X14', lowBound=0)\n", 132 | "x21 = LpVariable('X21', lowBound=0)\n", 133 | "x22 = LpVariable('X22', lowBound=0)\n", 134 | "x23 = LpVariable('X23', lowBound=0)\n", 135 | "x24 = LpVariable('X24', lowBound=0)\n", 136 | "x31 = LpVariable('X31', lowBound=0)\n", 137 | "x32 = LpVariable('X32', lowBound=0)\n", 138 | "x33 = LpVariable('X33', lowBound=0)\n", 139 | "x34 = LpVariable('X34', lowBound=0)\n", 140 | "\n", 141 | "prob += 4*x11 + 5*x12 + 6*x13 + 8*x14 + 4*x21 + 7*x22 + 9*x23 + 2*x24 + 5*x31 + 8*x32 + 7*x33 + 6*x34\n", 142 | "\n", 143 | "prob += x11 + x12 + x13 + x14 == 120\n", 144 | "prob += x21 + x22 + x23 + x24 == 150\n", 145 | "prob += x31 + x32 + x33 + x34 == 200\n", 146 | "\n", 147 | "prob += x11 + x21 + x31 == 100\n", 148 | "prob += x12 + x22 + x32 == 60\n", 149 | "prob += x13 + x23 + x33 == 130\n", 150 | "prob += x14 + x24 + x34 == 180\n", 151 | "\n", 152 | "# Solving problem\n", 153 | "prob.solve()\n", 154 | "output(prob)" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": 4, 160 | "metadata": {}, 161 | "outputs": [ 162 | { 163 | "name": "stdout", 164 | "output_type": "stream", 165 | "text": [ 166 | "Status: Optimal\n", 167 | "Objective value: 2130.0\n", 168 | "\n", 169 | "Variables Values\n", 170 | "----------- --------\n", 171 | "X_(0,_0) 60\n", 172 | "X_(0,_1) 60\n", 173 | "X_(0,_2) 0\n", 174 | "X_(0,_3) 0\n", 175 | "X_(1,_0) 0\n", 176 | "X_(1,_1) 0\n", 177 | "X_(1,_2) 0\n", 178 | "X_(1,_3) 150\n", 179 | "X_(2,_0) 40\n", 180 | "X_(2,_1) 0\n", 181 | "X_(2,_2) 130\n", 182 | "X_(2,_3) 30\n", 183 | "\n", 184 | "Statistics:\n", 185 | "- Number of variables: 12\n", 186 | "- Number of constraints: 7\n", 187 | "- Solve time: 0.006s\n" 188 | ] 189 | } 190 | ], 191 | "source": [ 192 | "from pulp import *\n", 193 | "from ortools.utils import output\n", 194 | "\n", 195 | "prob = LpProblem('Transportation Problem', LpMinimize)\n", 196 | "\n", 197 | "n_suppliers = 3\n", 198 | "n_buyers = 4\n", 199 | "\n", 200 | "costs = [\n", 201 | " [4, 5, 6, 8],\n", 202 | " [4, 7, 9, 2], \n", 203 | " [5, 8, 7, 6]\n", 204 | "]\n", 205 | "\n", 206 | "supply = [120, 150, 200]\n", 207 | "demand = [100, 60, 130, 180]\n", 208 | "\n", 209 | "routes = [(i, j) for i in range(n_suppliers) for j in range(n_buyers)]\n", 210 | "\n", 211 | "x = LpVariable.dicts('X', routes, lowBound=0)\n", 212 | "\n", 213 | "prob += lpSum([x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)])\n", 214 | "\n", 215 | "for i in range(n_suppliers):\n", 216 | " prob += lpSum([x[i, j] for j in range(n_buyers)]) == supply[i]\n", 217 | " \n", 218 | "for j in range(n_buyers):\n", 219 | " prob += lpSum([x[i, j] for i in range(n_suppliers)]) == demand[j]\n", 220 | " \n", 221 | "# Solving problem\n", 222 | "prob.solve()\n", 223 | "output(prob)" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "#### Optimization with GUROBI" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 5, 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "name": "stdout", 240 | "output_type": "stream", 241 | "text": [ 242 | "Status: Optimal\n", 243 | "Objective value: 2130.0\n", 244 | "\n", 245 | "Variables Values\n", 246 | "----------- --------\n", 247 | "X[0,0] 0\n", 248 | "X[0,1] 60\n", 249 | "X[0,2] 60\n", 250 | "X[0,3] 0\n", 251 | "X[1,0] 0\n", 252 | "X[1,1] 0\n", 253 | "X[1,2] 0\n", 254 | "X[1,3] 150\n", 255 | "X[2,0] 100\n", 256 | "X[2,1] 0\n", 257 | "X[2,2] 70\n", 258 | "X[2,3] 30\n", 259 | "\n", 260 | "Statistics:\n", 261 | "- Number of variables: 12\n", 262 | "- Number of constraints: 7\n", 263 | "- Solve time: 0.001s\n" 264 | ] 265 | } 266 | ], 267 | "source": [ 268 | "from gurobipy import *\n", 269 | "from ortools.utils import set_gurobi, custom_callback, output\n", 270 | "\n", 271 | "n_suppliers = 3\n", 272 | "n_buyers = 4\n", 273 | "\n", 274 | "costs = [\n", 275 | " [4, 5, 6, 8],\n", 276 | " [4, 7, 9, 2], \n", 277 | " [5, 8, 7, 6]\n", 278 | "]\n", 279 | "\n", 280 | "supply = [120, 150, 200]\n", 281 | "demand = [100, 60, 130, 180]\n", 282 | "\n", 283 | "routes = tuplelist([(i, j) for i in range(n_suppliers) for j in range(n_buyers)])\n", 284 | "\n", 285 | "m = Model('Transportation')\n", 286 | "\n", 287 | "x = m.addVars(routes, lb=0, vtype=GRB.CONTINUOUS, name='X')\n", 288 | "\n", 289 | "m.update()\n", 290 | "\n", 291 | "m.setObjective(quicksum(x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)), GRB.MINIMIZE)\n", 292 | "\n", 293 | "for i in range(n_suppliers):\n", 294 | " m.addConstr(quicksum(x[i, j] for j in range(n_buyers)) == supply[i])\n", 295 | " \n", 296 | "for j in range(n_buyers):\n", 297 | " m.addConstr(quicksum(x[i, j] for i in range(n_suppliers)) == demand[j])\n", 298 | "\n", 299 | "set_gurobi(m, verbose=False)\n", 300 | "\n", 301 | "# Optimize model\n", 302 | "m.optimize(custom_callback)\n", 303 | "output(m)" 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "metadata": {}, 309 | "source": [ 310 | "### Unbalanced Transportation Problem\n", 311 | "\n", 312 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 306.**\n", 313 | "\n", 314 | "

현실의 수송문제는 총 공급량과 총 수요량이 일치하는 경우는 거의 없으며 이들이 서로 일치하지 않는 공급과잉이나 수요과잉이 발생하는 것이 일반적인 현상이다. 이와 같이 총 공급량과 총 수요량이 서로 일치하지 않는 수송문제를 불균형 수송문제라고 한다.

\n", 315 | "\n", 316 | "#### Optimization with PuLP" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": 7, 322 | "metadata": {}, 323 | "outputs": [ 324 | { 325 | "name": "stdout", 326 | "output_type": "stream", 327 | "text": [ 328 | "Status: Optimal\n", 329 | "Objective value: 2030.0\n", 330 | "\n", 331 | "Variables Values\n", 332 | "----------- --------\n", 333 | "X_(0,_0) 0\n", 334 | "X_(0,_1) 60\n", 335 | "X_(0,_2) 60\n", 336 | "X_(0,_3) 0\n", 337 | "X_(1,_0) 0\n", 338 | "X_(1,_1) 0\n", 339 | "X_(1,_2) 0\n", 340 | "X_(1,_3) 150\n", 341 | "X_(2,_0) 150\n", 342 | "X_(2,_1) 0\n", 343 | "X_(2,_2) 20\n", 344 | "X_(2,_3) 30\n", 345 | "\n", 346 | "Statistics:\n", 347 | "- Number of variables: 12\n", 348 | "- Number of constraints: 7\n", 349 | "- Solve time: 0.007s\n" 350 | ] 351 | } 352 | ], 353 | "source": [ 354 | "from pulp import *\n", 355 | "\n", 356 | "prob = LpProblem('Unbalaced Transportation Problem', LpMinimize)\n", 357 | "\n", 358 | "n_suppliers = 3\n", 359 | "n_buyers = 4\n", 360 | "\n", 361 | "costs = [\n", 362 | " [4, 5, 6, 8],\n", 363 | " [4, 7, 9, 2], \n", 364 | " [5, 8, 7, 6]\n", 365 | "]\n", 366 | "\n", 367 | "supply = [120, 150, 200]\n", 368 | "demand = [150, 60, 130, 180]\n", 369 | "\n", 370 | "routes = [(i, j) for i in range(n_suppliers) for j in range(n_buyers)]\n", 371 | "\n", 372 | "x = LpVariable.dicts('X', routes, lowBound=0)\n", 373 | "\n", 374 | "prob += lpSum([x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)])\n", 375 | "\n", 376 | "for i in range(n_suppliers):\n", 377 | " prob += lpSum([x[i, j] for j in range(n_buyers)]) == supply[i]\n", 378 | " \n", 379 | "for j in range(n_buyers):\n", 380 | " prob += lpSum([x[i, j] for i in range(n_suppliers)]) <= demand[j]\n", 381 | " \n", 382 | "# Solving problem\n", 383 | "prob.solve()\n", 384 | "output(prob)" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": 9, 390 | "metadata": {}, 391 | "outputs": [ 392 | { 393 | "name": "stdout", 394 | "output_type": "stream", 395 | "text": [ 396 | "Status: Optimal\n", 397 | "Objective value: 2030.0\n", 398 | "\n", 399 | "Variables Values\n", 400 | "----------- --------\n", 401 | "X[0,0] 0\n", 402 | "X[0,1] 60\n", 403 | "X[0,2] 60\n", 404 | "X[0,3] 0\n", 405 | "X[1,0] 0\n", 406 | "X[1,1] 0\n", 407 | "X[1,2] 0\n", 408 | "X[1,3] 150\n", 409 | "X[2,0] 150\n", 410 | "X[2,1] 0\n", 411 | "X[2,2] 20\n", 412 | "X[2,3] 30\n", 413 | "\n", 414 | "Statistics:\n", 415 | "- Number of variables: 12\n", 416 | "- Number of constraints: 7\n", 417 | "- Solve time: 0.000s\n" 418 | ] 419 | } 420 | ], 421 | "source": [ 422 | "from gurobipy import *\n", 423 | "\n", 424 | "n_suppliers = 3\n", 425 | "n_buyers = 4\n", 426 | "\n", 427 | "costs = [\n", 428 | " [4, 5, 6, 8],\n", 429 | " [4, 7, 9, 2], \n", 430 | " [5, 8, 7, 6]\n", 431 | "]\n", 432 | "\n", 433 | "supply = [120, 150, 200]\n", 434 | "demand = [150, 60, 130, 180]\n", 435 | "\n", 436 | "routes = tuplelist([(i, j) for i in range(n_suppliers) for j in range(n_buyers)])\n", 437 | "\n", 438 | "m = Model('Unbalaced Transportation Problem')\n", 439 | "\n", 440 | "x = m.addVars(routes, lb=0, vtype=GRB.CONTINUOUS, name='X')\n", 441 | "\n", 442 | "m.update()\n", 443 | "\n", 444 | "m.setObjective(quicksum(x[i, j] * costs[i][j] for i in range(n_suppliers) for j in range(n_buyers)), GRB.MINIMIZE)\n", 445 | "\n", 446 | "for i in range(n_suppliers):\n", 447 | " m.addConstr(quicksum(x[i, j] for j in range(n_buyers)) == supply[i])\n", 448 | " \n", 449 | "for j in range(n_buyers):\n", 450 | " m.addConstr(quicksum(x[i, j] for i in range(n_suppliers)) <= demand[j])\n", 451 | "\n", 452 | "set_gurobi(m, verbose=False)\n", 453 | "\n", 454 | "# Optimize model\n", 455 | "m.optimize(custom_callback)\n", 456 | "output(m)" 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": null, 462 | "metadata": {}, 463 | "outputs": [], 464 | "source": [] 465 | } 466 | ], 467 | "metadata": { 468 | "kernelspec": { 469 | "display_name": "Python 3", 470 | "language": "python", 471 | "name": "python3" 472 | }, 473 | "language_info": { 474 | "codemirror_mode": { 475 | "name": "ipython", 476 | "version": 3 477 | }, 478 | "file_extension": ".py", 479 | "mimetype": "text/x-python", 480 | "name": "python", 481 | "nbconvert_exporter": "python", 482 | "pygments_lexer": "ipython3", 483 | "version": "3.7.3" 484 | }, 485 | "latex_envs": { 486 | "LaTeX_envs_menu_present": true, 487 | "autocomplete": true, 488 | "bibliofile": "biblio.bib", 489 | "cite_by": "apalike", 490 | "current_citInitial": 1, 491 | "eqLabelWithNumbers": true, 492 | "eqNumInitial": 1, 493 | "hotkeys": { 494 | "equation": "Ctrl-E", 495 | "itemize": "Ctrl-I" 496 | }, 497 | "labels_anchors": false, 498 | "latex_user_defs": false, 499 | "report_style_numbering": false, 500 | "user_envs_cfg": false 501 | }, 502 | "varInspector": { 503 | "cols": { 504 | "lenName": 16, 505 | "lenType": 16, 506 | "lenVar": 40 507 | }, 508 | "kernels_config": { 509 | "python": { 510 | "delete_cmd_postfix": "", 511 | "delete_cmd_prefix": "del ", 512 | "library": "var_list.py", 513 | "varRefreshCmd": "print(var_dic_list())" 514 | }, 515 | "r": { 516 | "delete_cmd_postfix": ") ", 517 | "delete_cmd_prefix": "rm(", 518 | "library": "var_list.r", 519 | "varRefreshCmd": "cat(var_dic_list()) " 520 | } 521 | }, 522 | "types_to_exclude": [ 523 | "module", 524 | "function", 525 | "builtin_function_or_method", 526 | "instance", 527 | "_Feature" 528 | ], 529 | "window_display": false 530 | } 531 | }, 532 | "nbformat": 4, 533 | "nbformat_minor": 4 534 | } 535 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/capital-budgeting.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Integer Programming\n", 8 | "\n", 9 | "### Capital Budgeting Problem\n", 10 | "\n", 11 | "**이강우 & 김정자. (2012). EXCEL 2010 경영과학. 한경사, 397.**\n", 12 | "\n", 13 | "

\n", 14 | "\n", 15 | "\n", 16 | " \n", 17 | " \n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | "
Table 1. Example
Investment targetsNPV
(net present value)
Investment amount
First yearSecond yearThird yearFourth year
13020252520
21010201510
3151530205
41210151015
53525303025
Capital by year70908060
\n", 76 | "\n", 77 | "

자본예산문제는 투자대상의 선택문제이므로 결정변수 $X_{j}$를 다음과 같이 이진변수로 정의하자.

\n", 78 | "\n", 79 | "$$X_{j} = \n", 80 | "\\begin{cases}\n", 81 | " 1, \\; \\text{if investment target $j$ is selected }(j=1,2,3)\\\\\n", 82 | " 0, \\; \\text{otherwise}\n", 83 | "\\end{cases}$$\n", 84 | "\n", 85 | "

\n", 86 | "\n", 87 | "

\n", 88 | "\n", 89 | "

\n", 90 | "\n", 91 | "

" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 1, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "import os\n", 101 | "import sys\n", 102 | "\n", 103 | "# Add the parent directory for importing custom library\n", 104 | "sys.path.append('../')" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 2, 110 | "metadata": {}, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "Status: Optimal\n", 117 | "Objective value: 80.0\n", 118 | "\n", 119 | "Variables Values\n", 120 | "----------- --------\n", 121 | "X_0 1\n", 122 | "X_1 0\n", 123 | "X_2 1\n", 124 | "X_3 0\n", 125 | "X_4 1\n", 126 | "\n", 127 | "Statistics:\n", 128 | "- Number of variables: 5\n", 129 | "- Number of constraints: 4\n", 130 | "- Solve time: 0.004s\n" 131 | ] 132 | } 133 | ], 134 | "source": [ 135 | "from pulp import *\n", 136 | "from ortools.utils import output\n", 137 | "\n", 138 | "n = 5\n", 139 | "year = 4\n", 140 | "\n", 141 | "npv = [30, 10, 15, 12, 35]\n", 142 | "\n", 143 | "amount = [\n", 144 | " [20, 25, 25, 20],\n", 145 | " [10, 20, 15, 10],\n", 146 | " [15, 30, 20, 5],\n", 147 | " [10, 15, 10, 15],\n", 148 | " [25, 30, 30, 25]\n", 149 | "]\n", 150 | "\n", 151 | "capital = [70, 90, 80, 60]\n", 152 | "\n", 153 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 154 | "\n", 155 | "indexs = [(i) for i in range(n)]\n", 156 | "\n", 157 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 158 | "\n", 159 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 160 | "\n", 161 | "for j in range(year):\n", 162 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 163 | " \n", 164 | "prob.solve()\n", 165 | "output(prob)" 166 | ] 167 | }, 168 | { 169 | "cell_type": "markdown", 170 | "metadata": {}, 171 | "source": [ 172 | "#### Multiple choice constraint\n", 173 | "\n", 174 | "

5개의 투자대상 $X_{j}$중에서 3개의 투자대상을 선택하는 제약식, 이를 선다형 제약식(multiple choice constraint)라고 한다.

" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 3, 180 | "metadata": {}, 181 | "outputs": [ 182 | { 183 | "name": "stdout", 184 | "output_type": "stream", 185 | "text": [ 186 | "Status: Optimal\n", 187 | "Objective value: 80.0\n", 188 | "\n", 189 | "Variables Values\n", 190 | "----------- --------\n", 191 | "X_0 1\n", 192 | "X_1 0\n", 193 | "X_2 1\n", 194 | "X_3 0\n", 195 | "X_4 1\n", 196 | "\n", 197 | "Statistics:\n", 198 | "- Number of variables: 5\n", 199 | "- Number of constraints: 5\n", 200 | "- Solve time: 0.007s\n" 201 | ] 202 | } 203 | ], 204 | "source": [ 205 | "from pulp import *\n", 206 | "from ortools.utils import output\n", 207 | "\n", 208 | "n = 5\n", 209 | "year = 4\n", 210 | "\n", 211 | "npv = [30, 10, 15, 12, 35]\n", 212 | "\n", 213 | "amount = [\n", 214 | " [20, 25, 25, 20],\n", 215 | " [10, 20, 15, 10],\n", 216 | " [15, 30, 20, 5],\n", 217 | " [10, 15, 10, 15],\n", 218 | " [25, 30, 30, 25]\n", 219 | "]\n", 220 | "\n", 221 | "capital = [70, 90, 80, 60]\n", 222 | "\n", 223 | "# Define problem\n", 224 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 225 | "\n", 226 | "indexs = [(i) for i in range(n)]\n", 227 | "\n", 228 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 229 | "\n", 230 | "# Set objective function\n", 231 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 232 | "\n", 233 | "# Set constraint\n", 234 | "for j in range(year):\n", 235 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 236 | " \n", 237 | "# Added multiple choice constraint\n", 238 | "prob += lpSum([x[i] for i in range(n)]) == 3\n", 239 | " \n", 240 | "prob.solve()\n", 241 | "output(prob)" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "#### Multiple choice constraint\n", 249 | "\n", 250 | "

5개의 투자대상 $X_{j}$중에서 3개 이내의 투자대상을 선택하는 제약식

" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 4, 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "Status: Optimal\n", 263 | "Objective value: 80.0\n", 264 | "\n", 265 | "Variables Values\n", 266 | "----------- --------\n", 267 | "X_0 1\n", 268 | "X_1 0\n", 269 | "X_2 1\n", 270 | "X_3 0\n", 271 | "X_4 1\n", 272 | "\n", 273 | "Statistics:\n", 274 | "- Number of variables: 5\n", 275 | "- Number of constraints: 5\n", 276 | "- Solve time: 0.007s\n" 277 | ] 278 | } 279 | ], 280 | "source": [ 281 | "from pulp import *\n", 282 | "from ortools.utils import output\n", 283 | "\n", 284 | "n = 5\n", 285 | "year = 4\n", 286 | "\n", 287 | "npv = [30, 10, 15, 12, 35]\n", 288 | "\n", 289 | "amount = [\n", 290 | " [20, 25, 25, 20],\n", 291 | " [10, 20, 15, 10],\n", 292 | " [15, 30, 20, 5],\n", 293 | " [10, 15, 10, 15],\n", 294 | " [25, 30, 30, 25]\n", 295 | "]\n", 296 | "\n", 297 | "capital = [70, 90, 80, 60]\n", 298 | "\n", 299 | "# Define problem\n", 300 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 301 | "\n", 302 | "indexs = [(i) for i in range(n)]\n", 303 | "\n", 304 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 305 | "\n", 306 | "# Set objective function\n", 307 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 308 | "\n", 309 | "# Set constraint\n", 310 | "for j in range(year):\n", 311 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 312 | " \n", 313 | "# Added multiple choice constraint\n", 314 | "prob += lpSum([x[i] for i in range(n)]) <= 3\n", 315 | " \n", 316 | "prob.solve()\n", 317 | "output(prob)" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "#### Conditional constraint\n", 325 | "\n", 326 | "

만일 투자대상 1($X_{1}$)이 선택되면 투자대상 2($X_{2}$)도 선택되어야 한다는 조건부 선택제약식, 위 제약식은 투자대상 2($X_{2}$)가 선택되더라도 투자대상 1($X_{1}$)이 선택된다는 보장이 없으므로 위 제약식을 조건 부 제약식(conditional constraint)이라고 한다.

" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 5, 332 | "metadata": {}, 333 | "outputs": [ 334 | { 335 | "name": "stdout", 336 | "output_type": "stream", 337 | "text": [ 338 | "Status: Optimal\n", 339 | "Objective value: 75.0\n", 340 | "\n", 341 | "Variables Values\n", 342 | "----------- --------\n", 343 | "X_0 1\n", 344 | "X_1 1\n", 345 | "X_2 0\n", 346 | "X_3 0\n", 347 | "X_4 1\n", 348 | "\n", 349 | "Statistics:\n", 350 | "- Number of variables: 5\n", 351 | "- Number of constraints: 5\n", 352 | "- Solve time: 0.008s\n" 353 | ] 354 | } 355 | ], 356 | "source": [ 357 | "from pulp import *\n", 358 | "from ortools.utils import output\n", 359 | "\n", 360 | "n = 5\n", 361 | "year = 4\n", 362 | "\n", 363 | "npv = [30, 10, 15, 12, 35]\n", 364 | "\n", 365 | "amount = [\n", 366 | " [20, 25, 25, 20],\n", 367 | " [10, 20, 15, 10],\n", 368 | " [15, 30, 20, 5],\n", 369 | " [10, 15, 10, 15],\n", 370 | " [25, 30, 30, 25]\n", 371 | "]\n", 372 | "\n", 373 | "capital = [70, 90, 80, 60]\n", 374 | "\n", 375 | "# Define problem\n", 376 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 377 | "\n", 378 | "indexs = [(i) for i in range(n)]\n", 379 | "\n", 380 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 381 | "\n", 382 | "# Set objective function\n", 383 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 384 | "\n", 385 | "# Set constraint\n", 386 | "for j in range(year):\n", 387 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 388 | " \n", 389 | "# Added multiple choice constraint\n", 390 | "prob += x[0] - x[1] <= 0\n", 391 | " \n", 392 | "prob.solve()\n", 393 | "output(prob)" 394 | ] 395 | }, 396 | { 397 | "cell_type": "markdown", 398 | "metadata": {}, 399 | "source": [ 400 | "#### Corequisite constraint\n", 401 | "\n", 402 | "

투자대상 1($X_{1}$)과 투자대상 2($X_{2}$)가 동시에 선택되거나 동시에 선택되지 않는 제약식, 이를 동시요구 제약식(corequisite constraint)이라고 한다.

" 403 | ] 404 | }, 405 | { 406 | "cell_type": "code", 407 | "execution_count": 6, 408 | "metadata": {}, 409 | "outputs": [ 410 | { 411 | "name": "stdout", 412 | "output_type": "stream", 413 | "text": [ 414 | "Status: Optimal\n", 415 | "Objective value: 75.0\n", 416 | "\n", 417 | "Variables Values\n", 418 | "----------- --------\n", 419 | "X_0 1\n", 420 | "X_1 1\n", 421 | "X_2 0\n", 422 | "X_3 0\n", 423 | "X_4 1\n", 424 | "\n", 425 | "Statistics:\n", 426 | "- Number of variables: 5\n", 427 | "- Number of constraints: 5\n", 428 | "- Solve time: 0.007s\n" 429 | ] 430 | } 431 | ], 432 | "source": [ 433 | "from pulp import *\n", 434 | "from ortools.utils import output\n", 435 | "\n", 436 | "n = 5\n", 437 | "year = 4\n", 438 | "\n", 439 | "npv = [30, 10, 15, 12, 35]\n", 440 | "\n", 441 | "amount = [\n", 442 | " [20, 25, 25, 20],\n", 443 | " [10, 20, 15, 10],\n", 444 | " [15, 30, 20, 5],\n", 445 | " [10, 15, 10, 15],\n", 446 | " [25, 30, 30, 25]\n", 447 | "]\n", 448 | "\n", 449 | "capital = [70, 90, 80, 60]\n", 450 | "\n", 451 | "# Define problem\n", 452 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 453 | "\n", 454 | "indexs = [(i) for i in range(n)]\n", 455 | "\n", 456 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 457 | "\n", 458 | "# Set objective function\n", 459 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 460 | "\n", 461 | "# Set constraint\n", 462 | "for j in range(year):\n", 463 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 464 | " \n", 465 | "# Added multiple choice constraint\n", 466 | "prob += x[0] - x[1] == 0\n", 467 | " \n", 468 | "prob.solve()\n", 469 | "output(prob)" 470 | ] 471 | }, 472 | { 473 | "cell_type": "markdown", 474 | "metadata": {}, 475 | "source": [ 476 | "

투자대상 1($X_{1}$)과 투자대상 2($X_{2}$) 중에서 반드시 1개의 투자대상만을 선택해야 한다는 양자택일형 제약식

" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": 7, 482 | "metadata": {}, 483 | "outputs": [ 484 | { 485 | "name": "stdout", 486 | "output_type": "stream", 487 | "text": [ 488 | "Status: Optimal\n", 489 | "Objective value: 80.0\n", 490 | "\n", 491 | "Variables Values\n", 492 | "----------- --------\n", 493 | "X_0 1\n", 494 | "X_1 0\n", 495 | "X_2 1\n", 496 | "X_3 0\n", 497 | "X_4 1\n", 498 | "\n", 499 | "Statistics:\n", 500 | "- Number of variables: 5\n", 501 | "- Number of constraints: 5\n", 502 | "- Solve time: 0.007s\n" 503 | ] 504 | } 505 | ], 506 | "source": [ 507 | "from pulp import *\n", 508 | "from ortools.utils import output\n", 509 | "\n", 510 | "n = 5\n", 511 | "year = 4\n", 512 | "\n", 513 | "npv = [30, 10, 15, 12, 35]\n", 514 | "\n", 515 | "amount = [\n", 516 | " [20, 25, 25, 20],\n", 517 | " [10, 20, 15, 10],\n", 518 | " [15, 30, 20, 5],\n", 519 | " [10, 15, 10, 15],\n", 520 | " [25, 30, 30, 25]\n", 521 | "]\n", 522 | "\n", 523 | "capital = [70, 90, 80, 60]\n", 524 | "\n", 525 | "# Define problem\n", 526 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 527 | "\n", 528 | "indexs = [(i) for i in range(n)]\n", 529 | "\n", 530 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 531 | "\n", 532 | "# Set objective function\n", 533 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 534 | "\n", 535 | "# Set constraint\n", 536 | "for j in range(year):\n", 537 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 538 | " \n", 539 | "# Added multiple choice constraint\n", 540 | "prob += x[0] + x[1] == 1\n", 541 | " \n", 542 | "prob.solve()\n", 543 | "output(prob)" 544 | ] 545 | }, 546 | { 547 | "cell_type": "markdown", 548 | "metadata": {}, 549 | "source": [ 550 | "#### Mutually exclusive constraint\n", 551 | "\n", 552 | "

투자대상 1($X_{1}$)과 투자대상 2($X_{2}$) 동시에 선택될 수 없다는 제약식, 이는 2개의 투자대상이 전혀 선택되지 않을 수도 있고 선택된다면 꼭 1개가 선택되는 경우이다. 이와 같은 제약식을 상호배타적 제약식(mutually exclusive constraint)이라고 한다.

" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": 19, 558 | "metadata": {}, 559 | "outputs": [ 560 | { 561 | "name": "stdout", 562 | "output_type": "stream", 563 | "text": [ 564 | "Status: Optimal\n", 565 | "Objective value: 80.0\n", 566 | "\n", 567 | "Variables Values\n", 568 | "----------- --------\n", 569 | "x_0 1\n", 570 | "x_1 0\n", 571 | "x_2 1\n", 572 | "x_3 0\n", 573 | "x_4 1\n", 574 | "\n", 575 | "Statistics:\n", 576 | "- Number of variables: 5\n", 577 | "- Number of constraints: 5\n", 578 | "- Solve time: 0.029s\n" 579 | ] 580 | } 581 | ], 582 | "source": [ 583 | "from pulp import *\n", 584 | "from ortools.utils import output\n", 585 | "\n", 586 | "n = 5\n", 587 | "year = 4\n", 588 | "\n", 589 | "npv = [30, 10, 15, 12, 35]\n", 590 | "\n", 591 | "amount = [\n", 592 | " [20, 25, 25, 20],\n", 593 | " [10, 20, 15, 10],\n", 594 | " [15, 30, 20, 5],\n", 595 | " [10, 15, 10, 15],\n", 596 | " [25, 30, 30, 25]\n", 597 | "]\n", 598 | "\n", 599 | "capital = [70, 90, 80, 60]\n", 600 | "\n", 601 | "# Define problem\n", 602 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 603 | "\n", 604 | "indexs = [(i) for i in range(n)]\n", 605 | "\n", 606 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 607 | "\n", 608 | "# Set objective function\n", 609 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 610 | "\n", 611 | "# Set constraint\n", 612 | "for j in range(year):\n", 613 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 614 | " \n", 615 | "# Added multiple choice constraint\n", 616 | "prob += x[0] + x[1] <= 1\n", 617 | " \n", 618 | "prob.solve()\n", 619 | "output(prob)" 620 | ] 621 | }, 622 | { 623 | "cell_type": "markdown", 624 | "metadata": {}, 625 | "source": [ 626 | "

다음은 4개의 제약식 중에서 1개의 제약식만을 선택하는 제약식,

" 627 | ] 628 | }, 629 | { 630 | "cell_type": "code", 631 | "execution_count": 25, 632 | "metadata": {}, 633 | "outputs": [ 634 | { 635 | "name": "stdout", 636 | "output_type": "stream", 637 | "text": [ 638 | "Status: Optimal\n", 639 | "Objective value: 55.0\n", 640 | "\n", 641 | "Variables Values\n", 642 | "----------- --------\n", 643 | "X1 1\n", 644 | "X2 1\n", 645 | "X3 1\n", 646 | "Y 0\n", 647 | "\n", 648 | "Statistics:\n", 649 | "- Number of variables: 4\n", 650 | "- Number of constraints: 2\n", 651 | "- Solve time: 0.025s\n" 652 | ] 653 | } 654 | ], 655 | "source": [ 656 | "from pulp import *\n", 657 | "from ortools.utils import output\n", 658 | "\n", 659 | "n = 5\n", 660 | "year = 4\n", 661 | "\n", 662 | "npv = [30, 10, 15, 12, 35]\n", 663 | "\n", 664 | "amount = [\n", 665 | " [20, 25, 25, 20],\n", 666 | " [10, 20, 15, 10],\n", 667 | " [15, 30, 20, 5],\n", 668 | " [10, 15, 10, 15],\n", 669 | " [25, 30, 30, 25]\n", 670 | "]\n", 671 | "\n", 672 | "capital = [70, 90, 80, 60]\n", 673 | "\n", 674 | "M = 1000\n", 675 | "\n", 676 | "# Define problem\n", 677 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 678 | "\n", 679 | "x1 = LpVariable('X1', lowBound=0, cat='Binary')\n", 680 | "x2 = LpVariable('X2', lowBound=0, cat='Binary')\n", 681 | "x3 = LpVariable('X3', lowBound=0, cat='Binary')\n", 682 | "y = LpVariable('Y', lowBound=0, cat='Binary')\n", 683 | "\n", 684 | "# Set objective function\n", 685 | "prob += 30*x1 + 10*x2 + 15*x3\n", 686 | "\n", 687 | "# Set constraint\n", 688 | "prob += 20*x1 + 10*x2 + 15*x3 <= 70 + y * M\n", 689 | "prob += 25*x1 + 20*x2 + 30*x3 <= 90 + (1 - y) * M\n", 690 | "\n", 691 | "prob.solve()\n", 692 | "output(prob)" 693 | ] 694 | }, 695 | { 696 | "cell_type": "markdown", 697 | "metadata": {}, 698 | "source": [ 699 | "

$m$개의 제약식 중에서 $k$개를 선택하는 제약식(단, $k" 700 | ] 701 | }, 702 | { 703 | "cell_type": "code", 704 | "execution_count": 27, 705 | "metadata": {}, 706 | "outputs": [ 707 | { 708 | "name": "stdout", 709 | "output_type": "stream", 710 | "text": [ 711 | "Status: Optimal\n", 712 | "Objective value: 80.0\n", 713 | "\n", 714 | "Variables Values\n", 715 | "----------- --------\n", 716 | "X_0 1\n", 717 | "X_1 0\n", 718 | "X_2 1\n", 719 | "X_3 0\n", 720 | "X_4 1\n", 721 | "\n", 722 | "Statistics:\n", 723 | "- Number of variables: 5\n", 724 | "- Number of constraints: 5\n", 725 | "- Solve time: 0.029s\n" 726 | ] 727 | } 728 | ], 729 | "source": [ 730 | "from pulp import *\n", 731 | "from ortools.utils import output\n", 732 | "\n", 733 | "n = 5\n", 734 | "year = 4\n", 735 | "\n", 736 | "npv = [30, 10, 15, 12, 35]\n", 737 | "\n", 738 | "amount = [\n", 739 | " [20, 25, 25, 20],\n", 740 | " [10, 20, 15, 10],\n", 741 | " [15, 30, 20, 5],\n", 742 | " [10, 15, 10, 15],\n", 743 | " [25, 30, 30, 25]\n", 744 | "]\n", 745 | "\n", 746 | "capital = [70, 90, 80, 60]\n", 747 | "\n", 748 | "M = 1000\n", 749 | "\n", 750 | "# Define problem\n", 751 | "prob = LpProblem('Capital Budgeting Problem', LpMaximize)\n", 752 | "\n", 753 | "indexs = [(i) for i in range(n)]\n", 754 | "\n", 755 | "x = LpVariable.dicts('X', indexs, lowBound=0, cat='Binary')\n", 756 | "y = LpVariable.dicts('Y', [i for i in range(n)], lowBound=0, cat='Binary')\n", 757 | "\n", 758 | "# Set objective function\n", 759 | "prob += lpSum([npv[i]*x[i] for i in range(n)])\n", 760 | "\n", 761 | "# Set constraint\n", 762 | "for j in range(year):\n", 763 | " prob += lpSum([amount[i][j]*x[i] for i in range(n)]) <= capital[j]\n", 764 | " \n", 765 | "prob.solve()\n", 766 | "output(prob)" 767 | ] 768 | } 769 | ], 770 | "metadata": { 771 | "kernelspec": { 772 | "display_name": "Python 3", 773 | "language": "python", 774 | "name": "python3" 775 | }, 776 | "language_info": { 777 | "codemirror_mode": { 778 | "name": "ipython", 779 | "version": 3 780 | }, 781 | "file_extension": ".py", 782 | "mimetype": "text/x-python", 783 | "name": "python", 784 | "nbconvert_exporter": "python", 785 | "pygments_lexer": "ipython3", 786 | "version": "3.7.3" 787 | } 788 | }, 789 | "nbformat": 4, 790 | "nbformat_minor": 4 791 | } 792 | -------------------------------------------------------------------------------- /or-tutorial/integer-programming/integer-programming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Integer Programming\n", 8 | "\n", 9 | "## Knapsack Problem\n", 10 | "\n", 11 | "### Example 6-4\n", 12 | "\n", 13 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 380.**\n", 14 | "\n", 15 | "

Table 6-1는 배낭에 넣어 갖고 갈 수 있는 3개의 상이한 품목에 대한 품목별 무게와 효용 및 배낭의 보관능력을 나타내고 있다. 배낭의 보관능력 제약을 만족하면서 배낭에 넣어 갖고 갈 수 있는 품목의 효용을 최대화하는 정수계획모형을 작성한 후 최적 정수해를 구하라. 단, 같은 품목을 중복으로 배낭에 넣을 수 없다.

\n", 16 | "\n", 17 | "\n", 18 | " \n", 19 | " \n", 20 | " \n", 21 | " \n", 22 | " \n", 23 | " \n", 24 | " \n", 25 | " \n", 26 | " \n", 27 | " \n", 28 | " \n", 29 | " \n", 30 | " \n", 31 | " \n", 32 | " \n", 33 | " \n", 34 | " \n", 35 | " \n", 36 | " \n", 37 | " \n", 38 | " \n", 39 | " \n", 40 | " \n", 41 | "
Table 6-1. 단일품목 배낭문제의 자료
품목무게(kg)효용배낭의 보관능력(kg)
1376
248
323
\n", 42 | "\n", 43 | "

단일품목 배낭문제를 0-1정수계획모형으로 정식화하기 위해서는 배낭에 넣을 수 있는 품목 $j$를 결정변수 $X_{j}$라고 정의하고 $X_{j}$가 0과 1 중에서 하나의 값만 갖도록 하여야 한다. 왜냐하면 배낭에 넣어 갖고 갈 수 있는 품목은 단일품목 배낭문제의 성격상 그 품목을 선택하거나 또는 선택하지 않기 때문이다. 따라서 $X_{j}$가 1의 값을 가질 때는 품목 $j$가 선택되고, 0의 값을 가질 때는 품목 $j$가 선택되지 않는다고 하자. 만일 각 품목에 대해 전체가 아닌 일부분의 선택이 가능하다면 이 문제는 분할성의 가정이 성립하므로 선형계획문제로 정식화할 수 있다.

\n", 44 | "\n", 45 | "$$X_{j} = \\begin{cases}\n", 46 | "1, \\text{ 배낭에 품목 $j$를 넣는 경우 $(j=1,2,3)$}\\\\\n", 47 | "0, \\text{ 배낭에 품목 $j$를 넣지 않는 경우 $(j=1,2,3)$}\\\\\n", 48 | "\\end{cases}$$" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 20, 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stdout", 58 | "output_type": "stream", 59 | "text": [ 60 | "Status Optimal\n", 61 | "Z = 11.0\n", 62 | "X1 = 0.0\n", 63 | "X2 = 1.0\n", 64 | "X3 = 1.0\n" 65 | ] 66 | } 67 | ], 68 | "source": [ 69 | "from pulp import *\n", 70 | "\n", 71 | "prob = LpProblem(name='0-1 Knapsack Problem', sense=LpMaximize)\n", 72 | "\n", 73 | "x1 = LpVariable(name='X1', lowBound=0, cat='Binary')\n", 74 | "x2 = LpVariable(name='X2', lowBound=0, cat='Binary')\n", 75 | "x3 = LpVariable(name='X3', lowBound=0, cat='Binary')\n", 76 | "\n", 77 | "prob += 7*x1 + 8*x2 + 3*x3\n", 78 | "\n", 79 | "prob += 3*x1 + 4*x2 + 2*x3 <= 6\n", 80 | "\n", 81 | "# Solving problem\n", 82 | "prob.solve()\n", 83 | "print('Status', LpStatus[prob.status])\n", 84 | "\n", 85 | "print('Z = {}'.format(value(prob.objective)))\n", 86 | "for i in prob.variables():\n", 87 | " print('{} = {}'.format(i.name, i.varValue))" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Multiple Knapsack Problem\n", 109 | "\n", 110 | "### Example 6-5\n", 111 | "\n", 112 | "**이강우 & 김정자. (2012). _EXCEL 2010 경영과학_. 한경사, 389.**\n", 113 | "\n", 114 | "

Table 6-3는 트럭에 적재할 수 있는 3개의 상이한 품목에 대하여 이들 품목별 단위당 무게, 적재가능 수량, 단위당 수익 및 트럭의 적재능력을 나타내고 있다. 트럭의 적재능력의 제약을 만족하면서 트럭에 품목들을 적재하여 최대의 수익을 얻을려고 한다. 트럭의 수익을 최대로 하는 적재 품목의 수량을 결정하기 위한 정수계획 모형을 작성하고 최적해를 구하라. 단, 트럭에 적재하는 품목의 수량은 정수단위로 결정해야 한다.

\n", 115 | "\n", 116 | "\n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | "
Table 6-1. 단일품목 배낭문제의 자료
품목단위당 무게(kg)적재가능수량(개)단위당 수익(천원)적재능력(kg)
130100706
24020080
32030030
" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 4, 150 | "metadata": {}, 151 | "outputs": [ 152 | { 153 | "name": "stdout", 154 | "output_type": "stream", 155 | "text": [ 156 | "Status Optimal\n", 157 | "Z = 21000.0\n", 158 | "x1 = 100.0\n", 159 | "x2 = 175.0\n", 160 | "x3 = 0.0\n" 161 | ] 162 | } 163 | ], 164 | "source": [ 165 | "from pulp import *\n", 166 | "\n", 167 | "prob = LpProblem(name='Knapsack Problem', sense=LpMaximize)\n", 168 | "\n", 169 | "x1 = LpVariable(name='x1', lowBound=0, cat='Integer')\n", 170 | "x2 = LpVariable(name='x2', lowBound=0, cat='Integer')\n", 171 | "x3 = LpVariable(name='x3', lowBound=0, cat='Integer')\n", 172 | "\n", 173 | "prob += 70*x1 + 80*x2 + 30*x3\n", 174 | "\n", 175 | "prob += 30*x1 + 40*x2 + 20*x3 <= 10000\n", 176 | "prob += x1 <= 100\n", 177 | "prob += x2 <= 200\n", 178 | "prob += x3 <= 300\n", 179 | "\n", 180 | "# Solving problem\n", 181 | "prob.solve()\n", 182 | "print('Status', LpStatus[prob.status])\n", 183 | "\n", 184 | "print('Z = {}'.format(value(prob.objective)))\n", 185 | "for i in prob.variables():\n", 186 | " print('{} = {}'.format(i.name, i.varValue))" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": null, 199 | "metadata": {}, 200 | "outputs": [], 201 | "source": [] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "### Example: Bin Packing Problem\n", 208 | "\n", 209 | "$$\\begin{split}\n", 210 | " & \\text{minimize } &Z=&\\sum_{i=1}^{n}y_{i} & &\\\\[1ex]\n", 211 | " & \\text{subject to } & &\\sum_{j=1}^{n}w_{j}x_{ij} \\le cy_{i},\\quad &i \\in N = \\{1,...,n\\},&\\\\[1ex]\n", 212 | " & & &\\sum_{i=1}^{n}x_{ij} = 1, &j \\in N,&\\\\[1ex] \n", 213 | " & & &y_{i} = 0 or 1, &i \\in N,&\\\\[1ex] \n", 214 | " & & &x_{ij} = 0 or 1 &i \\in N, j \\in N,&\\\\[1ex] \n", 215 | "\\end{split}$$" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 21, 221 | "metadata": {}, 222 | "outputs": [ 223 | { 224 | "name": "stdout", 225 | "output_type": "stream", 226 | "text": [ 227 | "Solved in 0.05406332015991211 seconds.\n", 228 | "Bins used: 5.0\n", 229 | "0: ['a', 'b', 'e', 'i', 'm', 'n']\n", 230 | "1: ['c', 'g', 'h', 'l']\n", 231 | "12: ['d']\n", 232 | "13: ['f']\n", 233 | "7: ['k']\n" 234 | ] 235 | } 236 | ], 237 | "source": [ 238 | "from pulp import *\n", 239 | "import time\n", 240 | "\n", 241 | "#\n", 242 | "# A list of item tuples (name, weight) -- name is meaningless except to humans.\n", 243 | "# Weight and Size are used interchangeably here and elsewhere.\n", 244 | "#\n", 245 | "items = [(\"a\", 5),\n", 246 | " (\"b\", 6),\n", 247 | " (\"c\", 7),\n", 248 | " (\"d\", 32),\n", 249 | " (\"e\", 2),\n", 250 | " (\"f\", 32),\n", 251 | " (\"g\", 5),\n", 252 | " (\"h\", 7),\n", 253 | " (\"i\", 9),\n", 254 | " (\"k\", 12),\n", 255 | " (\"l\", 11),\n", 256 | " (\"m\", 1),\n", 257 | " (\"n\", 2)]\n", 258 | "\n", 259 | "itemCount = len(items)\n", 260 | "\n", 261 | "# Max number of bins allowed.\n", 262 | "maxBins = 32\n", 263 | "\n", 264 | "# Bin Size\n", 265 | "binCapacity = 32\n", 266 | "\n", 267 | "\n", 268 | "\n", 269 | "# Indicator variable assigned 1 when the bin is used.\n", 270 | "y = pulp.LpVariable.dicts('BinUsed', range(maxBins),\n", 271 | " lowBound = 0,\n", 272 | " upBound = 1,\n", 273 | " cat = pulp.LpInteger)\n", 274 | "\n", 275 | "# An indicator variable that is assigned 1 when item is placed into binNum\n", 276 | "possible_ItemInBin = [(itemTuple[0], binNum) for itemTuple in items\n", 277 | " for binNum in range(maxBins)]\n", 278 | "x = pulp.LpVariable.dicts('itemInBin', possible_ItemInBin,\n", 279 | " lowBound = 0,\n", 280 | " upBound = 1,\n", 281 | " cat = pulp.LpInteger)\n", 282 | "\n", 283 | "# Initialize the problem\n", 284 | "prob = LpProblem(\"Bin Packing Problem\", LpMinimize)\n", 285 | "\n", 286 | "# Add the objective function.\n", 287 | "prob += lpSum([y[i] for i in range(maxBins)]), \"Objective: Minimize Bins Used\"\n", 288 | "\n", 289 | "#\n", 290 | "# This is the constraints section.\n", 291 | "#\n", 292 | "\n", 293 | "# First constraint: For every item, the sum of bins in which it appears must be 1\n", 294 | "for j in items:\n", 295 | " prob += lpSum([x[(j[0], i)] for i in range(maxBins)]) == 1, (\"An item can be in only 1 bin -- \" + str(j[0]))\n", 296 | "\n", 297 | "# Second constraint: For every bin, the number of items in the bin cannot exceed the bin capacity\n", 298 | "for i in range(maxBins):\n", 299 | " prob += lpSum([items[j][1] * x[(items[j][0], i)] for j in range(itemCount)]) <= binCapacity*y[i], (\"The sum of item sizes must be smaller than the bin -- \" + str(i))\n", 300 | "\n", 301 | "# Write the model to disk\n", 302 | "\n", 303 | "# Solve the optimization.\n", 304 | "start_time = time.time()\n", 305 | "prob.solve()\n", 306 | "print(\"Solved in %s seconds.\" % (time.time() - start_time))\n", 307 | "\n", 308 | "\n", 309 | "# Bins used\n", 310 | "print(\"Bins used: \" + str(sum(([y[i].value() for i in range(maxBins)]))))\n", 311 | "\n", 312 | "# The rest of this is some unpleasent massaging to get pretty results.\n", 313 | "bins = {}\n", 314 | "for itemBinPair in x.keys():\n", 315 | " if(x[itemBinPair].value() == 1):\n", 316 | " itemNum = itemBinPair[0]\n", 317 | " binNum = itemBinPair[1]\n", 318 | " if binNum in bins:\n", 319 | " bins[binNum].append(itemNum)\n", 320 | " else:\n", 321 | " bins[binNum] = [itemNum]\n", 322 | "\n", 323 | "for b in bins.keys():\n", 324 | " print(str(b) + \": \" + str(bins[b]))" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "metadata": {}, 337 | "source": [ 338 | "### Problem 3" 339 | ] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": null, 344 | "metadata": {}, 345 | "outputs": [], 346 | "source": [ 347 | "from pulp import *\n", 348 | "\n" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": null, 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [ 357 | "prob = LpProblem(name='Profit maximising problem', sense=LpMaximize)" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": null, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "x1 = LpVariable(name='x1', lowBound=0, cat='Integer')\n", 367 | "x2 = LpVariable(name='x2', lowBound=0, cat='Integer')\n", 368 | "\n", 369 | "prob += 30000*x1 + 45000*x2, 'Profit'\n", 370 | "\n", 371 | "prob += 3*x1 + 4*x2 <= 30\n", 372 | "prob += 5*x1 + 6*x2 <= 60\n", 373 | "prob += 1.5*x1 + 3*x2 <= 21" 374 | ] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "execution_count": null, 379 | "metadata": {}, 380 | "outputs": [], 381 | "source": [ 382 | "prob.solve(solver=GUROBI())\n", 383 | "\n", 384 | "print(LpStatus[prob.status])\n", 385 | "\n", 386 | "# Print our decision variable values\n", 387 | "print('Production of Car x1 = {}'.format(x1.varValue))\n", 388 | "print('Production of Car x2 = {}'.format(x2.varValue))\n", 389 | "\n", 390 | "# Print our objective function value\n", 391 | "print(value(prob.objective))" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "metadata": {}, 397 | "source": [ 398 | "### Knapsack Problem" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": null, 404 | "metadata": {}, 405 | "outputs": [], 406 | "source": [ 407 | "from pulp import *" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": null, 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "model = LpProblem('knapsack problem', LpMaximize)" 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": null, 422 | "metadata": {}, 423 | "outputs": [], 424 | "source": [ 425 | "x1 = LpVariable('x1', lowBound=0, upBound=1, cat=LpInteger)\n", 426 | "x2 = LpVariable('x2', lowBound=0, upBound=1, cat=LpInteger)\n", 427 | "x3 = LpVariable('x3', lowBound=0, upBound=1, cat=LpInteger)" 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "metadata": {}, 434 | "outputs": [], 435 | "source": [ 436 | "model += 7*x1 + 8*x2 + 3*x3\n", 437 | "model += 3*x1 + 4*x2 + 2*x3 <= 6" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": null, 443 | "metadata": {}, 444 | "outputs": [], 445 | "source": [ 446 | "model.solve(GUROBI())\n", 447 | "\n", 448 | "print('Status', LpStatus[model.status])\n", 449 | "print(value(model.objective))\n", 450 | "for v in model.variables():\n", 451 | " print(v.name, '=', v.varValue)" 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "metadata": {}, 457 | "source": [ 458 | "### Knapsack Problem II" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "from pulp import *" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": null, 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [ 476 | "items = ['A', 'B', 'C']\n", 477 | "weights = [3, 4, 2]\n", 478 | "values = [7, 8, 3]" 479 | ] 480 | }, 481 | { 482 | "cell_type": "code", 483 | "execution_count": null, 484 | "metadata": {}, 485 | "outputs": [], 486 | "source": [ 487 | "prob = LpProblem('knapsack problem', LpMaximize)\n", 488 | "\n", 489 | "# Define decision variables\n", 490 | "decision_variables = []\n", 491 | "for num, i in enumerate(items):\n", 492 | " var_str = str('x' + str(num))\n", 493 | " variables = LpVariable(str(var_str), lowBound=0, upBound=1, cat='Integer')\n", 494 | " decision_variables.append(variables)\n", 495 | "print(decision_variables)\n", 496 | "\n", 497 | "# Define objective function\n", 498 | "objective_function = ''\n", 499 | "for u, uf in enumerate(values):\n", 500 | " for d, dv in enumerate(decision_variables):\n", 501 | " if u == d:\n", 502 | " objective_function += uf * dv\n", 503 | " \n", 504 | "prob += objective_function\n", 505 | "print(objective_function)\n", 506 | "\n", 507 | "# Define constraint\n", 508 | "capacity = 6\n", 509 | "constraint = ''\n", 510 | "for w, wt in enumerate(weights):\n", 511 | " for d, dv in enumerate(decision_variables):\n", 512 | " if w == d:\n", 513 | " constraint += wt * dv\n", 514 | " \n", 515 | "prob += constraint <= capacity\n", 516 | "print(constraint)" 517 | ] 518 | }, 519 | { 520 | "cell_type": "code", 521 | "execution_count": null, 522 | "metadata": {}, 523 | "outputs": [], 524 | "source": [ 525 | "prob.solve(GUROBI())\n", 526 | "\n", 527 | "print('Status', LpStatus[prob.status])\n", 528 | "print(value(prob.objective))\n", 529 | "for v in prob.variables():\n", 530 | " print(v.name, '=', v.varValue)" 531 | ] 532 | }, 533 | { 534 | "cell_type": "code", 535 | "execution_count": null, 536 | "metadata": {}, 537 | "outputs": [], 538 | "source": [ 539 | "from pulp import *" 540 | ] 541 | }, 542 | { 543 | "cell_type": "code", 544 | "execution_count": null, 545 | "metadata": {}, 546 | "outputs": [], 547 | "source": [ 548 | "data = {\n", 549 | " (\"SMITH\"): [6,8,30,6,20], (\"JOHNSON\"): [6,8,50,0,24], \n", 550 | " ('WILLIAMS'): [6,8,30,0,24], ('JONES'): [6,8,30,0,24], \n", 551 | " ('BROWN'): [6,8,40,0,24], ('DAVIS'): [6,8,50,0,24],\n", 552 | " ('MILLER'): [6,8,45,6,18], ('WILSON'): [6,8,30,0,24], \n", 553 | " ('MOORE'): [6,8,35,0,24], ('TAYLOR'): [6,8,40,0,24], \n", 554 | " ('ANDERSON'): [2,3,60,0,6], ('THOMAS'): [2,4,40,0,24],\n", 555 | " ('JACKSON') :[2,4,60,8,16], ('WHITE'): [2,6,55,0,24], \n", 556 | " ('HARRIS'): [2,6,45,0,24], ('MARTIN'): [2,3,40,0,24], \n", 557 | " ('THOMPSON'): [2,5,50,12,24], ('GARCIA'): [2,4,50,0,24],\n", 558 | " ('MARTINEZ'): [2,4,40,0,24], ('ROBINSON'): [2,5,50,0,24]\n", 559 | "}\n", 560 | "\n", 561 | "required = [1,1,2,3,6,6,7,8,9,8,8,8,7,6,6,5,5,4,4,3,2,2,2,2]\n", 562 | "\n", 563 | "time = 24" 564 | ] 565 | }, 566 | { 567 | "cell_type": "code", 568 | "execution_count": null, 569 | "metadata": {}, 570 | "outputs": [], 571 | "source": [ 572 | "employee = list(data.keys())\n", 573 | "mins, maxs, costs, avs, ave = splitDict(data)" 574 | ] 575 | }, 576 | { 577 | "cell_type": "code", 578 | "execution_count": null, 579 | "metadata": {}, 580 | "outputs": [], 581 | "source": [ 582 | "x = {}\n", 583 | "for d in employee:\n", 584 | " for i in range(time):\n", 585 | " for j in range(i+1, time+1):\n", 586 | " x[d,i,j] = LpVariable(name='x_%s%d%d'%(d,i,j), cat='Binary')\n", 587 | " \n", 588 | "staff_number = {}\n", 589 | "for t in range(time):\n", 590 | " staff_number[t] = LpVariable(name='staffNumber_%d'%t, cat='Integer', lowBound=required[t])" 591 | ] 592 | }, 593 | { 594 | "cell_type": "code", 595 | "execution_count": null, 596 | "metadata": {}, 597 | "outputs": [], 598 | "source": [ 599 | "prob = LpProblem('Work Schedule', LpMinimize)" 600 | ] 601 | }, 602 | { 603 | "cell_type": "code", 604 | "execution_count": null, 605 | "metadata": {}, 606 | "outputs": [], 607 | "source": [ 608 | "prob += lpSum(lpSum(lpSum((j-i) * x[d,i,j] * costs[d] for j in range(i+1,time+1)) \\\n", 609 | " for i in range(time)) for d in employee)" 610 | ] 611 | }, 612 | { 613 | "cell_type": "code", 614 | "execution_count": null, 615 | "metadata": {}, 616 | "outputs": [], 617 | "source": [ 618 | "for d in employee:\n", 619 | " prob += (lpSum(lpSum(x[d,i,j] for j in range(i+1,ave[d]+1)if min[d] <= \\\n", 620 | " (j-i) <= max[d]) for i in range(avs[d],ave[d])) <= 1)\n", 621 | " \n", 622 | " prob += (lpSum(lpSum(x[d,i,j] for j in range(i+1,time+1)) for i in range(time)) <= \\\n", 623 | " lpSum(quicksum(x[d,i,j] for j in range(i+1,ave[d]+1) if min[d] <= (j-i) <= \\\n", 624 | " max[d]) for i in range(avs[d],ave[d])))\n", 625 | "\n", 626 | " \n", 627 | " \n", 628 | "for d in employee:\n", 629 | " m.addConstr(quicksum(quicksum(x[d,i,j] for j in range(i+1,ave[d]+1)if min[d] <= (j-i) <= max[d])for i in range(avs[d],ave[d]))<=1)\n", 630 | " m.addConstr(quicksum(quicksum(x[d,i,j] for j in range(i+1,t+1))for i in range(t))<=quicksum(quicksum(x[d,i,j] for j in range(i+1,ave[d]+1)\n", 631 | " if min[d] <= (j-i) <= max[d])for i in range(avs[d],ave[d])))\n", 632 | " \n", 633 | " \n", 634 | "for c in range(t):\n", 635 | " m.addConstr(quicksum(quicksum(quicksum(x[d,i,j] for j in range(i+1,t+1)if i <= c