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
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