{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "```{index} single: AMPL; sets\n", "```\n", "```{index} single: AMPL; parameters\n", "```\n", "```{index} single: solver; highs\n", "```\n", "```{index} single: application; production planning\n", "```\n", "```{index} pandas dataframe\n", "```\n", "\n", "# BIM production revisited" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# install dependencies and select solver\n", "%pip install -q amplpy\n", "\n", "SOLVER = \"highs\"\n", "\n", "from amplpy import AMPL, ampl_notebook\n", "\n", "ampl = ampl_notebook(\n", " modules=[\"highs\"], # modules to install\n", " license_uuid=\"default\", # license to use\n", ") # instantiate AMPL object and register magics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem description\n", "\n", "We consider BIM raw material planning, but now with more sophisticated pricing and acquisition protocols. There are now three suppliers, each of which can deliver the following materials:\n", " - A: **silicon**, **germanium** and **plastic**\n", " - B: **copper**\n", " - C: all of the above\n", " \n", "For the suppliers, the following conditions apply. Copper should be acquired in multiples of 100 gram, since it is delivered in sheets of 100 gram. Unitary materials such as silicon, germanium and plastic may be acquired in any number, but the price is in batches of 100. Meaning that 30 units of silicon with 10 units of germanium and 50 units of plastic cost as much as 1 unit of silicon but half as much as 30 units of silicon with 30 units of germanium and 50 units of plastic. Furthermore, supplier C sells all materials and offers a discount if purchased together: 100 gram of copper and a batch of unitary material cost just 7. This set price is only applied to pairs, meaning that 100 gram of copper and 2 batches cost 13.\n", "\n", "The summary of the prices in € is given in the following table:\n", "\n", "|Supplier|Copper per sheet of 100 gram|Batch of units|Together|\n", "|:-------|---------------------:|-----------------:|-------:|\n", "| A | - | 5 | - |\n", "| B | 3 | - | - |\n", "| C | 4 | 6 | 7 |\n", "\n", "Next, for stocked products inventory costs are incurred, whose summary is given in the following table:\n", "\n", "|Copper per 10 gram| Silicon per unit| Germanium per unit|Plastic per unit|\n", "|---:|-------:|---:|-----:|\n", "| 0.1| 0.02 |0.02| 0.02 |\n", "\n", "The holding price of copper is per 10 gram and the copper stocked is rounded up to multiples of 10 grams, meaning that 12 grams pay for 20. \n", "\n", "The capacity limitations of the warehouse allow for a maximum of $10$ kilogram of copper in stock at any moment, but there are no practical limitations to the number of units of unitary products in stock.\n", "\n", "Recall that BIM has the following stock at the beginning of the year:\n", "\n", "|Copper |Silicon |Germanium |Plastic|\n", "|---:|-------:|---:|-----:|\n", "| 480| 1000 |1500| 1750 |\n", "\n", "The company would like to have at least the following stock at the end of the year:\n", "\n", "|Copper |Silicon |Germanium |Plastic|\n", "|---:|-------:|---:|-----:|\n", "| 200| 500 | 500| 1000 |\n", "\n", "The goal is to build an optimization model using the data above and solve it to minimize the acquisition and holding costs of the products while meeting the required quantities for production. The production is made-to-order, meaning that no inventory of chips is kept.\n" ] }, { "cell_type": "code", "execution_count": 91, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 143 }, "id": "rvWwY74i7qEy", "outputId": "47766087-eb87-44e7-d3e2-ac2c00af78f4" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
chip
logic88125260217238286248238265293259244
memory4762816595118868982828466
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n", "chip \n", "logic 88 125 260 217 238 286 248 238 265 293 259 244\n", "memory 47 62 81 65 95 118 86 89 82 82 84 66" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from io import StringIO\n", "import pandas as pd\n", "\n", "demand_data = \"\"\"\n", "chip, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec\n", "logic, 88, 125, 260, 217, 238, 286, 248, 238, 265, 293, 259, 244\n", "memory, 47, 62, 81, 65, 95, 118, 86, 89, 82, 82, 84, 66\n", "\"\"\"\n", "\n", "demand_chips = pd.read_csv(StringIO(demand_data), index_col=\"chip\")\n", "display(demand_chips)" ] }, { "cell_type": "code", "execution_count": 92, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 174 }, "id": "I0Wl0BXOlv6j", "outputId": "2f785ea2-a004-4cd6-a65b-75eee443ef71" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
logicmemory
silicon10
plastic11
copper42
germanium01
\n", "
" ], "text/plain": [ " logic memory\n", "silicon 1 0\n", "plastic 1 1\n", "copper 4 2\n", "germanium 0 1" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "use = dict()\n", "use[\"logic\"] = {\"silicon\": 1, \"plastic\": 1, \"copper\": 4}\n", "use[\"memory\"] = {\"germanium\": 1, \"plastic\": 1, \"copper\": 2}\n", "use = pd.DataFrame.from_dict(use).fillna(0).astype(int)\n", "display(use)" ] }, { "cell_type": "code", "execution_count": 93, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 174 }, "id": "m8daTOpBlv6k", "outputId": "3bff6de3-8759-4dde-95f9-321d3231ccc7" }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
silicon88125260217238286248238265293259244
plastic135187341282333404334327347375343310
copper446624120299811421380116411301224133612041108
germanium4762816595118868982828466
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov \\\n", "silicon 88 125 260 217 238 286 248 238 265 293 259 \n", "plastic 135 187 341 282 333 404 334 327 347 375 343 \n", "copper 446 624 1202 998 1142 1380 1164 1130 1224 1336 1204 \n", "germanium 47 62 81 65 95 118 86 89 82 82 84 \n", "\n", " Dec \n", "silicon 244 \n", "plastic 310 \n", "copper 1108 \n", "germanium 66 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "demand = use.dot(demand_chips)\n", "display(demand)" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting BIMproduction_v1.mod\n" ] } ], "source": [ "%%writefile BIMproduction_v1.mod\n", "\n", "set periods ordered;\n", "set products;\n", "set supplying_batches;\n", "set supplying_copper;\n", "set unitary_products;\n", "\n", "param delta{products, periods}; # demand\n", "param pi{supplying_batches}; # aquisition price\n", "param kappa{supplying_copper}; # price of copper sheet\n", "param beta;\n", "param gamma{products}; # unitary holding costs\n", "param Alpha{products}; # initial stock\n", "param Omega{products}; # desired stock\n", "param copper_bucket_size;\n", "param stock_limit;\n", "param batch_size;\n", "param copper_sheet_mass;\n", "\n", "var y{periods, supplying_copper} integer >= 0;\n", "var x{unitary_products, periods, supplying_batches} >= 0;\n", "var ss{products, periods} >= 0;\n", "var uu{products, periods} >= 0;\n", "var bb{periods, supplying_batches} integer >= 0;\n", "var pp{periods} integer >= 0;\n", "var rr{periods} integer >= 0;\n", "\n", "s.t. units_in_batches {t in periods, s in supplying_batches}:\n", " sum{p in unitary_products} x[p,t,s] <= batch_size * bb[t,s];\n", "s.t. copper_in_buckets {t in periods}:\n", " ss['copper', t] <= copper_bucket_size * rr[t];\n", "s.t. inventory_capacity {t in periods}:\n", " ss['copper', t] <= stock_limit;\n", "s.t. pairs_in_batches {t in periods}:\n", " pp[t] <= bb[t,'C'];\n", "s.t. pairs_in_sheets {t in periods}:\n", " pp[t] <= y[t,'C'];\n", "s.t. bought {t in periods, p in products}:\n", " (if p == 'copper' then\n", " copper_sheet_mass * sum{s in supplying_copper} y[t,s]\n", " else\n", " sum{s in supplying_batches} x[p,t,s])\n", " == uu[p,t];\n", "\n", "var acquisition_cost =\n", " sum{t in periods}(\n", " sum{s in supplying_batches} pi[s] * bb[t,s] +\n", " sum{s in supplying_copper} kappa[s] * y[t,s] -\n", " beta * pp[t]\n", " );\n", "var inventory_cost =\n", " sum{t in periods}(\n", " gamma['copper'] * rr[t] + \n", " sum{p in unitary_products} gamma[p] * ss[p,t]\n", " );\n", "\n", "minimize total_cost: acquisition_cost + inventory_cost;\n", " \n", "s.t. balance {p in products, t in periods}:\n", " (if t == first(periods) then\n", " Alpha[p]\n", " else\n", " ss[p,prev(t)]) +\n", " uu[p,t] == delta[p,t] + ss[p,t];\n", "s.t. finish {p in products}:\n", " ss[p, last(periods)] >= Omega[p];" ] }, { "cell_type": "code", "execution_count": 95, "metadata": {}, "outputs": [], "source": [ "def BIMproduction_v1(\n", " demand,\n", " existing,\n", " desired,\n", " stock_limit,\n", " supplying_copper,\n", " supplying_batches,\n", " price_copper_sheet,\n", " price_batch,\n", " discounted_price,\n", " batch_size,\n", " copper_sheet_mass,\n", " copper_bucket_size,\n", " unitary_products,\n", " unitary_holding_costs,\n", "):\n", " m = AMPL()\n", " m.read(\"BIMproduction_v1.mod\")\n", "\n", " m.set[\"periods\"] = demand.columns\n", " m.set[\"products\"] = demand.index\n", " m.param[\"delta\"] = demand\n", "\n", " m.set[\"supplying_batches\"] = supplying_batches\n", " m.param[\"pi\"] = price_batch\n", "\n", " m.set[\"supplying_copper\"] = supplying_copper\n", " m.param[\"kappa\"] = price_copper_sheet\n", "\n", " m.param[\"beta\"] = price_batch[\"C\"] + price_copper_sheet[\"C\"] - discounted_price\n", "\n", " m.set[\"unitary_products\"] = unitary_products\n", " m.param[\"gamma\"] = unitary_holding_costs\n", "\n", " m.param[\"Alpha\"] = existing\n", " m.param[\"Omega\"] = desired\n", "\n", " m.param[\"batch_size\"] = batch_size\n", " m.param[\"copper_bucket_size\"] = copper_bucket_size\n", " m.param[\"stock_limit\"] = stock_limit\n", " m.param[\"copper_sheet_mass\"] = copper_sheet_mass\n", "\n", " return m" ] }, { "cell_type": "code", "execution_count": 96, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HiGHS 1.5.1: tech:outlev=1\n", "Running HiGHS 1.5.1 [date: 2023-06-22, git hash: 93f1876]\n", "Copyright (c) 2023 HiGHS under MIT licence terms\n", "Presolving model\n", "152 rows, 236 cols, 444 nonzeros\n", "113 rows, 197 cols, 366 nonzeros\n", "96 rows, 156 cols, 285 nonzeros\n", "\n", "Solving MIP model with:\n", " 96 rows\n", " 156 cols (0 binary, 72 integer, 23 implied int., 61 continuous)\n", " 285 nonzeros\n", "\n", " Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work \n", " Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time\n", "\n", " 0 0 0 0.00% -inf inf inf 0 0 0 0 0.0s\n", " R 0 0 0 0.00% 109104 114244 4.50% 0 0 0 67 0.1s\n", " L 0 0 0 0.00% 109957.085557 110216 0.23% 926 61 0 331 0.9s\n", "\n", "2.8% inactive integer columns, restarting\n", "Model after restart has 92 rows, 147 cols (11 bin., 58 int., 21 impl., 57 cont.), and 270 nonzeros\n", "\n", " 0 0 0 0.00% 109957.143248 110216 0.23% 32 0 0 466 0.9s\n", " 0 0 0 0.00% 110065.335725 110216 0.14% 32 15 2 500 0.9s\n", " L 0 0 0 0.00% 110065.431752 110216 0.14% 48 16 2 503 1.0s\n", "\n", "13.0% inactive integer columns, restarting\n", "Model after restart has 71 rows, 118 cols (12 bin., 46 int., 7 impl., 53 cont.), and 218 nonzeros\n", "\n", " 0 0 0 0.00% 110065.431752 110216 0.14% 16 0 0 695 1.0s\n", " 0 0 0 0.00% 110065.431752 110216 0.14% 16 15 0 718 1.0s\n", "\n", "13.8% inactive integer columns, restarting\n", "Model after restart has 57 rows, 96 cols (3 bin., 42 int., 6 impl., 45 cont.), and 173 nonzeros\n", "\n", " 0 0 0 0.00% 110091.208095 110216 0.11% 11 0 0 783 1.1s\n", " 0 0 0 0.00% 110091.208095 110216 0.11% 11 11 0 796 1.1s\n", "\n", "Solving report\n", " Status Optimal\n", " Primal bound 110216\n", " Dual bound 110206.854428\n", " Gap 0.0083% (tolerance: 0.01%)\n", " Solution status feasible\n", " 110216 (objective)\n", " 0 (bound viol.)\n", " 2.30926389122e-14 (int. viol.)\n", " 0 (row viol.)\n", " Timing 1.31 (total)\n", " 0.00 (presolve)\n", " 0.00 (postsolve)\n", " Nodes 11\n", " LP iterations 1510 (total)\n", " 453 (strong br.)\n", " 347 (separation)\n", " 466 (heuristics)\n", "HiGHS 1.5.1: optimal solution; objective 110216\n", "1510 simplex iterations\n", "11 branching nodes\n", "absmipgap=9.14557, relmipgap=8.29786e-05\n" ] } ], "source": [ "m1 = BIMproduction_v1(\n", " demand=demand,\n", " existing={\"silicon\": 1000, \"germanium\": 1500, \"plastic\": 1750, \"copper\": 4800},\n", " desired={\"silicon\": 500, \"germanium\": 500, \"plastic\": 1000, \"copper\": 2000},\n", " stock_limit=10000,\n", " supplying_copper=[\"B\", \"C\"],\n", " supplying_batches=[\"A\", \"C\"],\n", " price_copper_sheet={\"B\": 300, \"C\": 400},\n", " price_batch={\"A\": 500, \"C\": 600},\n", " discounted_price=700,\n", " batch_size=100,\n", " copper_sheet_mass=100,\n", " copper_bucket_size=10,\n", " unitary_products=[\"silicon\", \"germanium\", \"plastic\"],\n", " unitary_holding_costs={\"copper\": 10, \"silicon\": 2, \"germanium\": 2, \"plastic\": 2},\n", ")\n", "\n", "m1.option[\"solver\"] = SOLVER\n", "m1.option[\"highs_options\"] = \"outlev=1\"\n", "m1.solve()" ] }, { "cell_type": "code", "execution_count": 97, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
silicon912.0787.0527.0310.072.015.00.00.00.056.054.0500.0
plastic1615.01428.01087.0805.0472.068.01.036.024.00.00.01000.0
copper4354.03730.02528.01530.0388.08.044.014.090.054.050.02042.0
germanium1453.01391.01310.01245.01150.01032.0946.0857.0775.0693.0609.0543.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug \\\n", "silicon 912.0 787.0 527.0 310.0 72.0 15.0 0.0 0.0 \n", "plastic 1615.0 1428.0 1087.0 805.0 472.0 68.0 1.0 36.0 \n", "copper 4354.0 3730.0 2528.0 1530.0 388.0 8.0 44.0 14.0 \n", "germanium 1453.0 1391.0 1310.0 1245.0 1150.0 1032.0 946.0 857.0 \n", "\n", " Sep Oct Nov Dec \n", "silicon 0.0 56.0 54.0 500.0 \n", "plastic 24.0 0.0 0.0 1000.0 \n", "copper 90.0 54.0 50.0 2042.0 \n", "germanium 775.0 693.0 609.0 543.0 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "stock = m1.var[\"ss\"].to_pandas().unstack()\n", "stock.columns = stock.columns.get_level_values(1)\n", "stock = stock.reindex(index=demand.index, columns=demand.columns)\n", "display(stock)" ] }, { "cell_type": "code", "execution_count": 98, "metadata": {}, "outputs": [ { "data": { "image/png": "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WxUqVFD//v31yy+/BIdI/OlPf9LkyZPVoUMHPfLII/r888+LFUtWVpaio6N15ZVXnt9FRbBi9cCoUKGCLrnkkpB98fHxqlq1anD/kCFDNHr0aFWpUkWJiYm6++67lZqaqvbt20uSunXrpqZNm6p///564okntH//fj300EMaMWJEsAfF8OHD9eyzz2rMmDEaPHiwVq1apSVLlmj58uXhuGYAAAAAgFUc7hM9IXSix0D2kSNKrFChdHoMOEpmfsRdu3bp2muv1V133aVHH31UVapU0ccff6whQ4bI6/XK7XZr6NChSktL0/Lly7VixQpNmTJF06ZN0913312kv1HYaITyJOz3kBkzZujaa69Vnz591LFjRyUlJemNN94IHo+OjtY777yj6Ohopaam6vbbb9eAAQM0ceLEYJsGDRpo+fLlysjIUMuWLTVt2jS9+OKLSktLC3e4AAAAAIDSZBgnhnHk/zjcodsl+VPMeSHWr18fsr1u3To1atSowEoamzZtUiAQ0LRp09S+fXs1btxYe/fuLXC+5ORkDR8+XG+88Ybuu+8+vfDCC5Ikp/PE4gin69mRr3nz5goEAlq9enWxrqEsOadlVE+WmZkZsh0XF6fZs2cHJyo5nXr16undd98943k7deqkLVu2nG94AAAAAACck927d2v06NG68847tXnzZj3zzDOaNm1agXYNGzaUz+fTM888o+uuu05r1qzRc889F9Jm1KhR6tGjhxo3bqzffvtNH3zwQXCVk3r16skwDL3zzju65ppr5HK5gquX5Ktfv74GDhyowYMHByfx/P7773Xw4EHdfPPNJZcEG7HPrB4AAAAAANjIgAED5PF4dNlll2nEiBG65557NGzYsALtWrZsqenTp2vq1Km65JJLtHDhwuDSp/ny8vI0YsQIpaSkqHv37mrcuLHmzJkjSapdu7YmTJigP//5z6pZs6ZGjhx52njmzp2rG2+8UX/84x918cUX64477tCxY8fCf+E2dd49MAAAAAAAKIscDodmzpwZXEnkZLt27QrZvvfee0NWIpGk/v37B39/5plnzvi3Hn74YT388MMh+0434mH69OmaPn16EaIve+iBAQAAAAAAbI8CBgAAAAAAsD2GkAAAAAAAcIpTh2/AevTAAAAAAAAAtkcBAwAAAAAA2B4FDAAAAAAAYHvMgYFz5vF7rA7hjPx+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QgUNHDhQDzzwgD744AN9+eWXGjJkiKKiomQYhho3bqx+/fppwIABeuONN7Rz505t2LBBU6ZM0fLly8MeT6NGjZSRkaFPPvlE27dv15133qkDB85v9pCGDRsqOTlZ48eP1zfffKPly5dr2rRpYYo4/ChgAAAAAABwGtOnT1dqaqquvfZade3aVR06dFBKSori4uIknZjUc8CAAbrvvvvUpEkT9e7dO6THRjg99NBDatOmjdLS0tSpUyclJSWpd+/e53VOh8OhV199VV999ZVatGihqVOnavLkyeEJuAQwhAQAAAAAgNOoUKGCFi5cGNw+duyYJkyYoGHDhkk6UQCYMGGCJkyYcNrbDxo0SIMGDSqwf9euXQX2nbrEa/369UP2ValSRW+99dYZ4121apWys7ND9p16m1P/TocOHfT5558X2uZ01zB+/HiNHz8+ZN/8+fPPGFs4UMAAAAAAAOA0tmzZoq+++kqXXXaZDh8+rIkTJ0qSevXqZXFk5RMFDAAAAAAACvHUU09px44dcjqdatu2rT766CNVq1bN6rDKJQoYAAAAAACcRuvWrbVp0yarw8B/MYknAAAAAACwPQoYAAAAAIASd+rkkSg/wvVvTwEDAAAAAFBioqOjJUler9fiSGCV/H/7/PvCuWIODAAAAABAiYmJiZHb7dZPP/0kh8OhqKj/fY8eCATk9Xp1/PjxkP04N3bMZyAQ0E8//SS3262YmPMrQVDAAAAAAACUGMMwdMEFF2jnzp36/vvvQ46ZpimPxyOXyyXDMCyKsOywaz6joqJUt27d846JAgYAAAAAoEQ5nU41atSowDASn8+nDz/8UB07dpTD4bAourLDrvl0Op1h6RFCAQMAAAAAUOKioqIUFxcXsi86Olp+v19xcXG2+sAdqcp6PotVApk7d65atGihxMREJSYmKjU1Vf/+97+Dx48fP64RI0aoatWqSkhIUJ8+fXTgwIGQc+zevVs9e/aU2+1WjRo19MADD8jv94e0yczMVJs2bRQbG6uGDRtq/vz5536FAAAAAAAg4hWrgFGnTh09/vjj2rRpkz799FN17txZvXr10pdffilJuvfee/X2229r6dKlWr16tfbu3asbbrghePu8vDz17NlTXq9Xn3zyiRYsWKD58+dr3LhxwTY7d+5Uz549ddVVVykrK0ujRo3S0KFDlZ6eHqZLBgAA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27ZttXz5cn3xxRcaNmyY+vfvrw0bNoS0W7BggeLj47V+/Xo98cQTmjhxojIyMiyK2r58Pp/S0tJUoUIFffTRR1qzZo0SEhLUvXv3kALFypUrg9X0V199VW+88YYmTJhgYeTW6t+/vz766CPt3r1bkvTPf/5T9evXV5s2bULaFef+6nQ6tWbNGj333HOldh12cOTIES1dulS33367rr76ah0+fFgfffRRgXZPPvmkWrZsqS1btujPf/6z7rnnngKP6fHjx+v666/X1q1bNXjw4NK6BFsI133ypptuUl5enpYtWxa8zcGDB7V8+fJyl9Nz9cEHH+jbb7/VBx98oAULFmj+/Pl0+0VYFPX58kyys7N13XXXqXnz5tq8ebMmTZqksWPHllDE9pWUlKR169bp+++/L7TNwoULNW7cOD366KPavn27HnvsMT388MMFer488MADuu+++7Rlyxalpqbquuuu0y+//FLSl2ArixYtUuPGjXXdddcVOHbffffpl19+Cb5mL1++XNdff72uueYabdmyRStXrtRll10mSXrjjTdUp04dTZw4MdhjuDwaNWqU/H6/nnnmmdMeDwQC6tWrl3799VetXr1aGRkZ+u6773TLLbdIkho3bqx27dpp4cKFIbdbuHChbrvtthKP385cLpeGDx+uNWvW6ODBg5LO/lg/evSorrzySv34449atmyZPvvsM40ZM0aBQMDKSykaE6Xugw8+MCWZv/32W6Ftevbsad53333B7SuvvNL83e9+F9Lm0ksvNceOHVtSYdpafg7j4+NDfn7++Wfz73//u9mkSRMzEAgE2+fm5poul8tMT083TdM0Bw4caFapUsU8duxYsM3cuXPNhIQEMy8vr9Svx0on3x979+5tTpgwwTRN07zqqqvMWbNmmW+++aZ5tqeK091fW7duXaJx29nzzz9vtmrVKrh9zz33mAMHDgxpU69ePbN79+4h+2655RazR48ewW1J5qhRo0o0VjsqifvkXXfdFZLbadOmmRdeeGHI80R5cOrrzyOPPGK2bNkypM2MGTPMevXqBbcHDhxo1qtXz/T7/cF9N910k3nLLbeUQsSR4Vzz2qtXr1KL0a6K8nwpyXzzzTdD9lWsWNGcN2+eaZonXr+rVq1qejye4PEXXnjBlGRu2bKlhCK3n71795rt27c3JZmNGzc2Bw4caL722msh72suuugic9GiRSG3mzRpkpmammqapmnu3LnTlGQ+/vjjweM+n8+sU6eOOXXq1NK5EJu4+OKLC32M/vrrr6akYE5SU1PNfv36FXquevXqmTNmzCiBKO3v5OfH5557zqxSpYp56NAh0zRDH8crVqwwo6Ojzd27dwdv++WXX5qSzA0bNpimeeJ59KKLLgoe37FjhynJ3L59e+ldkIXO9Bny3//+tynJXL9+vWmaZ3+s/+1vfzMrVKhg/vLLLyUed7jRA8MG8vLyNGnSJDVv3lxVqlRRQkKC0tPTg9865mvRokXI9gUXXBCsspVXH330kbKysoI/lStX1meffab//Oc/qlChghISEpSQkKAqVaro+PHj+vbbb4O3bdmypdxud3A7NTVVR48e1Z49e6y4FFsYPHiw5s+fr++++05r165Vv379CrQp6v21bdu2pRW27bz88su6/fbbg9u33367li5dqiNHjoS0S01NLbC9ffv2kH3t2rUruUAjQLjuk3fccYdWrFihH3/8UZI0f/58DRo0qNx25S2uZs2aKTo6OrjN6w/CpajPl2eyY8cOtWjRQnFxccF9+d9+lycXXHCB1q5dq61bt+qee+6R3+/XwIED1b17dwUCAR07dkzffvuthgwZEnx/lJCQoMmTJ4e8P5JCX59iYmLUrl27Aq9P5YFpmkVql5WVpS5dupRwNJFvyJAhqlq1qqZOnVrg2Pbt25WcnKzk5OTgvqZNm6pSpUrB+96tt96qXbt2ad26dZJO9DJo06aNLr744tK5ABvLv68ahlGkx3pWVpZat26tKlWqWBn2OYmxOgCc6EY+a9YszZw5U82bN1d8fLxGjRpVYD4Gh8MRsm0YRmR08ylBDRo0UKVKlUL2HT16VG3bti3QxUySqlevXkqRRaYePXpo2LBhGjJkiK677jpVrVq1QJui3l/j4+NLK2xb2bZtm9atW6cNGzaEdGHOy8vT4sWLdccddxTrfOU1j/nCdZ9s3bq1WrZsqVdeeUXdunXTl19+qeXLl5fmpdhSVFRUgTfop5vbgtef4ilqXsu7oj5fGoZBPovhkksu0SWXXKI//vGPGj58uK644gqtXr1aTZs2lXRipZLLL7885DYnFyhxQuPGjQst2uTvb9y4saQTXfhxdjExMXr00Uc1aNAgjRw5sti3T0pKUufOnbVo0SK1b99eixYtKjDvQ3mVf5+sX79+cOLeMz3WI/k+Sw8MG1izZo169eql22+/XS1bttSFF16or7/+2uqwIlabNm30zTffqEaNGmrYsGHIT8WKFYPtPvvsM3k8nuD2unXrlJCQEFL5LW9iYmI0YMAAZWZmFjo3APfXM3vppZfUsWNHffbZZyG9g0aPHq2XXnoppG3+Nwgnb6ekpJRmuLYXzvvk0KFDNX/+fM2bN09du3Yt14/1fNWrV9f+/ftDPhyWp0kPSwp5LZqiPl9Wr149ZN6Ab775Rjk5OcHtJk2aaOvWrSGTA27cuLF0LsLm8osWx44dU82aNVWrVi199913Bd4fNWjQIOR2J78++f1+bdq0qdy9Pt1666365ptv9Pbbbxc4Nm3aNFWtWlVXX321pBO9pFeuXFnouZxOZ4ElRMurm266Sc2aNSsw71xKSor27NkT0hN627ZtOnToUPB+LEn9+vXTa6+9prVr1+q7777TrbfeWmqx25XH49Hzzz+vjh07qnr16kV6rLdo0UJZWVn69ddfLY6++Chg2ECjRo2UkZGhTz75RNu3b9edd94ZnHEXxdevXz9Vq1ZNvXr10kcffaSdO3cqMzNTf/rTn/TDDz8E23m9Xg0ZMkTbtm3Tu+++q0ceeUQjR45UVFT5flhMmjRJP/30U6Erh3B/LZzP59Pf//539e3bN/gNWP7P0KFDtX79en355ZfB9mvWrNETTzyhr7/+WrNnz9bSpUt1zz33WHgF9hSu++Rtt92mH374QS+88AKTd/5Xp06d9NNPP+mJJ57Qt99+q9mzZ+vf//631WFFPPJ6dsV5vuzcubOeffZZbdmyRZ9++qmGDx8e0ivotttuUyAQ0LBhw7R9+3alp6frqaeekqRyNUzsrrvu0qRJk7RmzRp9//33WrdunQYMGKDq1asHh4RMmDBBU6ZM0dNPP62vv/5aW7du1bx58zR9+vSQc82ePVtvvvmmvvrqK40YMUK//fZbuXvevPXWW3X99ddr4MCBeumll7Rr1y59/vnnuvPOO7Vs2TK9+OKLwV6SjzzyiF599VU98sgj2r59u7Zu3RoyTKJ+/fr68MMP9eOPP+rnn3+26pJs4/HHH9fLL78csopL165d1bx5c/Xr10+bN2/Whg0bNGDAAF155ZUhw2lvuOEGHTlyRHfddZeuuuoq1apVy4pLsNTBgwe1f/9+ffPNN1q8eLE6dOign3/+WXPnzg22OdtjvW/fvkpKSlLv3r21Zs0afffdd/rnP/+ptWvXWnVZRVa+P6lZJL/bbUzMiRE8Dz30kNq0aaO0tDR16tQpeGfCuXG73frwww9Vt25d3XDDDUpJSdGQIUN0/PhxJSYmBtt16dJFjRo1UseOHXXLLbfo97//PUvT6sS3BNWqVSv0TR/318ItW7ZMv/zyS3DptJOlpKQoJSUl5FvF++67T59++qlat26tyZMna/r06eVyydmzCdd9smLFiurTp48SEhLK7X321NeflJQUzZkzR7Nnz1bLli21YcMG3X///VaGGJHIa/EV5/ly2rRpSk5O1hVXXKHbbrtN999/f8gcVomJiXr77beVlZWlVq1a6a9//avGjRsnSSHzYpR1Xbt21bp163TTTTepcePG6tOnj+Li4rRy5crg8LuhQ4fqxRdf1Lx589S8eXNdeeWVmj9/foEeGI8//rgef/xxtWzZUh9//LGWLVumatWqWXFZljEMQ0uWLNFf/vIXzZgxQ02aNNEVV1yh77//XpmZmSGvI506ddLSpUu1bNkytWrVSp07dw5ZnW3ixInatWuXLrroIoYz60RRsnPnzvL7/cF9hmHoX//6lypXrqyOHTuqa9euuvDCC/Xaa6+F3LZChQq67rrr9Nlnn512XqzyoEmTJqpVq5batm2rxx9/XF27dtUXX3wR0lPlbI91p9OpFStWqEaNGrrmmmvUvHlzPf744xExnMwwizo7DcImf1xncSaoAlC21K9fX6NGjdKoUaOsDqVc6dKli5o1a6ann37a6lAswetPySCv9rNw4UL94Q9/0OHDhyN6rHdp27Vrlxo0aKAtW7aoVatWVocDAAUwiWcpys3N1bfffqtnn32WmYoBoBT99ttvyszMVGZmpubMmWN1OKWO15+SQV7t45VXXtGFF16o2rVr67PPPtPYsWN18803U7wAgDKGISSl6N///rcuv/xyxcfHl9tv/wDACq1bt9agQYM0depUNWnSxOpwSh2vPyWDvNrH/v37dfvttyslJUX33nuvbrrpJj3//PNWhwUACDOGkAAAAAAAANujBwYAAAAAALA9ChgAAAAAAMD2KGAAAAAAAADbo4ABAAAAAABsjwIGAAAAAACwPQoYAAAAAADA9ihgAAAAAAAA26OAAQAAAAAAbO//AZZN4ZuRmf+9AAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt, numpy as np\n", "\n", "stock.T.plot(drawstyle=\"steps-mid\", grid=True, figsize=(13, 4))\n", "plt.xticks(np.arange(len(stock.columns)), stock.columns)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 99, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
pp.val0.00.00.00.00.03.05.06.06.07.06.020.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n", "pp.val 0.0 0.0 0.0 0.0 0.0 3.0 5.0 6.0 6.0 7.0 6.0 20.0" ] }, "execution_count": 99, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m1.var[\"pp\"].to_pandas().T" ] }, { "cell_type": "code", "execution_count": 100, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
silicon0.00.00.00.00.0229.0233.0238.0265.0349.0257.0690.0
plastic0.00.00.00.00.00.0267.0362.0335.0351.0343.01310.0
copper0.00.00.00.00.01000.01200.01100.01300.01300.01200.03100.0
germanium0.00.00.00.00.00.00.00.00.00.00.00.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep \\\n", "silicon 0.0 0.0 0.0 0.0 0.0 229.0 233.0 238.0 265.0 \n", "plastic 0.0 0.0 0.0 0.0 0.0 0.0 267.0 362.0 335.0 \n", "copper 0.0 0.0 0.0 0.0 0.0 1000.0 1200.0 1100.0 1300.0 \n", "germanium 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "\n", " Oct Nov Dec \n", "silicon 349.0 257.0 690.0 \n", "plastic 351.0 343.0 1310.0 \n", "copper 1300.0 1200.0 3100.0 \n", "germanium 0.0 0.0 0.0 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "df = m1.var[\"uu\"].to_pandas().unstack()\n", "df.columns = df.columns.get_level_values(1)\n", "df = df.reindex(index=demand.index, columns=demand.columns)\n", "display(df)" ] }, { "cell_type": "code", "execution_count": 101, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
A0.00.00.00.00.00.00.00.00.00.00.00.0
C0.00.00.00.00.03.05.06.06.07.06.020.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n", "A 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", "C 0.0 0.0 0.0 0.0 0.0 3.0 5.0 6.0 6.0 7.0 6.0 20.0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "b = m1.var[\"bb\"].to_pandas().unstack().T\n", "b.index = b.index.get_level_values(1)\n", "b = b.reindex(columns=demand.columns)\n", "b.index.names = [None]\n", "display(b)" ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
B0.00.00.00.00.07.07.05.07.06.06.011.0
C0.00.00.00.00.03.05.06.06.07.06.020.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n", "B 0.0 0.0 0.0 0.0 0.0 7.0 7.0 5.0 7.0 6.0 6.0 11.0\n", "C 0.0 0.0 0.0 0.0 0.0 3.0 5.0 6.0 6.0 7.0 6.0 20.0" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "y = m1.var[\"y\"].to_pandas().unstack().T\n", "y.index = y.index.get_level_values(1)\n", "y = y.reindex(columns=demand.columns)\n", "y.index.names = [None]\n", "display(y)" ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
JanFebMarAprMayJunJulAugSepOctNovDec
materialssupplier
germaniumA0.00.00.00.00.00.00.00.00.00.00.00.0
C0.00.00.00.00.00.00.00.00.00.00.00.0
plasticA0.00.00.00.00.00.00.00.00.00.00.00.0
C0.00.00.00.00.00.0267.0362.0335.0351.0343.01310.0
siliconA0.00.00.00.00.00.00.00.00.00.00.00.0
C0.00.00.00.00.0229.0233.0238.0265.0349.0257.0690.0
\n", "
" ], "text/plain": [ " Jan Feb Mar Apr May Jun Jul Aug Sep \\\n", "materials supplier \n", "germanium A 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", " C 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", "plastic A 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", " C 0.0 0.0 0.0 0.0 0.0 0.0 267.0 362.0 335.0 \n", "silicon A 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \n", " C 0.0 0.0 0.0 0.0 0.0 229.0 233.0 238.0 265.0 \n", "\n", " Oct Nov Dec \n", "materials supplier \n", "germanium A 0.0 0.0 0.0 \n", " C 0.0 0.0 0.0 \n", "plastic A 0.0 0.0 0.0 \n", " C 351.0 343.0 1310.0 \n", "silicon A 0.0 0.0 0.0 \n", " C 349.0 257.0 690.0 " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x = m1.var[\"x\"].to_pandas().unstack(1)\n", "x.columns = x.columns.get_level_values(1)\n", "x = x.reindex(columns=demand.columns)\n", "x.index.names = [\"materials\", \"supplier\"]\n", "display(x)" ] } ], "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.10.6" } }, "nbformat": 4, "nbformat_minor": 4 }