{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ " The cell directly below is the one in which you should make changes\n", " Only change things to the right of the '=' sign. \n", " Note that the '#' symbol means a comment -- when the code runs, it ignores this.\n", " \n", " You can change the scenario name, the capacities of the plants, whether the capacity constraint is active at all, and whether or not the maps are output.\n", "\n", " To Run, go to the menu item Cell and select 'Run All'\n", " To see the results of the run: \n", " Go to the directory where you put this file and you'll see two output files\n", " One output file will be the summary and one will have details\n", " And, tabs will open if you decide to map the input and output" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "# You should change the name to the right of the '=' sign. This names each scenario. \n", "# If you don't change it, you'll overwrite the files when you run. Don't put spaces in the name\n", "# And, keep the single quotes in the name. \n", "\n", "scenario_name = 'scenario_a' #<<<----- you can change this name\n", "\n", "# Change production capacity of a mine by multiplying it by the plant_capacity_change_factor\n", "# e.g., change to 1.1 to increase by 10%\n", "plant_capacity_change_factor = 1\n", "\n", "\n", "# Change to \"True\" to completely remove the capacity constraint\n", "capacity_constraint_inactive = False\n", "\n", "# If you don't want either the input or output map to draw, type False with a capital F instead of True\n", "input_map = True #<<<----- you can change these from True to False\n", "output_map = True\n", "\n", "# To Run, go to the menu item Cell and select 'Run All'" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#This is code to start running the model and define the data. You don't need to interact with this\n", "from pulp import *\n", "import time\n", "import pandas as pd\n", "import plotly\n", "plotly.offline.init_notebook_mode()\n", "import plotly.graph_objs as go\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "def get_data():\n", " \n", " plants = {1: ['KY Mine', 37.54, -82.75, 25000000],\n", " 2: ['WY Mine', 41.56, -110.27, 17000000],\n", " 3: ['PA Mine', 41.24, -79.58, 15000000],\n", " 4: ['IA Mine', 40.88, -94.04, 13000000]}\n", "\n", " for p in plants:\n", " plants[p][3] *= plant_capacity_change_factor\n", "\n", " customers = {1: ('Los Angeles', 33.974044, -118.248849, 8000000),\n", " 2: ('New York City', 40.75012, -73.997846, 8000000),\n", " 3: ('Chicago', 41.88331, -87.624713, 6000000),\n", " 4: ('Houston', 29.757317, -95.365005, 4000000),\n", " 5: ('Atlanta', 33.753693, -84.389544, 6000000),\n", " 6: ('Dallas', 32.787642, -96.799525, 6000000),\n", " 7: ('Phoenix', 33.451015, -112.068554, 4000000),\n", " 8: ('New York', 42.526891, -73.609283, 6000000),\n", " 9: ('St. Louis', 38.631358, -90.192246, 4000000),\n", " 10: ('Baltimore', 39.294398, -76.622747, 2000000),\n", " 11: ('Pittsburgh', 40.474802, -79.95449, 4000000),\n", " 12: ('Seattle', 47.611601, -122.333038, 2000000),\n", " 13: ('Denver', 39.75071, -104.996225, 4000000),\n", " 14: ('Kansas City', 39.103883, -94.600613, 2000000),\n", " 15: ('San Francisco', 37.779887, -122.418066, 4000000)}\n", "\n", " distance = {(1, 1): 1992.3326524888942,\n", " (1, 2): 518.0989669001742,\n", " (1, 3): 396.0480359807119,\n", " (1, 4): 901.0765797760816,\n", " (1, 5): 277.12477072569794,\n", " (1, 6): 857.0949657896,\n", " (1, 7): 1665.1681404496649,\n", " (1, 8): 592.7938067624042,\n", " (1, 9): 411.28315503881726,\n", " (1, 10): 352.8162643440768,\n", " (1, 11): 252.0716409601713,\n", " (1, 12): 2102.8497649237233,\n", " (1, 13): 1206.1684857449923,\n", " (1, 14): 650.458636242596,\n", " (1, 15): 2151.680636447827,\n", " (2, 1): 680.5148308680015,\n", " (2, 2): 1872.517236056754,\n", " (2, 3): 1163.8302573605959,\n", " (2, 4): 1164.3055758471232,\n", " (2, 5): 1506.7656504283036,\n", " (2, 6): 955.0992403271943,\n", " (2, 7): 568.4475963673557,\n", " (2, 8): 1866.2388997283513,\n", " (2, 9): 1076.9829404027007,\n", " (2, 10): 1764.1747168256802,\n", " (2, 11): 1572.9544717071672,\n", " (2, 12): 724.24780720657,\n", " (2, 13): 303.10559716157576,\n", " (2, 14): 840.7376367271237,\n", " (2, 15): 695.5959565004515,\n", " (3, 1): 2155.0037799601123,\n", " (3, 2): 292.81048853908004,\n", " (3, 3): 417.820352457601,\n", " (3, 4): 1186.3865526496838,\n", " (3, 5): 579.9174845267695,\n", " (3, 6): 1111.2788105306925,\n", " (3, 7): 1850.417654790172,\n", " (3, 8): 319.44376082934673,\n", " (3, 9): 589.5341198637885,\n", " (3, 10): 205.69376534386242,\n", " (3, 11): 56.33641473786246,\n", " (3, 12): 2125.3228918682958,\n", " (3, 13): 1333.692803370853,\n", " (3, 14): 804.9962867330751,\n", " (3, 15): 2271.3079362505578,\n", " (4, 1): 1404.8701145815269,\n", " (4, 2): 1045.0454722285801,\n", " (4, 3): 339.4062975774761,\n", " (4, 4): 771.5589923146184,\n", " (4, 5): 722.2721514865867,\n", " (4, 6): 579.0845869628957,\n", " (4, 7): 1113.7108780331844,\n", " (4, 8): 1056.7113914913302,\n", " (4, 9): 256.50808300469276,\n", " (4, 10): 924.9842132101479,\n", " (4, 11): 737.2984106303244,\n", " (4, 12): 1464.9699150983704,\n", " (4, 13): 581.6600928346301,\n", " (4, 14): 126.16623450319042,\n", " (4, 15): 1523.8314979659976}\n", "\n", " #Accounting for circuity factor\n", " distance = {(p,c): distance[p,c]*1.17 for (p,c) in distance.keys()}\n", " \n", " return plants, customers, distance" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# This model minimizes the total distance\n", "\n", "def optimal_routing(plants, customers, distance, scenario_name):\n", " start_time=time.time()\n", " # Create the 'prob' variable to contain the problem data\n", " transportation_problem = LpProblem(\"transportation\", LpMinimize)\n", "\n", " # A dictionary called 'assign_vars' is created to contain the decision variables x(i,j) - the assignment of a mine to a power station\n", " # which is continuous and captures the fact that multiple mines can satisfy the same power station's demand\n", " # lower bound is 0; upper bound is 1; integer variable - this makes it binary\n", " assign_vars = LpVariable.dicts(\"Assignment\",[(p, c) for p in plants for c in customers],lowBound=0)\n", "\n", "\n", " # The objective function is added to 'prob' first\n", " total_distance = lpSum([distance[p,c]*assign_vars[p,c] for p in plants for c in customers])\n", "\n", " # Here we have 3 types of constraints\n", "\n", " \"\"\"\n", " \n", " A Pulp LP constraint: LpConstraint(e, sense, name, rhs)\n", " Parameters:\t\n", " e – an instance of LpExpression\n", " sense – one of LpConstraintEQ, LpConstraintGE, LpConstraintLE\n", " name – identifying string\n", " rhs – numerical value of constraint right hand side\n", " \"\"\"\n", " \n", " # All demand must be met\n", " for customer in customers:\n", " transportation_problem += LpConstraint(e = lpSum([assign_vars[p, customer] for p in plants]), \n", " sense=LpConstraintEQ, \n", " name=str(customer)+\"_Served\", \n", " rhs=customers[customer][3])\n", " \n", " \n", " # Ensures that the demand served by a mine doesn't exceed its capacity\n", " if capacity_constraint_inactive == False:\n", " for facility in plants:\n", " transportation_problem += LpConstraint(e = lpSum([assign_vars[facility, c] for c in customers]), \n", " sense=LpConstraintLE, \n", " name=str(facility) + \"_Capacity\",\n", " rhs=plants[facility][3])\n", "\n", "\n", " # Setting problem objective\n", " transportation_problem.setObjective(total_distance)\n", " # The problem data is written to an .lp file\n", " transportation_problem.writeLP(\"transportation_prob.lp\")\n", "\n", " # The problem is solved using PuLP's choice of Solver\n", " transportation_problem.solve()\n", " \n", " # The status of the solution is printed to the screen\n", " #print (\"Status:\", LpStatus[brazil_problem.status])\n", " file.write('\\nstatus:'+ LpStatus[transportation_problem.status])\n", " print(\"Optimization Status\",LpStatus[transportation_problem.status] ) #print in Jupyter Notebook\n", " #print objective\n", " total_demand = sum(customers[customer][3] for customer in customers)\n", " \n", " #print (total_demand_to_warehouse)\n", " #print (\"Total Demand\",total_demand)\n", " file.write(\"\\nTotal Demand:\"+ str(total_demand))\n", " print(\"Objective: \", value(transportation_problem.objective))\n", " file.write(\"\\nObjective: \"+ str(value(transportation_problem.objective)))\n", " print(\"Average service distance: \",round(value(transportation_problem.objective)/total_demand,1),\"mi\")\n", " file.write(\"\\nAverage service distance: \"+str(round(value(transportation_problem.objective)/total_demand,1))+\"mi\")\n", " \n", " end_time = time.time()\n", " time_diff = end_time - start_time\n", " file.write(\"\\nRun Time of model in seconds {:.1f}\" .format(time_diff))\n", " print(\"Run Time of model in seconds {:.1f}\" .format(time_diff))\n", " \n", " \n", "# print(\"Power Stations assigned to Mines\")\n", " customers_assignment = [] \n", " for (p,c) in assign_vars.keys():\n", " if assign_vars[(p,c)].varValue > 0:\n", " cust = {'Mine':plants[p][0],\n", " 'Power Station':str(customers[c][0]),\n", " 'Power Station Demand': customers[c][3],\n", " 'Distance': distance[p,c],\n", " 'Mine Latitude': plants[p][1],\n", " 'Mine Longitude': plants[p][2],\n", " 'Power Station Latitude': customers[c][1],\n", " 'Power Station Longitude': customers[c][2]\n", " }\n", " customers_assignment.append(cust)\n", " \n", " df_cu = pd.DataFrame.from_records(customers_assignment)\n", " df_cu_copy = df_cu.copy()\n", " df_cu_copy = df_cu_copy[['Mine', 'Power Station', 'Distance', 'Power Station Demand']]\n", " df_cu_copy.to_excel(writer,'Power Station Assignment',index=False)\n", " \n", " writer.close() \n", " return df_cu\n", "\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "# In this function, we are testing the input paramaters\n", "def test_input(plants, plant_capacities, customers, customer_demands, distance):\n", " for c in customers:\n", " for p in plants:\n", " if(distance[p,c]>=0):\n", " pass\n", " else:\n", " file.write(f'\\nDistance between warehouse {w} and customer {c} is not available or invalid')\n", "\n", " for c in customers.keys():\n", " if(customers[c][4]>=0):\n", " pass\n", " else:\n", " file.write(f'\\nDemand for Customer {c} is not available')\n" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "# In this function, we are visualizaing the input data-- the data before the optimization has run\n", "def input_visual(plants, customers):\n", " \n", " plant_list = []\n", " for p in plants:\n", " ph = {\n", " 'text':'Mine-'+plants[p][0],\n", " 'lat':plants[p][1],\n", " 'long':plants[p][2],\n", " 'cnt':10000000,\n", " 'size' : 30,\n", " 'color' : 'rgba(0, 100, 0)'\n", " }\n", " plant_list.append(ph) \n", "\n", " customer_list =[] \n", " for c in customers:\n", " cust = {\n", " 'text':'Customer-'+customers[c][0],\n", " 'lat':customers[c][1],\n", " 'long':customers[c][2],\n", " 'cnt':customers[c][3],\n", " 'size' : 3,\n", " 'color' : 'rgb(255, 0, 0)'\n", " }\n", " customer_list.append(cust) \n", "\n", " df = pd.DataFrame.from_records(plant_list)\n", " df['shape'] = \"triangle-down\"\n", " df_cust = pd.DataFrame.from_records(customer_list)\n", " df_cust['shape'] = \"circle\"\n", " df = df.append(df_cust, ignore_index = True) \n", "\n", " locations = [ dict(\n", " type = 'scattergeo',\n", " lon = df['long'],\n", " lat = df['lat'],\n", " hoverinfo = 'text',\n", " text = df['text'],\n", " mode = 'markers',\n", " marker = dict( \n", " size=df['size'], \n", " color=df['color'],\n", " symbol = df['shape'],\n", " line = dict(\n", " width=3,\n", " color='rgba(68, 68, 68, 0)'\n", " ),\n", "\n", " ))]\n", "\n", " layout = dict(\n", " title = \"Transportation Problem\",\n", " showlegend = False, \n", " geo = dict(\n", " scope='usa',\n", " projection=dict( type='albers usa' ),\n", " showland = True,\n", " landcolor = 'rgb(243, 243, 243)',\n", " countrycolor = 'rgb(204, 204, 204)',\n", " ),\n", " )\n", " plotly.offline.plot({ \"data\":locations, \"layout\":layout}, filename = scenario_name+'_input.html') " ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# In this function, we are visualizaing the output data-- the data after the optimization has run\n", "def output_visual(plants, customers, df_cu, scenario_name): \n", " \n", " plant_list = []\n", " for p in plants:\n", " ph = {\n", " 'text':'Mine-'+plants[p][0],\n", " 'lat':plants[p][1],\n", " 'long':plants[p][2],\n", " 'cnt':10000000,\n", " 'size' : 30,\n", " 'color' : 'rgba(0, 100, 0)'\n", " }\n", " plant_list.append(ph) \n", "\n", " customer_list =[] \n", " for c in customers:\n", " cust = {\n", " 'text':'Customer-'+customers[c][0],\n", " 'lat':customers[c][1],\n", " 'long':customers[c][2],\n", " 'cnt':customers[c][3],\n", " 'size' : 3,\n", " 'color' : 'rgb(255, 0, 0)'\n", " }\n", " customer_list.append(cust) \n", "\n", " df = pd.DataFrame.from_records(plant_list)\n", " df['shape'] = \"triangle-down\"\n", " df_cust = pd.DataFrame.from_records(customer_list)\n", " df_cust['shape'] = \"circle\"\n", " df = df.append(df_cust, ignore_index = True) \n", " \n", " paths = [] \n", " for i in range( len( df_cu) ):\n", " paths.append(\n", " dict(\n", " type = 'scattergeo',\n", " locationmode = 'USA-states',\n", " lon = [ df_cu['Mine Longitude'][i], df_cu['Power Station Longitude'][i] ],\n", " lat = [ df_cu['Mine Latitude'][i], df_cu['Power Station Latitude'][i] ],\n", " mode = 'lines',\n", " line = dict(\n", " width = 1,\n", " color = 'red',\n", " ),\n", " opacity = 0.8,\n", " )\n", " ) \n", " \n", " locations = [ dict(\n", " type = 'scattergeo',\n", " lon = df['long'],\n", " lat = df['lat'],\n", " hoverinfo = 'text',\n", " text = df['text'],\n", " mode = 'markers',\n", " marker = dict( \n", " size=df['size'], \n", " color=df['color'],\n", " symbol = df['shape'],\n", " line = dict(\n", " width=3,\n", " color='rgba(68, 68, 68, 0)'\n", " ),\n", " ))]\n", "\n", " layout = dict(\n", " title = \"Transportation Problem Output\",\n", " showlegend = False, \n", " geo = dict(\n", " scope='usa',\n", " projection=dict( type='albers usa' ),\n", " showland = True,\n", " landcolor = 'rgb(243, 243, 243)',\n", " countrycolor = 'rgb(204, 204, 204)',\n", " ),\n", " )\n", "\n", " plotly.offline.plot({\"data\":locations+paths, \"layout\":layout},filename=scenario_name+'_output.html') " ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optimization Status Optimal\n", "Objective: 34626600714.75066\n", "Average service distance: 494.7 mi\n", "Run Time of model in seconds 0.0\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\Riccardo\\AppData\\Local\\Temp\\ipykernel_1936\\2502375619.py:32: FutureWarning:\n", "\n", "The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.\n", "\n", "C:\\Users\\Riccardo\\AppData\\Local\\Temp\\ipykernel_1936\\2421950530.py:32: FutureWarning:\n", "\n", "The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.\n", "\n" ] } ], "source": [ "plants, customers, distance = get_data()\n", "file = open(scenario_name+'summary' + '.txt',\"w\")\n", "writer = pd.ExcelWriter(scenario_name+'_detailed.xlsx')\n", "df_cu = optimal_routing(plants, customers, distance, scenario_name)\n", "input_visual(plants, customers)\n", "output_visual(plants, customers, df_cu, scenario_name)\n", "file.close()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.12" } }, "nbformat": 4, "nbformat_minor": 2 }