{ "cells": [ { "cell_type": "markdown", "source": [ "# Hydropower Simulations with [PowerSimulations.jl](https://github.com/NREL-SIIP/PowerSimulations.jl)" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "**Originally Contributed by**: Clayton Barrows and Sourabh Dalvi" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Introduction" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "PowerSimulations.jl supports simulations that consist of sequential optimization problems\n", "where results from previous problems inform subsequent problems in a variety of ways.\n", "This example demonstrates a few of the options for modeling hydropower generation." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "## Dependencies" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using SIIPExamples" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Modeling Packages" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using PowerSystems\n", "using PowerSimulations\n", "const PSI = PowerSimulations\n", "using PowerSystemCaseBuilder" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Data management packages" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using Dates\n", "using DataFrames" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Optimization packages" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "using HiGHS # solver\n", "solver = optimizer_with_attributes(HiGHS.Optimizer, \"mip_rel_gap\" => 0.05)\n", "odir = mktempdir(\".\", cleanup = true) #tmpdir for build steps" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Data\n", "PowerSystemCaseBuilder links to some meaningless test data that is suitable for this example.\n", "We can load three systems of different resolution for the examples here:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "c_sys5_hy_wk = build_system(SIIPExampleSystems, \"5_bus_hydro_wk_sys\")\n", "c_sys5_hy_uc = build_system(SIIPExampleSystems, \"5_bus_hydro_uc_sys\")\n", "c_sys5_hy_ed = build_system(SIIPExampleSystems, \"5_bus_hydro_ed_sys\")\n", "\n", "c_sys5_hy_wk_targets = build_system(SIIPExampleSystems, \"5_bus_hydro_wk_sys_with_targets\")\n", "c_sys5_hy_uc_targets = build_system(SIIPExampleSystems, \"5_bus_hydro_uc_sys_with_targets\")\n", "c_sys5_hy_ed_targets = build_system(SIIPExampleSystems, \"5_bus_hydro_ed_sys_with_targets\")" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "This line just overloads JuMP printing to remove double underscores added by PowerSimulations.jl" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.JuMP._wrap_in_math_mode(str) = \"\\$\\$ $(replace(str, \"__\"=>\"\")) \\$\\$\"" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "## Two PowerSimulations features determine hydropower representation.\n", "There are two principal ways that we can customize hydropower representation in\n", "PowerSimulations. First, we can play with the formulation applied to hydropower generators\n", "using the `DeviceModel`. We can also adjust how simulations are configured to represent\n", "different decision making processes and the information flow between those processes." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "### Hydropower `DeviceModel`s" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "First, the assignment of device formulations to particular device types gives us control\n", "over the representation of devices. This is accomplished by defining `DeviceModel`\n", "instances. For hydro power representations, we have two available generator types in\n", "PowerSystems:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "print_tree(HydroGen)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And in PowerSimulations, we have several available formulations that can be applied to\n", "the hydropower generation devices:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "print_tree(PSI.AbstractHydroFormulation)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Let's see what some of the different combinations create. First, let's apply the\n", "`HydroDispatchRunOfRiver` formulation to the `HydroEnergyReservoir` generators, and the\n", "`FixedOutput` formulation to `HydroDispatch` generators.\n", " - The `FixedOutput` formulation just acts\n", "like a load subtractor, forcing the system to accept it's generation.\n", " - The `HydroDispatchRunOfRiver` formulation represents the the energy flowing out of\n", "a reservoir. The model can choose to produce power with that energy or just let it spill by." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "template = ProblemTemplate()\n", "set_device_model!(template, HydroEnergyReservoir, HydroDispatchRunOfRiver)\n", "set_device_model!(template, HydroDispatch, FixedOutput)\n", "set_device_model!(template, PowerLoad, StaticPowerLoad)\n", "\n", "prob = DecisionModel(template, c_sys5_hy_uc, horizon = 2)\n", "build!(prob, output_dir = odir)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Now we can see the resulting JuMP model:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(prob)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "The first two constraints are the power balance constraints that require the generation\n", "from the controllable `HydroEnergyReservoir` generators to be equal to the load (flat 10.0 for all time periods)\n", "minus the generation from the `HydroDispatch` generators [1.97, 1.983, ...]. The 3rd and 4th\n", "constraints limit the output of the `HydroEnergyReservoir` generator to the limit defined by the\n", "`max_activepwoer` time series. And the last 4 constraints are the lower and upper bounds of\n", "the `HydroEnergyReservoir` operating range." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "Next, let's apply the `HydroDispatchReservoirBudget` formulation to the `HydroEnergyReservoir` generators." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "template = ProblemTemplate()\n", "set_device_model!(template, HydroEnergyReservoir, HydroDispatchReservoirBudget)\n", "set_device_model!(template, PowerLoad, StaticPowerLoad)\n", "\n", "prob = DecisionModel(template, c_sys5_hy_uc, horizon = 2)\n", "build!(prob, output_dir = odir)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And, the resulting JuMP model:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(prob)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "Finally, let's apply the `HydroDispatchReservoirStorage` formulation to the `HydroEnergyReservoir` generators." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "template = ProblemTemplate()\n", "set_device_model!(template, HydroEnergyReservoir, HydroDispatchReservoirStorage)\n", "set_device_model!(template, PowerLoad, StaticPowerLoad)\n", "\n", "prob = DecisionModel(template, c_sys5_hy_uc_targets, horizon = 24)\n", "build!(prob, output_dir = odir)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(prob)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "### Multi-Stage `SimulationSequence`\n", "The purpose of a multi-stage simulation is to represent scheduling decisions consistently\n", "with the time scales that govern different elements of power systems." ], "metadata": {} }, { "cell_type": "markdown", "source": [ "#### Multi-Day to Daily Simulation:\n", "In the multi-day model, we'll use a really simple representation of all system devices\n", "so that we can maintain computational tractability while getting an estimate of system\n", "requirements/capabilities." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "template_md = ProblemTemplate()\n", "set_device_model!(template_md, ThermalStandard, ThermalStandardDispatch)\n", "set_device_model!(template_md, PowerLoad, StaticPowerLoad)\n", "set_device_model!(template_md, HydroEnergyReservoir, HydroDispatchReservoirStorage)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "For the daily model, we can increase the modeling detail since we'll be solving shorter\n", "problems." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "template_da = ProblemTemplate()\n", "set_device_model!(template_da, ThermalStandard, ThermalStandardDispatch)\n", "set_device_model!(template_da, PowerLoad, StaticPowerLoad)\n", "set_device_model!(template_da, HydroEnergyReservoir, HydroDispatchReservoirStorage)" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "problems = SimulationModels(\n", " decision_models = [\n", " DecisionModel(\n", " template_md,\n", " c_sys5_hy_wk_targets,\n", " name = \"MD\",\n", " optimizer = solver,\n", " system_to_file = false,\n", " ),\n", " DecisionModel(\n", " template_da,\n", " c_sys5_hy_uc_targets,\n", " name = \"DA\",\n", " optimizer = solver,\n", " system_to_file = false,\n", " ),\n", " ],\n", ")" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "This builds the sequence and passes the the energy dispatch schedule for the `HydroEnergyReservoir`\n", "generator from the \"MD\" problem to the \"DA\" problem in the form of an energy limit over the\n", "synchronized periods." ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "sequence = SimulationSequence(\n", " models = problems,\n", " feedforwards = Dict(\n", " \"DA\" => [\n", " EnergyLimitFeedforward(\n", " component_type = HydroEnergyReservoir,\n", " source = ActivePowerVariable,\n", " affected_values = [ActivePowerVariable],\n", " number_of_periods = get_forecast_horizon(c_sys5_hy_uc_targets),\n", " ),\n", " ],\n", " ),\n", " ini_cond_chronology = IntraProblemChronology(),\n", ");" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "sim = Simulation(\n", " name = \"hydro\",\n", " steps = 1,\n", " models = problems,\n", " sequence = sequence,\n", " simulation_folder = odir,\n", ")\n", "\n", "build!(sim)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "We can look at the \"MD\" Model" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(sim.models.decision_models[1])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And we can look at the \"DA\" model" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(sim.models.decision_models[2])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And we can execute the simulation by running the following command\n", "```julia\n", "execute!(sim)\n", "```" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "#### 3-Stage Simulation:" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "transform_single_time_series!(c_sys5_hy_wk, 2, Hour(24)) # TODO fix PSI to enable longer intervals of stage 1\n", "\n", "problems = SimulationModels(\n", " decision_models = [\n", " DecisionModel(\n", " template_md,\n", " c_sys5_hy_wk_targets,\n", " name = \"MD\",\n", " optimizer = solver,\n", " system_to_file = false,\n", " ),\n", " DecisionModel(\n", " template_da,\n", " c_sys5_hy_uc_targets,\n", " name = \"DA\",\n", " optimizer = solver,\n", " system_to_file = false,\n", " ),\n", " DecisionModel(\n", " template_da,\n", " c_sys5_hy_ed_targets,\n", " name = \"ED\",\n", " optimizer = solver,\n", " system_to_file = false,\n", " ),\n", " ],\n", ")\n", "\n", "sequence = SimulationSequence(\n", " models = problems,\n", " feedforwards = Dict(\n", " \"DA\" => [\n", " EnergyLimitFeedforward(\n", " component_type = HydroEnergyReservoir,\n", " source = ActivePowerVariable,\n", " affected_values = [ActivePowerVariable],\n", " number_of_periods = get_forecast_horizon(c_sys5_hy_uc_targets),\n", " ),\n", " ],\n", " \"ED\" => [\n", " EnergyLimitFeedforward(\n", " component_type = HydroEnergyReservoir,\n", " source = ActivePowerVariable,\n", " affected_values = [ActivePowerVariable],\n", " number_of_periods = get_forecast_horizon(c_sys5_hy_ed_targets),\n", " ),\n", " ],\n", " ),\n", " ini_cond_chronology = IntraProblemChronology(),\n", ");" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "sim = Simulation(\n", " name = \"hydro\",\n", " steps = 1,\n", " models = problems,\n", " sequence = sequence,\n", " simulation_folder = odir,\n", ")" ], "metadata": {}, "execution_count": null }, { "outputs": [], "cell_type": "code", "source": [ "build!(sim)" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "We can look at the \"MD\" Model" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(sim.models.decision_models[1])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And we can look at the \"DA\" model" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(sim.models.decision_models[2])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And we can look at the \"ED\" model" ], "metadata": {} }, { "outputs": [], "cell_type": "code", "source": [ "PSI.get_jump_model(sim.models.decision_models[3])" ], "metadata": {}, "execution_count": null }, { "cell_type": "markdown", "source": [ "And we can execute the simulation by running the following command\n", "```julia\n", "execute!(sim)\n", "```" ], "metadata": {} }, { "cell_type": "markdown", "source": [ "---\n", "\n", "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" ], "metadata": {} } ], "nbformat_minor": 3, "metadata": { "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.7.2" }, "kernelspec": { "name": "julia-1.7", "display_name": "Julia 1.7.2", "language": "julia" } }, "nbformat": 4 }