diff --git a/.github/workflows/doc-preview-cleanup.yml b/.github/workflows/doc-preview-cleanup.yml new file mode 100644 index 00000000..73f291a2 --- /dev/null +++ b/.github/workflows/doc-preview-cleanup.yml @@ -0,0 +1,34 @@ +name: Doc Preview Cleanup + +on: + pull_request: + types: [closed] + +# Ensure that only one "Doc Preview Cleanup" workflow is force pushing at a time +concurrency: + group: doc-preview-cleanup + cancel-in-progress: false + +jobs: + doc-preview-cleanup: + runs-on: ubuntu-latest + # This workflow pushes to gh-pages; permissions are per-job and independent of docs.yml + permissions: + contents: write + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v4 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "${preview_dir}" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "${preview_dir}" + git commit -m "delete preview" + git branch gh-pages-new "$(echo "delete history" | git commit-tree "HEAD^{tree}")" + git push --force origin gh-pages-new:gh-pages + fi + env: + preview_dir: previews/PR${{ github.event.number }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91c9631e..65a8ddce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contributing Community driven development of this package is encouraged. To maintain code quality standards, please adhere to the following guidlines when contributing: - - To get started, sign the Contributor License Agreement. - - Please do your best to adhere to our [coding style guide](docs/src/developer/style.md). - - To submit code contributions, [fork](https://help.github.com/articles/fork-a-repo/) the repository, commit your changes, and [submit a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). + + - To get started, sign the Contributor License Agreement. + - Please do your best to adhere to our [coding style guide](docs/src/developer/style.md). + - To submit code contributions, [fork](https://help.github.com/articles/fork-a-repo/) the repository, commit your changes, and [submit a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). diff --git a/docs/Project.toml b/docs/Project.toml index aad3e873..670345e3 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,9 +1,13 @@ [deps] +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" HybridSystemsSimulations = "bed98974-b02a-5e2f-9ee0-a103f5c450dd" +Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" +PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" [compat] -Documenter = "0.27" +Documenter = "1.0" julia = "^1.6" diff --git a/docs/make.jl b/docs/make.jl index 0670e4bb..dbe5d4dd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,31 +1,49 @@ -using Documenter, HybridSystemsSimulations -import DataStructures: OrderedDict +using Documenter +using HybridSystemsSimulations +using DataStructures +using DocumenterInterLinks +using Literate + +const _DOCS_BASE_URL = "https://nrel-sienna.github.io/HybridSystemsSimulations.jl/stable" + +links = InterLinks( + "Julia" => "https://docs.julialang.org/en/v1/", + "InfrastructureSystems" => "https://nrel-sienna.github.io/InfrastructureSystems.jl/stable/", + "PowerSystems" => "https://nrel-sienna.github.io/PowerSystems.jl/stable/", + "PowerSimulations" => "https://nrel-sienna.github.io/PowerSimulations.jl/stable/", +) + +include(joinpath(@__DIR__, "make_tutorials.jl")) +make_tutorials() pages = OrderedDict( "Welcome Page" => "index.md", - "Quick Start Guide" => "quick_start_guide.md", - "Tutorials" => "tutorials/intro_page.md", - "Public API Reference" => "api/public.md", - "Internal API Reference" => "api/internal.md", + "Tutorials" => Any[], + "Reference" => Any[ + "Public API" => "api/public.md", + "Internals" => "api/internal.md", + ], ) -makedocs( - modules=[HybridSystemsSimulations], - format=Documenter.HTML(; - mathengine=Documenter.MathJax(), - prettyurls=haskey(ENV, "GITHUB_ACTIONS"), +makedocs(; + modules = [HybridSystemsSimulations], + format = Documenter.HTML(; + mathengine = Documenter.MathJax(), + prettyurls = haskey(ENV, "GITHUB_ACTIONS"), + size_threshold = nothing, ), - sitename="HybridSystemsSimulations.jl", - authors="Jose Daniel Lara, Rodrigo Henriquez-Auba", - pages=Any[p for p in pages], + sitename = "HybridSystemsSimulations.jl", + authors = "Jose Daniel Lara, Rodrigo Henriquez-Auba", + pages = Any[p for p in pages], + plugins = [links], ) -deploydocs( - repo="github.com/NREL-Sienna/HybridSystemsSimulations.jl.git", - target="build", - branch="gh-pages", - devbranch="main", - devurl="dev", - push_preview=true, - versions=["stable" => "v^", "v#.#"], +deploydocs(; + repo = "github.com/NREL-Sienna/HybridSystemsSimulations.jl.git", + target = "build", + branch = "gh-pages", + devbranch = "main", + devurl = "dev", + push_preview = true, + versions = ["stable" => "v^", "v#.#"], ) diff --git a/docs/make_tutorials.jl b/docs/make_tutorials.jl new file mode 100644 index 00000000..54a86f61 --- /dev/null +++ b/docs/make_tutorials.jl @@ -0,0 +1,364 @@ +using Pkg +using Literate +using DataFrames +using PrettyTables + +# Override show for DataFrames to limit output size during doc builds +# This ensures large DataFrames are truncated when displayed as expression results in @example blocks +# Explicit show() calls in tutorials with their own arguments are NOT affected (they use their own kwargs) +# We override both text/plain and text/html since Documenter may use either +# +# Strategy: Call PrettyTables.pretty_table directly with explicit row/column limits. +# This bypasses DataFrames' default display logic and gives us full control. + +function Base.show(io::IO, mime::MIME"text/plain", df::DataFrame) + # Call PrettyTables directly with row/column limits + # This ensures only 10 rows are shown regardless of DataFrame size + PrettyTables.pretty_table(io, df; + backend = :text, + maximum_number_of_rows = 10, + maximum_number_of_columns = 80, + show_omitted_cell_summary = true, + compact_printing = false, + limit_printing = true) +end + +function Base.show(io::IO, mime::MIME"text/html", df::DataFrame) + # For HTML output (which Documenter prefers for large outputs) + # Use PrettyTables HTML backend with explicit row/column limits + PrettyTables.pretty_table(io, df; + backend = :html, + maximum_number_of_rows = 10, + maximum_number_of_columns = 80, + show_omitted_cell_summary = true, + compact_printing = false, + limit_printing = true) +end + +# Function to clean up old generated files +function clean_old_generated_files(dir::String) + if !isdir(dir) + @warn "Directory does not exist: $dir" + return + end + generated_files = filter( + f -> + startswith(f, "generated_") && + (endswith(f, ".md") || endswith(f, ".ipynb")), + readdir(dir), + ) + for file in generated_files + rm(joinpath(dir, file); force = true) + @info "Removed old generated file: $file" + end +end + +######################################################### +# Literate post-processing functions for tutorial generation +######################################################### + +# postprocess function to insert md +function insert_md(content) + m = match(r"APPEND_MARKDOWN\(\"(.*)\"\)", content) + if !isnothing(m) + md_content = read(m.captures[1], String) + content = replace(content, r"APPEND_MARKDOWN\(\"(.*)\"\)" => md_content) + end + return content +end + +# Default display titles for Documenter admonition types when no custom title is given. +# See https://documenter.juliadocs.org/stable/showcase/#Admonitions +const _ADMONITION_DISPLAY_NAMES = Dict{String, String}( + "note" => "Note", + "info" => "Info", + "tip" => "Tip", + "warning" => "Warning", + "danger" => "Danger", + "compat" => "Compat", + "todo" => "TODO", + "details" => "Details", +) + +# Preprocess Literate source to convert Documenter-style admonitions into Jupyter-friendly +# blockquotes. Used only for notebook output; markdown keeps `!!! type` and is rendered by +# Documenter. Admonitions are not recognized by common mark or Jupyter; see +# https://fredrikekre.github.io/Literate.jl/v2/tips/#admonitions-compatibility +function preprocess_admonitions_for_notebook(str::AbstractString) + lines = split(str, '\n'; keepempty = true) + out = String[] + i = 1 + n = length(lines) + admonition_start = + r"^# !!! (note|info|tip|warning|danger|compat|todo|details)(?:\s+\"([^\"]*)\")?\s*$" + content_line = r"^# (.*)$" # Documenter admonition body: # then 4 spaces + blank_comment = r"^#\s*$" # # or # with only spaces + + while i <= n + line = lines[i] + m = match(admonition_start, line) + if m !== nothing + typ = lowercase(m.captures[1]) + custom_title = m.captures[2] + title = if custom_title !== nothing && !isempty(custom_title) + custom_title + else + get(_ADMONITION_DISPLAY_NAMES, typ, titlecase(typ)) + end + push!(out, "# > *$(title)*") + push!(out, "# >") + i += 1 + # Consume blank comment lines and content lines + while i <= n + l = lines[i] + if match(blank_comment, l) !== nothing + push!(out, "# >") + i += 1 + elseif (cm = match(content_line, l)) !== nothing + push!(out, "# > " * cm.captures[1]) + i += 1 + else + break + end + end + continue + end + push!(out, line) + i += 1 + end + return join(out, '\n') +end + +# Function to add download links to generated markdown +function add_download_links(content, jl_file, ipynb_file) + # Add download links at the top of the file after the first heading + download_section = """ + +*To follow along, you can download this tutorial as a [Julia script (.jl)]($(jl_file)) or [Jupyter notebook (.ipynb)]($(ipynb_file)).* + +""" + # Insert after the first heading (which should be the title) + # Match the first heading line and replace it with heading + download section + m = match(r"^(#+ .+)$"m, content) + if m !== nothing + heading = m.match + content = replace(content, r"^(#+ .+)$"m => heading * download_section; count = 1) + end + return content +end + +# Function to add Pkg.status() to notebook within the first markdown cell +function add_pkg_status_to_notebook(nb::Dict) + cells = get(nb, "cells", []) + if isempty(cells) + return nb + end + + # Find the first markdown cell + first_markdown_idx = nothing + for (i, cell) in enumerate(cells) + if get(cell, "cell_type", "") == "markdown" + first_markdown_idx = i + break + end + end + + if first_markdown_idx === nothing + return nb # No markdown cell found, return unchanged + end + + first_cell = cells[first_markdown_idx] + cell_source = get(first_cell, "source", []) + + # Convert source array to string to find the first heading + source_text = join(cell_source) + + # Find the first heading (lines starting with #) + heading_pattern = r"^(#+\s+.+?)$"m + heading_match = match(heading_pattern, source_text) + + if heading_match === nothing + return nb # No heading found, return unchanged + end + + # Capture Pkg.status() output at build time + io = IOBuffer() + Pkg.status(; io = io) + pkg_status_output = String(take!(io)) + + # Create the content to insert: blockquote "Set up" with setup instructions and pkg.status() + # Blockquote title and body; hyperlinks for IJulia and create an environment + preface_lines = [ + "\n", + "> **Set up**\n", + ">\n", + "> To run this notebook, first install the Julia kernel for Jupyter Notebooks using [IJulia](https://julialang.github.io/IJulia.jl/stable/manual/installation/), then [create an environment](https://pkgdocs.julialang.org/v1/environments/) for this tutorial with the packages listed with `using ` further down.\n", + ">\n", + "> This tutorial has demonstrated compatibility with these package versions. If you run into any errors, first check your package versions for consistency using `Pkg.status()`.\n", + ">\n", + ] + + # Format Pkg.status() output as a code block inside the blockquote + pkg_status_lines = split(pkg_status_output, '\n'; keepempty = true) + pkg_status_block = [" > ```\n"] + for line in pkg_status_lines + push!(pkg_status_block, " > " * line * "\n") + end + push!(pkg_status_block, " > ```\n", "\n") + + # Find the first heading line in the source array + heading_line_idx = nothing + for (i, line) in enumerate(cell_source) + if match(heading_pattern, line) !== nothing + heading_line_idx = i + break + end + end + + if heading_line_idx === nothing + return nb # Couldn't find heading line + end + + # Build new source array + new_source = String[] + # Add all lines up to and including the heading line + for i in 1:heading_line_idx + push!(new_source, cell_source[i]) + end + + # Add the preface and pkg.status content right after the heading + append!(new_source, preface_lines) + append!(new_source, pkg_status_block) + + # Add all remaining lines after the heading + for i in (heading_line_idx + 1):length(cell_source) + push!(new_source, cell_source[i]) + end + + # Update the cell source + first_cell["source"] = new_source + cells[first_markdown_idx] = first_cell + + nb["cells"] = cells + return nb +end + +# Add italicized "view online" comment after each image from ```@raw html ... ``` (or +# the raw HTML / markdown form Literate writes). Used as a postprocess in Literate.notebook. +# Expects _DOCS_BASE_URL to be defined by the includer (e.g. in make.jl). +# Literate strips the backtick wrapper and outputs raw HTML; we match that multi-line block. +function add_image_links(nb::Dict, outputfile_base::AbstractString) + tutorial_url = "$_DOCS_BASE_URL/tutorials/$(outputfile_base)/" + msg = "_If image is not available when viewing in a Jupyter notebook, view the tutorial online [here]($tutorial_url)._" + cells = get(nb, "cells", []) + for (idx, cell) in enumerate(cells) + get(cell, "cell_type", "") != "markdown" && continue + source = get(cell, "source", []) + isempty(source) && continue + text = join(source) + # Check if this cell already has the "view online" message to avoid duplicates + contains(text, "If image is not available when viewing in a Jupyter notebook") && + continue + suffix = "\n\n" * msg * "\n" + append_after = m -> string(m) * suffix + # Use a single non-overlapping regex to match image-containing fragments: + # - ......

(Literate raw HTML paragraphs) + # - ```@raw html ... ``` blocks + # - Markdown images ![...](...) + # - standalone tags (only if not already matched by

