{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "_eaxyfyDjXUh" }, "source": [ "# Extra Material: Cutting Stock\n", "\n", "The cutting stock problem is familiar to anyone who has cut parts out of stock materials. In the one-dimensional case, the stock materials are available in predetermined lengths and prices. The task is to cut a specific list of parts from the stock materials. The problem is to determine which parts to cut from each piece of stock material to minimize cost. This problem applies broadly to commercial applications, including the allocation of non-physical resources like capital budgeting or resource allocation.\n", "\n", "This notebook presents several models and solution algorithms for the cutting stock problem." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "bmKKVtw8jcK6", "outputId": "1d4c996e-3b8d-4a08-dbea-a418b1a12d9f" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.6/5.6 MB\u001b[0m \u001b[31m17.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", "\u001b[?25hUsing default Community Edition License for Colab. Get yours at: https://ampl.com/ce\n", "Licensed to AMPL Community Edition License for the AMPL Model Colaboratory (https://colab.ampl.com).\n" ] } ], "source": [ "# install AMPL and solvers\n", "%pip install -q amplpy\n", "\n", "SOLVER_MILO = \"highs\"\n", "SOLVER_MINLO = \"ipopt\"\n", "\n", "from amplpy import AMPL, ampl_notebook\n", "\n", "ampl = ampl_notebook(\n", " modules=[\"coin\", \"highs\"], # modules to install\n", " license_uuid=\"default\", # license to use\n", ") # instantiate AMPL object and register magics" ] }, { "cell_type": "markdown", "metadata": { "id": "IYpKQkbd7fnS" }, "source": [ "## Problem formulation\n", "\n", "Consider a set ${S}$ of available stock materials that can be cut to size to produce a set of finished parts. Each stock $s\\in S$ is characterized by a length $l^S_s$, a cost $c_s$ per piece, and is available in unlimited quantity. A customer order is received to product a set of finished products $F$. Each finished product $f\\in F$ is specified by a required number $d_f$ and length $l^F_f$.\n", "\n", "The **cutting stock problem** is to find a minimum cost solution to fulfill the customer order from the stock materials. The problem is illustrated is by data for an example given in the original paper by Gilmore and Gamory (1961).\n", "\n", "**Stocks**\n", "\n", "| stocks
$s$ | length
$l^S_s$ | cost
$c_s$ |\n", "| :--: | :--: | :--: |\n", "| A | 5 | 6 |\n", "| B | 6 | 7 |\n", "| C | 9 |10 |\n", "\n", "**Finished Parts**\n", "\n", "| finished parts
$f$ | length
$l^F_f$ | demand
$d_f$ |\n", "| :--: | :--: | :--: |\n", "| S | 2 | 20 |\n", "| M | 3 | 10 |\n", "| L | 4 | 20 |\n", "\n", "This information is represented in Python as nested dictionaries where the names for stocks and finished parts are used as indices." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "nNeexBEbDCfB" }, "outputs": [], "source": [ "stocks = {\n", " \"A\": {\"length\": 5, \"cost\": 6},\n", " \"B\": {\"length\": 6, \"cost\": 7},\n", " \"C\": {\"length\": 9, \"cost\": 10},\n", "}\n", "\n", "finish = {\n", " \"S\": {\"length\": 2, \"demand\": 20},\n", " \"M\": {\"length\": 3, \"demand\": 10},\n", " \"L\": {\"length\": 4, \"demand\": 20},\n", "}" ] }, { "cell_type": "markdown", "metadata": { "id": "nu-V5mAWD_h0" }, "source": [ "## Patterns\n", "\n", "One approach to solving this problem is to create a list of all finished parts, a list of stocks for each length, and then use a set of binary decision variables to assign each finished product to a particular piece of stock. This approach will work well for a small problems, but the computational complexity scales much too rapidly with the size of the problem to be practical for business applications.\n", "\n", "To address the issue of computational complexity, in 1961 Gilmore and Gamory introduced an additional data structure for the problem that is now referred to as \"patterns\". A pattern is a list of finished parts that can be cut from a particular stock item.\n", "\n", "A pattern $p$ is specified by the stock $s_p$ assigned to the pattern and integers $a_{pf}$ that specify how many finished parts of type $f$ are cut from stock $s_p$. A pattern $p\\in P$ is feasible if\n", "\n", "$$\n", "\\begin{align}\n", "\\sum_{f\\in F}a_{pf}l^F_f & \\leq l^S_{s_p}\n", "\\end{align}\n", "$$\n", "\n", "The function `make_patterns` defined below produces a partial list of feasible patterns for given sets of stocks and finished parts. Each pattern is represented as dictionary that specifies an associated stock item, and a dictionary of cuts that specify the finished parts cut from the stock. The algorithm is simple, it just considers every finished parts and stock items, then reports the number of parts $f$ that can be cut from stock item $s$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 183 }, "id": "DE0rQaiZDCfC", "outputId": "7f34b301-8949-4865-93b9-90b1357c1ca5" }, "outputs": [ { "data": { "text/plain": [ "[{'stock': 'A', 'cuts': {'S': 2, 'M': 0, 'L': 0}},\n", " {'stock': 'B', 'cuts': {'S': 3, 'M': 0, 'L': 0}},\n", " {'stock': 'C', 'cuts': {'S': 4, 'M': 0, 'L': 0}},\n", " {'stock': 'A', 'cuts': {'S': 0, 'M': 1, 'L': 0}},\n", " {'stock': 'B', 'cuts': {'S': 0, 'M': 2, 'L': 0}},\n", " {'stock': 'C', 'cuts': {'S': 0, 'M': 3, 'L': 0}},\n", " {'stock': 'A', 'cuts': {'S': 0, 'M': 0, 'L': 1}},\n", " {'stock': 'B', 'cuts': {'S': 0, 'M': 0, 'L': 1}},\n", " {'stock': 'C', 'cuts': {'S': 0, 'M': 0, 'L': 2}}]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def make_patterns(stocks, finish):\n", " \"\"\"\n", " Generates patterns of feasible cuts from stock lengths to meet specified finish lengths.\n", "\n", " Parameters:\n", " stocks (dict): A dictionary where keys are stock identifiers and values are dictionaries\n", " with key 'length' representing the length of each stock.\n", "\n", " finish (dict): A dictionary where keys are finish identifiers and values are dictionaries\n", " with key 'length' representing the required finish lengths.\n", "\n", " Returns:\n", " patterns (list): A list of dictionaries, where each dictionary represents a pattern of cuts.\n", " Each pattern dictionary contains 'stock' (the stock identifier) and 'cuts'\n", " (a dictionary where keys are finish identifiers and the value is the number\n", " of cuts from the stock for each finish).\n", " \"\"\"\n", "\n", " patterns = []\n", " for f in finish:\n", " feasible = False\n", " for s in stocks:\n", " # max number of f that fit on s\n", " num_cuts = int(stocks[s][\"length\"] / finish[f][\"length\"])\n", "\n", " # make pattern and add to list of patterns\n", " if num_cuts > 0:\n", " feasible = True\n", " cuts_dict = {key: 0 for key in finish.keys()}\n", " cuts_dict[f] = num_cuts\n", " patterns.append({\"stock\": s, \"cuts\": cuts_dict})\n", "\n", " if not feasible:\n", " print(f\"No feasible pattern was found for {f}\")\n", " return []\n", "\n", " return patterns\n", "\n", "\n", "patterns = make_patterns(stocks, finish)\n", "display(patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "JqOW10HNDCfD" }, "source": [ "The function `plot_patterns`, defined below, displays a graphical depiction of the list of patterns." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 341 }, "id": "Gz3YrXfVDCfE", "outputId": "2b5f6410-171b-4c0a-e3d7-2c9890f2895e" }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAApEAAAFECAYAAAB/MGXKAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAhKElEQVR4nO3de4xU9d3H8c8wMxzGZVlWlqsCKwJCBbvcVVpCA5bUS+TpBrYNrVY3+ofYlpLHwDSxshGdemlDFZFKRmlU7CpiCkZNrDxVoSDIulUBuQlCamCBxWFhcZjb8wdCumVVfrBnfrPnvF/JCeRwDB/zPbt89pw55xfI5XI5AQAAAAY62A4AAACA9ocSCQAAAGOUSAAAABijRAIAAMAYJRIAAADGKJEAAAAwRokEAACAMUokAAAAjFEiAQAAYIwSCQAAAGOUSAAAABijRAIAAMAYJRIAAADGKJEAAAAwRokEAACAMUokAAAAjFEiAQAAYIwSCQAAAGOUSAAAABijRAIAAMBYyHYAt+3cn9CxZNp2DOTRkeaTKr2oo+0YyKMiJ6jybkW2Y1gRCAQUCnn+WzmAAuTp7zw79yc0ecEa2zGQR92LHc0Y10/Pv7dXB5uStuMgD07PfExZVt0v8t/NlXA4rL59+1IkAeSdp7/rcAXSf3oUO5o1ebDe3HKAEukTp2e+fstuOY6/SmQ6nVYqlVIul7MdBYAPebpEAvCPYDCkcDhoO0beZTIZ2xEA+JS/fmwHAABAm6BEAgAAwBi3sz3o/puH6eaKPgp2CGj3oeOa+sRapbN8ZsrLmDkAIN+MS2RTU5N69+6tqqoqxeNxNzLhAlxzeTf9/Jr+un3pRu1rbNb4gWW2I8FlzBwAYIPx7eza2lqNGjVKK1as0LFjx9zIhAvQpdOpnwtuvKq3ysuK9MKGvVyR8jhmDgCwwbhExuNxzZkzRxMmTFBtbe3XHpdMJnX06NEWWzLJK1fc9vb2g9r02RH9eOSlWnLLaP3f/05Uj2LHdiy4iJkDAGwwKpFbtmzRvn37NGXKFFVXV3/j7exYLKaSkpIWWywWu+DA+GZfprKqfPKfuuGxd/XM2t3q0zWiHwzpYTsWXMTMAQA2GJXIeDyuW265RcFgUNdff712796trVu3tnpsNBpVIpFosUWj0TYJja83prxUd3x/gNKZnNZ/2ihJOnyMK8BexswBADac84M1qVRKzz77rMLhsJYtWyZJam5uVjwe16OPPnrW8Y7jyHG4pZZvzSczurmij2ZfN1ipTFbLN+3T6k8abMeCi5g5AMCGcy6RK1eu1IABA7R+/foz+7Zu3aqJEycqFospHA67EhBmNn9+VDc+znrhfsLMAQA2nPPt7Hg8rhkzZrTYN3ToUF1yySVatWpVmwcDAABA4TrnK5GvvfZaq/vr6uraLAwAAADaB5Y9BAAAgDFKJAAAAIyxdjYAT8hk0kqlsrZj5FU6nbYdAYCPebpEdnY8/b+HVjQ0JbXg79vV0MR7Ev3i9MzHlGWVDPnv5ko4HFYgELAdA4APBXK5nKcX2d25P6FjSX5a95MjzSdVelFH2zGQR0VOUOXdimzHsCIQCCgU4gdmAPnn+RIJAACAtue/ez8AAAC4YJRIAAAAGKNEAgAAwBglEgAAAMYokQAAADBGiQQAAIAxSiQAAACMUSIBAABgzPPLHLBijf+wYo3/MHP/6eyENLBXie0YgK95ukTu3J/Q5AVrbMdAHnUvdjRjXD89/95eHWT9bF9g5v5zeuY3dwjosh5dbMcBfMvTt7O5Auk/PYodzZo8WD2KHdtRkCfM3H9Oz/xkJms7CuBrni6RAAAAcAclEgAAAMYokQAAADBm9GBNeXm5HMdRJBJRMpnUiBEjtGTJEhUVFbmVD+fh/puH6eaKPgp2CGj3oeOa+sRapbM527HgImbuP8wcgG3GT2fX1taqoqJC2WxWN910k5YuXaqZM2e6kQ3n4ZrLu+nn1/TX7Us3al9js8YPLLMdCS5j5v7DzAEUgvO+nX3y5Ek1NzertLS0LfPgAnXpdOrnghuv6q3ysiK9sGEvVyc8jpn7DzMHUAiMr0RWVVUpEoloz549GjVqlKZPn97qcclkUslky3e2OY4jx+E1HG56e/tBbfrsiH488lL9eOSl+vyLE5r6xFo18P48z2Lm/sPMARQC4yuRtbW1qq+v16FDh1ReXq45c+a0elwsFlNJSUmLLRaLXXBgfLMvU1lVPvlP3fDYu3pm7W716RrRD4b0sB0LLmLm/sPMARSC876dHQqFVFlZqTfeeKPVP49Go0okEi22aDR63kFxbsaUl+qO7w9QOpPT+k8bJUmHj3F1wsuYuf8wcwCF4IKWPVy9erWuuOKKVv+MW9d2NJ/M6OaKPpp93WClMlkt37RPqz9psB0LLmLm/sPMARSC8/5MZDqdVv/+/bV48WI3cuE8bf78qG58nPXC/YSZ+w8zB1AIjErknj17XIoBAACA9oQVawAAAGCMEgkAAABjlEgAAAAYo0QCAADAmKdLZGfngt5ghHaooSmpBX/fzsodPsLM/ef0zDsGPf1PGFDwArlcztMLru7cn9CxZNp2DOTRkeaTKr2oo+0YyCNm7j+dnZAG9iqxHQPwNc+XSAAAALQ97gUAAADAGCUSAAAAxiiRAAAAMEaJBAAAgDFKJAAAAIxRIgEAAGCMEgkAAABjnl/ShZeN+w8vnvYfZu4/zNyfipygyrsV2Y6Rd4FAQKFQ4VW2wkvUhnbuT2jygjW2YyCPuhc7mjGun55/b68OsgyeLzBz/2Hm/nR67mPKsup+kb9upIbDYfXt27fgimRhpWljXIH0nx7FjmZNHqw3txzgHxefYOb+w8z96fTc12/ZLcfxT4lMp9NKpVIqxAUGPV0iAQCAtwSDIYXDQdsx8iqTydiO0Cr/VHkAAAC0GUokAAAAjFEiAQAAYMz4M5HpdFoPPPCAXnjhBYVCIYVCIY0dO1YPP/ywunbt6kJEmLr/5mG6uaKPgh0C2n3ouKY+sVbpbOF9IBdth5n7DzP3J+aOQmJcIqurq9XY2Kh169aptLRUuVxOy5cvV2NjIyWyAFxzeTf9/Jr+un3pRu1rbNb4gWW2I8FlzNx/mLk/MXcUGqMSuXPnTr300kvau3evSktLJZ16Aea0adPOOjaZTCqZbPnqBcdx5DjOBcTFt+nS6dRIb7yqt17/eL9e2LCXn1I9jpn7DzP3J+aOQmP0mci6ujoNGjRIZWXf/tNPLBZTSUlJiy0Wi513UJybt7cf1KbPjujHIy/VkltG6//+d6J6FFPcvYyZ+w8z9yfmjkLj2oM10WhUiUSixRaNRt366/CVL1NZVT75T93w2Lt6Zu1u9eka0Q+G9LAdCy5i5v7DzP2JuaPQGJXIkSNHaseOHTp8+PC3Hus4jrp06dJi41a2+8aUl+qO7w9QOpPT+k8bJUmHj7Gig5cxc/9h5v7E3FFojD4TOXDgQFVWVqq6ulpLly5V165dlcvltGLFCo0YMUIDBgxwKyfOUfPJjG6u6KPZ1w1WKpPV8k37tPqTBtux4CJm7j/M3J+YOwqN8dPZTz/9tObPn69x48YpFAopm81qwoQJmjRpkhv5YGjz50d14+NrbMdAHjFz/2Hm/sTcUWiMS2Q4HFZNTY1qamrcyAMAAIB2gBVrAAAAYIwSCQAAAGPGt7MBAABsyWTSSqWytmPkTTqdth3ha3m6RHZ2PP2/h1Y0NCW14O/b1dDEay/8gpn7DzP3p9NzH1OWVTLkrxup4XBYgUDAdoyzBHK5nKfXTNq5P6FjycJt8Wh7R5pPqvSijrZjII+Yuf8wc38qcoIq71ZkO0beBQIBhUKFd2HM8yUSAAAAbc9f14MBAADQJiiRAAAAMEaJBAAAgDFKJAAAAIxRIgEAAGCMEgkAAABjlEgAAAAYo0QCAADAWOG9/ryNpRu2Kfdlk+0YQH44naWLL7edAnlUqCtZAPA+T3/nSTdsU2jRWNsxgPzo3FMafZv2lo7Xl5FettMgT8LhsPr27UuRBJB3nv6uwxVI+EpxL2liVJEta5VzHNtpkAfpdFqpVEqsXgvABk+XSMCPQuGgwuGw7RjIk0wmYzsCAJ/iwRoAAAAYo0QCAADAGCUSbaP8e9K8hDRnjxTseGpf1XOn9k2eZzMZAABwgXGJbGpqUufOnVVdXe1GHrR3kVJp8JRTvw66znYaAADgEuMSWVtbq1GjRmnFihU6duyYG5nQnu17T7qqSrryf6TP622nAQAALjEukfF4XHPmzNGECRNUW1v7tcclk0kdPXq0xZZMJi8oLNqBD188dQVyTLX04defHwAAoH0zKpFbtmzRvn37NGXKFFVXVysej3/tsbFYTCUlJS22WCx2wYFR4I7slj7/QOo2SNr8iu00AADAJUYlMh6P65ZbblEwGNT111+v3bt3a+vWra0eG41GlUgkWmzRaLRNQqPArf2T9M4j0okjtpMAAACXnPPLxlOplJ599lmFw2EtW7ZMktTc3Kx4PK5HH330rOMdx5HDqhn+tO31UxsAAPCscy6RK1eu1IABA7R+/foz+7Zu3aqJEycqFouxQobf7VkjzSs5e39r+wAAQLt3zrez4/G4ZsyY0WLf0KFDdckll2jVqlVtHgwAAACF65yvRL722mut7q+rq2uzMAAAAGgfWLEGAAAAxiiRAAAAMHbOt7MBtA/pVEapVMp2DORBOp22HQGAj3m6RAY6FduOAORP037pHzGdKB3P6lA+Eg6HFQgEbMcA4EOBXC6Xsx3CTemGbcp92WQ7BpAfTmfp4sttp0AeBQIBhUKevh4AoEB5vkQCAACg7fFgDQAAAIxRIgEAAGCMEgkAAABjlEgAAAAYo0QCAADAGCUSAAAAxiiRAAAAMEaJBAAAgDHPL3PAijXwlRONUuRi2ykA1wU6FSvU4wrbMQBf83SJTDdsU2jRWNsxgPzo3FMafZv0/jPSsQO20wDu+epcT3eYplDZQNtpAN/y9O1srkDCV4p7SROjp34FvOyrcz2XOmk7CeBrni6RAAAAcAclEgAAAMYokQAAADBGiUTbKP+eNC8hzdkjBTue2lf13Kl9k+fZTAa0Pc53ADB7Oru8vFyO4ygSiSiZTGrEiBFasmSJioqK3MqH9iZSKg2eIu1ZIw26znYawF2c7wB8zPhKZG1trerr67V582YlEgktXbrUhVhot/a9J11VJV35P9Ln9bbTAO7ifAfgY+d9O/vkyZNqbm5WaWlpq3+eTCZ19OjRFlsymTzvoGgnPnzx1BWZMdXSh7W20wDu4nwH4GPGJbKqqkoVFRXq1auXOnTooOnTp7d6XCwWU0lJSYstFotdcGAUuCO7pc8/kLoNkja/YjsN4C7OdwA+dt63sw8dOqTy8nLNmTOn1eOi0agSiUSLLRqNXnBgtANr/yS984h04ojtJID7ON8B+NR5384OhUKqrKzUG2+80eqfO46jLl26tNgcxznvoGhHtr1+6h9VwA843wH41AWtnb169WpdccUVbZUF7dmeNdK8krP3t7YPaO843wHAvERWVVUpEokonU6rf//+Wrx4sRu5AAAAUMCMSuSePXtcigEAAID2hBVrAAAAYIwSCQAAAGOUSAAAABjzdIkMdCq2HQHIn6b90j9ip34FvOyrcz0Q7mg7CeBrgVwul7Mdwk3phm3KfdlkOwaQHycapcjFtlMArgt0KlaoB6+YA2zyfIkEAABA2/P07WwAAAC4gxIJAAAAY5RIAAAAGKNEAgAAwBglEgAAAMYokQAAADBGiQQAAIAxSiQAAACMhWwHcBsr1sBXWLEGfsG5Dh8p1BWaPF0i0w3bFFo01nYMID8695RG3ya9/4x07IDtNIB7ONfhJ1+d7+kO0xQqG2g7TQuevp3NFUj4SnEvaWL01K+Al3Guw0++Ot9zqZO2k5zF0yUSAAAA7qBEAgAAwBglEgAAAMaMS2Q6nVZNTY2GDBmiYcOGqaKiQnfeeae++OILF+Kh3Sj/njQvIc3ZIwU7ntpX9dypfZPn2UwGtD3Od/gJ5zu+hvHT2dXV1WpsbNS6detUWlqqXC6n5cuXq7GxUV27dnUhItqVSKk0eIq0Z4006DrbaQB3cb7DTzjf8V+MSuTOnTv10ksvae/evSotLZUkBQIBTZs27axjk8mkkslki32O48hxnAuIi4K37z3pqiqpqLv0eb3U72rbiQD3cL7DTzjf8V+MbmfX1dVp0KBBKisr+9ZjY7GYSkpKWmyxWOy8g6Kd+PDFUz+hjqmWPqy1nQZwF+c7/ITzHf/FtQdrotGoEolEiy0ajbr116FQHNktff6B1G2QtPkV22kAd3G+w0843/FfjG5njxw5Ujt27NDhw4fVrVu3bzyWW9c+tvZPUs9h0okjtpMA7uN8h59wvuM/GF2JHDhwoCorK1VdXX3maexcLqeXX35Zn376qRv50B5te1165xHbKYD84HyHn3C+4z8YP5399NNPa/78+Ro3bpxCoZCy2awmTJigSZMmuZEP7cWeNdK8krP3t7YPaO843+EnnO/4GsYlMhwOq6amRjU1NW7kAQAAQDvAijUAAAAwRokEAACAMUokAAAAjHm6RAY6FduOAORP037pH7FTvwJexrkOP/nqfA+EO9pOcpZALpfL2Q7hpnTDNuW+bLIdA8iPE41S5GLbKQD3ca7DRwKdihXqcYXtGGfxfIkEAABA2/P07WwAAAC4gxIJAAAAY5RIAAAAGKNEAgAAwBglEgAAAMYokQAAADBGiQQAAIAxSiQAAACMhWwHcNuuxl06njpuOwYAFxWFitSvSz/bMawIBAIKhTz/rRxAAfL0d55djbs0ddVU2zEAuKgsUqZpg6epIlyhsnCZ7Th5Fw6H1bdvX4okgLzz9HcdrkAC3tc90l13VdylDTs3yAk7tuPkVTqdViqVEqvXArDB0yUSgH8Eg0GFw2HbMfIuk8nYjgDAp3iwBgAAAMYokQAAADBGiYRnjO45Wh/d+pHG9xlvOwoAAJ5nXCKbmprUuXNnVVdXu5EHAAAA7YBxiaytrdWoUaO0YsUKHTt2zI1MAAAAKHDGJTIej2vOnDmaMGGCamtrv/a4ZDKpo0ePttiSyeQFhQUAAEBhMCqRW7Zs0b59+zRlyhRVV1crHo9/7bGxWEwlJSUttlgsdsGBAQAAYJ9RiYzH47rlllsUDAZ1/fXXa/fu3dq6dWurx0ajUSUSiRZbNBptk9DANxly8RBd3ftqXd37anUI8OwYAABuOOeXjadSKT377LMKh8NatmyZJKm5uVnxeFyPPvroWcc7jiPH8dfqESgMs0bNOvP7sc+P1Yn0CXthAADwqHMukStXrtSAAQO0fv36M/u2bt2qiRMnKhaL+XKlCBSW9w+8r+F/GW47BgAAvnDO9/ri8bhmzJjRYt/QoUN1ySWXaNWqVW0eDAAAAIXrnK9Evvbaa63ur6ura7MwAAAAaB946gAAAADGKJEAAAAwds63swGgkGUyGaVSKdsx8iqdTtuOAMDHPF0ii8JFtiMAcNnBEwe1qH6RKsIVSmb9typWOBxWIBCwHQOADwVyuVzOdgg37WrcpeOp47ZjAHBRUahI/br0sx3DikAgoFDI09cDABQoz5dIAAAAtD0erAEAAIAxSiQAAACMUSIBAABgjBIJAAAAY5RIAAAAGKNEAgAAwBglEgAAAMYokQAAADDm+WUOWLEG8D4/r1jjV6zUA9jn6a/AXY27NHXVVNsxALioLFKmaYOnqSJcobJwme04yJNwOKy+fftSJAGLPP3VxxVIwPu6R7rrroq7tGHnBjlhx3Yc5EE6nVYqlRKr9gJ2ebpEAvCPYDCocDhsOwbyJJPJ2I4A+B4P1gAAAMAYJRIAAADGKJHwjNE9R+ujWz/S+D7jbUcBAMDzjD4TWV5eLsdxFIlElEwmNWLECC1ZskRFRUVu5QMAAEABMr4SWVtbq/r6em3evFmJREJLly51IRYAAAAK2Xk/nX3y5Ek1NzertLS01T9PJpNKJpMt9jmOI8fhFRwAAADtnfGVyKqqKlVUVKhXr17q0KGDpk+f3upxsVhMJSUlLbZYLHbBgQEAAGDfed/OPnTokMrLyzVnzpxWj4tGo0okEi22aDR6wYGBbzPk4iG6uvfVurr31eoQ4NkxAADccN63s0OhkCorK3XPPffoD3/4w1l/zq1r2DJr1Kwzvx/7/FidSJ+wFwYAAI+6oBVrVq9erSuuuKKtsgAX5P0D72v4X4bbjgEAgC8Yl8iqqipFIhGl02n1799fixcvdiMXAAAACphRidyzZ49LMQAAANCe8NQBAAAAjFEiAQAAYOyCHqwBgEKRyWSUSqVsx0AepNNp2xEAyOMlsijMmt6A1x08cVCL6hepIlyhZDb57f8BPCEcDisQCNiOAfhaIJfL5WyHcNOuxl06njpuOwYAFxWFitSvSz/bMZBHgUBAoZCnr4MABc/zJRIAAABtjwdrAAAAYIwSCQAAAGOUSAAAABijRAIAAMAYJRIAAADGKJEAAAAwRokEAACAMUokAAAAjHn+df+sWAN4X+LLhEo6ldiOAcBlfl2dqlBXaCq8RG1oV+MuTV011XYMAC4qi5Rp2uBpemn7Szp04pDtOABccvprvSJcobJwme04eRUOh9W3b9+CK5KFlaaNcQUS8L7uke66q+Iu/WPfPyiRgIed/lrfsHODnLBjO07epNNppVIpFeIq1Z4ukQAAwFuCwaDC4bDtGHmVyWRsR2gVD9YAAADAGCUSAAAAxoxLZDqdVk1NjYYMGaJhw4apoqJCd955p7744gsX4gHnbnTP0fro1o80vs9421EAuIivdaAwGH8msrq6Wo2NjVq3bp1KS0uVy+W0fPlyNTY2qmvXri5EBAAAQKExKpE7d+7USy+9pL1796q0tFTSqXcXTZs2zZVwAAAAKExGJbKurk6DBg1SWdm3v58pmUwqmUy22Oc4jhzHP4/lAwAAeJVrD9bEYjGVlJS02GKxmFt/HQAAAPLI6ErkyJEjtWPHDh0+fFjdunX7xmOj0ahmz57dYh9XIZEPQy4eokzu1Du1NuzfoGwuazkRADfwtQ7YZVQiBw4cqMrKSlVXV2vp0qXq2rWrcrmcVqxYoREjRmjAgAFnjuXWNWyZNWrWmd+PfX6sTqRP2AsDwDV8rQN2GT+d/fTTT2v+/PkaN26cQqGQstmsJkyYoEmTJrmRDzhn7x94X8P/Mtx2DAAu42sdKAzGJTIcDqumpkY1NTVu5AEAAEA7wIo1AAAAMEaJBAAAgDFKJAAAAIwZfyYSAADAlkwmo1QqZTtG3qTTadsRvpanS2RRuMh2BAAuO3jioBbVL9LBEwdtRwHgotNf6xXhCiWzyW//DzwkHA4rEAjYjnGWQC6Xy9kO4aZdjbt0PHXcdgwALkp8mVBJpxLbMQC4rChUpH5d+tmOkXeBQEChUOFd9/N8iQQAAEDb48EaAAAAGKNEAgAAwBglEgAAAMYokQAAADBGiQQAAIAxSiQAAACMUSIBAABgjBIJAAAAY5RIAAAAGPN0iUwmk5o3b56SSX+tselnzNx/mLn/MHN/Yu6Fx9PLHh49elQlJSVKJBLq0qWL7TjIA2buP8zcf5i5PzH3wuPpK5EAAABwByUSAAAAxiiRAAAAMObpEuk4ju677z45jmM7CvKEmfsPM/cfZu5PzL3wePrBGgAAALjD01ciAQAA4A5KJAAAAIxRIgEAAGCMEgkAAABjlEgAAAAY82yJfOKJJ1ReXq5OnTpp3Lhx2rBhg+1IcFEsFtOYMWNUXFysHj16aOrUqdq2bZvtWMiT3//+9woEApo1a5btKHDZv//9b/3sZz9Tt27dFIlENHz4cL3//vu2Y8ElmUxG9957ry677DJFIhFdfvnluv/++8WLZQqDJ0tkbW2tZs+erfvuu091dXX67ne/qylTpqihocF2NLjk7bff1syZM7V+/Xq9+eabSqVS+uEPf6jjx4/bjgaXbdy4UX/+85911VVX2Y4Clx05ckTjx49XOBzW66+/ri1btugPf/iDSktLbUeDSx566CE9+eSTWrhwobZu3aqHHnpIDz/8sB5//HHb0SCPvidy3LhxGjNmjBYuXChJymaz6tu3r375y19q7ty5ltMhHw4ePKgePXro7bff1oQJE2zHgUuOHTumkSNHatGiRZo/f74qKiq0YMEC27Hgkrlz52rt2rV69913bUdBntx4443q2bOn4vH4mX2VlZWKRCJ67rnnLCaD5MErkSdPntSmTZs0efLkM/s6dOigyZMna926dRaTIZ8SiYQk6eKLL7acBG6aOXOmbrjhhhZf7/CulStXavTo0Zo2bZp69OihESNGaMmSJbZjwUXXXnut3nrrLW3fvl2S9K9//Utr1qzRj370I8vJIEkh2wHa2qFDh5TJZNSzZ88W+3v27KlPPvnEUirkUzab1axZszR+/HgNGzbMdhy45K9//avq6uq0ceNG21GQJ59++qmefPJJzZ49W7/97W+1ceNG/epXv1LHjh1166232o4HF8ydO1dHjx7VkCFDFAwGlclk9MADD2jGjBm2o0EeLJHAzJkz9fHHH2vNmjW2o8Al+/bt069//Wu9+eab6tSpk+04yJNsNqvRo0frwQcflCSNGDFCH3/8sRYvXkyJ9KgXX3xRzz//vJYtW6Yrr7xS9fX1mjVrlvr06cPMC4DnSmRZWZmCwaAOHDjQYv+BAwfUq1cvS6mQL3fffbdeffVVvfPOO7r00kttx4FLNm3apIaGBo0cOfLMvkwmo3feeUcLFy5UMplUMBi0mBBu6N27t77zne+02Dd06FC9/PLLlhLBbffcc4/mzp2rn/zkJ5Kk4cOH67PPPlMsFqNEFgDPfSayY8eOGjVqlN56660z+7LZrN566y1dc801FpPBTblcTnfffbdeeeUVrV69WpdddpntSHDRpEmT9NFHH6m+vv7MNnr0aM2YMUP19fUUSI8aP378Wa/u2r59u/r3728pEdzW3NysDh1aVpVgMKhsNmspEf6T565EStLs2bN16623avTo0Ro7dqwWLFig48eP67bbbrMdDS6ZOXOmli1bpr/97W8qLi7W/v37JUklJSWKRCKW06GtFRcXn/V516KiInXr1o3PwXrYb37zG1177bV68MEHNX36dG3YsEFPPfWUnnrqKdvR4JKbbrpJDzzwgPr166crr7xSH3zwgf74xz/q9ttvtx0N8ugrfiRp4cKFeuSRR7R//35VVFToscce07hx42zHgksCgUCr+5955hn94he/yG8YWDFx4kRe8eMDr776qqLRqHbs2KHLLrtMs2fP1h133GE7FlzS1NSke++9V6+88ooaGhrUp08f/fSnP9Xvfvc7dezY0XY83/NsiQQAAIB7PPeZSAAAALiPEgkAAABjlEgAAAAYo0QCAADAGCUSAAAAxiiRAAAAMEaJBAAAgDFKJAAAAIxRIgEAAGCMEgkAAABjlEgAAAAY+3+UsTCO6YTldwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "\n", "\n", "def plot_patterns(stocks, finish, patterns):\n", " # set up figure parameters\n", " lw = 0.6\n", " cmap = plt.get_cmap(\"tab10\")\n", " colors = {f: cmap(k % 10) for k, f in enumerate(finish.keys())}\n", " fig, ax = plt.subplots(1, 1, figsize=(8, 0.05 + 0.4 * len(patterns)))\n", "\n", " for k, pattern in enumerate(patterns):\n", " # get stock key/name\n", " s = pattern[\"stock\"]\n", "\n", " # plot stock as a grey background\n", " y_lo = (-k - lw / 2, -k - lw / 2)\n", " y_hi = (-k + lw / 2, -k + lw / 2)\n", " ax.fill_between((0, stocks[s][\"length\"]), y_lo, y_hi, color=\"k\", alpha=0.1)\n", "\n", " # overlay finished parts\n", " xa = 0\n", " for f, n in pattern[\"cuts\"].items():\n", " for j in range(n):\n", " xb = xa + finish[f][\"length\"]\n", " ax.fill_between((xa, xb), y_lo, y_hi, alpha=1.0, color=colors[f])\n", " ax.plot((xb, xb), (y_lo[0], y_hi[0]), \"w\", lw=1, solid_capstyle=\"butt\")\n", " ax.text(\n", " (xa + xb) / 2,\n", " -k,\n", " f,\n", " ha=\"center\",\n", " va=\"center\",\n", " fontsize=6,\n", " color=\"w\",\n", " weight=\"bold\",\n", " )\n", " xa = xb\n", "\n", " # clean up axes\n", " ax.spines[[\"top\", \"right\", \"left\", \"bottom\"]].set_visible(False)\n", " ax.set_yticks(\n", " range(0, -len(patterns), -1),\n", " [pattern[\"stock\"] for pattern in patterns],\n", " fontsize=8,\n", " )\n", "\n", " return ax\n", "\n", "\n", "ax = plot_patterns(stocks, finish, patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "I9oE0frhDCfE" }, "source": [ "## Optimal cutting using known patterns\n", "\n", "Given a list of patterns, the optimization problem is to compute how many copies of each pattern should be cut to meet the demand for finished parts at minimum cost.\n", "\n", "Let the index $s_p$ denote the stock specified by pattern $p$, and let $x_{s_p}$ denote the number pieces of stock $s_p$ is used. For a given list of patterns, the minimum cost optimization problem is a mixed integer linear optimization (MILO) subject to meeting demand constraints for each finished item.\n", "\n", "$$\n", "\\begin{align}\n", "\\min\\quad & \\sum_{p\\in P} c_{s_p} x_{s_p} \\\\\n", "\\text{s.t.}\\quad\n", "& \\sum_{p\\in P}a_{pf} x_{s_p} \\geq d_f && \\forall f\\in F\\\\\n", "& x_{s_p} \\in \\mathbb{Z}_+ && \\forall p\\in P\\\\\n", "\\end{align}\n", "$$\n", "\n", "The following cell is an AMPL implementation of this optimization model." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "OK_yXfi7DCfF" }, "outputs": [], "source": [ "# Given dictionaries of stocks and finished parts, and a list of patterns,\n", "# find minimum choice of patterns to cut\n", "\n", "\n", "def cut_patterns(stocks, finish, patterns):\n", " m = AMPL()\n", "\n", " m.eval(\n", " \"\"\"\n", " set S;\n", " set F;\n", " set P;\n", "\n", " param c{P};\n", " param a{F, P};\n", " param demand_finish{F};\n", "\n", " var x{P} integer >= 0;\n", "\n", " minimize cost:\n", " sum{p in P} c[p]*x[p];\n", "\n", " subject to demand{f in F}:\n", " sum{p in P} a[f,p]*x[p] >= demand_finish[f];\n", "\n", " \"\"\"\n", " )\n", "\n", " m.set[\"S\"] = list(stocks.keys())\n", " m.set[\"F\"] = list(finish.keys())\n", " m.set[\"P\"] = list(range(len(patterns)))\n", "\n", " s = {p: patterns[p][\"stock\"] for p in range(len(patterns))}\n", " c = {p: stocks[s[p]][\"cost\"] for p in range(len(patterns))}\n", " m.param[\"c\"] = c\n", " a = {\n", " (f, p): patterns[p][\"cuts\"][f]\n", " for p in range(len(patterns))\n", " for f in finish.keys()\n", " }\n", " m.param[\"a\"] = a\n", " m.param[\"demand_finish\"] = {\n", " f_part: finish[f_part][\"demand\"] for f_part in finish.keys()\n", " }\n", "\n", " m.option[\"solver\"] = SOLVER_MILO\n", " m.get_output(\"solve;\")\n", "\n", " return [m.var[\"x\"][p].value() for p in range(len(patterns))], m.obj[\"cost\"].value()\n", "\n", "\n", "x, cost = cut_patterns(stocks, finish, patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "rM5zqGfFDCfG" }, "source": [ "The following function `plot_nonzero_patterns` is wrapper for `plot_patterns` that removes unused patterns from graphic, shows the number of times each pattern is used, and adds cost to the title." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 236 }, "id": "fjW3MT6hDCfG", "outputId": "e3cddf9a-7a24-4993-c02b-e45eb7eaf27d" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def plot_nonzero_patterns(stocks, finish, patterns, x, cost):\n", " k = [j for j, _ in enumerate(x) if _ > 0]\n", " ax = plot_patterns(stocks, finish, [patterns[j] for j in k])\n", " ticks = [\n", " f\"{x[k]} x {pattern['stock']}\" for k, pattern in enumerate(patterns) if x[k] > 0\n", " ]\n", " ax.set_yticks(range(0, -len(k), -1), ticks, fontsize=8)\n", " ax.set_title(f\"Cost = {round(cost,2)}\", fontsize=10)\n", " return ax\n", "\n", "\n", "ax = plot_nonzero_patterns(stocks, finish, patterns, x, cost)" ] }, { "cell_type": "markdown", "metadata": { "id": "JBX4XOSdDCfH" }, "source": [ "## Cutting Stock Problem: Bilinear reformulation\n", "\n", "The `cut_patterns` model requires a known list of cutting patterns. This works well if the patterns comprising an optimal solution to the problem are known. But since they are not initially known, an optimization model is needed that simultaneously solves for an optimal patterns and the cutting list.\n", "\n", "Let binary variable $b_{sp}\\in\\mathbb{Z}_2$ denote the assignment of stock $s$ to pattern $p$, and let $P = 0, 1, \\ldots, N_p-1$ index a list of patterns. For sufficiently large $N_p$, an optimal solution to the stock cutting problem is given by the model\n", "\n", "$$\n", "\\begin{align}\n", "\\min\\quad & \\sum_{s\\in S} \\sum_{p\\in P} c_{s} b_{sp} x_{p} \\\\\n", "\\text{s.t.}\\quad\n", "& \\sum_{s\\in S}b_{sp} = 1 && \\forall p\\in P \\\\\n", "& \\sum_{f\\in F}a_{fp}l^F_f \\leq \\sum_{s\\in S} b_{sp} l^S_s && \\forall p\\in P \\\\\n", "& \\sum_{p\\in P}a_{fp} x_{p} \\geq d_f && \\forall f\\in F\\\\\n", "& a_{fp}, x_p \\in \\mathbb{Z}_+ && \\forall f\\in F, \\forall p\\in P \\\\\n", "& b_{sp} \\in \\mathbb{Z}_2 && \\forall s\\in S, \\forall p\\in P \\\\\n", "\\end{align}\n", "$$\n", "\n", "Since there is no ordering of the patterns, without loss of generality an additional constraint can be added to reduce the symmetries present in the problem.\n", "\n", "$$\n", "\\begin{align}\n", "& x_{p-1} \\geq x_{p} && \\forall p\\in P, p > 0\n", "\\end{align}\n", "$$\n", "\n", "This is a challenging optimization problem with a cost objective that is bilinear with respect to the the decision variables $b_{sp}$ and $x_p$, and a set of constraints for the demand of finished parts that are bilinear in the decision variables $a_{fp}$ and $x_p$. Because the constraints are a lower bound on a positive sum of bilinear terms, a simple substitution to create rotated quadratic cones fails to produce a convex program.\n", "\n", "The following model is a direct translation of the bilinear optimization model into AMPL. A solution is attempted using a mixed-integer nonlinear optimization (MINLO) solver." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 114 }, "id": "hgKqaAzmFDlK", "outputId": "cda7a87f-8df6-4b09-8912-2862cc4a0d28" }, "outputs": [ { "data": { "image/png": "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UqlUiI6ORlFRkXXMYrGgqKgI48aNkzAZ2YsQAsnJySgoKMC+ffswdOhQqSORnT300EM4duwYTCaTddHpdEhMTITJZGJh7aPi4uI6vM6uoqICgYGBEiUie2tuboaTk201UiqVsFgsEiWi/4tXWnuAwWBAUlISdDodxo4di3Xr1qGpqQnz58+XOhrZgV6vR35+Pr744gtotVpUV1cDANzc3KDRaCROR/ag1Wo73LPs6uoKT09P3svch73yyisYP348MjMz8dRTT6G0tBQ5OTnIycmROhrZyaOPPoqMjAz84x//wKhRo1BeXo53330Xzz33nNTRCHzlVY9Zv3491q5di+rqakREROCDDz5AbGys1LHIDhQKxR3Ht27dimeffbZ3w5BkJk2axFdeOYDCwkIsW7YMZ8+exdChQ2EwGPDCCy9IHYvspKGhAW+++SYKCgpQU1ODIUOGYM6cOXjrrbegUqmkjufwWFqJiIiISPZ4TysRERERyR5LKxERERHJHksrEREREckeSysRERERyR5LKxERERHJHksrEREREckeSysRERERyR5LKxERERHJHksrEREREckeSysRERERyR5LKxERERHJ3v8CNXoBydMj+BoAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def bilinear_cut_stock(stocks, finish, Np=len(finish)):\n", " m = AMPL()\n", "\n", " m.eval(\n", " \"\"\"\n", " set S;\n", " set F;\n", " set P;\n", "\n", " param c{S};\n", "\n", " # length stock\n", " param length_s{S};\n", " # length finished pieces\n", " param length_f{F};\n", "\n", " param demand_finish{F};\n", " param a_upper_bound{F};\n", "\n", " # sum of all finished parts\n", " param f_total_demand;\n", "\n", " var a{f in F, p in P} integer >= 0 <= a_upper_bound[f];\n", " var b{S, P} binary;\n", " var x{P} integer >= 0 <= f_total_demand;\n", "\n", " minimize cost:\n", " sum{p in P, s in S} c[s] * b[s,p] * x[p];\n", "\n", " subject to assign_each_stock_to_pattern{p in P}:\n", " sum{s in S} b[s,p] = 1;\n", "\n", " subject to feasible_pattern{p in P}:\n", " sum{f in F} a[f,p] * length_f[f] <= sum{s in S} b[s,p] * length_s[s];\n", "\n", " subject to demand{f in F}:\n", " sum{p in P} a[f,p]*x[p] >= demand_finish[f];\n", "\n", " # order the patterns to reduce symmetries\n", " subject to order{p in P : p >= 1}:\n", " x[p] <= x[p-1];\n", "\n", " subject to max_patterns:\n", " sum{p in P} x[p] <= f_total_demand;\n", " \"\"\"\n", " )\n", "\n", " m.set[\"S\"] = list(stocks.keys())\n", " m.set[\"F\"] = list(finish.keys())\n", " m.set[\"P\"] = list(range(Np))\n", "\n", " m.param[\"c\"] = {s: stocks[s][\"cost\"] for s in list(stocks.keys())}\n", " m.param[\"length_s\"] = {s: stocks[s][\"length\"] for s in stocks.keys()}\n", " m.param[\"length_f\"] = {f: finish[f][\"length\"] for f in finish.keys()}\n", " m.param[\"demand_finish\"] = {f: finish[f][\"demand\"] for f in finish.keys()}\n", "\n", " m.eval(\"let f_total_demand := max{f in F} demand_finish[f];\")\n", " # or m.param['f_total_demand'] = max([finish[f]['demand'] for f in finish.keys()])\n", " a_upper_bound = {\n", " f: max([int(stocks[s][\"length\"] / finish[f][\"length\"]) for s in stocks.keys()])\n", " for f in finish.keys()\n", " }\n", " m.param[\"a_upper_bound\"] = a_upper_bound\n", "\n", " m.option[\"solver\"] = SOLVER_MINLO\n", " m.get_output(\"solve;\")\n", "\n", " cost = m.obj[\"cost\"].value()\n", " x = [round(m.var[\"x\"][p].value()) for p in range(Np)]\n", "\n", " # Retrieve the patterns\n", " patterns = []\n", " for p in range(Np):\n", " a = {f: round(m.var[\"a\"][f, p].value()) for f in finish.keys()}\n", " patterns.append(\n", " {\n", " \"stock\": [s for s in stocks.keys() if m.var[\"b\"][s, p].value() > 0][0],\n", " \"cuts\": a,\n", " }\n", " )\n", "\n", " return patterns, x, cost\n", "\n", "\n", "patterns, x, cost = bilinear_cut_stock(stocks, finish, 2)\n", "plot_nonzero_patterns(stocks, finish, patterns, x, cost);" ] }, { "cell_type": "markdown", "metadata": { "id": "z9DO1tf8DCfI" }, "source": [ "## Pattern Generation: Bilinear Model\n", "\n", "From limited testing, the bilinear model for the cutting stock problem appears to work well for small data sets, but does not scale well for larger problem instances, at least for the solvers included in the testing. This shouldn't be surprising given the non-convex nature of the problem, the exclusive use of integer and binary decision variables, and a high degree of symmetry in the model equations.\n", "\n", "So rather than attempt to solve the full problem all at once, the following model assumes an initial list of patterns has been determined, perhaps using the `make_patterns` function defined above, then attempts to generate one more pattern that further reduces the objective function. The result remains a non-convex, bilinear optimization problem, but with fewer binary decision variables and at most one bilinear term in the objective and constraints.\n", "\n", "$$\n", "\\begin{align}\n", "\\min\\quad & \\sum_{p\\in P} c_{s_p} x_{s_p} + x' \\sum_{s\\in S} b_s c_s\\\\\n", "\\text{s.t.}\\quad\n", "& \\sum_{s\\in S}b'_{s} = 1 \\\\\n", "& \\sum_{f\\in F}a'_{f}l^F_f \\leq \\sum_{s\\in S} b'_{s} l^S_s \\\\\n", "& \\sum_{p\\in P}a_{fp} x_{s_p} + a'_f x'\\geq d_f && \\forall f\\in F\\\\\n", "& a'_{f}, x_p \\in \\mathbb{Z}_+ && \\forall f\\in F, \\forall p\\in P \\\\\n", "& b'_{s} \\in \\mathbb{Z}_2 && \\forall s\\in S \\\\\n", "\\end{align}\n", "$$\n", "\n", "The function `generate_pattern_bilinear` is a direct AMPL implementation that uses a MINLO solver to create one additional feasible pattern that could be added to the list of known patterns." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "id": "ZcCETIcxagnl" }, "outputs": [], "source": [ "pattern_bilinear_prob = AMPL()\n", "pattern_bilinear_prob.option[\"solver\"] = SOLVER_MINLO\n", "pattern_bilinear_prob.eval(\n", " \"\"\"\n", " set S;\n", " set F;\n", " set P;\n", "\n", " param c{P union S};\n", "\n", " # length stock\n", " param length_s{S};\n", " # length finished pieces\n", " param length_f{F};\n", "\n", " param demand_finish{F};\n", " param a{F, P};\n", " param ap_upper_bound{F};\n", "\n", " var ap{f in F} integer >= 0 <= ap_upper_bound[f];\n", " var bp{S} binary;\n", " var x{P} integer >= 0;\n", " var xp integer >= 0;\n", "\n", " minimize cost:\n", " sum{p in P} c[p] * x[p] + xp * sum{s in S} bp[s]*c[s];\n", "\n", " subject to assign_each_stock_to_pattern:\n", " sum{s in S} bp[s] = 1;\n", "\n", " subject to add_pattern:\n", " sum{f in F} ap[f] * length_f[f] <= sum{s in S} bp[s] * length_s[s];\n", "\n", " subject to demand{f in F}:\n", " sum{p in P} a[f,p]*x[p] + ap[f]*xp >= demand_finish[f];\n", "\"\"\"\n", ")\n", "\n", "\n", "def generate_pattern_bilinear(stocks, finish, patterns):\n", " m = pattern_bilinear_prob\n", " m.eval(\"reset data;\")\n", "\n", " m.set[\"S\"] = list(stocks.keys())\n", " m.set[\"F\"] = list(finish.keys())\n", " m.set[\"P\"] = list(range(len(patterns)))\n", "\n", " s = {p: patterns[p][\"stock\"] for p in range(len(patterns))}\n", " c = {p: stocks[s[p]][\"cost\"] for p in range(len(patterns))}\n", " cstocks = {s: stocks[s][\"cost\"] for s in list(stocks.keys())}\n", " c.update(cstocks)\n", " m.param[\"c\"] = c\n", "\n", " a = {\n", " (f, p): patterns[p][\"cuts\"][f]\n", " for p in range(len(patterns))\n", " for f in finish.keys()\n", " }\n", " m.param[\"a\"] = a\n", " m.param[\"length_s\"] = {s: stocks[s][\"length\"] for s in stocks.keys()}\n", " m.param[\"length_f\"] = {f: finish[f][\"length\"] for f in finish.keys()}\n", " m.param[\"demand_finish\"] = {f: finish[f][\"demand\"] for f in finish.keys()}\n", "\n", " ap_upper_bound = {\n", " f: max([int(stocks[s][\"length\"] / finish[f][\"length\"]) for s in stocks.keys()])\n", " for f in finish.keys()\n", " }\n", " m.param[\"ap_upper_bound\"] = ap_upper_bound\n", "\n", " m.get_output(\"solve;\")\n", "\n", " bp_values = dict(m.var[\"bp\"].get_values())\n", " ap_values = dict(m.var[\"ap\"].get_values())\n", "\n", " # Retrieve the patterns\n", " new_pattern = {\n", " \"stock\": [s for s in stocks.keys() if bp_values[s] > 0.5][0],\n", " \"cuts\": {f: round(ap_values[f]) for f in finish.keys()},\n", " }\n", "\n", " return new_pattern" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "yVazz5d2DCfI", "outputId": "fe01b259-7d2d-4b47-e6e7-b5b9959fd583" }, "outputs": [ { "data": { "text/plain": [ "{'stock': 'C', 'cuts': {'S': 1, 'M': 1, 'L': 1}}" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stocks = {\n", " \"A\": {\"length\": 5, \"cost\": 6},\n", " \"B\": {\"length\": 6, \"cost\": 7},\n", " \"C\": {\"length\": 9, \"cost\": 10},\n", "}\n", "\n", "finish = {\n", " \"S\": {\"length\": 2, \"demand\": 20},\n", " \"M\": {\"length\": 3, \"demand\": 10},\n", " \"L\": {\"length\": 4, \"demand\": 20},\n", "}\n", "\n", "patterns = make_patterns(stocks, finish)\n", "generate_pattern_bilinear(stocks, finish, patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "il1p6y_7DCfJ" }, "source": [ "## Pattern Generation: Linear Dual\n", "\n", "A common approach to pattern generation for stock cutting begins by relaxing the MILO optimization problem with known patterns. The integer variables $x_{s_p}$ are relaxed to non-negative reals.\n", "\n", "$$\n", "\\begin{align}\n", "\\min\\quad & \\sum_{p\\in P} c_{s_p} x_{s_p} \\\\\n", "\\text{s.t.}\\quad\n", "& \\sum_{p\\in P}a_{pf} x_{s_p} \\geq d_f && \\forall f\\in F\\\\\n", "& x_{s_p} \\in \\mathbb{R}_+ && \\forall p\\in P\\\\\n", "\\end{align}\n", "$$\n", "\n", "Let $\\pi_f \\geq 0$ be the dual variables associated with the demand constraints. A large positive value $\\pi_f$ suggests a high value for including finished part $f$ in a new pattern. This motivates a set of dual optimization problems where the objective is to construct a new patterns that maximizes the the marginal value of each stock $s\\in S$.\n", "\n", "$$\n", "\\begin{align}\n", "V_s = \\max\\quad & \\left(\\sum_{f\\in F} \\pi_{f} a'_{sf}\\right) - c_s \\\\\n", "\\text{s.t.}\\quad\n", "& \\sum_{f\\in F}l^F_f a'_{sf} \\leq l^S_s && \\\\\n", "& a'_{sf} \\in \\mathbb{Z}_+ && \\forall f\\in F\\\\\n", "\\end{align}\n", "$$\n", "\n", "The pattern demonstrating the largest return $V_s$ is returned as a candidate to add the list of patterns." ] }, { "cell_type": "markdown", "metadata": { "id": "-qVilIDNHIX9" }, "source": [ "This dual optimization problem might be implemented with two AMPL objects, each one representing a model. After declaring the model, it is only necessary to reset the data and assign it with new patterns or dual values. The \"new_pattern_prob\" corresponds to the problem with dual values, and \"generate_pattern_dual_prob\" to the relaxed one." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "id": "iHVQ3XUGieiJ" }, "outputs": [], "source": [ "new_pattern_prob = AMPL()\n", "new_pattern_prob.option[\"solver\"] = SOLVER_MILO\n", "new_pattern_prob.eval(\n", " \"\"\"\n", " set F;\n", "\n", " param c;\n", " param ap_upper_bound{F};\n", " param demand_dual{F};\n", "\n", " # length stock\n", " param length_s;\n", " # length finished pieces\n", " param length_f{F};\n", "\n", " # Define second problems with new pattern in stock s\n", " var ap{f in F} integer >= 0 <= ap_upper_bound[f];\n", "\n", " maximize marginal_cost:\n", " sum{f in F} ap[f] * demand_dual[f] - c;\n", "\n", " subject to stock_length:\n", " sum{f in F} ap[f] * length_f[f] <= length_s;\n", "\"\"\"\n", ")\n", "\n", "\n", "def new_pattern_problem(finish, length_s, cost_s, ap_upper_bound, demand_duals):\n", " m = new_pattern_prob\n", " m.eval(\"reset data;\")\n", " m.set[\"F\"] = list(finish.keys())\n", " m.param[\"c\"] = cost_s\n", " m.param[\"length_s\"] = length_s\n", " m.param[\"length_f\"] = {f: finish[f][\"length\"] for f in finish.keys()}\n", " m.param[\"ap_upper_bound\"] = ap_upper_bound\n", " m.param[\"demand_dual\"] = demand_duals\n", " m.get_output(\"solve;\")\n", "\n", " marg_cost = m.obj[\"marginal_cost\"].value()\n", " pattern = {f: round(m.var[\"ap\"][f].value()) for f in finish.keys()}\n", " return marg_cost, pattern\n", "\n", "\n", "generate_pattern_dual_prob = AMPL()\n", "generate_pattern_dual_prob.option[\"solver\"] = SOLVER_MILO\n", "generate_pattern_dual_prob.eval(\n", " \"\"\"\n", " set F;\n", " set P;\n", "\n", " param c{P};\n", " param demand_finish{F};\n", " # how many F pieces are returned from pattern p\n", " param a{F, P};\n", "\n", " # Define first problem with known patterns\n", " var x{P} >= 0; # relaxed integrality\n", "\n", " minimize cost:\n", " sum{p in P} c[p] * x[p];\n", "\n", " subject to demand{f in F}:\n", " sum{p in P} a[f,p]*x[p] >= demand_finish[f];\n", "\"\"\"\n", ")\n", "\n", "\n", "def generate_pattern_dual(stocks, finish, patterns):\n", " m = generate_pattern_dual_prob\n", "\n", " m.set[\"F\"] = list(finish.keys())\n", " m.set[\"P\"] = list(range(len(patterns)))\n", "\n", " s = {p: patterns[p][\"stock\"] for p in range(len(patterns))}\n", " c = {p: stocks[s[p]][\"cost\"] for p in range(len(patterns))}\n", " m.param[\"c\"] = c\n", "\n", " a = {\n", " (f, p): patterns[p][\"cuts\"][f]\n", " for p in range(len(patterns))\n", " for f in finish.keys()\n", " }\n", " m.param[\"a\"] = a\n", " m.param[\"demand_finish\"] = {f: finish[f][\"demand\"] for f in finish.keys()}\n", " m.get_output(\"solve;\")\n", "\n", " dual_values = dict(m.getConstraint(\"demand\").get_values(suffixes=\"dual\"))\n", "\n", " ap_upper_bound = {\n", " f: max([int(stocks[s][\"length\"] / finish[f][\"length\"]) for s in stocks.keys()])\n", " for f in finish.keys()\n", " }\n", " demand_duals = {f: dual_values[f] for f in finish.keys()}\n", " # use get_values() for getting the duals\n", "\n", " marginal_values = {}\n", " pattern = {}\n", " for s in stocks.keys():\n", " marginal_values[s], pattern[s] = new_pattern_problem(\n", " finish, stocks[s][\"length\"], stocks[s][\"cost\"], ap_upper_bound, demand_duals\n", " )\n", "\n", " s = max(marginal_values, key=marginal_values.get)\n", " new_pattern = {\"stock\": s, \"cuts\": pattern[s]}\n", " return new_pattern" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MH8ngS1HDCfJ", "outputId": "0367a657-2493-493a-925d-50b624b3f16a" }, "outputs": [ { "data": { "text/plain": [ "{'stock': 'C', 'cuts': {'S': 1, 'M': 1, 'L': 1}}" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "stocks = {\n", " \"A\": {\"length\": 5, \"cost\": 6},\n", " \"B\": {\"length\": 6, \"cost\": 7},\n", " \"C\": {\"length\": 9, \"cost\": 10},\n", "}\n", "\n", "finish = {\n", " \"S\": {\"length\": 2, \"demand\": 20},\n", " \"M\": {\"length\": 3, \"demand\": 10},\n", " \"L\": {\"length\": 4, \"demand\": 20},\n", "}\n", "\n", "patterns = make_patterns(stocks, finish)\n", "generate_pattern_dual(stocks, finish, patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "oqSD5l2XDCfK" }, "source": [ "The following cell compares the time required to generate a new pattern by the two methods for a somewhat larger data set." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2BvsS4uWDCfK", "outputId": "316482a5-5740-4ecb-ae58-f3e3cf053ab3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Testing generate_patterns_bilinear: 104 ms ± 36.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", "Testing generate_patterns_dual: 111 ms ± 12.3 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], "source": [ "stocks = {\n", " \"log\": {\"length\": 100, \"cost\": 1},\n", "}\n", "\n", "finish = {\n", " 1: {\"length\": 75.0, \"demand\": 38},\n", " 2: {\"length\": 75.0, \"demand\": 44},\n", " 3: {\"length\": 75.0, \"demand\": 30},\n", " 4: {\"length\": 75.0, \"demand\": 41},\n", " 5: {\"length\": 75.0, \"demand\": 36},\n", " 6: {\"length\": 53.8, \"demand\": 33},\n", " 7: {\"length\": 53.0, \"demand\": 36},\n", " 8: {\"length\": 51.0, \"demand\": 41},\n", " 9: {\"length\": 50.2, \"demand\": 35},\n", " 10: {\"length\": 32.2, \"demand\": 37},\n", " 11: {\"length\": 30.8, \"demand\": 44},\n", " 12: {\"length\": 29.8, \"demand\": 49},\n", " 13: {\"length\": 20.1, \"demand\": 37},\n", " 14: {\"length\": 16.2, \"demand\": 36},\n", " 15: {\"length\": 14.5, \"demand\": 42},\n", " 16: {\"length\": 11.0, \"demand\": 33},\n", " 17: {\"length\": 8.6, \"demand\": 47},\n", " 18: {\"length\": 8.2, \"demand\": 35},\n", " 19: {\"length\": 6.6, \"demand\": 49},\n", " 20: {\"length\": 5.1, \"demand\": 42},\n", "}\n", "\n", "patterns = make_patterns(stocks, finish)\n", "\n", "print(\"Testing generate_patterns_bilinear: \", end=\"\")\n", "%timeit generate_pattern_bilinear(stocks, finish, patterns)\n", "\n", "print(\"Testing generate_patterns_dual: \", end=\"\")\n", "%timeit generate_pattern_dual(stocks, finish, patterns)" ] }, { "cell_type": "markdown", "metadata": { "id": "1IaDKpX6DCfK" }, "source": [ "## A hybrid solution algorithm using pattern generation\n", "\n", "The following cell incorporates the two methods of pattern generation into a hybrid algorithm to solve the cutting stock problem. The algorithm starts with with `make_patterns` to create an initial list of patterns, then uses the linear dual to adds patterns until no new patterns are found. In phase two of the algorithm, the bilinear model is used to find additional patterns, if any, that may further reduce the objective function. There has been no exhaustive testing or attempt to compare this empirical method with others." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 200 }, "id": "nwbOOUmcDCfK", "outputId": "6cdf3a83-7243-44b1-af01-14ca5dab4c1c" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Phase 1 ... Cost = 170.0\n", "Phase 2 ... Cost = 170.0\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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GfL399ftt9+ud955x+xYcKCkpCSVlJToyJEjkqR9+/Zpx44duvfee01OBklyMztAb3HmzBm1tbUpICCgw/6AgAAdOnTIpFRwpvb2duXk5Cg5OVm33nqr2XHgQB988IH27NmjsrIys6PACb755hutXLlSubm5euGFF1RWVqann35a7u7umj17ttnx4AALFy5UY2OjRowYIVdXV7W1tenVV19VRkaG2dEgyi3gNNnZ2dq/f7927NhhdhQ4UE1NjZ555hlt2bJFffv2NTsOnKC9vV3x8fFasmSJJOn222/X/v37tWrVKsqtRX344Ydat26d3n//fY0ePVoVFRXKyclRUFAQ9/wmQLl1ksGDB8vV1VV1dXUd9tfV1SkwMNCkVHCW+fPn65NPPtEXX3yh4OBgs+PAgcrLy1VfX68xY8bY97W1temLL77QsmXL1NLSIldXVxMToqcNGTJEo0aN6rBv5MiR+uijj0xKBEdbsGCBFi5cqJkzZ0qSYmJidOLECRUUFFBubwKsuXUSd3d3jR07ViUlJfZ97e3tKikp0YQJE0xMBkcyDEPz58/X5s2btXXrVkVERJgdCQ529913q7KyUhUVFfYtPj5eGRkZqqiooNhaUHJycqdX/B05ckRhYWEmJYKjff/993Jx6VihXF1d1d7eblIi/DOe3DpRbm6uZs+erfj4eCUmJqqwsFAXL17Uo48+anY0OEh2drbef/99/elPf5K3t7dqa2slST4+PvL09DQ5HRzB29u705pqLy8v+fr6stbaop599lklJSVpyZIleuihh1RaWqo1a9ZozZo1ZkeDg0ydOlWvvvqqQkNDNXr0aO3du1e/+93v9Nhjj5kdDeJVYE63bNkyLV26VLW1tYqLi9Nbb72lcePGmR0LDmKz2brcX1RUpMzMTOeGgWkmT57Mq8As7pNPPtGiRYt09OhRRUREKDc3V3PnzjU7Fhzku+++U15enjZv3qz6+noFBQXpl7/8pV588UW5u7ubHa/Xo9wCAADAMlhzCwAAAMug3AIAAMAyKLcAAACwDMotAAAALINyCwAAAMug3AIAAMAyKLcAAACwDMotAAAALINyCwAAAMug3AIAAMAyKLcAAACwjP8D9QsOReZbWfcAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def cut_stock(stocks, finish):\n", " # Generate initial set of patterns\n", " patterns = make_patterns(stocks, finish)\n", "\n", " # Phase 1: Generate patterns using dual method\n", " print(\"Phase 1 \", end=\".\")\n", " new_pattern = generate_pattern_dual(stocks, finish, patterns)\n", " while new_pattern not in patterns:\n", " patterns.append(new_pattern)\n", " new_pattern = generate_pattern_dual(stocks, finish, patterns)\n", " print(end=\".\")\n", "\n", " x, cost = cut_patterns(stocks, finish, patterns)\n", " print(f\" Cost = {cost}\")\n", "\n", " # Phase 2: Generate patterns using bilinear method\n", " print(\"Phase 2 \", end=\".\")\n", " new_pattern = generate_pattern_bilinear(stocks, finish, patterns)\n", " while new_pattern not in patterns:\n", " patterns.append(new_pattern)\n", " new_pattern = generate_pattern_bilinear(stocks, finish, patterns)\n", " print(end=\".\")\n", "\n", " x, cost = cut_patterns(stocks, finish, patterns)\n", " print(f\" Cost = {cost}\")\n", "\n", " # Get the indices of non-zero patterns\n", " non_zero_indices = [index for index, value in enumerate(x) if value > 0]\n", "\n", " # Return only the non-zero patterns, their corresponding values, and the cost\n", " return (\n", " [patterns[index] for index in non_zero_indices],\n", " [x[index] for index in non_zero_indices],\n", " cost,\n", " )\n", "\n", "\n", "stocks = {\n", " \"A\": {\"length\": 5, \"cost\": 6},\n", " \"B\": {\"length\": 6, \"cost\": 7},\n", " \"C\": {\"length\": 9, \"cost\": 10},\n", "}\n", "\n", "finish = {\n", " \"S\": {\"length\": 2, \"demand\": 20},\n", " \"M\": {\"length\": 3, \"demand\": 10},\n", " \"L\": {\"length\": 4, \"demand\": 20},\n", "}\n", "\n", "patterns, x, cost = cut_stock(stocks, finish)\n", "plot_nonzero_patterns(stocks, finish, patterns, x, cost)" ] }, { "cell_type": "markdown", "metadata": { "id": "3i1M5Tw_DCfL" }, "source": [ "## Examples" ] }, { "cell_type": "markdown", "metadata": { "id": "FVQkjUXiDCfL" }, "source": [ "### Example from JuMP documentation for column generation\n", "\n", "https://jump.dev/JuMP.jl/stable/tutorials/algorithms/cutting_stock_column_generation/#:~:text=The%20cutting%20stock%20problem%20is,while%20maximizing%20the%20total%20profit." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 662 }, "id": "LgNNwOrcDCfL", "outputId": "4fef5f12-8353-487a-e387-a345c91d02b0" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Phase 1 ......... Cost = 360.0\n", "Phase 2 .. Cost = 360.0\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsEAAAJOCAYAAABWV9p5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACDcUlEQVR4nOzdeVhU590+8HuGGYZFFsFBJLKJCCoIblFUEq2EJF2IP4xLi6JVXNDEGhtrbEIaYwxZbLTGGtdowFYNamlKYi3EfUMiIm6oGBBUENzYxGFmOL8/fDPJhB2RM8O5P9c118uc5Zn74czbfH0453lkgiAIICIiIiKSELnYAYiIiIiI2huLYCIiIiKSHBbBRERERCQ5LIKJiIiISHJYBBMRERGR5LAIJiIiIiLJYRFMRERERJLDIpiIiIiIJIdFMBERERFJDotgIiIiIpIcFsFERG2suLgYr776Knr06AGVSgV3d3f85je/wbffftsm7W/ZsgWOjo5t0lZjjhw5guHDh8PZ2RnW1tbw9/fHihUr6hx348YNTJo0yXBcYGAgvvvuO8N+QRDw9ttvo1u3brC2tkZYWBiuXLnS5Of//e9/h5eXF6ysrDBkyBCcPHmyTftHRNKmEDsAEVFHkp+fj+HDh8PR0REff/wxAgMDodVqsXfvXsydOxc5OTliR2w2W1tbvPLKK+jXrx9sbW1x5MgRzJo1C7a2tpg5cyYA4N69exg+fDhGjRqFPXv2QK1W48qVK+jcubOhnY8++girVq3CF198AW9vb8TFxeH555/HhQsXYGVlVe9n79ixAwsWLMDatWsxZMgQrFy5Es8//zwuXboEFxeXduk/EXVwAhERtZkXX3xReOqpp4TKyso6++7du2f4+dq1a0JERIRga2sr2NnZCePGjROKi4sN+7OysoSRI0cKnTp1Euzs7IQBAwYIGRkZwv79+wUARq+//OUv7dCzR/7f//t/wqRJkwzvFy1aJIwYMaLB42trawVXV1fh448/Nmy7f/++oFKphG3btjV43tNPPy3MnTvX8F6v1wtubm5CfHz8Y/aAiOgR3g5BRNRG7t69i//+97+YO3cubG1t6+z/4RaG2tpavPTSS7h79y4OHjyI1NRUfP/995gwYYLh2KioKHTv3h0ZGRk4deoU3njjDSiVSgwbNgwrV66Evb09ioqKUFRUhNdff73ePIcPH0anTp0aff3jH/9odv9Onz6NY8eO4dlnnzVs++qrrzBo0CCMGzcOLi4u6N+/PzZs2GDYn5eXh+LiYoSFhRm2OTg4YMiQITh+/Hi9n1NTU4NTp04ZnSOXyxEWFtbgOURELcXbIYiI2khubi4EQYC/v3+jx3377bc4e/Ys8vLy4O7uDgBISEhA3759kZGRgcGDB6OgoAALFy40tOXr62s438HBATKZDK6uro1+zqBBg5CVldXoMV27dm2yX927d0dpaSl0Oh3eeecdxMTEGPZ9//33+Oyzz7BgwQL8+c9/RkZGBubNmwdLS0tMmTIFxcXF9X5O165dDft+7vbt29Dr9fWeY063kxCRaWMRTETURgRBaNZxFy9ehLu7u6EABoA+ffrA0dERFy9exODBg7FgwQLExMQgMTERYWFhGDduHHx8fFqUx9raGj179mzROfU5fPgwKisrceLECbzxxhvo2bMnfvvb3wJ4NKo9aNAgvP/++wCA/v3749y5c1i7di2mTJny2J9NRPSk8HYIIqI24uvrC5lM1iajle+88w7Onz+PX/3qV9i3bx/69OmDf/3rXy1qo61uh/D29kZgYCBmzJiB1157De+8845hX7du3dCnTx+j43v37o2CggIAMIxW37p1y+iYW7duNTiS3aVLF1hYWLToHCKilmIRTETURpycnPD888/j73//O6qqqursv3//PoBHRWJhYSEKCwsN+y5cuID79+8bFZS9evXCa6+9hv/973+IjIzE5s2bAQCWlpbQ6/VN5vnhdojGXhERES3qY21tLTQajeH98OHDcenSJaNjLl++DE9PTwCPCmhXV1ej6eHKy8uRnp6OkJCQej/D0tISAwcONDqntrYW3377bYPnEBG1mNhP5hERdSRXr14VXF1dhT59+gg7d+4ULl++LFy4cEH429/+Jvj7+wuC8GjGhODgYCE0NFQ4deqUkJ6eLgwcOFB49tlnBUEQhAcPHghz584V9u/fL+Tn5wtHjhwRfHx8hD/96U+CIAjC0aNHBQBCWlqaUFpaKlRVVT2RvqxevVr46quvhMuXLwuXL18WNm7cKNjZ2Qlvvvmm4ZiTJ08KCoVCWLZsmXDlyhXhH//4h2BjYyNs3brVcMwHH3wgODo6Cv/+97+F7Oxs4aWXXhK8vb2F6upqwzG/+MUvhE8//dTwfvv27YJKpRK2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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "stocks = {\n", " \"log\": {\"length\": 100, \"cost\": 1},\n", "}\n", "\n", "finish = {\n", " 1: {\"length\": 75.0, \"demand\": 38},\n", " 2: {\"length\": 75.0, \"demand\": 44},\n", " 3: {\"length\": 75.0, \"demand\": 30},\n", " 4: {\"length\": 75.0, \"demand\": 41},\n", " 5: {\"length\": 75.0, \"demand\": 36},\n", " 6: {\"length\": 53.8, \"demand\": 33},\n", " 7: {\"length\": 53.0, \"demand\": 36},\n", " 8: {\"length\": 51.0, \"demand\": 41},\n", " 9: {\"length\": 50.2, \"demand\": 35},\n", " 10: {\"length\": 32.2, \"demand\": 37},\n", " 11: {\"length\": 30.8, \"demand\": 44},\n", " 12: {\"length\": 29.8, \"demand\": 49},\n", " 13: {\"length\": 20.1, \"demand\": 37},\n", " 14: {\"length\": 16.2, \"demand\": 36},\n", " 15: {\"length\": 14.5, \"demand\": 42},\n", " 16: {\"length\": 11.0, \"demand\": 33},\n", " 17: {\"length\": 8.6, \"demand\": 47},\n", " 18: {\"length\": 8.2, \"demand\": 35},\n", " 19: {\"length\": 6.6, \"demand\": 49},\n", " 20: {\"length\": 5.1, \"demand\": 42},\n", "}\n", "\n", "patterns, x, cost = cut_stock(stocks, finish)\n", "plot_nonzero_patterns(stocks, finish, patterns, x, cost)" ] }, { "cell_type": "markdown", "metadata": { "id": "Rsff2lbSDCfL" }, "source": [ "### Example from Wikipedia\n", "\n", "https://en.wikipedia.org/wiki/Cutting_stock_problem\n", "\n", "The minimum number of rolls is 73.0." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 538 }, "id": "uCvb6vQXDCfL", "outputId": "fe54ea3e-6051-4eb0-d99f-fd761b9ee5bd" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Phase 1 ....................... Cost = 73.0\n", "Phase 2 .. Cost = 73.0\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "stocks = {\n", " \"roll\": {\"length\": 5600, \"cost\": 1},\n", "}\n", "\n", "finish = {\n", " 1380: {\"length\": 1380, \"demand\": 22},\n", " 1520: {\"length\": 1520, \"demand\": 25},\n", " 1560: {\"length\": 1560, \"demand\": 12},\n", " 1710: {\"length\": 1710, \"demand\": 14},\n", " 1820: {\"length\": 1820, \"demand\": 18},\n", " 1880: {\"length\": 1880, \"demand\": 18},\n", " 1930: {\"length\": 1930, \"demand\": 20},\n", " 2000: {\"length\": 2000, \"demand\": 10},\n", " 2050: {\"length\": 2050, \"demand\": 12},\n", " 2100: {\"length\": 2100, \"demand\": 14},\n", " 2140: {\"length\": 2140, \"demand\": 16},\n", " 2150: {\"length\": 2150, \"demand\": 18},\n", " 2200: {\"length\": 2200, \"demand\": 20},\n", "}\n", "\n", "patterns, x, cost = cut_stock(stocks, finish)\n", "plot_nonzero_patterns(stocks, finish, patterns, x, cost)" ] }, { "cell_type": "markdown", "metadata": { "id": "DOmXamBBuM17" }, "source": [ "### Woodworking: Problem data from Google sheets\n", "\n", "Find a minimum cost order of 2x4 lumber to build the [\"One Arm 2x4 Outdoor Sofa\" described by Ana White](https://www.ana-white.com/woodworking-projects/one-arm-2x4-outdoor-sofa-sectional-piece).\n", "\n", "![](https://www.ana-white.com/sites/default/files/images/diy%202x4%20sectional%20single%20arm%20ana%20white%20dimensions.jpg)\n", "Image source: www.ana-white.com" ] }, { "cell_type": "markdown", "metadata": { "id": "RbMtlRjbY7YO" }, "source": [ "Data source: https://docs.google.com/spreadsheets/d/1ZX7KJ2kwTGgyqEv_a3LOG0nQSxsc38Ykk53A7vGWAFU/edit#gid=1104632299" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "id": "BZtYAaoxDwsS" }, "outputs": [], "source": [ "import pandas as pd\n", "\n", "\n", "def read_google_sheet(sheet_id, sheet_name):\n", " \"\"\"\n", " Reads a Google Sheet and returns a pandas DataFrame.\n", "\n", " This function reads a Google Sheet with the specified sheet ID and sheet name,\n", " and returns a pandas DataFrame with the data. The column names are converted to\n", " lowercase.\n", "\n", " Args:\n", " sheet_id (str): The Google Sheet ID.\n", " sheet_name (str): The name of the sheet to read.\n", "\n", " Returns:\n", " df (pd.DataFrame): A pandas DataFrame containing the data from the Google Sheet.\n", " \"\"\"\n", " url = f\"https://docs.google.com/spreadsheets/d/{sheet_id}/gviz/tq?tqx=out:csv&sheet={sheet_name}\"\n", " df = pd.read_csv(url)\n", " df.columns = map(str.lower, df.columns)\n", " return df" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 852 }, "id": "s13K6p_eu5IQ", "outputId": "c24f9c17-b3e7-4703-b49e-ffaa8ceb493a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Settings\n" ] }, { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
settingvalue
0kerf0.125
\n", "
\n", " \n", "\n", "\n", "\n", "
\n", " \n", "
\n", "\n", "\n", "\n", " \n", "\n", " \n", " \n", "\n", " \n", "
\n", "
\n" ], "text/plain": [ " setting value\n", "0 kerf 0.125" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Finish\n" ] }, { "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", "
kindlengthquantitylabel
02x470.50370.50
12x425.501025.50
22x412.50112.50
32x472.00672.00
42x470.75170.75
52x428.50128.50
\n", "
\n", " \n", "\n", "\n", "\n", "
\n", " \n", "
\n", "\n", "\n", "\n", " \n", "\n", " \n", " \n", "\n", " \n", "
\n", "
\n" ], "text/plain": [ " kind length quantity label\n", "0 2x4 70.50 3 70.50\n", "1 2x4 25.50 10 25.50\n", "2 2x4 12.50 1 12.50\n", "3 2x4 72.00 6 72.00\n", "4 2x4 70.75 1 70.75\n", "5 2x4 28.50 1 28.50" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Stocks\n" ] }, { "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", "
kindlengthprice
02x4361.68
12x4481.86
22x4722.57
32x4842.65
42x4962.92
52x41203.67
62x41444.40
72x41685.14
82x41926.92
92x42168.62
102x424010.40
112x6964.43
122x61929.36
\n", "
\n", " \n", "\n", "\n", "\n", "
\n", " \n", "
\n", "\n", "\n", "\n", " \n", "\n", " \n", " \n", "\n", " \n", "
\n", "
\n" ], "text/plain": [ " kind length price\n", "0 2x4 36 1.68\n", "1 2x4 48 1.86\n", "2 2x4 72 2.57\n", "3 2x4 84 2.65\n", "4 2x4 96 2.92\n", "5 2x4 120 3.67\n", "6 2x4 144 4.40\n", "7 2x4 168 5.14\n", "8 2x4 192 6.92\n", "9 2x4 216 8.62\n", "10 2x4 240 10.40\n", "11 2x6 96 4.43\n", "12 2x6 192 9.36" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Google Sheet ID\n", "sheet_id = \"1ZX7KJ2kwTGgyqEv_a3LOG0nQSxsc38Ykk53A7vGWAFU\"\n", "\n", "# read settings\n", "settings_df = read_google_sheet(sheet_id, \"settings\")\n", "print(\"\\nSettings\")\n", "display(settings_df)\n", "\n", "# read parts\n", "finish_df = read_google_sheet(sheet_id, \"finish\")\n", "print(\"\\nFinish\")\n", "display(finish_df)\n", "\n", "# read and display stocks\n", "stocks_df = read_google_sheet(sheet_id, \"stocks\")\n", "# stocks = stocks.drop([\"price\"], axis=1)\n", "if not \"price\" in stocks_df.columns:\n", " stocks[\"price\"] = stocks_df[\"length\"]\n", "print(\"\\nStocks\")\n", "display(stocks_df)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 323 }, "id": "c2tfCmEo3QDC", "outputId": "fbcb04af-ef0e-49ce-d06f-646d045d9290" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Kind = 2x4\n", "Phase 1 ..... Cost = 32.440000000000005\n", "Phase 2 .. Cost = 32.440000000000005\n" ] }, { "data": { "image/png": "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xEcffcSMGTPo2bOnzeWJAZydnUlPT2f8+PGkpKTQsGFDhg0bxtNPPw1At27dmDZtGqNGjSI9Pb3SLk9cVFTEnDlzSEhIwMXFhWbNmvGvf/2LiIgIa8wf69S7d2+beVesWMGECROA4ksxOznpaG8RqVp0HxUREREREXE4+npFREREREQcjhoVERERERFxOGpURERERETE4ahRERERERERh6NGRUREREREHI4aFRERERERcThqVERERERExOGoUREREREREYejRkVERERERByOGhUREREREXE4alRERERERMThqFERERERERGHo0ZFREREREQcjhoVERERERFxOC72TkDEkVlSj2BcOG/vNKqnvLPg4WPvLKof1bXyuNcBn2YVvliTyYSLi/4ci4j8lX4zipTBknoEl9e72DuN6qmOP3SaCN+vgOwUe2dTfaiulef32ibVD+eCR0CFLtrV1ZXGjRurWRER+Qv9VhQpg/akVKK6AdB7Dhz5VB+oK5LqWnl+r63Hoe8w3N0rbLEWiwWz2YxhGBW2TBGR6kKNioiIyCVycXXG1dW1QpdZWFhYocsTEakudDK9iIiIiIg4HDUqIiIiIiLicNSoiIiIiIiIw7msRuXo0aN069aNli1b0rlzZ3766adS44qKinjkkUdo27YtrVq1YvLkyRQUFFjHX3jhBdq2bUubNm0YOnQomZmZ1rHVq1cTFhZG27Zt6du3L0lJSdaxzz77jE6dOtG+fXtuuukm9u/fbx3bu3cv4eHhhIWF0aFDB7766iubvG+99VbCwsK4/vrref/9961jKSkpDBs2jPbt29O6dWsWL15sHcvJyWHixIm0a9eOVq1a8fjjj1tPeKwu65iXl8f48eNp27Ytbdu2ZfDgwaSlpZX6c71UR48epU+fPnTo0IFWrVrx8MMPU1RUZBOTmpqKv78/Q4YMuar3qjFCukPk9/DPFJgRC836Fk+bl/XnY8z7JefzDraNeWDntc/dkamulUe1FRGRq3RZjUpERAT3338/v/zyC4899hgTJkwoNW758uXExsYSGxvL4cOHcXJy4uWXXwZg8+bNrFixgp07d3Lo0CE6duzIE088AcDPP//M7Nmz+eyzz/jxxx+ZOHEif//73wHIyMhg7NixrFq1igMHDvDCCy8wduxYAAzDYOjQoTz99NPs37+fdevWMWHCBPLy8gCY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8ODBDB48mKysLHr37o2TkxNFRUU8+OCD3HnnndbY/v37U1RUREFBAePGjbM5J2LSpEmcOHGC/Px87rjjDptv95988km2bduGxWLh5ptvZvnyP49vfvPNN1mzZg2GYdC6dWs2bNiAyWQC4OOPP2bBggU4OzsTGBjIf//7X+uhR3v27GHmzJm4uLhQt25d1q1bZz3x+/jx49x99924uLjg4uLCSy+9RIcOHQCqzTrOmzeP+++/39qctGrVimXLlpX42cfGxrJo0SJ2796Nn58f7777Lvfeey979+6lQQPbQzdWrVpFZGQkL7/8MgUFBQwePJh77rmnxDLlMsStLX781a7XLx67pHPl5VXVqa6VR7UVEZGrZDL+uDGIiNgwJ32P69t97Z1G9dQorPjKT8t6wpn9F4+XS6O6Vp7fa5txdBd5niUPeb5SZrOZ/Px8QkNDcXV1rbDliohUB7ozvYiIiIiIOBw1KiIiIiIi4nDUqIiIiIiIiMO5rJPpRUREajKLudB6FcUKWZ7FUmHLEhGpbtSoiJTBVKuuvVOovs4nw9b5xc9ScVTXyvN7bfPqh5Ofn1+hi3Z1dbVewVFERP6kq36JlMOSegTjwnl7p1E95Z0FDx97Z1H9qK6Vx70O+DSr8MWaTCZcXPS9oYjIX6lRERERERERh6OT6UVERERExOGoUREREREREYejRkVERERERByOGhUREREREXE4alRERERERMThqFERERERERGHo0ZFREREREQcjhoVERERERFxOLoVrkg5dGf6SqQ7qFcO1bXyqLaVQ3WtHO51wKeZvbOoMCaTCRcXfWytafQTFymDJfUILq93sXca1VMdf+g0Eb5fAdkp9s6m+lBdK49qWzlU18rxe12T6odzwSPA3tlUCFdXVxo3bqxmpYbRT1ukDNqTUonqBkDvOXDkU304qUiqa+VRbSuH6lo5fq+rx6HvMNzd7Z3NVbNYLJjNZgzDsHcqco2pURERERGphlxcnXF1dbV3GhWisLDQ3imIHehkehERERERcThqVERERERExOHo0C+RqiCkOwxaDN6NIesU/Hc2FObDhE1/xvzyGawdZTufdzBEHfzzdeoheP3ma5JylaC6Vh7VtnKorpVDdRVxSFe0R2XmzJmEhIRgMpmIi4srN3b58uW0aNGCZs2aMXXqVMxms8au8VhiYiK9e/fGy8uL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Tp0ivm8/fCZOwDcykp/bh5H18SfUateeoFdfxZySyulHH7PvSki1Ftw2jHq+ftQDJr9cfG7ue3MfIaBZS4b/42kK8vLYtnYleefPcfOIMXQbOYbF9w7FUlBA7/GT8fT2ISP5NJ8s/pd9V6QGMxmGYdg7Cbk6ISEhxMTE0KFDh1LHjx8/Tvfu3YmNjcXf35+77rqL/v37E/mXS0sCLFmyhJiYGNauXctNN93E6tWrCQ8PL/O9J0yYQIcOHYiKirJOe/7558nMzGThwoXk5eXRs2dPHn30UUaOHEn79u2Jiopi0qRJ5OTk0KtXLx5++GFGjx59tWWocOak73F9u6+906ieGoUVn6y+rCec2W/vbKqN44070nTyV9z98d0cPnvY3umUq6/RisUTokkYNpwLh3R5WpGqyHxDe9q/9z6rH3+Q1IT4i88gFc4vtBnjFrzMsYMHqN2gYakxZrOZ/Px8QkNDy717vSPSOSpVWEREBEFBQfz666/cdtttNG/e3Do2ZcoUNm4s/pa0adOmPP3004SHh9O8eXN8fX2JiIgosbzY2FgWLVrEmjVr8PPz491332XcuHGkp6eXiF25ciVBQUFER0czb948goKC2Lev+BvpWbNmkZKSQps2bejUqRO33347I0eOBGDVqlUsX76csLAwOnXqRN++fbnnnnsqozwiIiIiUoXp0K8qbNmyZWWO/d///Z/N66lTpzJ1avn3ObjxxhtJTEy0vu7WrRvHjx8vNXbChAlMmDCh1LFatWqxatWqUsduuOEGvvvuu3LzEBERERHRHhUREREREXE4alRERERERMTh6NAvEREREZEqrLDQgtlsLnXMYrFc42wqjhoVkTKYatW1dwrV1/lk2Dq/+FkqjNv5ZF6Pe520vDR7p3JRZwrSSFuyBEua4+cqIqUzUtLYEb2WnAzdtNNecjLOsiN6LXVCm2PKzy8zztXVtfgeMlWMLk8sUg5L6hGMC+ftnUb1lHcWPHzsnUW1k2Q5T45nA3uncUlqn8nE3+Rl7zRE5Cqcy8sFn/r2TqNGc/PwwDsgsNwYk8mEi0vV2z+hRkVERERERByOTqYXERERERGHo0ZFREREREQcjhoVERERERFxOGpURERERETE4ahRERERERERh6NGRUREREREHI4aFRERERERcThqVERERERExOGoUREREREREYfjYu8ERBzZ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8f1puNd2xdu/NkmH9IWDSFWiRqUGGD58ON27d2fevHn4+/uzdOlS7rnnnlJjlyxZQkZGBvv37+emm26ie/fuhIeHX/J7tWvXjrS0P6+oMm/ePDIzM61X/UpP//NExpUrVxITE1PmVb/c3d3VmMhV8x4+DEtaGtlbtwLFJ8o7ubnhPWQI3kOGUHDqFPF9++EzcQK+kZH83D6MrI8/oVa79gS9+irmlFROP/qYfVdCpAZp3S2Q3Kx864nvcV+epH4jT+566AYyzuTw1TtHAAgf2YImbXxY8dh3QHGjknjgN/LO/7m3pXY9N+6c0QEnZxOJB9P54b+J13x9ROTKmQzDMOydhFyZiIgINm3aRHJyMg0aNKBu3bocO3YMgClTpjB48GAGDx4MwFtvvcWCBQsA6N27N0uXLi1xeeLY2FiGDRvG7t278ff3Z8eOHdx7773s3buXBg0a2MSuXLmSf/7zn2RkZODq6kqdOnX4+OOPueGGG2zi/tqo/HUZ5TUq9nZ+/35+HVV6QyciVUdhxz60XfM67z+3h99OZts7HRG5BA0b12HUE104fuhXnD0Lyc/PJzQ09KpurSBVjxoVkTKoURGpHtSoiFQ9alQEdDK9iIiIiIg4IDUqIiIiIiLicNSoiIiIiIiIw9FVv0RERETEIRUWFmJYLPZOQ+xEe1REyuBcp469UxCRClCYeoY9nySQm1Vg71RE5BLlZhWw55MELpgvUFhYiKura4kbSUv1p6t+iZQjNz6ewmxdJUikqssucIMGAfZOQ0Qug6u7M15+HgCYTCZcXHQgUE2jRkVERERERByODv0SERERERGHo0ZFREREREQcjhoVERERERFxOGpURERERETE4ahRERERERERh6NGRaQM+fn5zJs3j/z8fHunUi2onhVL9axYqmfFUj0rlupZsVTPilWZ9dTliUXKcO7cOby8vMjKyqJevXr2TqfKUz0rlupZsVTPiqV6VizVs2KpnhWrMuupPSoiIiIiIuJw1KiIiIiIiIjDUaMiIiIiIiIOR42KSBnc3d156qmncHd3t3cq1YLqWbFUz4qlelYs1bNiqZ4VS/WsWJVZT51MLyIiIiIiDkd7VERERERExOGoUREREREREYejRkVERERERByOGhUREREREXE4alRERERERMThqFERKcNrr71GSEgItWrVomvXruzZs8feKVUJ8+fPp3PnztStWxc/Pz+GDBnCkSNHbGJ69+6NyWSyeUybNs1OGTuuefPmlahTq1atrOMXLlxg+vTpNGjQgDp16jB8+HBSUlLsmLFjCwkJKVFPk8nE9OnTAW2XF/Ptt99y5513EhgYiMlkIiYmxmbcMAyefPJJGjVqhIeHB/369ePo0aM2MWfPnmXs2LHUq1cPb29vJk+eTHZ29jVcC8dRXj3NZjOPPfYY7dq1w9PTk8DAQMaPH8/p06dtllHaNr1gwYJrvCaO4WLb54QJE0rUasCAATYx2j7/dLF6lva71GQy8cILL1hjKmL7VKMiUor333+fWbNm8dRTTxEbG0tYWBi33XYbqamp9k7N4X3zzTdMnz6dXbt2sXnzZsxmM/379ycnJ8cmburUqZw5c8b6WLhwoZ0ydmzXX3+9TZ22b99uHXvooYf4+OOPiY6O5ptvvuH06dMMGzbMjtk6tr1799rUcvPmzQCMHDnSGqPtsmw5OTmEhYXx2muvlTq+cOFCXnnlFZYuXcru3bvx9PTktttu48KFC9aYsWPH8tNPP7F582Y++eQTvv32W+6///5rtQoOpbx65ubmEhsby9y5c4mNjeXDDz/kyJEjDB48uETsM888Y7PNzpgx41qk73Autn0CDBgwwKZW7733ns24ts8/Xaye/1vHM2fO8Pbbb2MymRg+fLhN3FVvn4aIlNClSxdj+vTp1teFhYVGYGCgMX/+fDtmVTWlpqYagPHNN99Yp/Xq1ct48MEH7ZdUFfHUU08ZYWFhpY5lZmYarq6uRnR0tHXa4cOHDcDYuXPnNcqwanvwwQeNZs2aGUVFRYZhaLu8HICxYcMG6+uioiIjICDAeOGFF6zTMjMzDXd3d+O9994zDMMwDh06ZADG3r17rTGffvqpYTKZjFOnTl2z3B3RX+tZmj179hiAceLECeu0Jk2aGC+99FLlJlcFlVbP++67z7jrrrvKnEfbZ9kuZfu86667jFtuucVmWkVsn9qjIvIXBQUF/PDDD/Tr1886zcnJiX79+rFz5047ZlY1ZWVlAeDj42Mzfc2aNTRs2JC2bdsyZ84ccnNz7ZGewzt69CiBgYE0bdqUsWPHkpSUBMAPP/yA2Wy22U5btWpFcHCwttNLUFBQwLvvvsukSZMwmUzW6dour0xCQgLJyck226OXlxddu3a1bo87d+7E29ubTp06WWP69euHk5MTu3fvvuY5VzVZWVmYTCa8vb1tpi9YsIAGDRpwww038MILL2CxWOyTYBWwdetW/Pz8+Nvf/sbf//530tPTrWPaPq9cSkoKmzZtYvLkySXGrnb7dKmoJEWqi99++43CwkL8/f1tpvv7+/Pzzz/bKauqqaioiKioKMLDw2nbtq11+pgxY2jSpAmBgYEcOHCAxx57jCNHjvDhhx/aMVvH07VrV1auXMnf/vY3zpw5w9NPP02PHj348ccfSU5Oxs3NrcSHFn9/f5KTk+2TcBUSExNDZmYmEyZMsE7Tdnnl/tjmSvu9+cdYcnIyfn5+NuMuLi74+Phom72ICxcu8NhjjzF69Gjq1atnnT5z5kxuvPFGfHx82LFjB3PmzOHMmTO8+OKLdszWMQ0YMIBhw4YRGhpKfHw8//jHP7j99tvZuXMnzs7O2j6vwqpVq6hbt26JQ48rYvtUoyIilWb69On8+OOPNudVADbH/LZr145GjRrRt29f4uPjadas2bVO02Hdfvvt1n+3b9+erl270qRJE9atW4eHh4cdM6v6li9fzu23305gYKB1mrZLcURms5m7774bwzB44403bMZmzZpl/Xf79u1xc3MjIiKC+fPn4+7ufq1TdWj33HOP9d/t2rWjffv2NGvWjK1bt9K3b187Zlb1vf3224wdO5ZatWrZTK+I7VOHfon8RcOGDXF2di5x9aSUlBQCAgLslFXVExkZySeffMLXX39NUFBQubFdu3YF4NixY9citSrL29ubli1bcuzYMQICAigoKCAzM9MmRtvpxZ04cYIvv/ySKVOmlBun7fLS/bHNlfd7MyAgoMQFSSwWC2fPntU2W4Y/mpQTJ06wefNmm70ppenatSsWi4XExMRrk2AV1rRpUxo2bGj9/63t88ps27aNI0eOXPT3KVzZ9qlGReQv3Nzc6NixI1u2bLFOKyoqYsuWLdx88812zKxqMAyDyMhINmzYwFdffUVoaOhF54mLiwOgUaNGlZxd1ZadnU18fDyNGjWiY8eOuLq62mynR44cISkpSdvpRaxYsQI/Pz/uuOOOcuO0XV660NBQAgICbLbHc+fOsXv3buv2ePPNN5OZmckPP/xgjfnqq68oKiqyNoXypz+alKNHj/Lll1/SoEGDi84TFxeHk5NTiUOYpKRff/2V9PR06/9vbZ9XZvny5XTs2JGwsLCLxl7J9qlDv0RKMWvWLO677z46depEly5dWLx4MTk5OUycONHeqTm86dOns3btWj766CPq1q1rPbbXy8sLDw8P4uPjWbt2LQMHDqRBgwYcOHCAhx56iJ49e9K+fXs7Z+9YHnnkEe68806aNGnC6dOneeqpp3B2dmb06NF4eXkxefJkZs2ahY+PD/Xq1WPGjBncfPPN3HTTTfZO3WEVFRWxYsUK7rvvPlxc/vwTqO3y4rKzs232LiUkJBAXF4ePjw/BwcFERUXx7LPP0qJFC0JDQ5k7dy6BgYEMGTIEgNatWzNgwACmTp3K0qVLMZvNREZGcs8999gcgldTlFfPRo0aMWLECGJjY/nkk08oLCy0/i718fHBzc2NnTt3snv3bvr06UPdunXZuXMnDz30EPfeey/169e312rZTXn19PHx4emnn2b48OEEBAQQHx/Po48+SvPmzbntttsAbZ9/dbH/71D8ZUR0dDT//ve/S8xfYdvnVV0zTKQae/XVV43g4GDDzc3N6NKli7Fr1y57p1QlAKU+VqxYYRiGYSQlJRk9e/Y0fHx8DHd3d6N58+bG7NmzjaysLPsm7oBGjRplNGrUyHBzczOuu+46Y9SoUcaxY8es43l5ecYDDzxg1K9f36hdu7YxdOhQ48yZM3bM2PF9/vnnBmAcOXLEZrq2y4v7+uuvS/2/fd999xmGUXyJ4rlz5xr+/v6Gu7u70bdv3xJ1Tk9PN0aPHm3UqVPHqFevnjFx4kTj/Pnzdlgb+yuvngkJCWX+Lv36668NwzCMH374wejatavh5eVl1KpVy2jdurXx/PPPGxcuXLDvitlJefXMzc01+vfvb/j6+hqurq5GkyZNjKlTpxrJyck2y9D2+aeL/X83DMNYtmyZ4eHhYWRmZpaYv6K2T5NhGMaltzUiIiIiIiKVT+eoiIiIiIiIw1GjIiIiIiIiDkeNioiIiIiIOBw1KiIiIiIi4nDUqIiIiIiIiMNRoyIiIiIiIg5HjYqIiIiIiDgcNSoiIiIiIuJw1KiIiIiIiIjDUaMiIiIiIiIOR42KiIiIiIg4nP8PKlhiXJBsSK8AAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "kinds = tuple(set(finish_df[\"kind\"]))\n", "\n", "kerf = 0.25\n", "\n", "for kind in kinds:\n", " print(f\"Kind = {kind}\")\n", "\n", " finish = dict()\n", " for i in finish_df.loc[finish_df[\"kind\"] == kind].index:\n", " finish[finish_df.loc[i, \"label\"]] = {\n", " \"length\": finish_df.loc[i, \"length\"] + kerf,\n", " \"demand\": finish_df.loc[i, \"quantity\"],\n", " }\n", "\n", " stocks = dict()\n", " for i in stocks_df.loc[stocks_df[\"kind\"] == kind].index:\n", " stocks[stocks_df.loc[i, \"length\"]] = {\n", " \"length\": stocks_df.loc[i, \"length\"] + 0 * kerf,\n", " \"cost\": stocks_df.loc[i, \"price\"],\n", " }\n", "\n", " patterns, x, cost = cut_stock(stocks, finish)\n", " plot_nonzero_patterns(stocks, finish, patterns, x, cost)" ] }, { "cell_type": "markdown", "metadata": { "id": "I_bMp1UvDCfN" }, "source": [ "Purchase List" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 174 }, "id": "bWZ3l97pDCfN", "outputId": "c8ec9a39-445a-4cc7-80d4-3c2a7c1c2600" }, "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", "
cuts
stock
842.0
1445.0
1681.0
\n", "
\n", " \n", "\n", "\n", "\n", "
\n", " \n", "
\n", "\n", "\n", "\n", " \n", "\n", " \n", " \n", "\n", " \n", "
\n", "
\n" ], "text/plain": [ " cuts\n", "stock \n", "84 2.0\n", "144 5.0\n", "168 1.0" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.DataFrame(patterns)\n", "df[\"cuts\"] = x\n", "df[\"stock\"] = df[\"stock\"].astype(str)\n", "df = df.pivot_table(index=\"stock\", values=\"cuts\", aggfunc=\"sum\")\n", "df.index = df.index.astype(int)\n", "df = df.sort_index()\n", "\n", "df" ] }, { "cell_type": "markdown", "metadata": { "id": "LLM-1Fqm3BKd" }, "source": [ "## References\n", "\n", "The one dimensional cutting stock problem addressed in this notebook is generally attributed to two classic papers by Gilmore and Gomory. This first paper considers the more general case of stocks available in multiple lengths, while the second paper specializes to the needs of a paper trimming operation.\n", "\n", "> Gilmore, P. C., & Gomory, R. E. (1961). A linear programming approach to the cutting-stock problem. Operations research, 9(6), 849-859. [[jstor](https://www.jstor.org/stable/pdf/167051.pdf)]\n", "\n", "> Gilmore, P. C., & Gomory, R. E. (1963). A linear programming approach to the cutting stock problem—Part II. Operations research, 11(6), 863-888. [[jstor](https://www.jstor.org/stable/pdf/167827.pdf)]\n", "\n", "A useful survey of subsequent development of the cutting stock problem is given by:\n", "\n", "> Haessler, R. W., & Sweeney, P. E. (1991). Cutting stock problems and solution procedures. European Journal of Operational Research, 54(2), 141-150. [[pdf](https://deepblue.lib.umich.edu/bitstream/handle/2027.42/29128/0000167.pdf)]\n", "\n", "> Delorme, M., Iori, M., & Martello, S. (2016). Bin packing and cutting stock problems: Mathematical models and exact algorithms. European Journal of Operational Research, 255(1), 1-20. [[sciencedirect](https://www.sciencedirect.com/science/article/pii/S0377221716302491)]\n", "\n", "The solution proposed by Gilmore and Gamory has been refined over time and now generally referred to as \"column generation\". A number of tutorial implemenations are available, these are representative:\n", "\n", "> * [Mathworks/Matlab: Cutting Stock Problem](https://www.mathworks.com/help/optim/ug/cutting-stock-problem-based.html)\n", "* [AIMMS: Cutting Stock Problem](https://download.aimms.com/aimms/download/manuals/AIMMS3OM_CuttingStock.pdf)\n", "* [SCIP:Bin packing and cutting stock problems](https://scipbook.readthedocs.io/en/latest/bpp.html)\n", "* [PuLP: Implementation](https://github.com/coin-or/pulp/blob/master/examples/CGcolumnwise.py)\n", "\n", "More recently, the essential bilinear structure of the problem has been noted, and various convex transformations of the problem have been studied:\n", "\n", "> Harjunkoski, I., Westerlund, T., Pörn, R., & Skrifvars, H. (1998). Different transformations for solving non-convex trim-loss problems by MINLP. European Journal of Operational Research, 105(3), 594-603. [[abo.fi](http://users.abo.fi/twesterl/some-selected-papers/49.%20EJOR-IH-TW-RP-HS-1998.pdf)][[sciencedirect](https://www.sciencedirect.com/science/article/pii/S0377221797000660)]\n", "\n", "> Harjunkoski, I., Pörn, R., & Westerlund, T. (1999). Exploring the convex transformations for solving non-convex bilinear integer problems. Computers & Chemical Engineering, 23, S471-S474. [[sciencedirect](https://www.sciencedirect.com/science/article/pii/S0098135499801161)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "id": "Ck3gDfbnDCfN" }, "outputs": [], "source": [] } ], "metadata": { "colab": { "gpuType": "T4", "provenance": [] }, "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.12" } }, "nbformat": 4, "nbformat_minor": 1 }