├── HW3_Data.xlsx ├── README.md ├── power_gen_optimization.ipynb └── power_gen_optimization.m /HW3_Data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natetsang/optimizing-power-dispatch/16b44f74670a9d306cb7aee60bfbc87a41a39374/HW3_Data.xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optimizing the dispatch of energy generators 2 | ## Summary 3 | This project utilizes convex optimization with application to the economic dispatch problem in power systems. The purpose of this is to optimally schedule distributed energy generators in a distribution feeder to minimize economic costs, while maintaining safe operating constraints. I utilize the convex DistFlow equations of Baran & Wu, which model power flow through radial distribution power networks. These equations model active & reactive power, branch power flow limits, node voltage limits, etc. This was completed as part of my Energy Systems Control class in my MS program. Thanks to Prof. Scott Moura for providing data, overall framework and cvxpy syntax support for this problem. 4 | 5 | The mathematical formulation of this problem is not included in this document, although it is necessary to understand the constraints and forumlas presented in this notebook. 6 | 7 | ## Key questions explored 8 | - Can we dispatch generators in a electrical network to minimize cost? 9 | - How does the solution change when considering variable renewable generation? 10 | 11 | ## Techniques used 12 | - convex optimization and relaxation 13 | - energy systems engineering 14 | - second-order cone programming to deal with stochasticity 15 | 16 | ## Key findings 17 | - As we increase the complexity of the model, we add more constraints to power flow. This increases the minimum cost. 18 | - The cost when considering renewable generation is slightly than if there were no renewables. This makes sense because renewables are variable and we don't know exactly how much power will be available from them. Thus, if we include renewables in our power flow schedule, we’ve introduced uncertainty and thus risk. There is a cost associated with this risk. 19 | -------------------------------------------------------------------------------- /power_gen_optimization.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Optimal Economic Dispatch in Distribution Feeders with Renewables using Convex Optimization\n", 8 | "---\n", 9 | "## Summary\n", 10 | "This project utilizes convex optimization with application to the economic dispatch problem in power systems. The purpose of this is to optimally schedule distributed energy generators in a distribution feeder to minimize economic costs, while maintaining safe operating constraints. I utilize the convex DistFlow equations of Baran & Wu, which model power flow through radial distribution power networks. These equations model active & reactive power, branch power flow limits, node voltage limits, etc. This was completed as part of a class project in my MS program. Thanks to Prof. Scott Moura for providing data and overall framework on this. \n", 11 | "\n", 12 | "The mathematical formulation of this problem is not included in this document, although it is necessary to understand the constraints and forumlas presented in this notebook.\n", 13 | "\n", 14 | "## Key questions explored\n", 15 | "- Can we dispatch generators in a electrical network to minimize cost?\n", 16 | "- How does the solution change when considering variable renewable generation?\n", 17 | "\n", 18 | "## Techniques used\n", 19 | "- convex optimization and relaxation\n", 20 | "- energy systems engineering\n", 21 | "- second-order cone programming to deal with stochasticity\n", 22 | "\n", 23 | "## Key findings\n", 24 | "- As we increase the complexity of the model, we add more constraints to power flow. This increases the minimum cost.\n", 25 | "- The cost when considering renewable generation is slightly than if there were no renewables. This makes sense because renewables are variable and we don't know exactly how much power will be available from them. Thus, if we include renewables in our power flow schedule, we’ve introduced uncertainty and thus risk. There is a cost associated with this risk.\n", 26 | "\n", 27 | "## Download cvxpy\n", 28 | "`pip install cvxpy`\n", 29 | "\n", 30 | "See directions here: https://www.cvxpy.org/install/\n", 31 | "\n", 32 | "I recommend creating a separate pip virtualenv before installing." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 1, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "import numpy as np\n", 42 | "import matplotlib.pyplot as plt\n", 43 | "from cvxpy import *\n", 44 | "%matplotlib inline\n", 45 | "import pandas as pd" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "### 1. Define network parameters for the IEEE 13-node test feeder" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "# Node (aka Bus) Data\n", 62 | "# l_j^P: Active power consumption [MW]\n", 63 | "l_P = np.array([0, 0.2, 0, 0.4, 0.17, 0.23, 1.155, \n", 64 | " 0, 0.17, 0.843, 0, 0.17, 0.128])\n", 65 | "\n", 66 | "# l_j^Q: Reactive power consumption [MVAr]\n", 67 | "l_Q = np.array([0, 0.116, 0, 0.29, 0.125, 0.132, \n", 68 | " 0.66, 0, 0.151, 0.462, 0, 0.08, 0.086])\n", 69 | "\n", 70 | "# l_j^S: Apparent power consumption [MVA]\n", 71 | "l_S = np.sqrt(l_P**2 + l_Q**2)\n", 72 | "\n", 73 | "# s_j,max: Maximal generating power [MW]\n", 74 | "s_max = np.array([5, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0])\n", 75 | "\n", 76 | "# c_j: Marginal generation cost [USD/MW]\n", 77 | "c = np.array([100, 0, 0, 150, 0, 0, 0, 0, 0, 50, 0, 0, 0])\n", 78 | "\n", 79 | "# V_min, V_max: Minimum and maximum nodal voltages [V]\n", 80 | "v_min = 0.95\n", 81 | "v_max = 1.05\n", 82 | "\n", 83 | "### Edge (aka Line) Data\n", 84 | "# r_ij: Resistance [p.u.]\n", 85 | "r = np.array([\n", 86 | "[0, 0.007547918, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 87 | "[0, 0, 0.0041, 0, 0.007239685, 0, 0.007547918, 0, 0, 0, 0, 0, 0],\n", 88 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 89 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 90 | "[0, 0, 0, 0, 0, 0.004343811, 0, 0, 0, 0, 0, 0, 0],\n", 91 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 92 | "[0, 0, 0, 0, 0, 0, 0, 0.003773959, 0, 0, 0.004322245, 0, 0],\n", 93 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 94 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00434686, 0, 0, 0],\n", 95 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 96 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.004343157, 0.01169764],\n", 97 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 98 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n", 99 | "\n", 100 | "# x_ij: Reactance [p.u.]\n", 101 | "x = np.array([\n", 102 | "[0, 0.022173236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 103 | "[0, 0, 0.0064, 0, 0.007336076, 0, 0.022173236, 0, 0, 0, 0, 0, 0],\n", 104 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 105 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 106 | "[0, 0, 0, 0, 0, 0.004401645, 0, 0, 0, 0, 0, 0, 0],\n", 107 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 108 | "[0, 0, 0, 0, 0, 0, 0, 0.011086618, 0, 0, 0.004433667, 0, 0],\n", 109 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 110 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0.002430473, 0, 0, 0],\n", 111 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 112 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.004402952, 0.004490848],\n", 113 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 114 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n", 115 | "\n", 116 | "# I_max_ij: Maximal line current [p.u.]\n", 117 | "I_max = np.array([\n", 118 | "[0, 3.0441, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 119 | "[0, 0, 1.4178, 0, 0.9591, 0, 3.0441, 0, 0, 0, 0, 0, 0],\n", 120 | "[0, 0, 0, 3.1275, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 121 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 122 | "[0, 0, 0, 0, 0, 0.9591, 0, 0, 0, 0, 0, 0, 0],\n", 123 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 124 | "[0, 0, 0, 0, 0, 0, 0, 3.0441, 3.1275, 0, 0.9591, 0, 0],\n", 125 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 126 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1.37193, 0, 0, 0],\n", 127 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 128 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.9591, 1.2927],\n", 129 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 130 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n", 131 | "\n", 132 | "# A_ij: Adjacency matrix; A_ij = 1 if i is parent of j\n", 133 | "A = np.array([\n", 134 | "[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 135 | "[0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0],\n", 136 | "[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 137 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 138 | "[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],\n", 139 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 140 | "[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0],\n", 141 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 142 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],\n", 143 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 144 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],\n", 145 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 146 | "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])\n", 147 | "\n", 148 | "# Set Data\n", 149 | "# List of node indices\n", 150 | "j_idx = np.arange(13)\n", 151 | "\n", 152 | "# \\rho(j): Parent node of node j\n", 153 | "rho = np.array([0, 0, 1, 2, 1, 4, 1, 6, 6, 8, 6, 10, 10])" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "### 2. Plot activate and reactive power consumption at each node\n", 161 | "This will help provide better intuition for our data." 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 3, 167 | "metadata": {}, 168 | "outputs": [ 169 | { 170 | "data": { 171 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAEzCAYAAAAsH2BdAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAMTQAADE0B0s6tTgAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzs3XlcFWX///H3KCoqomks4hHJBVcUhDTBjcBMWyy3MtQQTdPMu6zb7jvt6/K1cim720wr3DLR0iwzl5JEu11z13ANURBcMjPJhW1+f/jzfD2hcECOLL6ej8d5PDgz11zzmTmgvLmumTFM0zQFAAAAAAAKXZmiLgAAAAAAgNKK0A0AAAAAgIMQugEAAAAAcBBCNwAAAAAADkLoBgAAAADAQQjdAAAAAAA4CKEbAAAAAAAHIXQDAIB8GzdunNq2bVvUZRRrFotFc+bMKeoyAABFjNANALjtOnbsKMMwZBiGXFxc1KpVK61evbqoy8rTmTNnNGLECPn4+KhChQqqW7euIiMjdeDAgaIuzaHatm2rcePG2Sx7+eWXtWzZsqIpCACAEoTQDQAoEi+88IJSU1O1c+dOtWzZUt26ddORI0eKuixduXLlhsvPnDmj1q1ba/v27fr44491+PBhLV68WDVr1tT48eNvc5VFz8XFRdWrVy/qMvIlOztbmZmZRV0GAOAOQ+gGABSJypUry9PTUw0aNNAHH3ygsmXLas2aNZKkv/76S4MGDdJdd90lFxcX9ejRQ6dOnZIk7dixQxUqVNDly5clSevWrZNhGPr444+tfTdu3FgLFy6UJGVlZem1116TxWJRlSpV1LFjR+3Zs8fa9to06XfeeUe1atVSUFDQDev997//rczMTMXGxuqBBx6Qt7e3WrZsqTfffFMfffSRtd28efNUv359VahQQX5+flq5cqV1XVxcnAzDUGxsrJo0aaIqVaroscce07lz56xtYmJi1KhRIzk7O8vT01ODBw+2rjMMw3qOJCkxMVGGYVj/WDFnzhxZLBYtXLhQ99xzj1xcXPT8889bz0GNGjVksVg0f/78HDWtWLFCvr6+qlixorp3764//vhDkhQZGakNGzZo/PjxMgxDPj4+Nuftmtw+s2v99O3bV2PGjFH16tXl5eWladOm3fBcX+Pj46PJkyfrkUceUcWKFdWoUSPFxcXZtFmzZo2CgoJUsWJF+fr66sMPP8xxfhYvXqxWrVrJ2dlZu3btyrEfez6XvI4vPT1dgwcPlouLi2rXrq3PPvssx34SEhL0yCOPyMXFRV5eXho+fLguXryY6zkAAJR8hG4AQJFzcnJSuXLllJGRIUl68cUXtW7dOn3zzTdav369Tpw4oX79+kmSWrRoIWdnZ23dulWS9NNPP6lGjRr66aefJF0dkT5w4IA1EI4fP14rVqxQTEyMdu7cqZCQEHXq1El//vmndf+7du3Sli1b9P333+uLL77IUV92drYWL16s5557Ts7OzjnWV6tWTZK0ceNGRUVFacSIEdqzZ48ef/xxPfbYY0pMTLRpP3HiRM2ZM0dr167V3r17NXHiRElSamqqBgwYoPHjx+vgwYNavny5AgMD83Uuz549qwULFujbb7/VokWL9PHHH6tLly7Kzs7Wpk2bNHToUD3zzDM6c+aMzXZjx47V3LlztXbtWh04cEAvvPCCJOndd99Vq1at9NJLLyk1NVU///zzDfeb22d2zbJly5SRkaHNmzdr3Lhxeumll2z+AHIjkyZN0kMPPaSdO3eqU6dOeuyxx3T+/HlJ0sGDB9W9e3cNHTpUv/zyi6ZNm6bx48dr0aJFNn289tprmjhxouLj49WgQYOb7utmn4s9x/fmm2/q22+/1VdffaXly5crOjpaZ8+eta5PT09X586d1aBBA23fvl3ffPONfv75Z7300ku5Hj8AoBQwAQC4zTp06GCOHj3aNE3TTE9PN998802zTJky5s6dO80///zTdHJyMr/77jtr+/3795uSzH379pmmaZqdO3c2J06caJqmaXbq1MmcMGGC6e3tbZqmaS5ZssS85557TNM0zUuXLpkVK1Y09+7da7P/Bg0amJ999plpmqY5duxY08XFxbxw4cJN6z158qQpyVy6dGmux/XEE0+YvXr1slnWunVr8+WXXzZN0zTXrl1rSjK3bNliXf/GG2+YgYGBpmma5rZt20xXV9eb1iLJ/OGHH6zvjx49akoyDx8+bJqmac6ePds0DMM8efKktU3nzp3Npk2bWt9nZmaalStXNpctW2ZT08qVK61tfvjhB9PJyck8d+6caZqmGRISYo4dO9amlrFjx5ohISGmaZp2fWZPP/202aRJE5s+fH19zffff/+Gx2qaplmnTh3ziSeesKnd29vb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\n", 172 | "text/plain": [ 173 | "
" 174 | ] 175 | }, 176 | "metadata": {}, 177 | "output_type": "display_data" 178 | } 179 | ], 180 | "source": [ 181 | "# Plot active and reactive power consumption\n", 182 | "plt.figure(num=1, figsize=(15, 4), dpi=80, facecolor='w', edgecolor='k')\n", 183 | " \n", 184 | "# create plot\n", 185 | "width = 0.2\n", 186 | "plt.bar(j_idx - width, l_P, width,\n", 187 | " color='b',\n", 188 | " label='Active Power (MW)')\n", 189 | "plt.bar(j_idx, l_Q, width,\n", 190 | " color='g',\n", 191 | " label='Reactive Power (MVAR)')\n", 192 | "plt.xticks(j_idx)\n", 193 | "plt.xlabel('Node index number (J)')\n", 194 | "plt.ylabel('Power Consumption [p.u./MW]')\n", 195 | "plt.title('Power Consumption per node')\n", 196 | "plt.legend()\n", 197 | "plt.show()" 198 | ] 199 | }, 200 | { 201 | "cell_type": "markdown", 202 | "metadata": {}, 203 | "source": [ 204 | "### 3. Simple model - balance supply and demand only\n", 205 | "Since the task at hand is non-trivial, I will first break it down into a simpler problem. Here I will disregard the entire network diagram. That is, I seek to balance active & reactive power supply & demand, while\n", 206 | "minimizing generation cost and completely ignoring line losses and constraints. Instead, I will balance supply and demand without network considerations. The goal is to minimize generation costs, given by $c^Ts$. This is achieved by using the `cvxpy` package." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 4, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "data": { 216 | "text/plain": [ 217 | "255.35860662961574" 218 | ] 219 | }, 220 | "execution_count": 4, 221 | "metadata": {}, 222 | "output_type": "execute_result" 223 | } 224 | ], 225 | "source": [ 226 | "# Solve with CVXPY\n", 227 | "\n", 228 | "# Define optimization vars\n", 229 | "p = Variable(13)\n", 230 | "q = Variable(13)\n", 231 | "s = Variable(13)\n", 232 | "\n", 233 | "# Define objective function\n", 234 | "objective = Minimize(c*s)\n", 235 | "\n", 236 | "# Define constraints\n", 237 | "# Apparent Power Limits\n", 238 | "constraints = [s <= s_max]\n", 239 | "\n", 240 | "# Balance power generation with power consumption\n", 241 | "constraints += [sum(p) == sum(l_P), sum(q) == sum(l_Q)]\n", 242 | "\n", 243 | "# Loop over each node\n", 244 | "for jj in j_idx:\n", 245 | " \n", 246 | " # Non-negative power generation\n", 247 | " constraints += [p[jj] >= 0, q[jj] >= 0]\n", 248 | " # Compute apparent power from active & reactive power\n", 249 | " constraints += [norm(vstack((p[jj],q[jj]))) <= s[jj]]\n", 250 | " \n", 251 | "# Define problem and solve\n", 252 | "balance_only = Problem(objective, constraints)\n", 253 | "balance_only.solve()" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 5, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "name": "stdout", 263 | "output_type": "stream", 264 | "text": [ 265 | "------------------ NO NETWORK --------------------\n", 266 | "--------------------------------------------------\n", 267 | "optimal\n", 268 | "Minimum Generating Cost : 255.36 USD\n", 269 | " \n", 270 | "Node 0 [Grid] Gen Power : p_0 = 0.901 MW | q_0 = 0.546 MW | s_0 = 1.054 MW || mu_s0 = 0.0 USD/MW\n", 271 | "Node 3 [Gas] Gen Power : p_3 = 0.0 MW | q_3 = 0.0 MW | s_0 = 0.0 MW || mu_s3 = 0.0 USD/MW\n", 272 | "Node 9 [Solar] Gen Power : p_3 = 2.565 MW | q_3 = 1.556 MW | s_0 = 3.0 MW || mu_s9 = 50.0 USD/MW\n", 273 | " \n", 274 | "Total active power : 3.466 MW consumed | 3.466 MW generated\n", 275 | "Total reactive power : 2.102 MVAr consumed | 2.102 MVAr generated\n", 276 | "Total apparent power : 4.063 MVA consumed | 4.054 MVA generated\n", 277 | " \n" 278 | ] 279 | } 280 | ], 281 | "source": [ 282 | "# Output Results\n", 283 | "print(\"------------------ NO NETWORK --------------------\")\n", 284 | "print(\"--------------------------------------------------\")\n", 285 | "print(balance_only.status)\n", 286 | "print(f\"Minimum Generating Cost : {np.round(balance_only.value, 2)} USD\")\n", 287 | "print(\" \")\n", 288 | "print(f\"Node 0 [Grid] Gen Power : p_0 = {np.round(p[0].value, 3)} MW | q_0 = {np.round(q[0].value, 3)} MW | s_0 = {np.round(s[0].value, 3)} MW || mu_s0 = {np.round(constraints[0].dual_value[0])} USD/MW\")\n", 289 | "print(f\"Node 3 [Gas] Gen Power : p_3 = {np.round(p[3].value, 3)} MW | q_3 = {np.round(q[3].value, 3)} MW | s_0 = {np.round(s[3].value, 3)} MW || mu_s3 = {np.round(constraints[0].dual_value[3])} USD/MW\")\n", 290 | "print(f\"Node 9 [Solar] Gen Power : p_3 = {np.round(p[9].value, 3)} MW | q_3 = {np.round(q[9].value, 3)} MW | s_0 = {np.round(s[9].value, 3)} MW || mu_s9 = {np.round(constraints[0].dual_value[9])} USD/MW\")\n", 291 | "print(\" \")\n", 292 | "print(f\"Total active power : {np.round(np.sum(l_P), 3)} MW consumed | {np.round(np.sum(p.value), 3)} MW generated\")\n", 293 | "print(f\"Total reactive power : {np.round(np.sum(l_Q), 3)} MVAr consumed | {np.round(np.sum(q.value), 3)} MVAr generated\")\n", 294 | "print(f\"Total apparent power : {np.round(np.sum(l_S), 3)} MVA consumed | {np.round(np.sum(s.value), 3)} MVA generated\")\n", 295 | "print(\" \")" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "### 4. Add power line flows\n", 303 | "I'm building up to the full model. Next, I add line power flows $P_{ij}$, $Q_{ij}$ but still neglect the nodal voltage $V_j$ and $L_{ij}$ terms. " 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 6, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "data": { 313 | "text/plain": [ 314 | "255.3586066145166" 315 | ] 316 | }, 317 | "execution_count": 6, 318 | "metadata": {}, 319 | "output_type": "execute_result" 320 | } 321 | ], 322 | "source": [ 323 | "# Assumptions:\n", 324 | "# - Disregard L_ij, the squared magnitude of complex line current\n", 325 | "# - Disregard nodal voltage equation\n", 326 | "# - Disregard nodal voltage limits\n", 327 | "# - Disregard maximum line current\n", 328 | "# - Goal is to minimize generation costs, given by c^T s\n", 329 | "\n", 330 | "# Solve with CVXPY\n", 331 | "p = Variable(13)\n", 332 | "q = Variable(13)\n", 333 | "s = Variable(13)\n", 334 | "P = Variable((13, 13))\n", 335 | "Q = Variable((13, 13))\n", 336 | "\n", 337 | "# Define objective function\n", 338 | "objective = Minimize(c*s)\n", 339 | "\n", 340 | "# Define constraints\n", 341 | "# Apparent Power Limits\n", 342 | "constraints = [s <= s_max]\n", 343 | "\n", 344 | "# Boundary condition for power line flows\n", 345 | "constraints += [P[0,0] == 0, Q[0,0] == 0]\n", 346 | "\n", 347 | "# Loop over each node\n", 348 | "for jj in j_idx:\n", 349 | " \n", 350 | " # Parent node, i = \\rho(j)\n", 351 | " ii = rho[jj]\n", 352 | " \n", 353 | " # Line Power Flows\n", 354 | " constraints += [P[ii,jj] == l_P[jj] - p[jj] + A[jj,:]*P[jj,:].T,\n", 355 | " Q[ii,jj] == l_Q[jj] - q[jj] + A[jj,:]*Q[jj,:].T]\n", 356 | "\n", 357 | " # Compute apparent power from active & reactive power\n", 358 | " constraints += [norm(vstack((p[jj], q[jj]))) <= s[jj]]\n", 359 | "\n", 360 | "# Define problem and solve\n", 361 | "line_flows = Problem(objective, constraints)\n", 362 | "line_flows.solve()\n" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 7, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "name": "stdout", 372 | "output_type": "stream", 373 | "text": [ 374 | "---------------- LINE FLOWS ONLY -----------------\n", 375 | "--------------------------------------------------\n", 376 | "optimal\n", 377 | "Minimum Generating Cost : 255.36 USD\n", 378 | " \n", 379 | "Node 0 [Grid] Gen Power : p_0 = 0.901 MW | q_0 = 0.546 MW | s_0 = 1.054 MW || mu_s0 = 0.0 USD/MW\n", 380 | "Node 3 [Gas] Gen Power : p_3 = 0.0 MW | q_3 = 0.0 MW | s_0 = 0.0 MW || mu_s3 = 0.0 USD/MW\n", 381 | "Node 9 [Solar] Gen Power : p_3 = 2.565 MW | q_3 = 1.556 MW | s_0 = 3.0 MW || mu_s9 = 50.0 USD/MW\n", 382 | " \n", 383 | "Total active power : 3.466 MW consumed | 3.466 MW generated\n", 384 | "Total reactive power : 2.102 MVAr consumed | 2.102 MVAr generated\n", 385 | "Total apparent power : 4.063 MVA consumed | 4.054 MVA generated\n", 386 | " \n" 387 | ] 388 | } 389 | ], 390 | "source": [ 391 | "# Output Results\n", 392 | "print(\"---------------- LINE FLOWS ONLY -----------------\")\n", 393 | "print(\"--------------------------------------------------\")\n", 394 | "print(line_flows.status)\n", 395 | "print(f\"Minimum Generating Cost : {np.round(line_flows.value, 2)} USD\")\n", 396 | "print(\" \")\n", 397 | "print(f\"Node 0 [Grid] Gen Power : p_0 = {np.round(p[0].value, 3)} MW | q_0 = {np.round(q[0].value, 3)} MW | s_0 = {np.round(s[0].value, 3)} MW || mu_s0 = {np.round(constraints[0].dual_value[0])} USD/MW\")\n", 398 | "print(f\"Node 3 [Gas] Gen Power : p_3 = {np.round(p[3].value, 3)} MW | q_3 = {np.round(q[3].value, 3)} MW | s_0 = {np.round(s[3].value, 3)} MW || mu_s3 = {np.round(constraints[0].dual_value[3])} USD/MW\")\n", 399 | "print(f\"Node 9 [Solar] Gen Power : p_3 = {np.round(p[9].value, 3)} MW | q_3 = {np.round(q[9].value, 3)} MW | s_0 = {np.round(s[9].value, 3)} MW || mu_s9 = {np.round(constraints[0].dual_value[9])} USD/MW\")\n", 400 | "print(\" \")\n", 401 | "print(f\"Total active power : {np.round(np.sum(l_P), 3)} MW consumed | {np.round(np.sum(p.value), 3)} MW generated\")\n", 402 | "print(f\"Total reactive power : {np.round(np.sum(l_Q), 3)} MVAr consumed | {np.round(np.sum(q.value), 3)} MVAr generated\")\n", 403 | "print(f\"Total apparent power : {np.round(np.sum(l_S), 3)} MVA consumed | {np.round(np.sum(s.value), 3)} MVA generated\")\n", 404 | "print(\" \")" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "metadata": {}, 410 | "source": [ 411 | "### 5. Full economic dispatch with DistFlow equations\n", 412 | "Now I'm ready to solve the full economic dispatch. I now add the nodal voltages $V_j$, squared current magnitudes $L_{ij}$, and their bounds. This incorporates impedance (i.e. losses) across the network, along with nodal voltage and line transmission limits." 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 8, 418 | "metadata": {}, 419 | "outputs": [ 420 | { 421 | "data": { 422 | "text/plain": [ 423 | "299.6867745233192" 424 | ] 425 | }, 426 | "execution_count": 8, 427 | "metadata": {}, 428 | "output_type": "execute_result" 429 | } 430 | ], 431 | "source": [ 432 | "# Assumptions:\n", 433 | "# - Add back all previously disregarded terms and constraints\n", 434 | "# - Relax squared line current equation into inequality\n", 435 | "# - Goal is to minimize generation costs, given by c^T s\n", 436 | "\n", 437 | "# Define optimization vars\n", 438 | "p = Variable(13)\n", 439 | "q = Variable(13)\n", 440 | "s = Variable(13)\n", 441 | "P = Variable((13,13))\n", 442 | "Q = Variable((13,13))\n", 443 | "L = Variable((13,13))\n", 444 | "V = Variable(13)\n", 445 | "\n", 446 | "# Define objective function\n", 447 | "objective = Minimize(c*s)\n", 448 | "\n", 449 | "# Define constraints\n", 450 | "# Apparent Power Limits\n", 451 | "constraints = [s <= s_max]\n", 452 | "\n", 453 | "# Nodal voltage limits\n", 454 | "constraints += [v_min**2 <= V,\n", 455 | " V <= v_max**2]\n", 456 | "\n", 457 | "# Squared line current limits\n", 458 | "constraints += [L <= I_max**2]\n", 459 | "\n", 460 | "# Boundary condition for power line flows\n", 461 | "constraints += [P[0,0] == 0,\n", 462 | " Q[0,0] == 0]\n", 463 | "\n", 464 | "# Boundary condition for squared line current\n", 465 | "constraints += [L[0,0] == 0]\n", 466 | "\n", 467 | "# Fix node 0 voltage to be 1 \"per unit\" (p.u.)\n", 468 | "constraints += [V[0] == 1]\n", 469 | "\n", 470 | "# Loop over each node\n", 471 | "for jj in j_idx:\n", 472 | " \n", 473 | " # Parent node, i = \\rho(j)\n", 474 | " ii = rho[jj]\n", 475 | " \n", 476 | " # Line Power Flows\n", 477 | " constraints += [P[ii,jj] == l_P[jj] - p[jj] + r[ii,jj]*L[ii,jj] + A[jj,:]*P[jj,:].T,\n", 478 | " Q[ii,jj] == l_Q[jj] - q[jj] + x[ii,jj]*L[ii,jj] + A[jj,:]*Q[jj,:].T]\n", 479 | "\n", 480 | " # Nodal voltage\n", 481 | " constraints += [V[jj] == V[ii] + (r[ii,jj]**2 + x[ii,jj]**2)*L[ii,jj] - \n", 482 | " 2*(r[ii,jj]*P[ii,jj] + x[ii,jj]*Q[ii,jj])]\n", 483 | " \n", 484 | " # Squared current magnitude on lines\n", 485 | " constraints += [L[ii,jj] >= quad_over_lin(vstack((P[ii,jj],Q[ii,jj])),V[jj])]\n", 486 | " \n", 487 | " # Compute apparent power from active & reactive power\n", 488 | " constraints += [norm(vstack((p[jj],q[jj]))) <= s[jj]]\n", 489 | " \n", 490 | "\n", 491 | "# Define problem and solve\n", 492 | "full_network = Problem(objective, constraints)\n", 493 | "full_network.solve()" 494 | ] 495 | }, 496 | { 497 | "cell_type": "code", 498 | "execution_count": 9, 499 | "metadata": {}, 500 | "outputs": [ 501 | { 502 | "name": "stdout", 503 | "output_type": "stream", 504 | "text": [ 505 | "----------------- FULL NETWORK -------------------\n", 506 | "--------------------------------------------------\n", 507 | "optimal\n", 508 | "Minimum Generating Cost : 299.69 USD\n", 509 | " \n", 510 | "Node 0 [Grid] Gen Power : p_0 = 1.568 MW | q_0 = 0.985 MW | s_0 = 1.852 MW || mu_s0 = 0.0 USD/MW\n", 511 | "Node 3 [Gas] Gen Power : p_3 = -0.0 MW | q_3 = 0.0 MW | s_0 = 0.0 MW || mu_s3 = 0.0 USD/MW\n", 512 | "Node 9 [Solar] Gen Power : p_3 = 1.941 MW | q_3 = 1.216 MW | s_0 = 2.29 MW || mu_s9 = 0.0 USD/MW\n", 513 | " \n", 514 | "Total active power : 3.466 MW consumed | 3.509 MW generated\n", 515 | "Total reactive power : 2.102 MVAr consumed | 2.201 MVAr generated\n", 516 | "Total apparent power : 4.063 MVA consumed | 4.142 MVA generated\n", 517 | " \n", 518 | "Node 0 Voltage : 1.0 p.u.\n", 519 | "Node 1 Voltage : 0.967 p.u.\n", 520 | "Node 2 Voltage : 0.963 p.u.\n", 521 | "Node 3 Voltage : 0.963 p.u.\n", 522 | "Node 4 Voltage : 0.962 p.u.\n", 523 | "Node 5 Voltage : 0.96 p.u.\n", 524 | "Node 6 Voltage : 0.957 p.u.\n", 525 | "Node 7 Voltage : 0.957 p.u.\n", 526 | "Node 8 Voltage : 0.957 p.u.\n", 527 | "Node 9 Voltage : 0.964 p.u.\n", 528 | "Node 10 Voltage : 0.955 p.u.\n", 529 | "Node 11 Voltage : 0.954 p.u.\n", 530 | "Node 12 Voltage : 0.953 p.u.\n" 531 | ] 532 | } 533 | ], 534 | "source": [ 535 | "# Output Results\n", 536 | "print(\"----------------- FULL NETWORK -------------------\")\n", 537 | "print(\"--------------------------------------------------\")\n", 538 | "print(full_network.status)\n", 539 | "print(f\"Minimum Generating Cost : {np.round(full_network.value, 2)} USD\")\n", 540 | "print(\" \")\n", 541 | "print(f\"Node 0 [Grid] Gen Power : p_0 = {np.round(p[0].value, 3)} MW | q_0 = {np.round(q[0].value, 3)} MW | s_0 = {np.round(s[0].value, 3)} MW || mu_s0 = {np.round(constraints[0].dual_value[0])} USD/MW\")\n", 542 | "print(f\"Node 3 [Gas] Gen Power : p_3 = {np.round(p[3].value, 3)} MW | q_3 = {np.round(q[3].value, 3)} MW | s_0 = {np.round(s[3].value, 3)} MW || mu_s3 = {np.round(constraints[0].dual_value[3])} USD/MW\")\n", 543 | "print(f\"Node 9 [Solar] Gen Power : p_3 = {np.round(p[9].value, 3)} MW | q_3 = {np.round(q[9].value, 3)} MW | s_0 = {np.round(s[9].value, 3)} MW || mu_s9 = {np.round(constraints[0].dual_value[9])} USD/MW\")\n", 544 | "print(\" \")\n", 545 | "print(f\"Total active power : {np.round(np.sum(l_P), 3)} MW consumed | {np.round(np.sum(p.value), 3)} MW generated\")\n", 546 | "print(f\"Total reactive power : {np.round(np.sum(l_Q), 3)} MVAr consumed | {np.round(np.sum(q.value), 3)} MVAr generated\")\n", 547 | "print(f\"Total apparent power : {np.round(np.sum(l_S), 3)} MVA consumed | {np.round(np.sum(s.value), 3)} MVA generated\")\n", 548 | "print(\" \")\n", 549 | "for jj in j_idx:\n", 550 | " print(f\"Node {jj} Voltage : {np.round((V[jj].value)**0.5, 3)} p.u.\")" 551 | ] 552 | }, 553 | { 554 | "cell_type": "markdown", 555 | "metadata": {}, 556 | "source": [ 557 | "### 6. Robust economic dispatch with variable renewables sources\n", 558 | "So far, I've explored a network that includes only non-renewable sources. In these scenarios, the generating capacity of each source is known. Now, we assume renewables are available, which have an unknown generating capacity. More specifically, I assume there's now a solar generator at node 9 that is comprised of two solar panels, A and B. The output of each panel is uncertain, and weather dependent." 559 | ] 560 | }, 561 | { 562 | "cell_type": "code", 563 | "execution_count": 10, 564 | "metadata": {}, 565 | "outputs": [ 566 | { 567 | "data": { 568 | "text/plain": [ 569 | "308.72071356743197" 570 | ] 571 | }, 572 | "execution_count": 10, 573 | "metadata": {}, 574 | "output_type": "execute_result" 575 | } 576 | ], 577 | "source": [ 578 | "# Assumptions:\n", 579 | "# - Assume solar generator at node 9 has uncertain power capacity\n", 580 | "# - Goal is to minimize generation costs, given by c^T s, in face of uncertainty\n", 581 | "\n", 582 | "# Second order cone constraint parameters\n", 583 | "a_bar = np.array([[1.], [-1.25], [-1.25]])\n", 584 | "E = np.diag([0, 0.25, 0.25])\n", 585 | "b = 0\n", 586 | "\n", 587 | "# Solve with CVXPY\n", 588 | "\n", 589 | "# Define optimization vars\n", 590 | "p = Variable(13)\n", 591 | "q = Variable(13)\n", 592 | "s = Variable(13)\n", 593 | "P = Variable((13,13))\n", 594 | "Q = Variable((13,13))\n", 595 | "L = Variable((13,13))\n", 596 | "V = Variable(13)\n", 597 | "sig = Variable(2)\n", 598 | "\n", 599 | "# Define objective function\n", 600 | "objective = Minimize(c*s)\n", 601 | "\n", 602 | "# Define constraints\n", 603 | "# Apparent Power Limits\n", 604 | "constraints = [s <= s_max]\n", 605 | "\n", 606 | "# Nodal voltage limits\n", 607 | "constraints += [v_min**2 <= V,\n", 608 | " V <= v_max**2]\n", 609 | "\n", 610 | "# Squared line current limits\n", 611 | "constraints += [L <= I_max**2]\n", 612 | "\n", 613 | "# Second order cone constraint for solar generator on node 9\n", 614 | "constraints += [a_bar.T * vstack([s[9], sig[0], sig[1]]) + norm(E.T * vstack([s[9], sig[0], sig[1]])) <= b,\n", 615 | " 0 <= sig,\n", 616 | " sig <= 1]\n", 617 | "\n", 618 | "# Boundary condition for power line flows\n", 619 | "constraints += [P[0,0] == 0,\n", 620 | " Q[0,0] == 0]\n", 621 | "\n", 622 | "# Boundary condition for squared line current\n", 623 | "constraints += [L[0,0] == 0]\n", 624 | "\n", 625 | "# Fix node 0 voltage to be 1 \"per unit\" (p.u.)\n", 626 | "constraints += [V[0] == 1]\n", 627 | "\n", 628 | "# Loop over each node\n", 629 | "for jj in j_idx:\n", 630 | " \n", 631 | " # Parent node, i = \\rho(j)\n", 632 | " ii = rho[jj]\n", 633 | " \n", 634 | " # Line Power Flows\n", 635 | " constraints += [P[ii,jj] == l_P[jj] - p[jj] + r[ii,jj]*L[ii,jj] + A[jj,:]*P[jj,:].T,\n", 636 | " Q[ii,jj] == l_Q[jj] - q[jj] + x[ii,jj]*L[ii,jj] + A[jj,:]*Q[jj,:].T]\n", 637 | "\n", 638 | " # Nodal voltage\n", 639 | " constraints += [V[jj] == V[ii] + (r[ii,jj]**2 + x[ii,jj]**2)*L[ii,jj] - 2*(r[ii,jj]*P[ii,jj] + x[ii,jj]*Q[ii,jj])]\n", 640 | " \n", 641 | " # Squared current magnitude on lines\n", 642 | " constraints += [L[ii,jj] >= quad_over_lin(vstack([P[ii,jj],Q[ii,jj]]),V[jj])]\n", 643 | " \n", 644 | " # Compute apparent power from active & reactive power\n", 645 | " constraints += [norm(vstack([p[jj],q[jj]])) <= s[jj]]\n", 646 | " \n", 647 | "\n", 648 | "# Define problem and solve\n", 649 | "robust_with_renewables = Problem(objective, constraints)\n", 650 | "robust_with_renewables.solve()" 651 | ] 652 | }, 653 | { 654 | "cell_type": "code", 655 | "execution_count": 11, 656 | "metadata": {}, 657 | "outputs": [ 658 | { 659 | "name": "stdout", 660 | "output_type": "stream", 661 | "text": [ 662 | "------ ROBUST OPTIMIZATION WITH RENEWABLES -------\n", 663 | "--------------------------------------------------\n", 664 | "optimal\n", 665 | "Minimum Generating Cost : 308.72 USD\n", 666 | " \n", 667 | "Node 0 [Grid] Gen Power : p_0 = 1.745 MW | q_0 = 1.006 MW | s_0 = 2.014 MW || mu_s0 = 0.0 USD/MW\n", 668 | "Node 3 [Gas] Gen Power : p_3 = 0.0 MW | q_3 = 0.0 MW | s_0 = -0.0 MW || mu_s3 = 0.0 USD/MW\n", 669 | "Node 9 [Solar] Gen Power : p_3 = 1.77 MW | q_3 = 1.215 MW | s_0 = 2.146 MW || mu_s9 = 0.0 USD/MW\n", 670 | " \n", 671 | "Total active power : 3.466 MW consumed | 3.514 MW generated\n", 672 | "Total reactive power : 2.102 MVAr consumed | 2.221 MVAr generated\n", 673 | "Total apparent power : 4.063 MVA consumed | 4.16 MVA generated\n", 674 | " \n", 675 | "Node 0 Voltage : 1.0 p.u.\n", 676 | "Node 1 Voltage : 0.965 p.u.\n", 677 | "Node 2 Voltage : 0.961 p.u.\n", 678 | "Node 3 Voltage : 0.961 p.u.\n", 679 | "Node 4 Voltage : 0.96 p.u.\n", 680 | "Node 5 Voltage : 0.958 p.u.\n", 681 | "Node 6 Voltage : 0.954 p.u.\n", 682 | "Node 7 Voltage : 0.954 p.u.\n", 683 | "Node 8 Voltage : 0.954 p.u.\n", 684 | "Node 9 Voltage : 0.96 p.u.\n", 685 | "Node 10 Voltage : 0.952 p.u.\n", 686 | "Node 11 Voltage : 0.951 p.u.\n", 687 | "Node 12 Voltage : 0.95 p.u.\n" 688 | ] 689 | } 690 | ], 691 | "source": [ 692 | "print(\"------ ROBUST OPTIMIZATION WITH RENEWABLES -------\")\n", 693 | "print(\"--------------------------------------------------\")\n", 694 | "print(robust_with_renewables.status)\n", 695 | "print(f\"Minimum Generating Cost : {np.round(robust_with_renewables.value, 2)} USD\")\n", 696 | "print(\" \")\n", 697 | "print(f\"Node 0 [Grid] Gen Power : p_0 = {np.round(p[0].value, 3)} MW | q_0 = {np.round(q[0].value, 3)} MW | s_0 = {np.round(s[0].value, 3)} MW || mu_s0 = {np.round(constraints[0].dual_value[0])} USD/MW\")\n", 698 | "print(f\"Node 3 [Gas] Gen Power : p_3 = {np.round(p[3].value, 3)} MW | q_3 = {np.round(q[3].value, 3)} MW | s_0 = {np.round(s[3].value, 3)} MW || mu_s3 = {np.round(constraints[0].dual_value[3])} USD/MW\")\n", 699 | "print(f\"Node 9 [Solar] Gen Power : p_3 = {np.round(p[9].value, 3)} MW | q_3 = {np.round(q[9].value, 3)} MW | s_0 = {np.round(s[9].value, 3)} MW || mu_s9 = {np.round(constraints[0].dual_value[9])} USD/MW\")\n", 700 | "print(\" \")\n", 701 | "print(f\"Total active power : {np.round(np.sum(l_P), 3)} MW consumed | {np.round(np.sum(p.value), 3)} MW generated\")\n", 702 | "print(f\"Total reactive power : {np.round(np.sum(l_Q), 3)} MVAr consumed | {np.round(np.sum(q.value), 3)} MVAr generated\")\n", 703 | "print(f\"Total apparent power : {np.round(np.sum(l_S), 3)} MVA consumed | {np.round(np.sum(s.value), 3)} MVA generated\")\n", 704 | "print(\" \")\n", 705 | "for jj in j_idx:\n", 706 | " print(f\"Node {jj} Voltage : {np.round((V[jj].value)**0.5, 3)} p.u.\")" 707 | ] 708 | }, 709 | { 710 | "cell_type": "code", 711 | "execution_count": null, 712 | "metadata": {}, 713 | "outputs": [], 714 | "source": [] 715 | } 716 | ], 717 | "metadata": { 718 | "kernelspec": { 719 | "display_name": "Python 3", 720 | "language": "python", 721 | "name": "python3" 722 | }, 723 | "language_info": { 724 | "codemirror_mode": { 725 | "name": "ipython", 726 | "version": 3 727 | }, 728 | "file_extension": ".py", 729 | "mimetype": "text/x-python", 730 | "name": "python", 731 | "nbconvert_exporter": "python", 732 | "pygments_lexer": "ipython3", 733 | "version": "3.6.2" 734 | } 735 | }, 736 | "nbformat": 4, 737 | "nbformat_minor": 1 738 | } 739 | -------------------------------------------------------------------------------- /power_gen_optimization.m: -------------------------------------------------------------------------------- 1 | %% CE 295 - Energy Systems and Control 2 | % Optimal Economic Dispatch in Distribution Feeders with Renewables 3 | % Prof. Moura 4 | % Last updated: February 20, 2018 5 | 6 | % TSANG_NATHAN_HW3.m 7 | 8 | clear; close all; 9 | fs = 15; % Font Size for plots 10 | 11 | %% 13 Node IEEE Test Feeder Parameters 12 | 13 | %%% Node (aka Bus) Data 14 | % l_j^P: Active power consumption [MW] 15 | l_P = [0; 0.2; 0; 0.4; 0.17; 0.23; 1.155; 0; 0.17; 0.843; 0; 0.17; 0.128]; 16 | 17 | % l_j^Q: Reactive power consumption [MVAr] 18 | l_Q = [0; 0.116; 0; 0.29; 0.125; 0.132; 0.66; 0; 0.151; 0.462; 0; 0.08; 0.086]; 19 | 20 | % l_j^S: Apparent power consumption [MVA] 21 | l_S = sqrt(l_P.^2 + l_Q.^2); 22 | 23 | % s_j,max: Maximal generating power [MW] 24 | s_max = [5; 0; 0; 3; 0; 0; 0; 0; 0; 3; 0; 0; 0]; 25 | 26 | % c_j: Marginal generation cost [USD/MW] 27 | c = [100; 0; 0; 150; 0; 0; 0; 0; 0; 50; 0; 0; 0]; 28 | 29 | % V_min, V_max: Minimum and maximum nodal voltages [V] 30 | v_min = 0.95; 31 | v_max = 1.05; 32 | 33 | %%% Edge (aka Line) Data 34 | % r_ij: Resistance [p.u.] 35 | r = xlsread('HW3_Data.xlsx', 'Line-Data', 'B6:N18'); 36 | 37 | % x_ij: Reactance [p.u.] 38 | x = xlsread('HW3_Data.xlsx', 'Line-Data', 'B24:N36'); 39 | 40 | % I_max_ij: Maximal line current [p.u.] 41 | I_max = xlsread('HW3_Data.xlsx', 'Line-Data', 'B42:N54'); 42 | 43 | % A_ij: Adjacency matrix; A_ij = 1 if i is parent of j 44 | A = xlsread('HW3_Data.xlsx', 'Line-Data', 'B60:N72'); 45 | 46 | 47 | %%% Set Data (add +1 everywhere for Matlab indexing) 48 | % \rho(j): Parent node of node j 49 | rho = [0; 0; 1; 2; 1; 4; 1; 6; 6; 8; 6; 10; 10]+1; 50 | 51 | % Node index set - CREATED BY NATE 52 | j = [0 1 2 3 4 5 6 7 8 9 10 11 12]; 53 | 54 | %% Problem 1 55 | 56 | % Plot active and reactive power consumption 57 | figure(1); 58 | bar(j, [l_P,l_Q]); 59 | legend('Real power (MW)','Reactive power (MVAR)') 60 | xlabel('Node index number') 61 | ylabel('Power consumption') 62 | set(gca,'FontSize',fs) 63 | 64 | %% Problem 2 65 | 66 | % Assumptions: 67 | % - Disregard the entire network diagram 68 | % - Balance supply and demand, without any network considerations 69 | % - Goal is to minimize generation costs, given by c^T s 70 | 71 | % Solve with CVX 72 | cvx_begin 73 | variables s(13,1) p(13,1) q(13,1) % declare your optimization variables here 74 | minimize(c'*s) % objective function here 75 | subject to % constraints 76 | % Balance power generation with power consumption 77 | sum(p) == sum(l_P); 78 | sum(q) == sum(l_Q); 79 | 80 | % Loop over each node 81 | for jj = 1:13 82 | 83 | % Non-negative power generation 84 | q(jj) >= 0; 85 | p(jj) >= 0; 86 | 87 | % Compute apparent power from active & reactive power 88 | norm([p(jj) q(jj)],2) <= s(jj); 89 | 90 | end 91 | 92 | % Apparent Power Limits 93 | s <= s_max; 94 | 95 | cvx_end 96 | 97 | % Output Results 98 | fprintf(1,'------------------- PROBLEM 2 --------------------\n'); 99 | fprintf(1,'--------------------------------------------------\n'); 100 | fprintf(1,'Minimum Generating Cost : %4.2f USD\n',cvx_optval); 101 | fprintf(1,'\n'); 102 | fprintf(1,'Node 0 [Grid] Gen Power : p_0 = %1.3f MW | q_0 = %1.3f MW | s_0 = %1.3f MW\n',p(1),q(1),s(1)); 103 | fprintf(1,'Node 3 [Gas] Gen Power : p_3 = %1.3f MW | q_3 = %1.3f MW | s_3 = %1.3f MW\n',p(4),q(4),s(4)); 104 | fprintf(1,'Node 9 [Solar] Gen Power : p_9 = %1.3f MW | q_9 = %1.3f MW | s_9 = %1.3f MW\n',p(10),q(10),s(10)); 105 | fprintf(1,'\n'); 106 | fprintf(1,'Total active power : %1.3f MW consumed | %1.3f MW generated\n',sum(l_P),sum(p)); 107 | fprintf(1,'Total reactive power : %1.3f MVAr consumed | %1.3f MVAr generated\n',sum(l_Q),sum(q)); 108 | fprintf(1,'Total apparent power : %1.3f MVA consumed | %1.3f MVA generated\n',sum(l_S),sum(s)); 109 | 110 | %% Problem 3 111 | 112 | % Assumptions: 113 | % - Disregard L_ij, the squared magnitude of complex line current 114 | % - Disregard nodal voltage equation 115 | % - Disregard nodal voltage limits 116 | % - Disregard maximum line current 117 | % - Goal is to minimize generation costs, given by c^T s 118 | 119 | % Solve with CVX 120 | cvx_begin 121 | variables p(13,1) q(13,1) s(13,1) P(13,13) Q(13,13) 122 | dual variable mu_s 123 | minimize(c' * s) 124 | subject to 125 | 126 | % Boundary condition for power line flows 127 | P( 1 , 1 ) == 0; 128 | Q( 1 , 1 ) == 0; 129 | 130 | % Loop over each node 131 | for jj = 1:13 132 | 133 | % Parent node, i = \rho(j) 134 | ii = rho(jj); 135 | 136 | % Line Power Flows 137 | P(ii,jj) == l_P(jj) - p(jj) + sum(A(jj,:).* P(jj,:)); 138 | Q(ii,jj) == l_Q(jj) - q(jj) + sum(A(jj,:).* Q(jj,:)); 139 | 140 | % Compute apparent power from active & reactive power 141 | norm([p(jj) q(jj)],2) <= s(jj); 142 | q(jj) >= 0; 143 | p(jj) >= 0; 144 | 145 | end 146 | 147 | % Apparent Power Limits 148 | s <= s_max : mu_s; 149 | 150 | cvx_end 151 | 152 | % Output Results 153 | fprintf(1,'------------------- PROBLEM 3 --------------------\n'); 154 | fprintf(1,'--------------------------------------------------\n'); 155 | fprintf(1,'Minimum Generating Cost : %4.2f USD\n',cvx_optval); 156 | fprintf(1,'\n'); 157 | fprintf(1,'Node 0 [Grid] Gen Power : p_0 = %1.3f MW | q_0 = %1.3f MW | s_0 = %1.3f MW || mu_s0 = %3.0f USD/MW\n',p(1),q(1),s(1),mu_s(1)); 158 | fprintf(1,'Node 3 [Gas] Gen Power : p_3 = %1.3f MW | q_3 = %1.3f MW | s_3 = %1.3f MW || mu_s3 = %3.0f USD/MW\n',p(4),q(4),s(4),mu_s(4)); 159 | fprintf(1,'Node 9 [Solar] Gen Power : p_9 = %1.3f MW | q_9 = %1.3f MW | s_9 = %1.3f MW || mu_s9 = %3.0f USD/MW\n',p(10),q(10),s(10),mu_s(10)); 160 | fprintf(1,'\n'); 161 | fprintf(1,'Total active power : %1.3f MW consumed | %1.3f MW generated\n',sum(l_P),sum(p)); 162 | fprintf(1,'Total reactive power : %1.3f MVAr consumed | %1.3f MVAr generated\n',sum(l_Q),sum(q)); 163 | fprintf(1,'Total apparent power : %1.3f MVA consumed | %1.3f MVA generated\n',sum(l_S),sum(s)); 164 | 165 | 166 | %% Problem 4 167 | 168 | % Assumptions: 169 | % - Add back all previously disregarded terms and constraints 170 | % - Relax squared line current equation into inequality 171 | % - Goal is to minimize generation costs, given by c^T s 172 | 173 | % Solve with CVX 174 | cvx_begin 175 | variables p(13,1) q(13,1) s(13,1) P(13,13) Q(13,13) L(13,13) V(13,1) 176 | dual variables mu_s mu_L mu_vmin mu_vmax 177 | minimize(c'*s) 178 | subject to 179 | 180 | % Boundary condition for power line flows 181 | P( 1 , 1 ) == 0; 182 | Q( 1 , 1 ) == 0; 183 | 184 | % Boundary condition for squared line current 185 | L( 1 , 1 ) == 0; 186 | 187 | % Fix node 0 voltage to be 1 "per unit" (p.u.) 188 | V(1) == 1; 189 | 190 | % Loop over each node 191 | for jj = 1:13 192 | 193 | % Parent node, i = \rho(j) 194 | ii = rho(jj); 195 | 196 | % Line Power Flows 197 | P(ii,jj) == l_P(jj) - p(jj) + sum(A(jj,:).* P(jj,:)) + r(ii,jj)*L(ii,jj); 198 | Q(ii,jj) == l_Q(jj) - q(jj) + sum(A(jj,:).* Q(jj,:)) + x(ii,jj)*L(ii,jj); 199 | 200 | % Nodal voltage 201 | V(jj) == V(ii)+(r(ii,jj)^2 + x(ii,jj)^2) * L(ii,jj) - 2*(r(ii,jj) * P(ii,jj) + x(ii,jj) * Q(ii,jj)); 202 | 203 | % Squared current magnitude on lines 204 | L(ii,jj) >= quad_over_lin(P(ii,jj),V(jj))+ quad_over_lin(Q(ii,jj),V(jj)); 205 | 206 | % Compute apparent power from active & reactive power 207 | norm([p(jj) q(jj)],2) <= s(jj); 208 | q(jj) >= 0; 209 | p(jj) >= 0; 210 | 211 | end 212 | 213 | % Squared line current limits 214 | L <= I_max.^2 : mu_L; 215 | 216 | % Nodal voltage limits 217 | V <= v_max.^2 : mu_vmax; 218 | V >= v_min.^2 : mu_vmin; 219 | 220 | % Apparent Power Limits 221 | s <= s_max : mu_s; 222 | 223 | cvx_end 224 | 225 | % Output Results 226 | fprintf(1,'------------------- PROBLEM 4 --------------------\n'); 227 | fprintf(1,'--------------------------------------------------\n'); 228 | fprintf(1,'Minimum Generating Cost : %4.2f USD\n',cvx_optval); 229 | fprintf(1,'\n'); 230 | fprintf(1,'Node 0 [Grid] Gen Power : p_0 = %1.3f MW | q_0 = %1.3f MW | s_0 = %1.3f MW || mu_s0 = %3.0f USD/MW | mu_vmax0 = %3.2f p.u.| mu_vmin0 = %3.2f p.u./MW\n',p(1),q(1),s(1),mu_s(1),mu_vmax(1),mu_vmin(1)); 231 | fprintf(1,'Node 3 [Gas] Gen Power : p_3 = %1.3f MW | q_3 = %1.3f MW | s_3 = %1.3f MW || mu_s3 = %3.0f USD/MW | mu_vmax3 = %3.2f p.u.| mu_vmin3 = %3.2f p.u./MW\n',p(4),q(4),s(4),mu_s(4),mu_vmax(4),mu_vmin(4)); 232 | fprintf(1,'Node 9 [Solar] Gen Power : p_9 = %1.3f MW | q_9 = %1.3f MW | s_9 = %1.3f MW || mu_s9 = %3.0f USD/MW | mu_vmax9 = %3.2f p.u.| mu_vmin9 = %3.2f p.u./MW\n',p(10),q(10),s(10),mu_s(10),mu_vmax(10),mu_vmin(10)); 233 | fprintf(1,'\n'); 234 | fprintf(1,'Total active power : %1.3f MW consumed | %1.3f MW generated\n',sum(l_P),sum(p)); 235 | fprintf(1,'Total reactive power : %1.3f MVAr consumed | %1.3f MVAr generated\n',sum(l_Q),sum(q)); 236 | fprintf(1,'Total apparent power : %1.3f MVA consumed | %1.3f MVA generated\n',sum(l_S),sum(s)); 237 | fprintf(1,'\n'); 238 | for jj = 1:13 239 | fprintf(1,'Node %2.0f Voltage : %1.3f p.u.\n',jj,sqrt(V(jj))); 240 | end 241 | 242 | 243 | %% Problem 5 244 | 245 | % Assumptions: 246 | % - Assume solar generator at node 9 has uncertain power capacity 247 | % - Goal is to minimize generation costs, given by c^T s, in face of uncertainty 248 | 249 | % Solve with CVX 250 | 251 | % define new variables 252 | a_bar = [-1.25; -1.25; 1]; 253 | E = diag([0.25 0.25 0]); 254 | b = 0; 255 | 256 | cvx_begin 257 | variables p(13,1) q(13,1) s(13,1) P(13,13) Q(13,13) L(13,13) V(13,1) y(3,1) 258 | dual variables mu_s mu_L mu_vmin mu_vmax 259 | minimize(c'*s) 260 | subject to 261 | 262 | % Boundary condition for power line flows 263 | P( 1 , 1 ) == 0; 264 | Q( 1 , 1 ) == 0; 265 | 266 | % Boundary condition for squared line current 267 | L( 1 , 1 ) == 0; 268 | 269 | % Fix node 0 voltage to be 1 "per unit" (p.u.) 270 | V(1) == 1; 271 | 272 | % Loop over each node 273 | for jj = 1:13 274 | % Parent node, i = \rho(j) 275 | ii = rho(jj); 276 | if jj == 10 277 | a_bar'*y + norm([E'*y],2) <= b; 278 | y(1:2,1) >= 0; 279 | y(1:2,1) <= 1; 280 | s(jj) == y(3); 281 | 282 | % Line Power Flows 283 | P(ii,jj) == l_P(jj) - p(jj) + sum(A(jj,:).* P(jj,:)) + r(ii,jj)*L(ii,jj); 284 | Q(ii,jj) == l_Q(jj) - q(jj) + sum(A(jj,:).* Q(jj,:)) + x(ii,jj)*L(ii,jj); 285 | 286 | % Nodal voltage 287 | V(jj) == V(ii)+(r(ii,jj)^2 + x(ii,jj)^2) * L(ii,jj) - 2*(r(ii,jj) * P(ii,jj) + x(ii,jj) * Q(ii,jj)); 288 | 289 | % Squared current magnitude on lines 290 | L(ii,jj) >= quad_over_lin(P(ii,jj),V(jj))+ quad_over_lin(Q(ii,jj),V(jj)); 291 | 292 | % Compute apparent power from active & reactive power 293 | norm([p(jj) q(jj)],2) <= s(jj); 294 | q(jj) >= 0; 295 | p(jj) >= 0; 296 | 297 | else 298 | 299 | % Line Power Flows 300 | P(ii,jj) == l_P(jj) - p(jj) + sum(A(jj,:).* P(jj,:)) + r(ii,jj)*L(ii,jj); 301 | Q(ii,jj) == l_Q(jj) - q(jj) + sum(A(jj,:).* Q(jj,:)) + x(ii,jj)*L(ii,jj); 302 | 303 | % Nodal voltage 304 | V(jj) == V(ii)+(r(ii,jj)^2 + x(ii,jj)^2) * L(ii,jj) - 2*(r(ii,jj) * P(ii,jj) + x(ii,jj) * Q(ii,jj)); 305 | 306 | % Squared current magnitude on lines 307 | L(ii,jj) >= quad_over_lin(P(ii,jj),V(jj))+ quad_over_lin(Q(ii,jj),V(jj)); 308 | 309 | % Compute apparent power from active & reactive power 310 | norm([p(jj) q(jj)],2) <= s(jj); 311 | q(jj) >= 0; 312 | p(jj) >= 0; 313 | end 314 | 315 | end 316 | 317 | % Squared line current limits 318 | L <= I_max.^2 : mu_L; 319 | 320 | % Nodal voltage limits 321 | V <= v_max.^2 : mu_vmax; 322 | V >= v_min.^2 : mu_vmin; 323 | 324 | % Apparent Power Limits 325 | s <= s_max : mu_s; 326 | 327 | cvx_end 328 | 329 | % Output Results 330 | fprintf(1,'------------------- PROBLEM 5 --------------------\n'); 331 | fprintf(1,'--------------------------------------------------\n'); 332 | fprintf(1,'Minimum Generating Cost : %4.2f USD\n',cvx_optval); 333 | fprintf(1,'\n'); 334 | fprintf(1,'Node 0 [Grid] Gen Power : p_0 = %1.3f MW | q_0 = %1.3f MW | s_0 = %1.3f MW || mu_s0 = %3.0f USD/MW | mu_vmax0 = %3.2f p.u.| mu_vmin0 = %3.2f p.u./MW\n',p(1),q(1),s(1),mu_s(1),mu_vmax(1),mu_vmin(1)); 335 | fprintf(1,'Node 3 [Gas] Gen Power : p_3 = %1.3f MW | q_3 = %1.3f MW | s_3 = %1.3f MW || mu_s3 = %3.0f USD/MW | mu_vmax3 = %3.2f p.u.| mu_vmin3 = %3.2f p.u./MW\n',p(4),q(4),s(4),mu_s(4),mu_vmax(4),mu_vmin(4)); 336 | fprintf(1,'Node 9 [Solar] Gen Power : p_9 = %1.3f MW | q_9 = %1.3f MW | s_9 = %1.3f MW || mu_s9 = %3.0f USD/MW | mu_vmax9 = %3.2f p.u.| mu_vmin9 = %3.2f p.u./MW\n',p(10),q(10),s(10),mu_s(10),mu_vmax(10),mu_vmin(10)); 337 | fprintf(1,'\n'); 338 | fprintf(1,'Total active power : %1.3f MW consumed | %1.3f MW generated\n',sum(l_P),sum(p)); 339 | fprintf(1,'Total reactive power : %1.3f MVAr consumed | %1.3f MVAr generated\n',sum(l_Q),sum(q)); 340 | fprintf(1,'Total apparent power : %1.3f MVA consumed | %1.3f MVA generated\n',sum(l_S),sum(s)); 341 | fprintf(1,'\n'); 342 | for jj = 1:13 343 | fprintf(1,'Node %2.0f Voltage : %1.3f p.u.\n',jj,sqrt(V(jj))); 344 | end 345 | --------------------------------------------------------------------------------