{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 60 Minutes to Pyomo: An Energy Storage Model Predictive Control Example" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# This code cell installs packages on Colab\n", "\n", "import sys\n", "if \"google.colab\" in sys.modules:\n", " !wget \"https://raw.githubusercontent.com/ndcbe/CBE60499/main/notebooks/helper.py\"\n", " import helper\n", " helper.install_idaes()\n", " helper.install_ipopt()\n", " helper.install_glpk()\n", " '''\n", " helper.download_data(['Prices_DAM_ALTA2G_7_B1.csv'])\n", " helper.download_figures(['battery.png','pyomo-table-4.1.png',\n", " 'pyomo-table-4.2.png','pyomo-table-4.3.png',\n", " 'pyomo-table-4.4.png','pyomo-table-4.6.png'])\n", " '''" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import pyomo.environ as pyo\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Problem Setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Background" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In many regions of the world, including the US, electricity generation is scheduled through wholesale electricity markets. Individual generators (resources) transmit information about their operating costs and constraints to the market via a bid. The market operator then solves an optimization problem (e.g., the unit commitment problem) to minimize the total electricity generator cost. The market operator decides which generators to dispatch during each hour to satisfy the forecasted demand while honoring limitations for each generator (e.g., maximum ramp rate, the required time for start-up/shutdown, etc.).\n", "\n", "![US_markets](https://www.ferc.gov/sites/default/files/2020-06/map-overview-electric.jpg)\n", "\n", "Read more information here:\n", "* https://www.ferc.gov/industries-data/market-assessments/electric-power-markets\n", "* https://www.sciencedirect.com/science/article/pii/S0306261916318487" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pandas and Energy Prices" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The CSV (comma separated value) file `Prices_DAM_ALTA2G_7_B1.csv` contains price data for a single location in California for an entire year. The prices are set every hour and have units $/MWh. We will use the package `pandas` to import and analyze the data." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
price
036.757
134.924
233.389
332.035
433.694
\n", "
" ], "text/plain": [ " price\n", "0 36.757\n", "1 34.924\n", "2 33.389\n", "3 32.035\n", "4 33.694" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Load the data file\n", "ca_data = pd.read_csv('https://raw.githubusercontent.com/ndcbe/optimization/main/notebooks/data/Prices_DAM_ALTA2G_7_B1.csv',names=['price'])\n", "\n", "# Print the first 10 rows\n", "ca_data.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we can calculate summary statistics:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
price
count8760.000000
mean32.516994
std9.723477
min-2.128700
25%26.510000
50%30.797500
75%37.544750
max116.340000
\n", "
" ], "text/plain": [ " price\n", "count 8760.000000\n", "mean 32.516994\n", "std 9.723477\n", "min -2.128700\n", "25% 26.510000\n", "50% 30.797500\n", "75% 37.544750\n", "max 116.340000" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ca_data.describe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " What are 2 or 3 interesting observations from these summary statistics?\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, let's visualize the data in a histogram:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaMAAAEOCAYAAAAkF3jEAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/Il7ecAAAACXBIWXMAAAsTAAALEwEAmpwYAAAniUlEQVR4nO3de5wcVZ338c+XcJUIhFtEwhpWonJbL2SRfVjdiaBEYQF9REFuQTCKsIsalaB7UXfR+CguugpLUAwXIWaRmyIqIoO6y0VAlLsEMiIEuYMZ5Jb4e/44p03R0z3TPdOZMz3zfb9e/eruU6eqzqmurl/VqVNVigjMzMxKWqt0AczMzByMzMysOAcjMzMrzsHIzMyKczAyM7Pi1i5dgFI233zzmD59esem99RTT7Hhhht2bHqljaf6jKe6wPiqj+sydjWrzw033PBIRGzR6flN2GA0ffp0rr/++o5Nr7e3l56eno5Nr7TxVJ/xVBcYX/VxXcauZvWR9Ns1MT8305mZWXEORmZmVlzRYCSpT9LNkm6SdH1O21TS5ZLuyu9TKvlPkLRU0p2S9qqk75Kns1TSVySpRH3MzGx4xsKR0ayIeE1EzMzf5wNXRMQM4Ir8HUk7AAcCOwKzgVMkTcrjnArMBWbk1+xRLL+ZmY3QWAhG9fYDzsyfzwT2r6QvjohnI2IZsBTYVdJWwEYRcXWkG+2dVRnHzMy6gEreKFXSMuBxIIDTImKhpCciYpNKnscjYoqkrwLXRMQ5Of0bwGVAH7AgIvbM6W8Ajo+IfRrMby7pCIqpU6fusnjx4o7Vpb+/n8mTJ3dseqWNp/qMp7rA+KqP6zJ2NavPrFmzbqi0ZHVM6a7du0fEcklbApdLumOQvI3OA8Ug6QMTIxYCCwFmzpwZneyGOVG6dXaj8VQXGF/1cV3GrtGuT9FmuohYnt8fAi4EdgUezE1v5PeHcvb7gG0qo08Dluf0aQ3SzcysSxQLRpI2lPTi2mfgLcAtwCXA4Tnb4cDF+fMlwIGS1pO0LamjwnUR8QCwQtJuuRfdYZVxzMysC5RsppsKXJh7Ya8NnBsRP5D0C2CJpCOBe4EDACLiVklLgNuAlcAxEbEqT+toYBGwAek80mWjWZGJYPr8S4vMt2/B3kXma2ajq1gwioh7gFc3SH8U2KPJOCcCJzZIvx7YqdNlNDOz0TEWu3abmdkE42BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFVc8GEmaJOmXkr6Xv28q6XJJd+X3KZW8J0haKulOSXtV0neRdHMe9hVJKlEXMzMbnuLBCDgOuL3yfT5wRUTMAK7I35G0A3AgsCMwGzhF0qQ8zqnAXGBGfs0enaKbmVknFA1GkqYBewNfryTvB5yZP58J7F9JXxwRz0bEMmApsKukrYCNIuLqiAjgrMo4ZmbWBZS234VmLp0PfA54MfDRiNhH0hMRsUklz+MRMUXSV4FrIuKcnP4N4DKgD1gQEXvm9DcAx0fEPg3mN5d0BMXUqVN3Wbx4ccfq0t/fz+TJkzs2vdLq63Pz/U8WKcfOW2884mmM99+mm7kuY1ez+syaNeuGiJjZ6fmt3ekJtkrSPsBDEXGDpJ5WRmmQFoOkD0yMWAgsBJg5c2b09LQy29b09vbSyemVVl+fOfMvLVKOvoN7hswzlPH+23Qz12XsGu36FAtGwO7AvpLeBqwPbCTpHOBBSVtFxAO5Ce6hnP8+YJvK+NOA5Tl9WoN0MzPrEsXOGUXECRExLSKmkzom/CQiDgEuAQ7P2Q4HLs6fLwEOlLSepG1JHRWui4gHgBWSdsu96A6rjGNmZl2g5JFRMwuAJZKOBO4FDgCIiFslLQFuA1YCx0TEqjzO0cAiYAPSeaTLRrvQZmY2fGMiGEVEL9CbPz8K7NEk34nAiQ3Srwd2WnMlNDOzNWksXGdkZmYTnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsW1FYwk3SNp30GG7yPpnpEXy8zMJpJ2j4ymA5MHGb4h8LJhl8bMzCakTjfTTQX+2EpGSetLuk7SryTdKunTOX1TSZdLuiu/T6mMc4KkpZLulLRXJX0XSTfnYV+RpA7Xy8zM1qC1h8og6Y1ATyXpHZK2a5B1U+BA4KYW5/0s8KaI6Je0DvBzSZcB7wCuiIgFkuYD84HjJe2Qp78j8FLgx5JeERGrgFOBucA1wPeB2cBlLZbDzMwKGzIYAbOAf82fgxQs3tEk71Lgw63MOCIC6M9f18mvAPZjdfA7E+gFjs/piyPiWWCZpKXArpL6gI0i4moASWcB++NgZGbWNZRiwiAZpI2BTQAB9wAfAi6uyxZAf0Q81tbMpUnADcB2wNci4nhJT0TEJpU8j0fEFElfBa6JiHNy+jdIAacPWBARe+b0NwDHR8Q+DeY3l3QExdSpU3dZvHhxO8UdVH9/P5MnD3Y6rbvU1+fm+58sUo6dt954xNMY779NN3Ndxq5m9Zk1a9YNETGz0/Mb8sgoIp4EngSQNAu4PSIe6sTMcxPbayRtAlwoaadBsjc6DxSDpDea30JgIcDMmTOjp6enrfIOpre3l05Or7T6+syZf2mRcvQd3DNknqGM99+mm7kuY9do16etDgwRcVWnAlHddJ8gNcfNBh6UtBVAfq/N7z5gm8po04DlOX1ag3QzM+sSrZwzegFJfwG8H5gBbMbAI5OIiD1amM4WwPMR8YSkDYA9gc8DlwCHAwvye61J8BLgXElfInVgmAFcFxGrJK2QtBtwLXAY8J/t1svMzMppKxhJeitwIbAusAJo6xxRna2AM/N5o7WAJRHxPUlXA0skHQncCxwAEBG3SloC3AasBI7JzXwARwOLgA1I55HcecHMrIu0e2T0OeARYP+IuH4kM46IXwOvbZD+KNDwyCoiTgRObJB+PTDY+SYzMxvD2r3o9VXAySMNRGZmZlXtBqOHgefWREHMzGziajcYnQ383zVREDMzm7jaPWe0CJgl6WLgy8AyYFV9poi4d+RFMzOziaLdYHQHqy80HXCHg4pJwy6RmZlNOO0Go8/Q5O4GZmZmw9VWMIqIT62hcpiZ2QTmx46bmVlx7d6B4Y2t5IuInw6vOGZmNhG1e86ol9bOGbkDg5mZtazdYHREk2m8HJhDerbQaSMrkpmZTTTtdmA4s9kwSV8AbhxxiczMbMLpWAeGiHgc+Drw8U5N08zMJoZO96Z7HPjLDk/TzMzGuY4FI0nrA4cCv+/UNM3MbGJot2v3GU0GbQr8DbAF8LGRFsrMzCaWdnvTzWmS/hjwG+DDEXHuiEpkVjF9/qUjnsa8nVcyZxjT6Vuw94jnbWatabc3ne/YYGZmHefgYmZmxbXbTAeApI2APVndc+4e4PKIWNGpgpmZ2cTRdjCSdBRwEjCZ9FwjSLcI6pf0kYj4RgfLZ2ZmE0C7ven2BRaSjoT+BbglD9oR+AdgoaSHIuK7HS2lmZmNa+0eGX0cuB14fUT0V9KvkPRN4BrgeMDByMzMWtZuB4ZXA4vqAhEA+XzRmTmPmZlZy4bTm06DDPMjyc3MrG3tBqNfAYdL2rB+gKTJpItif9WBcpmZ2QTS7jmjLwIXADdK+gpwW06vdWDYDnhH54pnZmYTQbt3YLhI0rHA54H/ZHWznICngGMj4uLOFtHMzMa7tq8ziohTJJ0LvBnYlhSI7iZd9Ppkh8tnZmYTwLDuwBARTwD/3dmimJnZRDVkBwZJkyQtkPSBIfIdLemzkgbrbWdmZjZAK73pDiE9o+gXQ+S7jnTB60EjLZSZmU0srQSjdwE/jogbBsuUh/8QByMzM2tTK8FoF+DHLU7vSmBmKxklbSPpSkm3S7pV0nE5fVNJl0u6K79PqYxzgqSlku6UtFclfRdJN+dhX3FToZlZd2klGG0KPNTi9B7O+VuxEpgXEdsDuwHHSNoBmA9cEREzgCvyd/KwA0nXNM0GTpE0KU/rVGAuMCO/ZrdYBjMzGwNaCUYrgM1bnN5mwID71jUSEQ9ExI358wrSDVi3BvYj3eOO/L5//rwfsDgino2IZcBSYFdJWwEbRcTVERHAWZVxzMysC7QSjG4F3tLi9N6c87dF0nTgtcC1wNSIeABSwAK2zNm2Bn5XGe2+nLZ1/lyfbmZmXaKV64wuAE6StN9gd1fIzzp6M/CRdgqQ72n3HeBDEfGHQU73NBoQg6Q3mtdcUnMeU6dOpbe3t52iDqq/v7+j0yutvj7zdl5ZrjAjNHWD4ZV/rP6e42ldc13GrtGuTyvB6DTgaGCJpC8Cp0dEX21gPqo5Cvgo8JucvyWS1iEFom9FxAU5+UFJW0XEA7kJrna+6j5gm8ro04DlOX1ag/QBImIh6eGAzJw5M3p6elot6pB6e3vp5PRKq6/PnPmXlivMCM3beSUn3dz+9d19B/d0vjAdMJ7WNddl7Brt+gzZTBcRTwN7A8uAE4C7JT0h6V5Jj5NuBfSJPHyfiHimlRnnHm/fAG6PiC9VBl0CHJ4/Hw5cXEk/UNJ6krYldVS4LjflrZC0W57mYZVxzMysC7S0uxgRSyW9Bngf8E5Sj7aXAH8AfkY6uvl6Dlyt2h04FLhZ0k057RPAAtJR2JHAvcABuQy3SlpCulP4SuCYiFiVxzsaWARsAFyWX2Zm1iVabrvIRzz/mV8jFhE/p/mD+vZoMs6JwIkN0q8HdupEuczMbPQN50mvZmZmHeVgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsU5GJmZWXEORmZmVpyDkZmZFedgZGZmxTkYmZlZcQ5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlacg5GZmRXnYGRmZsUVC0aSzpD0kKRbKmmbSrpc0l35fUpl2AmSlkq6U9JelfRdJN2ch31Fkka7LmZmNjIlj4wWAbPr0uYDV0TEDOCK/B1JOwAHAjvmcU6RNCmPcyowF5iRX/XTNDOzMa5YMIqInwKP1SXvB5yZP58J7F9JXxwRz0bEMmApsKukrYCNIuLqiAjgrMo4ZmbWJdYuXYA6UyPiAYCIeEDSljl9a+CaSr77ctrz+XN9ekOS5pKOopg6dSq9vb0dK3h/f39Hp1dafX3m7byyXGFGaOoGwyv/WP09x9O65rqMXaNdn7EWjJppdB4oBklvKCIWAgsBZs6cGT09PR0pHKQNVyenV1p9febMv7RcYUZo3s4rOenm9lf1voN7Ol+YDhhP65rrMnaNdn3GWm+6B3PTG/n9oZx+H7BNJd80YHlOn9Yg3czMushYC0aXAIfnz4cDF1fSD5S0nqRtSR0VrstNeisk7ZZ70R1WGcfMzLpEsWY6SecBPcDmku4D/hVYACyRdCRwL3AAQETcKmkJcBuwEjgmIlblSR1N6pm3AXBZfpmZWRcpFowi4qAmg/Zokv9E4MQG6dcDO3WwaGZmNsrGWjOdmZlNQA5GZmZWnIORmZkV52BkZmbFORiZmVlxDkZmZlZct9wOyGzUTS90C6S+BXsXma9ZST4yMjOz4hyMzMysOAcjMzMrzsHIzMyKczAyM7PiHIzMzKw4ByMzMyvOwcjMzIpzMDIzs+IcjMzMrDgHIzMzK87ByMzMivONUrvIaN64c97OK5lT6EahZjbx+MjIzMyKczAyM7PiHIzMzKw4ByMzMyvOwcjMzIpzMDIzs+IcjMzMrDgHIzMzK87ByMzMinMwMjOz4hyMzMysON+bbhga3SPO93IzMxs+ByOzMWaoG+KuyR2fvgV7r5Hpmg1l3DTTSZot6U5JSyXNL10eMzNr3bgIRpImAV8D3grsABwkaYeypTIzs1aNl2a6XYGlEXEPgKTFwH7AbUVLZdZlRvOZWVD+XKubJccORUTpMoyYpHcCsyPiqPz9UOD1EXFsXb65wNz89ZXAnR0sxubAIx2cXmnjqT7jqS4wvurjuoxdzerzsojYotMzGy9HRmqQNiDKRsRCYOEaKYB0fUTMXBPTLmE81Wc81QXGV31cl7FrtOszLs4ZAfcB21S+TwOWFyqLmZm1abwEo18AMyRtK2ld4EDgksJlMjOzFo2LZrqIWCnpWOCHwCTgjIi4dZSLsUaa/woaT/UZT3WB8VUf12XsGtX6jIsODGZm1t3GSzOdmZl1MQcjMzMrzsGoA7r5VkSStpF0paTbJd0q6bicvqmkyyXdld+nlC5rqyRNkvRLSd/L37u5LptIOl/SHfk3+pturY+kD+d17BZJ50lav5vqIukMSQ9JuqWS1rT8kk7I24Q7Je1VptSNNanLF/J69mtJF0rapDJsjdfFwWiExsGtiFYC8yJie2A34Jhc/vnAFRExA7gif+8WxwG3V753c12+DPwgIl4FvJpUr66rj6StgX8EZkbETqSORgfSXXVZBMyuS2tY/vwfOhDYMY9zSt5WjBWLGFiXy4GdIuKvgN8AJ8Do1cXBaOT+fCuiiHgOqN2KqCtExAMRcWP+vIK0sduaVIczc7Yzgf2LFLBNkqYBewNfryR3a102At4IfAMgIp6LiCfo0vqQeu9uIGlt4EWkawG7pi4R8VPgsbrkZuXfD1gcEc9GxDJgKWlbMSY0qktE/CgiVuav15Cu14RRqouD0chtDfyu8v2+nNZ1JE0HXgtcC0yNiAcgBSxgy4JFa8fJwMeBP1XSurUufwk8DHwzNzt+XdKGdGF9IuJ+4IvAvcADwJMR8SO6sC51mpW/27cL7wUuy59HpS4ORiPX0q2IxjpJk4HvAB+KiD+ULs9wSNoHeCgibihdlg5ZG3gdcGpEvBZ4irHdjNVUPpeyH7At8FJgQ0mHlC3VGtW12wVJnyQ133+rltQgW8fr4mA0cl1/KyJJ65AC0bci4oKc/KCkrfLwrYCHSpWvDbsD+0rqIzWXvknSOXRnXSCtW/dFxLX5+/mk4NSN9dkTWBYRD0fE88AFwP+hO+tS1az8XbldkHQ4sA9wcKy+CHVU6uJgNHJdfSsiSSKdk7g9Ir5UGXQJcHj+fDhw8WiXrV0RcUJETIuI6aTf4ScRcQhdWBeAiPg98DtJr8xJe5Aei9KN9bkX2E3Si/I6twfp/GQ31qWqWfkvAQ6UtJ6kbYEZwHUFytcySbOB44F9I+KPlUGjU5eI8GuEL+BtpN4ndwOfLF2eNsv+t6RD7l8DN+XX24DNSL2D7srvm5Yua5v16gG+lz93bV2A1wDX59/nImBKt9YH+DRwB3ALcDawXjfVBTiPdL7redLRwpGDlR/4ZN4m3Am8tXT5W6jLUtK5odp24L9Gsy6+HZCZmRXnZjozMyvOwcjMzIpzMDIzs+IcjMzMrDgHIzMzK87BaAyStEjSmO3mKOlTkiLfPqgrSOrNF8PaOCRpTl4newrNvzfPPyStHCJv3ygVq+Pa+R9J2q2yTELSpwbL39XBSFJPXWVXSXo836L+TKVHOzS6lUUxkpbksv64dFlGQ+VP+ryklzTJ8+XKb9gzuiUcPkkfkjSnzXFiiNffrqHijkmVIFJ7/UnSk5J+Lumw0uVr0yPAoUBHyi3pgLxMXpm/9+Xvj0par8k4F1eW5fSc9oH8/cgG+Q/Kw343YGJp+J2SnpA0nFixlLQ8PtxK5rWHMYOx6Dzg+6R7KL0YeCXp7rmHAT+WdECkux0XJWkzYF/SxWNvkjQ9IvrKlmpU1PYUDwW+UB2Q71pxMPAMsP4ol2ukPgT0kW7H346bgJOaDLtz2KXpbl8h3c1kLWA68D7gTEnTIuKzLYx/NukWUM+tsRIO7amIOGewDPnWW2tJWjfSXf4Hsz/pzijVdeIZYFPSduS/66Y9lXTBev1/6cr8Pot8B/iKHtL/c5qk7SJiaWV6LwFeAXw3Iv5EmyLiEeCcHBT/Y6j8XX1kVHFjRJwTEWdHxCkRcRzpjsdfIt0T67yyxfuzQ4B1gXeTrnw+omxxRs2zpDsAN6rvfqSr2C9oMGxElEzu9HQ74P68vjZ6PVyiQEoPJHxRiXlnP8v1PysiPkO6b93TwPFKj5xoSNKLASJiVUQ8M5yN5miQdJSku0iBYhvgWUm/l/StJvnXIT0K5aK6QXcDN9P4v1Q7IvtuNTEHs+WkwFOvh7R9XNlg+Kz83tuojJ02XoLRAHnlnAf8HJhdbf6Q9FJJJ0m6KTfrPSPpNknHq/LQKEnvyIewRzWah9JTK5e20RT4XqA30l2lLwXmDHb4K2ljSacqPZHxGUn/I+n1DfJJ0tGSbpD0R0krlJ7eOqtB3g9K+pGk+yU9J+kBSbW9l/q8ayk94XFZnv/Nkg5usa71vgls36D8RwC/An7ZYP4vlvTvkq6V9IikZ/PyXlC/4dTqJts5ko6RdBvpj//RZgWStJmkq3Oz0B6V9D3zMnoi1/vXkj5QN24ALwP+rq6ZaXqby6Wp3CzTK+lVki7Nv+uTSk9+HdDkmdeXz+dl9Kykh5WeqPqXdflqTWN7SvpnSXeTltW7KsvlDKXmoH5JP5H0WtWdL5D0K0n3NlqHJb0rz+PQ4dQ9In5Hug/fRsAWeZqhdD51D6VmvH7yhldNzhlJWlfSx/N//Y95+V0v6djhLLvhkPQu4HTS40AOAR4E3gMsJB15NPImYGPgwgbDvgm8RemBhVVzSNuVRjeb7QW2ljSjUq6t8vwvBW5gdfCp6cnvV9al17ah5yltP5+S9ENJzerSmtL3SBrh/ZV6SPdV++ggeebkPJ+rpM0GlpGOnI4hNbf8MOc7rZJvbdL9m65uMN3dcv5PtFjWv875D8/f983f39Ig76I87BrSn+1Y4F+BJ4FHgRfX5T8HWAV8O+edB9xI2tvZty7vPcC5pBsivo/0JNGnSHtOm9XlPTmX4yrSUzr/HXgiTzuA6S3UuxfoB9Yh/Qmry/eluYz/SAoaAfRUhr8K+D3pSbrH5d9qCelZRT9ssi7cRLq/1r8Ac8n30crl6Kvkn05qElsOvKaSPjdP/3+BjwEfJG0QAvhCJd8hpI3L7flz7bXhEMsj8rq2eYNX/fLvI93z7PfAqcAH8vufgB/V5d0YuBVYkX/TuXmdeTCX82UN/hM35XHm53r+DenI/Zd5+DeBo/M8H81lqS7DY3O+vRrU8wd5XdlgiOVRK8s769LXI21Un69NI+e7JdfxS6T1931106muP+uSNqS1Zf7RXObTSDfRbXvZDbGe9zUZtoT0H3hR7XdtYXr/RbpnnOrWh1vyuvIccEKD7dG+wFep+38CR+W091XSDsppLwE+Tzpir5bhTtID+Naqq+fDpPNB5+T14/+RWj9uByY1qMv0PJ9PDVrnoRbKWH7RWjB6Xc7znUraBtUfuZJ+NmmjvlUl7bN5/B3q8p5O2pC+tMWynppXyMn5+9p5Zf92g7yL8jxPqUs/IKe/v5L29pw2ty7v2qQbbC6rW6EHbCxJd1AO4OOVtFeSNnpXVFewvDz/VL+yD1LvXqA/fz6JFFBrG5dP5JV4MxoHo3WBdRpM899y3l0brAuPAVs2KUdf/vwa0k7GHbzwD7sV6Qjh3AbjfzmvGy+vpPWRjnTbWWdjkFd/Xd6+nP6uuvSv5fRX1ZXvaeDVdXlfBvwBWFRJm5PHv5O8gawM+2Ae9skm6X2VtI1JOzJL6vJuk5fVKYMti7qyHEHayG5J2nG7KKef12DZ7TnIdKrrz8dz2mcb5K9uYFtedkOs531Nhp2Tpz+l9rsOMS3l9fOrDdaHW/Ln7wC/qQxbSNppWZvGwejlOe3cStppwB3581vz8FdU/gsBXNSgni/YVuT0j9F8x2Q6LQSjcdtMV1F7UNxGtYSIeDpqv3o6jN9U0uakvae1gJmV8U8nLcg/90RRetrmu4HLImLI53pI2oC0F/KdiOjPZVhJOkLZT9KmTUb9j7rvP8nvMypph5D26C6StHntBWxCOqqaXs0fEU/lMq2VmyY2JzWTPQlUm9D2I/0pvhQRqyrj3whcPlSdmziD9Du8I3+fA1wcEY82yhzpMdvP5/KuLWlKLm+tJ+KAJkvgrIho+kwcSXsCPyX9sXePF3YgeSdpj/wb1WWZ5/ld0rqxByN3LfDmBq99GuRdHhFL6tJq68F2uU4idQL5KXB/XbmfIh1hv6XBtE+NFz4qAODvSYHky3Xpp5PWkT+LiCdJJ9H3y/OqOYK0rOpPlg/mDNIe94OkxxO8jfQY7/fV5ftVRLTaE/Vg4HHgM/UDIp9bGsGya8d/kVoGfqHUvXk9SdMGyb8b6WjlokHynEF6dM3uefvybtK637BbeUTcTWox6Kkk95BaPSCdzlhVGV57v7LB5P5E6nBS1Wjb1Jbx0ptuMLUg9OenlyqdEJ1POuG3HQOfZDil9iEilil1wz5U0vy8cXwXqdfe1yvT3AKYVJnGqlh9MvqdpL3IqyRtV8nzU1IT4SEM/HEhNan9WUQ8mv47bFZJ3j6X5cEG49dMJT3iAklvIjVhvZ6BvdemVD7X2srvaDC92xjGHzQibpX0C+AISfeSVtzjBhtH0gdJzVM7MvAc55SBY6R6NjGV1OvyNmCPBhvi7fP7YBu7qYMMa9UjbWxQ72mQVgvetfVgi/z5LaQNeiONTuw3WlbbkgJgfzUxIp6XtIyBy3wh6Tk+hwAn5437EcBN0d4Tdz8D/CyXcwVpj31Fi2VuZkYuxzOD5BnusmtZRPxc6Zz1PFKT9BTSc6puJx211ffAezspiPYOMtkfkI6ejiD9VzciNasO5krgMKWu4n8gnS/6dC7jCkm/JJ03WsjqYNSoDMsbLNP6dbJtEyEY/VV+r3aP/BLwD6RzLCeyum36daS20/qN3kLSHuC+pMPjI0mHxJdW8vyCdFhf81vSUQmsPqpqtqf4XhoEo+oRSR3VfX6YdEK0mVsAJP018CNSe+98UhPe06Qjv8W8sN61ecQQ82/XGcAp+fP9uTwNSfoIqWnvR6Tls5zUVr41qSmz0ZF9fYCpeox0vmtv0t7w6fWzzO+Hkf7ojTQKDmtSs3UAVpe39v5j0vrbqkbLqq3fNiL+V9ItpHX8ZNKR43TSuZl23NxigB7s922k0fpbNdxl114hIq4hNbMjaTmpWfojwNmSnoqICyvZ3w5c2uwoJ09vlaSzSM2nOwLXRMTtQxTjStK63cPqnfOrKsOvIv0vyHkeJT1Hq14r62TbJkIwqgWCauA4FPhpRBxYzVh31FJ1MSlgHZn/eLsDn69bWQ4mnYuqeTpP8+XAG0nPk7+owbT3AD4gaZc29yRr7iLt4VxTvzfbwHtIR29vjYhltcTc7Fi/x3t3ft+egRvg7Rm+80g7A3uQ9goHW7EPJTWnvTUqXXaVnkg5HM+Tmgi/DZwmaZ2IOKUy/K783uqRy1AbutHyMKmzwEZtHHE1swzYU9Lk6vqk1NV42zyfeqcDX5a0K+n/9gxpfS/tN6QenOtFxLNN8nRy2bXquYg4V9IlpBaNg8i95iTtRGqtOb6F6ZyR8+1G6nQxlFqT2yxSk+vdEXF/ZfhVwDylXrivAC6onc4YDeP2nJHSdRNfJD3J9PsR8T+Vwauoi+B5g9zwSuHcNLcI2IvUywbqjnIi4n8i4seVV21+72X1uZfz61/Agkq+4TiL9Dt+rtFApQvhamob/vq9l08wcF24hLSx/Yhe2N39daRrt4Yln2f4AKl54LQhsq/KZfhzeStNrMOdf62Z9Xzga5KqzYRLSB0qPp3b4V8gn2OrXvneT7oAsagcqL8F7CrpnY3ySNqyxcl9l7TDUt98+j5SU3MjZ5MC0MdIe/XfiTFwkTlpmUwB/ql+QG5O7PSya0jpYvdGJpH+d9Umr7eTdmR/ONR0I+I3pN/p06QdrKHy/5a0s/F3vPB8UU2tmfRT+XvvUNPspPFyZPQ6SYfkz9U7MLyM1MRT34R1PvB+Sd8mHZ5PJQWDhifSs9NJf7aDgKsi4q5B8gIpIJLa0/vyif8BIuK3km4A3iNp3hDt243GP1/SN4Fjc6D4Hum2JNNIXXW3Y/X5nwtJAff7khaSmrzeTGrKfKRuundI+hqpueUnkr5D6ul0LKnDw2vbKWfdtM9qMev5pCB7maQLSO3i7yEd4QxbRKyUdFCezsmS1o6IkyLiPklHk84F3i7pbFJz6xbAzqR1agfS0Rqkk9tHSvo3UrfWP5GuVn9qiCJsXVlf612dTza365OkI/Ylkpbksj1H+g+8jXQdyZwWpvN14P3Av+eWgutI68e7SM27A7YZEfG4pPNJ541q0xgLvkzqkPFPlSbqZ0jNWq9k9U5Vp5ZdM0uUron6Hunoe31J7yX9l9YlddSoeTtweQvrEAAR0ehc82CuJG3rXkI6RVGd1hOSfk1qyanlHT2DdbUb6y9Wd+etvVaRDj9vJf3As5uM9yLSbWl+S1o57yLtbde6OM9pMt4VefihLZZv75z/pCHynZDzvSd/X5R+moZ5gwZdTUlNWj8jtQU/Q9pgXgC8uy7f/qQ/11OkALQY+AsadFMm7bV9Mi+nZ0nnng4m7Tm9oOvoIHXrpa7LcpN8jbp2T8rLZmme/29J1zRsT11X0cq60Oy366WuS22u36I8XvWajd1Jgfsh0kZpOemPOQ9Yv5JvS9I5xMdosbt73fra6HVUJe+A32Swuub1+p9JV+g/TeoIcDtpR+r1lXxz6pd13XS2yMvlsbye/ITUHf564LYm47whT/MuGlw2McjyqJXlnS3kbbjuD1YnUiedT5K2Cc+QmuR+AXxwOMtuiPW8r8mwHlJLyp15ukHa8b2KyjaKFPwGW4f7yF27hyjLgK7dlWGHVNa1lzUYfnIe9nCj37FZPRmk+/Zgw6ov5czWAknfJx1tvDQini5dHrPRko/yHwGujYgB5+zy+aJrSReBN2wyHs8k9ZJaIF5H2pFs2soiqS8ipjdIP47UYWfqYON3i9ykvgnpurMbgU9HxKea5R8vzXRrXG6y2Av4mgORjWeSNmiwjn+AtGFpdo3ZsaRmz6G6F49n25COKFYxvG3rA8A/jodAlM0Erm41s4PREJTupbY96fqA50g9wczGs9MlrU+6JdKzpNaA95CaSxfWMuVOP39POgdzCLAwIn4/+sUdE+axukfqUNclndwoMQZe3NztbiOdk64Z9LIIN9MNQdIiUt/8e0i3wOj43aXNxhKl5wgdQ+reO5nU/fj7wD9HxIOVfNNJvbP6SXdlPyoi/jBggmYtcDAyM7Pixu11RmZm1j0cjMzMrDgHIzMzK87ByMzMinMwMjOz4v4/Cabnu02zU7sAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.hist(ca_data[\"price\"])\n", "plt.xlabel('Day-Ahead Market Energy Price [$/MWh]',fontsize=18)\n", "plt.ylabel('Count',fontsize=18)\n", "plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finaly, let's visualize the prices during the first full calendar week. The data are for calendar year 2015. For reference, January 1, 2015 was a Thursday." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "offset = 4 # days\n", "number_of_days = 7\n", "first_week = ca_data[\"price\"].to_numpy()[(0 + offset)*24: (0 + offset + number_of_days)*24]\n", "\n", "# Customize the major and minor ticks on the plots\n", "from matplotlib.ticker import MultipleLocator\n", "fig, ax = plt.subplots()\n", "\n", "# Plot data\n", "ax.plot(range(0,number_of_days*24), first_week)\n", "\n", "# Set major ticks every 24 hours (1 day)\n", "ax.xaxis.set_major_locator(MultipleLocator(24))\n", "\n", "# Set minor ticks every 6 hours\n", "ax.xaxis.set_minor_locator(MultipleLocator(6))\n", "\n", "# Set labels, add grid\n", "plt.xlabel('Hour',fontsize=18)\n", "plt.ylabel('DAM Energy Price [$/MWh]',fontsize=18)\n", "plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " What are 1 or 2 interesting observations from these plots?\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Optimization Mathematical Model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Energy (price) arbitrage* is the idea of using energy storage (e.g., a battery) to take advantage of the significant daily energy price swings. This gives rise to many analysis questions including:\n", "\n", "*If a battery energy storage system perfectly timed it's energy purchases and sales (i.e., it could perfectly forecast the market price), how much money could it make from energy arbitrage?*\n", "\n", "We can answer this question using mathematical/computational optimization!\n", "\n", "Let's start by drawing a picture." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![battery-optimization](https://ndcbe.github.io/optimization/_images/battery.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Sets\n", "Let's say we want to define our optimization problem over a 24 hour window. The day-ahead market sets the energy prices in 1-hour intervals. We'll define the set\n", "\n", "$$\\mathcal{T} = \\{0, 1, ..., N\\}$$\n", "\n", "for time where $N = 24$ for a 24-hour planning horizon. For convienence, we'll also define $\\mathcal{T}' := \\mathcal{T} / \\{0\\}$, which is the original set $\\mathcal{T}$ substract subset $\\{0\\}$.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Variables\n", "\n", "Next, let's identify the variables in the optimization problem:\n", "* $E_t$, energy stored in battery at time $t$, units: MWh\n", "* $d_t$, battery discharge power (sold to market) during time interval [t-1, t), units: MW\n", "* $c_t$, battery charge power (purchased from the market) during time interval [t-1, t), units: MW\n", "\n", "Notice how all of these variables are indexed by the timestep $t$. We'll write in the model $t \\in \\mathcal{T}'$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Parameters\n", "\n", "Parameters are data that are constant during the optimization problem. Here we have:\n", "* $\\pi_t$: Energy price during time interval [t-1, t), units: \\$/MW\n", "* $\\eta$: Round trip efficiency, units: dimensionless\n", "* $c_{max}$ Maximum charge power, units: MW\n", "* $d_{max}$ Maximum discharge power, units: MW\n", "* $E_{max}$ Maximum storage energy, units: MWh\n", "* $E_{0}$ Energy in storage at time $t=0$, units: MWh\n", "* $\\Delta t = 1$ hour, Timestep for grid decisions and prices (fixed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Objective and Constraints" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we'll identify the objective, which is the function to improve, and the mathematical constraints. Below is the full mathematical model for the problem:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{align*}\n", " \\max_{\\mathbf{E},\\mathbf{d},\\mathbf{c}} \\quad & \\psi := \\sum_{t \\in \\mathcal{T}'} \\pi_{t} \\Delta t (d_{t} - c_{t}) \\\\\n", "\\mathrm{s.t.} \\quad & E_{t} = E_{t-1} + \\Delta t \\left( c_{t} \\sqrt{\\eta} - \\frac{d_{t}}{\\sqrt{\\eta}} \\right), ~~ \\forall ~ t \\in \\mathcal{T}' \\\\\n", " & E_{0} = E_{N} \\\\\n", " & 0 \\leq c_{t} \\leq c_{max}, ~~\\forall ~ t \\in \\mathcal{T}' \\\\\n", " & 0 \\leq d_{t} \\leq d_{max}, ~~\\forall ~ t \\in \\mathcal{T}' \\\\\n", " & 0 \\leq E_{t} \\leq E_{max}, ~~\\forall ~ t \\in \\mathcal{T}'\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Write on paper a 1-sentence description for each equation.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Degree of Freedom Analysis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before we program our model in Pyomo, it is *very important* to first perform a degree of freedom analysis. Here are the steps:\n", "* Count the number of variables\n", "* Count the number of equality constraints\n", "* Degrees of freedom = number of variables subtract number of equality constraints\n", "\n", "The degrees of freedom are the number of decisions variables that be freely manipulated by the optimizer. If there are no degrees of freedom, we ofter say the problem is square or it is a simulation problem.\n", "\n", "For now, we will ignore inequality constraints and bounds. Later in the semester we will revisit degree of freedom analysis using some optimization theory concepts (e.g., active sets)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Perform degree of freedom analysis.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Pyomo Modeling Components" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Important: Do NOT implement an optimization model in Pyomo (or any other software) until you have written it on paper and performed degree of freedom analysis, as done above. Be sure to resolve any doubts, questions, or concerns while your model is still on paper. When applying optimization to a problem, a majority of the mistakes happen at the problem formulation step. So do not rush it!\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Create `ConcreteModel`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will start by creating a concrete Pyomo model. Recall, Pyomo is an object-oriented algebriac modeling language. The line below creates an instance of the ConcreteModel class." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "m = pyo.ConcreteModel()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For those unfamilar with object-oriented programming, `m` is a container to define an optimization model. It includes a bunch of functionality to interface with different optimization solvers, perform diagnostics, and inspect the solution.\n", "\n", "Pyomo also supports abstract models, but we will stick with concrete models this semester. See the Pyomo textbook for more details if you are curious." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We start by declaring a set for time. From above, recall we want to index all of the variables and constraints over the set\n", "\n", "$$\n", "\\mathcal{T}' = \\mathcal{T} / \\{0\\} = \\{1, ..., N\\}\n", "$$" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# Save the number of timesteps\n", "m.N = 24\n", "\n", "# Define the horizon set\n", "m.HORIZON = pyo.Set(initialize=range(1,m.N+1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some Pyomo modelers prefer to use all capital names for sets; this is a personal preference." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Variables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we can declare our three variables: $E_t$, $c_t$, $d_t$" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "# Charging rate [MW]\n", "m.c = pyo.Var(m.HORIZON, initialize = 0.0, bounds=(0, 1), domain=pyo.NonNegativeReals)\n", "\n", "# Discharging rate [MW]\n", "m.d = pyo.Var(m.HORIZON, initialize = 0.0, bounds=(0, 1), domain=pyo.NonNegativeReals)\n", "\n", "# Energy (state-of-charge) [MWh]\n", "m.E = pyo.Var(m.HORIZON, initialize = 0.0, bounds=(0, 4), domain=pyo.NonNegativeReals)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "See the table below (Hart et al., 2017) for an explanation of the arguments for `Var`:\n", " \n", "![pyomo-var-arguments](https://ndcbe.github.io/optimization/_images/pyomo-table-4.1.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pyomo also supports units. Even though we are not explicitly using the feature, the units for all variables are clearly marked in the comments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following table (Hart et al., 2017) shows the options for the `within`/`domain` variable keyword:\n", "\n", "![pyomo-var-domain](https://ndcbe.github.io/optimization/_images/pyomo-table-4.2.png)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above, `domain=pyo.NonNegativeReals` is not needs, as we are specifying stricker bounds. It is included above to show the syntax." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Parameters (Constants / Data)\n", "\n", "The next step is to define the parameter data: $\\pi_t$ (energy prices), $\\eta$ (round trip efficiency) and $E_0$ (intial energy storage level)." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Square root of round trip efficiency\n", "m.sqrteta = pyo.Param(initialize = pyo.sqrt(0.88))\n", "\n", "# Energy in battery at t=0\n", "m.E0 = pyo.Param(initialize = 2.0, mutable=True)\n", "\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 Set Declarations\n", " HORIZON : Size=1, Index=None, Ordered=Insertion\n", " Key : Dimen : Domain : Size : Members\n", " None : 1 : Any : 24 : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}\n", "\n", "2 Param Declarations\n", " E0 : Size=1, Index=None, Domain=Any, Default=None, Mutable=True\n", " Key : Value\n", " None : 2.0\n", " sqrteta : Size=1, Index=None, Domain=Any, Default=None, Mutable=False\n", " Key : Value\n", " None : 0.938083151964686\n", "\n", "3 Var Declarations\n", " E : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " c : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " d : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", "\n", "6 Declarations: HORIZON c d E sqrteta E0\n" ] } ], "source": [ "m.pprint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see the `initialize` keyword is used to set the parameter value. When `mutable=True`, Pyomo builds the model such that we can easily update the parameter and resolved. Later in the notebook, we will see how this is helpful.\n", "\n", "Below is a table (Hart et al., 2017) of options for `Param`:\n", "\n", " \n", "![pyomo-param-arguments](https://ndcbe.github.io/optimization/_images/pyomo-table-4.6.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's dig in more to the `initialize` syntax. First, let's convert the price data from pandas into a numpy array:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "len(my_np_array) = 8760\n" ] } ], "source": [ "my_np_array = ca_data[\"price\"].to_numpy()\n", "\n", "# get the length\n", "print(\"len(my_np_array) =\",len(my_np_array))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recall, our dataset constains an entire year (which has 8760 hours). To access the first 24 hours, we use the following slice:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([36.757, 34.924, 33.389, 32.035, 33.694, 36.88 , 38.662, 38.975,\n", " 35.08 , 29.979, 27.546, 25.944, 24.587, 23.788, 25.236, 30.145,\n", " 44.622, 50.957, 59.345, 52.564, 52.819, 48.816, 46.685, 38.575])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_np_array[0:24]" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "24" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(my_np_array[0:24])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Initializing parameters in Pyomo can be precarious. The most fool proof strategy is to prepare a dictionary where the keys match the elements of the sets that index the parameter of interest. In our example, `m.HORIZON` contains 1, ..., 24, so we need a dictionary with the keys 1, ..., 24." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{0: 36.757,\n", " 1: 34.924,\n", " 2: 33.389,\n", " 3: 32.035,\n", " 4: 33.694,\n", " 5: 36.88,\n", " 6: 38.662,\n", " 7: 38.975,\n", " 8: 35.08,\n", " 9: 29.979,\n", " 10: 27.546,\n", " 11: 25.944,\n", " 12: 24.587,\n", " 13: 23.788,\n", " 14: 25.236,\n", " 15: 30.145,\n", " 16: 44.622,\n", " 17: 50.957,\n", " 18: 59.345,\n", " 19: 52.564,\n", " 20: 52.819,\n", " 21: 48.816,\n", " 22: 46.685,\n", " 23: 38.575}" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ca_data[\"price\"][0:24].to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That was easy. But what if we wanted to build the optimization model using the second day of data? Let's give it a try:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{24: 37.239,\n", " 25: 34.766,\n", " 26: 34.645,\n", " 27: 33.21,\n", " 28: 35.524,\n", " 29: 44.143,\n", " 30: 39.231,\n", " 31: 41.251,\n", " 32: 36.406,\n", " 33: 31.194,\n", " 34: 29.695,\n", " 35: 27.034,\n", " 36: 26.009,\n", " 37: 24.829,\n", " 38: 26.168,\n", " 39: 29.921,\n", " 40: 44.137,\n", " 41: 51.751,\n", " 42: 51.652,\n", " 43: 46.675,\n", " 44: 45.274,\n", " 45: 44.053,\n", " 46: 46.779,\n", " 47: 37.307}" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ca_data[\"price\"][24:48].to_dict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Uncomment the line below and look at the error message and read below. Then comment the line out again and rerun the notebook up to this cell.\n", "
" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# m.price = pyo.Param(m.HORIZON, initialize = ca_data[\"price\"][24:48].to_dict(), domain=pyo.Reals)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should get the following error:\n", "\n", "```\n", "ERROR: Constructing component 'price' from data=None failed: KeyError: \"Index\n", " '25' is not valid for indexed component 'price'\"\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why did this happen? Our dictionary started with key 25 but we tried to create a `Param` indexed over 1 through 24." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's say we want to build the optimization model starting for an arbitrary day. We need to extract the correct data from the pandas DataFrame and convert it to a dictionary with the correct keys. The function below does this using a simple, easy to follow approach. There is more compact \"Pythonic\" syntax to do this, but we will skip it for this getting started tutorial." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{1: 37.239, 2: 34.766, 3: 34.645, 4: 33.21, 5: 35.524, 6: 44.143, 7: 39.231, 8: 41.251, 9: 36.406, 10: 31.194, 11: 29.695, 12: 27.034, 13: 26.009, 14: 24.829, 15: 26.168, 16: 29.921, 17: 44.137, 18: 51.751, 19: 51.652, 20: 46.675, 21: 45.274, 22: 44.053, 23: 46.779, 24: 37.307}\n" ] } ], "source": [ "def prepare_price_data(day):\n", " ''' Prepare dictionary of price data\n", " \n", " Arugments:\n", " day: int, day to start. day = 0 is the first day\n", " \n", " Returns:\n", " data_dict: dictionary of price data with keys 1 to 24\n", " \n", " Notes:\n", " This function assumes the pandas DataFrame ca_data is in scope.\n", " \n", " '''\n", " \n", " # Create empty dictionary\n", " data_dict = {}\n", " \n", " # Extract data as numpy array\n", " data_np_array = ca_data[\"price\"][(day)*24:24*(day+1)].to_numpy()\n", " \n", " # Loop over elements of numpy array\n", " for i in range(0,24):\n", " \n", " # Add element to data_dict\n", " data_dict[i + 1] = data_np_array[i]\n", " \n", " return data_dict\n", "\n", "# Create input data for day 1 (i.e., January 2, 2015)\n", "my_data_dict = prepare_price_data(1)\n", "print(my_data_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Confirm my_data_dict contains the correct prices for the January 2, 2015.\n", "
" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([37.239, 34.766, 34.645, 33.21 , 35.524, 44.143, 39.231, 41.251,\n", " 36.406, 31.194, 29.695, 27.034, 26.009, 24.829, 26.168, 29.921,\n", " 44.137, 51.751, 51.652, 46.675, 45.274, 44.053, 46.779, 37.307])" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Add your solution here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we are ready to define the price data parameter:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "m.price = pyo.Param(m.HORIZON, initialize = my_data_dict, domain=pyo.Reals, mutable=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Tip: When initializing variables and parameters with a pandas DataFrame, always convert to dictionary and check the keys. Often incorrectly loaded data is the root cause of unexpected errors or strange results. Checking the keys while building the model helps prevent these mistakes. \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Objectives" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will declare the objective function in Pyomo. Below are two equally valid syntax optimizations." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# Approach 1: Define a function and use *rule=*\n", "def objfun(model):\n", " return sum((-model.c[t] + model.d[t]) * model.price[t] for t in model.HORIZON)\n", "m.OBJ = pyo.Objective(rule = objfun, sense = pyo.maximize)\n", "\n", "# Approach 2: Use *expr=*\n", "# m.OBJ = pyo.Objective(expr = sum((-m.c[t] + m.d[t]) * m.price[t] for t in model.HORIZON), \n", "# sense = pyo.maximize)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Comment out Approach 2 above and rerun the notebook. You'll get a warning message if you do not comment out Approach 1. The answer should not change.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following table (Hart et al., 2017) summarizes the keyword arguments for `Objective`:\n", "\n", "![pyomo-objective-arguments](https://ndcbe.github.io/optimization/_images/pyomo-table-4.3.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Constraints" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's add the last model component: the constraints." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "# Define Energy Balance constraints. [MWh] = [MW]*[1 hr]\n", "# Note: this model assumes 1-hour timestep in price data and control actions.\n", "def EnergyBalance(model,t):\n", " # First timestep\n", " if t == 1 :\n", " return model.E[t] == model.E0 + model.c[t]*model.sqrteta-model.d[t]/model.sqrteta \n", "\n", " # Subsequent timesteps\n", " else :\n", " return model.E[t] == model.E[t-1]+model.c[t]*model.sqrteta-model.d[t]/model.sqrteta\n", "\n", "m.EnergyBalance_Con = pyo.Constraint(m.HORIZON, rule = EnergyBalance)\n", "\n", "# Enforce the amount of energy is the storage at the final time must equal\n", "# the initial time.\n", "# [MWh] = [MWh]\n", "m.PeriodicBoundaryCondition = pyo.Constraint(expr=m.E0 == m.E[m.N])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the above model includes detailed comments with the units for the left-hand side and right-hand side of each equation. This is strongly recommend; it is easy for units mistakes to go undetected in a complicated Pyomo model. Alternately, you can also use the units feature in Pyomo.\n", "\n", "We see in this example a big advantage of the `rule=` approach for defininng a constraint. Inside the function `EnergyBalance` we incorporate a logical statement for how to handle the first timestep (which uses parameter `E0`).\n", "\n", "Below is a table of keyword arguments for `Constraint`:\n", "\n", "![pyomo-constraint-arguments](https://ndcbe.github.io/optimization/_images/pyomo-table-4.4.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Compare the two model equations above to the optimization formulation below. Notice the one-to-one corespondance between the equality constraints in the mathematical formulation (below) and calls to pyo.Constraint. Also notice the sets used to create the Pyomo model are listed next to each constraint in the mathematical model. Once you learn the Pyomo syntax, translated a mathematical model into code is easy! \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Printing the Model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is the optimization model, reproduced from above for convenience:\n", "\n", "$$\n", "\\begin{align*}\n", " \\max_{\\mathbf{E},\\mathbf{d},\\mathbf{c}} \\quad & \\psi := \\sum_{t \\in \\mathcal{T}'} \\pi_{t} \\Delta t (d_{t} - c_{t}) \\\\\n", "\\mathrm{s.t.} \\quad & E_{t} = E_{t-1} + \\Delta t \\left( c_{t} \\sqrt{\\eta} - \\frac{d_{t}}{\\sqrt{\\eta}} \\right), ~~ \\forall ~ t \\in \\mathcal{T}' \\\\\n", " & E_{0} = E_{N} \\\\\n", " & 0 \\leq c_{t} \\leq c_{max}, ~~\\forall ~ t \\in \\mathcal{T}' \\\\\n", " & 0 \\leq d_{t} \\leq d_{max}, ~~\\forall ~ t \\in \\mathcal{T}' \\\\\n", " & 0 \\leq E_{t} \\leq E_{max}, ~~\\forall ~ t \\in \\mathcal{T}'\n", "\\end{align*}\n", "$$\n", "\n", "Now let's see if our Pyomo model matches the optimization formulation. We will use the `pprint()` command (pretty print) to inspect the full model." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 Set Declarations\n", " HORIZON : Size=1, Index=None, Ordered=Insertion\n", " Key : Dimen : Domain : Size : Members\n", " None : 1 : Any : 24 : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}\n", "\n", "3 Param Declarations\n", " E0 : Size=1, Index=None, Domain=Any, Default=None, Mutable=True\n", " Key : Value\n", " None : 2.0\n", " price : Size=24, Index=HORIZON, Domain=Reals, Default=None, Mutable=True\n", " Key : Value\n", " 1 : 37.239\n", " 2 : 34.766\n", " 3 : 34.645\n", " 4 : 33.21\n", " 5 : 35.524\n", " 6 : 44.143\n", " 7 : 39.231\n", " 8 : 41.251\n", " 9 : 36.406\n", " 10 : 31.194\n", " 11 : 29.695\n", " 12 : 27.034\n", " 13 : 26.009\n", " 14 : 24.829\n", " 15 : 26.168\n", " 16 : 29.921\n", " 17 : 44.137\n", " 18 : 51.751\n", " 19 : 51.652\n", " 20 : 46.675\n", " 21 : 45.274\n", " 22 : 44.053\n", " 23 : 46.779\n", " 24 : 37.307\n", " sqrteta : Size=1, Index=None, Domain=Any, Default=None, Mutable=False\n", " Key : Value\n", " None : 0.938083151964686\n", "\n", "3 Var Declarations\n", " E : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " c : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " d : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", "\n", "1 Objective Declarations\n", " OBJ : Size=1, Index=None, Active=True\n", " Key : Active : Sense : Expression\n", " None : True : maximize : (- c[1] + d[1])*price[1] + (- c[2] + d[2])*price[2] + (- c[3] + d[3])*price[3] + (- c[4] + d[4])*price[4] + (- c[5] + d[5])*price[5] + (- c[6] + d[6])*price[6] + (- c[7] + d[7])*price[7] + (- c[8] + d[8])*price[8] + (- c[9] + d[9])*price[9] + (- c[10] + d[10])*price[10] + (- c[11] + d[11])*price[11] + (- c[12] + d[12])*price[12] + (- c[13] + d[13])*price[13] + (- c[14] + d[14])*price[14] + (- c[15] + d[15])*price[15] + (- c[16] + d[16])*price[16] + (- c[17] + d[17])*price[17] + (- c[18] + d[18])*price[18] + (- c[19] + d[19])*price[19] + (- c[20] + d[20])*price[20] + (- c[21] + d[21])*price[21] + (- c[22] + d[22])*price[22] + (- c[23] + d[23])*price[23] + (- c[24] + d[24])*price[24]\n", "\n", "2 Constraint Declarations\n", " EnergyBalance_Con : Size=24, Index=HORIZON, Active=True\n", " Key : Lower : Body : Upper : Active\n", " 1 : 0.0 : E[1] - (E0 + 0.938083151964686*c[1] - 1.0660035817780522*d[1]) : 0.0 : True\n", " 2 : 0.0 : E[2] - (E[1] + 0.938083151964686*c[2] - 1.0660035817780522*d[2]) : 0.0 : True\n", " 3 : 0.0 : E[3] - (E[2] + 0.938083151964686*c[3] - 1.0660035817780522*d[3]) : 0.0 : True\n", " 4 : 0.0 : E[4] - (E[3] + 0.938083151964686*c[4] - 1.0660035817780522*d[4]) : 0.0 : True\n", " 5 : 0.0 : E[5] - (E[4] + 0.938083151964686*c[5] - 1.0660035817780522*d[5]) : 0.0 : True\n", " 6 : 0.0 : E[6] - (E[5] + 0.938083151964686*c[6] - 1.0660035817780522*d[6]) : 0.0 : True\n", " 7 : 0.0 : E[7] - (E[6] + 0.938083151964686*c[7] - 1.0660035817780522*d[7]) : 0.0 : True\n", " 8 : 0.0 : E[8] - (E[7] + 0.938083151964686*c[8] - 1.0660035817780522*d[8]) : 0.0 : True\n", " 9 : 0.0 : E[9] - (E[8] + 0.938083151964686*c[9] - 1.0660035817780522*d[9]) : 0.0 : True\n", " 10 : 0.0 : E[10] - (E[9] + 0.938083151964686*c[10] - 1.0660035817780522*d[10]) : 0.0 : True\n", " 11 : 0.0 : E[11] - (E[10] + 0.938083151964686*c[11] - 1.0660035817780522*d[11]) : 0.0 : True\n", " 12 : 0.0 : E[12] - (E[11] + 0.938083151964686*c[12] - 1.0660035817780522*d[12]) : 0.0 : True\n", " 13 : 0.0 : E[13] - (E[12] + 0.938083151964686*c[13] - 1.0660035817780522*d[13]) : 0.0 : True\n", " 14 : 0.0 : E[14] - (E[13] + 0.938083151964686*c[14] - 1.0660035817780522*d[14]) : 0.0 : True\n", " 15 : 0.0 : E[15] - (E[14] + 0.938083151964686*c[15] - 1.0660035817780522*d[15]) : 0.0 : True\n", " 16 : 0.0 : E[16] - (E[15] + 0.938083151964686*c[16] - 1.0660035817780522*d[16]) : 0.0 : True\n", " 17 : 0.0 : E[17] - (E[16] + 0.938083151964686*c[17] - 1.0660035817780522*d[17]) : 0.0 : True\n", " 18 : 0.0 : E[18] - (E[17] + 0.938083151964686*c[18] - 1.0660035817780522*d[18]) : 0.0 : True\n", " 19 : 0.0 : E[19] - (E[18] + 0.938083151964686*c[19] - 1.0660035817780522*d[19]) : 0.0 : True\n", " 20 : 0.0 : E[20] - (E[19] + 0.938083151964686*c[20] - 1.0660035817780522*d[20]) : 0.0 : True\n", " 21 : 0.0 : E[21] - (E[20] + 0.938083151964686*c[21] - 1.0660035817780522*d[21]) : 0.0 : True\n", " 22 : 0.0 : E[22] - (E[21] + 0.938083151964686*c[22] - 1.0660035817780522*d[22]) : 0.0 : True\n", " 23 : 0.0 : E[23] - (E[22] + 0.938083151964686*c[23] - 1.0660035817780522*d[23]) : 0.0 : True\n", " 24 : 0.0 : E[24] - (E[23] + 0.938083151964686*c[24] - 1.0660035817780522*d[24]) : 0.0 : True\n", " PeriodicBoundaryCondition : Size=1, Index=None, Active=True\n", " Key : Lower : Body : Upper : Active\n", " None : E0 : E[24] : E0 : True\n", "\n", "10 Declarations: HORIZON c d E sqrteta E0 price OBJ EnergyBalance_Con PeriodicBoundaryCondition\n" ] } ], "source": [ "m.pprint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Does our Pyomo model match the optimization mathematical model (equations above)? How did we incorporate the inequality constraints into the Pyomo model?\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Another Approach: Build the Model in a Function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To emphasize the tutorial nature of this example, we build the model on piece at a time above. An often preferred approach is to define a Python function that builds the model, such as the one below." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " The function below uses elements of price directly. Update the function to add the price data as a parameter in the Pyomo model. Make this parameter mutable as shown above.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Fix any syntax errors with the function below.\n", "
" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "# define a function to build model\n", "def build_model(price,e0 = 0):\n", " '''\n", " Create optimization model for MPC\n", "\n", " Arguments (inputs):\n", " price: NumPy array with energy price timeseries\n", " e0: initial value for energy storage level\n", " \n", " Returns (outputs):\n", " my_model: Pyomo optimization model\n", " '''\n", " \n", " # Create a concrete Pyomo model. We'll learn more about this in a few weeks\n", " my_model = pyo.ConcreteModel()\n", "\n", " ## Define Sets\n", "\n", " # Number of timesteps in planning horizon\n", " my_model.HORIZON = pyo.Set(initialize = range(len(price)))\n", "\n", " ## Define Parameters\n", "\n", " # Square root of round trip efficiency\n", " my_model.sqrteta = pyo.Param(initialize = sqrt(0.88))\n", "\n", " # Energy in battery at t=0\n", " my_model.E0 = pyo.Param(initialize = e0, mutable=True)\n", "\n", " ## Define variables\n", " \n", " # Charging rate [MW]\n", " my_model.c = pyo.Var(my_model.HORIZON, initialize = 0.0, bounds=(0, 1))\n", "\n", " # Discharging rate [MW]\n", " my_model.d = pyo.Var(my_model.HORIZON, initialize = 0.0, bounds=(0, 1))\n", "\n", " # Energy (state-of-charge) [MWh]\n", " my_model.E = pyo.Var(my_model.HORIZON, initialize = 0.0, bounds=(0, 4))\n", "\n", " ## Define constraints\n", " \n", " # Define Energy Balance constraints. [MWh] = [MW]*[1 hr]\n", " # Note: this model assumes 1-hour timestep in price data and control actions.\n", " def EnergyBalance(model,t):\n", " # First timestep\n", " if t == 0 :\n", " return model.E[t] == model.E0 + model.c[t]*model.sqrteta-model.d[t]/model.sqrteta \n", " \n", " # Subsequent timesteps\n", " else :\n", " return model.E[t] == model.E[t-1]+model.c[t]*model.sqrteta-model.d[t]/model.sqrteta\n", " \n", " my_model.EnergyBalance_Con = pyo.Constraint(my_model.HORIZON, rule = EnergyBalance)\n", " \n", " # Enforce the amount of energy is the storage at the final time must equal\n", " # the initial time.\n", " # [MWh] = [MWh]\n", " my_model.PeriodicBoundaryCondition = pyo.Constraint(expr=my_model.E0 == my_model.E[len(price)-1])\n", " \n", " ## Define the objective function (profit)\n", " # Receding horizon\n", " def objfun(model):\n", " return sum((-model.c[t] + model.d[t]) * price[t] for t in model.HORIZON)\n", " my_model.OBJ = Objective(rule = objfun, sense = maximize)\n", " \n", " return my_model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calling Optimization Solver" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that our Pyomo model is complete, we can numerically solve the model!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `SolverFactory` and Solver Options" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Algebraic Modeling Languages, including Pyomo, allow us to define optimization problems is a general, solver agnostic way. This means we can quickly swap between solvers.\n", "\n", "We will start by using Ipopt. First, we will create an instance of the `SolverFactory`:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "# Specify the solver\n", "solver = pyo.SolverFactory('ipopt')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we can specify options for `ipopt` such as setting the maximum number of iterations to 50:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "solver.options['max_iter'] = 50" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above `solver` is a `SolverFactory` objection which includes the dictionary `options` used to set solver specific options.\n", "\n", "Finally, we are ready to solve our model!" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ipopt 3.13.2: max_iter=50\n", "\n", "\n", "******************************************************************************\n", "This program contains Ipopt, a library for large-scale nonlinear optimization.\n", " Ipopt is released as open source code under the Eclipse Public License (EPL).\n", " For more information visit http://projects.coin-or.org/Ipopt\n", "******************************************************************************\n", "\n", "This is Ipopt version 3.13.2, running with linear solver ma27.\n", "\n", "Number of nonzeros in equality constraint Jacobian...: 96\n", "Number of nonzeros in inequality constraint Jacobian.: 0\n", "Number of nonzeros in Lagrangian Hessian.............: 0\n", "\n", "Total number of variables............................: 72\n", " variables with only lower bounds: 0\n", " variables with lower and upper bounds: 72\n", " variables with only upper bounds: 0\n", "Total number of equality constraints.................: 25\n", "Total number of inequality constraints...............: 0\n", " inequality constraints with only lower bounds: 0\n", " inequality constraints with lower and upper bounds: 0\n", " inequality constraints with only upper bounds: 0\n", "\n", "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", " 0 4.9960036e-16 1.99e+00 9.90e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", " 1 1.6802497e-01 1.96e+00 9.85e+00 -1.0 1.99e+00 - 5.21e-03 1.44e-02f 1\n", " 2 2.1332988e+00 1.75e+00 9.89e+00 -1.0 1.96e+00 - 1.53e-02 1.09e-01f 1\n", " 3 2.8652446e+00 1.35e+00 8.39e+00 -1.0 2.08e+00 - 1.09e-01 2.30e-01f 1\n", " 4 -4.0482581e+00 1.01e+00 7.83e+00 -1.0 1.86e+00 - 6.95e-02 2.48e-01f 1\n", " 5 -1.9676532e+01 8.93e-01 8.09e+00 -1.0 7.32e+00 - 6.32e-02 1.17e-01f 1\n", " 6 -3.4249188e+01 7.76e-01 7.89e+00 -1.0 6.21e+00 - 7.33e-02 1.32e-01f 1\n", " 7 -4.6657256e+01 6.62e-01 6.69e+00 -1.0 4.81e+00 - 1.52e-01 1.47e-01f 1\n", " 8 -5.7915679e+01 5.23e-01 5.29e+00 -1.0 3.16e+00 - 2.06e-01 2.09e-01f 1\n", " 9 -6.3119259e+01 3.85e-01 3.92e+00 -1.0 1.34e+00 - 2.30e-01 2.65e-01f 1\n", "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", " 10 -6.3706910e+01 3.12e-02 6.94e+00 -1.0 1.08e+00 - 2.85e-01 9.19e-01f 1\n", " 11 -6.5994667e+01 7.15e-03 3.17e+00 -1.0 6.93e-01 - 4.40e-01 7.71e-01f 1\n", " 12 -6.6628205e+01 3.78e-03 7.49e-01 -1.0 9.35e-01 - 1.00e+00 4.72e-01f 1\n", " 13 -6.9888933e+01 5.35e-04 1.10e-01 -1.7 2.22e-01 - 8.88e-01 8.58e-01f 1\n", " 14 -7.1043057e+01 8.75e-05 1.55e-02 -2.5 2.18e-01 - 7.83e-01 8.36e-01f 1\n", " 15 -7.1364922e+01 1.18e-05 3.85e-02 -3.8 2.82e-01 - 6.56e-01 8.65e-01f 1\n", " 16 -7.1428935e+01 4.44e-16 7.22e-03 -3.8 5.71e-02 - 8.66e-01 1.00e+00f 1\n", " 17 -7.1430287e+01 4.44e-16 7.11e-15 -3.8 8.19e-03 - 1.00e+00 1.00e+00f 1\n", " 18 -7.1437864e+01 4.44e-16 6.15e-15 -5.7 1.65e-03 - 1.00e+00 1.00e+00f 1\n", " 19 -7.1437961e+01 8.88e-16 8.30e-15 -8.6 2.85e-05 - 1.00e+00 1.00e+00f 1\n", "\n", "Number of Iterations....: 19\n", "\n", " (scaled) (unscaled)\n", "Objective...............: -7.1437960657726151e+01 -7.1437960657726151e+01\n", "Dual infeasibility......: 8.2967961385731204e-15 8.2967961385731204e-15\n", "Constraint violation....: 8.8817841970012523e-16 8.8817841970012523e-16\n", "Complementarity.........: 3.2570997913605615e-09 3.2570997913605615e-09\n", "Overall NLP error.......: 3.2570997913605615e-09 3.2570997913605615e-09\n", "\n", "\n", "Number of objective function evaluations = 20\n", "Number of objective gradient evaluations = 20\n", "Number of equality constraint evaluations = 20\n", "Number of inequality constraint evaluations = 0\n", "Number of equality constraint Jacobian evaluations = 20\n", "Number of inequality constraint Jacobian evaluations = 0\n", "Number of Lagrangian Hessian evaluations = 19\n", "Total CPU secs in IPOPT (w/o function evaluations) = 0.004\n", "Total CPU secs in NLP function evaluations = 0.000\n", "\n", "EXIT: Optimal Solution Found.\n" ] } ], "source": [ "results = solver.solve(m, tee=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The keyword argument `tee=True` tells the solve to dispaly its output to the screen." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Interpreting Ipopt Output - Verifying Degree of Freedom Analysis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your Ipopt output should include the following:\n", "\n", "```\n", "Number of nonzeros in equality constraint Jacobian...: 96\n", "Number of nonzeros in inequality constraint Jacobian.: 0\n", "Number of nonzeros in Lagrangian Hessian.............: 0\n", "\n", "Total number of variables............................: 72\n", " variables with only lower bounds: 0\n", " variables with lower and upper bounds: 72\n", " variables with only upper bounds: 0\n", "Total number of equality constraints.................: 25\n", "Total number of inequality constraints...............: 0\n", " inequality constraints with only lower bounds: 0\n", " inequality constraints with lower and upper bounds: 0\n", " inequality constraints with only upper bounds: 0\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Compare this output to your degree of freedom analysis.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Try Another Solver" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see how easy it is to switch to another solver with Pyomo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Create a new instance of SolverFactory by specifying 'glpk' as the solver name. Then solve the Pyomo model m and store the results in results2.\n", "
" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "GLPSOL: GLPK LP/MIP Solver, v4.65\n", "Parameter(s) specified in the command line:\n", " --write /var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmpgl_k9y29.glpk.raw\n", " --wglp /var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmphvy697ij.glpk.glp\n", " --cpxlp /var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmpm8vhuqto.pyomo.lp\n", "Reading problem data from '/var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmpm8vhuqto.pyomo.lp'...\n", "26 rows, 73 columns, 97 non-zeros\n", "303 lines were read\n", "Writing problem data to '/var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmphvy697ij.glpk.glp'...\n", "322 lines were written\n", "GLPK Simplex Optimizer, v4.65\n", "26 rows, 73 columns, 97 non-zeros\n", "Preprocessing...\n", "24 rows, 47 columns, 70 non-zeros\n", "Scaling...\n", " A: min|aij| = 9.381e-01 max|aij| = 1.066e+00 ratio = 1.136e+00\n", "Problem data seem to be well scaled\n", "Constructing initial basis...\n", "Size of triangular part is 24\n", " 0: obj = -1.449764871e-01 inf = 3.062e+00 (2)\n", " 5: obj = -2.220151385e+01 inf = 0.000e+00 (0)\n", "* 33: obj = 7.143795836e+01 inf = 0.000e+00 (0)\n", "OPTIMAL LP SOLUTION FOUND\n", "Time used: 0.0 secs\n", "Memory used: 0.1 Mb (78205 bytes)\n", "Writing basic solution to '/var/folders/xy/24xvnyss36v3d8mw68tygxdw0000gp/T/tmpgl_k9y29.glpk.raw'...\n", "108 lines were written\n" ] } ], "source": [ "# Add your solution here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice we used `solver2`, which is an instance of `SolverFactory` for the solver `glpk`. But we reused model `m`. This means the solver `glpk` used the solution from `ipopt` as its initial point." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inspecting the Solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can inspect the entire model solution using `pprint()`." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 Set Declarations\n", " HORIZON : Size=1, Index=None, Ordered=Insertion\n", " Key : Dimen : Domain : Size : Members\n", " None : 1 : Any : 24 : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}\n", "\n", "3 Param Declarations\n", " E0 : Size=1, Index=None, Domain=Any, Default=None, Mutable=True\n", " Key : Value\n", " None : 2.0\n", " price : Size=24, Index=HORIZON, Domain=Reals, Default=None, Mutable=True\n", " Key : Value\n", " 1 : 37.239\n", " 2 : 34.766\n", " 3 : 34.645\n", " 4 : 33.21\n", " 5 : 35.524\n", " 6 : 44.143\n", " 7 : 39.231\n", " 8 : 41.251\n", " 9 : 36.406\n", " 10 : 31.194\n", " 11 : 29.695\n", " 12 : 27.034\n", " 13 : 26.009\n", " 14 : 24.829\n", " 15 : 26.168\n", " 16 : 29.921\n", " 17 : 44.137\n", " 18 : 51.751\n", " 19 : 51.652\n", " 20 : 46.675\n", " 21 : 45.274\n", " 22 : 44.053\n", " 23 : 46.779\n", " 24 : 37.307\n", " sqrteta : Size=1, Index=None, Domain=Any, Default=None, Mutable=False\n", " Key : Value\n", " None : 0.938083151964686\n", "\n", "3 Var Declarations\n", " E : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 2.0 : 4 : False : False : NonNegativeReals\n", " 2 : 0 : 2.0 : 4 : False : False : NonNegativeReals\n", " 3 : 0 : 2.0 : 4 : False : False : NonNegativeReals\n", " 4 : 0 : 2.93808315196469 : 4 : False : False : NonNegativeReals\n", " 5 : 0 : 2.93808315196469 : 4 : False : False : NonNegativeReals\n", " 6 : 0 : 1.87207957018663 : 4 : False : False : NonNegativeReals\n", " 7 : 0 : 1.06600358177805 : 4 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 4 : False : False : NonNegativeReals\n", " 11 : 0 : 0.247667392141256 : 4 : False : False : NonNegativeReals\n", " 12 : 0 : 1.18575054410594 : 4 : False : False : NonNegativeReals\n", " 13 : 0 : 2.12383369607063 : 4 : False : False : NonNegativeReals\n", " 14 : 0 : 3.06191684803531 : 4 : False : False : NonNegativeReals\n", " 15 : 0 : 4.0 : 4 : False : False : NonNegativeReals\n", " 16 : 0 : 4.0 : 4 : False : False : NonNegativeReals\n", " 17 : 0 : 4.0 : 4 : False : False : NonNegativeReals\n", " 18 : 0 : 2.93399641822195 : 4 : False : False : NonNegativeReals\n", " 19 : 0 : 1.8679928364439 : 4 : False : False : NonNegativeReals\n", " 20 : 0 : 1.8679928364439 : 4 : False : False : NonNegativeReals\n", " 21 : 0 : 1.8679928364439 : 4 : False : False : NonNegativeReals\n", " 22 : 0 : 1.8679928364439 : 4 : False : False : NonNegativeReals\n", " 23 : 0 : 1.06191684803531 : 4 : False : False : NonNegativeReals\n", " 24 : 0 : 2.0 : 4 : False : False : NonNegativeReals\n", " c : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : -0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.264014327112209 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : -0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " d : Size=24, Index=HORIZON\n", " Key : Lower : Value : Upper : Fixed : Stale : Domain\n", " 1 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 2 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 3 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 4 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 5 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 6 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 7 : 0 : 0.756166303929372 : 1 : False : False : NonNegativeReals\n", " 8 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 9 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 10 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 11 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 12 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 13 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 14 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 15 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 16 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 17 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 18 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 19 : 0 : 1.0 : 1 : False : False : NonNegativeReals\n", " 20 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 21 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 22 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", " 23 : 0 : 0.756166303929372 : 1 : False : False : NonNegativeReals\n", " 24 : 0 : 0.0 : 1 : False : False : NonNegativeReals\n", "\n", "1 Objective Declarations\n", " OBJ : Size=1, Index=None, Active=True\n", " Key : Active : Sense : Expression\n", " None : True : maximize : (- c[1] + d[1])*price[1] + (- c[2] + d[2])*price[2] + (- c[3] + d[3])*price[3] + (- c[4] + d[4])*price[4] + (- c[5] + d[5])*price[5] + (- c[6] + d[6])*price[6] + (- c[7] + d[7])*price[7] + (- c[8] + d[8])*price[8] + (- c[9] + d[9])*price[9] + (- c[10] + d[10])*price[10] + (- c[11] + d[11])*price[11] + (- c[12] + d[12])*price[12] + (- c[13] + d[13])*price[13] + (- c[14] + d[14])*price[14] + (- c[15] + d[15])*price[15] + (- c[16] + d[16])*price[16] + (- c[17] + d[17])*price[17] + (- c[18] + d[18])*price[18] + (- c[19] + d[19])*price[19] + (- c[20] + d[20])*price[20] + (- c[21] + d[21])*price[21] + (- c[22] + d[22])*price[22] + (- c[23] + d[23])*price[23] + (- c[24] + d[24])*price[24]\n", "\n", "2 Constraint Declarations\n", " EnergyBalance_Con : Size=24, Index=HORIZON, Active=True\n", " Key : Lower : Body : Upper : Active\n", " 1 : 0.0 : E[1] - (E0 + 0.938083151964686*c[1] - 1.0660035817780522*d[1]) : 0.0 : True\n", " 2 : 0.0 : E[2] - (E[1] + 0.938083151964686*c[2] - 1.0660035817780522*d[2]) : 0.0 : True\n", " 3 : 0.0 : E[3] - (E[2] + 0.938083151964686*c[3] - 1.0660035817780522*d[3]) : 0.0 : True\n", " 4 : 0.0 : E[4] - (E[3] + 0.938083151964686*c[4] - 1.0660035817780522*d[4]) : 0.0 : True\n", " 5 : 0.0 : E[5] - (E[4] + 0.938083151964686*c[5] - 1.0660035817780522*d[5]) : 0.0 : True\n", " 6 : 0.0 : E[6] - (E[5] + 0.938083151964686*c[6] - 1.0660035817780522*d[6]) : 0.0 : True\n", " 7 : 0.0 : E[7] - (E[6] + 0.938083151964686*c[7] - 1.0660035817780522*d[7]) : 0.0 : True\n", " 8 : 0.0 : E[8] - (E[7] + 0.938083151964686*c[8] - 1.0660035817780522*d[8]) : 0.0 : True\n", " 9 : 0.0 : E[9] - (E[8] + 0.938083151964686*c[9] - 1.0660035817780522*d[9]) : 0.0 : True\n", " 10 : 0.0 : E[10] - (E[9] + 0.938083151964686*c[10] - 1.0660035817780522*d[10]) : 0.0 : True\n", " 11 : 0.0 : E[11] - (E[10] + 0.938083151964686*c[11] - 1.0660035817780522*d[11]) : 0.0 : True\n", " 12 : 0.0 : E[12] - (E[11] + 0.938083151964686*c[12] - 1.0660035817780522*d[12]) : 0.0 : True\n", " 13 : 0.0 : E[13] - (E[12] + 0.938083151964686*c[13] - 1.0660035817780522*d[13]) : 0.0 : True\n", " 14 : 0.0 : E[14] - (E[13] + 0.938083151964686*c[14] - 1.0660035817780522*d[14]) : 0.0 : True\n", " 15 : 0.0 : E[15] - (E[14] + 0.938083151964686*c[15] - 1.0660035817780522*d[15]) : 0.0 : True\n", " 16 : 0.0 : E[16] - (E[15] + 0.938083151964686*c[16] - 1.0660035817780522*d[16]) : 0.0 : True\n", " 17 : 0.0 : E[17] - (E[16] + 0.938083151964686*c[17] - 1.0660035817780522*d[17]) : 0.0 : True\n", " 18 : 0.0 : E[18] - (E[17] + 0.938083151964686*c[18] - 1.0660035817780522*d[18]) : 0.0 : True\n", " 19 : 0.0 : E[19] - (E[18] + 0.938083151964686*c[19] - 1.0660035817780522*d[19]) : 0.0 : True\n", " 20 : 0.0 : E[20] - (E[19] + 0.938083151964686*c[20] - 1.0660035817780522*d[20]) : 0.0 : True\n", " 21 : 0.0 : E[21] - (E[20] + 0.938083151964686*c[21] - 1.0660035817780522*d[21]) : 0.0 : True\n", " 22 : 0.0 : E[22] - (E[21] + 0.938083151964686*c[22] - 1.0660035817780522*d[22]) : 0.0 : True\n", " 23 : 0.0 : E[23] - (E[22] + 0.938083151964686*c[23] - 1.0660035817780522*d[23]) : 0.0 : True\n", " 24 : 0.0 : E[24] - (E[23] + 0.938083151964686*c[24] - 1.0660035817780522*d[24]) : 0.0 : True\n", " PeriodicBoundaryCondition : Size=1, Index=None, Active=True\n", " Key : Lower : Body : Upper : Active\n", " None : E0 : E[24] : E0 : True\n", "\n", "10 Declarations: HORIZON c d E sqrteta E0 price OBJ EnergyBalance_Con PeriodicBoundaryCondition\n" ] } ], "source": [ "m.pprint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The solution is stored in the `value` column. This is helpful for debugging small models but tendious overwise." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Extracting Solution from Pyomo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A key advantage of Pyomo is that it is an Algebriac Modeling Language in Python. So let's use Python to analyze the solution! The code below extracts the values of the variables into three lists." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "# Declare empty lists\n", "c_control = []\n", "d_control = []\n", "E_control = []\n", "t = []\n", "\n", "# Loop over elements of HORIZON set.\n", "for i in m.HORIZON:\n", " \n", " t.append(pyo.value(i))\n", " \n", " # Use value( ) function to extract the solution for each varliable and append to the results lists\n", " c_control.append(pyo.value(m.c[i]))\n", " \n", " # Adding negative sign to discharge for plotting\n", " d_control.append(-pyo.value(m.d[i]))\n", " E_control.append(pyo.value(m.E[i]))" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.0, 0.264014327112209, 1.0, 1.0, 1.0, 1.0, -0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]\n" ] } ], "source": [ "print(c_control)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-0.0, -0.0, -0.0, -0.0, -0.0, -1.0, -0.756166303929372, -1.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -0.0, -1.0, -1.0, -0.0, -0.0, -0.0, -0.756166303929372, -0.0]\n" ] } ], "source": [ "print(d_control)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2.0, 2.0, 2.0, 2.93808315196469, 2.93808315196469, 1.87207957018663, 1.06600358177805, 0.0, 0.0, 0.0, 0.247667392141256, 1.18575054410594, 2.12383369607063, 3.06191684803531, 4.0, 4.0, 4.0, 2.93399641822195, 1.8679928364439, 1.8679928364439, 1.8679928364439, 1.8679928364439, 1.06191684803531, 2.0]\n" ] } ], "source": [ "print(E_control)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Visualizing the Solution" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# Plot the state of charge (E)\n", "plt.figure()\n", "\n", "# add E0\n", "t_ = [0] + t\n", "\n", "E_control_ = [pyo.value(m.E0)] + E_control\n", "\n", "plt.plot(t,E_control,'b.-')\n", "plt.xlabel('Time (hr)')\n", "plt.ylabel('Energy in Storage (MWh)')\n", "plt.xticks(range(0,25,3))\n", "plt.grid(True)\n", "plt.show()\n", "\n", "# This should NOT be a stair plot as energy is the integral of power. The graph below is reasonable." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "# Plot the charging and discharging rates\n", "plt.figure()\n", "\n", "# double up first data point to make the step plot\n", "c_control_ = [c_control[0]] + c_control\n", "d_control_ = [d_control[0]] + d_control\n", "\n", "plt.step(t_,c_control_,'r.-',where='pre')\n", "plt.step(t_,d_control_,'g.-',where='pre')\n", "plt.xlabel('Time (hr)')\n", "plt.ylabel('Power from Grid (MW)')\n", "plt.xticks(range(0,25,3))\n", "plt.grid(True)\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Activity

\n", " Improve the formatting of the plots using the code from the top of the notebook (pandas section).\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessing Dual Variables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Coming Soon! This will get updated sometime during the first month. We will revisit dual variables later in the semester after introducing some optimization theory concepts." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References\n", "\n", "All tables are from Chapter 4 of Hart, W. E., Laird, C. D., Watson, J. P., Woodruff, D. L., Hackebeil, G. A., Nicholson, B. L., & Siirola, J. D. (2017). *Pyomo-Optimization Modeling in Python* (Vol. 67). Berlin: Springer. https://www.springer.com/gp/book/9783319588193" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" } }, "nbformat": 4, "nbformat_minor": 4 }