├── _config.yml ├── Planning_optimization_part4 ├── Scheduling_chqngeover.py ├── temp.py ├── Constraints.xlsx ├── Customer_orders.xlsx ├── Planning_model4v2.csv ├── Changeover.py ├── planning_load_model4.html ├── Inventory.html ├── Planning_model4_list.csv ├── Inventory_Shortage.html └── planning_MO_model4.html ├── .flake8 ├── Planning_optimization_part3 ├── Constraints.xlsx ├── Customer_orders.xlsx ├── Planning_model4v2.csv ├── planning_load_model4.html ├── Inventory.html ├── Planning_model4_list.csv ├── Inventory_Shortage.html ├── Model4.py └── planning_MO_model4.html ├── Planning_optimization_part2 ├── Result_Model4.png ├── planning_time_model2.html ├── planning_time_model3.html ├── Model2.py └── Model3.py ├── Planning_optimization_part1 ├── dp-cache │ └── dp-tmp-sm4rt1fj │ │ └── dp-tmp-3hpcxgik.vl.json.gz ├── README.md ├── planning_time_model1.html ├── Model1.py └── Planning_optimization.lp ├── pyproject.toml ├── README.md ├── .gitignore └── temp ├── Model3.py ├── Model4.py └── Model5.py /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /Planning_optimization_part4/Scheduling_chqngeover.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Planning_optimization_part4/temp.py: -------------------------------------------------------------------------------- 1 | seq = range(1, 2) 2 | print(seq) -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501,E203 3 | exclude = .git,__pycache__,docs/source/conf.py,old,build,dist 4 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Constraints.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part3/Constraints.xlsx -------------------------------------------------------------------------------- /Planning_optimization_part4/Constraints.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part4/Constraints.xlsx -------------------------------------------------------------------------------- /Planning_optimization_part2/Result_Model4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part2/Result_Model4.png -------------------------------------------------------------------------------- /Planning_optimization_part3/Customer_orders.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part3/Customer_orders.xlsx -------------------------------------------------------------------------------- /Planning_optimization_part4/Customer_orders.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part4/Customer_orders.xlsx -------------------------------------------------------------------------------- /Planning_optimization_part1/dp-cache/dp-tmp-sm4rt1fj/dp-tmp-3hpcxgik.vl.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baptistesoulard/Production-plan-optimization/HEAD/Planning_optimization_part1/dp-cache/dp-tmp-sm4rt1fj/dp-tmp-3hpcxgik.vl.json.gz -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "production-plan-optimization" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["soulabat"] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | pandas = "^1.1.2" 10 | matplotlib = "^3.3.1" 11 | 12 | [tool.poetry.dev-dependencies] 13 | black = "^20.8b1" 14 | flake8 = "^3.8.3" 15 | bandit = "^1.6.2" 16 | mypy = "^0.782" 17 | 18 | [build-system] 19 | requires = ["poetry>=0.12"] 20 | build-backend = "poetry.masonry.api" 21 | -------------------------------------------------------------------------------- /Planning_optimization_part1/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This project aims at providing a solution for planning optimization. The example shown here is a manufacturing company that need to optimize its daily production plan in order to reduce the costs. 3 | 4 | Each model is an improvement of the previou sone, here is a brief comment about each file: 5 | 6 | # Part 1: 7 | Model 1: Daily requirement in number of hours to allocate between 3 different production lines, on the same day. Capacity is the same for each production line but not hourly cost. One production line can not be opened for less than 7 hours or more than 12 hours. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Production Plan Optimization 2 | 3 | ## Introduction 4 | This project aims at providing a solution for planning optimization. 5 | The example shown here is a manufacturing company that need to optimize its daily production plan in order to reduce the costs. 6 | 7 | Each model is an improvement of the previou sone, here is a brief comment about each file: 8 | 9 | ### Part 1: 10 | - Model 1: 11 | Daily requirement in number of hours to allocate between 3 different production lines, on the same day. 12 | Capacity is the same for each production line but not hourly cost. One production line can not be opened for less than 7 hours or more than 12 hours. 13 | 14 | ### Part 2: 15 | - Model 2: 16 | The concept of overtime and weekend extra cost is added. Hours worked from 8 to 12 hours are paid 50% higher and hours worked during weekends are paid double. 17 | - Model 3: 18 | In order to better optimize our planning, we now allow to plan the production in advance to reduce extra costs due to OT or weekends. 19 | Concept of storage cost is introduced. 20 | 21 | 22 | ## Requirements 23 | - Python 3.8 24 | - Poetry 25 | - Gurobi Python installed with a valid license 26 | 27 | ## How to setup 28 | 29 | ```shell script 30 | poetry install 31 | ``` 32 | 33 | ## How to run 34 | 35 | 36 | To run Model1: 37 | ```shell script 38 | python Model1/Model1.py 39 | ``` 40 | 41 | To run Model2: 42 | ```shell script 43 | python Model2/Model2.py 44 | ``` 45 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Planning_model4v2.csv: -------------------------------------------------------------------------------- 1 | Date,2020/07/13,2020/07/13,2020/07/13,2020/07/14,2020/07/14,2020/07/14,2020/07/15,2020/07/15,2020/07/15,2020/07/16,2020/07/16,2020/07/16,2020/07/17,2020/07/17,2020/07/17,2020/07/18,2020/07/18,2020/07/18,2020/07/19,2020/07/19,2020/07/19 2 | Line,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3 3 | Customer_Order,,,,,,,,,,,,,,,,,,,,, 4 | A,320.0,0.0,280.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 5 | B,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 6 | C,0.0,0.0,0.0,0.0,0.0,150.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 7 | D,0.0,0.0,0.0,0.0,0.0,0.0,120.0,0.0,30.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 8 | E,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 9 | F,0.0,0.0,0.0,0.0,0.0,0.0,150.0,0.0,50.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 10 | G,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,400.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 11 | H,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,350.0,50.0,0.0,0.0,0.0,0.0,0.0,0.0 12 | I,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,400.0,0.0,0.0,0.0,0.0,0.0,0.0 13 | J,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,150.0,0.0,0.0,0.0 14 | K,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,100.0,0.0,0.0,0.0 15 | L,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0 16 | -------------------------------------------------------------------------------- /Planning_optimization_part4/Planning_model4v2.csv: -------------------------------------------------------------------------------- 1 | Date,2020/07/13,2020/07/13,2020/07/13,2020/07/14,2020/07/14,2020/07/14,2020/07/15,2020/07/15,2020/07/15,2020/07/16,2020/07/16,2020/07/16,2020/07/17,2020/07/17,2020/07/17,2020/07/18,2020/07/18,2020/07/18,2020/07/19,2020/07/19,2020/07/19 2 | Line,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3,Line_1,Line_2,Line_3 3 | Customer_Order,,,,,,,,,,,,,,,,,,,,, 4 | A,320.0,0.0,280.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 5 | B,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 6 | C,0.0,0.0,0.0,0.0,0.0,150.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 7 | D,0.0,0.0,0.0,0.0,0.0,0.0,120.0,0.0,30.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 8 | E,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 9 | F,0.0,0.0,0.0,0.0,0.0,0.0,150.0,0.0,50.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 10 | G,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,400.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 11 | H,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,350.0,50.0,0.0,0.0,0.0,0.0,0.0,0.0 12 | I,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,400.0,0.0,0.0,0.0,0.0,0.0,0.0 13 | J,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,150.0,0.0,0.0,0.0 14 | K,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,100.0,0.0,0.0,0.0 15 | L,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,200.0,0.0,0.0,0.0 16 | -------------------------------------------------------------------------------- /Planning_optimization_part4/Changeover.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import datetime 3 | from typing import List, Dict 4 | 5 | # Define hourly cost per line - regular, overtime and weekend 6 | reg_costs_per_line = {"Line_1": 245, "Line_2": 315, "Line_3": 245} 7 | lines: List[str] = list(reg_costs_per_line.keys()) 8 | 9 | # Get orders 10 | customer_orders = pd.read_excel("Customer_orders.xlsx") 11 | 12 | # Get cycle times 13 | capacity = pd.read_excel("Constraints.xlsx", sheet_name="8h capacity").set_index("Line") 14 | cycle_time = capacity.rdiv(8) 15 | 16 | def check_duplicates(list_to_check): 17 | if len(list_to_check) == len(set(list_to_check)): 18 | return 19 | else: 20 | print("Duplicate order, please check the requirements file") 21 | exit() 22 | return 23 | 24 | 25 | order_list = customer_orders["Order"].to_list() 26 | check_duplicates(order_list) 27 | 28 | # Create cycle times dictionnary 29 | customer_orders = customer_orders.merge( 30 | cycle_time, left_on="Product_Family", right_index=True 31 | ) 32 | 33 | customer_orders["Delivery_Date"] = pd.to_datetime( 34 | customer_orders["Delivery_Date"] 35 | ).dt.strftime("%Y/%m/%d") 36 | customer_orders = customer_orders.sort_values(by=["Delivery_Date", "Order"]) 37 | 38 | # Define calendar 39 | start_date = datetime.datetime.strptime( 40 | customer_orders["Delivery_Date"].min(), "%Y/%m/%d" 41 | ) 42 | end_date = datetime.datetime.strptime( 43 | customer_orders["Delivery_Date"].max(), "%Y/%m/%d" 44 | ) 45 | 46 | date_modified = start_date 47 | calendar = [start_date.strftime("%Y/%m/%d")] 48 | 49 | while date_modified < end_date: 50 | date_modified += datetime.timedelta(days=1) 51 | calendar.append(date_modified.strftime("%Y/%m/%d")) 52 | 53 | # Get changeover 54 | data = pd.read_csv("Planning_model4_list.csv") 55 | x_qty = { 56 | (day, order, line): data['Qty'][data.Date == day][data.Line == line][data.Customer_Order == order].item() 57 | for day in calendar 58 | for order in order_list 59 | for line in lines 60 | } 61 | 62 | 63 | 64 | print(calendar) 65 | print(order_list) 66 | print(lines) 67 | 68 | print(x_qty) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | .idea/ 141 | .vscode/ 142 | .history/ 143 | -------------------------------------------------------------------------------- /Planning_optimization_part3/planning_load_model4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part4/planning_load_model4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part1/planning_time_model1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part2/planning_time_model2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part2/planning_time_model3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part1/Model1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: Baptiste Soulard 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | from typing import List, Dict 11 | import altair as alt 12 | import datapane as dp 13 | 14 | 15 | def optimize_planning( 16 | timeline: List[str], 17 | workcenters: List[str], 18 | needs: Dict[str, int], 19 | wc_cost_reg: Dict[str, int], 20 | ) -> pd.DataFrame: 21 | 22 | # Initiate optimization model 23 | model = gurobipy.Model("Optimize production planning") 24 | 25 | # DEFINE VARIABLES 26 | # Variable total load (hours) 27 | working_hours = model.addVars( 28 | timeline, 29 | workcenters, 30 | lb=7, 31 | ub=12, 32 | vtype=gurobipy.GRB.INTEGER, 33 | name="Working hours", 34 | ) 35 | 36 | # Status of the line (0 = closed, 1 = opened) 37 | line_opening = model.addVars( 38 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open status" 39 | ) 40 | 41 | # Variable total load (hours) 42 | total_hours = model.addVars( 43 | timeline, 44 | workcenters, 45 | lb=0, 46 | ub=12, 47 | vtype=gurobipy.GRB.INTEGER, 48 | name="Total hours", 49 | ) 50 | 51 | # Variable cost 52 | labor_cost = model.addVars( 53 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Labor cost" 54 | ) 55 | 56 | # CONSTRAINTS 57 | # Set the value of total load 58 | model.addConstrs( 59 | ( 60 | total_hours[(date, wc)] 61 | == working_hours[(date, wc)] * line_opening[(date, wc)] 62 | for date in timeline 63 | for wc in workcenters 64 | ), 65 | name="Link total hours - reg/ot hours", 66 | ) 67 | 68 | # Set the value of cost (hours * hourly cost) 69 | model.addConstrs( 70 | ( 71 | labor_cost[(date, wc)] 72 | == total_hours[(date, wc)] * wc_cost_reg[wc] * line_opening[(date, wc)] 73 | for date in timeline 74 | for wc in workcenters 75 | ), 76 | name="Link labor cost - working hours", 77 | ) 78 | 79 | # Total load = requirement 80 | model.addConstrs( 81 | ((total_hours.sum(date, "*") == needs[date] for date in timeline)), 82 | name="Link total hours - requirement", 83 | ) 84 | 85 | # DEFINE MODEL 86 | # Objective : minimize a function 87 | model.ModelSense = gurobipy.GRB.MINIMIZE 88 | # Function to minimize 89 | optimization_var = gurobipy.quicksum( 90 | labor_cost[(date, wc)] for date in timeline for wc in workcenters 91 | ) 92 | objective = 0 93 | objective += optimization_var 94 | 95 | # SOLVE MODEL 96 | model.setObjective(objective) 97 | model.optimize() 98 | 99 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 100 | 101 | print("Total cost = $" + str(model.ObjVal)) 102 | 103 | # model.write("Planning_optimization.lp") 104 | # file = open("Planning_optimization.lp", 'r') 105 | # print(file.read()) 106 | # file.close() 107 | 108 | return sol 109 | 110 | 111 | def plot_planning(planning, need, timeline): 112 | # Plot graph - Requirement 113 | source = need.copy() 114 | source = source.rename(columns={0: "Hours"}) 115 | source["Date"] = source.index 116 | 117 | bars_need = ( 118 | alt.Chart(source) 119 | .mark_bar() 120 | .encode( 121 | y="Hours:Q", 122 | column=alt.Column("Date:N"), 123 | tooltip=["Date", "Hours"], 124 | ) 125 | .interactive() 126 | .properties( 127 | width=550 / len(timeline) - 22, 128 | height=75, 129 | title='Requirement', 130 | ) 131 | ) 132 | 133 | # Plot graph - Optimized planning 134 | source = planning.filter(like="Total hours", axis=0).copy() 135 | source["Date"] = list(source.index.values) 136 | source = source.rename(columns={"Solution": "Hours"}).reset_index() 137 | source[["Date", "Line"]] = source["Date"].str.split(",", expand=True) 138 | source["Date"] = source["Date"].str.split("[").str[1] 139 | source["Line"] = source["Line"].str.split("]").str[0] 140 | source["Min capacity"] = 7 141 | source["Max capacity"] = 12 142 | source = source.round({"Hours": 1}) 143 | source["Load%"] = pd.Series( 144 | ["{0:.0f}%".format(val / 8 * 100) for val in source["Hours"]], 145 | index=source.index, 146 | ) 147 | 148 | bars = ( 149 | alt.Chart(source) 150 | .mark_bar() 151 | .encode( 152 | x="Line:N", 153 | y="Hours:Q", 154 | column=alt.Column("Date:N"), 155 | color="Line:N", 156 | tooltip=["Date", "Line", "Hours", "Load%"], 157 | ) 158 | .interactive() 159 | .properties( 160 | width=550 / len(timeline) - 22, 161 | height=150, 162 | title="Optimized Production Schedule", 163 | ) 164 | ) 165 | 166 | chart = alt.vconcat(bars, bars_need) 167 | chart.save("planning_time_model1.html") 168 | 169 | dp.Report(dp.Plot(chart, caption="Production schedule model 1 - Time")).publish( 170 | name="Optimized production schedule model 1 - Time", 171 | description="Optimized production schedule model 1 - Time", 172 | open=True, 173 | visibily="PUBLIC", 174 | ) 175 | 176 | 177 | # Define daily requirement (hours/day) 178 | daily_requirements: Dict[str, int] = { 179 | "2020/7/13": 30, 180 | "2020/7/14": 10, 181 | "2020/7/15": 34, 182 | "2020/7/16": 25, 183 | "2020/7/17": 23, 184 | "2020/7/18": 24, 185 | "2020/7/19": 25, 186 | } 187 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 188 | 189 | # List of days on which we have to optimize our planning 190 | calendar: List[str] = list(daily_requirements.keys()) 191 | 192 | # Define hourly cost per line - regular 193 | reg_costs_per_line = {"Line_1": 245, "Line_2": 315, "Line_3": 245} 194 | 195 | # List of production lines available 196 | lines: List[str] = list(reg_costs_per_line.keys()) 197 | 198 | # Optimize planning 199 | solution = optimize_planning( 200 | calendar, 201 | lines, 202 | daily_requirements, 203 | reg_costs_per_line, 204 | ) 205 | 206 | # Plot the new planning 207 | plot_planning(solution, daily_requirements_df, calendar) 208 | -------------------------------------------------------------------------------- /temp/Model3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: soulba01 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | from matplotlib import pyplot as plt 11 | import datetime 12 | from typing import List, Dict 13 | 14 | 15 | def optimize_planning( 16 | timeline: List[str], 17 | workcenters: List[str], 18 | needs: Dict[str, int], 19 | wc_cost_reg: Dict[str, int], 20 | wc_cost_ot: Dict[str, int], 21 | wc_cost_we: Dict[str, int], 22 | ) -> pd.DataFrame: 23 | 24 | # Weekdays / Weekends 25 | weekdays = [] 26 | weekend = [] 27 | for i in timeline: 28 | date = datetime.datetime.strptime(i, "%Y/%m/%d") 29 | if date.weekday() < 5: 30 | weekdays.append(i) 31 | else: 32 | weekend.append(i) 33 | 34 | # Initiate optimization model 35 | model = gurobipy.Model("Optimize production planning") 36 | 37 | # DEFINE VARIABLES 38 | # Load variables (hours) - regular and overtime 39 | reg_hours = model.addVars( 40 | timeline, 41 | workcenters, 42 | lb=7, 43 | ub=8, 44 | vtype=gurobipy.GRB.INTEGER, 45 | name="Regular hours", 46 | ) 47 | ot_hours = model.addVars( 48 | timeline, workcenters, lb=0, ub=4, vtype=gurobipy.GRB.INTEGER, name="OT hours" 49 | ) 50 | # Status of the line ( 0 = closed, 1 = opened) 51 | line_opening = model.addVars( 52 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open" 53 | ) 54 | # Variable total load (hours) 55 | total_hours = model.addVars( 56 | timeline, 57 | workcenters, 58 | lb=0, 59 | ub=12, 60 | vtype=gurobipy.GRB.INTEGER, 61 | name="Total hours", 62 | ) 63 | # Variable cost 64 | cost = model.addVars( 65 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Cost" 66 | ) 67 | 68 | # CONSTRAINTS 69 | # Set the value of cost (hours * hourly cost) 70 | model.addConstrs( 71 | ( 72 | ( 73 | cost[(i, j)] 74 | == reg_hours[(i, j)] * wc_cost_reg[j] * line_opening[(i, j)] 75 | + ot_hours[(i, j)] * wc_cost_ot[j] * line_opening[(i, j)] 76 | for i in weekdays 77 | for j in workcenters 78 | ) 79 | ), 80 | name="Cost constr weekdays", 81 | ) 82 | model.addConstrs( 83 | ( 84 | ( 85 | cost[(i, j)] 86 | == (reg_hours[(i, j)] + ot_hours[(i, j)]) 87 | * wc_cost_we[j] 88 | * line_opening[(i, j)] 89 | for i in weekend 90 | for j in workcenters 91 | ) 92 | ), 93 | name="Cost constr weekend", 94 | ) 95 | # Set the value of total load (regular + overtime) 96 | model.addConstrs( 97 | ( 98 | ( 99 | total_hours[(i, j)] 100 | == (reg_hours[(i, j)] + ot_hours[(i, j)]) * line_opening[(i, j)] 101 | for i in timeline 102 | for j in workcenters 103 | ) 104 | ), 105 | name="Total hours constr", 106 | ) 107 | 108 | # Constraint : requirement = load of the 3 lines 109 | model.addConstrs( 110 | ((total_hours.sum(d, "*") == needs[d] for d in timeline)), 111 | name="Requirements", 112 | ) 113 | 114 | # DEFINE MODEL 115 | # Objective : minimize a function 116 | model.ModelSense = gurobipy.GRB.MINIMIZE 117 | # Function to minimize 118 | optimization_var = gurobipy.quicksum( 119 | cost[(i, j)] for i in timeline for j in workcenters 120 | ) 121 | objective = 0 122 | objective += optimization_var 123 | 124 | # SOLVE MODEL 125 | model.setObjective(objective) 126 | model.optimize() 127 | 128 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 129 | sol = sol.filter(like="Total hours", axis=0) 130 | 131 | print("Total cost = $" + str(model.ObjVal)) 132 | return sol 133 | 134 | 135 | def plot_planning(plan, need): 136 | plan = plan.T 137 | plan["Min capacity"] = 7 138 | plan["Max capacity"] = 12 139 | 140 | my_colors = ["skyblue", "salmon", "lightgreen"] 141 | 142 | fig, axs = plt.subplots(2) 143 | need.plot( 144 | kind="bar", 145 | width=0.2, 146 | title="Need in h per day", 147 | ax=axs[0], 148 | color="midnightblue", 149 | ) 150 | 151 | plan[["Min capacity", "Max capacity"]].plot( 152 | rot=90, ax=axs[1], style=["b", "b--"], linewidth=1 153 | ) 154 | 155 | plan.drop(["Min capacity", "Max capacity"], axis=1).plot( 156 | kind="bar", title="Load in h per line", ax=axs[1], color=my_colors 157 | ) 158 | 159 | axs[0].tick_params(axis="x", labelsize=7) 160 | axs[0].tick_params(axis="y", labelsize=7) 161 | axs[0].get_legend().remove() 162 | axs[0].set_xticklabels([]) 163 | axs[1].tick_params(axis="x", labelsize=7) 164 | axs[1].tick_params(axis="y", labelsize=7) 165 | 166 | plt.savefig("Result_Model3.png", bbox_inches="tight", dpi=1200) 167 | axe = plt.show() 168 | return axe 169 | 170 | 171 | # Generate inputs 172 | # Define the daily requirement 173 | 174 | daily_requirements: Dict[str, int] = { 175 | "2020/7/13": 30, 176 | "2020/7/14": 10, 177 | "2020/7/15": 34, 178 | "2020/7/16": 23, 179 | "2020/7/17": 23, 180 | "2020/7/18": 24, 181 | "2020/7/19": 25, 182 | } 183 | 184 | calendar: List[str] = list(daily_requirements.keys()) 185 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 186 | 187 | # Define the hourly cost per line - regular, overtime and weekend 188 | 189 | reg_costs_per_line = {"Curtain_C1": 350, "Curtain_C2": 300, "Curtain_C3": 350} 190 | ot_costs_per_line = { 191 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 192 | } 193 | we_costs_per_line = { 194 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 195 | } 196 | 197 | lines: List[str] = list(reg_costs_per_line.keys()) 198 | 199 | # Optimize the planning 200 | solution = optimize_planning( 201 | calendar, 202 | lines, 203 | daily_requirements, 204 | reg_costs_per_line, 205 | ot_costs_per_line, 206 | we_costs_per_line, 207 | ) 208 | 209 | # Format the result 210 | planning = pd.DataFrame(index=lines, columns=calendar) 211 | 212 | for line in lines: 213 | for day in calendar: 214 | planning.at[line, day] = solution.loc[ 215 | "Total hours[" + str(day) + "," + str(line) + "]" 216 | ][0] 217 | 218 | planning = solution 219 | planning["Date"] = list(planning.index.values) 220 | planning[["Date", "Line"]] = planning["Date"].str.split( 221 | ",", expand=True 222 | ) 223 | planning["Date"] = planning["Date"].str.split("[").str[1] 224 | planning["Line"] = planning["Line"].str.split("]").str[0] 225 | planning = planning.pivot( 226 | index="Line", columns="Date", values="Solution" 227 | ) 228 | 229 | # Plot the new planning 230 | plot_planning(planning, daily_requirements_df) 231 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Inventory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part4/Inventory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part2/Model2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: Baptiste Soulard 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | import datetime 11 | from typing import List, Dict 12 | import altair as alt 13 | import datapane as dp 14 | 15 | 16 | def optimize_planning( 17 | timeline: List[str], 18 | workcenters: List[str], 19 | needs: Dict[str, int], 20 | wc_cost_reg: Dict[str, int], 21 | wc_cost_ot: Dict[str, int], 22 | wc_cost_we: Dict[str, int], 23 | ) -> pd.DataFrame: 24 | 25 | # Split weekdays/weekends 26 | weekdays = [] 27 | weekend = [] 28 | for date in timeline: 29 | day = datetime.datetime.strptime(date, "%Y/%m/%d") 30 | if day.weekday() < 5: 31 | weekdays.append(date) 32 | else: 33 | weekend.append(date) 34 | 35 | # Initiate optimization model 36 | model = gurobipy.Model("Optimize production planning") 37 | 38 | # DEFINE VARIABLES 39 | # Load variables (hours) - regular and overtime 40 | reg_hours = model.addVars( 41 | timeline, 42 | workcenters, 43 | lb=7, 44 | ub=8, 45 | vtype=gurobipy.GRB.INTEGER, 46 | name="Regular hours", 47 | ) 48 | ot_hours = model.addVars( 49 | timeline, 50 | workcenters, 51 | lb=0, 52 | ub=4, 53 | vtype=gurobipy.GRB.INTEGER, 54 | name="Overtime hours", 55 | ) 56 | 57 | # Status of the line ( 0 = closed, 1 = opened) 58 | line_opening = model.addVars( 59 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open status" 60 | ) 61 | 62 | # Variable total load (hours) 63 | total_hours = model.addVars( 64 | timeline, 65 | workcenters, 66 | lb=0, 67 | ub=12, 68 | vtype=gurobipy.GRB.INTEGER, 69 | name="Total hours", 70 | ) 71 | 72 | # Variable cost 73 | labor_cost = model.addVars( 74 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Labor cost" 75 | ) 76 | 77 | # CONSTRAINTS 78 | # Set the value of total load (regular + overtime) 79 | model.addConstrs( 80 | ( 81 | total_hours[(date, wc)] 82 | == (reg_hours[(date, wc)] + ot_hours[(date, wc)]) * line_opening[(date, wc)] 83 | for date in timeline 84 | for wc in workcenters 85 | ), 86 | name="Link total hours - reg/ot hours", 87 | ) 88 | 89 | # Set the value of cost (hours * hourly cost) 90 | model.addConstrs( 91 | ( 92 | labor_cost[(date, wc)] 93 | == reg_hours[(date, wc)] * wc_cost_reg[wc] * line_opening[(date, wc)] 94 | + ot_hours[(date, wc)] * wc_cost_ot[wc] * line_opening[(date, wc)] 95 | for date in weekdays 96 | for wc in workcenters 97 | ), 98 | name="Link labor cost - working hours - wd", 99 | ) 100 | 101 | model.addConstrs( 102 | ( 103 | labor_cost[(date, wc)] == total_hours[(date, wc)] * wc_cost_we[wc] 104 | for date in weekend 105 | for wc in workcenters 106 | ), 107 | name="Link labor cost - working hours - we", 108 | ) 109 | 110 | # Total load = requirement 111 | model.addConstrs( 112 | ((total_hours.sum(date, "*") == needs[date] for date in timeline)), 113 | name="Link total hours - requirement", 114 | ) 115 | 116 | # DEFINE MODEL 117 | # Objective : minimize a function 118 | model.ModelSense = gurobipy.GRB.MINIMIZE 119 | # Function to minimize 120 | objective = 0 121 | objective += gurobipy.quicksum( 122 | labor_cost[(date, wc)] for date in timeline for wc in workcenters 123 | ) 124 | 125 | # SOLVE MODEL 126 | model.setObjective(objective) 127 | model.optimize() 128 | 129 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 130 | 131 | print("Total cost = $" + str(model.ObjVal)) 132 | return sol 133 | 134 | 135 | def plot_planning(planning, need, timeline): 136 | # Plot graph - Requirement 137 | source = need.copy() 138 | source = source.rename(columns={0: "Hours"}) 139 | source["Date"] = source.index 140 | 141 | bars_need = ( 142 | alt.Chart(source) 143 | .mark_bar() 144 | .encode( 145 | y="Hours:Q", 146 | column=alt.Column("Date:N"), 147 | tooltip=["Date", "Hours"], 148 | ) 149 | .interactive() 150 | .properties( 151 | width=550 / len(timeline) - 22, 152 | height=75, 153 | title='Requirement', 154 | ) 155 | ) 156 | 157 | # Plot graph - Optimized planning 158 | source = planning.filter(like="Total hours", axis=0).copy() 159 | source["Date"] = list(source.index.values) 160 | source = source.rename(columns={"Solution": "Hours"}).reset_index() 161 | source[["Date", "Line"]] = source["Date"].str.split(",", expand=True) 162 | source["Date"] = source["Date"].str.split("[").str[1] 163 | source["Line"] = source["Line"].str.split("]").str[0] 164 | source["Min capacity"] = 7 165 | source["Max capacity"] = 12 166 | source = source.round({"Hours": 1}) 167 | source["Load%"] = pd.Series( 168 | ["{0:.0f}%".format(val / 8 * 100) for val in source["Hours"]], 169 | index=source.index, 170 | ) 171 | 172 | bars = ( 173 | alt.Chart(source) 174 | .mark_bar() 175 | .encode( 176 | x="Line:N", 177 | y="Hours:Q", 178 | column=alt.Column("Date:N"), 179 | color="Line:N", 180 | tooltip=["Date", "Line", "Hours", "Load%"], 181 | ) 182 | .interactive() 183 | .properties( 184 | width=550 / len(timeline) - 22, 185 | height=150, 186 | title="Optimized Production Schedule", 187 | ) 188 | ) 189 | 190 | chart = alt.vconcat(bars, bars_need) 191 | chart.save("planning_time_model2.html") 192 | 193 | dp.Report(dp.Plot(chart, caption="Production schedule model 2 - Time")).publish( 194 | name="Optimized production schedule model 2 - Time", 195 | description="Optimized production schedule model 2 - Time", 196 | open=True, 197 | visibily="PUBLIC", 198 | ) 199 | 200 | 201 | # Define daily requirement 202 | daily_requirements: Dict[str, int] = { 203 | "2020/7/13": 30, 204 | "2020/7/14": 10, 205 | "2020/7/15": 34, 206 | "2020/7/16": 25, 207 | "2020/7/17": 23, 208 | "2020/7/18": 24, 209 | "2020/7/19": 25, 210 | } 211 | 212 | calendar: List[str] = list(daily_requirements.keys()) 213 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 214 | 215 | # Define hourly cost per line - regular, overtime and weekend 216 | reg_costs_per_line = {"Line_1": 245, "Line_2": 315, "Line_3": 245} 217 | ot_costs_per_line = { 218 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 219 | } 220 | we_costs_per_line = { 221 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 222 | } 223 | 224 | lines: List[str] = list(reg_costs_per_line.keys()) 225 | 226 | # Optimize planning 227 | solution = optimize_planning( 228 | calendar, 229 | lines, 230 | daily_requirements, 231 | reg_costs_per_line, 232 | ot_costs_per_line, 233 | we_costs_per_line, 234 | ) 235 | 236 | # Plot the new planning 237 | plot_planning(solution, daily_requirements_df, calendar) 238 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Planning_model4_list.csv: -------------------------------------------------------------------------------- 1 | ,Date,Line,Qty,Customer_Order 2 | 0,2020/07/13,Line_1,320.0,A 3 | 1,2020/07/13,Line_2,-0.0,A 4 | 2,2020/07/13,Line_3,280.0,A 5 | 3,2020/07/13,Line_1,0.0,B 6 | 4,2020/07/13,Line_2,-0.0,B 7 | 5,2020/07/13,Line_3,0.0,B 8 | 6,2020/07/13,Line_1,0.0,C 9 | 7,2020/07/13,Line_2,-0.0,C 10 | 8,2020/07/13,Line_3,0.0,C 11 | 9,2020/07/13,Line_1,0.0,D 12 | 10,2020/07/13,Line_2,-0.0,D 13 | 11,2020/07/13,Line_3,0.0,D 14 | 12,2020/07/13,Line_1,0.0,E 15 | 13,2020/07/13,Line_2,-0.0,E 16 | 14,2020/07/13,Line_3,0.0,E 17 | 15,2020/07/13,Line_1,0.0,F 18 | 16,2020/07/13,Line_2,-0.0,F 19 | 17,2020/07/13,Line_3,0.0,F 20 | 18,2020/07/13,Line_1,0.0,G 21 | 19,2020/07/13,Line_2,-0.0,G 22 | 20,2020/07/13,Line_3,0.0,G 23 | 21,2020/07/13,Line_1,0.0,H 24 | 22,2020/07/13,Line_2,-0.0,H 25 | 23,2020/07/13,Line_3,0.0,H 26 | 24,2020/07/13,Line_1,0.0,I 27 | 25,2020/07/13,Line_2,-0.0,I 28 | 26,2020/07/13,Line_3,0.0,I 29 | 27,2020/07/13,Line_1,0.0,J 30 | 28,2020/07/13,Line_2,-0.0,J 31 | 29,2020/07/13,Line_3,0.0,J 32 | 30,2020/07/13,Line_1,0.0,K 33 | 31,2020/07/13,Line_2,-0.0,K 34 | 32,2020/07/13,Line_3,0.0,K 35 | 33,2020/07/13,Line_1,0.0,L 36 | 34,2020/07/13,Line_2,-0.0,L 37 | 35,2020/07/13,Line_3,0.0,L 38 | 36,2020/07/14,Line_1,0.0,A 39 | 37,2020/07/14,Line_2,-0.0,A 40 | 38,2020/07/14,Line_3,0.0,A 41 | 39,2020/07/14,Line_1,-0.0,B 42 | 40,2020/07/14,Line_2,-0.0,B 43 | 41,2020/07/14,Line_3,200.0,B 44 | 42,2020/07/14,Line_1,-0.0,C 45 | 43,2020/07/14,Line_2,-0.0,C 46 | 44,2020/07/14,Line_3,150.0,C 47 | 45,2020/07/14,Line_1,0.0,D 48 | 46,2020/07/14,Line_2,-0.0,D 49 | 47,2020/07/14,Line_3,0.0,D 50 | 48,2020/07/14,Line_1,0.0,E 51 | 49,2020/07/14,Line_2,-0.0,E 52 | 50,2020/07/14,Line_3,0.0,E 53 | 51,2020/07/14,Line_1,0.0,F 54 | 52,2020/07/14,Line_2,-0.0,F 55 | 53,2020/07/14,Line_3,0.0,F 56 | 54,2020/07/14,Line_1,0.0,G 57 | 55,2020/07/14,Line_2,-0.0,G 58 | 56,2020/07/14,Line_3,0.0,G 59 | 57,2020/07/14,Line_1,0.0,H 60 | 58,2020/07/14,Line_2,-0.0,H 61 | 59,2020/07/14,Line_3,0.0,H 62 | 60,2020/07/14,Line_1,0.0,I 63 | 61,2020/07/14,Line_2,-0.0,I 64 | 62,2020/07/14,Line_3,0.0,I 65 | 63,2020/07/14,Line_1,0.0,J 66 | 64,2020/07/14,Line_2,-0.0,J 67 | 65,2020/07/14,Line_3,0.0,J 68 | 66,2020/07/14,Line_1,0.0,K 69 | 67,2020/07/14,Line_2,-0.0,K 70 | 68,2020/07/14,Line_3,0.0,K 71 | 69,2020/07/14,Line_1,0.0,L 72 | 70,2020/07/14,Line_2,-0.0,L 73 | 71,2020/07/14,Line_3,0.0,L 74 | 72,2020/07/15,Line_1,-0.0,A 75 | 73,2020/07/15,Line_2,0.0,A 76 | 74,2020/07/15,Line_3,-0.0,A 77 | 75,2020/07/15,Line_1,0.0,B 78 | 76,2020/07/15,Line_2,0.0,B 79 | 77,2020/07/15,Line_3,0.0,B 80 | 78,2020/07/15,Line_1,-0.0,C 81 | 79,2020/07/15,Line_2,0.0,C 82 | 80,2020/07/15,Line_3,-0.0,C 83 | 81,2020/07/15,Line_1,120.0,D 84 | 82,2020/07/15,Line_2,-0.0,D 85 | 83,2020/07/15,Line_3,30.0,D 86 | 84,2020/07/15,Line_1,-0.0,E 87 | 85,2020/07/15,Line_2,-0.0,E 88 | 86,2020/07/15,Line_3,200.0,E 89 | 87,2020/07/15,Line_1,150.0,F 90 | 88,2020/07/15,Line_2,-0.0,F 91 | 89,2020/07/15,Line_3,50.0,F 92 | 90,2020/07/15,Line_1,-0.0,G 93 | 91,2020/07/15,Line_2,0.0,G 94 | 92,2020/07/15,Line_3,-0.0,G 95 | 93,2020/07/15,Line_1,-0.0,H 96 | 94,2020/07/15,Line_2,0.0,H 97 | 95,2020/07/15,Line_3,-0.0,H 98 | 96,2020/07/15,Line_1,-0.0,I 99 | 97,2020/07/15,Line_2,0.0,I 100 | 98,2020/07/15,Line_3,-0.0,I 101 | 99,2020/07/15,Line_1,0.0,J 102 | 100,2020/07/15,Line_2,0.0,J 103 | 101,2020/07/15,Line_3,0.0,J 104 | 102,2020/07/15,Line_1,0.0,K 105 | 103,2020/07/15,Line_2,0.0,K 106 | 104,2020/07/15,Line_3,0.0,K 107 | 105,2020/07/15,Line_1,0.0,L 108 | 106,2020/07/15,Line_2,0.0,L 109 | 107,2020/07/15,Line_3,0.0,L 110 | 108,2020/07/16,Line_1,-0.0,A 111 | 109,2020/07/16,Line_2,-0.0,A 112 | 110,2020/07/16,Line_3,-0.0,A 113 | 111,2020/07/16,Line_1,-0.0,B 114 | 112,2020/07/16,Line_2,-0.0,B 115 | 113,2020/07/16,Line_3,-0.0,B 116 | 114,2020/07/16,Line_1,-0.0,C 117 | 115,2020/07/16,Line_2,-0.0,C 118 | 116,2020/07/16,Line_3,-0.0,C 119 | 117,2020/07/16,Line_1,-0.0,D 120 | 118,2020/07/16,Line_2,-0.0,D 121 | 119,2020/07/16,Line_3,-0.0,D 122 | 120,2020/07/16,Line_1,-0.0,E 123 | 121,2020/07/16,Line_2,-0.0,E 124 | 122,2020/07/16,Line_3,-0.0,E 125 | 123,2020/07/16,Line_1,-0.0,F 126 | 124,2020/07/16,Line_2,-0.0,F 127 | 125,2020/07/16,Line_3,-0.0,F 128 | 126,2020/07/16,Line_1,-0.0,G 129 | 127,2020/07/16,Line_2,-0.0,G 130 | 128,2020/07/16,Line_3,-0.0,G 131 | 129,2020/07/16,Line_1,-0.0,H 132 | 130,2020/07/16,Line_2,-0.0,H 133 | 131,2020/07/16,Line_3,-0.0,H 134 | 132,2020/07/16,Line_1,-0.0,I 135 | 133,2020/07/16,Line_2,-0.0,I 136 | 134,2020/07/16,Line_3,-0.0,I 137 | 135,2020/07/16,Line_1,-0.0,J 138 | 136,2020/07/16,Line_2,-0.0,J 139 | 137,2020/07/16,Line_3,-0.0,J 140 | 138,2020/07/16,Line_1,-0.0,K 141 | 139,2020/07/16,Line_2,-0.0,K 142 | 140,2020/07/16,Line_3,-0.0,K 143 | 141,2020/07/16,Line_1,-0.0,L 144 | 142,2020/07/16,Line_2,-0.0,L 145 | 143,2020/07/16,Line_3,-0.0,L 146 | 144,2020/07/17,Line_1,0.0,A 147 | 145,2020/07/17,Line_2,0.0,A 148 | 146,2020/07/17,Line_3,0.0,A 149 | 147,2020/07/17,Line_1,0.0,B 150 | 148,2020/07/17,Line_2,0.0,B 151 | 149,2020/07/17,Line_3,0.0,B 152 | 150,2020/07/17,Line_1,0.0,C 153 | 151,2020/07/17,Line_2,0.0,C 154 | 152,2020/07/17,Line_3,0.0,C 155 | 153,2020/07/17,Line_1,-0.0,D 156 | 154,2020/07/17,Line_2,-0.0,D 157 | 155,2020/07/17,Line_3,-0.0,D 158 | 156,2020/07/17,Line_1,0.0,E 159 | 157,2020/07/17,Line_2,0.0,E 160 | 158,2020/07/17,Line_3,0.0,E 161 | 159,2020/07/17,Line_1,0.0,F 162 | 160,2020/07/17,Line_2,0.0,F 163 | 161,2020/07/17,Line_3,0.0,F 164 | 162,2020/07/17,Line_1,400.0,G 165 | 163,2020/07/17,Line_2,-0.0,G 166 | 164,2020/07/17,Line_3,-0.0,G 167 | 165,2020/07/17,Line_1,-0.0,H 168 | 166,2020/07/17,Line_2,350.0,H 169 | 167,2020/07/17,Line_3,50.0,H 170 | 168,2020/07/17,Line_1,-0.0,I 171 | 169,2020/07/17,Line_2,-0.0,I 172 | 170,2020/07/17,Line_3,400.0,I 173 | 171,2020/07/17,Line_1,-0.0,J 174 | 172,2020/07/17,Line_2,-0.0,J 175 | 173,2020/07/17,Line_3,-0.0,J 176 | 174,2020/07/17,Line_1,-0.0,K 177 | 175,2020/07/17,Line_2,-0.0,K 178 | 176,2020/07/17,Line_3,-0.0,K 179 | 177,2020/07/17,Line_1,0.0,L 180 | 178,2020/07/17,Line_2,-0.0,L 181 | 179,2020/07/17,Line_3,-0.0,L 182 | 180,2020/07/18,Line_1,0.0,A 183 | 181,2020/07/18,Line_2,-0.0,A 184 | 182,2020/07/18,Line_3,0.0,A 185 | 183,2020/07/18,Line_1,0.0,B 186 | 184,2020/07/18,Line_2,-0.0,B 187 | 185,2020/07/18,Line_3,0.0,B 188 | 186,2020/07/18,Line_1,0.0,C 189 | 187,2020/07/18,Line_2,-0.0,C 190 | 188,2020/07/18,Line_3,0.0,C 191 | 189,2020/07/18,Line_1,0.0,D 192 | 190,2020/07/18,Line_2,-0.0,D 193 | 191,2020/07/18,Line_3,0.0,D 194 | 192,2020/07/18,Line_1,0.0,E 195 | 193,2020/07/18,Line_2,-0.0,E 196 | 194,2020/07/18,Line_3,0.0,E 197 | 195,2020/07/18,Line_1,0.0,F 198 | 196,2020/07/18,Line_2,-0.0,F 199 | 197,2020/07/18,Line_3,0.0,F 200 | 198,2020/07/18,Line_1,-0.0,G 201 | 199,2020/07/18,Line_2,-0.0,G 202 | 200,2020/07/18,Line_3,-0.0,G 203 | 201,2020/07/18,Line_1,0.0,H 204 | 202,2020/07/18,Line_2,-0.0,H 205 | 203,2020/07/18,Line_3,0.0,H 206 | 204,2020/07/18,Line_1,0.0,I 207 | 205,2020/07/18,Line_2,-0.0,I 208 | 206,2020/07/18,Line_3,0.0,I 209 | 207,2020/07/18,Line_1,-0.0,J 210 | 208,2020/07/18,Line_2,-0.0,J 211 | 209,2020/07/18,Line_3,150.0,J 212 | 210,2020/07/18,Line_1,-0.0,K 213 | 211,2020/07/18,Line_2,-0.0,K 214 | 212,2020/07/18,Line_3,100.0,K 215 | 213,2020/07/18,Line_1,-0.0,L 216 | 214,2020/07/18,Line_2,-0.0,L 217 | 215,2020/07/18,Line_3,200.0,L 218 | 216,2020/07/19,Line_1,-0.0,A 219 | 217,2020/07/19,Line_2,0.0,A 220 | 218,2020/07/19,Line_3,-0.0,A 221 | 219,2020/07/19,Line_1,-0.0,B 222 | 220,2020/07/19,Line_2,0.0,B 223 | 221,2020/07/19,Line_3,-0.0,B 224 | 222,2020/07/19,Line_1,-0.0,C 225 | 223,2020/07/19,Line_2,0.0,C 226 | 224,2020/07/19,Line_3,-0.0,C 227 | 225,2020/07/19,Line_1,-0.0,D 228 | 226,2020/07/19,Line_2,0.0,D 229 | 227,2020/07/19,Line_3,-0.0,D 230 | 228,2020/07/19,Line_1,-0.0,E 231 | 229,2020/07/19,Line_2,0.0,E 232 | 230,2020/07/19,Line_3,-0.0,E 233 | 231,2020/07/19,Line_1,-0.0,F 234 | 232,2020/07/19,Line_2,0.0,F 235 | 233,2020/07/19,Line_3,-0.0,F 236 | 234,2020/07/19,Line_1,-0.0,G 237 | 235,2020/07/19,Line_2,0.0,G 238 | 236,2020/07/19,Line_3,-0.0,G 239 | 237,2020/07/19,Line_1,-0.0,H 240 | 238,2020/07/19,Line_2,0.0,H 241 | 239,2020/07/19,Line_3,-0.0,H 242 | 240,2020/07/19,Line_1,-0.0,I 243 | 241,2020/07/19,Line_2,0.0,I 244 | 242,2020/07/19,Line_3,-0.0,I 245 | 243,2020/07/19,Line_1,-0.0,J 246 | 244,2020/07/19,Line_2,0.0,J 247 | 245,2020/07/19,Line_3,-0.0,J 248 | 246,2020/07/19,Line_1,-0.0,K 249 | 247,2020/07/19,Line_2,0.0,K 250 | 248,2020/07/19,Line_3,-0.0,K 251 | 249,2020/07/19,Line_1,-0.0,L 252 | 250,2020/07/19,Line_2,-0.0,L 253 | 251,2020/07/19,Line_3,-0.0,L 254 | -------------------------------------------------------------------------------- /Planning_optimization_part4/Planning_model4_list.csv: -------------------------------------------------------------------------------- 1 | ,Date,Line,Qty,Customer_Order 2 | 0,2020/07/13,Line_1,320.0,A 3 | 1,2020/07/13,Line_2,-0.0,A 4 | 2,2020/07/13,Line_3,280.0,A 5 | 3,2020/07/13,Line_1,0.0,B 6 | 4,2020/07/13,Line_2,-0.0,B 7 | 5,2020/07/13,Line_3,0.0,B 8 | 6,2020/07/13,Line_1,0.0,C 9 | 7,2020/07/13,Line_2,-0.0,C 10 | 8,2020/07/13,Line_3,0.0,C 11 | 9,2020/07/13,Line_1,0.0,D 12 | 10,2020/07/13,Line_2,-0.0,D 13 | 11,2020/07/13,Line_3,0.0,D 14 | 12,2020/07/13,Line_1,0.0,E 15 | 13,2020/07/13,Line_2,-0.0,E 16 | 14,2020/07/13,Line_3,0.0,E 17 | 15,2020/07/13,Line_1,0.0,F 18 | 16,2020/07/13,Line_2,-0.0,F 19 | 17,2020/07/13,Line_3,0.0,F 20 | 18,2020/07/13,Line_1,0.0,G 21 | 19,2020/07/13,Line_2,-0.0,G 22 | 20,2020/07/13,Line_3,0.0,G 23 | 21,2020/07/13,Line_1,0.0,H 24 | 22,2020/07/13,Line_2,-0.0,H 25 | 23,2020/07/13,Line_3,0.0,H 26 | 24,2020/07/13,Line_1,0.0,I 27 | 25,2020/07/13,Line_2,-0.0,I 28 | 26,2020/07/13,Line_3,0.0,I 29 | 27,2020/07/13,Line_1,0.0,J 30 | 28,2020/07/13,Line_2,-0.0,J 31 | 29,2020/07/13,Line_3,0.0,J 32 | 30,2020/07/13,Line_1,0.0,K 33 | 31,2020/07/13,Line_2,-0.0,K 34 | 32,2020/07/13,Line_3,0.0,K 35 | 33,2020/07/13,Line_1,0.0,L 36 | 34,2020/07/13,Line_2,-0.0,L 37 | 35,2020/07/13,Line_3,0.0,L 38 | 36,2020/07/14,Line_1,0.0,A 39 | 37,2020/07/14,Line_2,-0.0,A 40 | 38,2020/07/14,Line_3,0.0,A 41 | 39,2020/07/14,Line_1,-0.0,B 42 | 40,2020/07/14,Line_2,-0.0,B 43 | 41,2020/07/14,Line_3,200.0,B 44 | 42,2020/07/14,Line_1,-0.0,C 45 | 43,2020/07/14,Line_2,-0.0,C 46 | 44,2020/07/14,Line_3,150.0,C 47 | 45,2020/07/14,Line_1,0.0,D 48 | 46,2020/07/14,Line_2,-0.0,D 49 | 47,2020/07/14,Line_3,0.0,D 50 | 48,2020/07/14,Line_1,0.0,E 51 | 49,2020/07/14,Line_2,-0.0,E 52 | 50,2020/07/14,Line_3,0.0,E 53 | 51,2020/07/14,Line_1,0.0,F 54 | 52,2020/07/14,Line_2,-0.0,F 55 | 53,2020/07/14,Line_3,0.0,F 56 | 54,2020/07/14,Line_1,0.0,G 57 | 55,2020/07/14,Line_2,-0.0,G 58 | 56,2020/07/14,Line_3,0.0,G 59 | 57,2020/07/14,Line_1,0.0,H 60 | 58,2020/07/14,Line_2,-0.0,H 61 | 59,2020/07/14,Line_3,0.0,H 62 | 60,2020/07/14,Line_1,0.0,I 63 | 61,2020/07/14,Line_2,-0.0,I 64 | 62,2020/07/14,Line_3,0.0,I 65 | 63,2020/07/14,Line_1,0.0,J 66 | 64,2020/07/14,Line_2,-0.0,J 67 | 65,2020/07/14,Line_3,0.0,J 68 | 66,2020/07/14,Line_1,0.0,K 69 | 67,2020/07/14,Line_2,-0.0,K 70 | 68,2020/07/14,Line_3,0.0,K 71 | 69,2020/07/14,Line_1,0.0,L 72 | 70,2020/07/14,Line_2,-0.0,L 73 | 71,2020/07/14,Line_3,0.0,L 74 | 72,2020/07/15,Line_1,-0.0,A 75 | 73,2020/07/15,Line_2,0.0,A 76 | 74,2020/07/15,Line_3,-0.0,A 77 | 75,2020/07/15,Line_1,0.0,B 78 | 76,2020/07/15,Line_2,0.0,B 79 | 77,2020/07/15,Line_3,0.0,B 80 | 78,2020/07/15,Line_1,-0.0,C 81 | 79,2020/07/15,Line_2,0.0,C 82 | 80,2020/07/15,Line_3,-0.0,C 83 | 81,2020/07/15,Line_1,120.0,D 84 | 82,2020/07/15,Line_2,-0.0,D 85 | 83,2020/07/15,Line_3,30.0,D 86 | 84,2020/07/15,Line_1,-0.0,E 87 | 85,2020/07/15,Line_2,-0.0,E 88 | 86,2020/07/15,Line_3,200.0,E 89 | 87,2020/07/15,Line_1,150.0,F 90 | 88,2020/07/15,Line_2,-0.0,F 91 | 89,2020/07/15,Line_3,50.0,F 92 | 90,2020/07/15,Line_1,-0.0,G 93 | 91,2020/07/15,Line_2,0.0,G 94 | 92,2020/07/15,Line_3,-0.0,G 95 | 93,2020/07/15,Line_1,-0.0,H 96 | 94,2020/07/15,Line_2,0.0,H 97 | 95,2020/07/15,Line_3,-0.0,H 98 | 96,2020/07/15,Line_1,-0.0,I 99 | 97,2020/07/15,Line_2,0.0,I 100 | 98,2020/07/15,Line_3,-0.0,I 101 | 99,2020/07/15,Line_1,0.0,J 102 | 100,2020/07/15,Line_2,0.0,J 103 | 101,2020/07/15,Line_3,0.0,J 104 | 102,2020/07/15,Line_1,0.0,K 105 | 103,2020/07/15,Line_2,0.0,K 106 | 104,2020/07/15,Line_3,0.0,K 107 | 105,2020/07/15,Line_1,0.0,L 108 | 106,2020/07/15,Line_2,0.0,L 109 | 107,2020/07/15,Line_3,0.0,L 110 | 108,2020/07/16,Line_1,-0.0,A 111 | 109,2020/07/16,Line_2,-0.0,A 112 | 110,2020/07/16,Line_3,-0.0,A 113 | 111,2020/07/16,Line_1,-0.0,B 114 | 112,2020/07/16,Line_2,-0.0,B 115 | 113,2020/07/16,Line_3,-0.0,B 116 | 114,2020/07/16,Line_1,-0.0,C 117 | 115,2020/07/16,Line_2,-0.0,C 118 | 116,2020/07/16,Line_3,-0.0,C 119 | 117,2020/07/16,Line_1,-0.0,D 120 | 118,2020/07/16,Line_2,-0.0,D 121 | 119,2020/07/16,Line_3,-0.0,D 122 | 120,2020/07/16,Line_1,-0.0,E 123 | 121,2020/07/16,Line_2,-0.0,E 124 | 122,2020/07/16,Line_3,-0.0,E 125 | 123,2020/07/16,Line_1,-0.0,F 126 | 124,2020/07/16,Line_2,-0.0,F 127 | 125,2020/07/16,Line_3,-0.0,F 128 | 126,2020/07/16,Line_1,-0.0,G 129 | 127,2020/07/16,Line_2,-0.0,G 130 | 128,2020/07/16,Line_3,-0.0,G 131 | 129,2020/07/16,Line_1,-0.0,H 132 | 130,2020/07/16,Line_2,-0.0,H 133 | 131,2020/07/16,Line_3,-0.0,H 134 | 132,2020/07/16,Line_1,-0.0,I 135 | 133,2020/07/16,Line_2,-0.0,I 136 | 134,2020/07/16,Line_3,-0.0,I 137 | 135,2020/07/16,Line_1,-0.0,J 138 | 136,2020/07/16,Line_2,-0.0,J 139 | 137,2020/07/16,Line_3,-0.0,J 140 | 138,2020/07/16,Line_1,-0.0,K 141 | 139,2020/07/16,Line_2,-0.0,K 142 | 140,2020/07/16,Line_3,-0.0,K 143 | 141,2020/07/16,Line_1,-0.0,L 144 | 142,2020/07/16,Line_2,-0.0,L 145 | 143,2020/07/16,Line_3,-0.0,L 146 | 144,2020/07/17,Line_1,0.0,A 147 | 145,2020/07/17,Line_2,0.0,A 148 | 146,2020/07/17,Line_3,0.0,A 149 | 147,2020/07/17,Line_1,0.0,B 150 | 148,2020/07/17,Line_2,0.0,B 151 | 149,2020/07/17,Line_3,0.0,B 152 | 150,2020/07/17,Line_1,0.0,C 153 | 151,2020/07/17,Line_2,0.0,C 154 | 152,2020/07/17,Line_3,0.0,C 155 | 153,2020/07/17,Line_1,-0.0,D 156 | 154,2020/07/17,Line_2,-0.0,D 157 | 155,2020/07/17,Line_3,-0.0,D 158 | 156,2020/07/17,Line_1,0.0,E 159 | 157,2020/07/17,Line_2,0.0,E 160 | 158,2020/07/17,Line_3,0.0,E 161 | 159,2020/07/17,Line_1,0.0,F 162 | 160,2020/07/17,Line_2,0.0,F 163 | 161,2020/07/17,Line_3,0.0,F 164 | 162,2020/07/17,Line_1,400.0,G 165 | 163,2020/07/17,Line_2,-0.0,G 166 | 164,2020/07/17,Line_3,-0.0,G 167 | 165,2020/07/17,Line_1,-0.0,H 168 | 166,2020/07/17,Line_2,350.0,H 169 | 167,2020/07/17,Line_3,50.0,H 170 | 168,2020/07/17,Line_1,-0.0,I 171 | 169,2020/07/17,Line_2,-0.0,I 172 | 170,2020/07/17,Line_3,400.0,I 173 | 171,2020/07/17,Line_1,-0.0,J 174 | 172,2020/07/17,Line_2,-0.0,J 175 | 173,2020/07/17,Line_3,-0.0,J 176 | 174,2020/07/17,Line_1,-0.0,K 177 | 175,2020/07/17,Line_2,-0.0,K 178 | 176,2020/07/17,Line_3,-0.0,K 179 | 177,2020/07/17,Line_1,0.0,L 180 | 178,2020/07/17,Line_2,-0.0,L 181 | 179,2020/07/17,Line_3,-0.0,L 182 | 180,2020/07/18,Line_1,0.0,A 183 | 181,2020/07/18,Line_2,-0.0,A 184 | 182,2020/07/18,Line_3,0.0,A 185 | 183,2020/07/18,Line_1,0.0,B 186 | 184,2020/07/18,Line_2,-0.0,B 187 | 185,2020/07/18,Line_3,0.0,B 188 | 186,2020/07/18,Line_1,0.0,C 189 | 187,2020/07/18,Line_2,-0.0,C 190 | 188,2020/07/18,Line_3,0.0,C 191 | 189,2020/07/18,Line_1,0.0,D 192 | 190,2020/07/18,Line_2,-0.0,D 193 | 191,2020/07/18,Line_3,0.0,D 194 | 192,2020/07/18,Line_1,0.0,E 195 | 193,2020/07/18,Line_2,-0.0,E 196 | 194,2020/07/18,Line_3,0.0,E 197 | 195,2020/07/18,Line_1,0.0,F 198 | 196,2020/07/18,Line_2,-0.0,F 199 | 197,2020/07/18,Line_3,0.0,F 200 | 198,2020/07/18,Line_1,-0.0,G 201 | 199,2020/07/18,Line_2,-0.0,G 202 | 200,2020/07/18,Line_3,-0.0,G 203 | 201,2020/07/18,Line_1,0.0,H 204 | 202,2020/07/18,Line_2,-0.0,H 205 | 203,2020/07/18,Line_3,0.0,H 206 | 204,2020/07/18,Line_1,0.0,I 207 | 205,2020/07/18,Line_2,-0.0,I 208 | 206,2020/07/18,Line_3,0.0,I 209 | 207,2020/07/18,Line_1,-0.0,J 210 | 208,2020/07/18,Line_2,-0.0,J 211 | 209,2020/07/18,Line_3,150.0,J 212 | 210,2020/07/18,Line_1,-0.0,K 213 | 211,2020/07/18,Line_2,-0.0,K 214 | 212,2020/07/18,Line_3,100.0,K 215 | 213,2020/07/18,Line_1,-0.0,L 216 | 214,2020/07/18,Line_2,-0.0,L 217 | 215,2020/07/18,Line_3,200.0,L 218 | 216,2020/07/19,Line_1,-0.0,A 219 | 217,2020/07/19,Line_2,0.0,A 220 | 218,2020/07/19,Line_3,-0.0,A 221 | 219,2020/07/19,Line_1,-0.0,B 222 | 220,2020/07/19,Line_2,0.0,B 223 | 221,2020/07/19,Line_3,-0.0,B 224 | 222,2020/07/19,Line_1,-0.0,C 225 | 223,2020/07/19,Line_2,0.0,C 226 | 224,2020/07/19,Line_3,-0.0,C 227 | 225,2020/07/19,Line_1,-0.0,D 228 | 226,2020/07/19,Line_2,0.0,D 229 | 227,2020/07/19,Line_3,-0.0,D 230 | 228,2020/07/19,Line_1,-0.0,E 231 | 229,2020/07/19,Line_2,0.0,E 232 | 230,2020/07/19,Line_3,-0.0,E 233 | 231,2020/07/19,Line_1,-0.0,F 234 | 232,2020/07/19,Line_2,0.0,F 235 | 233,2020/07/19,Line_3,-0.0,F 236 | 234,2020/07/19,Line_1,-0.0,G 237 | 235,2020/07/19,Line_2,0.0,G 238 | 236,2020/07/19,Line_3,-0.0,G 239 | 237,2020/07/19,Line_1,-0.0,H 240 | 238,2020/07/19,Line_2,0.0,H 241 | 239,2020/07/19,Line_3,-0.0,H 242 | 240,2020/07/19,Line_1,-0.0,I 243 | 241,2020/07/19,Line_2,0.0,I 244 | 242,2020/07/19,Line_3,-0.0,I 245 | 243,2020/07/19,Line_1,-0.0,J 246 | 244,2020/07/19,Line_2,0.0,J 247 | 245,2020/07/19,Line_3,-0.0,J 248 | 246,2020/07/19,Line_1,-0.0,K 249 | 247,2020/07/19,Line_2,0.0,K 250 | 248,2020/07/19,Line_3,-0.0,K 251 | 249,2020/07/19,Line_1,-0.0,L 252 | 250,2020/07/19,Line_2,-0.0,L 253 | 251,2020/07/19,Line_3,-0.0,L 254 | -------------------------------------------------------------------------------- /Planning_optimization_part2/Model3.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: Baptiste Soulard 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | import datetime 11 | from typing import List, Dict 12 | import altair as alt 13 | import datapane as dp 14 | 15 | 16 | def optimize_planning( 17 | timeline: List[str], 18 | workcenters: List[str], 19 | needs: Dict[str, int], 20 | wc_cost_reg: Dict[str, int], 21 | wc_cost_ot: Dict[str, int], 22 | wc_cost_we: Dict[str, int], 23 | inventory_carrying_cost: int, 24 | ) -> pd.DataFrame: 25 | 26 | # Split weekdays/weekends 27 | weekdays = [] 28 | weekend = [] 29 | for date in timeline: 30 | day = datetime.datetime.strptime(date, "%Y/%m/%d") 31 | if day.weekday() < 5: 32 | weekdays.append(date) 33 | else: 34 | weekend.append(date) 35 | 36 | # Initiate optimization model 37 | model = gurobipy.Model("Optimize production planning") 38 | 39 | # DEFINE VARIABLES 40 | # Load variables (hours) - regular and overtime 41 | reg_hours = model.addVars( 42 | timeline, 43 | workcenters, 44 | lb=7, 45 | ub=8, 46 | vtype=gurobipy.GRB.INTEGER, 47 | name="Regular hours", 48 | ) 49 | ot_hours = model.addVars( 50 | timeline, 51 | workcenters, 52 | lb=0, 53 | ub=4, 54 | vtype=gurobipy.GRB.INTEGER, 55 | name="Overtime hours", 56 | ) 57 | 58 | # Status of the line ( 0 = closed, 1 = opened) 59 | line_opening = model.addVars( 60 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open status" 61 | ) 62 | 63 | # Variable total load (hours) 64 | total_hours = model.addVars( 65 | timeline, 66 | workcenters, 67 | lb=0, 68 | ub=12, 69 | vtype=gurobipy.GRB.INTEGER, 70 | name="Total hours", 71 | ) 72 | 73 | # Variable cost 74 | labor_cost = model.addVars( 75 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Labor cost" 76 | ) 77 | 78 | # Set the value of total load (regular + overtime) 79 | model.addConstrs( 80 | ( 81 | total_hours[(date, wc)] 82 | == (reg_hours[(date, wc)] + ot_hours[(date, wc)]) * line_opening[(date, wc)] 83 | for date in timeline 84 | for wc in workcenters 85 | ), 86 | name="Link total hours - reg/ot hours", 87 | ) 88 | 89 | # Set the value of cost (hours * hourly cost) 90 | model.addConstrs( 91 | ( 92 | labor_cost[(date, wc)] 93 | == reg_hours[(date, wc)] * wc_cost_reg[wc] * line_opening[(date, wc)] 94 | + ot_hours[(date, wc)] * wc_cost_ot[wc] * line_opening[(date, wc)] 95 | for date in weekdays 96 | for wc in workcenters 97 | ), 98 | name="Link labor cost - working hours - wd", 99 | ) 100 | 101 | model.addConstrs( 102 | ( 103 | labor_cost[(date, wc)] == total_hours[(date, wc)] * wc_cost_we[wc] 104 | for date in weekend 105 | for wc in workcenters 106 | ), 107 | name="Link labor cost - working hours - we", 108 | ) 109 | 110 | # CONSTRAINTS 111 | # Constraint: Total hours of production = required production time 112 | model.addConstr( 113 | gurobipy.quicksum( 114 | total_hours[(date, wc)] for date in timeline for wc in workcenters 115 | ) 116 | == gurobipy.quicksum(needs[date] for date in timeline) 117 | ) 118 | 119 | # Create variable "early production" and "inventory costs" 120 | early_prod = model.addVars( 121 | timeline, lb=0, vtype=gurobipy.GRB.INTEGER, name="early prod" 122 | ) 123 | 124 | # Set the value of gap for early production 125 | for k in range(len(timeline)): 126 | model.addConstr( 127 | early_prod[timeline[k]] 128 | == gurobipy.quicksum( 129 | total_hours[(date, wc)] 130 | for date in timeline[: k + 1] 131 | for wc in workcenters 132 | ) 133 | - (gurobipy.quicksum(needs[date] for date in timeline[: k + 1])) 134 | ) 135 | 136 | # DEFINE MODEL 137 | # Objective : minimize a function 138 | model.ModelSense = gurobipy.GRB.MINIMIZE 139 | # Function to minimize 140 | objective = 0 141 | objective += gurobipy.quicksum( 142 | labor_cost[(date, wc)] for date in timeline for wc in workcenters 143 | ) 144 | objective += gurobipy.quicksum(early_prod[date] * inventory_carrying_cost for date in timeline) 145 | 146 | # SOLVE MODEL 147 | model.setObjective(objective) 148 | model.optimize() 149 | 150 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 151 | 152 | print("Total cost = $" + str(model.ObjVal)) 153 | 154 | # model.write("Planning_optimization.lp") 155 | # file = open("Planning_optimization.lp", 'r') 156 | # print(file.read()) 157 | # file.close() 158 | 159 | return sol 160 | 161 | 162 | def plot_planning(planning, need, timeline): 163 | # Plot graph - Requirement 164 | source = need.copy() 165 | source = source.rename(columns={0: "Hours"}) 166 | source["Date"] = source.index 167 | 168 | bars_need = ( 169 | alt.Chart(source) 170 | .mark_bar() 171 | .encode( 172 | y="Hours:Q", 173 | column=alt.Column("Date:N"), 174 | tooltip=["Date", "Hours"], 175 | ) 176 | .interactive() 177 | .properties( 178 | width=550 / len(timeline) - 22, 179 | height=75, 180 | title='Requirement', 181 | ) 182 | ) 183 | 184 | # Plot graph - Optimized planning 185 | source = planning.filter(like="Total hours", axis=0).copy() 186 | source["Date"] = list(source.index.values) 187 | source = source.rename(columns={"Solution": "Hours"}).reset_index() 188 | source[["Date", "Line"]] = source["Date"].str.split(",", expand=True) 189 | source["Date"] = source["Date"].str.split("[").str[1] 190 | source["Line"] = source["Line"].str.split("]").str[0] 191 | source["Min capacity"] = 7 192 | source["Max capacity"] = 12 193 | source = source.round({"Hours": 1}) 194 | source["Load%"] = pd.Series( 195 | ["{0:.0f}%".format(val / 8 * 100) for val in source["Hours"]], 196 | index=source.index, 197 | ) 198 | 199 | bars = ( 200 | alt.Chart(source) 201 | .mark_bar() 202 | .encode( 203 | x="Line:N", 204 | y="Hours:Q", 205 | column=alt.Column("Date:N"), 206 | color="Line:N", 207 | tooltip=["Date", "Line", "Hours", "Load%"], 208 | ) 209 | .interactive() 210 | .properties( 211 | width=550 / len(timeline) - 22, 212 | height=150, 213 | title="Optimized Production Schedule", 214 | ) 215 | ) 216 | 217 | chart = alt.vconcat(bars, bars_need) 218 | chart.save("planning_time_model3.html") 219 | 220 | dp.Report(dp.Plot(chart, caption="Production schedule model 3 - Time")).publish( 221 | name="Optimized production schedule model 3 - Time", 222 | description="Optimized production schedule model 3 - Time", 223 | open=True, 224 | visibily="PUBLIC", 225 | ) 226 | 227 | 228 | # Define daily requirement 229 | daily_requirements: Dict[str, int] = { 230 | "2020/7/13": 30, 231 | "2020/7/14": 10, 232 | "2020/7/15": 34, 233 | "2020/7/16": 25, 234 | "2020/7/17": 23, 235 | "2020/7/18": 24, 236 | "2020/7/19": 25, 237 | } 238 | 239 | calendar: List[str] = list(daily_requirements.keys()) 240 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 241 | 242 | # Define hourly cost per line - regular, overtime and weekend 243 | reg_costs_per_line = {"Line_1": 245, "Line_2": 315, "Line_3": 245} 244 | ot_costs_per_line = { 245 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 246 | } 247 | we_costs_per_line = { 248 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 249 | } 250 | 251 | storage_cost = 25 252 | 253 | lines: List[str] = list(reg_costs_per_line.keys()) 254 | 255 | # Optimize planning 256 | solution = optimize_planning( 257 | calendar, 258 | lines, 259 | daily_requirements, 260 | reg_costs_per_line, 261 | ot_costs_per_line, 262 | we_costs_per_line, 263 | storage_cost, 264 | ) 265 | 266 | # Plot the new planning 267 | plot_planning(solution, daily_requirements_df, calendar) 268 | -------------------------------------------------------------------------------- /temp/Model4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: soulba01 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | from matplotlib import pyplot as plt 11 | import datetime 12 | from typing import List, Dict 13 | 14 | 15 | def optimize_planning( 16 | timeline: List[str], 17 | workcenters: List[str], 18 | needs: Dict[str, int], 19 | wc_cost_reg: Dict[str, int], 20 | wc_cost_ot: Dict[str, int], 21 | wc_cost_we: Dict[str, int], 22 | inventory_cost: int, 23 | ) -> pd.DataFrame: 24 | 25 | # Weekdays / Weekends 26 | weekdays = [] 27 | weekend = [] 28 | for i in timeline: 29 | date = datetime.datetime.strptime(i, "%Y/%m/%d") 30 | if date.weekday() < 5: 31 | weekdays.append(i) 32 | else: 33 | weekend.append(i) 34 | 35 | # Initiate optimization model 36 | model = gurobipy.Model("Optimize production planning") 37 | 38 | # DEFINE VARIABLES 39 | # Load variables (hours) - regular and overtime 40 | reg_hours = model.addVars( 41 | timeline, 42 | workcenters, 43 | lb=7, 44 | ub=8, 45 | vtype=gurobipy.GRB.INTEGER, 46 | name="Regular hours", 47 | ) 48 | ot_hours = model.addVars( 49 | timeline, workcenters, lb=0, ub=4, vtype=gurobipy.GRB.INTEGER, name="OT hours" 50 | ) 51 | # Status of the line ( 0 = closed, 1 = opened) 52 | line_opening = model.addVars( 53 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open" 54 | ) 55 | # Variable total load (hours) 56 | total_hours = model.addVars( 57 | timeline, 58 | workcenters, 59 | lb=0, 60 | ub=12, 61 | vtype=gurobipy.GRB.INTEGER, 62 | name="Total hours", 63 | ) 64 | # Variable cost 65 | cost = model.addVars( 66 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Cost" 67 | ) 68 | 69 | # Set the value of cost (hours * hourly cost) 70 | model.addConstrs( 71 | ( 72 | ( 73 | cost[(i, j)] 74 | == reg_hours[(i, j)] * wc_cost_reg[j] * line_opening[(i, j)] 75 | + ot_hours[(i, j)] * wc_cost_ot[j] * line_opening[(i, j)] 76 | for i in weekdays 77 | for j in workcenters 78 | ) 79 | ), 80 | name="Cost weekdays", 81 | ) 82 | model.addConstrs( 83 | ( 84 | ( 85 | cost[(i, j)] 86 | == (reg_hours[(i, j)] + ot_hours[(i, j)]) 87 | * wc_cost_we[j] 88 | * line_opening[(i, j)] 89 | for i in weekend 90 | for j in workcenters 91 | ) 92 | ), 93 | name="Cost weekend", 94 | ) 95 | 96 | # Set the value of total load (regular + overtime) 97 | model.addConstrs( 98 | ( 99 | ( 100 | total_hours[(i, j)] 101 | == (reg_hours[(i, j)] + ot_hours[(i, j)]) * line_opening[(i, j)] 102 | for i in timeline 103 | for j in workcenters 104 | ) 105 | ), 106 | name="Total hours = reg + OT", 107 | ) 108 | 109 | # Constraint: requirement <= Load of the previous days 110 | print(needs) 111 | print(timeline) 112 | for k in range(len(timeline)): 113 | model.addConstr( 114 | ( 115 | gurobipy.quicksum( 116 | total_hours[(i, j)] for i in timeline[: k + 1] for j in workcenters 117 | ) 118 | ) 119 | >= (gurobipy.quicksum(needs[i] for i in timeline[: k + 1])) 120 | ) 121 | 122 | # Constraint: Total hours of production = required production time 123 | model.addConstr( 124 | ( 125 | gurobipy.quicksum( 126 | total_hours[(day, line)] for day in timeline for line in workcenters 127 | ) 128 | ) 129 | == (gurobipy.quicksum(needs[day] for day in timeline)) 130 | ) 131 | 132 | # Create variable "early production" and "inventory costs" 133 | early_prod = model.addVars( 134 | timeline, lb=0, vtype=gurobipy.GRB.INTEGER, name="early prod" 135 | ) 136 | inventory_costs = model.addVars( 137 | timeline, lb=0, vtype=gurobipy.GRB.INTEGER, name="inventory costs" 138 | ) 139 | 140 | # Set the value of gap for early production 141 | for k in range(len(timeline)): 142 | model.addConstr( 143 | ( 144 | early_prod[timeline[k]] 145 | == ( 146 | gurobipy.quicksum( 147 | total_hours[(i, j)] for j in lines for i in timeline[: k + 1] 148 | ) 149 | ) 150 | - (gurobipy.quicksum(needs[i] for i in timeline[: k + 1])) 151 | ) 152 | ) 153 | 154 | # Set the value of inventory costs 155 | for k in range(len(timeline)): 156 | model.addConstr( 157 | (inventory_costs[timeline[k]] == early_prod[timeline[k]] * inventory_cost) 158 | ) 159 | 160 | # DEFINE MODEL 161 | # Objective : minimize a function 162 | model.ModelSense = gurobipy.GRB.MINIMIZE 163 | # Function to minimize 164 | optimization_var = gurobipy.quicksum( 165 | cost[(i, j)] for i in timeline for j in workcenters 166 | ) + gurobipy.quicksum(inventory_costs[i] for i in timeline) 167 | 168 | objective = 0 169 | objective += optimization_var 170 | 171 | # SOLVE MODEL 172 | model.setObjective(objective) 173 | model.optimize() 174 | 175 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 176 | sol = sol.filter(like="Total hours", axis=0) 177 | 178 | print("Total cost = $" + str(model.ObjVal)) 179 | 180 | # FORMAT THE RESULT 181 | planning = sol 182 | planning["Date"] = list(planning.index.values) 183 | planning[["Date", "Line"]] = planning["Date"].str.split(",", expand=True) 184 | planning["Date"] = planning["Date"].str.split("[").str[1] 185 | planning["Line"] = planning["Line"].str.split("]").str[0] 186 | planning = planning.pivot(index="Line", columns="Date", values="Solution") 187 | 188 | return planning 189 | 190 | 191 | def plot_planning(plan, need): 192 | plan = plan.T 193 | plan["Min capacity"] = 7 194 | plan["Max capacity"] = 12 195 | 196 | my_colors = ["skyblue", "salmon", "lightgreen"] 197 | 198 | fig, axs = plt.subplots(2) 199 | need.plot( 200 | kind="bar", 201 | width=0.2, 202 | title="Need in h per day", 203 | ax=axs[0], 204 | color="midnightblue", 205 | ) 206 | 207 | plan[["Min capacity", "Max capacity"]].plot( 208 | rot=90, ax=axs[1], style=["b", "b--"], linewidth=1 209 | ) 210 | 211 | plan.drop(["Min capacity", "Max capacity"], axis=1).plot( 212 | kind="bar", title="Load in h per line", ax=axs[1], color=my_colors 213 | ) 214 | 215 | axs[0].tick_params(axis="x", labelsize=7) 216 | axs[0].tick_params(axis="y", labelsize=7) 217 | axs[0].get_legend().remove() 218 | axs[0].set_xticklabels([]) 219 | axs[1].tick_params(axis="x", labelsize=7) 220 | axs[1].tick_params(axis="y", labelsize=7) 221 | axs[1].get_legend().remove() 222 | 223 | plt.savefig("Result_Model3.png", bbox_inches="tight", dpi=1200) 224 | axe = plt.show() 225 | return axe 226 | 227 | 228 | # Define the daily requirement 229 | daily_requirements: Dict[str, int] = { 230 | "2020/7/13": 30, 231 | "2020/7/14": 10, 232 | "2020/7/15": 34, 233 | "2020/7/16": 23, 234 | "2020/7/17": 23, 235 | "2020/7/18": 24, 236 | "2020/7/19": 25, 237 | } 238 | 239 | calendar: List[str] = list(daily_requirements.keys()) 240 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 241 | 242 | # Define the hourly cost per line - regular, overtime and weekend 243 | reg_costs_per_line = {"Curtain_C1": 350, "Curtain_C2": 300, "Curtain_C3": 350} 244 | ot_costs_per_line = { 245 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 246 | } 247 | we_costs_per_line = { 248 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 249 | } 250 | 251 | early_prod_cost = 17 252 | 253 | lines: List[str] = list(reg_costs_per_line.keys()) 254 | 255 | # Optimize the planning 256 | solution = optimize_planning( 257 | calendar, 258 | lines, 259 | daily_requirements, 260 | reg_costs_per_line, 261 | ot_costs_per_line, 262 | we_costs_per_line, 263 | early_prod_cost, 264 | ) 265 | 266 | # Plot the new planning 267 | plot_planning(solution, daily_requirements_df) 268 | -------------------------------------------------------------------------------- /temp/Model5.py: -------------------------------------------------------------------------------- 1 | # Import required packages 2 | import pandas as pd 3 | import gurobipy 4 | from matplotlib import pyplot as plt 5 | import datetime 6 | from typing import List, Dict 7 | 8 | 9 | def optimize_planning( 10 | timeline: List[str], 11 | workcenters: List[str], 12 | needs: Dict[str, int], 13 | wc_cost_reg: Dict[str, int], 14 | wc_cost_ot: Dict[str, int], 15 | wc_cost_we: Dict[str, int], 16 | inventory_cost: int, 17 | delay_cost: int, 18 | ) -> pd.DataFrame: 19 | 20 | # Weekdays / Weekends 21 | weekdays = [] 22 | weekend = [] 23 | for i in timeline: 24 | date = datetime.datetime.strptime(i, "%Y/%m/%d") 25 | if date.weekday() < 5: 26 | weekdays.append(i) 27 | else: 28 | weekend.append(i) 29 | 30 | # Initiate optimization model 31 | model = gurobipy.Model("Optimize production planning") 32 | 33 | # DEFINE VARIABLES 34 | # Load variables (hours) - regular and overtime 35 | reg_hours = model.addVars( 36 | timeline, 37 | workcenters, 38 | lb=7, 39 | ub=8, 40 | vtype=gurobipy.GRB.CONTINUOUS, 41 | name="Regular hours", 42 | ) 43 | ot_hours = model.addVars( 44 | timeline, 45 | workcenters, 46 | lb=0, 47 | ub=4, 48 | vtype=gurobipy.GRB.CONTINUOUS, 49 | name="OT hours", 50 | ) 51 | # Status of the line ( 0 = closed, 1 = opened) 52 | line_opening = model.addVars( 53 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open" 54 | ) 55 | # Variable total load (hours) 56 | total_hours = model.addVars( 57 | timeline, 58 | workcenters, 59 | vtype=gurobipy.GRB.CONTINUOUS, 60 | name="Total hours", 61 | ) 62 | # Variable cost 63 | cost = model.addVars( 64 | timeline, workcenters, vtype=gurobipy.GRB.CONTINUOUS, name="Cost" 65 | ) 66 | 67 | # Set the value of cost (hours * hourly cost) 68 | model.addConstrs( 69 | ( 70 | cost[(date, wc)] 71 | == reg_hours[(date, wc)] * wc_cost_reg[wc] * line_opening[(date, wc)] 72 | + ot_hours[(date, wc)] * wc_cost_ot[wc] * line_opening[(date, wc)] 73 | for date in weekdays 74 | for wc in workcenters 75 | ), 76 | name="Cost weekdays", 77 | ) 78 | model.addConstrs( 79 | ( 80 | cost[(date, wc)] 81 | == (reg_hours[(date, wc)] + ot_hours[(date, wc)]) 82 | * wc_cost_we[wc] 83 | * line_opening[(date, wc)] 84 | for date in weekend 85 | for wc in workcenters 86 | ), 87 | name="Cost weekend", 88 | ) 89 | 90 | # Set the value of total load (regular + overtime) 91 | model.addConstrs( 92 | ( 93 | total_hours[(date, wc)] 94 | == (reg_hours[(date, wc)] + ot_hours[(date, wc)]) * line_opening[(date, wc)] 95 | for date in timeline 96 | for wc in workcenters 97 | ), 98 | name="Total hours = reg + OT", 99 | ) 100 | 101 | # Constraint: Total hours of production = required production time 102 | model.addConstr( 103 | ( 104 | gurobipy.quicksum( 105 | total_hours[(date, wc)] for date in timeline for wc in workcenters 106 | ) 107 | == gurobipy.quicksum(needs[date] for date in timeline) 108 | ), 109 | name="Total hours = need", 110 | ) 111 | 112 | # Create variable "early production", "late production" and "inventory costs" 113 | # Gap early/late production 114 | gap_prod = model.addVars( 115 | timeline, 116 | lb=-10000, 117 | ub=10000, 118 | vtype=gurobipy.GRB.CONTINUOUS, 119 | name="gapProd", 120 | ) 121 | abs_gap_prod = model.addVars( 122 | timeline, 123 | vtype=gurobipy.GRB.CONTINUOUS, 124 | name="absGapProd", 125 | ) 126 | 127 | # Set the value of gap production vs need 128 | model.addConstrs( 129 | ( 130 | ( 131 | gap_prod[timeline[k]] 132 | == gurobipy.quicksum( 133 | total_hours[(date, wc)] 134 | for date in timeline[: k + 1] 135 | for wc in workcenters 136 | ) 137 | - (gurobipy.quicksum(needs[date] for date in timeline[: k + 1])) 138 | ) 139 | for k in range(len(timeline)) 140 | ), 141 | name="gap prod", 142 | ) 143 | 144 | model.addConstrs( 145 | ((abs_gap_prod[date] == gurobipy.abs_(gap_prod[date])) for date in timeline), 146 | name="abs gap prod", 147 | ) 148 | 149 | # Create variable "early production" and "inventory costs" 150 | early_prod = model.addVars( 151 | timeline, 152 | vtype=gurobipy.GRB.CONTINUOUS, 153 | name="early prod", 154 | ) 155 | inventory_costs = model.addVars( 156 | timeline, 157 | vtype=gurobipy.GRB.CONTINUOUS, 158 | name="inventory costs", 159 | ) 160 | 161 | # Set the value of early production 162 | model.addConstrs( 163 | ( 164 | (early_prod[date] == (gap_prod[date] + abs_gap_prod[date]) / 2) 165 | for date in timeline 166 | ), 167 | name="early prod", 168 | ) 169 | 170 | # Set the value of inventory costs 171 | model.addConstrs( 172 | ( 173 | (inventory_costs[date] == early_prod[date] * inventory_cost) 174 | for date in timeline 175 | ), 176 | name="inventory costs", 177 | ) 178 | 179 | # Create variable "late production" and "delay costs" 180 | late_prod = model.addVars( 181 | timeline, 182 | vtype=gurobipy.GRB.CONTINUOUS, 183 | name="early prod", 184 | ) 185 | delay_costs = model.addVars( 186 | timeline, 187 | vtype=gurobipy.GRB.CONTINUOUS, 188 | name="inventory costs", 189 | ) 190 | 191 | # Set the value of late production 192 | model.addConstrs( 193 | ( 194 | (late_prod[date] == (abs_gap_prod[date] - gap_prod[date]) / 2) 195 | for date in timeline 196 | ), 197 | name="late prod", 198 | ) 199 | 200 | # Set the value of delay costs 201 | model.addConstrs( 202 | ((delay_costs[date] == late_prod[date] * delay_cost) for date in timeline), 203 | name="delay costs", 204 | ) 205 | 206 | # DEFINE MODEL 207 | # Objective : minimize a function 208 | model.ModelSense = gurobipy.GRB.MINIMIZE 209 | # Function to minimize 210 | optimization_var = ( 211 | gurobipy.quicksum(cost[(date, wc)] for date in timeline for wc in workcenters) 212 | + gurobipy.quicksum(inventory_costs[date] for date in timeline) 213 | + gurobipy.quicksum(delay_costs[date] for date in timeline) 214 | ) 215 | 216 | objective = 0 217 | objective += optimization_var 218 | 219 | # SOLVE MODEL 220 | model.setObjective(objective) 221 | model.optimize() 222 | 223 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 224 | sol = sol.filter(like="Total hours", axis=0) 225 | 226 | print("Total cost = $" + str(model.ObjVal)) 227 | 228 | # FORMAT THE RESULT 229 | planning = sol 230 | planning["Date"] = list(planning.index.values) 231 | planning[["Date", "Line"]] = planning["Date"].str.split(",", expand=True) 232 | planning["Date"] = planning["Date"].str.split("[").str[1] 233 | planning["Line"] = planning["Line"].str.split("]").str[0] 234 | planning = planning.pivot(index="Line", columns="Date", values="Solution") 235 | 236 | return planning 237 | 238 | 239 | def plot_planning(plan, need): 240 | plan = plan.T 241 | plan["Min capacity"] = 7 242 | plan["Max capacity"] = 12 243 | 244 | my_colors = ["skyblue", "salmon", "lightgreen"] 245 | 246 | fig, axs = plt.subplots(2) 247 | need.plot( 248 | kind="bar", 249 | width=0.2, 250 | title="Need in h per day", 251 | ax=axs[0], 252 | color="midnightblue", 253 | ) 254 | 255 | plan[["Min capacity", "Max capacity"]].plot( 256 | rot=90, ax=axs[1], style=["b", "b--"], linewidth=1 257 | ) 258 | 259 | plan.drop(["Min capacity", "Max capacity"], axis=1).plot( 260 | kind="bar", title="Load in h per line", ax=axs[1], color=my_colors 261 | ) 262 | 263 | axs[0].tick_params(axis="x", labelsize=7) 264 | axs[0].tick_params(axis="y", labelsize=7) 265 | axs[0].get_legend().remove() 266 | axs[0].set_xticklabels([]) 267 | axs[1].tick_params(axis="x", labelsize=7) 268 | axs[1].tick_params(axis="y", labelsize=7) 269 | axs[1].get_legend().remove() 270 | 271 | plt.savefig("Result_Model5.png", bbox_inches="tight", dpi=1200) 272 | axe = plt.show() 273 | return axe 274 | 275 | 276 | # Define the daily requirement 277 | daily_requirements: Dict[str, int] = { 278 | "2020/7/13": 30, 279 | "2020/7/14": 10, 280 | "2020/7/15": 34, 281 | "2020/7/16": 23, 282 | "2020/7/17": 23, 283 | "2020/7/18": 24, 284 | "2020/7/19": 25, 285 | } 286 | 287 | calendar: List[str] = list(daily_requirements.keys()) 288 | daily_requirements_df = pd.DataFrame.from_dict(daily_requirements, orient="index") 289 | 290 | # Define the hourly cost per line - regular, overtime and weekend 291 | reg_costs_per_line = {"Curtain_C1": 350, "Curtain_C2": 300, "Curtain_C3": 350} 292 | ot_costs_per_line = { 293 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 294 | } 295 | we_costs_per_line = { 296 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 297 | } 298 | 299 | early_prod_cost = 17 300 | late_prod_cost = 100 301 | 302 | lines: List[str] = list(reg_costs_per_line.keys()) 303 | 304 | # Optimize the planning 305 | solution = optimize_planning( 306 | calendar, 307 | lines, 308 | daily_requirements, 309 | reg_costs_per_line, 310 | ot_costs_per_line, 311 | we_costs_per_line, 312 | early_prod_cost, 313 | late_prod_cost, 314 | ) 315 | 316 | # Plot the new planning 317 | plot_planning(solution, daily_requirements_df) 318 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Inventory_Shortage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part4/Inventory_Shortage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part1/Planning_optimization.lp: -------------------------------------------------------------------------------- 1 | \ Model Optimize production planning 2 | \ LP format - for model browsing. Use MPS format to capture full model detail. 3 | Minimize 4 | Labor_cost[2020/7/13,Line_1] + Labor_cost[2020/7/13,Line_2] 5 | + Labor_cost[2020/7/13,Line_3] + Labor_cost[2020/7/14,Line_1] 6 | + Labor_cost[2020/7/14,Line_2] + Labor_cost[2020/7/14,Line_3] 7 | + Labor_cost[2020/7/15,Line_1] + Labor_cost[2020/7/15,Line_2] 8 | + Labor_cost[2020/7/15,Line_3] + Labor_cost[2020/7/16,Line_1] 9 | + Labor_cost[2020/7/16,Line_2] + Labor_cost[2020/7/16,Line_3] 10 | + Labor_cost[2020/7/17,Line_1] + Labor_cost[2020/7/17,Line_2] 11 | + Labor_cost[2020/7/17,Line_3] + Labor_cost[2020/7/18,Line_1] 12 | + Labor_cost[2020/7/18,Line_2] + Labor_cost[2020/7/18,Line_3] 13 | + Labor_cost[2020/7/19,Line_1] + Labor_cost[2020/7/19,Line_2] 14 | + Labor_cost[2020/7/19,Line_3] 15 | Subject To 16 | Link_total_hours_-_requirement[2020/7/13]: Total_hours[2020/7/13,Line_1] 17 | + Total_hours[2020/7/13,Line_2] + Total_hours[2020/7/13,Line_3] = 30 18 | Link_total_hours_-_requirement[2020/7/14]: Total_hours[2020/7/14,Line_1] 19 | + Total_hours[2020/7/14,Line_2] + Total_hours[2020/7/14,Line_3] = 10 20 | Link_total_hours_-_requirement[2020/7/15]: Total_hours[2020/7/15,Line_1] 21 | + Total_hours[2020/7/15,Line_2] + Total_hours[2020/7/15,Line_3] = 34 22 | Link_total_hours_-_requirement[2020/7/16]: Total_hours[2020/7/16,Line_1] 23 | + Total_hours[2020/7/16,Line_2] + Total_hours[2020/7/16,Line_3] = 25 24 | Link_total_hours_-_requirement[2020/7/17]: Total_hours[2020/7/17,Line_1] 25 | + Total_hours[2020/7/17,Line_2] + Total_hours[2020/7/17,Line_3] = 23 26 | Link_total_hours_-_requirement[2020/7/18]: Total_hours[2020/7/18,Line_1] 27 | + Total_hours[2020/7/18,Line_2] + Total_hours[2020/7/18,Line_3] = 24 28 | Link_total_hours_-_requirement[2020/7/19]: Total_hours[2020/7/19,Line_1] 29 | + Total_hours[2020/7/19,Line_2] + Total_hours[2020/7/19,Line_3] = 25 30 | Link_total_hours_-_reg/ot_hours[2020/7/13,Line_1]: 31 | Total_hours[2020/7/13,Line_1] + [ 32 | - Working_hours[2020/7/13,Line_1] * Open_status[2020/7/13,Line_1] ] = 0 33 | Link_total_hours_-_reg/ot_hours[2020/7/13,Line_2]: 34 | Total_hours[2020/7/13,Line_2] + [ 35 | - Working_hours[2020/7/13,Line_2] * Open_status[2020/7/13,Line_2] ] = 0 36 | Link_total_hours_-_reg/ot_hours[2020/7/13,Line_3]: 37 | Total_hours[2020/7/13,Line_3] + [ 38 | - Working_hours[2020/7/13,Line_3] * Open_status[2020/7/13,Line_3] ] = 0 39 | Link_total_hours_-_reg/ot_hours[2020/7/14,Line_1]: 40 | Total_hours[2020/7/14,Line_1] + [ 41 | - Working_hours[2020/7/14,Line_1] * Open_status[2020/7/14,Line_1] ] = 0 42 | Link_total_hours_-_reg/ot_hours[2020/7/14,Line_2]: 43 | Total_hours[2020/7/14,Line_2] + [ 44 | - Working_hours[2020/7/14,Line_2] * Open_status[2020/7/14,Line_2] ] = 0 45 | Link_total_hours_-_reg/ot_hours[2020/7/14,Line_3]: 46 | Total_hours[2020/7/14,Line_3] + [ 47 | - Working_hours[2020/7/14,Line_3] * Open_status[2020/7/14,Line_3] ] = 0 48 | Link_total_hours_-_reg/ot_hours[2020/7/15,Line_1]: 49 | Total_hours[2020/7/15,Line_1] + [ 50 | - Working_hours[2020/7/15,Line_1] * Open_status[2020/7/15,Line_1] ] = 0 51 | Link_total_hours_-_reg/ot_hours[2020/7/15,Line_2]: 52 | Total_hours[2020/7/15,Line_2] + [ 53 | - Working_hours[2020/7/15,Line_2] * Open_status[2020/7/15,Line_2] ] = 0 54 | Link_total_hours_-_reg/ot_hours[2020/7/15,Line_3]: 55 | Total_hours[2020/7/15,Line_3] + [ 56 | - Working_hours[2020/7/15,Line_3] * Open_status[2020/7/15,Line_3] ] = 0 57 | Link_total_hours_-_reg/ot_hours[2020/7/16,Line_1]: 58 | Total_hours[2020/7/16,Line_1] + [ 59 | - Working_hours[2020/7/16,Line_1] * Open_status[2020/7/16,Line_1] ] = 0 60 | Link_total_hours_-_reg/ot_hours[2020/7/16,Line_2]: 61 | Total_hours[2020/7/16,Line_2] + [ 62 | - Working_hours[2020/7/16,Line_2] * Open_status[2020/7/16,Line_2] ] = 0 63 | Link_total_hours_-_reg/ot_hours[2020/7/16,Line_3]: 64 | Total_hours[2020/7/16,Line_3] + [ 65 | - Working_hours[2020/7/16,Line_3] * Open_status[2020/7/16,Line_3] ] = 0 66 | Link_total_hours_-_reg/ot_hours[2020/7/17,Line_1]: 67 | Total_hours[2020/7/17,Line_1] + [ 68 | - Working_hours[2020/7/17,Line_1] * Open_status[2020/7/17,Line_1] ] = 0 69 | Link_total_hours_-_reg/ot_hours[2020/7/17,Line_2]: 70 | Total_hours[2020/7/17,Line_2] + [ 71 | - Working_hours[2020/7/17,Line_2] * Open_status[2020/7/17,Line_2] ] = 0 72 | Link_total_hours_-_reg/ot_hours[2020/7/17,Line_3]: 73 | Total_hours[2020/7/17,Line_3] + [ 74 | - Working_hours[2020/7/17,Line_3] * Open_status[2020/7/17,Line_3] ] = 0 75 | Link_total_hours_-_reg/ot_hours[2020/7/18,Line_1]: 76 | Total_hours[2020/7/18,Line_1] + [ 77 | - Working_hours[2020/7/18,Line_1] * Open_status[2020/7/18,Line_1] ] = 0 78 | Link_total_hours_-_reg/ot_hours[2020/7/18,Line_2]: 79 | Total_hours[2020/7/18,Line_2] + [ 80 | - Working_hours[2020/7/18,Line_2] * Open_status[2020/7/18,Line_2] ] = 0 81 | Link_total_hours_-_reg/ot_hours[2020/7/18,Line_3]: 82 | Total_hours[2020/7/18,Line_3] + [ 83 | - Working_hours[2020/7/18,Line_3] * Open_status[2020/7/18,Line_3] ] = 0 84 | Link_total_hours_-_reg/ot_hours[2020/7/19,Line_1]: 85 | Total_hours[2020/7/19,Line_1] + [ 86 | - Working_hours[2020/7/19,Line_1] * Open_status[2020/7/19,Line_1] ] = 0 87 | Link_total_hours_-_reg/ot_hours[2020/7/19,Line_2]: 88 | Total_hours[2020/7/19,Line_2] + [ 89 | - Working_hours[2020/7/19,Line_2] * Open_status[2020/7/19,Line_2] ] = 0 90 | Link_total_hours_-_reg/ot_hours[2020/7/19,Line_3]: 91 | Total_hours[2020/7/19,Line_3] + [ 92 | - Working_hours[2020/7/19,Line_3] * Open_status[2020/7/19,Line_3] ] = 0 93 | Link_labor_cost_-_working_hours[2020/7/13,Line_1]: 94 | Labor_cost[2020/7/13,Line_1] + [ 95 | - 245 Open_status[2020/7/13,Line_1] * Total_hours[2020/7/13,Line_1] ] 96 | = 0 97 | Link_labor_cost_-_working_hours[2020/7/13,Line_2]: 98 | Labor_cost[2020/7/13,Line_2] + [ 99 | - 315 Open_status[2020/7/13,Line_2] * Total_hours[2020/7/13,Line_2] ] 100 | = 0 101 | Link_labor_cost_-_working_hours[2020/7/13,Line_3]: 102 | Labor_cost[2020/7/13,Line_3] + [ 103 | - 245 Open_status[2020/7/13,Line_3] * Total_hours[2020/7/13,Line_3] ] 104 | = 0 105 | Link_labor_cost_-_working_hours[2020/7/14,Line_1]: 106 | Labor_cost[2020/7/14,Line_1] + [ 107 | - 245 Open_status[2020/7/14,Line_1] * Total_hours[2020/7/14,Line_1] ] 108 | = 0 109 | Link_labor_cost_-_working_hours[2020/7/14,Line_2]: 110 | Labor_cost[2020/7/14,Line_2] + [ 111 | - 315 Open_status[2020/7/14,Line_2] * Total_hours[2020/7/14,Line_2] ] 112 | = 0 113 | Link_labor_cost_-_working_hours[2020/7/14,Line_3]: 114 | Labor_cost[2020/7/14,Line_3] + [ 115 | - 245 Open_status[2020/7/14,Line_3] * Total_hours[2020/7/14,Line_3] ] 116 | = 0 117 | Link_labor_cost_-_working_hours[2020/7/15,Line_1]: 118 | Labor_cost[2020/7/15,Line_1] + [ 119 | - 245 Open_status[2020/7/15,Line_1] * Total_hours[2020/7/15,Line_1] ] 120 | = 0 121 | Link_labor_cost_-_working_hours[2020/7/15,Line_2]: 122 | Labor_cost[2020/7/15,Line_2] + [ 123 | - 315 Open_status[2020/7/15,Line_2] * Total_hours[2020/7/15,Line_2] ] 124 | = 0 125 | Link_labor_cost_-_working_hours[2020/7/15,Line_3]: 126 | Labor_cost[2020/7/15,Line_3] + [ 127 | - 245 Open_status[2020/7/15,Line_3] * Total_hours[2020/7/15,Line_3] ] 128 | = 0 129 | Link_labor_cost_-_working_hours[2020/7/16,Line_1]: 130 | Labor_cost[2020/7/16,Line_1] + [ 131 | - 245 Open_status[2020/7/16,Line_1] * Total_hours[2020/7/16,Line_1] ] 132 | = 0 133 | Link_labor_cost_-_working_hours[2020/7/16,Line_2]: 134 | Labor_cost[2020/7/16,Line_2] + [ 135 | - 315 Open_status[2020/7/16,Line_2] * Total_hours[2020/7/16,Line_2] ] 136 | = 0 137 | Link_labor_cost_-_working_hours[2020/7/16,Line_3]: 138 | Labor_cost[2020/7/16,Line_3] + [ 139 | - 245 Open_status[2020/7/16,Line_3] * Total_hours[2020/7/16,Line_3] ] 140 | = 0 141 | Link_labor_cost_-_working_hours[2020/7/17,Line_1]: 142 | Labor_cost[2020/7/17,Line_1] + [ 143 | - 245 Open_status[2020/7/17,Line_1] * Total_hours[2020/7/17,Line_1] ] 144 | = 0 145 | Link_labor_cost_-_working_hours[2020/7/17,Line_2]: 146 | Labor_cost[2020/7/17,Line_2] + [ 147 | - 315 Open_status[2020/7/17,Line_2] * Total_hours[2020/7/17,Line_2] ] 148 | = 0 149 | Link_labor_cost_-_working_hours[2020/7/17,Line_3]: 150 | Labor_cost[2020/7/17,Line_3] + [ 151 | - 245 Open_status[2020/7/17,Line_3] * Total_hours[2020/7/17,Line_3] ] 152 | = 0 153 | Link_labor_cost_-_working_hours[2020/7/18,Line_1]: 154 | Labor_cost[2020/7/18,Line_1] + [ 155 | - 245 Open_status[2020/7/18,Line_1] * Total_hours[2020/7/18,Line_1] ] 156 | = 0 157 | Link_labor_cost_-_working_hours[2020/7/18,Line_2]: 158 | Labor_cost[2020/7/18,Line_2] + [ 159 | - 315 Open_status[2020/7/18,Line_2] * Total_hours[2020/7/18,Line_2] ] 160 | = 0 161 | Link_labor_cost_-_working_hours[2020/7/18,Line_3]: 162 | Labor_cost[2020/7/18,Line_3] + [ 163 | - 245 Open_status[2020/7/18,Line_3] * Total_hours[2020/7/18,Line_3] ] 164 | = 0 165 | Link_labor_cost_-_working_hours[2020/7/19,Line_1]: 166 | Labor_cost[2020/7/19,Line_1] + [ 167 | - 245 Open_status[2020/7/19,Line_1] * Total_hours[2020/7/19,Line_1] ] 168 | = 0 169 | Link_labor_cost_-_working_hours[2020/7/19,Line_2]: 170 | Labor_cost[2020/7/19,Line_2] + [ 171 | - 315 Open_status[2020/7/19,Line_2] * Total_hours[2020/7/19,Line_2] ] 172 | = 0 173 | Link_labor_cost_-_working_hours[2020/7/19,Line_3]: 174 | Labor_cost[2020/7/19,Line_3] + [ 175 | - 245 Open_status[2020/7/19,Line_3] * Total_hours[2020/7/19,Line_3] ] 176 | = 0 177 | Bounds 178 | 7 <= Working_hours[2020/7/13,Line_1] <= 12 179 | 7 <= Working_hours[2020/7/13,Line_2] <= 12 180 | 7 <= Working_hours[2020/7/13,Line_3] <= 12 181 | 7 <= Working_hours[2020/7/14,Line_1] <= 12 182 | 7 <= Working_hours[2020/7/14,Line_2] <= 12 183 | 7 <= Working_hours[2020/7/14,Line_3] <= 12 184 | 7 <= Working_hours[2020/7/15,Line_1] <= 12 185 | 7 <= Working_hours[2020/7/15,Line_2] <= 12 186 | 7 <= Working_hours[2020/7/15,Line_3] <= 12 187 | 7 <= Working_hours[2020/7/16,Line_1] <= 12 188 | 7 <= Working_hours[2020/7/16,Line_2] <= 12 189 | 7 <= Working_hours[2020/7/16,Line_3] <= 12 190 | 7 <= Working_hours[2020/7/17,Line_1] <= 12 191 | 7 <= Working_hours[2020/7/17,Line_2] <= 12 192 | 7 <= Working_hours[2020/7/17,Line_3] <= 12 193 | 7 <= Working_hours[2020/7/18,Line_1] <= 12 194 | 7 <= Working_hours[2020/7/18,Line_2] <= 12 195 | 7 <= Working_hours[2020/7/18,Line_3] <= 12 196 | 7 <= Working_hours[2020/7/19,Line_1] <= 12 197 | 7 <= Working_hours[2020/7/19,Line_2] <= 12 198 | 7 <= Working_hours[2020/7/19,Line_3] <= 12 199 | Total_hours[2020/7/13,Line_1] <= 12 200 | Total_hours[2020/7/13,Line_2] <= 12 201 | Total_hours[2020/7/13,Line_3] <= 12 202 | Total_hours[2020/7/14,Line_1] <= 12 203 | Total_hours[2020/7/14,Line_2] <= 12 204 | Total_hours[2020/7/14,Line_3] <= 12 205 | Total_hours[2020/7/15,Line_1] <= 12 206 | Total_hours[2020/7/15,Line_2] <= 12 207 | Total_hours[2020/7/15,Line_3] <= 12 208 | Total_hours[2020/7/16,Line_1] <= 12 209 | Total_hours[2020/7/16,Line_2] <= 12 210 | Total_hours[2020/7/16,Line_3] <= 12 211 | Total_hours[2020/7/17,Line_1] <= 12 212 | Total_hours[2020/7/17,Line_2] <= 12 213 | Total_hours[2020/7/17,Line_3] <= 12 214 | Total_hours[2020/7/18,Line_1] <= 12 215 | Total_hours[2020/7/18,Line_2] <= 12 216 | Total_hours[2020/7/18,Line_3] <= 12 217 | Total_hours[2020/7/19,Line_1] <= 12 218 | Total_hours[2020/7/19,Line_2] <= 12 219 | Total_hours[2020/7/19,Line_3] <= 12 220 | Binaries 221 | Open_status[2020/7/13,Line_1] Open_status[2020/7/13,Line_2] 222 | Open_status[2020/7/13,Line_3] Open_status[2020/7/14,Line_1] 223 | Open_status[2020/7/14,Line_2] Open_status[2020/7/14,Line_3] 224 | Open_status[2020/7/15,Line_1] Open_status[2020/7/15,Line_2] 225 | Open_status[2020/7/15,Line_3] Open_status[2020/7/16,Line_1] 226 | Open_status[2020/7/16,Line_2] Open_status[2020/7/16,Line_3] 227 | Open_status[2020/7/17,Line_1] Open_status[2020/7/17,Line_2] 228 | Open_status[2020/7/17,Line_3] Open_status[2020/7/18,Line_1] 229 | Open_status[2020/7/18,Line_2] Open_status[2020/7/18,Line_3] 230 | Open_status[2020/7/19,Line_1] Open_status[2020/7/19,Line_2] 231 | Open_status[2020/7/19,Line_3] 232 | Generals 233 | Working_hours[2020/7/13,Line_1] Working_hours[2020/7/13,Line_2] 234 | Working_hours[2020/7/13,Line_3] Working_hours[2020/7/14,Line_1] 235 | Working_hours[2020/7/14,Line_2] Working_hours[2020/7/14,Line_3] 236 | Working_hours[2020/7/15,Line_1] Working_hours[2020/7/15,Line_2] 237 | Working_hours[2020/7/15,Line_3] Working_hours[2020/7/16,Line_1] 238 | Working_hours[2020/7/16,Line_2] Working_hours[2020/7/16,Line_3] 239 | Working_hours[2020/7/17,Line_1] Working_hours[2020/7/17,Line_2] 240 | Working_hours[2020/7/17,Line_3] Working_hours[2020/7/18,Line_1] 241 | Working_hours[2020/7/18,Line_2] Working_hours[2020/7/18,Line_3] 242 | Working_hours[2020/7/19,Line_1] Working_hours[2020/7/19,Line_2] 243 | Working_hours[2020/7/19,Line_3] Total_hours[2020/7/13,Line_1] 244 | Total_hours[2020/7/13,Line_2] Total_hours[2020/7/13,Line_3] 245 | Total_hours[2020/7/14,Line_1] Total_hours[2020/7/14,Line_2] 246 | Total_hours[2020/7/14,Line_3] Total_hours[2020/7/15,Line_1] 247 | Total_hours[2020/7/15,Line_2] Total_hours[2020/7/15,Line_3] 248 | Total_hours[2020/7/16,Line_1] Total_hours[2020/7/16,Line_2] 249 | Total_hours[2020/7/16,Line_3] Total_hours[2020/7/17,Line_1] 250 | Total_hours[2020/7/17,Line_2] Total_hours[2020/7/17,Line_3] 251 | Total_hours[2020/7/18,Line_1] Total_hours[2020/7/18,Line_2] 252 | Total_hours[2020/7/18,Line_3] Total_hours[2020/7/19,Line_1] 253 | Total_hours[2020/7/19,Line_2] Total_hours[2020/7/19,Line_3] 254 | End 255 | -------------------------------------------------------------------------------- /Planning_optimization_part3/Model4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jul 27 15:09:01 2020 4 | @author: Baptiste Soulard 5 | """ 6 | 7 | # Import required packages 8 | import pandas as pd 9 | import gurobipy 10 | import datetime 11 | from typing import List, Dict 12 | import altair as alt 13 | import datapane as dp 14 | 15 | 16 | def optimize_planning( 17 | timeline: List[str], 18 | workcenters: List[str], 19 | needs, 20 | wc_cost_reg: Dict[str, int], 21 | wc_cost_ot: Dict[str, int], 22 | wc_cost_we: Dict[str, int], 23 | inventory_carrying_cost: int, 24 | customer_orders: List[str], 25 | cycle_times, 26 | delay_cost: int, 27 | ) -> pd.DataFrame: 28 | # Split weekdays/weekends 29 | weekdays = [] 30 | weekend = [] 31 | for date in timeline: 32 | day = datetime.datetime.strptime(date, "%Y/%m/%d") 33 | if day.weekday() < 5: 34 | weekdays.append(date) 35 | else: 36 | weekend.append(date) 37 | 38 | # Initiate optimization model 39 | model = gurobipy.Model("Optimize production planning") 40 | 41 | # DEFINE VARIABLES 42 | # Quantity variable 43 | x_qty = model.addVars( 44 | timeline, 45 | customer_orders, 46 | workcenters, 47 | lb=0, 48 | vtype=gurobipy.GRB.INTEGER, 49 | name="plannedQty", 50 | ) 51 | 52 | # Time variable 53 | x_time = model.addVars( 54 | timeline, 55 | customer_orders, 56 | workcenters, 57 | lb=0, 58 | vtype=gurobipy.GRB.CONTINUOUS, 59 | name="plannedTime", 60 | ) 61 | 62 | # Set the value of x_time 63 | model.addConstrs( 64 | ( 65 | ( 66 | x_time[(date, mo, wc)] == x_qty[(date, mo, wc)] * cycle_times[(mo, wc)] 67 | for date in timeline 68 | for mo in customer_orders 69 | for wc in workcenters 70 | ) 71 | ), 72 | name="x_time_constr", 73 | ) 74 | 75 | # Qty to display 76 | quantity = model.addVars( 77 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.INTEGER, name="qty" 78 | ) 79 | 80 | # Set the value of qty 81 | model.addConstrs( 82 | ( 83 | ( 84 | quantity[(date, wc)] 85 | == gurobipy.quicksum(x_qty[(date, mo, wc)] for mo in customer_orders) 86 | for date in timeline 87 | for wc in workcenters 88 | ) 89 | ), 90 | name="wty_time_constr", 91 | ) 92 | 93 | # Variable status of the line ( 0 = closed, 1 = opened) 94 | line_opening = model.addVars( 95 | timeline, workcenters, vtype=gurobipy.GRB.BINARY, name="Open status" 96 | ) 97 | 98 | # Load variables (hours) - regular and overtime 99 | reg_hours = model.addVars( 100 | timeline, 101 | workcenters, 102 | vtype=gurobipy.GRB.CONTINUOUS, 103 | name="Regular hours", 104 | ) 105 | 106 | ot_hours = model.addVars( 107 | timeline, 108 | workcenters, 109 | vtype=gurobipy.GRB.CONTINUOUS, 110 | name="Overtime hours", 111 | ) 112 | 113 | reg_hours_bis = model.addVars( 114 | timeline, 115 | workcenters, 116 | lb=7, 117 | ub=8, 118 | vtype=gurobipy.GRB.CONTINUOUS, 119 | name="regHours", 120 | ) 121 | ot_hours_bis = model.addVars( 122 | timeline, workcenters, lb=0, ub=4, vtype=gurobipy.GRB.CONTINUOUS, name="OTHours" 123 | ) 124 | 125 | # Set the value of reg and OT hours) 126 | model.addConstrs( 127 | ( 128 | reg_hours[(date, wc)] 129 | == reg_hours_bis[(date, wc)] * line_opening[(date, wc)] 130 | for date in timeline 131 | for wc in workcenters 132 | ), 133 | name="total_hours_constr", 134 | ) 135 | 136 | model.addConstrs( 137 | ( 138 | ot_hours[(date, wc)] == ot_hours_bis[(date, wc)] * line_opening[(date, wc)] 139 | for date in timeline 140 | for wc in workcenters 141 | ), 142 | name="total_hours_constr", 143 | ) 144 | 145 | # Variable total load (hours) 146 | total_hours = model.addVars( 147 | timeline, 148 | workcenters, 149 | vtype=gurobipy.GRB.CONTINUOUS, 150 | name="Total hours", 151 | ) 152 | 153 | # Set the value of total load (regular + overtime) 154 | model.addConstrs( 155 | ( 156 | total_hours[(date, wc)] == (reg_hours[(date, wc)] + ot_hours[(date, wc)]) 157 | for date in timeline 158 | for wc in workcenters 159 | ), 160 | name="Link total hours - reg/ot hours", 161 | ) 162 | 163 | # Set total hours of production in link with the time variable 164 | model.addConstrs( 165 | ( 166 | ( 167 | total_hours[(date, wc)] 168 | == gurobipy.quicksum(x_time[(date, mo, wc)] for mo in customer_orders) 169 | for date in timeline 170 | for wc in workcenters 171 | ) 172 | ), 173 | name="total_hours_constr", 174 | ) 175 | 176 | # Variable cost 177 | labor_cost = model.addVars( 178 | timeline, workcenters, lb=0, vtype=gurobipy.GRB.CONTINUOUS, name="Labor cost" 179 | ) 180 | 181 | # Set the value of cost (hours * hourly cost) 182 | model.addConstrs( 183 | ( 184 | labor_cost[(date, wc)] 185 | == reg_hours[(date, wc)] * wc_cost_reg[wc] 186 | + ot_hours[(date, wc)] * wc_cost_ot[wc] 187 | for date in weekdays 188 | for wc in workcenters 189 | ), 190 | name="Link labor cost - working hours - wd", 191 | ) 192 | 193 | model.addConstrs( 194 | ( 195 | labor_cost[(date, wc)] 196 | == total_hours[(date, wc)] * wc_cost_we[wc] 197 | for date in weekend 198 | for wc in workcenters 199 | ), 200 | name="Link labor cost - working hours - we", 201 | ) 202 | 203 | # Variable gap early/late production 204 | gap_prod = model.addVars( 205 | timeline, 206 | customer_orders, 207 | lb=-10000, 208 | ub=10000, 209 | vtype=gurobipy.GRB.CONTINUOUS, 210 | name="gapProd", 211 | ) 212 | abs_gap_prod = model.addVars( 213 | timeline, 214 | customer_orders, 215 | vtype=gurobipy.GRB.CONTINUOUS, 216 | name="absGapProd", 217 | ) 218 | # Set the value of gap for early production 219 | for l in range(len(timeline)): 220 | model.addConstrs( 221 | ( 222 | gap_prod[(timeline[l], mo)] 223 | == gurobipy.quicksum( 224 | x_qty[(date, mo, wc)] 225 | for date in timeline[: l + 1] 226 | for wc in workcenters 227 | ) 228 | - (gurobipy.quicksum(needs[(date, mo)] for date in timeline[: l + 1])) 229 | for mo in customer_orders 230 | ), 231 | name="gap_prod", 232 | ) 233 | 234 | # Set the value of ABS(gap for early production) 235 | model.addConstrs( 236 | ( 237 | (abs_gap_prod[(date, mo)] == gurobipy.abs_(gap_prod[(date, mo)])) 238 | for date in timeline 239 | for mo in customer_orders 240 | ), 241 | name="abs gap prod", 242 | ) 243 | 244 | # Create variable "early production" and "inventory costs" 245 | early_prod = model.addVars( 246 | timeline, 247 | customer_orders, 248 | vtype=gurobipy.GRB.CONTINUOUS, 249 | name="early prod", 250 | ) 251 | inventory_costs = model.addVars( 252 | timeline, 253 | customer_orders, 254 | vtype=gurobipy.GRB.CONTINUOUS, 255 | name="inventory costs", 256 | ) 257 | 258 | # Set the value of early production 259 | model.addConstrs( 260 | ( 261 | early_prod[(date, m)] == (gap_prod[(date, m)] + abs_gap_prod[(date, m)]) / 2 262 | for date in timeline 263 | for m in customer_orders 264 | ), 265 | name="early prod", 266 | ) 267 | 268 | # Set the value of inventory costs 269 | model.addConstrs( 270 | ( 271 | (inventory_costs[(date, m)] == early_prod[(date, m)] * inventory_carrying_cost) 272 | for date in timeline 273 | for m in customer_orders 274 | ), 275 | name="inventory costs", 276 | ) 277 | 278 | # Create variable "late production" and "delay costs" 279 | late_prod = model.addVars( 280 | timeline, 281 | customer_orders, 282 | vtype=gurobipy.GRB.CONTINUOUS, 283 | name="late prod", 284 | ) 285 | delay_costs = model.addVars( 286 | timeline, 287 | customer_orders, 288 | vtype=gurobipy.GRB.CONTINUOUS, 289 | name="inventory costs", 290 | ) 291 | 292 | # Set the value of late production 293 | model.addConstrs( 294 | ( 295 | late_prod[(date, m)] == (abs_gap_prod[(date, m)] - gap_prod[(date, m)]) / 2 296 | for date in timeline 297 | for m in customer_orders 298 | ), 299 | name="late prod", 300 | ) 301 | 302 | # Set the value of delay costs 303 | model.addConstrs( 304 | ( 305 | (delay_costs[(date, m)] == late_prod[(date, m)] * delay_cost) 306 | for date in timeline 307 | for m in customer_orders 308 | ), 309 | name="delay costs", 310 | ) 311 | 312 | # CONSTRAINT 313 | # Constraint: Total hours of production = required production time 314 | model.addConstr( 315 | ( 316 | gurobipy.quicksum( 317 | x_qty[(date, mo, wc)] 318 | for date in timeline 319 | for mo in customer_orders 320 | for wc in workcenters 321 | ) 322 | == (gurobipy.quicksum(needs[(date, mo)] for date in timeline for mo in customer_orders)) 323 | ), 324 | name="total_req", 325 | ) 326 | 327 | # DEFINE MODEL 328 | # Objective : minimize a function 329 | model.ModelSense = gurobipy.GRB.MINIMIZE 330 | 331 | # Function to minimize 332 | objective = 0 333 | objective += gurobipy.quicksum( 334 | labor_cost[(date, wc)] for date in timeline for wc in workcenters 335 | ) 336 | objective += gurobipy.quicksum( 337 | inventory_costs[(date, mo)] 338 | for date in timeline 339 | for mo in customer_orders 340 | ) 341 | objective += gurobipy.quicksum( 342 | delay_costs[(date, mo)] 343 | for date in timeline 344 | for mo in customer_orders 345 | ) 346 | 347 | # SOLVE MODEL 348 | model.setObjective(objective) 349 | model.optimize() 350 | 351 | sol = pd.DataFrame(data={"Solution": model.X}, index=model.VarName) 352 | 353 | print("Total cost = $" + str(model.ObjVal)) 354 | 355 | # model.write("Planning_optimization.lp") 356 | # file = open("Planning_optimization.lp", 'r') 357 | # print(file.read()) 358 | # file.close() 359 | 360 | return sol 361 | 362 | 363 | def plot_load(planning: pd.DataFrame, need: pd.DataFrame, timeline: List[str]) -> None: 364 | # Plot graph - Requirement 365 | source = ( 366 | pd.Series(need).rename_axis(["Date", "Customer_Order"]).reset_index(name="Qty") 367 | ) 368 | source = source.groupby(["Date"]).sum() 369 | source["Date"] = source.index 370 | 371 | # Plot graph - Optimized planning 372 | source = planning.filter(like="Total hours", axis=0).copy() 373 | source["Date"] = source.index 374 | source = source.reset_index(drop=True) 375 | source = source.rename(columns={"Solution": "Hours"}).reset_index() 376 | source[["Date", "Line"]] = source["Date"].str.split(",", expand=True) 377 | source["Date"] = source["Date"].str.split("[").str[1] 378 | source["Line"] = source["Line"].str.split("]").str[0] 379 | source["Min capacity"] = 7 380 | source["Max capacity"] = 12 381 | source = source.round({"Hours": 1}) 382 | source["Load%"] = pd.Series( 383 | ["{0:.0f}%".format(val / 8 * 100) for val in source["Hours"]], 384 | index=source.index, 385 | ) 386 | 387 | bars = ( 388 | alt.Chart(source) 389 | .mark_bar() 390 | .encode( 391 | x="Line:N", 392 | y="Hours:Q", 393 | color="Line:N", 394 | tooltip=["Date", "Line", "Hours", "Load%"], 395 | ) 396 | .interactive() 397 | .properties(width=550 / len(timeline) - 22, height=150) 398 | ) 399 | 400 | line_min = alt.Chart(source).mark_rule(color="darkgrey").encode(y="Min capacity:Q") 401 | 402 | line_max = ( 403 | alt.Chart(source) 404 | .mark_rule(color="darkgrey") 405 | .encode(y=alt.Y("Max capacity:Q", title="Load (hours)")) 406 | ) 407 | 408 | chart = ( 409 | alt.layer(bars, line_min, line_max, data=source) 410 | .facet(column="Date:N") 411 | .properties(title="Daily working time") 412 | ) 413 | 414 | chart.save("planning_load_model4.html") 415 | 416 | # dp.Report( 417 | # '### Working time', 418 | # dp.Plot(chart, caption="Production schedule model 4 - Time") 419 | # ).publish(name='Optimized production schedule - Time', 420 | # description="Optimized production schedule - Time", open=True, visibility='PUBLIC') 421 | 422 | 423 | def plot_planning( 424 | planning: pd.DataFrame, need: pd.DataFrame, timeline: List[str] 425 | ) -> None: 426 | # Plot graph - Requirement 427 | source = pd.Series(need).rename_axis(["Date", "Order"]).reset_index(name="Qty") 428 | 429 | chart_need = ( 430 | alt.Chart(source) 431 | .mark_bar() 432 | .encode( 433 | y=alt.Y("Qty", axis=alt.Axis(grid=False)), 434 | column=alt.Column("Date:N"), 435 | color="Order:N", 436 | tooltip=["Order", "Qty"], 437 | ) 438 | .interactive() 439 | .properties( 440 | width=800 / len(timeline) - 22, 441 | height=100, 442 | title="Customer's requirement", 443 | ) 444 | ) 445 | 446 | df = ( 447 | planning.filter(like="plannedQty", axis=0) 448 | .copy() 449 | .rename(columns={"Solution": "Qty"}) 450 | .reset_index() 451 | ) 452 | df[["Date", "Order", "Line"]] = df["index"].str.split(",", expand=True) 453 | df["Date"] = df["Date"].str.split("[").str[1] 454 | df["Line"] = df["Line"].str.split("]").str[0] 455 | df = df[["Date", "Line", "Qty", "Order"]] 456 | 457 | chart_planning = ( 458 | alt.Chart(df) 459 | .mark_bar() 460 | .encode( 461 | y=alt.Y("Qty", axis=alt.Axis(grid=False)), 462 | x="Line:N", 463 | column=alt.Column("Date:N"), 464 | color="Order:N", 465 | tooltip=["Line", "Order", "Qty"], 466 | ) 467 | .interactive() 468 | .properties( 469 | width=800 / len(timeline) - 22, 470 | height=200, 471 | title="Optimized Production Schedule", 472 | ) 473 | ) 474 | 475 | chart = alt.vconcat(chart_planning, chart_need) 476 | chart.save("planning_MO_model4.html") 477 | 478 | # dp.Report( 479 | # '### Production schedule', 480 | # dp.Plot(chart, caption="Production schedule model 4 - Qty") 481 | # ).publish(name='Optimized production schedule - Qty', 482 | # description="Optimized production schedule - Qty", open=True, visibility='PUBLIC') 483 | 484 | 485 | def plot_inventory( 486 | planning: pd.DataFrame, timeline: List[str], cust_orders, 487 | ) -> None: 488 | # Plot inventory 489 | df = ( 490 | planning.filter(like="early prod", axis=0) 491 | .copy() 492 | .rename(columns={"Solution": "Qty"}) 493 | .reset_index() 494 | ) 495 | 496 | df[["Date", "Order"]] = df["index"].str.split(",", expand=True) 497 | df["Date"] = df["Date"].str.split("[").str[1] 498 | df["Order"] = df["Order"].str.split("]").str[0] 499 | df = df[["Date", "Qty", "Order"]] 500 | 501 | models_list = cust_orders[['Order', 'Product_Family']] 502 | df = pd.merge(df, models_list, on='Order', how='inner') 503 | df = df[["Date", "Qty", "Product_Family"]] 504 | 505 | bars = ( 506 | alt.Chart(df) 507 | .mark_bar() 508 | .encode( 509 | y="Qty:Q", 510 | color="Product_Family:N", 511 | tooltip=["Product_Family", "Qty"], 512 | ) 513 | .interactive() 514 | .properties(width=550 / len(timeline) - 22, height=60) 515 | ) 516 | 517 | chart_inventory = ( 518 | alt.layer(bars, data=df) 519 | .facet(column="Date:N") 520 | .properties(title="Inventory") 521 | ) 522 | 523 | # Plot shortage 524 | df = ( 525 | planning.filter(like="late prod", axis=0) 526 | .copy() 527 | .rename(columns={"Solution": "Qty"}) 528 | .reset_index() 529 | ) 530 | 531 | df[["Date", "Order"]] = df["index"].str.split(",", expand=True) 532 | df["Date"] = df["Date"].str.split("[").str[1] 533 | df["Order"] = df["Order"].str.split("]").str[0] 534 | df = df[["Date", "Qty", "Order"]] 535 | 536 | models_list = cust_orders[['Order', 'Product_Family']] 537 | df = pd.merge(df, models_list, on='Order', how='inner') 538 | df = df[["Date", "Qty", "Product_Family"]] 539 | 540 | bars = ( 541 | alt.Chart(df) 542 | .mark_bar() 543 | .encode( 544 | y="Qty:Q", 545 | color="Product_Family:N", 546 | tooltip=["Product_Family", "Qty"], 547 | ) 548 | .interactive() 549 | .properties(width=550 / len(timeline) - 22, height=60) 550 | ) 551 | 552 | chart_shortage = ( 553 | alt.layer(bars, data=df) 554 | .facet(column="Date:N") 555 | .properties(title="Shortage") 556 | ) 557 | 558 | chart = alt.vconcat(chart_inventory, chart_shortage) 559 | chart.save("Inventory_Shortage.html") 560 | 561 | # dp.Report( 562 | # '#### Inventory and shortage report', 563 | # dp.Plot(chart, caption="Inventory_Shortage") 564 | # ).publish(name='Inventory_Shortage', 565 | # description="Inventory_Shortage", open=True, visibility='PUBLIC') 566 | 567 | 568 | def print_planning(planning: pd.DataFrame) -> None: 569 | df = ( 570 | planning.filter(like="plannedQty", axis=0) 571 | .copy() 572 | .rename(columns={"Solution": "Qty"}) 573 | .reset_index() 574 | ) 575 | df[["Date", "Customer_Order", "Line"]] = df["index"].str.split(",", expand=True) 576 | df["Date"] = df["Date"].str.split("[").str[1] 577 | df["Line"] = df["Line"].str.split("]").str[0] 578 | df = df[["Date", "Line", "Qty", "Customer_Order"]] 579 | 580 | df.to_csv(r"Planning_model4_list.csv", index=True) 581 | df.pivot_table( 582 | values="Qty", index="Customer_Order", columns=["Date", "Line"] 583 | ).to_csv(r"Planning_model4v2.csv", index=True) 584 | 585 | 586 | # Define hourly cost per line - regular, overtime and weekend 587 | reg_costs_per_line = {"Line_1": 245, "Line_2": 315, "Line_3": 245} 588 | ot_costs_per_line = { 589 | k: 1.5 * reg_costs_per_line[k] for k, v in reg_costs_per_line.items() 590 | } 591 | we_costs_per_line = { 592 | k: 2 * reg_costs_per_line[k] for k, w in reg_costs_per_line.items() 593 | } 594 | 595 | storage_cost = 5 596 | late_prod_cost = 1000 597 | 598 | lines: List[str] = list(reg_costs_per_line.keys()) 599 | 600 | # Get orders 601 | customer_orders = pd.read_excel("Customer_orders.xlsx") 602 | 603 | # Get cycle times 604 | capacity = pd.read_excel("Constraints.xlsx", sheet_name="8h capacity").set_index("Line") 605 | cycle_time = capacity.rdiv(8) 606 | 607 | 608 | def check_duplicates(list_to_check): 609 | if len(list_to_check) == len(set(list_to_check)): 610 | return 611 | else: 612 | print("Duplicate order, please check the requirements file") 613 | exit() 614 | return 615 | 616 | 617 | order_list = customer_orders["Order"].to_list() 618 | check_duplicates(order_list) 619 | 620 | # Create cycle times dictionnary 621 | customer_orders = customer_orders.merge( 622 | cycle_time, left_on="Product_Family", right_index=True 623 | ) 624 | 625 | customer_orders["Delivery_Date"] = pd.to_datetime( 626 | customer_orders["Delivery_Date"] 627 | ).dt.strftime("%Y/%m/%d") 628 | customer_orders = customer_orders.sort_values(by=["Delivery_Date", "Order"]) 629 | 630 | cycle_times = { 631 | (order, line): customer_orders[line][customer_orders.Order == order].item() 632 | for order in order_list 633 | for line in lines 634 | } 635 | 636 | # Define calendar 637 | start_date = datetime.datetime.strptime( 638 | customer_orders["Delivery_Date"].min(), "%Y/%m/%d" 639 | ) 640 | end_date = datetime.datetime.strptime( 641 | customer_orders["Delivery_Date"].max(), "%Y/%m/%d" 642 | ) 643 | 644 | date_modified = start_date 645 | calendar = [start_date.strftime("%Y/%m/%d")] 646 | 647 | while date_modified < end_date: 648 | date_modified += datetime.timedelta(days=1) 649 | calendar.append(date_modified.strftime("%Y/%m/%d")) 650 | 651 | # Create daily requirements dictionnary 652 | daily_requirements = {} 653 | for day in calendar: 654 | for order in order_list: 655 | try: 656 | daily_requirements[(day, order)] = customer_orders[ 657 | (customer_orders.Order == order) 658 | & (customer_orders.Delivery_Date == day) 659 | ]["Quantity"].item() 660 | except ValueError: 661 | daily_requirements[(day, order)] = 0 662 | 663 | # Optimize planning 664 | solution = optimize_planning( 665 | calendar, 666 | lines, 667 | daily_requirements, 668 | reg_costs_per_line, 669 | ot_costs_per_line, 670 | we_costs_per_line, 671 | storage_cost, 672 | order_list, 673 | cycle_times, 674 | late_prod_cost, 675 | ) 676 | 677 | # Plot the new planning 678 | plot_load(solution, daily_requirements, calendar) 679 | print_planning(solution) 680 | plot_planning(solution, daily_requirements, calendar) 681 | plot_inventory(solution, calendar, customer_orders) 682 | -------------------------------------------------------------------------------- /Planning_optimization_part3/planning_MO_model4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | -------------------------------------------------------------------------------- /Planning_optimization_part4/planning_MO_model4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | --------------------------------------------------------------------------------