wrapper) + p_with_img_pattern = r"]*>[\s\S]*?" + raw_html_block_pattern = r"```@raw html[\s\S]*?```" + markdown_image_pattern = r"!\[[^\]]*\]\([^\)]*\)" + standalone_img_pattern = r"]*?/?>" + image_fragment_pattern = Regex( + "(?:" * + p_with_img_pattern.pattern * "|" * + raw_html_block_pattern.pattern * "|" * + markdown_image_pattern.pattern * "|" * + standalone_img_pattern.pattern * ")", + ) + text = replace( + text, + image_fragment_pattern => + append_after, + ) + # Convert back to notebook source array (lines, last without trailing \n if non-empty) + lines = split(text, "\n"; keepempty = true) + new_source = String[] + for i in 1:length(lines) + if i < length(lines) + push!(new_source, lines[i] * "\n") + else + isempty(lines[i]) || push!(new_source, lines[i]) + end + end + cell["source"] = new_source + cells[idx] = cell + end + nb["cells"] = cells + return nb +end + +######################################################### +# Process tutorials with Literate +######################################################### + +# Markdown files are postprocessed to add download links for the Julia script and Jupyter notebook +# Jupyter notebooks are postprocessed to add image links and pkg.status() +function make_tutorials() + # Exclude helper scripts that start with "_" + if isdir("docs/src/tutorials") + tutorial_files = + filter( + x -> occursin(".jl", x) && !startswith(x, "_"), + readdir("docs/src/tutorials"), + ) + if !isempty(tutorial_files) + # Clean up old generated tutorial files + tutorial_outputdir = joinpath(pwd(), "docs", "src", "tutorials") + clean_old_generated_files(tutorial_outputdir) + + for file in tutorial_files + @show file + infile_path = joinpath(pwd(), "docs", "src", "tutorials", file) + execute = + if occursin("EXECUTE = TRUE", uppercase(readline(infile_path))) + true + else + false + end + execute && include(infile_path) + + outputfile = string("generated_", replace("$file", ".jl" => "")) + + # Generate markdown + Literate.markdown(infile_path, + tutorial_outputdir; + name = outputfile, + credit = false, + flavor = Literate.DocumenterFlavor(), + documenter = true, + postprocess = ( + content -> add_download_links( + insert_md(content), + file, + string(outputfile, ".ipynb"), + ) + ), + execute = execute) + + # Generate notebook (chain add_image_links after add_pkg_status_to_notebook). + # preprocess_admonitions_for_notebook converts Documenter admonitions to blockquotes + # so they render in Jupyter; markdown output keeps !!! style for Documenter. + Literate.notebook(infile_path, + tutorial_outputdir; + name = outputfile, + credit = false, + execute = false, + preprocess = preprocess_admonitions_for_notebook, + postprocess = nb -> + add_image_links(add_pkg_status_to_notebook(nb), outputfile)) + end + end + end +end diff --git a/docs/src/api/public.md b/docs/src/api/public.md index 9aeb00f2..115ea280 100644 --- a/docs/src/api/public.md +++ b/docs/src/api/public.md @@ -1,6 +1,186 @@ +```@meta +CurrentModule = HybridSystemsSimulations +DocTestSetup = quote + using HybridSystemsSimulations +end +``` + # Public API Reference -```@autodocs -Modules = [HybridSystemsSimulations] -Public = true +```@contents +Pages = ["public.md"] +Depth = 3 +``` + +```@raw html +  +  +``` + +## Device Formulations + +Device formulations for hybrid systems (single PCC with renewable, thermal, and storage). +Use with [`PowerSimulations.DeviceModel`](@extref PowerSimulations.DeviceModel) for unit +commitment or economic dispatch. + +```@docs +HybridDispatchWithReserves +HybridEnergyOnlyDispatch +HybridFixedDA +``` + +```@raw html +  +  +``` + +* * * + +## Decision Models + +Decision problem types for merchant hybrid participation in day-ahead and real-time markets. + +```@docs +MerchantHybridEnergyCase +MerchantHybridEnergyFixedDA +MerchantHybridCooptimizerCase +MerchantHybridBilevelCase +``` + +```@raw html +  +  +``` + +* * * + +## Variables + +### Energy Bids + +Day-ahead and real-time energy bid/offer variables at the PCC. + +```@docs +EnergyDABidOut +EnergyDABidIn +EnergyRTBidOut +EnergyRTBidIn +``` + +### Ancillary Service Bids + +Day-ahead ancillary service bid/offer variables at the PCC. + +```@docs +BidReserveVariableOut +BidReserveVariableIn +``` + +### Reserve Variables + +Reserve quantities allocated to the hybrid's internal assets and total reserve. + +```@docs +ReserveVariableOut +ReserveVariableIn +TotalReserve +``` + +```@raw html +  +  +``` + +* * * + +## Feedforwards + +Feedforwards for hybrid storage cycle limits in recurrent simulations. + +```@docs +CyclingChargeLimitFeedforward +CyclingDischargeLimitFeedforward +``` + +```@raw html +  +  +``` + +* * * + +## Constraints + +### Dual Optimality Conditions + +KKT stationarity constraints for the merchant (lower-level) model; used in bilevel/MPEC formulations. + +```@docs +OptConditionThermalPower +OptConditionRenewablePower +OptConditionBatteryCharge +OptConditionBatteryDischarge +OptConditionEnergyVariable +``` + +### Complementary Slackness + +Complementary slackness constraints for MPEC/bilevel reformulation. Each upper-bound (Ub) +constraint has a corresponding lower-bound (Lb) variant. + +```@docs +ComplementarySlacknessEnergyAssetBalanceUb +ComplementarySlacknessEnergyAssetBalanceLb +ComplementarySlacknessRenewableActivePowerLimitConstraintUb +``` + +```@docs; canonical=false +ComplementarySlacknessRenewableActivePowerLimitConstraintLb +``` + +```@docs +ComplementarySlacknessBatteryStatusDischargeOnUb +ComplementarySlacknessBatteryStatusDischargeOnLb +ComplementarySlacknessBatteryStatusChargeOnUb +ComplementarySlacknessBatteryStatusChargeOnLb +ComplementarySlacknessBatteryBalanceUb +ComplementarySlacknessBatteryBalanceLb +ComplementarySlacknessCyclingCharge +ComplementarySlacknessCyclingDischarge +ComplementarySlacknessEnergyLimitUb +ComplementarySlacknessEnergyLimitLb +``` + +### Strong Duality + +```@docs +StrongDualityCut +``` + +```@raw html +  +  +``` + +* * * + +## Parameters + +### Objective Function Parameters + +Price parameters used in the merchant objective (DA/RT energy and ancillary services). + +```@docs +DayAheadEnergyPrice +RealTimeEnergyPrice +AncillaryServicePrice +``` + +### Variable Value Parameters + +Parameters for storage cycle limits (used with feedforwards in recurrent runs). + +```@docs +CyclingChargeLimitParameter +CyclingDischargeLimitParameter ``` diff --git a/docs/src/index.md b/docs/src/index.md index cf843a1b..805ee51b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,4 +1,4 @@ -# PowerSystems.jl +# HybridSystemsSimulations.jl ```@meta CurrentModule = HybridSystemsSimulations @@ -6,8 +6,55 @@ CurrentModule = HybridSystemsSimulations ## Overview -`HybridSystemsSimulations.jl` is a [`Julia`](http://www.julialang.org) package that provides blah blah +`HybridSystemsSimulations.jl` is a power system operations simulation package that extends +[`PowerSimulations.jl`](https://nrel-sienna.github.io/PowerSimulations.jl/stable/) to model +hybrid systems (co-located renewable, thermal, and storage behind a single point of common +coupling). It provides device formulations, decision models, and constraints for +production-cost and merchant-style studies, including ancillary services and bilevel +formulations. -* * * +`HybridSystemsSimulations.jl` is an active project under development, and we welcome your +feedback, suggestions, and bug reports. -HybridSystemsSimulations has been developed as part of the FlexPower Project at the U.S. Department of Energy's National Renewable Energy Laboratory ([NREL](https://www.nrel.gov/)) +## About Sienna + +`HybridSystemsSimulations.jl` is part of the National Laboratory of the Rockies's (NLR, formerly NREL) +[Sienna ecosystem](https://nrel-sienna.github.io/Sienna/), an open source framework for +power system modeling, simulation, and optimization. The Sienna ecosystem can be +[found on Github](https://github.com/NREL-Sienna/Sienna). It contains three applications: + + - [Sienna\Data](https://nrel-sienna.github.io/Sienna/pages/applications/sienna_data.html) enables + efficient data input, analysis, and transformation + - [Sienna\Ops](https://nrel-sienna.github.io/Sienna/pages/applications/sienna_ops.html) + enables system scheduling simulations by formulating and solving optimization problems + - [Sienna\Dyn](https://nrel-sienna.github.io/Sienna/pages/applications/sienna_dyn.html) enables + system transient analysis including small signal stability and full system dynamic + simulations + +Each application uses multiple packages in the [`Julia`](http://www.julialang.org) +programming language. + +## FlexPower Project + +`HybridSystemsSimulations.jl` has been developed as part of the FlexPower Project at the +U.S. Department of Energy's National Laboratory of the Rockies +([NLR](https://www.nlr.gov/)), formerly NREL. + +## Installation and Quick Links + + - [Sienna installation page](https://nrel-sienna.github.io/Sienna/SiennaDocs/docs/build/how-to/install/): + Instructions to install `HybridSystemsSimulations.jl` and other Sienna\Ops packages + - [`JuMP.jl` solver's page](https://jump.dev/JuMP.jl/stable/installation/#Install-a-solver): An appropriate optimization solver is required for running models. Refer to this page to select and install a solver for your application. + - [Sienna Documentation Hub](https://nrel-sienna.github.io/Sienna/SiennaDocs/docs/build/index.html): + Links to other Sienna packages' documentation + +## How To Use This Documentation + +This documentation is organized following the [Diataxis](https://diataxis.fr/) framework: + + - **Tutorials** - Detailed walk-throughs to help you *learn* how to use + `HybridSystemsSimulations.jl` + - **How to...** - Directions to help *guide* your work for a particular task + - **Explanation** - Additional details and background information to help you *understand* + `HybridSystemsSimulations.jl`, its structure, and how it works behind the scenes + - **Reference** - API and technical reference for a quick *look-up* during your work diff --git a/docs/src/quick_start_guide.md b/docs/src/quick_start_guide.md deleted file mode 100644 index d5adf773..00000000 --- a/docs/src/quick_start_guide.md +++ /dev/null @@ -1,3 +0,0 @@ -# Quick Start Guide - -HybridSystemsSimulations.jl is structured to enable stuff diff --git a/docs/src/tutorials/intro_page.md b/docs/src/tutorials/intro_page.md deleted file mode 100644 index 9ceae520..00000000 --- a/docs/src/tutorials/intro_page.md +++ /dev/null @@ -1,4 +0,0 @@ -# SIIP-Examples - -All the tutorials for the SIIP project are part of a separate repository -[SIIP-Examples](https://github.com//SIIPExamples.jl). diff --git a/scripts/formatter/formatter_code.jl b/scripts/formatter/formatter_code.jl index 2caa2698..4221a325 100644 --- a/scripts/formatter/formatter_code.jl +++ b/scripts/formatter/formatter_code.jl @@ -1,41 +1,29 @@ using Pkg Pkg.activate(@__DIR__) Pkg.instantiate() -using JuliaFormatter +Pkg.update() -main_paths = [".", "./docs/src"] -for main_path in main_paths - format( - main_path; - whitespace_ops_in_indices=true, - remove_extra_newlines=true, - verbose=true, - always_for_in=true, - whitespace_typedefs=true, - whitespace_in_kwargs=false, - format_docstrings=true, - always_use_return=false, # removed since it has false positives. - ) -end +using JuliaFormatter -# Documentation Formatter -main_paths = ["./docs/src"] +main_paths = ["."] for main_path in main_paths - for folder in readdir(main_path) - @show folder_path = joinpath(main_path, folder) - if isfile(folder_path) - !occursin(".md", folder_path) && continue + for (root, dir, files) in walkdir(main_path) + for f in files + @show file_path = abspath(root, f) + !((occursin(".jl", f) || occursin(".md", f))) && continue + format(file_path; + whitespace_ops_in_indices = true, + remove_extra_newlines = true, + verbose = true, + always_for_in = true, + whitespace_typedefs = true, + conditional_to_if = true, + join_lines_based_on_source = true, + separate_kwargs_with_semicolon = true, + format_markdown = true, + # ignore = [ ], + # always_use_return = true. # Disabled since it throws a lot of false positives + ) end - format( - folder_path; - format_markdown=true, - whitespace_ops_in_indices=true, - remove_extra_newlines=true, - verbose=true, - always_for_in=true, - whitespace_typedefs=true, - whitespace_in_kwargs=false, - # always_use_return = true # removed since it has false positives. - ) end end diff --git a/src/HybridSystemsSimulations.jl b/src/HybridSystemsSimulations.jl index acbac90c..c054f049 100644 --- a/src/HybridSystemsSimulations.jl +++ b/src/HybridSystemsSimulations.jl @@ -43,8 +43,8 @@ export ComplementarySlacknessBatteryStatusChargeOnUb export ComplementarySlacknessBatteryStatusChargeOnLb export ComplementarySlacknessBatteryBalanceUb export ComplementarySlacknessBatteryBalanceLb -export ComplentarySlacknessCyclingCharge -export ComplentarySlacknessCyclingDischarge +export ComplementarySlacknessCyclingCharge +export ComplementarySlacknessCyclingDischarge export ComplementarySlacknessEnergyLimitUb export ComplementarySlacknessEnergyLimitLb #export ComplementarySlacknessThermalOnVariableOn diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 58f3f578..8fb47015 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -154,8 +154,8 @@ function PSI.update_decision_state!( state_data_index = 1 state_data.timestamps[:] .= range( simulation_time; - step=state_resolution, - length=PSI.get_num_rows(state_data), + step = state_resolution, + length = PSI.get_num_rows(state_data), ) else state_data_index = PSI.find_timestamp_index(state_timestamps, simulation_time) diff --git a/src/add_constraints.jl b/src/add_constraints.jl index f378846f..4b9f2298 100644 --- a/src/add_constraints.jl +++ b/src/add_constraints.jl @@ -31,7 +31,8 @@ function _add_constraints_statusout!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.ReservationVariable(), D) p_out = PSI.get_variable(container, PSI.ActivePowerOutVariable(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -75,8 +76,10 @@ function _add_constraints_statusout_withreserves!( p_out = PSI.get_variable(container, PSI.ActivePowerOutVariable(), D) res_out_up = PSI.get_expression(container, TotalReserveOutUpExpression(), D) res_out_down = PSI.get_expression(container, TotalReserveOutDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -111,8 +114,10 @@ function _add_constraints_statusout_withreserves!( res_out_down = PSI.get_expression(container, TotalReserveOutDownExpression(), D) #serv_reg_out_up = PSI.get_expression(container, ServedReserveOutUpExpression(), D) #serv_reg_out_down = PSI.get_expression(container, ServedReserveOutDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -162,7 +167,8 @@ function _add_constraints_statusin!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.ReservationVariable(), D) p_in = PSI.get_variable(container, PSI.ActivePowerInVariable(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -206,8 +212,10 @@ function _add_constraints_statusin_withreserves!( p_in = PSI.get_variable(container, PSI.ActivePowerInVariable(), D) res_in_up = PSI.get_expression(container, TotalReserveInUpExpression(), D) res_in_down = PSI.get_expression(container, TotalReserveInDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -243,8 +251,10 @@ function _add_constraints_statusin_withreserves!( res_in_down = PSI.get_expression(container, TotalReserveInDownExpression(), D) #serv_reg_in_up = PSI.get_expression(container, ServedReserveInUpExpression(), D) #serv_reg_in_down = PSI.get_expression(container, ServedReserveInDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -535,7 +545,8 @@ function _add_constraints_thermalon_variableon!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.OnVariable(), D) p_th = PSI.get_variable(container, ThermalPower(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -576,7 +587,8 @@ function _add_constraints_thermalon_variableoff!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.OnVariable(), D) p_th = PSI.get_variable(container, ThermalPower(), D) - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -620,7 +632,7 @@ function _add_constraints_batterychargeon!( status_st = PSI.get_variable(container, BatteryStatus(), D) p_ch = PSI.get_variable(container, BatteryCharge(), D) con_ub_ch = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -662,7 +674,7 @@ function _add_constraints_batterydischargeon!( status_st = PSI.get_variable(container, BatteryStatus(), D) p_ds = PSI.get_variable(container, BatteryDischarge(), D) con_ub_ds = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1263,8 +1275,8 @@ function PSI.add_constraints!( ChargeRegularizationConstraint(), V, names, - time_steps, - meta="ub", + time_steps; + meta = "ub", ) constraint_lb = PSI.add_constraints_container!( @@ -1272,8 +1284,8 @@ function PSI.add_constraints!( ChargeRegularizationConstraint(), V, names, - time_steps, - meta="lb", + time_steps; + meta = "lb", ) if has_services @@ -1356,8 +1368,8 @@ function PSI.add_constraints!( DischargeRegularizationConstraint(), V, names, - time_steps, - meta="ub", + time_steps; + meta = "ub", ) constraint_lb = PSI.add_constraints_container!( @@ -1365,8 +1377,8 @@ function PSI.add_constraints!( DischargeRegularizationConstraint(), V, names, - time_steps, - meta="lb", + time_steps; + meta = "lb", ) if has_services @@ -1442,7 +1454,7 @@ function _add_constraints_renewablelimit!( p_re = PSI.get_variable(container, RenewablePower(), D) names = [PSY.get_name(d) for d in devices] con_ub_re = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") param_container = PSI.get_parameter(container, RenewablePowerTimeSeries(), D) for device in devices ci_name = PSY.get_name(device) @@ -1489,8 +1501,10 @@ function _add_thermallimit_withreserves!( p_th = PSI.get_variable(container, ThermalPower(), D) reg_th_up = PSI.get_expression(container, ThermalReserveUpExpression(), D) reg_th_dn = PSI.get_expression(container, ThermalReserveDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1549,8 +1563,8 @@ function _add_constraints_reservecoverage_withreserves!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for ic in initial_conditions device = PSI.get_component(ic) @@ -1617,8 +1631,8 @@ function _add_constraints_reservecoverage_withreserves!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for ic in initial_conditions device = PSI.get_component(ic) @@ -1686,8 +1700,8 @@ function _add_constraints_reservecoverage_withreserves_endofperiod!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1750,8 +1764,8 @@ function _add_constraints_reservecoverage_withreserves_endofperiod!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1805,8 +1819,10 @@ function _add_constraints_charging_reservelimit!( p_ch = PSI.get_variable(container, BatteryCharge(), D) reg_ch_up = PSI.get_expression(container, ChargeReserveUpExpression(), D) reg_ch_dn = PSI.get_expression(container, ChargeReserveDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1854,8 +1870,10 @@ function _add_constraints_discharging_reservelimit!( p_ds = PSI.get_variable(container, BatteryDischarge(), D) reg_ds_up = PSI.get_expression(container, DischargeReserveUpExpression(), D) reg_ds_dn = PSI.get_expression(container, DischargeReserveDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -1902,8 +1920,10 @@ function _add_constraints_renewablereserve_limit!( p_re = PSI.get_variable(container, RenewablePower(), D) reg_re_up = PSI.get_expression(container, RenewableReserveUpExpression(), D) reg_re_dn = PSI.get_expression(container, RenewableReserveDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") param_container = PSI.get_parameter(container, P(), D) for device in devices ci_name = PSY.get_name(device) @@ -1970,8 +1990,8 @@ function PSI.add_constraints!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -2070,8 +2090,8 @@ function PSI.add_constraints!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -2111,8 +2131,8 @@ function PSI.add_constraints!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices ci_name = PSY.get_name(device) @@ -2184,8 +2204,10 @@ function add_constraints_dayaheadlimit_out_withreserves!( bid_out = PSI.get_variable(container, EnergyDABidOut(), D) res_out_up = PSI.get_expression(container, TotalReserveOutUpExpression(), D) res_out_down = PSI.get_expression(container, TotalReserveOutDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -2218,8 +2240,10 @@ function add_constraints_dayaheadlimit_in_withreserves!( bid_in = PSI.get_variable(container, EnergyDABidIn(), D) res_in_up = PSI.get_expression(container, TotalReserveInUpExpression(), D) res_in_down = PSI.get_expression(container, TotalReserveInDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps ci_name = PSY.get_name(device) @@ -2252,8 +2276,10 @@ function add_constraints_realtimelimit_out_withreserves!( bid_out = PSI.get_variable(container, EnergyRTBidOut(), D) res_out_up = PSI.get_expression(container, TotalReserveOutUpExpression(), D) res_out_down = PSI.get_expression(container, TotalReserveOutDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps tmap = PSY.get_ext(device)["tmap"] @@ -2287,8 +2313,10 @@ function add_constraints_realtimelimit_in_withreserves!( bid_in = PSI.get_variable(container, EnergyRTBidIn(), D) res_in_up = PSI.get_expression(container, TotalReserveInUpExpression(), D) res_in_down = PSI.get_expression(container, TotalReserveInDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps tmap = PSY.get_ext(device)["tmap"] @@ -2323,8 +2351,10 @@ function _add_thermallimit_withreserves!( p_th = PSI.get_variable(container, ThermalPower(), D) reg_th_up = PSI.get_expression(container, ThermalReserveUpExpression(), D) reg_th_dn = PSI.get_expression(container, ThermalReserveDownExpression(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps tmap = PSY.get_ext(device)["tmap"] @@ -2355,7 +2385,8 @@ function _add_constraints_thermalon_variableon!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.OnVariable(), D) p_th = PSI.get_variable(container, ThermalPower(), D) - con_ub = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="ub") + con_ub = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "ub") for device in devices, t in time_steps tmap = PSY.get_ext(device)["tmap"] @@ -2383,7 +2414,8 @@ function _add_constraints_thermalon_variableoff!( names = [PSY.get_name(d) for d in devices] varon = PSI.get_variable(container, PSI.OnVariable(), D) p_th = PSI.get_variable(container, ThermalPower(), D) - con_lb = PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="lb") + con_lb = + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "lb") for device in devices, t in time_steps tmap = PSY.get_ext(device)["tmap"] @@ -2495,8 +2527,8 @@ function _add_constraints_reservebalance!( T(), D, names, - time_steps, - meta=service_name, + time_steps; + meta = service_name, ) for device in devices tmap = PSY.get_ext(device)["tmap"] @@ -2811,9 +2843,9 @@ function add_constraints!( primal_var = PSI.get_variable(container, PSI.EnergyVariable(), D) k_variable = PSI.get_variable(container, ComplementarySlackVarEnergyLimitUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for dev in devices n = PSY.get_name(dev) @@ -2845,7 +2877,7 @@ function add_constraints!( dual_var = PSI.get_variable(container, νStLb(), D) primal_var = PSI.get_variable(container, PSI.EnergyVariable(), D) sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps #assignment_constraint[n, t] = @@ -2872,9 +2904,9 @@ function add_constraints!( variable = PSI.get_variable(container, ComplementarySlackVarEnergyAssetBalanceUb(), D) dual_var = PSI.get_variable(container, λUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps assignment_constraint[n, t] = @@ -2900,9 +2932,9 @@ function add_constraints!( variable = PSI.get_variable(container, ComplementarySlackVarEnergyAssetBalanceLb(), D) dual_var = PSI.get_variable(container, λLb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps assignment_constraint[n, t] = @@ -2930,9 +2962,9 @@ function add_constraints!( varon = PSI.get_variable(container, PSI.OnVariable(), D) k_variable = PSI.get_variable(container, ComplementarySlackVarThermalOnVariableUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for dev in devices tmap = PSY.get_ext(dev)["tmap"] @@ -2968,9 +3000,9 @@ function add_constraints!( varon = PSI.get_variable(container, PSI.OnVariable(), D) k_variable = PSI.get_variable(container, ComplementarySlackVarThermalOnVariableLb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for dev in devices tmap = PSY.get_ext(dev)["tmap"] @@ -3009,9 +3041,9 @@ function add_constraints!( primal_var = PSI.get_variable(container, RenewablePower(), D) re_param_container = PSI.get_parameter(container, RenewablePowerTimeSeries(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for d in devices name = PSY.get_name(d) @@ -3047,7 +3079,7 @@ function add_constraints!( dual_var = PSI.get_variable(container, μReLb(), D) primal_var = PSI.get_variable(container, RenewablePower(), D) sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps #assignment_constraint[n, t] = @@ -3075,9 +3107,9 @@ function add_constraints!( k_variable = PSI.get_variable(container, ComplementarySlackVarBatteryStatusDischargeOnUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for dev in devices n = PSY.get_name(dev) @@ -3111,7 +3143,7 @@ function add_constraints!( dual_var = PSI.get_variable(container, μDsLb(), D) primal_var = PSI.get_variable(container, BatteryDischarge(), D) sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps #assignment_constraint[n, t] = @@ -3139,9 +3171,9 @@ function add_constraints!( k_variable = PSI.get_variable(container, ComplementarySlackVarBatteryStatusChargeOnUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for dev in devices n = PSY.get_name(dev) @@ -3175,7 +3207,7 @@ function add_constraints!( dual_var = PSI.get_variable(container, μChLb(), D) primal_var = PSI.get_variable(container, BatteryCharge(), D) sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") jm = PSI.get_jump_model(container) for n in names, t in time_steps #assignment_constraint[n, t] = @@ -3205,9 +3237,9 @@ function add_constraints!( discharge_var = PSI.get_variable(container, BatteryDischarge(), D) dual_var = PSI.get_variable(container, γStBalUb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") initial_conditions = PSI.get_initial_condition(container, PSI.InitialEnergyLevel(), D) jm = PSI.get_jump_model(container) for ic in initial_conditions @@ -3267,9 +3299,9 @@ function add_constraints!( discharge_var = PSI.get_variable(container, BatteryDischarge(), D) dual_var = PSI.get_variable(container, γStBalLb(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="eq") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "eq") sos_constraint = - PSI.add_constraints_container!(container, T(), D, names, time_steps, meta="sos") + PSI.add_constraints_container!(container, T(), D, names, time_steps; meta = "sos") initial_conditions = PSI.get_initial_condition(container, PSI.InitialEnergyLevel(), D) jm = PSI.get_jump_model(container) for ic in initial_conditions @@ -3312,7 +3344,7 @@ end function add_constraints!( container::PSI.OptimizationContainer, - T::Type{<:ComplentarySlacknessCyclingCharge}, + T::Type{<:ComplementarySlacknessCyclingCharge}, devices::U, ::W, ) where { @@ -3325,8 +3357,8 @@ function add_constraints!( charge_var = PSI.get_variable(container, BatteryCharge(), D) dual_var = PSI.get_variable(container, κStCh(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, meta="eq") - sos_constraint = PSI.add_constraints_container!(container, T(), D, names, meta="sos") + PSI.add_constraints_container!(container, T(), D, names; meta = "eq") + sos_constraint = PSI.add_constraints_container!(container, T(), D, names; meta = "sos") jm = PSI.get_jump_model(container) resolution = PSI.get_resolution(container) Δt_RT = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -3349,7 +3381,7 @@ end function add_constraints!( container::PSI.OptimizationContainer, - T::Type{<:ComplentarySlacknessCyclingDischarge}, + T::Type{<:ComplementarySlacknessCyclingDischarge}, devices::U, ::W, ) where { @@ -3362,8 +3394,8 @@ function add_constraints!( charge_var = PSI.get_variable(container, BatteryDischarge(), D) dual_var = PSI.get_variable(container, κStDs(), D) assignment_constraint = - PSI.add_constraints_container!(container, T(), D, names, meta="eq") - sos_constraint = PSI.add_constraints_container!(container, T(), D, names, meta="sos") + PSI.add_constraints_container!(container, T(), D, names; meta = "eq") + sos_constraint = PSI.add_constraints_container!(container, T(), D, names; meta = "sos") jm = PSI.get_jump_model(container) resolution = PSI.get_resolution(container) Δt_RT = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -3424,7 +3456,7 @@ function PSI._add_parameters!( key, names, time_steps; - meta=PSI.get_service_name(model), + meta = PSI.get_service_name(model), ) jump_model = PSI.get_jump_model(container) for name in names, t in time_steps diff --git a/src/add_parameters.jl b/src/add_parameters.jl index db1ae133..4ded27c6 100644 --- a/src/add_parameters.jl +++ b/src/add_parameters.jl @@ -91,7 +91,7 @@ function _add_price_time_series_parameters( Float64, device_names, time_steps; - meta="$var", + meta = "$var", ) for device in devices @@ -149,7 +149,7 @@ function _add_price_time_series_parameters( Float64, device_names, time_steps; - meta="$(var)_$(service_name)", + meta = "$(var)_$(service_name)", ) for device in devices @@ -183,7 +183,7 @@ function add_time_series_parameters!( container::PSI.OptimizationContainer, param::RenewablePowerTimeSeries, devices::Vector{PSY.HybridSystem}, - ts_name="RenewableDispatch__max_active_power", + ts_name = "RenewableDispatch__max_active_power", ) _add_time_series_parameters(container, ts_name, param, devices) end @@ -192,7 +192,7 @@ function add_time_series_parameters!( container::PSI.OptimizationContainer, param::ElectricLoadTimeSeries, devices::Vector{PSY.HybridSystem}, - ts_name="PowerLoad__max_active_power", + ts_name = "PowerLoad__max_active_power", ) _add_time_series_parameters(container, ts_name, param, devices) return @@ -359,7 +359,7 @@ function PSI._update_parameter_values!( t_step = model_resolution ÷ state_data.resolution end state_data_index = find_timestamp_index(state_timestamps, current_time) - sim_timestamps = range(current_time; step=model_resolution, length=time[end]) + sim_timestamps = range(current_time; step = model_resolution, length = time[end]) for t in time timestamp_ix = min(max_state_index, state_data_index + t_step) @debug "parameter horizon is over the step" max_state_index > state_data_index + 1 @@ -475,7 +475,7 @@ function PSI._add_parameters!( device_names, service_names, time_steps; - meta="$TotalReserve", + meta = "$TotalReserve", ) jump_model = PSI.get_jump_model(container) for d in devices @@ -512,7 +512,7 @@ function PSI._fix_parameter_value!( JuMP.fix( variable[name, s_name, t], parameter_array[name, s_name, t]; - force=true, + force = true, ) end end diff --git a/src/add_variables.jl b/src/add_variables.jl index f7b89b44..521d608b 100644 --- a/src/add_variables.jl +++ b/src/add_variables.jl @@ -88,7 +88,7 @@ function PSI.add_variables!( typeof(service), PSY.get_name.(devices), time_steps; - meta=PSY.get_name(service), + meta = PSY.get_name(service), ) for d in devices, t in time_steps @@ -126,7 +126,7 @@ function PSI.add_variables!( typeof(service), PSY.get_name.(devices), time_steps; - meta=PSY.get_name(service), + meta = PSY.get_name(service), ) for d in devices, t in time_steps @@ -205,7 +205,7 @@ function PSI.add_variables!( typeof(service), PSY.get_name.(devices), time_steps; - meta=PSY.get_name(service), + meta = PSY.get_name(service), ) for d in devices, t in time_steps diff --git a/src/core/constraints.jl b/src/core/constraints.jl index 11b38b1b..c22bf530 100644 --- a/src/core/constraints.jl +++ b/src/core/constraints.jl @@ -13,6 +13,7 @@ struct RealTimeBidOutRangeLimit <: PSI.ConstraintType end struct RealTimeBidInRangeLimit <: PSI.ConstraintType end ## Energy Market Asset Balance ## +"""Links day-ahead energy bids to internal asset power (upper level).""" struct EnergyBidAssetBalance <: PSI.ConstraintType end ## AS Market Convergence ## @@ -35,37 +36,58 @@ struct BatteryDischargeBidDown <: PSI.ConstraintType end ## Across Markets Balance ## struct BidBalanceOut <: PSI.ConstraintType end struct BidBalanceIn <: PSI.ConstraintType end +"""Binary status for hybrid output (generation) direction at the PCC.""" struct StatusOutOn <: PSI.ConstraintType end +"""Binary status for hybrid input (consumption) direction at the PCC.""" struct StatusInOn <: PSI.ConstraintType end ## AS for Components +"""Ensures storage has sufficient energy to meet ancillary service commitments.""" struct ReserveCoverageConstraint <: PSI.ConstraintType end +"""End-of-period energy coverage for ancillary services.""" struct ReserveCoverageConstraintEndOfPeriod <: PSI.ConstraintType end +"""Upper bound on charging power allocated to ancillary services.""" struct ChargingReservePowerLimit <: PSI.ConstraintType end +"""Upper bound on discharging power allocated to ancillary services.""" struct DischargingReservePowerLimit <: PSI.ConstraintType end +"""Upper bound on thermal power allocated to ancillary services.""" struct ThermalReserveLimit <: PSI.ConstraintType end +"""Upper bound on renewable power allocated to ancillary services.""" struct RenewableReserveLimit <: PSI.ConstraintType end ## Auxiliary for Output +"""Total reserve at PCC equals sum of component reserve allocations.""" struct ReserveBalance <: PSI.ConstraintType end -# Used for DeviceModels inside UC/ED to equate with the ActivePowerReserveVariable +"""Links component reserve variables to total reserve at the PCC.""" struct HybridReserveAssignmentConstraint <: PSI.ConstraintType end ################### ### Lower Level ### ################### +"""Net internal power (thermal + renewable + discharge − charge − load) equals net PCC power (out − in).""" struct EnergyAssetBalance <: PSI.ConstraintType end +"""Thermal power upper bound: ``p^{\\text{th}}_t \\leq u^{\\text{th}}_t P_{\\max,\\text{th}}``.""" struct ThermalOnVariableUb <: PSI.ConstraintType end +"""Thermal power lower bound: ``p^{\\text{th}}_t \\geq u^{\\text{th}}_t P_{\\min,\\text{th}}``.""" struct ThermalOnVariableLb <: PSI.ConstraintType end +"""Charge power upper bound when not discharging: ``p^{\\text{ch}}_t \\leq (1 - ss^{\\text{st}}_t) P_{\\max,\\text{ch}}``.""" struct BatteryStatusChargeOn <: PSI.ConstraintType end +"""Discharge power upper bound when discharging: ``p^{\\text{ds}}_t \\leq ss^{\\text{st}}_t P_{\\max,\\text{ds}}``.""" struct BatteryStatusDischargeOn <: PSI.ConstraintType end +"""Storage energy balance: ``e^{\\text{st}}_t = e^{\\text{st}}_{t-1} + \\Delta t(\\eta_{\\text{ch}} p^{\\text{ch}}_t - p^{\\text{ds}}_t/\\eta_{\\text{ds}})``.""" struct BatteryBalance <: PSI.ConstraintType end +"""Cumulative charging energy over horizon ≤ ``C_{\\text{st}} E_{\\max,\\text{st}}``.""" struct CyclingCharge <: PSI.ConstraintType end +"""Cumulative discharging energy over horizon ≤ ``C_{\\text{st}} E_{\\max,\\text{st}}``.""" struct CyclingDischarge <: PSI.ConstraintType end +"""Regularization on charge power changes (when `"regularization" => true`): penalizes ``|\\Delta p^{\\text{ch}}_t|``-style changes. See formulation docstrings for full constraint.""" struct ChargeRegularizationConstraint <: PSI.ConstraintType end +"""Regularization on discharge power changes (when `"regularization" => true`): penalizes ``|\\Delta p^{\\text{ds}}_t|``-style changes. See formulation docstrings for full constraint.""" struct DischargeRegularizationConstraint <: PSI.ConstraintType end +"""End-of-horizon storage energy target (when `"energy_target" => true`): ``e^{\\text{st}}_T = E^{\\text{st}}_T``.""" struct StateofChargeTargetConstraint <: PSI.ConstraintType end +"""Renewable power upper bound: ``p^{\\text{re}}_t \\leq P^{*,\\text{re}}_t``.""" struct RenewableActivePowerLimitConstraint <: PSI.ConstraintType end ################### @@ -79,31 +101,154 @@ struct FeedForwardCyclingDischargeConstraint <: PSI.ConstraintType end ### Dual Optimality Conditions Constraints ### ############################################## # Names track the variable types in variables.jl +""" + OptConditionThermalPower + +Constraint enforcing Karush-Kuhn-Tucker (KKT) stationarity for thermal power in the merchant (lower-level) +model: links dual of thermal limits (``\\mu^{\\text{ThUb}}``, ``\\mu^{\\text{ThLb}}``) to the thermal power variable. +Used in bilevel/mathematical program with equilibrium constraints (MPEC) formulations. +""" struct OptConditionThermalPower <: PSI.ConstraintType end + +""" + OptConditionRenewablePower + +Constraint enforcing Karush-Kuhn-Tucker (KKT) stationarity for renewable power (``p_{\\text{re},t}``) in the merchant +model; ties duals of renewable limit (``\\mu^{\\text{ReUb}}``, ``\\mu^{\\text{ReLb}}``) to the renewable power variable. +""" struct OptConditionRenewablePower <: PSI.ConstraintType end + +""" + OptConditionBatteryCharge + +Constraint enforcing Karush-Kuhn-Tucker (KKT) stationarity for storage charging (``p_{\\text{ch},t}``) in the merchant +model; involves duals ``\\mu^{\\text{ChUb}}``, ``\\mu^{\\text{ChLb}}`` and charge limits. +""" struct OptConditionBatteryCharge <: PSI.ConstraintType end + +""" + OptConditionBatteryDischarge + +Constraint enforcing Karush-Kuhn-Tucker (KKT) stationarity for storage discharging (``p_{\\text{ds},t}``) in the merchant +model; involves duals ``\\mu^{\\text{DsUb}}``, ``\\mu^{\\text{DsLb}}``. +""" struct OptConditionBatteryDischarge <: PSI.ConstraintType end -# EnergyVariable is defined in PSI + +""" + OptConditionEnergyVariable + +Constraint enforcing Karush-Kuhn-Tucker (KKT) stationarity for the energy variable at the point of common coupling (PCC) in the +merchant model. #TODO DOCS +""" struct OptConditionEnergyVariable <: PSI.ConstraintType end ############################################### ##### Complementaty Slackness Constraints ##### ############################################### # Names track the constraint types and their Meta Ub and Lb +""" + ComplementarySlacknessEnergyAssetBalanceUb + +Complementary slackness constraint (upper bound) for the energy asset balance +equation in the merchant model; used in mathematical program with equilibrium constraints (MPEC)/bilevel reformulation. +""" struct ComplementarySlacknessEnergyAssetBalanceUb <: PSI.ConstraintType end + +""" + ComplementarySlacknessEnergyAssetBalanceLb + +Complementary slackness constraint (lower bound) for the energy asset balance. +""" struct ComplementarySlacknessEnergyAssetBalanceLb <: PSI.ConstraintType end + struct ComplementarySlacknessThermalOnVariableUb <: PSI.ConstraintType end struct ComplementarySlacknessThermalOnVariableLb <: PSI.ConstraintType end + +""" + ComplementarySlacknessRenewableActivePowerLimitConstraintUb + +Complementary slackness (upper bound) for renewable active power limit (``p_{\\text{re},t} \\leq P^*_{\\text{re},t}``). +""" struct ComplementarySlacknessRenewableActivePowerLimitConstraintUb <: PSI.ConstraintType end + +""" + ComplementarySlacknessRenewableActivePowerLimitConstraintLb + +Complementary slackness (lower bound) for renewable active power limit. +""" struct ComplementarySlacknessRenewableActivePowerLimitConstraintLb <: PSI.ConstraintType end + +""" + ComplementarySlacknessBatteryStatusDischargeOnUb + +Complementary slackness (upper bound) for battery status discharge-on constraint (``ss_{\\text{st},t}``). +""" struct ComplementarySlacknessBatteryStatusDischargeOnUb <: PSI.ConstraintType end +""" + ComplementarySlacknessBatteryStatusDischargeOnLb + +Complementary slackness (lower bound) for battery status discharge-on constraint. +""" struct ComplementarySlacknessBatteryStatusDischargeOnLb <: PSI.ConstraintType end + +""" + ComplementarySlacknessBatteryStatusChargeOnUb + +Complementary slackness (upper bound) for battery status charge-on constraint. +""" struct ComplementarySlacknessBatteryStatusChargeOnUb <: PSI.ConstraintType end +""" + ComplementarySlacknessBatteryStatusChargeOnLb + +Complementary slackness (lower bound) for battery status charge-on constraint. +""" struct ComplementarySlacknessBatteryStatusChargeOnLb <: PSI.ConstraintType end + +""" + ComplementarySlacknessBatteryBalanceUb + +Complementary slackness (upper bound) for storage energy balance (``e_{\\text{st},t}``). +""" struct ComplementarySlacknessBatteryBalanceUb <: PSI.ConstraintType end +""" + ComplementarySlacknessBatteryBalanceLb + +Complementary slackness (lower bound) for storage energy balance. +""" struct ComplementarySlacknessBatteryBalanceLb <: PSI.ConstraintType end -struct ComplentarySlacknessCyclingCharge <: PSI.ConstraintType end -struct ComplentarySlacknessCyclingDischarge <: PSI.ConstraintType end + +""" + ComplementarySlacknessCyclingCharge + +Complementary slackness for the charging cycle limit (``c_{\\text{ch}}^-``). +""" +struct ComplementarySlacknessCyclingCharge <: PSI.ConstraintType end + +""" + ComplementarySlacknessCyclingDischarge + +Complementary slackness for the discharging cycle limit (``c_{\\text{ds}}^-``). +""" +struct ComplementarySlacknessCyclingDischarge <: PSI.ConstraintType end + +""" + ComplementarySlacknessEnergyLimitUb + +Complementary slackness (upper bound) for storage energy capacity (``e_{\\text{st},t} \\leq E_{\\max,\\text{st}}``). +""" struct ComplementarySlacknessEnergyLimitUb <: PSI.ConstraintType end +""" + ComplementarySlacknessEnergyLimitLb + +Complementary slackness (lower bound) for storage energy capacity. +""" struct ComplementarySlacknessEnergyLimitLb <: PSI.ConstraintType end + +""" + StrongDualityCut + +Constraint that enforces strong duality for the merchant (lower-level) problem +in a bilevel formulation: objective value equals dual objective (or equivalent +cut), so that the lower level is replaced by its Karush-Kuhn-Tucker (KKT) conditions. +""" struct StrongDualityCut <: PSI.ConstraintType end diff --git a/src/core/decision_models.jl b/src/core/decision_models.jl index b0340802..aa27d764 100644 --- a/src/core/decision_models.jl +++ b/src/core/decision_models.jl @@ -1,6 +1,37 @@ abstract type HybridDecisionProblem <: PSI.DecisionProblem end +""" + MerchantHybridEnergyCase + +Decision problem for a merchant hybrid resource that co-optimizes energy bids/offers +in day-ahead and real-time markets only (no ancillary services). The hybrid optimizer +maximizes profit from energy (e.g. DA/RT spread) subject to internal asset limits. +""" struct MerchantHybridEnergyCase <: HybridDecisionProblem end + +""" + MerchantHybridEnergyFixedDA + +Decision problem for a merchant hybrid with fixed day-ahead energy positions; used +when solving the real-time subproblem with locked DA bids/offers. +""" struct MerchantHybridEnergyFixedDA <: HybridDecisionProblem end + +""" + MerchantHybridCooptimizerCase + +Decision problem for a merchant hybrid that co-optimizes energy and ancillary services +in day-ahead and real-time markets. Maximizes ``d'y - c_h' x`` (revenue from bids/offers minus operating cost) subject to +market and asset constraints; ancillary services are committed in DA and fulfilled by internal asset +allocation in RT. +""" struct MerchantHybridCooptimizerCase <: HybridDecisionProblem end + +""" + MerchantHybridBilevelCase + +Decision problem implementing a bilevel formulation for the merchant hybrid +(e.g. upper level: bids/offers, lower level: internal dispatch); used for +equilibrium or regulatory analysis. #TODO DOCS +""" struct MerchantHybridBilevelCase <: HybridDecisionProblem end diff --git a/src/core/formulations.jl b/src/core/formulations.jl index 44adee3e..e6f0a263 100644 --- a/src/core/formulations.jl +++ b/src/core/formulations.jl @@ -1,8 +1,373 @@ ########################### Hybrid Generation Formulations ################################ abstract type AbstractHybridFormulation <: PSI.AbstractDeviceFormulation end abstract type AbstractHybridFormulationWithReserves <: AbstractHybridFormulation end + +""" + HybridDispatchWithReserves + +Device formulation for a hybrid system (single point of common coupling (PCC) with renewable, +thermal, and storage) that participates in both energy and ancillary services markets. +Implements the centralized production cost modeling (PCM) model where the hybrid plant's net +power at the PCC is constrained by ``P_{\\max,\\text{pcc}}`` and ancillary service allocations +(``sb^{\\text{out}}_{p,t}``, ``sb^{\\text{in}}_{p,t}``) are assigned to internal assets (thermal, +renewable, charge, discharge) per the four-quadrant ancillary service model. + +Use with a hybrid system in a +[`PowerSimulations.DeviceModel`](@extref PowerSimulations.DeviceModel) for unit commitment +or economic dispatch. + +**Variables:** + + - [`PowerSimulations.ActivePowerOutVariable`](@extref PowerSimulations.ActivePowerOutVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{out}}_t`` + + - [`PowerSimulations.ActivePowerInVariable`](@extref PowerSimulations.ActivePowerInVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{in}}_t`` + + - [`PowerSimulations.ReservationVariable`](@extref PowerSimulations.ReservationVariable): + + + Bounds: {0, 1} + + Symbol: ``u^{\\text{st}}_t`` + + - `ThermalPower`: + + + Bounds: [0.0, ``P_{\\max,\\text{th}}``] when on + + Symbol: ``p^{\\text{th}}_t`` + + - [`PowerSimulations.OnVariable`](@extref PowerSimulations.OnVariable): + + + Bounds: {0, 1} + + Symbol: ``u^{\\text{th}}_t`` + + - `RenewablePower`: + + + Bounds: [0.0, ``P^{*,\\text{re}}_t``] + + Symbol: ``p^{\\text{re}}_t`` + + - `BatteryCharge`: + + + Bounds: [0.0, ``P_{\\max,\\text{ch}}``] when charging + + Symbol: ``p^{\\text{ch}}_t`` + + - `BatteryDischarge`: + + + Bounds: [0.0, ``P_{\\max,\\text{ds}}``] when discharging + + Symbol: ``p^{\\text{ds}}_t`` + + - [`PowerSimulations.EnergyVariable`](@extref PowerSimulations.EnergyVariable): + + + Bounds: [0.0, ``E_{\\max,\\text{st}}``] + + Symbol: ``e^{\\text{st}}_t`` + + - `BatteryStatus`: + + + Bounds: {0, 1} + + Symbol: ``ss^{\\text{st}}_t`` (0 = charge, 1 = discharge) + + - [`ReserveVariableOut`](@ref): + + + Bounds: [0.0, ] + + Symbol: ``sb^{\\text{out}}_t`` + + - [`ReserveVariableIn`](@ref): + + + Bounds: [0.0, ] + + Symbol: ``sb^{\\text{in}}_t`` + +**Time Series Parameters:** + + - `RenewablePowerTimeSeries`: ``P^{*,\\text{re}}_t`` = renewable forecast at time ``t`` + - `ElectricLoadTimeSeries`: ``P^{\\text{ld}}_t`` = load consumption at time ``t`` + +**Static Parameters:** + + - ``P_{\\max,\\text{pcc}}`` = `PowerSystems.get_output_active_power_limits(device).max` + - ``P_{\\max,\\text{th}}`` = `PowerSystems.get_active_power_limits(thermal_unit).max` + - ``P_{\\min,\\text{th}}`` = `PowerSystems.get_active_power_limits(thermal_unit).min` + - ``P_{\\max,\\text{ch}}`` = `PowerSystems.get_input_active_power_limits(storage).max` + - ``P_{\\max,\\text{ds}}`` = `PowerSystems.get_output_active_power_limits(storage).max` + - ``\\eta_{\\text{ch}}`` = `PowerSystems.get_efficiency(storage).in` + - ``\\eta_{\\text{ds}}`` = `PowerSystems.get_efficiency(storage).out` + - ``E_{\\max,\\text{st}}`` = `PowerSystems.get_storage_level_limits(storage).max × capacity` + - ``E^{\\text{st}}_0`` = initial storage energy + - ``R^{*}_{p,t}`` = ancillary service deployment forecast for service ``p`` at time ``t`` + - ``F_p`` = fraction of ``P_{\\max,\\text{pcc}}`` allowed for service ``p`` + - ``N_p`` = number of periods of compliance for service ``p`` + +**Expressions:** + +Adds ``p^{\\text{out}}_t`` and ``p^{\\text{in}}_t`` to PowerSimulations' `ActivePowerBalance` expression +for use in network balance constraints. When services are present, adds reserve expressions +(`TotalReserveOutUpExpression`, `TotalReserveOutDownExpression`, `TotalReserveInUpExpression`, +`TotalReserveInDownExpression`) and served reserve expressions for tracking deployed reserves. + +**Constraints:** + +Let ``\\mathcal{T} = \\{1, \\dots, T\\}`` denote the set of time steps. + +PCC and status ([`PowerSimulations.InputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.InputActivePowerVariableLimitsConstraint), [`PowerSimulations.OutputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.OutputActivePowerVariableLimitsConstraint), [`StatusOutOn`](@ref), [`StatusInOn`](@ref)): + +```math +\\begin{align*} +& 0 \\leq p^{\\text{in}}_t \\leq P_{\\max,\\text{pcc}}, \\quad 0 \\leq p^{\\text{out}}_t \\leq P_{\\max,\\text{pcc}}, \\quad \\forall t \\in \\mathcal{T} \\\\ +& u^{\\text{st}}_t \\in \\{0,1\\} \\quad \\text{(output/input status at PCC)} +\\end{align*} +``` + +Energy asset balance ([`EnergyAssetBalance`](@ref)): + +```math +p^{\\text{th}}_t + p^{\\text{re}}_t + p^{\\text{ds}}_t - p^{\\text{ch}}_t - P^{\\text{ld}}_t = p^{\\text{out}}_t - p^{\\text{in}}_t, \\quad \\forall t \\in \\mathcal{T} +``` + +Thermal limits ([`ThermalOnVariableUb`](@ref), [`ThermalOnVariableLb`](@ref)): + +```math +u^{\\text{th}}_t P_{\\min,\\text{th}} \\leq p^{\\text{th}}_t \\leq u^{\\text{th}}_t P_{\\max,\\text{th}}, \\quad u^{\\text{th}}_t \\in \\{0,1\\}, \\quad \\forall t \\in \\mathcal{T} +``` + +Renewable limit ([`RenewableActivePowerLimitConstraint`](@ref)): + +```math +0 \\leq p^{\\text{re}}_t \\leq P^{*,\\text{re}}_t, \\quad \\forall t \\in \\mathcal{T} +``` + +Storage charge/discharge status ([`BatteryStatusChargeOn`](@ref), [`BatteryStatusDischargeOn`](@ref)): + +```math +\\begin{align*} +& p^{\\text{ch}}_t \\leq (1 - ss^{\\text{st}}_t) P_{\\max,\\text{ch}}, \\quad p^{\\text{ds}}_t \\leq ss^{\\text{st}}_t P_{\\max,\\text{ds}}, \\quad \\forall t \\in \\mathcal{T} \\\\ +& ss^{\\text{st}}_t \\in \\{0,1\\} \\quad \\text{(0 = charge, 1 = discharge)} +\\end{align*} +``` + +Storage energy balance ([`BatteryBalance`](@ref)): + +```math +e^{\\text{st}}_t = e^{\\text{st}}_{t-1} + \\Delta t \\left( \\eta_{\\text{ch}} p^{\\text{ch}}_t - \\frac{p^{\\text{ds}}_t}{\\eta_{\\text{ds}}} \\right), \\quad \\forall t \\in \\mathcal{T}, \\quad e^{\\text{st}}_0 = E^{\\text{st}}_0 +``` + +When ancillary services are present: [`ThermalReserveLimit`](@ref), [`RenewableReserveLimit`](@ref), [`ChargingReservePowerLimit`](@ref), [`DischargingReservePowerLimit`](@ref), [`ReserveCoverageConstraint`](@ref), [`ReserveCoverageConstraintEndOfPeriod`](@ref), [`HybridReserveAssignmentConstraint`](@ref), [`ReserveBalance`](@ref). + +Cycling limits (if `"cycling" => true`), ([`CyclingCharge`](@ref), [`CyclingDischarge`](@ref)): + +```math +\\begin{align*} +& \\eta_{\\text{ch}} \\Delta t \\sum_{t \\in \\mathcal{T}} p^{\\text{ch}}_t \\leq C_{\\text{st}} E_{\\max,\\text{st}} \\\\ +& \\frac{\\Delta t}{\\eta_{\\text{ds}}} \\sum_{t \\in \\mathcal{T}} p^{\\text{ds}}_t \\leq C_{\\text{st}} E_{\\max,\\text{st}} +\\end{align*} +``` + +End-of-horizon energy target (if `"energy_target" => true`), ([`StateofChargeTargetConstraint`](@ref)): + +```math +e^{\\text{st}}_T = E^{\\text{st}}_T +``` + +Regularization (if `"regularization" => true`): [`ChargeRegularizationConstraint`](@ref), [`DischargeRegularizationConstraint`](@ref). + +**Objective:** + +Adds cost terms for thermal generation (variable and fixed costs), storage variable O&M, +and penalties for energy target deviations and cycling violations (if enabled). +""" struct HybridDispatchWithReserves <: AbstractHybridFormulationWithReserves end + +""" + HybridEnergyOnlyDispatch + +Device formulation for a hybrid system that participates in energy only (no ancillary +services). Net power at the point of common coupling (PCC) is ``p^{\\text{out}}_t - p^{\\text{in}}_t`` +from thermal, renewable, discharge, minus charge and load; subject to ``P_{\\max,\\text{pcc}}`` +and asset limits. + +**Variables:** + + - [`PowerSimulations.ActivePowerOutVariable`](@extref PowerSimulations.ActivePowerOutVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{out}}_t`` + + - [`PowerSimulations.ActivePowerInVariable`](@extref PowerSimulations.ActivePowerInVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{in}}_t`` + + - [`PowerSimulations.ReservationVariable`](@extref PowerSimulations.ReservationVariable): + + + Bounds: {0, 1} + + Symbol: ``u^{\\text{st}}_t`` + + - `ThermalPower`: + + + Bounds: [0.0, ``P_{\\max,\\text{th}}``] when on + + Symbol: ``p^{\\text{th}}_t`` + + - [`PowerSimulations.OnVariable`](@extref PowerSimulations.OnVariable): + + + Bounds: {0, 1} + + Symbol: ``u^{\\text{th}}_t`` + + - `RenewablePower`: + + + Bounds: [0.0, ``P^{*,\\text{re}}_t``] + + Symbol: ``p^{\\text{re}}_t`` + + - `BatteryCharge`: + + + Bounds: [0.0, ``P_{\\max,\\text{ch}}``] when charging + + Symbol: ``p^{\\text{ch}}_t`` + + - `BatteryDischarge`: + + + Bounds: [0.0, ``P_{\\max,\\text{ds}}``] when discharging + + Symbol: ``p^{\\text{ds}}_t`` + + - [`PowerSimulations.EnergyVariable`](@extref PowerSimulations.EnergyVariable): + + + Bounds: [0.0, ``E_{\\max,\\text{st}}``] + + Symbol: ``e^{\\text{st}}_t`` + + - `BatteryStatus`: + + + Bounds: {0, 1} + + Symbol: ``ss^{\\text{st}}_t`` (0 = charge, 1 = discharge) + +**Time Series Parameters:** + + - `RenewablePowerTimeSeries`: ``P^{*,\\text{re}}_t`` = renewable forecast at time ``t`` + - `ElectricLoadTimeSeries`: ``P^{\\text{ld}}_t`` = load consumption at time ``t`` + +**Static Parameters:** + + - ``P_{\\max,\\text{pcc}}`` = `PowerSystems.get_output_active_power_limits(device).max` + - ``P_{\\max,\\text{th}}`` = `PowerSystems.get_active_power_limits(thermal_unit).max` + - ``P_{\\min,\\text{th}}`` = `PowerSystems.get_active_power_limits(thermal_unit).min` + - ``P_{\\max,\\text{ch}}`` = `PowerSystems.get_input_active_power_limits(storage).max` + - ``P_{\\max,\\text{ds}}`` = `PowerSystems.get_output_active_power_limits(storage).max` + - ``\\eta_{\\text{ch}}`` = `PowerSystems.get_efficiency(storage).in` + - ``\\eta_{\\text{ds}}`` = `PowerSystems.get_efficiency(storage).out` + - ``E_{\\max,\\text{st}}`` = `PowerSystems.get_storage_level_limits(storage).max × capacity` + - ``E^{\\text{st}}_0`` = initial storage energy + +**Expressions:** + +Adds ``p^{\\text{out}}_t`` and ``p^{\\text{in}}_t`` to PowerSimulations' `ActivePowerBalance` expression +for use in network balance constraints. + +**Constraints:** + +Let ``\\mathcal{T} = \\{1, \\dots, T\\}`` denote the set of time steps. + +PCC and status ([`PowerSimulations.InputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.InputActivePowerVariableLimitsConstraint), [`PowerSimulations.OutputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.OutputActivePowerVariableLimitsConstraint), [`StatusOutOn`](@ref), [`StatusInOn`](@ref)): + +```math +\\begin{align*} +& 0 \\leq p^{\\text{in}}_t \\leq P_{\\max,\\text{pcc}}, \\quad 0 \\leq p^{\\text{out}}_t \\leq P_{\\max,\\text{pcc}}, \\quad \\forall t \\in \\mathcal{T} \\\\ +& u^{\\text{st}}_t \\in \\{0,1\\} \\quad \\text{(output/input status at PCC)} +\\end{align*} +``` + +Energy asset balance ([`EnergyAssetBalance`](@ref)): + +```math +p^{\\text{th}}_t + p^{\\text{re}}_t + p^{\\text{ds}}_t - p^{\\text{ch}}_t - P^{\\text{ld}}_t = p^{\\text{out}}_t - p^{\\text{in}}_t, \\quad \\forall t \\in \\mathcal{T} +``` + +Thermal limits ([`ThermalOnVariableUb`](@ref), [`ThermalOnVariableLb`](@ref)): + +```math +u^{\\text{th}}_t P_{\\min,\\text{th}} \\leq p^{\\text{th}}_t \\leq u^{\\text{th}}_t P_{\\max,\\text{th}}, \\quad u^{\\text{th}}_t \\in \\{0,1\\}, \\quad \\forall t \\in \\mathcal{T} +``` + +Renewable limit ([`RenewableActivePowerLimitConstraint`](@ref)): + +```math +0 \\leq p^{\\text{re}}_t \\leq P^{*,\\text{re}}_t, \\quad \\forall t \\in \\mathcal{T} +``` + +Storage charge/discharge status ([`BatteryStatusChargeOn`](@ref), [`BatteryStatusDischargeOn`](@ref)): + +```math +\\begin{align*} +& p^{\\text{ch}}_t \\leq (1 - ss^{\\text{st}}_t) P_{\\max,\\text{ch}}, \\quad p^{\\text{ds}}_t \\leq ss^{\\text{st}}_t P_{\\max,\\text{ds}}, \\quad \\forall t \\in \\mathcal{T} \\\\ +& ss^{\\text{st}}_t \\in \\{0,1\\} \\quad \\text{(0 = charge, 1 = discharge)} +\\end{align*} +``` + +Storage energy balance ([`BatteryBalance`](@ref)): + +```math +e^{\\text{st}}_t = e^{\\text{st}}_{t-1} + \\Delta t \\left( \\eta_{\\text{ch}} p^{\\text{ch}}_t - \\frac{p^{\\text{ds}}_t}{\\eta_{\\text{ds}}} \\right), \\quad \\forall t \\in \\mathcal{T}, \\quad e^{\\text{st}}_0 = E^{\\text{st}}_0 +``` + +Cycling limits (if `"cycling" => true`), ([`CyclingCharge`](@ref), [`CyclingDischarge`](@ref)): + +```math +\\begin{align*} +& \\eta_{\\text{ch}} \\Delta t \\sum_{t \\in \\mathcal{T}} p^{\\text{ch}}_t \\leq C_{\\text{st}} E_{\\max,\\text{st}} \\\\ +& \\frac{\\Delta t}{\\eta_{\\text{ds}}} \\sum_{t \\in \\mathcal{T}} p^{\\text{ds}}_t \\leq C_{\\text{st}} E_{\\max,\\text{st}} +\\end{align*} +``` + +End-of-horizon energy target (if `"energy_target" => true`), ([`StateofChargeTargetConstraint`](@ref)): + +```math +e^{\\text{st}}_T = E^{\\text{st}}_T +``` + +Regularization (if `"regularization" => true`): [`ChargeRegularizationConstraint`](@ref), [`DischargeRegularizationConstraint`](@ref). + +**Objective:** + +Adds cost terms for thermal generation (variable and fixed costs), storage variable O&M, +and penalties for energy target deviations and cycling violations (if enabled). +""" struct HybridEnergyOnlyDispatch <: AbstractHybridFormulation end + +""" + HybridFixedDA + +Device formulation for a hybrid system with day-ahead (DA) energy bids/offers fixed; +used in multi-step simulations when the real-time (RT) subproblem is solved with +locked DA positions (e.g. merchant co-optimization with "then vs. now" RT adjustment). + +**Variables:** + + - [`PowerSimulations.ActivePowerOutVariable`](@extref PowerSimulations.ActivePowerOutVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{out}}_t`` + + - [`PowerSimulations.ActivePowerInVariable`](@extref PowerSimulations.ActivePowerInVariable): + + + Bounds: [0.0, ``P_{\\max,\\text{pcc}}``] + + Symbol: ``p^{\\text{in}}_t`` + + - `TotalReserve` (if services present): + + + Bounds: [0.0, ] + + Symbol: total reserve at PCC + +**Expressions:** + +Adds ``p^{\\text{out}}_t`` and ``p^{\\text{in}}_t`` to PowerSimulations' `ActivePowerBalance` expression +for use in network balance constraints. + +**Constraints:** + +PCC power limits ([`PowerSimulations.InputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.InputActivePowerVariableLimitsConstraint), [`PowerSimulations.OutputActivePowerVariableLimitsConstraint`](@extref PowerSimulations.OutputActivePowerVariableLimitsConstraint)): + +```math +0 \\leq p^{\\text{in}}_t \\leq P_{\\max,\\text{pcc}}, \\quad 0 \\leq p^{\\text{out}}_t \\leq P_{\\max,\\text{pcc}}, \\quad \\forall t \\in \\mathcal{T} +``` + +When ancillary services are present: [`HybridReserveAssignmentConstraint`](@ref) links component reserves to total reserve at the PCC. +""" struct HybridFixedDA <: AbstractHybridFormulation end struct MerchantModelEnergyOnly <: AbstractHybridFormulation end diff --git a/src/core/parameters.jl b/src/core/parameters.jl index 63b0b4eb..4972a734 100644 --- a/src/core/parameters.jl +++ b/src/core/parameters.jl @@ -5,12 +5,55 @@ const REG_COST = 0.001 struct RenewablePowerTimeSeries <: PSI.TimeSeriesParameter end struct ElectricLoadTimeSeries <: PSI.TimeSeriesParameter end +""" + DayAheadEnergyPrice + +Objective function parameter for day-ahead energy price. + +Docs abbreviation: ``\\Pi^*_{\\text{DA},t}`` (USD/MWh). Used in the merchant objective +(e.g. ``f_{\\text{DA},t}`` term) when building the decision model. +""" struct DayAheadEnergyPrice <: PSI.ObjectiveFunctionParameter end + +""" + RealTimeEnergyPrice + +Objective function parameter for real-time energy price. + +Docs abbreviation: ``\\Pi^*_{\\text{RT},t}`` (USD/MWh). Used in the merchant profit +expression for RT energy and DART spread. +""" struct RealTimeEnergyPrice <: PSI.ObjectiveFunctionParameter end + +""" + AncillaryServicePrice + +Objective function parameter for ancillary service price. + +Docs abbreviation: ``\\Pi^*_{p,t}`` (USD/MWh) for service ``p \\in P``. Used in the DA +profit term for ancillary services (``sb^{\\text{out}}`` + ``sb^{\\text{in}}``). +""" struct AncillaryServicePrice <: PSI.ObjectiveFunctionParameter end struct EnergyTargetParameter <: PSI.VariableValueParameter end + +""" + CyclingChargeLimitParameter + +Variable-value parameter that provides the right-hand side for the storage charging +cycle limit: ``\\eta_{\\text{ch}} \\Delta t \\sum_t p_{\\text{ch},t} - c_{\\text{ch}}^- \\leq C_{\\text{st}} E_{\\max,\\text{st}}``. Used with +[`CyclingChargeLimitFeedforward`](@ref) in recurrent simulations to pass cumulative +cycling from previous horizons. +""" struct CyclingChargeLimitParameter <: PSI.VariableValueParameter end + +""" + CyclingDischargeLimitParameter + +Variable-value parameter for the storage discharging cycle limit: +``(\\Delta t/\\eta_{\\text{ds}}) \\sum_t p_{\\text{ds},t} - c_{\\text{ds}}^- \\leq C_{\\text{st}} E_{\\max,\\text{st}}``. Used with +[`CyclingDischargeLimitFeedforward`](@ref). +""" struct CyclingDischargeLimitParameter <: PSI.VariableValueParameter end PSI.should_write_resulting_value(::Type{DayAheadEnergyPrice}) = true diff --git a/src/core/variables.jl b/src/core/variables.jl index bdc9d8f8..fc63732b 100644 --- a/src/core/variables.jl +++ b/src/core/variables.jl @@ -1,8 +1,40 @@ ### Define Variables using PSI.VariableType # Energy Bids +""" + EnergyDABidOut + +Variable type for day-ahead energy offer (generating power) at the point of common coupling (PCC). + +Docs abbreviation: ``e^{\\text{out}}_{\\text{DA},t} \\in [0, P_{\\max,\\text{pcc}}]`` [MW]. +""" struct EnergyDABidOut <: PSI.VariableType end + +""" + EnergyDABidIn + +Variable type for day-ahead energy bid (consuming power) at the point of common coupling (PCC). + +Docs abbreviation: ``e^{\\text{in}}_{\\text{DA},t} \\in [0, P_{\\max,\\text{pcc}}]`` [MW]. +""" struct EnergyDABidIn <: PSI.VariableType end + +""" + EnergyRTBidOut + +Variable type for real-time energy offer at the point of common coupling (PCC). + +Docs abbreviation: ``e^{\\text{out}}_{\\text{RT},t}``. Net RT position with DA locked +is used in the merchant profit expression (e.g. DART spread). +""" struct EnergyRTBidOut <: PSI.VariableType end + +""" + EnergyRTBidIn + +Variable type for real-time energy bid at the point of common coupling (PCC). + +Docs abbreviation: ``e^{\\text{in}}_{\\text{RT},t}``. +""" struct EnergyRTBidIn <: PSI.VariableType end # Energy Asset Bids @@ -11,8 +43,25 @@ struct EnergyRenewableBid <: PSI.VariableType end struct EnergyBatteryChargeBid <: PSI.VariableType end struct EnergyBatteryDischargeBid <: PSI.VariableType end -# AS Total DA Bids +# Ancillary Service Total DA Bids +""" + BidReserveVariableOut + +Variable type for day-ahead ancillary service offer (generation direction) for the +hybrid at the point of common coupling (PCC). + +Docs abbreviation: ``sb^{\\text{out}}_{p,t} \\in [0, F_p P_{\\max,\\text{pcc}}]`` for product ``p``. +""" struct BidReserveVariableOut <: PSI.VariableType end + +""" + BidReserveVariableIn + +Variable type for day-ahead ancillary service bid (consumption direction) for the +hybrid at the point of common coupling (PCC). + +Docs abbreviation: ``sb^{\\text{in}}_{p,t} \\in [0, F_p P_{\\max,\\text{pcc}}]`` for product ``p``. +""" struct BidReserveVariableIn <: PSI.VariableType end # Component Variables @@ -30,11 +79,32 @@ abstract type BatteryRegularizationVariable <: PSI.VariableType end struct ChargeRegularizationVariable <: BatteryRegularizationVariable end struct DischargeRegularizationVariable <: BatteryRegularizationVariable end -# AS Variable for Hybrid +# Ancillary Service Variable for Hybrid abstract type ReserveVariableType <: PSI.VariableType end abstract type AssetReserveVariableType <: ReserveVariableType end + +""" + ReserveVariableOut + +Variable type for ancillary service reserve quantity in the "out" (generation) +direction allocated to the hybrid's internal assets (``sb^{\\text{th}}``, ``sb^{\\text{re}}``, ``sb^{\\text{ds}}``, ``sb^{\\text{ch}}``). +""" struct ReserveVariableOut <: AssetReserveVariableType end + +""" + ReserveVariableIn + +Variable type for ancillary service reserve quantity in the "in" (consumption) +direction allocated to the hybrid's internal assets. +""" struct ReserveVariableIn <: AssetReserveVariableType end + +""" + TotalReserve + +Auxiliary variable type for the total reserve quantity (sum of component reserves) +at the point of common coupling (PCC). Used in reserve balance constraints; not written to results by default. +""" struct TotalReserve <: AssetReserveVariableType end struct SlackReserveUp <: PSI.VariableType end struct SlackReserveDown <: PSI.VariableType end diff --git a/src/decision_models/bilevel_decision_model.jl b/src/decision_models/bilevel_decision_model.jl index ddf1da64..26dc1ee2 100644 --- a/src/decision_models/bilevel_decision_model.jl +++ b/src/decision_models/bilevel_decision_model.jl @@ -949,8 +949,8 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridBilevel ComplementarySlacknessBatteryBalanceLb, ComplementarySlacknessEnergyLimitUb, ComplementarySlacknessEnergyLimitLb, - ComplentarySlacknessCyclingCharge, - ComplentarySlacknessCyclingDischarge, + ComplementarySlacknessCyclingCharge, + ComplementarySlacknessCyclingDischarge, ] add_constraints!(container, c, hybrids, MerchantModelWithReserves()) end diff --git a/src/decision_models/only_energy_decision_model.jl b/src/decision_models/only_energy_decision_model.jl index 22a13c5f..e4f165d7 100644 --- a/src/decision_models/only_energy_decision_model.jl +++ b/src/decision_models/only_energy_decision_model.jl @@ -419,8 +419,8 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridEnergyC RenewableActivePowerLimitConstraint(), PSY.HybridSystem, h_names, - T_rt, - meta="ub", + T_rt; + meta = "ub", ) re_param_container = diff --git a/src/feedforwards.jl b/src/feedforwards.jl index 20ea09a1..634434a6 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -1,3 +1,13 @@ +""" + CyclingChargeLimitFeedforward + +Feedforward that enforces a cumulative charging cycle limit on the hybrid's storage +over the simulation. The constraint is ``\\eta_{\\text{ch}} \\Delta t \\sum_t (p_{\\text{ch},t} + s^{\\text{down}}_{\\text{reg},t} - s^{\\text{up}}_{\\text{reg},t}) \\leq \\text{limit}``, +where ``s^{\\text{up}}_{\\text{reg},t}`` and ``s^{\\text{down}}_{\\text{reg},t}`` denote served reserve (up/down). The limit is from [`CyclingChargeLimitParameter`](@ref) in recurrent solves or +``C_{\\text{horizon}} \\times E_{\\max,\\text{st}}`` otherwise. Use with PowerSimulations' `add_feedforward!` in a +[`PowerSimulations.DeviceModel`](@extref PowerSimulations.DeviceModel) for +[`HybridDispatchWithReserves`](@ref) or [`HybridEnergyOnlyDispatch`](@ref). +""" struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward optimization_container_key::PSI.OptimizationContainerKey affected_values::Vector{<:PSI.OptimizationContainerKey} @@ -7,7 +17,7 @@ struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward source::Type{T}, affected_values::Vector{DataType}, penalty_cost::Float64, - meta=ISOPT.CONTAINER_KEY_EMPTY_META, + meta = ISOPT.CONTAINER_KEY_EMPTY_META, ) where {T} values_vector = Vector{PSI.ParameterKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) @@ -33,6 +43,15 @@ PSI.get_default_parameter_type(::CyclingChargeLimitFeedforward, _) = PSI.get_optimization_container_key(ff::CyclingChargeLimitFeedforward) = ff.optimization_container_key +""" + CyclingDischargeLimitFeedforward + +Feedforward that enforces a cumulative discharging cycle limit on the hybrid's storage: +``(1/\\eta_{\\text{ds}}) \\Delta t \\sum_t (p_{\\text{ds},t} + s^{\\text{up}}_{\\text{reg},t} - s^{\\text{down}}_{\\text{reg},t}) \\leq \\text{limit}``, +where ``s^{\\text{up}}_{\\text{reg},t}`` and ``s^{\\text{down}}_{\\text{reg},t}`` denote served reserve (up/down). The limit comes from +[`CyclingDischargeLimitParameter`](@ref) in recurrent runs. See +[`CyclingChargeLimitFeedforward`](@ref) for usage pattern. +""" struct CyclingDischargeLimitFeedforward <: PSI.AbstractAffectFeedforward optimization_container_key::PSI.OptimizationContainerKey affected_values::Vector{<:PSI.OptimizationContainerKey} @@ -42,7 +61,7 @@ struct CyclingDischargeLimitFeedforward <: PSI.AbstractAffectFeedforward source::Type{T}, affected_values::Vector{DataType}, penalty_cost::Float64, - meta=ISOPT.CONTAINER_KEY_EMPTY_META, + meta = ISOPT.CONTAINER_KEY_EMPTY_META, ) where {T} values_vector = Vector{PSI.ParameterKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) @@ -203,7 +222,10 @@ function PSI.add_feedforward_constraints!( cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY if PSI.built_for_recurrent_solves(container) param_value = - PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name, time_steps[end]] + PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ + ci_name, + time_steps[end], + ] con_cycling_ch[ci_name] = JuMP.@constraint( PSI.get_jump_model(container), efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= @@ -307,7 +329,10 @@ function PSI.add_feedforward_constraints!( cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY if PSI.built_for_recurrent_solves(container) param_value = - PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name, time_steps[end]] + PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ + ci_name, + time_steps[end], + ] con_cycling_ds[ci_name] = JuMP.@constraint( PSI.get_jump_model(container), (1.0 / efficiency.out) * diff --git a/src/hybrid_system_decision_models.jl b/src/hybrid_system_decision_models.jl index 1a63296f..45941305 100644 --- a/src/hybrid_system_decision_models.jl +++ b/src/hybrid_system_decision_models.jl @@ -204,8 +204,8 @@ function PSI.update_decision_state!( state_data_index = 1 state_data.timestamps[:] .= range( simulation_time; - step=state_resolution, - length=PSI.get_num_rows(state_data), + step = state_resolution, + length = PSI.get_num_rows(state_data), ) else state_data_index = PSI.find_timestamp_index(state_timestamps, simulation_time) @@ -245,7 +245,7 @@ function PSI._update_parameter_values!( state_timestamps = state_data.timestamps max_state_index = PSI.get_num_rows(state_data) state_data_index = PSI.find_timestamp_index(state_timestamps, current_time) - sim_timestamps = range(current_time; step=model_resolution, length=time[end]) + sim_timestamps = range(current_time; step = model_resolution, length = time[end]) for t in time timestamp_ix = min(max_state_index, state_data_index + t_step) @debug "parameter horizon is over the step" max_state_index > state_data_index + 1 @@ -283,11 +283,11 @@ function PSI._fix_parameter_value!( if time_var[end] < time[end] for t in time_var, name in component_names t_ = 1 + (t - 1) * time[end] ÷ time_var[end] - JuMP.fix(variable[name, t], parameter_array[name, t_]; force=true) + JuMP.fix(variable[name, t], parameter_array[name, t_]; force = true) end elseif time_var[end] == time[end] for t in time_var, name in component_names - JuMP.fix(variable[name, t], parameter_array[name, t]; force=true) + JuMP.fix(variable[name, t], parameter_array[name, t]; force = true) end else error("invalid condition") @@ -319,8 +319,8 @@ function PSI.update_decision_state!( state_data_index = 1 state_data.timestamps[:] .= range( simulation_time; - step=state_resolution, - length=PSI.get_num_rows(state_data), + step = state_resolution, + length = PSI.get_num_rows(state_data), ) else state_data_index = PSI.find_timestamp_index(state_timestamps, simulation_time) @@ -376,7 +376,7 @@ function PSI._update_parameter_values!( @assert false end state_data_index = PSI.find_timestamp_index(state_timestamps, current_time) - sim_timestamps = range(current_time; step=model_resolution, length=time[end]) + sim_timestamps = range(current_time; step = model_resolution, length = time[end]) for t in time timestamp_ix = min(max_state_index, state_data_index + t_step) @debug "parameter horizon is over the step" max_state_index > state_data_index + 1 diff --git a/src/utils.jl b/src/utils.jl index b8684fb8..958d4a74 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -18,7 +18,7 @@ function get_time_series( subcomponent_type::Type{T}, parameter::TimeSeriesParameter, # HSA - 10.02.2024 --------------- - meta=ISOPT.CONTAINER_KEY_EMPTY_META, + meta = ISOPT.CONTAINER_KEY_EMPTY_META, ) where {S <: PSY.HybridSystem, T <: PSY.Component} parameter_container = get_parameter(container, parameter, S, meta) subcomponent = get_subcomponent(component, subcomponent_type) diff --git a/test/runtests.jl b/test/runtests.jl index f2478dc3..bf1fe9cc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -110,9 +110,9 @@ function run_tests() config = IS.LoggingConfiguration(logging_config_filename) else config = IS.LoggingConfiguration(; - filename=LOG_FILE, - file_level=Logging.Info, - console_level=Logging.Error, + filename = LOG_FILE, + file_level = Logging.Info, + console_level = Logging.Error, ) end console_logger = ConsoleLogger(config.console_stream, config.console_level) diff --git a/test/test_device_hybrid_generation_constructors.jl b/test/test_device_hybrid_generation_constructors.jl index 2f23c88e..7aaf4352 100644 --- a/test/test_device_hybrid_generation_constructors.jl +++ b/test/test_device_hybrid_generation_constructors.jl @@ -2,13 +2,13 @@ device_model = DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ) sys = PSB.build_system(PSITestSystems, "c_sys5_hybrid") # Parameters Testing model = - DecisionModel(MockOperationProblem, DCPPowerModel, sys; store_variable_names=true) + DecisionModel(MockOperationProblem, DCPPowerModel, sys; store_variable_names = true) mock_construct_device!(model, device_model) moi_tests(model, 816, 0, 720, 192, 192, true) psi_checkobjfun_test(model, GAEVF) @@ -18,7 +18,7 @@ end device_model = DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ) sys = PSB.build_system(PSITestSystems, "c_sys5_hybrid") diff --git a/test/test_hybrid_device.jl b/test/test_hybrid_device.jl index a78c7ddd..70d5b9cb 100644 --- a/test/test_hybrid_device.jl +++ b/test/test_hybrid_device.jl @@ -18,18 +18,18 @@ DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => true), + attributes = Dict{String, Any}("cycling" => true), ), ) m = DecisionModel( template_uc_dcp, - sys_rts_da, - optimizer=HiGHS_optimizer, - store_variable_names=true, + sys_rts_da; + optimizer = HiGHS_optimizer, + store_variable_names = true, ) - build_out = PSI.build!(m, output_dir=mktempdir(cleanup=true)) + build_out = PSI.build!(m; output_dir = mktempdir(; cleanup = true)) @test build_out == PSI.BuildStatus.BUILT solve_out = PSI.solve!(m) @test solve_out == PSI.RunStatus.SUCCESSFUL @@ -74,18 +74,18 @@ end DeviceModel( PSY.HybridSystem, HybridDispatchWithReserves; - attributes=Dict{String, Any}("cycling" => true), + attributes = Dict{String, Any}("cycling" => true), ), ) m = DecisionModel( template_uc_dcp, - sys_rts_da, - optimizer=HiGHS_optimizer, - store_variable_names=true, + sys_rts_da; + optimizer = HiGHS_optimizer, + store_variable_names = true, ) - build_out = PSI.build!(m, output_dir=mktempdir(cleanup=true)) + build_out = PSI.build!(m; output_dir = mktempdir(; cleanup = true)) @test build_out == PSI.BuildStatus.BUILT solve_out = PSI.solve!(m) @test solve_out == PSI.RunStatus.SUCCESSFUL diff --git a/test/test_hybrid_simulations.jl b/test/test_hybrid_simulations.jl index 55c217ea..db8f7498 100644 --- a/test/test_hybrid_simulations.jl +++ b/test/test_hybrid_simulations.jl @@ -7,32 +7,35 @@ DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ), ) - set_network_model!(template_uc, NetworkModel(CopperPlatePowerModel, use_slacks=true)) + set_network_model!(template_uc, NetworkModel(CopperPlatePowerModel; use_slacks = true)) - models = SimulationModels( - decision_models=[ + models = SimulationModels(; + decision_models = [ DecisionModel( template_uc, sys_uc; - name="UC", - optimizer=HiGHS_optimizer, - initialize_model=false, + name = "UC", + optimizer = HiGHS_optimizer, + initialize_model = false, ), ], ) sequence = - SimulationSequence(models=models, ini_cond_chronology=InterProblemChronology()) + SimulationSequence(; + models = models, + ini_cond_chronology = InterProblemChronology(), + ) - sim = Simulation( - name="hybrid_test", - steps=2, - models=models, - sequence=sequence, - simulation_folder=mktempdir(cleanup=true), + sim = Simulation(; + name = "hybrid_test", + steps = 2, + models = models, + sequence = sequence, + simulation_folder = mktempdir(; cleanup = true), ) build_out = build!(sim) @test build_out == PSI.BuildStatus.BUILT @@ -48,50 +51,53 @@ end DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ), ) - set_network_model!(template_uc, NetworkModel(CopperPlatePowerModel, use_slacks=true)) + set_network_model!(template_uc, NetworkModel(CopperPlatePowerModel; use_slacks = true)) template_ed = get_thermal_dispatch_template_network( - NetworkModel(CopperPlatePowerModel, use_slacks=true), + NetworkModel(CopperPlatePowerModel; use_slacks = true), ) set_device_model!( template_ed, DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ), ) - models = SimulationModels( - decision_models=[ + models = SimulationModels(; + decision_models = [ DecisionModel( template_uc, sys_uc; - name="UC", - optimizer=HiGHS_optimizer, - initialize_model=false, + name = "UC", + optimizer = HiGHS_optimizer, + initialize_model = false, ), DecisionModel( template_ed, sys_ed; - name="ED", - optimizer=HiGHS_optimizer, - initialize_model=false, + name = "ED", + optimizer = HiGHS_optimizer, + initialize_model = false, ), ], ) sequence = - SimulationSequence(models=models, ini_cond_chronology=InterProblemChronology()) + SimulationSequence(; + models = models, + ini_cond_chronology = InterProblemChronology(), + ) - sim = Simulation( - name="hybrid_test", - steps=2, - models=models, - sequence=sequence, - simulation_folder=mktempdir(cleanup=true), + sim = Simulation(; + name = "hybrid_test", + steps = 2, + models = models, + sequence = sequence, + simulation_folder = mktempdir(; cleanup = true), ) build_out = build!(sim) @test build_out == PSI.BuildStatus.BUILT diff --git a/test/test_merchant_cooptimizer.jl b/test/test_merchant_cooptimizer.jl index 22146978..04e02d11 100644 --- a/test/test_merchant_cooptimizer.jl +++ b/test/test_merchant_cooptimizer.jl @@ -2,12 +2,12 @@ #### Create Systems #### horizon_merchant_rt = 288 horizon_merchant_da = 24 - sys_rts_merchant = PSB.build_RTS_GMLC_RT_sys( - raw_data=PSB.RTS_DIR, - horizon=horizon_merchant_rt, - interval=Hour(24), + sys_rts_merchant = PSB.build_RTS_GMLC_RT_sys(; + raw_data = PSB.RTS_DIR, + horizon = horizon_merchant_rt, + interval = Hour(24), ) - sys_rts_da = PSB.build_RTS_GMLC_DA_sys(raw_data=PSB.RTS_DIR, horizon=24) + sys_rts_da = PSB.build_RTS_GMLC_DA_sys(; raw_data = PSB.RTS_DIR, horizon = 24) # There is no Wind + Thermal in a Single Bus. # We will try to pick the Wind in 317 bus Chuhsi @@ -55,16 +55,16 @@ decision_optimizer_DA = DecisionModel( MerchantHybridCooptimizerCase, ProblemTemplate(CopperPlatePowerModel), - sys, - optimizer=HiGHS_optimizer, - calculate_conflict=true, - optimizer_solve_log_print=true, - store_variable_names=true, - initial_time=DateTime("2020-10-03T00:00:00"), - name="MerchantHybridCooptimizerCase_DA", + sys; + optimizer = HiGHS_optimizer, + calculate_conflict = true, + optimizer_solve_log_print = true, + store_variable_names = true, + initial_time = DateTime("2020-10-03T00:00:00"), + name = "MerchantHybridCooptimizerCase_DA", ) - build!(decision_optimizer_DA; output_dir=mktempdir()) + build!(decision_optimizer_DA; output_dir = mktempdir()) solve!(decision_optimizer_DA) results = ProblemResults(decision_optimizer_DA) diff --git a/test/test_merchant_only_energy.jl b/test/test_merchant_only_energy.jl index c342afd3..c4985519 100644 --- a/test/test_merchant_only_energy.jl +++ b/test/test_merchant_only_energy.jl @@ -1,12 +1,12 @@ @testset "Test HybridSystem Merchant Decision Model Only Energy" begin horizon_merchant_rt = 288 horizon_merchant_da = 24 - sys_rts_merchant = PSB.build_RTS_GMLC_RT_sys( - raw_data=PSB.RTS_DIR, - horizon=horizon_merchant_rt, - interval=Hour(24), + sys_rts_merchant = PSB.build_RTS_GMLC_RT_sys(; + raw_data = PSB.RTS_DIR, + horizon = horizon_merchant_rt, + interval = Hour(24), ) - sys_rts_da = PSB.build_RTS_GMLC_DA_sys(raw_data=PSB.RTS_DIR, horizon=24) + sys_rts_da = PSB.build_RTS_GMLC_DA_sys(; raw_data = PSB.RTS_DIR, horizon = 24) # There is no Wind + Thermal in a Single Bus. # We will try to pick the Wind in 317 bus Chuhsi @@ -37,14 +37,14 @@ decision_optimizer_DA = DecisionModel( MerchantHybridEnergyCase, ProblemTemplate(CopperPlatePowerModel), - sys, - optimizer=HiGHS_optimizer, - calculate_conflict=true, - store_variable_names=true; - name="MerchantHybridEnergyCase_DA", + sys; + optimizer = HiGHS_optimizer, + calculate_conflict = true, + store_variable_names = true, + name = "MerchantHybridEnergyCase_DA", ) - build!(decision_optimizer_DA; output_dir=mktempdir()) + build!(decision_optimizer_DA; output_dir = mktempdir()) solve!(decision_optimizer_DA) results = ProblemResults(decision_optimizer_DA) diff --git a/test/test_utils/additional_templates.jl b/test/test_utils/additional_templates.jl index e07709b9..f9e956e8 100644 --- a/test/test_utils/additional_templates.jl +++ b/test/test_utils/additional_templates.jl @@ -18,7 +18,7 @@ function set_uc_models!(template_uc) DeviceModel( PSY.HybridSystem, HybridEnergyOnlyDispatch; - attributes=Dict{String, Any}("cycling" => false), + attributes = Dict{String, Any}("cycling" => false), ), ) set_device_model!(template_uc, GenericBattery, BookKeeping) @@ -51,7 +51,7 @@ end function set_ptdf_line_template!(template_uc) set_device_model!( template_uc, - DeviceModel(Line, StaticBranch, duals=[NetworkFlowConstraint]), + DeviceModel(Line, StaticBranch; duals = [NetworkFlowConstraint]), ) return end @@ -75,10 +75,10 @@ end function get_uc_ptdf_template(sys_rts_da) template_uc = ProblemTemplate( NetworkModel( - PTDFPowerModel, - use_slacks=true, - PTDF_matrix=PTDF(sys_rts_da), - duals=[CopperPlateBalanceConstraint], + PTDFPowerModel; + use_slacks = true, + PTDF_matrix = PTDF(sys_rts_da), + duals = [CopperPlateBalanceConstraint], ), ) set_uc_models!(template_uc) @@ -111,10 +111,10 @@ end function get_uc_copperplate_template(sys_rts_da) template_uc = ProblemTemplate( NetworkModel( - CopperPlatePowerModel, - use_slacks=true, - PTDF_matrix=PTDF(sys_rts_da), - duals=[CopperPlateBalanceConstraint], + CopperPlatePowerModel; + use_slacks = true, + PTDF_matrix = PTDF(sys_rts_da), + duals = [CopperPlateBalanceConstraint], ), ) set_uc_models!(template_uc) @@ -132,7 +132,11 @@ end function get_uc_dcp_template() template_uc = ProblemTemplate( - NetworkModel(DCPPowerModel, use_slacks=true, duals=[NodalBalanceActiveConstraint]), + NetworkModel( + DCPPowerModel; + use_slacks = true, + duals = [NodalBalanceActiveConstraint], + ), ) set_uc_models!(template_uc) set_dcp_line_template!(template_uc) @@ -155,60 +159,60 @@ function build_simulation_case( mipgap::Float64, start_time, ) - models = SimulationModels( - decision_models=[ + models = SimulationModels(; + decision_models = [ DecisionModel( template_uc, sys_da; - name="UC", - optimizer=HiGHS_optimizer, - system_to_file=false, - initialize_model=true, - optimizer_solve_log_print=true, - direct_mode_optimizer=true, - rebuild_model=false, - store_variable_names=true, + name = "UC", + optimizer = HiGHS_optimizer, + system_to_file = false, + initialize_model = true, + optimizer_solve_log_print = true, + direct_mode_optimizer = true, + rebuild_model = false, + store_variable_names = true, #check_numerical_bounds=false, ), DecisionModel( template_ed, sys_rt; - name="ED", - optimizer=optimizer_with_attributes(Xpress.Optimizer), - system_to_file=false, - initialize_model=true, - optimizer_solve_log_print=false, - check_numerical_bounds=false, - rebuild_model=false, - calculate_conflict=true, - store_variable_names=true, + name = "ED", + optimizer = optimizer_with_attributes(Xpress.Optimizer), + system_to_file = false, + initialize_model = true, + optimizer_solve_log_print = false, + check_numerical_bounds = false, + rebuild_model = false, + calculate_conflict = true, + store_variable_names = true, #export_pwl_vars = true, ), ], ) # Set-up the sequence UC-ED - sequence = SimulationSequence( - models=models, - feedforwards=Dict( + sequence = SimulationSequence(; + models = models, + feedforwards = Dict( "ED" => [ - SemiContinuousFeedforward( - component_type=ThermalStandard, - source=OnVariable, - affected_values=[ActivePowerVariable], + SemiContinuousFeedforward(; + component_type = ThermalStandard, + source = OnVariable, + affected_values = [ActivePowerVariable], ), ], ), - ini_cond_chronology=InterProblemChronology(), + ini_cond_chronology = InterProblemChronology(), ) - sim = Simulation( - name="compact_sim", - steps=num_steps, - models=models, - sequence=sequence, - initial_time=start_time, - simulation_folder=mktempdir(cleanup=true), + sim = Simulation(; + name = "compact_sim", + steps = num_steps, + models = models, + sequence = sequence, + initial_time = start_time, + simulation_folder = mktempdir(; cleanup = true), ) return sim @@ -224,52 +228,52 @@ function build_simulation_case_optimizer( mipgap::Float64, start_time, ) - models = SimulationModels( - decision_models=[ + models = SimulationModels(; + decision_models = [ decision_optimizer, DecisionModel( template_uc, sys_da; - name="UC", - optimizer=HiGHS_optimizer, - system_to_file=false, - initialize_model=true, - optimizer_solve_log_print=false, - direct_mode_optimizer=true, - rebuild_model=false, - store_variable_names=true, + name = "UC", + optimizer = HiGHS_optimizer, + system_to_file = false, + initialize_model = true, + optimizer_solve_log_print = false, + direct_mode_optimizer = true, + rebuild_model = false, + store_variable_names = true, #check_numerical_bounds=false, ), ], ) # Set-up the sequence Optimizer-UC - sequence = SimulationSequence( - models=models, - feedforwards=Dict( + sequence = SimulationSequence(; + models = models, + feedforwards = Dict( "UC" => [ - FixValueFeedforward( - component_type=PSY.HybridSystem, - source=EnergyDABidOut, - affected_values=[ActivePowerOutVariable], + FixValueFeedforward(; + component_type = PSY.HybridSystem, + source = EnergyDABidOut, + affected_values = [ActivePowerOutVariable], ), - FixValueFeedforward( - component_type=PSY.HybridSystem, - source=EnergyDABidIn, - affected_values=[ActivePowerInVariable], + FixValueFeedforward(; + component_type = PSY.HybridSystem, + source = EnergyDABidIn, + affected_values = [ActivePowerInVariable], ), ], ), - ini_cond_chronology=InterProblemChronology(), + ini_cond_chronology = InterProblemChronology(), ) - sim = Simulation( - name="compact_sim", - steps=num_steps, - models=models, - sequence=sequence, - initial_time=start_time, - simulation_folder=mktempdir(cleanup=true), + sim = Simulation(; + name = "compact_sim", + steps = num_steps, + models = models, + sequence = sequence, + initial_time = start_time, + simulation_folder = mktempdir(; cleanup = true), ) return sim diff --git a/test/test_utils/function_utils.jl b/test/test_utils/function_utils.jl index 41f6c751..50816664 100644 --- a/test/test_utils/function_utils.jl +++ b/test/test_utils/function_utils.jl @@ -4,22 +4,22 @@ function get_da_max_active_power_series(r_gen, starttime, steps::Int) ta = get_time_series_array( SingleTimeSeries, r_gen, - "max_active_power", - start_time=starttime, - len=24 * steps, + "max_active_power"; + start_time = starttime, + len = 24 * steps, ) - return DataFrame(DateTime=timestamp(ta), MaxPower=values(ta)) + return DataFrame(; DateTime = timestamp(ta), MaxPower = values(ta)) end function get_rt_max_active_power_series(r_gen, starttime, steps::Int) ta = get_time_series_array( SingleTimeSeries, r_gen, - "max_active_power", - start_time=starttime, - len=24 * 12 * steps, + "max_active_power"; + start_time = starttime, + len = 24 * 12 * steps, ) - return DataFrame(DateTime=timestamp(ta), MaxPower=values(ta)) + return DataFrame(; DateTime = timestamp(ta), MaxPower = values(ta)) end function get_battery_params(b_gen::GenericBattery) @@ -49,7 +49,7 @@ function get_battery_params(b_gen::GenericBattery) η_in, η_out, ] - return DataFrame(ParamName=battery_params_names, Value=battery_params_vals) + return DataFrame(; ParamName = battery_params_names, Value = battery_params_vals) end function get_thermal_params(t_gen) @@ -60,9 +60,9 @@ function get_thermal_params(t_gen) second_part = three_cost.variable[2] slope = (second_part[1] - first_part[1]) / (second_part[2] - first_part[2]) # $/MWh fix_cost = three_cost.fixed # $/h - return DataFrame( - ParamName=["P_min", "P_max", "C_var", "C_fix"], - Value=[P_min, P_max, slope, fix_cost], + return DataFrame(; + ParamName = ["P_min", "P_max", "C_var", "C_fix"], + Value = [P_min, P_max, slope, fix_cost], ) end @@ -89,21 +89,21 @@ function _build_battery( ) name = string(bus.number) * "_BATTERY" device = GenericBattery(; - name=name, - available=true, - bus=bus, - prime_mover_type=PSY.PrimeMovers.BA, - initial_energy=energy_capacity / 2, - state_of_charge_limits=(min=energy_capacity * 0.05, max=energy_capacity), - rating=rating, - active_power=rating, - input_active_power_limits=(min=0.0, max=rating), - output_active_power_limits=(min=0.0, max=rating), - efficiency=(in=efficiency_in, out=efficiency_out), - reactive_power=0.0, - reactive_power_limits=nothing, - base_power=100.0, - operation_cost=PSY.TwoPartCost(0.0, 0.0), + name = name, + available = true, + bus = bus, + prime_mover_type = PSY.PrimeMovers.BA, + initial_energy = energy_capacity / 2, + state_of_charge_limits = (min = energy_capacity * 0.05, max = energy_capacity), + rating = rating, + active_power = rating, + input_active_power_limits = (min = 0.0, max = rating), + output_active_power_limits = (min = 0.0, max = rating), + efficiency = (in = efficiency_in, out = efficiency_out), + reactive_power = 0.0, + reactive_power_limits = nothing, + base_power = 100.0, + operation_cost = PSY.TwoPartCost(0.0, 0.0), ) return device end @@ -128,24 +128,24 @@ function add_hybrid_to_chuhsi_bus!(sys::System) load = get_component(PowerLoad, sys, load_name) # Create the Hybrid hybrid_name = string(bus.number) * "_Hybrid" - hybrid = PSY.HybridSystem( - name=hybrid_name, - available=true, - status=true, - bus=bus, - active_power=1.0, - reactive_power=0.0, - base_power=100.0, - operation_cost=TwoPartCost(nothing), - thermal_unit=thermal, #new_th, - electric_load=load, #new_load, - storage=bat, - renewable_unit=renewable, #new_ren, - interconnection_impedance=0.0 + 0.0im, - interconnection_rating=nothing, - input_active_power_limits=(min=0.0, max=10.0), - output_active_power_limits=(min=0.0, max=10.0), - reactive_power_limits=nothing, + hybrid = PSY.HybridSystem(; + name = hybrid_name, + available = true, + status = true, + bus = bus, + active_power = 1.0, + reactive_power = 0.0, + base_power = 100.0, + operation_cost = TwoPartCost(nothing), + thermal_unit = thermal, #new_th, + electric_load = load, #new_load, + storage = bat, + renewable_unit = renewable, #new_ren, + interconnection_impedance = 0.0 + 0.0im, + interconnection_rating = nothing, + input_active_power_limits = (min = 0.0, max = 10.0), + output_active_power_limits = (min = 0.0, max = 10.0), + reactive_power_limits = nothing, ) # Add Hybrid add_component!(sys, hybrid) diff --git a/test/test_utils/price_generation_utils.jl b/test/test_utils/price_generation_utils.jl index 41f6c751..50816664 100644 --- a/test/test_utils/price_generation_utils.jl +++ b/test/test_utils/price_generation_utils.jl @@ -4,22 +4,22 @@ function get_da_max_active_power_series(r_gen, starttime, steps::Int) ta = get_time_series_array( SingleTimeSeries, r_gen, - "max_active_power", - start_time=starttime, - len=24 * steps, + "max_active_power"; + start_time = starttime, + len = 24 * steps, ) - return DataFrame(DateTime=timestamp(ta), MaxPower=values(ta)) + return DataFrame(; DateTime = timestamp(ta), MaxPower = values(ta)) end function get_rt_max_active_power_series(r_gen, starttime, steps::Int) ta = get_time_series_array( SingleTimeSeries, r_gen, - "max_active_power", - start_time=starttime, - len=24 * 12 * steps, + "max_active_power"; + start_time = starttime, + len = 24 * 12 * steps, ) - return DataFrame(DateTime=timestamp(ta), MaxPower=values(ta)) + return DataFrame(; DateTime = timestamp(ta), MaxPower = values(ta)) end function get_battery_params(b_gen::GenericBattery) @@ -49,7 +49,7 @@ function get_battery_params(b_gen::GenericBattery) η_in, η_out, ] - return DataFrame(ParamName=battery_params_names, Value=battery_params_vals) + return DataFrame(; ParamName = battery_params_names, Value = battery_params_vals) end function get_thermal_params(t_gen) @@ -60,9 +60,9 @@ function get_thermal_params(t_gen) second_part = three_cost.variable[2] slope = (second_part[1] - first_part[1]) / (second_part[2] - first_part[2]) # $/MWh fix_cost = three_cost.fixed # $/h - return DataFrame( - ParamName=["P_min", "P_max", "C_var", "C_fix"], - Value=[P_min, P_max, slope, fix_cost], + return DataFrame(; + ParamName = ["P_min", "P_max", "C_var", "C_fix"], + Value = [P_min, P_max, slope, fix_cost], ) end @@ -89,21 +89,21 @@ function _build_battery( ) name = string(bus.number) * "_BATTERY" device = GenericBattery(; - name=name, - available=true, - bus=bus, - prime_mover_type=PSY.PrimeMovers.BA, - initial_energy=energy_capacity / 2, - state_of_charge_limits=(min=energy_capacity * 0.05, max=energy_capacity), - rating=rating, - active_power=rating, - input_active_power_limits=(min=0.0, max=rating), - output_active_power_limits=(min=0.0, max=rating), - efficiency=(in=efficiency_in, out=efficiency_out), - reactive_power=0.0, - reactive_power_limits=nothing, - base_power=100.0, - operation_cost=PSY.TwoPartCost(0.0, 0.0), + name = name, + available = true, + bus = bus, + prime_mover_type = PSY.PrimeMovers.BA, + initial_energy = energy_capacity / 2, + state_of_charge_limits = (min = energy_capacity * 0.05, max = energy_capacity), + rating = rating, + active_power = rating, + input_active_power_limits = (min = 0.0, max = rating), + output_active_power_limits = (min = 0.0, max = rating), + efficiency = (in = efficiency_in, out = efficiency_out), + reactive_power = 0.0, + reactive_power_limits = nothing, + base_power = 100.0, + operation_cost = PSY.TwoPartCost(0.0, 0.0), ) return device end @@ -128,24 +128,24 @@ function add_hybrid_to_chuhsi_bus!(sys::System) load = get_component(PowerLoad, sys, load_name) # Create the Hybrid hybrid_name = string(bus.number) * "_Hybrid" - hybrid = PSY.HybridSystem( - name=hybrid_name, - available=true, - status=true, - bus=bus, - active_power=1.0, - reactive_power=0.0, - base_power=100.0, - operation_cost=TwoPartCost(nothing), - thermal_unit=thermal, #new_th, - electric_load=load, #new_load, - storage=bat, - renewable_unit=renewable, #new_ren, - interconnection_impedance=0.0 + 0.0im, - interconnection_rating=nothing, - input_active_power_limits=(min=0.0, max=10.0), - output_active_power_limits=(min=0.0, max=10.0), - reactive_power_limits=nothing, + hybrid = PSY.HybridSystem(; + name = hybrid_name, + available = true, + status = true, + bus = bus, + active_power = 1.0, + reactive_power = 0.0, + base_power = 100.0, + operation_cost = TwoPartCost(nothing), + thermal_unit = thermal, #new_th, + electric_load = load, #new_load, + storage = bat, + renewable_unit = renewable, #new_ren, + interconnection_impedance = 0.0 + 0.0im, + interconnection_rating = nothing, + input_active_power_limits = (min = 0.0, max = 10.0), + output_active_power_limits = (min = 0.0, max = 10.0), + reactive_power_limits = nothing, ) # Add Hybrid add_component!(sys, hybrid) diff --git a/test/x_test_cooptimizer_with_build.jl b/test/x_test_cooptimizer_with_build.jl index 07389aa5..84c15822 100644 --- a/test/x_test_cooptimizer_with_build.jl +++ b/test/x_test_cooptimizer_with_build.jl @@ -1,5 +1,5 @@ @testset "Test HybridSystem CoOptimizer DecisionModel" begin - sys = PSB.build_RTS_GMLC_RT_sys(raw_data=PSB.RTS_DIR, horizon=864) + sys = PSB.build_RTS_GMLC_RT_sys(; raw_data = PSB.RTS_DIR, horizon = 864) # Attach Data to System Ext bus_name = "chuhsi" @@ -31,11 +31,11 @@ m = DecisionModel( MerchantHybridCooptimized, ProblemTemplate(CopperPlatePowerModel), - sys, - optimizer=HiGHS_optimizer, - store_variable_names=true, + sys; + optimizer = HiGHS_optimizer, + store_variable_names = true, ) - build_out = PSI.build!(m, output_dir=mktempdir(cleanup=true)) + build_out = PSI.build!(m; output_dir = mktempdir(; cleanup = true)) @test build_out == PSI.BuildStatus.BUILT solve_out = PSI.solve!(m) @test solve_out == PSI.RunStatus.SUCCESSFUL diff --git a/test/x_test_optimizer_sequence.jl b/test/x_test_optimizer_sequence.jl index afc20505..f851cc47 100644 --- a/test/x_test_optimizer_sequence.jl +++ b/test/x_test_optimizer_sequence.jl @@ -3,9 +3,13 @@ ######## Load Systems ######### ############################### - sys_rts_da = PSB.build_RTS_GMLC_DA_sys(raw_data=PSB.RTS_DIR, horizon=48) + sys_rts_da = PSB.build_RTS_GMLC_DA_sys(; raw_data = PSB.RTS_DIR, horizon = 48) sys_rts_rt = - PSB.build_RTS_GMLC_RT_sys(raw_data=PSB.RTS_DIR, horizon=864, interval=Minute(1440)) + PSB.build_RTS_GMLC_RT_sys(; + raw_data = PSB.RTS_DIR, + horizon = 864, + interval = Minute(1440), + ) # There is no Wind + Thermal in a Single Bus. # We will try to pick the Wind in 317 bus Chuhsi @@ -62,9 +66,9 @@ m = DecisionModel( MerchantHybridEnergyOnly, ProblemTemplate(CopperPlatePowerModel), - sys_rts_rt, - optimizer=HiGHS_optimizer, - horizon=864, + sys_rts_rt; + optimizer = HiGHS_optimizer, + horizon = 864, ) sim_optimizer = build_simulation_case_optimizer( diff --git a/test/x_test_optimizer_with_build.jl b/test/x_test_optimizer_with_build.jl index ea141090..f0f0ded2 100644 --- a/test/x_test_optimizer_with_build.jl +++ b/test/x_test_optimizer_with_build.jl @@ -1,5 +1,5 @@ @testset "Test HybridSystem CoOptimizer DecisionModel" begin - sys = PSB.build_RTS_GMLC_RT_sys(raw_data=PSB.RTS_DIR, horizon=864) + sys = PSB.build_RTS_GMLC_RT_sys(; raw_data = PSB.RTS_DIR, horizon = 864) # Attach Data to System Ext bus_name = "chuhsi" @@ -30,12 +30,12 @@ m = DecisionModel( MerchantHybridEnergyOnly, ProblemTemplate(CopperPlatePowerModel), - sys, - optimizer=HiGHS_optimizer, - calculate_conflict=true, - store_variable_names=true, + sys; + optimizer = HiGHS_optimizer, + calculate_conflict = true, + store_variable_names = true, ) - build_out = PSI.build!(m, output_dir=mktempdir(cleanup=true)) + build_out = PSI.build!(m; output_dir = mktempdir(; cleanup = true)) @test build_out == PSI.BuildStatus.BUILT solve_out = PSI.solve!(m) @test solve_out == PSI.RunStatus.SUCCESSFUL