{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "title: Power Systems\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Originally Contributed by**: Yury Dvorkin and Miles Lubin" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook demonstrates how to formulate basic power systems engineering models in JuMP using a 3 bus example. \n", "We will consider basic \"economic dispatch\" and \"unit commitment\" models without taking into account transmission constraints." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Illustrative example\n", "In the following notes for the sake of simplicity, we are going to use a three bus example mirroring the interface between Western and Eastern Texas. This example is taken from R. Baldick, \"[Wind and Energy Markets: A Case Study of Texas](http://dx.doi.org/10.1109/JSYST.2011.2162798),\" IEEE Systems Journal, vol. 6, pp. 27-34, 2012." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For this example, we set the following characteristics of generators, transmission lines, wind farms and demands:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Quantity | Generator 1 | Generator 2 |\n", "|:-----------------|:-----------:|------------:|\n", "| $g_{min}$, MW | 0 | 300 |\n", "| $g_{max}$, MW | 1000 | 1000 |\n", "| $c^g$, \\$/MWh | 50 | 100 |\n", "| $c^{g0}$, \\$/MWh | 1000 | 0 |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Quantity | Line 1 | Line 2 |\n", "|:--------------|:------:|-------:|\n", "| $f^{max}$, MW | 100 | 1000 |\n", "| x, p.u. | 0.001 | 0.001 |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Quantity | Wind farm 1 | Wind farm 2 |\n", "|:----------------|:-----------:|------------:|\n", "| $w^{f}$, MW | 150 | 50 |\n", "| $c^{w}$, \\$/MWh | 50 | 50 |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Quantity | Bus 1 | Bus 2 | Bus 3 |\n", "|:---------|:-----:|:-----:|------:|\n", "| $d$, MW | 0 | 0 | 15000 |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Economic dispatch \n", "Economic dispatch (ED) is an optimization problem that minimizes the cost of supplying energy demand subject to operational constraints on power system assets. In its simplest modification, ED is an LP problem solved for an aggregated load and wind forecast and for a single infinitesimal moment. Mathematically, the ED problem can be written as follows:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\min \\sum_{i \\in I} c^g_{i} \\cdot g_{i} + c^w \\cdot w,\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $c_{i}$ and $g_{i}$ are the incremental cost ($\\$/MWh$) and power output ($MW$) of the $i^{th}$ generator, respectively, and $c^w$ and $w$ are the incremental cost ($\\$/MWh$) and wind power injection ($MW$), respectively." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Subject to the constraints:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
  • Minimum ($g^{\\min}$) and maximum ($g^{\\max}$) limits on power outputs of generators:
  • " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "g^{\\min}_{i} \\leq g_{i} \\leq g^{\\max}_{i}.\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
  • Constraint on the wind power injection:
  • " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "0 \\leq w \\leq w^f, \n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $w$ and $w^f$ are the wind power injection and wind power forecast, respectively." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
  • Power balance constraint:
  • " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\sum_{i \\in I} g_{i} + w = d^f, \n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $d^f$ is the demand forecast." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Further reading on ED models can be found in A. J. Wood, B. F. Wollenberg, and G. B. Sheblé, \"Power Generation, Operation and Control\", Wiley, 2013." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## JuMP Implementation of Economic Dispatch" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "using JuMP, GLPK, LinearAlgebra, DataFrames" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Define some input data about the test system\n", "# Maximum power output of generators\n", "g_max = [1000, 1000];\n", "# Minimum power output of generators\n", "g_min = [0, 300];\n", "# Incremental cost of generators \n", "c_g = [50, 100];\n", "# Fixed cost of generators\n", "c_g0 = [1000, 0]\n", "# Incremental cost of wind generators\n", "c_w = 50;\n", "# Total demand\n", "d = 1500;\n", "# Wind forecast\n", "w_f = 200;" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Dispatch of Generators: [1000.0, 300.0] MW\n", "Dispatch of Wind: 200.0 MW\n", "Wind spillage: 0.0 MW\n", "\n", "\n", "Total cost: 90000.0$\n" ] } ], "source": [ "# In this cell we create function solve_ed, which solves the economic dispatch problem for a given set of input parameters.\n", "function solve_ed(g_max, g_min, c_g, c_w, d, w_f)\n", " #Define the economic dispatch (ED) model\n", " ed = Model(GLPK.Optimizer)\n", " \n", " # Define decision variables \n", " @variable(ed, 0 <= g[i = 1:2] <= g_max[i]) # power output of generators\n", " @variable(ed, 0 <= w <= w_f) # wind power injection\n", "\n", " # Define the objective function\n", " @objective(ed, Min, dot(c_g, g) + c_w * w)\n", "\n", " # Define the constraint on the maximum and minimum power output of each generator\n", " @constraint(ed, [i = 1:2], g[i] <= g_max[i]) #maximum\n", " @constraint(ed, [i = 1:2], g[i] >= g_min[i]) #minimum\n", "\n", " # Define the constraint on the wind power injection\n", " @constraint(ed, w <= w_f)\n", "\n", " # Define the power balance constraint\n", " @constraint(ed, sum(g) + w == d)\n", "\n", " # Solve statement\n", " optimize!(ed)\n", " \n", " # return the optimal value of the objective function and its minimizers\n", " return value.(g), value(w), w_f - value(w), objective_value(ed)\n", "end\n", "\n", "# Solve the economic dispatch problem\n", "(g_opt, w_opt, ws_opt, obj) = solve_ed(g_max, g_min, c_g, c_w, d, w_f);\n", "\n", "println(\"\\n\")\n", "println(\"Dispatch of Generators: \", g_opt, \" MW\")\n", "println(\"Dispatch of Wind: \", w_opt, \" MW\")\n", "println(\"Wind spillage: \", w_f - w_opt, \" MW\") \n", "println(\"\\n\")\n", "println(\"Total cost: \", obj, \"\\$\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Economic dispatch with adjustable incremental costs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following exercise we adjust the incremental cost of generator G1 and observe its impact on the total cost." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "c_g_scale_df = DataFrame(Symbol(\"Dispatch of Generator 1(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Generator 2(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Wind(MW)\") => Float64[],\n", " Symbol(\"Spillage of Wind(MW)\") => Float64[],\n", " Symbol(\"Total cost(\\$)\") => Float64[])\n", "for c_g1_scale = 0.5:0.1:3.0\n", " c_g_scale = [c_g[1] * c_g1_scale, c_g[2]] # update the incremental cost of the first generator at every iteration\n", " local g_opt, w_opt, ws_opt, obj = solve_ed(g_max, g_min, c_g_scale, c_w, d, w_f) # solve the ed problem with the updated incremental cost\n", " push!(c_g_scale_df, (g_opt[1], g_opt[2], w_opt, ws_opt, obj))\n", "end" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

    26 rows × 5 columns

    Dispatch of Generator 1(MW)Dispatch of Generator 2(MW)Dispatch of Wind(MW)Spillage of Wind(MW)Total cost($)
    Float64Float64Float64Float64Float64
    11000.0300.0200.00.065000.0
    21000.0300.0200.00.070000.0
    31000.0300.0200.00.075000.0
    41000.0300.0200.00.080000.0
    51000.0300.0200.00.085000.0
    61000.0300.0200.00.090000.0
    71000.0300.0200.00.095000.0
    81000.0300.0200.00.0100000.0
    91000.0300.0200.00.0105000.0
    101000.0300.0200.00.0110000.0
    111000.0300.0200.00.0115000.0
    121000.0300.0200.00.0120000.0
    131000.0300.0200.00.0125000.0
    141000.0300.0200.00.0130000.0
    151000.0300.0200.00.0135000.0
    161000.0300.0200.00.0140000.0
    17300.01000.0200.00.0141500.0
    18300.01000.0200.00.0143000.0
    19300.01000.0200.00.0144500.0
    20300.01000.0200.00.0146000.0
    21300.01000.0200.00.0147500.0
    22300.01000.0200.00.0149000.0
    23300.01000.0200.00.0150500.0
    24300.01000.0200.00.0152000.0
    25300.01000.0200.00.0153500.0
    26300.01000.0200.00.0155000.0
    " ], "text/latex": [ "\\begin{tabular}{r|ccccc}\n", "\t& Dispatch of Generator 1(MW) & Dispatch of Generator 2(MW) & Dispatch of Wind(MW) & Spillage of Wind(MW) & Total cost(\\$)\\\\\n", "\t\\hline\n", "\t& Float64 & Float64 & Float64 & Float64 & Float64\\\\\n", "\t\\hline\n", "\t1 & 1000.0 & 300.0 & 200.0 & 0.0 & 65000.0 \\\\\n", "\t2 & 1000.0 & 300.0 & 200.0 & 0.0 & 70000.0 \\\\\n", "\t3 & 1000.0 & 300.0 & 200.0 & 0.0 & 75000.0 \\\\\n", "\t4 & 1000.0 & 300.0 & 200.0 & 0.0 & 80000.0 \\\\\n", "\t5 & 1000.0 & 300.0 & 200.0 & 0.0 & 85000.0 \\\\\n", "\t6 & 1000.0 & 300.0 & 200.0 & 0.0 & 90000.0 \\\\\n", "\t7 & 1000.0 & 300.0 & 200.0 & 0.0 & 95000.0 \\\\\n", "\t8 & 1000.0 & 300.0 & 200.0 & 0.0 & 100000.0 \\\\\n", "\t9 & 1000.0 & 300.0 & 200.0 & 0.0 & 105000.0 \\\\\n", "\t10 & 1000.0 & 300.0 & 200.0 & 0.0 & 110000.0 \\\\\n", "\t11 & 1000.0 & 300.0 & 200.0 & 0.0 & 115000.0 \\\\\n", "\t12 & 1000.0 & 300.0 & 200.0 & 0.0 & 120000.0 \\\\\n", "\t13 & 1000.0 & 300.0 & 200.0 & 0.0 & 125000.0 \\\\\n", "\t14 & 1000.0 & 300.0 & 200.0 & 0.0 & 130000.0 \\\\\n", "\t15 & 1000.0 & 300.0 & 200.0 & 0.0 & 135000.0 \\\\\n", "\t16 & 1000.0 & 300.0 & 200.0 & 0.0 & 140000.0 \\\\\n", "\t17 & 300.0 & 1000.0 & 200.0 & 0.0 & 141500.0 \\\\\n", "\t18 & 300.0 & 1000.0 & 200.0 & 0.0 & 143000.0 \\\\\n", "\t19 & 300.0 & 1000.0 & 200.0 & 0.0 & 144500.0 \\\\\n", "\t20 & 300.0 & 1000.0 & 200.0 & 0.0 & 146000.0 \\\\\n", "\t21 & 300.0 & 1000.0 & 200.0 & 0.0 & 147500.0 \\\\\n", "\t22 & 300.0 & 1000.0 & 200.0 & 0.0 & 149000.0 \\\\\n", "\t23 & 300.0 & 1000.0 & 200.0 & 0.0 & 150500.0 \\\\\n", "\t24 & 300.0 & 1000.0 & 200.0 & 0.0 & 152000.0 \\\\\n", "\t25 & 300.0 & 1000.0 & 200.0 & 0.0 & 153500.0 \\\\\n", "\t26 & 300.0 & 1000.0 & 200.0 & 0.0 & 155000.0 \\\\\n", "\\end{tabular}\n" ], "text/plain": [ "26×5 DataFrame\n", "│ Row │ Dispatch of Generator 1(MW) │ Dispatch of Generator 2(MW) │ Dispatch of Wind(MW) │ Spillage of Wind(MW) │ Total cost($) │\n", "│ │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │\n", "├─────┼─────────────────────────────┼─────────────────────────────┼──────────────────────┼──────────────────────┼───────────────┤\n", "│ 1 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 65000.0 │\n", "│ 2 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 70000.0 │\n", "│ 3 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 75000.0 │\n", "│ 4 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 80000.0 │\n", "│ 5 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 85000.0 │\n", "│ 6 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 90000.0 │\n", "│ 7 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 95000.0 │\n", "│ 8 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 100000.0 │\n", "│ 9 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 105000.0 │\n", "│ 10 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 110000.0 │\n", "⋮\n", "│ 16 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 140000.0 │\n", "│ 17 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 141500.0 │\n", "│ 18 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 143000.0 │\n", "│ 19 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 144500.0 │\n", "│ 20 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 146000.0 │\n", "│ 21 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 147500.0 │\n", "│ 22 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 149000.0 │\n", "│ 23 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 150500.0 │\n", "│ 24 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 152000.0 │\n", "│ 25 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 153500.0 │\n", "│ 26 │ 300.0 │ 1000.0 │ 200.0 │ 0.0 │ 155000.0 │" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ENV[\"COLUMNS\"]=250 # Helps us display the complete table\n", "c_g_scale_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modifying the JuMP model in place" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that in the previous exercise we entirely rebuilt the optimization model at every iteration of the internal loop, which incurs an additional computational burden. This burden can be alleviated if instead of re-building the entire model, we modify a specific constraint(s) or the objective function, as it shown in the example below." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compare the computing time in case of the above and below models." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "elapsed time: 0.4870901107788086 seconds" ] } ], "source": [ "function solve_ed_inplace(c_w_scale)\n", " start = time()\n", " obj_out = Float64[]\n", " w_out = Float64[]\n", " g1_out = Float64[]\n", " g2_out = Float64[]\n", " \n", " ed = Model(GLPK.Optimizer)\n", " \n", " # Define decision variables \n", " @variable(ed, 0 <= g[i = 1:2] <= g_max[i]) # power output of generators\n", " @variable(ed, 0 <= w <= w_f ) # wind power injection\n", "\n", " # Define the objective function\n", " @objective(ed, Min, dot(c_g, g) + c_w * w)\n", "\n", " # Define the constraint on the maximum and minimum power output of each generator\n", " @constraint(ed, [i = 1:2], g[i] <= g_max[i]) #maximum\n", " @constraint(ed, [i = 1:2], g[i] >= g_min[i]) #minimum\n", "\n", " # Define the constraint on the wind power injection\n", " @constraint(ed, w <= w_f)\n", "\n", " # Define the power balance constraint\n", " @constraint(ed, sum(g) + w == d)\n", " \n", " optimize!(ed)\n", " \n", " for c_g1_scale = 0.5:0.01:3.0\n", " @objective(ed, Min, c_g1_scale*c_g[1]*g[1] + c_g[2]*g[2] + c_w_scale*c_w*w)\n", " optimize!(ed)\n", " push!(obj_out, objective_value(ed))\n", " push!(w_out, value(w))\n", " push!(g1_out, value(g[1]))\n", " push!(g2_out, value(g[2]))\n", " end\n", " elapsed = time() - start\n", " print(string(\"elapsed time: \", elapsed, \" seconds\"))\n", " return obj_out, w_out, g1_out, g2_out\n", "end\n", "\n", "solve_ed_inplace(2.0);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Adjusting specific constraints and/or the objective function is faster than re-building the entire model." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A few practical limitations of the economic dispatch model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inefficient usage of wind generators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The economic dispatch problem does not perform commitment decisions and, thus, assumes that all generators must be dispatched at least at their minimum power output limit. This approach is not cost efficient and may lead to absurd decisions. For example, if $ d = \\sum_{i \\in I} g^{\\min}_{i}$, the wind power injection must be zero, i.e. all available wind generation is spilled, to meet the minimum power output constraints on generators." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, we adjust the total demand and observed how it affects wind spillage." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "demandscale_df = DataFrame(Symbol(\"Dispatch of Generators(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Generator 2(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Wind(MW)\") => Float64[],\n", " Symbol(\"Spillage of Wind(MW)\") => Float64[],\n", " Symbol(\"Total cost(\\$)\") => Float64[])\n", "\n", "for demandscale = 0.2:0.1:1.5\n", " local g_opt,w_opt,ws_opt,obj = solve_ed(g_max, g_min, c_g, c_w, demandscale*d, w_f)\n", "\n", " push!(demandscale_df, (g_opt[1], g_opt[2], w_opt, ws_opt, obj))\n", "end" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

    14 rows × 5 columns

    Dispatch of Generators(MW)Dispatch of Generator 2(MW)Dispatch of Wind(MW)Spillage of Wind(MW)Total cost($)
    Float64Float64Float64Float64Float64
    10.0300.00.0200.030000.0
    2150.0300.00.0200.037500.0
    3300.0300.00.0200.045000.0
    4450.0300.00.0200.052500.0
    5600.0300.00.0200.060000.0
    6750.0300.00.0200.067500.0
    7900.0300.00.0200.075000.0
    81000.0300.050.0150.082500.0
    91000.0300.0200.00.090000.0
    101000.0450.0200.00.0105000.0
    111000.0600.0200.00.0120000.0
    121000.0750.0200.00.0135000.0
    131000.0900.0200.00.0150000.0
    141000.01000.0200.00.0160000.0
    " ], "text/latex": [ "\\begin{tabular}{r|ccccc}\n", "\t& Dispatch of Generators(MW) & Dispatch of Generator 2(MW) & Dispatch of Wind(MW) & Spillage of Wind(MW) & Total cost(\\$)\\\\\n", "\t\\hline\n", "\t& Float64 & Float64 & Float64 & Float64 & Float64\\\\\n", "\t\\hline\n", "\t1 & 0.0 & 300.0 & 0.0 & 200.0 & 30000.0 \\\\\n", "\t2 & 150.0 & 300.0 & 0.0 & 200.0 & 37500.0 \\\\\n", "\t3 & 300.0 & 300.0 & 0.0 & 200.0 & 45000.0 \\\\\n", "\t4 & 450.0 & 300.0 & 0.0 & 200.0 & 52500.0 \\\\\n", "\t5 & 600.0 & 300.0 & 0.0 & 200.0 & 60000.0 \\\\\n", "\t6 & 750.0 & 300.0 & 0.0 & 200.0 & 67500.0 \\\\\n", "\t7 & 900.0 & 300.0 & 0.0 & 200.0 & 75000.0 \\\\\n", "\t8 & 1000.0 & 300.0 & 50.0 & 150.0 & 82500.0 \\\\\n", "\t9 & 1000.0 & 300.0 & 200.0 & 0.0 & 90000.0 \\\\\n", "\t10 & 1000.0 & 450.0 & 200.0 & 0.0 & 105000.0 \\\\\n", "\t11 & 1000.0 & 600.0 & 200.0 & 0.0 & 120000.0 \\\\\n", "\t12 & 1000.0 & 750.0 & 200.0 & 0.0 & 135000.0 \\\\\n", "\t13 & 1000.0 & 900.0 & 200.0 & 0.0 & 150000.0 \\\\\n", "\t14 & 1000.0 & 1000.0 & 200.0 & 0.0 & 160000.0 \\\\\n", "\\end{tabular}\n" ], "text/plain": [ "14×5 DataFrame\n", "│ Row │ Dispatch of Generators(MW) │ Dispatch of Generator 2(MW) │ Dispatch of Wind(MW) │ Spillage of Wind(MW) │ Total cost($) │\n", "│ │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │\n", "├─────┼────────────────────────────┼─────────────────────────────┼──────────────────────┼──────────────────────┼───────────────┤\n", "│ 1 │ 0.0 │ 300.0 │ 0.0 │ 200.0 │ 30000.0 │\n", "│ 2 │ 150.0 │ 300.0 │ 0.0 │ 200.0 │ 37500.0 │\n", "│ 3 │ 300.0 │ 300.0 │ 0.0 │ 200.0 │ 45000.0 │\n", "│ 4 │ 450.0 │ 300.0 │ 0.0 │ 200.0 │ 52500.0 │\n", "│ 5 │ 600.0 │ 300.0 │ 0.0 │ 200.0 │ 60000.0 │\n", "│ 6 │ 750.0 │ 300.0 │ 0.0 │ 200.0 │ 67500.0 │\n", "│ 7 │ 900.0 │ 300.0 │ 0.0 │ 200.0 │ 75000.0 │\n", "│ 8 │ 1000.0 │ 300.0 │ 50.0 │ 150.0 │ 82500.0 │\n", "│ 9 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 90000.0 │\n", "│ 10 │ 1000.0 │ 450.0 │ 200.0 │ 0.0 │ 105000.0 │\n", "│ 11 │ 1000.0 │ 600.0 │ 200.0 │ 0.0 │ 120000.0 │\n", "│ 12 │ 1000.0 │ 750.0 │ 200.0 │ 0.0 │ 135000.0 │\n", "│ 13 │ 1000.0 │ 900.0 │ 200.0 │ 0.0 │ 150000.0 │\n", "│ 14 │ 1000.0 │ 1000.0 │ 200.0 │ 0.0 │ 160000.0 │" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "demandscale_df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This particular drawback can be overcome by introducing binary decisions on the \"on/off\" status of generators. This model is called unit commitment and considered later in these notes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For further reading on the interplay between wind generation and the minimum power output constraints of generators, we refer interested readers to R. Baldick, \"Wind and Energy Markets: A Case Study of Texas,\" IEEE Systems Journal, vol. 6, pp. 27-34, 2012." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Transmission-infeasible solution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ED solution is entirely market-based and disrespects limitations of the transmission network. Indeed, the flows in transmission lines would attain the following values:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$f_{1-2} = 150 MW \\leq f_{1-2}^{\\max} = 100 MW $$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$f_{2-3} = 1200 MW \\leq f_{2-3}^{\\max} = 1000 MW $$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Thus, if this ED solution was enforced in practice, the power flow limits on both lines would be violated. Therefore, in the following section we consider the optimal power flow model, which amends the ED model with network constraints." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The importance of the transmission-aware decisions is emphasized in E. Lannoye, D. Flynn, and M. O'Malley, \"Transmission, Variable Generation, and Power System Flexibility,\" IEEE Transactions on Power Systems, vol. 30, pp. 57-66, 2015." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unit Commitment model \n", "The Unit Commitment (UC) model can be obtained from ED model by introducing binary variable associated with each generator. This binary variable can attain two values: if it is \"1\", the generator is synchronized and, thus, can be dispatched, otherwise, i.e. if the binary variable is \"0\", that generator is not synchronized and its power output is set to 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To obtain the mathematical formulation of the UC model, we will modify the constraints of the ED model as follows:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "g^{\\min}_{i} \\cdot u_{t,i} \\leq g_{i} \\leq g^{\\max}_{i} \\cdot u_{t,i},\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where $ u_{i} \\in \\{0;1\\}. $ In this constraint, if $ u_{i} = 0$, then $g_{i} = 0$. On the other hand, if $ u_{i} = 1$, then $g^{max}_{i} \\leq g_{i} \\leq g^{min}_{i}$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For further reading on the UC problem we refer interested readers to G. Morales-Espana, J. M. Latorre, and A. Ramos, \"Tight and Compact MILP Formulation for the Thermal Unit Commitment Problem,\" IEEE Transactions on Power Systems, vol. 28, pp. 4897-4908, 2013.\n", "In the following example we convert the ED model explained above to the UC model." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Dispatch of Generators: [1000.0, 300.0] MW\n", "Commitments of Generators: [0.0, 0.0]\n", "Dispatch of Wind: 200.0 MW\n", "Wind spillage: 0.0 MW\n", "\n", "\n", "Total cost: 90000.0$\n" ] } ], "source": [ "# In this cell we introduce binary decision u to the economic dispatch problem (function solve_ed)\n", "function solve_uc(g_max, g_min, c_g, c_w, d, w_f)\n", " #Define the unit commitment (UC) model\n", " uc = Model(GLPK.Optimizer)\n", " \n", " # Define decision variables \n", " @variable(uc, 0 <= g[i=1:2] <= g_max[i]) # power output of generators\n", " @variable(uc, u[i = 1:2], Bin) # Binary status of generators\n", " @variable(uc, 0 <= w <= w_f ) # wind power injection\n", "\n", " # Define the objective function\n", " @objective(uc, Min, dot(c_g, g) + c_w * w)\n", "\n", " # Define the constraint on the maximum and minimum power output of each generator\n", " @constraint(uc, [i = 1:2], g[i] <= g_max[i]) #maximum\n", " @constraint(uc, [i = 1:2], g[i] >= g_min[i]) #minimum\n", "\n", " # Define the constraint on the wind power injection\n", " @constraint(uc, w <= w_f)\n", "\n", " # Define the power balance constraint\n", " @constraint(uc, sum(g) + w == d)\n", "\n", " # Solve statement\n", " optimize!(uc)\n", " \n", " status = termination_status(uc)\n", " if status != MOI.OPTIMAL\n", " return status, zeros(length(g)), 0.0, 0.0, zeros(length(u)), Inf\n", " end\n", " return status, value.(g), value(w), w_f - value(w), value.(u), objective_value(uc)\n", "end\n", "\n", "# Solve the economic dispatch problem\n", "status, g_opt, w_opt, ws_opt, u_opt, obj = solve_uc(g_max, g_min, c_g, c_w, d, w_f);\n", "\n", "println(\"\\n\")\n", "println(\"Dispatch of Generators: \", g_opt[:], \" MW\")\n", "println(\"Commitments of Generators: \", u_opt[:])\n", "println(\"Dispatch of Wind: \", w_opt, \" MW\")\n", "println(\"Wind spillage: \", w_f - w_opt, \" MW\") \n", "println(\"\\n\")\n", "println(\"Total cost: \", obj, \"\\$\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Unit Commitment as a function of demand" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After implementing the UC model, we can now assess the interplay between the minimum power output constraints on generators and wind generation." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Status: INFEASIBLE for demandscale = 1.5 \n", "\n" ] } ], "source": [ "uc_df = DataFrame(Symbol(\"Commitment of Generator 1(MW)\") => Float64[],\n", " Symbol(\"Commitment of Generator 2(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Generator 1(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Generator 2(MW)\") => Float64[],\n", " Symbol(\"Dispatch of Wind(MW)\") => Float64[],\n", " Symbol(\"Spillage of Wind(MW)\") => Float64[],\n", " Symbol(\"Total cost(\\$)\") => Float64[])\n", "\n", "for demandscale = 0.2:0.1:1.5\n", " local status, g_opt, w_opt, ws_opt, u_opt, obj = solve_uc(g_max, g_min, c_g, c_w, demandscale*d, w_f)\n", " \n", " if status == MOI.OPTIMAL\n", " push!(uc_df, (u_opt[1], u_opt[2], g_opt[1], g_opt[2], w_opt, ws_opt, obj))\n", " else\n", " println(\"Status: $status for demandscale = $demandscale \\n\")\n", " end\n", "end" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "

    13 rows × 7 columns

    Commitment of Generator 1(MW)Commitment of Generator 2(MW)Dispatch of Generator 1(MW)Dispatch of Generator 2(MW)Dispatch of Wind(MW)Spillage of Wind(MW)Total cost($)
    Float64Float64Float64Float64Float64Float64Float64
    10.00.00.0300.00.0200.030000.0
    20.00.0150.0300.00.0200.037500.0
    30.00.0300.0300.00.0200.045000.0
    40.00.0450.0300.00.0200.052500.0
    50.00.0600.0300.00.0200.060000.0
    60.00.0750.0300.00.0200.067500.0
    70.00.0900.0300.00.0200.075000.0
    80.00.01000.0300.050.0150.082500.0
    90.00.01000.0300.0200.00.090000.0
    100.00.01000.0450.0200.00.0105000.0
    110.00.01000.0600.0200.00.0120000.0
    120.00.01000.0750.0200.00.0135000.0
    130.00.01000.0900.0200.00.0150000.0
    " ], "text/latex": [ "\\begin{tabular}{r|ccccccc}\n", "\t& Commitment of Generator 1(MW) & Commitment of Generator 2(MW) & Dispatch of Generator 1(MW) & Dispatch of Generator 2(MW) & Dispatch of Wind(MW) & Spillage of Wind(MW) & Total cost(\\$)\\\\\n", "\t\\hline\n", "\t& Float64 & Float64 & Float64 & Float64 & Float64 & Float64 & Float64\\\\\n", "\t\\hline\n", "\t1 & 0.0 & 0.0 & 0.0 & 300.0 & 0.0 & 200.0 & 30000.0 \\\\\n", "\t2 & 0.0 & 0.0 & 150.0 & 300.0 & 0.0 & 200.0 & 37500.0 \\\\\n", "\t3 & 0.0 & 0.0 & 300.0 & 300.0 & 0.0 & 200.0 & 45000.0 \\\\\n", "\t4 & 0.0 & 0.0 & 450.0 & 300.0 & 0.0 & 200.0 & 52500.0 \\\\\n", "\t5 & 0.0 & 0.0 & 600.0 & 300.0 & 0.0 & 200.0 & 60000.0 \\\\\n", "\t6 & 0.0 & 0.0 & 750.0 & 300.0 & 0.0 & 200.0 & 67500.0 \\\\\n", "\t7 & 0.0 & 0.0 & 900.0 & 300.0 & 0.0 & 200.0 & 75000.0 \\\\\n", "\t8 & 0.0 & 0.0 & 1000.0 & 300.0 & 50.0 & 150.0 & 82500.0 \\\\\n", "\t9 & 0.0 & 0.0 & 1000.0 & 300.0 & 200.0 & 0.0 & 90000.0 \\\\\n", "\t10 & 0.0 & 0.0 & 1000.0 & 450.0 & 200.0 & 0.0 & 105000.0 \\\\\n", "\t11 & 0.0 & 0.0 & 1000.0 & 600.0 & 200.0 & 0.0 & 120000.0 \\\\\n", "\t12 & 0.0 & 0.0 & 1000.0 & 750.0 & 200.0 & 0.0 & 135000.0 \\\\\n", "\t13 & 0.0 & 0.0 & 1000.0 & 900.0 & 200.0 & 0.0 & 150000.0 \\\\\n", "\\end{tabular}\n" ], "text/plain": [ "13×7 DataFrame\n", "│ Row │ Commitment of Generator 1(MW) │ Commitment of Generator 2(MW) │ Dispatch of Generator 1(MW) │ Dispatch of Generator 2(MW) │ Dispatch of Wind(MW) │ Spillage of Wind(MW) │ Total cost($) │\n", "│ │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │ \u001b[90mFloat64\u001b[39m │\n", "├─────┼───────────────────────────────┼───────────────────────────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────┼──────────────────────┼───────────────┤\n", "│ 1 │ 0.0 │ 0.0 │ 0.0 │ 300.0 │ 0.0 │ 200.0 │ 30000.0 │\n", "│ 2 │ 0.0 │ 0.0 │ 150.0 │ 300.0 │ 0.0 │ 200.0 │ 37500.0 │\n", "│ 3 │ 0.0 │ 0.0 │ 300.0 │ 300.0 │ 0.0 │ 200.0 │ 45000.0 │\n", "│ 4 │ 0.0 │ 0.0 │ 450.0 │ 300.0 │ 0.0 │ 200.0 │ 52500.0 │\n", "│ 5 │ 0.0 │ 0.0 │ 600.0 │ 300.0 │ 0.0 │ 200.0 │ 60000.0 │\n", "│ 6 │ 0.0 │ 0.0 │ 750.0 │ 300.0 │ 0.0 │ 200.0 │ 67500.0 │\n", "│ 7 │ 0.0 │ 0.0 │ 900.0 │ 300.0 │ 0.0 │ 200.0 │ 75000.0 │\n", "│ 8 │ 0.0 │ 0.0 │ 1000.0 │ 300.0 │ 50.0 │ 150.0 │ 82500.0 │\n", "│ 9 │ 0.0 │ 0.0 │ 1000.0 │ 300.0 │ 200.0 │ 0.0 │ 90000.0 │\n", "│ 10 │ 0.0 │ 0.0 │ 1000.0 │ 450.0 │ 200.0 │ 0.0 │ 105000.0 │\n", "│ 11 │ 0.0 │ 0.0 │ 1000.0 │ 600.0 │ 200.0 │ 0.0 │ 120000.0 │\n", "│ 12 │ 0.0 │ 0.0 │ 1000.0 │ 750.0 │ 200.0 │ 0.0 │ 135000.0 │\n", "│ 13 │ 0.0 │ 0.0 │ 1000.0 │ 900.0 │ 200.0 │ 0.0 │ 150000.0 │" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "uc_df" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.5.1", "language": "julia", "name": "julia-1.5" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.5.1" } }, "nbformat": 4, "nbformat_minor": 2 }