{ "cells": [ { "cell_type": "markdown", "metadata": { "nbpages": { "level": 0, "link": "[](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html)", "section": "" } }, "source": [ "\n", "*This notebook contains material from [cbe30338-2021](https://jckantor.github.io/cbe30338-2021);\n", "content is available [on Github](https://github.com/jckantor/cbe30338-2021.git).*\n" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 0, "link": "[](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html)", "section": "" } }, "source": [ "\n", "< [5.1 Linear Production Model](https://jckantor.github.io/cbe30338-2021/05.01-Linear-Production-Model.html) | [Contents](toc.html) | [Tag Index](tag_index.html) | [5.3 Homework Assignment 4](https://jckantor.github.io/cbe30338-2021/05.03-Homework_4.html) >

\"Open

\"Download\"" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 1, "link": "[5.2 Linear Blending Problems](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2-Linear-Blending-Problems)", "section": "5.2 Linear Blending Problems" } }, "source": [ "# 5.2 Linear Blending Problems\n", "\n", "This notebook introduces simple material blending problems, and outlines a multi-step procedure for creating and solving models for these problems using CVXPY." ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.1 Learning Goals](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.1-Learning-Goals)", "section": "5.2.1 Learning Goals" } }, "source": [ "### 5.2.1 Learning Goals\n", "\n", "* Linear Blending problems\n", " * Frequently encountered in material blending \n", " * Models generally consist of linear mixing rules and mass/material balances\n", " * Decision variables are indexed by a set of raw materials\n", "\n", "\n", "* Further practice with key elements of modeling for optimization\n", " * cvxpy.Variable(): Create instance of an optimization **decision variable**\n", " * cvxpy.Minimize()/cvxpy.Maximize(): Create instance of an **optimization objective**\n", " * cvxpy.Problem(): Create instance of an optimization problem with objective and **constraints**.\n", " \n", " \n", "* Modeling and solving linear blending problems in CVXPY\n", " * Step 1. Coding problem data. Nested dictionaries or Pandas dataframes.\n", " * Step 2. Create index set. Use .keys() with nested dictionaries, or .index with Pandas dataframes.\n", " * Step 3. Create a dictionary of decision variables. Add any pertinent qualifiers or constraints for individual variables such as lower and upper bounds, non-negativity, variable names.\n", " * Step 4. Create an expression defining the problem objective.\n", " * Step 5. Create a one or more lists of problem constraints.\n", " * Step 6. Create the problem object from the objectives and constraints.\n", " * Step 7. Solve and display the solution." ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[5.2.1 Problem Statement (Jenchura, 2017)](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.1-Problem-Statement-(Jenchura,-2017))", "section": "5.2.1 Problem Statement (Jenchura, 2017)" } }, "source": [ "## 5.2.1 Problem Statement (Jenchura, 2017)\n", "\n", "A brewery receives an order for 100 gallons that is at least 4% ABV (alchohol by volume) beer. The brewery has on hand beer A that is 4.5% ABV and costs \\\\$0.32 per gallon to make, and beer B that is 3.7% ABV and costs \\\\$0.25 per gallon. Water (W) can also be used as a blending agent at a cost of \\\\$0.05 per gallon. Find the minimum cost blend of A, B, and W that meets the customer requirements." ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[5.2.2 Solving optimization problems with CVXPY](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2-Solving-optimization-problems-with-CVXPY)", "section": "5.2.2 Solving optimization problems with CVXPY" } }, "source": [ "## 5.2.2 Solving optimization problems with CVXPY\n", "\n", "The blending problem described above is relatively simple, it can be solved in CVXPY using no more than `cp.Variable()`, `cp.Minimize()`, and `cp.Problem()` as demonstrated below." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "nbpages": { "level": 2, "link": "[5.2.2 Solving optimization problems with CVXPY](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2-Solving-optimization-problems-with-CVXPY)", "section": "5.2.2 Solving optimization problems with CVXPY" } }, "outputs": [], "source": [ "import numpy as np\n", "import cvxpy as cp" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.1 Step 1. Coding Problem Data as a Python Dictionary](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.1-Step-1.-Coding-Problem-Data-as-a-Python-Dictionary)", "section": "5.2.2.1 Step 1. Coding Problem Data as a Python Dictionary" } }, "source": [ "### 5.2.2.1 Step 1. Coding Problem Data as a Python Dictionary\n", "\n", "The first step is to represent the problem data in a generic manner that could be extended to include additional blending components. Here we use a dictionary of raw materials, each key denoting a unique blending agent. For each key there is a second level dictionary containing attributes of the blending component. This nested \"dictionary of dictionaries\" organization is a useful of organizing tabular data for optimization problems." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.1 Step 1. Coding Problem Data as a Python Dictionary](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.1-Step-1.-Coding-Problem-Data-as-a-Python-Dictionary)", "section": "5.2.2.1 Step 1. Coding Problem Data as a Python Dictionary" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'A': {'abv': 0.045, 'cost': 0.32}, 'B': {'abv': 0.037, 'cost': 0.25}, 'W': {'abv': 0.0, 'cost': 0.05}}\n" ] } ], "source": [ "data = {\n", " 'A': {'abv': 0.045, 'cost': 0.32},\n", " 'B': {'abv': 0.037, 'cost': 0.25},\n", " 'W': {'abv': 0.000, 'cost': 0.05},\n", "}\n", "print(data)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.2 Step 2. Identifying index sets](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.2-Step-2.-Identifying-index-sets)", "section": "5.2.2.2 Step 2. Identifying index sets" } }, "source": [ "### 5.2.2.2 Step 2. Identifying index sets\n", "\n", "The objectives and constraints encountered in optimization problems often include sums over a set of objects. In the case, we will need to create sums over the set of raw materials in the blending problem." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.2 Step 2. Identifying index sets](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.2-Step-2.-Identifying-index-sets)", "section": "5.2.2.2 Step 2. Identifying index sets" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'A', 'B', 'W'}\n" ] } ], "source": [ "components = set(data.keys())\n", "print(components)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.3 Step 3. Create decision variables](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.3-Step-3.-Create-decision-variables)", "section": "5.2.2.3 Step 3. Create decision variables" } }, "source": [ "### 5.2.2.3 Step 3. Create decision variables" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.3 Step 3. Create decision variables](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.3-Step-3.-Create-decision-variables)", "section": "5.2.2.3 Step 3. Create decision variables" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'A': Variable((), nonneg=True), 'B': Variable((), nonneg=True), 'W': Variable((), nonneg=True)}\n" ] } ], "source": [ "x = {c: cp.Variable(nonneg=True) for c in components}\n", "print(x)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.4 Step 4. Objective Function](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.4-Step-4.-Objective-Function)", "section": "5.2.2.4 Step 4. Objective Function" } }, "source": [ "### 5.2.2.4 Step 4. Objective Function\n", "\n", "If we let subscript $c$ denote a blending component from the set of blending components $C$, and denote the volume of $c$ used in the blend as $x_c$, the cost of the blend is\n", "\n", "\\begin{align}\n", "\\mbox{cost} & = \\sum_{c\\in C} x_c P_c\n", "\\end{align}\n", "\n", "where $P_c$ is the price per unit volume of $c$. Using the Python data dictionary defined above, the price $P_c$ is given by `data[c]['cost']`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.4 Step 4. Objective Function](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.4-Step-4.-Objective-Function)", "section": "5.2.2.4 Step 4. Objective Function" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "minimize var0 @ 0.32 + var1 @ 0.25 + var2 @ 0.05\n" ] } ], "source": [ "total_cost = sum(x[c]*data[c][\"cost\"] for c in components)\n", "objective = cp.Minimize(total_cost)\n", "print(objective)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.5 Step 5. Constraints](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5-Step-5.-Constraints)", "section": "5.2.2.5 Step 5. Constraints" } }, "source": [ "### 5.2.2.5 Step 5. Constraints" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.5 Step 5. Constraints](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5-Step-5.-Constraints)", "section": "5.2.2.5 Step 5. Constraints" } }, "outputs": [], "source": [ "volume = 100\n", "abv = 0.040" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.1 Volume Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.1-Volume-Constraint)", "section": "5.2.2.5.1 Volume Constraint" } }, "source": [ "#### 5.2.2.5.1 Volume Constraint\n", "\n", "The customer requirement is produce a total volume $V$. Assuming ideal solutions, the constraint is given by\n", "\n", "\\begin{align}\n", "V & = \\sum_{c\\in C} x_c\n", "\\end{align}\n", "\n", "where $x_c$ denotes the volume of component $c$ used in the blend." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.1 Volume Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.1-Volume-Constraint)", "section": "5.2.2.5.1 Volume Constraint" } }, "outputs": [], "source": [ "constraints = [volume == sum(x[c] for c in components)]" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.2 Product Composition Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.2-Product-Composition-Constraint)", "section": "5.2.2.5.2 Product Composition Constraint" } }, "source": [ "#### 5.2.2.5.2 Product Composition Constraint\n", "\n", "The product composition is specified as requiring at least 4% alchohol by volume. Denoting the specification as $\\bar{A}$, the constraint may be written as\n", "\n", "\\begin{align}\n", "\\bar{A} & \\leq \\frac{\\sum_{c\\in C}x_c A_c}{\\sum_{c\\in C} x_c}\n", "\\end{align}\n", "\n", "where $A_c$ is the alcohol by volume for component $c$. As written, this is a nonlinear constraint. Multiplying both sides of the equation by the denominator yields a linear constraint\n", "\n", "\\begin{align}\n", "\\bar{A}\\sum_{c\\in C} x_c & \\leq \\sum_{c\\in C}x_c A_c\n", "\\end{align}\n", "\n", "A final form for this constraint can be given in either of two versions. In the first version we subtract the left-hand side from the right to give\n", "\n", "\\begin{align}\n", "0 & \\leq \\sum_{c\\in C}x_c \\left(A_c - \\bar{A}\\right) & \\mbox{ Version 1 of the linear blending constraint}\n", "\\end{align}\n", "\n", "Alternatively, the summation on the left-hand side corresponds to total volume. Since that is known as part of the problem specification, the blending constraint could also be written as\n", "\n", "\\begin{align}\n", "\\bar{A}V & \\leq \\sum_{c\\in C}x_c A_c & \\mbox{ Version 2 of the linear blending constraint}\n", "\\end{align}\n", "\n", "Which should you use? Normally either will work well. The advantage of version 1 is that it is fully specified by a single product requirement $\\bar{A}$, and doesn't require knowledge of the other product requirement $V$. This may be helpful in writing elegant Python code." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.2 Product Composition Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.2-Product-Composition-Constraint)", "section": "5.2.2.5.2 Product Composition Constraint" } }, "outputs": [], "source": [ "constraints.append(0 <= sum(x[c]*(data[c]['abv'] - abv) for c in components))" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.2 Product Composition Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.2-Product-Composition-Constraint)", "section": "5.2.2.5.2 Product Composition Constraint" } }, "source": [ "We can review the constraints by printing them out. If no name is specified for a decision variable when it is created, then cvxpy will assign a name beginiing with `var`. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.2 Product Composition Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.2-Product-Composition-Constraint)", "section": "5.2.2.5.2 Product Composition Constraint" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "var0 + var1 + var2 == 100.0\n", "0.0 <= var0 @ 0.0049999999999999975 + var1 @ -0.0030000000000000027 + var2 @ -0.04\n" ] } ], "source": [ "for constraint in constraints:\n", " print(constraint)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 4, "link": "[5.2.2.5.2 Product Composition Constraint](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.5.2-Product-Composition-Constraint)", "section": "5.2.2.5.2 Product Composition Constraint" } }, "source": [ "


\n", "\n", "**Study Question:** Consult the CVXPY document for `cvxpy.Variable()`. Modify the code above to assign a name to each variable corresponding to the key for its entry in the data dictionary. Rerun the cells to see how the objective and constraints are printed.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.6 Step 6. Create CVXPY Problem object](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.6-Step-6.-Create-CVXPY-Problem-object)", "section": "5.2.2.6 Step 6. Create CVXPY Problem object" } }, "source": [ "### 5.2.2.6 Step 6. Create CVXPY Problem object\n", "\n", "An optimization problem consists of decision variables, and algebraic expressions that define an objective and a list of constranits. These are encapsulated into a CVXPY Problem." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.6 Step 6. Create CVXPY Problem object](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.6-Step-6.-Create-CVXPY-Problem-object)", "section": "5.2.2.6 Step 6. Create CVXPY Problem object" } }, "outputs": [ { "data": { "text/plain": [ "27.625000014836907" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "problem = cp.Problem(objective, constraints)\n", "problem.solve()" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.7 Step 7. Display solution](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.7-Step-7.-Display-solution)", "section": "5.2.2.7 Step 7. Display solution" } }, "source": [ "### 5.2.2.7 Step 7. Display solution\n", "\n", "Following solution, the values of any CVXPY variable, expression, objective, or constraint can be accessed using the associated `value` property." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.7 Step 7. Display solution](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.7-Step-7.-Display-solution)", "section": "5.2.2.7 Step 7. Display solution" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "var0 @ 0.32 + var1 @ 0.25 + var2 @ 0.05\n" ] } ], "source": [ "print(total_cost)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.7 Step 7. Display solution](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.7-Step-7.-Display-solution)", "section": "5.2.2.7 Step 7. Display solution" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "27.625000014836907\n" ] } ], "source": [ "print(total_cost.value)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.7 Step 7. Display solution](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.7-Step-7.-Display-solution)", "section": "5.2.2.7 Step 7. Display solution" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Minimum cost to produce 100 gallons at ABV=0.04: $27.63\n" ] } ], "source": [ "print(f\"Minimum cost to produce {volume} gallons at ABV={abv}: ${total_cost.value:5.2f}\")" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "nbpages": { "level": 3, "link": "[5.2.2.7 Step 7. Display solution](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.2.7-Step-7.-Display-solution)", "section": "5.2.2.7 Step 7. Display solution" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A: 37.50 gallons\n", "B: 62.50 gallons\n", "W: 0.00 gallons\n" ] } ], "source": [ "for c in sorted(components):\n", " print(f\"{c}: {x[c].value:5.2f} gallons\")" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 2, "link": "[5.2.3 Parametric Studies](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3-Parametric-Studies)", "section": "5.2.3 Parametric Studies" } }, "source": [ "## 5.2.3 Parametric Studies\n", "\n", "An important use of optimization models is to investigate how operations depend on critical parameters. For example, for this blending problem we may be interested in questions like:\n", "\n", "* How does the operating cost change with product alcohol content?\n", "* What is the cost of producing one more gallon of product?\n", "* What if the supply of a raw material is constrained?\n", "* What if we produce two products rather than one?\n", "* How much would be pay for raw materials with different specifications" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "source": [ "### 5.2.3.1 Consolidating\n", "\n", "To enable parametric studies, our first step is to consolidate the model into a function that accepts problem data and reports an optimal solution." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "outputs": [], "source": [ "import numpy as np\n", "import cvxpy as cp\n", "\n", "def brew_blend(volume, abv, data):\n", " \n", " # create set of components\n", " components = set(data.keys())\n", " \n", " # create variables\n", " x = {c: cp.Variable(nonneg=True, name=c) for c in components}\n", " \n", " # create objective function\n", " total_cost = sum(x[c]*data[c]['cost'] for c in components)\n", " \n", " # create list of constraints\n", " constraints = [\n", " volume == sum(x[c] for c in components),\n", " 0 == sum(x[c]*(data[c]['abv'] - abv) for c in components)\n", " ]\n", " \n", " # create and solve problem\n", " problem = cp.Problem(cp.Minimize(total_cost), constraints)\n", " problem.solve()\n", " \n", " # return results\n", " min_cost = problem.value\n", " optimal_blend = {c: x[c].value for c in components}\n", " return min_cost, optimal_blend\n" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "source": [ "Demonstration" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Minimum cost to produce 100 gallons at ABV=0.04 = $27.63\n", "A: 37.50\n", "B: 62.50\n", "W: 0.00\n" ] } ], "source": [ "# problem data\n", "data = {\n", " 'A': {'abv': 0.045, 'cost': 0.32},\n", " 'B': {'abv': 0.037, 'cost': 0.25},\n", " 'W': {'abv': 0.000, 'cost': 0.05},\n", "}\n", "\n", "# product requirement\n", "volume = 100\n", "abv = 0.04\n", "\n", "# optimal solution\n", "min_cost, optimal_blend = brew_blend(volume, abv, data)\n", "\n", "# display solutoin\n", "print(f\"Minimum cost to produce {volume} gallons at ABV={abv} = ${min_cost:5.2f}\")\n", "for c in sorted(optimal_blend.keys()):\n", " print(f\"{c}: {optimal_blend[c]:5.2f}\")" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "source": [ "The Pandas library has a function to convert dictionaries of data to DataFrames. This is a convenient way to display and visualize the data resulting from a complex optimization problem." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.1 Consolidating](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.1-Consolidating)", "section": "5.2.3.1 Consolidating" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'A': 37.500001136050194, 'B': 62.49999861831747, 'W': 2.4563236418836055e-07}\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import pandas as pd\n", "\n", "print(optimal_blend)\n", "df = pd.DataFrame.from_dict(optimal_blend, orient=\"index\")\n", "df.plot(kind=\"bar\")" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "source": [ "### 5.2.3.2 Optimal blend as a function of product specification" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "data = {\n", " 'A': {'abv': 0.045, 'cost': 0.32},\n", " 'B': {'abv': 0.037, 'cost': 0.25},\n", " 'W': {'abv': 0.000, 'cost': 0.05},\n", "}\n", "\n", "# gather results for a range of abv values\n", "abv = np.linspace(0, 0.05)\n", "results = [brew_blend(volume, a, data) for a in abv]\n", "\n", "fig, ax = plt.subplots(2, 1, sharex=True)\n", "ax[0].plot(abv, [cost for cost, values in results])\n", "ax[0].set_ylabel(\"$\")\n", "ax[0].grid(True)\n", "\n", "for c in sorted(data.keys()):\n", " ax[1].plot(abv, [values[c] for cost, values in results], label=c)\n", "ax[1].set_xlabel('Blended ABV')\n", "ax[1].set_ylabel('gallons')\n", "ax[1].legend()\n", "ax[1].grid(True)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "source": [ "
\n", "\n", "**Study Question:** Suppose an additional raw material \"C\" becomes available with an abv of 4.2% at a cost of 28 cents per gallon. How does that change the optimal blend?\n", "\n", "**Study Question:** Having decided to use \"C\" for the blended product, you later learn only 50 gallons of \"C\" are available. Modify the solution procedure to allow for limits on the amount of raw maaterial, and investigate the implications for the optimal blend of having only 50 gallons of \"C\" available, and assuming the amounts of the other components \"A\", \"B\", and \"W\" remain unlimited.\n", "\n", "**Study Question:** An opportunity has developed to sell a second product with an abv of 3.8%. The first product is now labeled \"X\" with abv 4.0% and sells for \\\\$1.25 per gallon, and the second product is designated \"Y\" and sells for \\\\$1.10 per gallon. You've also learned that all of your raw materials are limited to 50 gallons. What should your production plan be to maximize profits?\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "import cvxpy as cp\n", "\n", "def brew_blend(volume, abv, data):\n", " \n", " # create set of components\n", " components = set(data.keys())\n", " \n", " # create variables\n", " x = {c: cp.Variable(nonneg=True, name=c) for c in components}\n", " \n", " # create objective function\n", " total_cost = sum(x[c]*data[c]['cost'] for c in components)\n", " \n", " # create list of constraints\n", " constraints = [\n", " volume == sum(x[c] for c in components),\n", " 0 == sum(x[c]*(data[c]['abv'] - abv) for c in components)\n", " ]\n", " \n", " # add volume constraints\n", " for c in components:\n", " constraints.append(x[c] <= data[c]['volume']) \n", " \n", " # create and solve problem\n", " problem = cp.Problem(cp.Minimize(total_cost), constraints)\n", " problem.solve()\n", " \n", " # return results\n", " min_cost = problem.value\n", " optimal_blend = {c: x[c].value for c in components}\n", " return min_cost, optimal_blend\n", "\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "data = {\n", " 'A': {'abv': 0.045, 'cost': 0.32, 'volume': 50},\n", " 'B': {'abv': 0.037, 'cost': 0.25, 'volume': 50},\n", " 'C': {'abv': 0.042, 'cost': 0.28, 'volume': 50}, # <= add raw material data\n", " 'W': {'abv': 0.000, 'cost': 0.05, 'volume': 50},\n", "}\n", "\n", "# gather results for a range of abv values\n", "abv = np.linspace(0.03, 0.05, 200) # <= narrow range of plott\n", "results = [brew_blend(volume, a, data) for a in abv]\n", "\n", "fig, ax = plt.subplots(2, 1, sharex=True)\n", "ax[0].plot(abv, [cost for cost, values in results])\n", "ax[0].set_ylabel(\"$\")\n", "ax[0].grid(True)\n", "\n", "for c in sorted(data.keys()):\n", " ax[1].plot(abv, [values[c] for cost, values in results], label=c)\n", "ax[1].set_xlabel('Blended ABV')\n", "ax[1].set_ylabel('gallons')\n", "ax[1].legend()\n", "ax[1].grid(True)" ] }, { "cell_type": "markdown", "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "source": [ "\n", "< [Linear Programming in Pyomo](http://nbviewer.jupyter.org/github/jckantor/CBE30338/blob/master/notebooks/06.04-Linear-Programming-in-Pyomo.ipynb) | [Contents](toc.ipynb) | [Design of a Cold Weather Fuel](http://nbviewer.jupyter.org/github/jckantor/CBE30338/blob/master/notebooks/06.07-Design-of-a-Cold-Weather-Fuel.ipynb) >

\"Open

\"Download\"" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A,Y\n", "A,X\n", "W,Y\n", "W,X\n", "C,Y\n", "C,X\n", "B,Y\n", "B,X\n", "A,Y @ 0.32 + A,X @ 0.32 + W,Y @ 0.05 + W,X @ 0.05 + C,Y @ 0.28 + C,X @ 0.28 + B,Y @ 0.25 + B,X @ 0.25\n" ] }, { "ename": "KeyError", "evalue": "'A'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0;31m# gather results for a range of abv values\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 59\u001b[0m \u001b[0mabv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlinspace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.03\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.05\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m200\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# <= narrow range of plott\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 60\u001b[0;31m \u001b[0mresults\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mbrew_blend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvolume\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0ma\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mabv\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 61\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 62\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msharex\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0;31m# gather results for a range of abv values\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 59\u001b[0m \u001b[0mabv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlinspace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.03\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.05\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m200\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# <= narrow range of plott\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 60\u001b[0;31m \u001b[0mresults\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mbrew_blend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvolume\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0ma\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mabv\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 61\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 62\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msharex\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m\u001b[0m in \u001b[0;36mbrew_blend\u001b[0;34m(volume, abv, data)\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;31m# create list of constraints\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m constraints = [\n\u001b[0;32m---> 25\u001b[0;31m \u001b[0mvolume\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcomponents\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 26\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'abv'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mabv\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcomponents\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 27\u001b[0m ]\n", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;31m# create list of constraints\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 24\u001b[0m constraints = [\n\u001b[0;32m---> 25\u001b[0;31m \u001b[0mvolume\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcomponents\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 26\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'abv'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mabv\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcomponents\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 27\u001b[0m ]\n", "\u001b[0;31mKeyError\u001b[0m: 'A'" ] } ], "source": [ "import numpy as np\n", "import cvxpy as cp\n", "\n", "def brew_blend(volume, abv, data):\n", " \n", " # create set of components\n", " components = set(data.keys())\n", " products = set(product_specs.keys())\n", " \n", " # create variables\n", " x = {(c, p): cp.Variable(nonneg=True, name=f\"{c},{p}\") for c in components for p in products}\n", " y = {p: cp.Variable(nonneg=True, name=p) for p in products}\n", " \n", " \n", " # create objective function\n", " total_cost = sum(x[c,p]*data[c]['cost'] for c in components for p in products)\n", " revenue = sum(y[p*product_specs[])\n", " \n", " print(total_cost)\n", " \n", " # create list of constraints\n", " constraints = [\n", " volume == sum(x[c] for c in components),\n", " 0 == sum(x[c]*(data[c]['abv'] - abv) for c in components)\n", " ]\n", " \n", " # add volume constraints\n", " for c in components:\n", " constraints.append(x[c] <= data[c]['volume']) \n", " \n", " # create and solve problem\n", " problem = cp.Problem(cp.Minimize(total_cost), constraints)\n", " problem.solve()\n", " \n", " # return results\n", " min_cost = problem.value\n", " optimal_blend = {c: x[c].value for c in components}\n", " return min_cost, optimal_blend\n", "\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "product_specs = {\n", " 'X': {'abv': 0.040, 'price': 1.25},\n", " 'Y': {'abv': 0.038, 'price': 1.10}\n", "}\n", "\n", "data = {\n", " 'A': {'abv': 0.045, 'cost': 0.32, 'volume': 50},\n", " 'B': {'abv': 0.037, 'cost': 0.25, 'volume': 50},\n", " 'C': {'abv': 0.042, 'cost': 0.28, 'volume': 50}, # <= add raw material data\n", " 'W': {'abv': 0.000, 'cost': 0.05, 'volume': 50},\n", "}\n", "\n", "# gather results for a range of abv values\n", "abv = np.linspace(0.03, 0.05, 200) # <= narrow range of plott\n", "results = [brew_blend(volume, a, data) for a in abv]\n", "\n", "fig, ax = plt.subplots(2, 1, sharex=True)\n", "ax[0].plot(abv, [cost for cost, values in results])\n", "ax[0].set_ylabel(\"$\")\n", "ax[0].grid(True)\n", "\n", "for c in sorted(data.keys()):\n", " ax[1].plot(abv, [values[c] for cost, values in results], label=c)\n", "ax[1].set_xlabel('Blended ABV')\n", "ax[1].set_ylabel('gallons')\n", "ax[1].legend()\n", "ax[1].grid(True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbpages": { "level": 3, "link": "[5.2.3.2 Optimal blend as a function of product specification](https://jckantor.github.io/cbe30338-2021/05.02-Linear-Blending-Problem.html#5.2.3.2-Optimal-blend-as-a-function-of-product-specification)", "section": "5.2.3.2 Optimal blend as a function of product specification" } }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [5.1 Linear Production Model](https://jckantor.github.io/cbe30338-2021/05.01-Linear-Production-Model.html) | [Contents](toc.html) | [Tag Index](tag_index.html) | [5.3 Homework Assignment 4](https://jckantor.github.io/cbe30338-2021/05.03-Homework_4.html) >

\"Open

\"Download\"" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.8.5" } }, "nbformat": 4, "nbformat_minor": 4 }