You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
20 KiB
175 lines
20 KiB
<!DOCTYPE html>
|
|
<html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>Market Clearing · UnitCommitment.jl</title><meta name="title" content="Market Clearing · UnitCommitment.jl"/><meta property="og:title" content="Market Clearing · UnitCommitment.jl"/><meta property="twitter:title" content="Market Clearing · UnitCommitment.jl"/><meta name="description" content="Documentation for UnitCommitment.jl."/><meta property="og:description" content="Documentation for UnitCommitment.jl."/><meta property="twitter:description" content="Documentation for UnitCommitment.jl."/><script data-outdated-warner src="../../assets/warner.js"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/lato-font/3.0.0/css/lato-font.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/juliamono/0.050/juliamono.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/fontawesome.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/solid.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/brands.min.css" rel="stylesheet" type="text/css"/><link href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.css" rel="stylesheet" type="text/css"/><script>documenterBaseURL="../.."</script><script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js" data-main="../../assets/documenter.js"></script><script src="../../search_index.js"></script><script src="../../siteinfo.js"></script><script src="../../../versions.js"></script><link class="docs-theme-link" rel="stylesheet" type="text/css" href="../../assets/themes/documenter-dark.css" data-theme-name="documenter-dark" data-theme-primary-dark/><link class="docs-theme-link" rel="stylesheet" type="text/css" href="../../assets/themes/documenter-light.css" data-theme-name="documenter-light" data-theme-primary/><script src="../../assets/themeswap.js"></script><link href="../../assets/custom.css" rel="stylesheet" type="text/css"/></head><body><div id="documenter"><nav class="docs-sidebar"><div class="docs-package-name"><span class="docs-autofit"><a href="../../">UnitCommitment.jl</a></span></div><button class="docs-search-query input is-rounded is-small is-clickable my-2 mx-auto py-1 px-2" id="documenter-search-query">Search docs (Ctrl + /)</button><ul class="docs-menu"><li><a class="tocitem" href="../../">Home</a></li><li><span class="tocitem">Tutorials</span><ul><li><a class="tocitem" href="../usage/">Getting started</a></li><li><a class="tocitem" href="../customizing/">Model customization</a></li><li><a class="tocitem" href="../lmp/">Locational Marginal Prices</a></li><li class="is-active"><a class="tocitem" href>Market Clearing</a><ul class="internal"><li><a class="tocitem" href="#A-small-example"><span>A small example</span></a></li><li><a class="tocitem" href="#Customizing-the-model-and-LMPs"><span>Customizing the model and LMPs</span></a></li><li><a class="tocitem" href="#Additional-considerations"><span>Additional considerations</span></a></li></ul></li><li><a class="tocitem" href="../decomposition/">Decomposition methods</a></li></ul></li><li><span class="tocitem">User guide</span><ul><li><a class="tocitem" href="../../guides/problem/">Problem definition</a></li><li><a class="tocitem" href="../../guides/format/">JSON data format</a></li><li><a class="tocitem" href="../../guides/instances/">Benchmark instances</a></li></ul></li><li><a class="tocitem" href="../../api/">API Reference</a></li></ul><div class="docs-version-selector field has-addons"><div class="control"><span class="docs-label button is-static is-size-7">Version</span></div><div class="docs-selector control is-expanded"><div class="select is-fullwidth is-size-7"><select id="documenter-version-selector"></select></div></div></div></nav><div class="docs-main"><header class="docs-navbar"><a class="docs-sidebar-button docs-navbar-link fa-solid fa-bars is-hidden-desktop" id="documenter-sidebar-button" href="#"></a><nav class="breadcrumb"><ul class="is-hidden-mobile"><li><a class="is-disabled">Tutorials</a></li><li class="is-active"><a href>Market Clearing</a></li></ul><ul class="is-hidden-tablet"><li class="is-active"><a href>Market Clearing</a></li></ul></nav><div class="docs-right"><a class="docs-navbar-link" href="https://github.com/ANL-CEEESA/UnitCommitment.jl" title="View the repository on GitHub"><span class="docs-icon fa-brands"></span><span class="docs-label is-hidden-touch">GitHub</span></a><a class="docs-navbar-link" href="https://github.com/ANL-CEEESA/UnitCommitment.jl/blob/dev/docs/src/tutorials/market.jl" title="Edit source on GitHub"><span class="docs-icon fa-solid"></span></a><a class="docs-settings-button docs-navbar-link fa-solid fa-gear" id="documenter-settings-button" href="#" title="Settings"></a><a class="docs-article-toggle-button fa-solid fa-chevron-up" id="documenter-article-toggle-button" href="javascript:;" title="Collapse all docstrings"></a></div></header><article class="content" id="documenter-page"><h1 id="Market-Clearing"><a class="docs-heading-anchor" href="#Market-Clearing">Market Clearing</a><a id="Market-Clearing-1"></a><a class="docs-heading-anchor-permalink" href="#Market-Clearing" title="Permalink"></a></h1><p>In North America, electricity markets are structured around two primary types of markets: the day-ahead (DA) market and the real-time (RT) market. The DA market schedules electricity generation and consumption for the next day, based on forecasts and bids from electricity suppliers and consumers. The RT market, on the other hand, operates continuously throughout the day, addressing the discrepancies between the DA schedule and actual demand, typically every five minutes. UnitCommitment.jl is able to simulate the DA and RT market clearing process. Specifically, the package provides the function <code>UnitCommitment.solve_market</code> which performs the following steps:</p><ol><li>Solve the DA market problem.</li><li>Extract commitment status of all generators.</li><li>Solve a sequence of RT market problems, fixing the commitment status of each generator to the corresponding optimal solution of the DA problem.</li></ol><p>To use this function, we need to prepare an instance file corresponding to the DA market problem and multiple instance files corresponding to the RT market problems. The number of required files depends on the time granularity and window. For example, suppose that the DA problem is solved at hourly granularity and has 24 time periods, whereas the RT problems are solved at 5-minute granularity and have a single time period. Then we would need to prepare one files for the DA problem and 288 files <span>$\left(24 \times \frac{60}{5}\right)$</span> for the RT market problems.</p><h2 id="A-small-example"><a class="docs-heading-anchor" href="#A-small-example">A small example</a><a id="A-small-example-1"></a><a class="docs-heading-anchor-permalink" href="#A-small-example" title="Permalink"></a></h2><p>For simplicity, in this tutorial we illustate the usage of <code>UnitCommitment.solve_market</code> with a very small example, in which the DA problem has only two time periods. We start by creating the DA instance file:</p><pre><code class="language-julia hljs">da_contents = """
|
|
{
|
|
"Parameters": {
|
|
"Version": "0.4",
|
|
"Time horizon (h)": 2
|
|
},
|
|
"Buses": {
|
|
"b1": {
|
|
"Load (MW)": [200, 400]
|
|
}
|
|
},
|
|
"Generators": {
|
|
"g1": {
|
|
"Bus": "b1",
|
|
"Type": "Thermal",
|
|
"Production cost curve (MW)": [0, 200],
|
|
"Production cost curve (\$)": [0, 1000],
|
|
"Initial status (h)": -24,
|
|
"Initial power (MW)": 0
|
|
},
|
|
"g2": {
|
|
"Bus": "b1",
|
|
"Type": "Thermal",
|
|
"Production cost curve (MW)": [0, 300],
|
|
"Production cost curve (\$)": [0, 3000],
|
|
"Initial status (h)": -24,
|
|
"Initial power (MW)": 0
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
open("da.json", "w") do file
|
|
return write(file, da_contents)
|
|
end;</code></pre><p>Next, we create eight single-period RT market problems, each one with a 15-minute time granularity:</p><pre><code class="language-julia hljs">for i in 1:8
|
|
rt_contents = """
|
|
{
|
|
"Parameters": {
|
|
"Version": "0.4",
|
|
"Time horizon (min)": 15,
|
|
"Time step (min)": 15
|
|
},
|
|
"Buses": {
|
|
"b1": {
|
|
"Load (MW)": [$(150 + 50 * i)]
|
|
}
|
|
},
|
|
"Generators": {
|
|
"g1": {
|
|
"Bus": "b1",
|
|
"Type": "Thermal",
|
|
"Production cost curve (MW)": [0, 200],
|
|
"Production cost curve (\$)": [0, 1000],
|
|
"Initial status (h)": -24,
|
|
"Initial power (MW)": 0
|
|
},
|
|
"g2": {
|
|
"Bus": "b1",
|
|
"Type": "Thermal",
|
|
"Production cost curve (MW)": [0, 300],
|
|
"Production cost curve (\$)": [0, 3000],
|
|
"Initial status (h)": -24,
|
|
"Initial power (MW)": 0
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
open("rt_$i.json", "w") do file
|
|
return write(file, rt_contents)
|
|
end
|
|
end</code></pre><p>Finally, we call <code>UnitCommitment.solve_market</code>, providing as arguments (1) the path to the DA problem; (2) a list of paths to the RT problems; (3) the mixed-integer linear optimizer.</p><pre><code class="language-julia hljs">using UnitCommitment
|
|
using HiGHS
|
|
|
|
solution = UnitCommitment.solve_market(
|
|
"da.json",
|
|
[
|
|
"rt_1.json",
|
|
"rt_2.json",
|
|
"rt_3.json",
|
|
"rt_4.json",
|
|
"rt_5.json",
|
|
"rt_6.json",
|
|
"rt_7.json",
|
|
"rt_8.json",
|
|
],
|
|
optimizer = HiGHS.Optimizer,
|
|
)</code></pre><pre class="documenter-example-output"><code class="nohighlight hljs ansi">OrderedCollections.OrderedDict{Any, Any} with 2 entries:
|
|
"DA" => OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"=>[2…
|
|
"RT" => Any[OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"…</code></pre><p>To retrieve the day-ahead market solution, we can query <code>solution["DA"]</code>:</p><pre><code class="language-julia hljs">@show solution["DA"]</code></pre><pre class="documenter-example-output"><code class="nohighlight hljs ansi">OrderedCollections.OrderedDict{Any, Any} with 15 entries:
|
|
"Thermal production (MW)" => OrderedDict("g1"=>[200.0, 200.0], "g2"=>…
|
|
"Thermal production cost (\$)" => OrderedDict("g1"=>[1000.0, 1000.0], "g2"…
|
|
"Startup cost (\$)" => OrderedDict("g1"=>[0.0, 0.0], "g2"=>[0.0…
|
|
"Is on" => OrderedDict("g1"=>[1.0, 1.0], "g2"=>[1.0…
|
|
"Switch on" => OrderedDict("g1"=>[1.0, 0.0], "g2"=>[1.0…
|
|
"Switch off" => OrderedDict("g1"=>[0.0, 0.0], "g2"=>[0.0…
|
|
"Net injection (MW)" => OrderedDict("b1"=>[0.0, 0.0])
|
|
"Load curtail (MW)" => OrderedDict("b1"=>[0.0, 0.0])
|
|
"Spinning reserve (MW)" => OrderedDict{Any, Any}()
|
|
"Spinning reserve shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"Up-flexiramp (MW)" => OrderedDict{Any, Any}()
|
|
"Up-flexiramp shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"Down-flexiramp (MW)" => OrderedDict{Any, Any}()
|
|
"Down-flexiramp shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"LMP (\$/MW)" => OrderedDict(("s1", "b1", 1)=>5.0, ("s1",…</code></pre><p>To query each real-time market solution, we can query <code>solution["RT"][i]</code>. Note that LMPs are automativally calculated.</p><pre><code class="language-julia hljs">@show solution["RT"][1]</code></pre><pre class="documenter-example-output"><code class="nohighlight hljs ansi">OrderedCollections.OrderedDict{Any, Any} with 15 entries:
|
|
"Thermal production (MW)" => OrderedDict("g1"=>[200.0], "g2"=>[0.0])
|
|
"Thermal production cost (\$)" => OrderedDict("g1"=>[1000.0], "g2"=>[0.0])
|
|
"Startup cost (\$)" => OrderedDict("g1"=>[0.0], "g2"=>[0.0])
|
|
"Is on" => OrderedDict("g1"=>[1.0], "g2"=>[1.0])
|
|
"Switch on" => OrderedDict("g1"=>[1.0], "g2"=>[1.0])
|
|
"Switch off" => OrderedDict("g1"=>[0.0], "g2"=>[0.0])
|
|
"Net injection (MW)" => OrderedDict("b1"=>[0.0])
|
|
"Load curtail (MW)" => OrderedDict("b1"=>[0.0])
|
|
"Spinning reserve (MW)" => OrderedDict{Any, Any}()
|
|
"Spinning reserve shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"Up-flexiramp (MW)" => OrderedDict{Any, Any}()
|
|
"Up-flexiramp shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"Down-flexiramp (MW)" => OrderedDict{Any, Any}()
|
|
"Down-flexiramp shortfall (MW)" => OrderedDict{Any, Any}()
|
|
"LMP (\$/MW)" => OrderedDict(("s1", "b1", 1)=>5.0)</code></pre><h2 id="Customizing-the-model-and-LMPs"><a class="docs-heading-anchor" href="#Customizing-the-model-and-LMPs">Customizing the model and LMPs</a><a id="Customizing-the-model-and-LMPs-1"></a><a class="docs-heading-anchor-permalink" href="#Customizing-the-model-and-LMPs" title="Permalink"></a></h2><p>When using the <code>solve_market</code> function it is still possible to customize the problem formulation and the LMP calculation method. In the next example, we use a custom formulation and explicitly specify the LMP method through the <code>settings</code> keyword argument:</p><pre><code class="language-julia hljs">UnitCommitment.solve_market(
|
|
"da.json",
|
|
[
|
|
"rt_1.json",
|
|
"rt_2.json",
|
|
"rt_3.json",
|
|
"rt_4.json",
|
|
"rt_5.json",
|
|
"rt_6.json",
|
|
"rt_7.json",
|
|
"rt_8.json",
|
|
],
|
|
settings = UnitCommitment.MarketSettings(
|
|
lmp_method = UnitCommitment.ConventionalLMP(),
|
|
formulation = UnitCommitment.Formulation(
|
|
pwl_costs = UnitCommitment.KnuOstWat2018.PwlCosts(),
|
|
ramping = UnitCommitment.MorLatRam2013.Ramping(),
|
|
startup_costs = UnitCommitment.MorLatRam2013.StartupCosts(),
|
|
transmission = UnitCommitment.ShiftFactorsFormulation(
|
|
isf_cutoff = 0.008,
|
|
lodf_cutoff = 0.003,
|
|
),
|
|
),
|
|
),
|
|
optimizer = HiGHS.Optimizer,
|
|
)</code></pre><pre class="documenter-example-output"><code class="nohighlight hljs ansi">OrderedCollections.OrderedDict{Any, Any} with 2 entries:
|
|
"DA" => OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"=>[2…
|
|
"RT" => Any[OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"…</code></pre><p>It is also possible to add custom variables and constraints to either the DA or RT market problems, through the usage of <code>after_build_da</code> and <code>after_build_rt</code> callback functions. Similarly, the <code>after_optimize_da</code> and <code>after_optimize_rt</code> can be used to directly analyze the JuMP models, after they have been optimized:</p><pre><code class="language-julia hljs">using JuMP
|
|
|
|
function after_build_da(model, instance)
|
|
@constraint(model, model[:is_on]["g1", 1] <= model[:is_on]["g2", 1])
|
|
end
|
|
|
|
function after_optimize_da(solution, model, instance)
|
|
@show value(model[:is_on]["g1", 1])
|
|
end
|
|
|
|
UnitCommitment.solve_market(
|
|
"da.json",
|
|
[
|
|
"rt_1.json",
|
|
"rt_2.json",
|
|
"rt_3.json",
|
|
"rt_4.json",
|
|
"rt_5.json",
|
|
"rt_6.json",
|
|
"rt_7.json",
|
|
"rt_8.json",
|
|
],
|
|
after_build_da = after_build_da,
|
|
after_optimize_da = after_optimize_da,
|
|
optimizer = HiGHS.Optimizer,
|
|
)</code></pre><pre class="documenter-example-output"><code class="nohighlight hljs ansi">OrderedCollections.OrderedDict{Any, Any} with 2 entries:
|
|
"DA" => OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"=>[2…
|
|
"RT" => Any[OrderedDict{Any, Any}("Thermal production (MW)"=>OrderedDict("g1"…</code></pre><h2 id="Additional-considerations"><a class="docs-heading-anchor" href="#Additional-considerations">Additional considerations</a><a id="Additional-considerations-1"></a><a class="docs-heading-anchor-permalink" href="#Additional-considerations" title="Permalink"></a></h2><ul><li>UC.jl supports two-stage stochastic DA market problems. In this case, we need one file for each DA market scenario. All RT market problems must be deterministic.</li><li>UC.jl also supports multi-period RT market problems. Assume, for example, that the DA market problem is an hourly problem with 24 time periods, whereas the RT market problem uses 5-minute granularity with 4 time periods. UC.jl assumes that the first RT file covers period <code>0:00</code> to <code>0:20</code>, the second covers <code>0:05</code> to <code>0:25</code> and so on. We therefore still need 288 RT market files. To avoid going beyond the 24-hour period covered by the DA market solution, however, the last few RT market problems must have only 3, 2, and 1 time periods, covering <code>23:45</code> to <code>24:00</code>, <code>23:50</code> to <code>24:00</code> and <code>23:55</code> to <code>24:00</code>, respectively.</li><li>Some MILP solvers (such as Cbc) have issues handling linear programming problems, which are required for the RT market. In this case, a separate linear programming solver can be provided to <code>solve_market</code> using the <code>lp_optimizer</code> argument. For example, <code>solve_market(da_file, rt_files, optimizer=Cbc.Optimizer, lp_optimizer=Clp.Optimizer)</code>.</li></ul></article><nav class="docs-footer"><a class="docs-footer-prevpage" href="../lmp/">« Locational Marginal Prices</a><a class="docs-footer-nextpage" href="../decomposition/">Decomposition methods »</a><div class="flexbox-break"></div><p class="footer-message">Powered by <a href="https://github.com/JuliaDocs/Documenter.jl">Documenter.jl</a> and the <a href="https://julialang.org/">Julia Programming Language</a>.</p></nav></div><div class="modal" id="documenter-settings"><div class="modal-background"></div><div class="modal-card"><header class="modal-card-head"><p class="modal-card-title">Settings</p><button class="delete"></button></header><section class="modal-card-body"><p><label class="label">Theme</label><div class="select"><select id="documenter-themepicker"><option value="documenter-light">documenter-light</option><option value="documenter-dark">documenter-dark</option><option value="auto">Automatic (OS)</option></select></div></p><hr/><p>This document was generated with <a href="https://github.com/JuliaDocs/Documenter.jl">Documenter.jl</a> version 1.2.1 on <span class="colophon-date" title="Tuesday 21 May 2024 10:39">Tuesday 21 May 2024</span>. Using Julia version 1.10.3.</p></section><footer class="modal-card-foot"></footer></div></div></div></body></html>
|