diff --git a/docs/make.jl b/docs/make.jl
index ced9f477cc..5c348efed5 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -15,7 +15,9 @@ links = InterLinks(
# This is a fallback for the docstrings that are referenced within IS docstrings
fallbacks = ExternalFallbacks(
"ComponentContainer" => "@extref InfrastructureSystems.ComponentContainer",
- "InfrastructureSystemsComponent" => "@extref InfrastructureSystems.InfrastructureSystemsComponent"
+ "InfrastructureSystemsComponent" => "@extref InfrastructureSystems.InfrastructureSystemsComponent",
+ "InfrastructureSystemsInternal" => "@extref InfrastructureSystems.InfrastructureSystemsInternal",
+ "PiecewiseLinearData" => "@extref InfrastructureSystems.PiecewiseLinearData",
)
# This is commented out because the output is not user-friendly. Deliberation on how to best
@@ -40,8 +42,15 @@ pages = OrderedDict(
"Save and read data with a JSON" => "how_to/serialize_data.md",
],
"...add a component using natural units (MW)" => "how_to/add_component_natural_units.md",
+ "...read component values in different unit systems" => "how_to/convert_unit_systems.md",
+ "...convert transformer impedances between per-unit bases" => "how_to/convert_transformer_impedance.md",
+ "...use subsystems" => "how_to/use_subsystems.md",
"...use context managers for bulk operations" => "how_to/use_context_managers.md",
- "...add additional data to a component" => "how_to/adding_additional_fields.md",
+ "...add additional data to a component" => Any[
+ "Add Supplemental Attributes to a System" => "how_to/add_supplemental_attributes.md",
+ "Use Supplemental Attributes" => "how_to/use_supplemental_attributes.md",
+ "Add additional fields to a component" => "how_to/adding_additional_fields.md",
+ ],
"...add time-series data" => Any[
"Parse time series data from .csv files" => "how_to/parse_ts_from_csvs.md",
"Improve performance with time series data" => "how_to/improve_ts_performance.md",
@@ -70,7 +79,6 @@ pages = OrderedDict(
"explanation/per_unit.md",
"explanation/power_concepts.md",
"explanation/conforming_and_non_conforming_loads.md",
- "explanation/transformer_per_unit_models.md",
"explanation/time_series.md",
"explanation/dynamic_data.md",
"explanation/supplemental_attributes.md",
diff --git a/docs/src/api/enumerated_types.md b/docs/src/api/enumerated_types.md
index 2ee4ce37ec..1dccd40cdf 100644
--- a/docs/src/api/enumerated_types.md
+++ b/docs/src/api/enumerated_types.md
@@ -92,7 +92,7 @@ EIA Annual Energy Review. `ThermalFuels` has the options:
| `BLACK_LIQUOR` | BLQ | Black Liquor |
| `WOOD_WASTE_LIQUIDS` | WDL | Wood Waste Liquids excluding Black Liquor (includes red liquor, sludge wood, spent sulfite liquor, and other wood-based liquids) |
| `LANDFILL_GAS` | LFG | Landfill Gas |
-| `OTHEHR_BIOMASS_GAS` | OBG | Other Biomass Gas (includes digester gas, methane, and other biomass gasses) |
+| `OTHER_BIOMASS_GAS` | OBG | Other Biomass Gas (includes digester gas, methane, and other biomass gasses) |
| `NUCLEAR` | NUC | Nuclear Uranium, Plutonium, Thorium |
| `WASTE_HEAT` | WH | Waste heat not directly attributed to a fuel source |
| `TIREDERIVED_FUEL` | TDF | Tire-derived Fuels |
@@ -159,7 +159,7 @@ the different alternatives of `ReservoirDataType`, which has the options:
| `CONFORMING` | Conforming load |
| `UNDEFINED` | Undefined or unknown whether load is conforming or non-conforming |
-## [Tranformer Control Objectives](@id xtf_crtl)
+## [Transformer Control Objectives](@id xtf_crtl)
`TransformerControlObjective` is used to select the control objective for a transformer's
tap changer, which can be used to determine the tap position during power flow calculations.
diff --git a/docs/src/api/glossary.md b/docs/src/api/glossary.md
index 70a702bcd0..08bc415776 100644
--- a/docs/src/api/glossary.md
+++ b/docs/src/api/glossary.md
@@ -1,7 +1,7 @@
# Glossary and Acronyms
-[A](@ref) | [D](@ref) | [E](@ref) | [F](@ref) | [H](@ref) | [I](@ref) | [O](@ref) | [P](@ref) | [R](@ref) |
-[S](@ref) | [V](@ref) | [W](@ref) | [Z](@ref)
+[A](@ref) | [C](@ref) | [D](@ref) | [E](@ref) | [F](@ref) | [H](@ref) | [I](@ref) | [O](@ref) | [P](@ref) | [R](@ref) |
+[S](@ref) | [U](@ref) | [V](@ref) | [W](@ref) | [Z](@ref)
### A
@@ -13,12 +13,24 @@
- *AVR*: Automatic Voltage Regulator
+### C
+
+ - *CA*: EIA prime mover code for the steam turbine (combined cycle steam) portion of a combined cycle plant
+
+ - *CAISO*: California Independent System Operator
+
+ - *CC*: EIA prime mover code for combined cycle units
+
+ - *CT*: Combustion Turbine
+
### D
- *DC*: Direct current
- *DERA1*:
+ - *Deterministic*: mathematical model in which the outcomes are precisely determined through known relationships among states and events. For contrast, see the definition of [Probabilistic](@ref P).
+
- *Dynamic*: Refers to data and simulations for power system transient simulations using differential
equations. Common examples include signal stability analysis to verify the power system will
maintain stability in the few seconds following an unexpected fault or generator trip. For contrast,
@@ -26,12 +38,22 @@
### E
+ - *EIA*: U.S. Energy Information Administration
+
+ - *EIM*: Energy Imbalance Market
+
- *EMF*: Electromotive force
+
- *ESAC*: IEEE Type AC Excitation System model
+
- *ESDC*: IEEE Type DC Excitation System model
+
- *EXAC*: IEEE Type AC Excitation System (modified) model
+
- *EXPIC*: Proportional/Integral Excitation System from PSS/E
+
- *EXST*: IEEE Type ST (Static) Excitation System model
+
- *EX4VSA*: IEEE Excitation System for Voltage Security Assessment with Over-Excitation Limits.
### F
@@ -53,6 +75,8 @@
forecast included the next day plus a 24-hour lookahead window, the horizon would be
`Dates.Hour(48)` or `Dates.Day(2)`. See the article on [`Time Series Data`](@ref ts_data).
+ - *HRSG*: Heat Recovery Steam Generator
+
- *HVDC*: High-voltage DC
### I
@@ -83,6 +107,8 @@
### P
+ - *PCC*: Point of Common Coupling. The point where a generator or plant connects to the grid.
+
- *PLL*: Phase-locked loop
- *PSS*: Power System Stabilizer
@@ -101,6 +127,8 @@
- *pu* or *p.u.*: Per-unit
+ - *PWM*: Pulse-width modulation. A switching technique used in power converters to synthesize a desired AC output voltage by rapidly toggling switches at a high frequency.
+
### R
- *REECB1*: Renewable Energy Electric Controller Type B1
@@ -117,6 +145,8 @@
- *SIL*: Surge impedance loading
+ - *ST*: Steam Turbine
+
- *States*: Correspond to the set of inputs, outputs or variables, that evolve dynamically in
[`PowerSimulationsDynamics.jl`](https://sienna-platform.github.io/PowerSimulationsDynamics.jl/stable/),
commonly via a differential-algebraic system of equations. In `PowerSystems.jl`, a component
@@ -133,6 +163,16 @@
- *STAB*: Speed Sensitive Stabilizing PSS Model
+ - *Struct*: A composite data type in Julia that can store multiple values in a single object. See the Julia documentation on [`struct`](https://docs.julialang.org/en/v1/base/base/#struct)
+ and [Composite Types](https://docs.julialang.org/en/v1/manual/types/#Composite-Types).
+
+ - *SVM*: Space vector modulation. A control algorithm for three-phase inverters that represents the desired output voltage as a vector in the complex plane and selects switching states to approximate it, achieving lower harmonic distortion than basic [PWM](@ref D).
+
+### U
+
+ - *UUID*: Universally Unique Identifier. A 128-bit identifier formatted as a 32-character
+ hexadecimal string (e.g. `5f180c4c-cd81-4b80-8c60-627c28aef8b0`).
+
### V
- *VSCLine*: Voltage-Source Converter HVDC Line
@@ -141,6 +181,8 @@
### W
+ - *WECC*: Western Electricity Coordinating Council
+
- *Window*: A forecast window is one forecast run that starts at one [initial time](@ref I)
and extends through the forecast [horizon](@ref H). Typically, a forecast data set
contains multiple forecast windows, with sequential initial times. For example, a
diff --git a/docs/src/api/public.md b/docs/src/api/public.md
index 1beb449405..1e7399548c 100644
--- a/docs/src/api/public.md
+++ b/docs/src/api/public.md
@@ -10,7 +10,7 @@ Pages = ["PowerSystems.jl",
"injection.jl",
"devices.jl",
"loads.jl",
- "supplemental_constructors",
+ "supplemental_constructors.jl",
"generation.jl",
"reserves.jl",
"storage.jl",
@@ -20,7 +20,6 @@ Pages = ["PowerSystems.jl",
"static_models.jl",
"subsystems.jl",
"static_injection_subsystem.jl",
- "dynamic_models.jl",
"operational_cost.jl",
"cost_function_timeseries.jl",
"definitions.jl"
diff --git a/docs/src/api/static_injection_subtypes.md b/docs/src/api/static_injection_subtypes.md
index 25c1302356..6fe8906250 100644
--- a/docs/src/api/static_injection_subtypes.md
+++ b/docs/src/api/static_injection_subtypes.md
@@ -41,7 +41,7 @@ This document summarizes the similarities and differences between [`StaticInject
² EnergyReservoirStorage uses `input_active_power_limits` and `output_active_power_limits` instead
-Here, "`MinMax` (optional)" means `Union{MinMax, Nothing}`, with `nothing` repesenting "no limits" and being the default.
+Here, "`MinMax` (optional)" means `Union{MinMax, Nothing}`, with `nothing` representing "no limits" and being the default.
⊕ = Split across 3 ZIP fields: `*_constant_*`, `*_impedance_*`, `*_current_*`
diff --git a/docs/src/api/type_tree.md b/docs/src/api/type_tree.md
index 62b03c8562..2a3df37d29 100644
--- a/docs/src/api/type_tree.md
+++ b/docs/src/api/type_tree.md
@@ -2,7 +2,7 @@
Here is the complete `PowerSystems.jl` type hierarchy:
-```@repl types
+```@example types
using PowerSystems #hide
import TypeTree: tt #hide
docs_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "utils"); #hide
diff --git a/docs/src/explanation/buses_type_explanation.md b/docs/src/explanation/buses_type_explanation.md
index 46045a8b9a..4f03cc6e85 100644
--- a/docs/src/explanation/buses_type_explanation.md
+++ b/docs/src/explanation/buses_type_explanation.md
@@ -1,26 +1,50 @@
# [Understanding ACBusTypes](@id bustypes)
-`PowerSystems.jl` supports multiple types of AC buses, [listed here](@ref acbustypes_list).
-When creating nodal datasets, the definitions for AC Buses can have a significant impact on the
-topology logic for the network.
+In AC power flow analysis, every bus in the network has four associated quantities: real
+power injection ($P$), reactive power injection ($Q$), voltage magnitude ($|V|$), and
+voltage angle ($\delta$). The power flow problem is solvable only when exactly two of
+these four quantities are specified at each bus — the other two are determined by the
+solver. The [`ACBusTypes`](@ref) of a bus declares which two quantities are known, and therefore
+shapes how the power flow problem is formulated across the whole network.
-## Voltage Control Types
+`PowerSystems.jl` supports five [`ACBusTypes`](@ref)s, [listed here](@ref acbustypes_list). The
+choice of bus type for each bus in a dataset has a direct effect on solver behavior,
+convergence, and the interpretation of results.
+
+## [Voltage Control Types](https://en.wikipedia.org/wiki/Voltage_control_and_reactive_power_management)
+
+Most buses in a network fall into one of two voltage control categories, depending on
+whether the equipment connected can actively regulate its terminal voltage.
- `PQ`:
- + **Known:** Real Power Injection ($P$) and Reactive Power Injection ($Q$). These are typically the loads at that bus or fixed power factor generators.
- + **Unknown:** Voltage Magnitude ($|V|$) and Voltage Angle ($\delta$).
- + Represents a bus where the voltage magnitude and angle are free to vary based on the system conditions.
+ + **Known:** Real power injection ($P$) and reactive power injection ($Q$). These
+ are typically fixed loads or generators operating at a fixed power factor.
+ + **Unknown:** Voltage magnitude ($|V|$) and voltage angle ($\delta$), which are
+ determined by the power flow solution.
+ + This is the most common bus type. Because $|V|$ is unconstrained, the voltage at
+ a `PQ` bus reflects the state of the surrounding network rather than any local
+ control action.
- `PV`:
- + **Known:** Real Power Injection ($P$) and Voltage Magnitude ($|V|$).
- + **Unknown:** Reactive Power Injection ($Q$) and Voltage Angle ($\delta$).
- + Typically represents a bus with an injector connected, where the injector controls the reactive power output and regulates the bus voltage magnitude to a setpoint.
+ + **Known:** Real power injection ($P$) and voltage magnitude ($|V|$).
+ + **Unknown:** Reactive power injection ($Q$) and voltage angle ($\delta$).
+ + Represents a bus with a generator or other device actively regulating its
+ terminal voltage to a setpoint. The reactive power output floats to whatever
+ value is needed to hold that voltage. This is the typical representation of a
+ synchronous generator with an automatic voltage regulator (AVR).
+
+The distinction matters because placing a generator on a `PV` bus rather than a `PQ` bus
+allows the power flow solver to use the voltage setpoint as a constraint, which is closer
+to how real generators operate and tends to produce more physically meaningful results.
## Reference and Slack Buses
-There is a nuanced distinction between a slack bus and a reference bus. In most small test sytems and academic discussions, the system has a single slack bus which is also the reference bus. However, for large interconnected cases or cases with a very radial structure, having a single bus that takes on all the real power mistmatch in the system can lead to erroneous results. In PowerSystems.jl we distinguish the posibility of having slacks and reference buses. Is up to the modeler to decide how to handle the classifications inside of the applications. In other words, wether a reference bus is also a slack or viceversa is left to the application developer.
+Every power flow problem also requires buses that handle system-wide power balance and
+provide an angular reference. `PowerSystems.jl` distinguishes between these two roles,
+because conflating them — as many textbooks and smaller test systems do — can produce
+misleading results in large or radially structured networks.
- `SLACK`:
@@ -38,8 +62,20 @@ For the study of large interconnected areas that include different asynchronous
## Isolated Buses and the `available` field
-In certain modeling applications, particularly power flow modeling tools, the designation of
-"isolated" is used to signal that the bus is temporarily disconnected from the network, as are any other components attached to it. However, in `PowerSystems.jl`, a bus and its components can be excluded from an analysis or optimization without changing the underlying network topology by setting the `available` field to false: `set_available!(bus, false)`.
-
-In PowerSystems.jl the `ISOLATED` type means exactly that: The bus is not connected to the network. In
-resource analysis where systems contain isolated subsystems that can be ignored for the purposes of the power flow but are relevant when performing optimization, the `ISOLATED` designation provides the capability to describe those situations in precise terms. `ISOLATED` buses can also be made unavailable to make the components attached to them also unavailable.
+Many power flow tools use an "isolated" designation to signal that a bus is temporarily
+disconnected from the network. `PowerSystems.jl` keeps this concept but separates it from
+the question of whether a component participates in a given analysis.
+
+In `PowerSystems.jl`, `ISOLATED` means precisely that the bus is structurally
+disconnected from the network — it has no active connections. This is distinct from
+*excluding* a bus from a particular analysis, which is handled by setting the `available`
+field to `false` via `set_available!(bus, false)`. Setting `available = false` removes the
+bus and its attached components from consideration without altering the underlying network
+topology, which is important when the same dataset is used across multiple modeling
+contexts.
+
+This design supports resource analysis workflows where isolated subsystems exist in the
+data — perhaps representing planned expansions or decommissioned equipment — and must be
+represented precisely while being excluded from active power flow or optimization runs.
+`ISOLATED` buses can additionally be made unavailable, which propagates the exclusion to
+all components attached to them.
diff --git a/docs/src/explanation/conforming_and_non_conforming_loads.md b/docs/src/explanation/conforming_and_non_conforming_loads.md
index 5a1054329c..04b592ed69 100644
--- a/docs/src/explanation/conforming_and_non_conforming_loads.md
+++ b/docs/src/explanation/conforming_and_non_conforming_loads.md
@@ -1,35 +1,65 @@
# [Conforming and Non-Conforming Loads](@id conf_loads)
-The difference between conforming and non-conforming loads is not particularly significant for how PowerSystems.jl manages data, as loads can be assigned either aggregate or individual time series.
+At its core, the distinction between conforming and non-conforming loads is about
+forecastability. The definitions and practical criteria used here draw from the
+[CAISO EIM's "Non-Conforming Load FAQ"](https://www.westerneim.com/Documents/EIM-Non-Conforming-Load-FAQ.pdf).
-## Definitions and use cases
+**Conforming loads** are typically residential and commercial loads that, in aggregate,
+follow a predictable daily and seasonal pattern influenced by time of day, day of the week,
+and weather conditions. This predictability allows modelers to use aggregate forecasts of
+the total area load with a high degree of accuracy and then disaggregate the curve using
+participation factors.
-At its core, the distinction is about forecastability. The De Facto-Criteria and Practical Uses of this distinction comes from CAISO's Energy Imbalance Market (EIM) definitions. This section draws from the [CAISO EIM's "Non-Conforming Load FAQ"](https://www.westerneim.com/Documents/EIM-Non-Conforming-Load-FAQ.pdf) document.
+**Non-conforming loads** have consumption patterns that don't follow the aggregate system
+behavior and can fluctuate independently of the total system load. These are often large
+industrial processes with unique operational cycles, for example:
-Conforming loads are the typically residential and commercial loads that, in aggregate, follow a predictable daily and seasonal pattern influenced by factors like time of day, day of the week, and weather conditions. This predictability allows modelers to use aggregate forecasts of the total area load with a high degree of accuracy and then desagregate the curve using participation factors.
+ - **Electric Arc Furnaces:** Used in steel manufacturing, electric arc furnaces cause
+ massive, sudden spikes in power demand when in operation. Depending on the time-scale
+ of modeling, these loads can require a consumption pattern that matches the underlying
+ industrial process.
-Non-conforming loads, on the other hand have patterns of consumption that don't follow the aggregate behavior. Their consumption does not follow typical patterns and can fluctuate with different rates as the total system load. These are often large industrial processes with unique operational cycles. For example:
+ - **Large Data Centers:** While having a relatively constant base load, the computational
+ demands of large data centers almost never change with the patterns of the rest of the
+ system. These loads tend to be flat and in some advanced models include the behavior of
+ compute load dispatch algorithms that conduct geographic price arbitrage.
- - Electric Arc Furnaces: Used in steel manufacturing, electric arc furnaces cause massive, sudden spikes in power demand when they are in operation. Depending on the time-scale of modeling these loads can require a consumption pattern that mathches the underlying industrial process.
+ - **Traction Loads for Railways:** The movement of electric trains results in fluctuating
+ power demand along railway lines based on transportation demand.
- - Large Data Centers: While having a relatively constant base load, the computational demands of large data centers almost never change with the patterns from the rest of the system. These loads tend to be flat and in some advanced models include the behavior of compute load dispatch algorithms that conduct geographic price arbitrage.
-
- - Traction Loads for Railways: The movement of electric trains results in fluctuating power demand along the railway lines based on the transportation demand.
-
- - Pumping Loads: Similarly to tranction loads, pumping loads can change according to water or gas demand and supply needs and not system level behavior. In its data collection manuals, WECC specifies that pumping loads are typically modeled as non-conforming in power flow cases.
+ - **Pumping Loads:** Pumping loads can change according to water or gas demand and supply
+ needs rather than system-level behavior. [WECC](@ref W) specifies in its data collection manuals
+ that pumping loads are typically modeled as non-conforming in power flow cases.
## Modeling using PowerSystems.jl
-Drawing again from CAISO's EIM procedures, the management of non-conforming loads involves:
+In practice — following conventions established by markets such as
+[CAISO's EIM](https://www.westerneim.com/Documents/EIM-Non-Conforming-Load-FAQ.pdf) —
+non-conforming loads are handled differently from conforming ones in three key ways:
+their historical data is segregated from the aggregate load before training forecast models;
+they are forecasted or scheduled independently rather than by disaggregating an area
+forecast; and in some market contexts they are represented as dispatchable negative
+generation rather than passive demand. This is also known as "Dispatchable Demand Response" (DDR) in the CAISO market.
- 1. **Segregated Data Submission**: The historical consumption data for the non-conforming load must be separated from the general, or "conforming," load data. This "cleanses" the historical data used to train weather-based load forecasting models, thereby improving their accuracy for the bulk of the system's load.
+In `PowerSystems.jl`, these distinctions surface in two places:
- 2. **Independent Forecasting**: While the system operator forecasts the aggregate conforming load, the entity responsible for the non-conforming load is often required to submit its own forecast or schedule.
+ 1. **The `conformity` field.** Concrete subtypes of [`StaticLoad`](@ref) carry a
+ `conformity` field that records whether a load is conforming or non-conforming (see the
+ [options listed here](@ref loadconform_list)). This field exists for monitoring and
+ bookkeeping purposes — it allows downstream tools and analysts to identify which loads
+ were treated as non-conforming without needing to inspect the time series data directly.
- 3. **Specialized Modeling**: In market and operational models, non-conforming loads are often treated as a type of resource. For instance, in the CAISO market, they are represented as "Dispatchable Demand Response" (DDR) resources, which are essentially modeled as negative generation. This allows their behavior to be explicitly accounted for in market clearing and dispatch instructions.
+ 2. **Time series assignment.** The behavioral difference between conforming and
+ non-conforming loads is expressed through time series. A conforming load typically
+ shares an aggregate area forecast that is then scaled by a participation factor; a
+ non-conforming load carries its own individual time series. `PowerSystems.jl` supports
+ both patterns equally — the `conformity` flag declares the intent, while the time series
+ assignment carries it out.
-If a modeler wants to account for the differences in behavior between various loads, they only need to assign a distinct time series to each load. In `PowerSystems.jl`, we keep track of data related to "conformity" for monitoring purposes. This data is defined in the `conformity` field for concrete subtypes of [`StaticLoad`](@ref) and has the [options listed here](@ref loadconform_list). However, the behavioral variations described in the literature are already taken into consideration through the ways modelers can manage these time series assignments.
+This design means that modeling the distinction requires no special data structures or
+separate code paths: assigning a distinct time series to a non-conforming load is
+sufficient to capture its independent behavior.
-### See also:
+### See also
- - Parsing [time series](@ref parsing_time_series)
+ - [Parsing time series](@ref parsing_time_series)
diff --git a/docs/src/explanation/dynamic_data.md b/docs/src/explanation/dynamic_data.md
index 591864bc10..510305383a 100644
--- a/docs/src/explanation/dynamic_data.md
+++ b/docs/src/explanation/dynamic_data.md
@@ -1,54 +1,49 @@
-# Dynamic Devices
+# [Dynamic Devices](@id dynamic_data)
+
+A **dynamic device** is a power system component whose behavior is described by differential equations that evolve over time, rather than by a single steady-state operating point. Dynamic devices capture the [transient response](https://en.wikipedia.org/wiki/Transient_response) of equipment — such as how a generator's rotor speed, voltage, or current changes in the milliseconds to seconds following a disturbance. In `PowerSystems.jl`, every dynamic device is attached to a corresponding [static](@ref S) component that provides the power flow
+solution, while the dynamic component adds the differential equations needed for transient stability and electromagnetic simulation.
+
+A dynamic device has two data layers, the static data layer with static components, and the dynamic data layer with dynamic components.
## Static and Dynamic Data Layers
`PowerSystems.jl` uses two categories to define data for dynamic simulations:
1. [Static](@ref S) components, which includes the data needed to run a power flow problem
- 2. [Dynamic](@ref D) components are those that define differential equations to run a transient simulation. These dynamic
- data are attached to the static components.
+ 2. [Dynamic](@ref D) components are those that define differential equations to run a transient simulation. **These dynamic data are attached to the static components.**
-Although `PowerSystems.jl` is not constrained to only PSS/e files, commonly the data for a
-dynamic simulation comes in a pair of files: One for the static data power flow case (e.g.,
-`.raw` file) and a second one with the dynamic components information (e.g., `.dyr` file).
-However, `PowerSystems.jl` is able to take any power flow case and specify dynamic
-components to it. The two data layers in `PowerSystems.jl` are similar to the data
-division between those two files.
+Although `PowerSystems.jl` is not constrained to only PSS/e files, commonly the data for a dynamic simulation comes in a pair of files: One for the static data power flow case (e.g.,`.raw` file) and a second one with the dynamic components information (e.g., `.dyr` file). However, `PowerSystems.jl` is able to take any power flow case and specify dynamic components to it. The two data layers in `PowerSystems.jl`, static components and dynamic components, are similar to the data division between the static and dynamic data layers.
### Layer 1: Static Components
-The first data layer contains all the information necessary to run a power flow problem:
+The first data layer contains all the information necessary to run a power flow problem or dynamic simulations:
- Vector of `Bus` elements, that define all the buses in the network.
- Vector of `Branch` elements, that define all the branches elements (that connect two buses) in the network.
- - Vector of `StaticInjection` elements, that define all the devices connected to buses that can inject (or withdraw) power. These static devices, typically generators, in `PowerSimulationsDynamics` are used to solve the Power Flow problem that determines the active and reactive power provided for each device.
- - Vector of `PowerLoad` elements, that define all the loads connected to buses that can withdraw current. These are also used to solve the Power Flow.
+ - Vector of [`StaticInjection`](@ref) elements, that define all the devices connected to buses that can inject (or withdraw) power. These static devices, typically generators, in [`PowerSimulationsDynamics`](https://nrel-sienna.github.io/PowerSimulationsDynamics.jl/stable/) are used to solve the power flow problem that determines the active and reactive power provided for each device.
+ - Vector of [`PowerLoad`](@ref) elements, that define all the loads connected to buses that can withdraw current. These are also used to solve power flow.
- Vector of `Source` elements, that define source components behind a reactance that can inject or withdraw current.
- - The base of power used to define per unit values, in MVA as a `Float64` value.
+ - The base of power used to define per unit values, in MVA as a `Float64` value. See [Per-unit Conventions](@ref per_unit).
- The base frequency used in the system, in Hz as a `Float64` value.
+For a hands-on example of building a system with static components, see the [Creating a System](../tutorials/generated_creating_system.md) tutorial.
+
+Once the static layer establishes the network topology and the power flow equilibrium, the dynamic layer can be overlaid on top of it — adding the differential equations that describe how each device behaves when that equilibrium is disturbed.
+
### Layer 2: Dynamic Components
-The second data layer contains the *additional* information describing the dynamic response
-of certain components in the `System`. This data is all attached to components defined in
-the static data layer:
+The second data layer contains the *additional* information describing the dynamic response of certain components in the `System`. This data is all attached to components defined in the static data layer:
- (Optional) Selecting which of the `Lines` (of the `Branch` vector) elements must be modeled of `DynamicLines` elements, that can be used to model lines with differential equations.
- - Vector of `DynamicInjection` elements. These components must be attached to a `StaticInjection` that connects the power flow solution to the dynamic formulation of such device.
+ - Vector of [`DynamicInjection`](@ref) elements. These components must be attached to a [`StaticInjection`](@ref) that connects the power flow solution to the dynamic formulation of such device.
-`DynamicInjection` can be `DynamicGenerator` or `DynamicInverter`, and its specific formulation (i.e. differential equations) will depend on the specific components that define each device (see the sections below). As
-a result, it is possible to flexibly define dynamic data models and methods according to
-the analysis requirements. [`DynamicInjection`](@ref) components use a parametric
-type pattern to materialize the full specification of the dynamic injection model with
-parameters. This design enable the use of parametric methods to specify the mathematical
+[`DynamicInjection`](@ref) can be [`DynamicGenerator`](@ref) or [`DynamicInverter`](@ref), and its specific formulation (i.e. differential equations) will depend on the specific components that define each device (see the sections below). As
+a result, it is possible to flexibly define dynamic data models and methods according to the analysis requirements. [`DynamicInjection`](@ref) components use a parametric type pattern to materialize the full specification of the dynamic injection model with parameters. This design enables the use of parametric methods to specify the mathematical
model of the dynamic components separately.
-[`DynamicInjection`](@ref) components also implement some additional information useful for
-the modeling, like the usual states assumed by the model and the number of states. These values are
-derived from the documentation associated with the model, for instance PSS/e models provide
-parameters, states and variables. Although `PowerSystems.jl` doesn't assume a specific
-mathematical model for the components, the default values for these parameters are derived
-directly from the data model source.
+[`DynamicInjection`](@ref) components also implement some additional information useful for the modeling, like the usual states assumed by the model and the number of states. These values are derived from the documentation associated with the model, for instance PSS/e models provide parameters, states and variables. Although `PowerSystems.jl` doesn't assume a specific mathematical model for the components, the default values for these parameters are derived directly from the data model source.
+
+The two concrete forms of [`DynamicInjection`](@ref) — [`DynamicGenerator`](@ref) and [`DynamicInverter`](@ref) — reflect the two fundamentally different physical mechanisms by which machines couple to the grid: rotating synchronous machines and power-electronics-based converters. Each has its own set of sub-components corresponding to the physical and control processes that govern its dynamic behavior.
## Dynamic Generator Structure
@@ -60,26 +55,20 @@ Each generator is a data structure that is defined by the following components:
- [Power System Stabilizer](@ref PSS): Control dynamics to define an stabilization signal for the AVR.
- [Prime Mover and Turbine Governor](@ref TurbineGov): Thermo-mechanical dynamics and associated controllers.
-```@raw html
-
-```
+Where a synchronous generator's dynamics are rooted in the physics of a rotating mass and magnetic flux, an inverter-based resource has no rotating components — its dynamic behavior is instead shaped entirely by its control algorithms and power electronics. This calls for a different set of sub-components.
## Dynamic Inverter Structure
Each inverter is a data structure that is defined by the following components:
- [DC Source](@ref DCSource): Defines the dynamics of the DC side of the converter.
- - [Frequency Estimator](@ref FrequencyEstimator): That describes how the frequency of the grid
- can be estimated using the grid voltages. Typically a phase-locked loop (PLL).
- - [Outer Loop Control](@ref OuterControl): That describes the active and reactive power
- control dynamics.
- - [Inner Loop Control](@ref InnerControl): That can describe virtual impedance,
- voltage control and current control dynamics.
- - [Converter](@ref Converter): That describes the dynamics of the pulse width modulation (PWM)
- or space vector modulation (SVM).
- - [Filter](@ref Filter): Used to connect the converter output to the grid.
-
-```@raw html
-
-``` ⠀
+ - [Frequency Estimator](@ref FrequencyEstimator): That describes how the frequency of the grid can be estimated using the grid voltages. Typically a phase-locked loop (PLL).
+ - [Outer Loop Control](@ref OuterControl): That describes the active and reactive power control dynamics.
+ - [Inner Loop Control](@ref InnerControl): That can describe virtual impedance, voltage control, and current control dynamics.
+ - [Converter](@ref Converter): That describes the dynamics of the pulse width modulation (PWM) or space vector modulation (SVM).
+ - [Filter](@ref Filter): Used to connect the converter output to the grid.⠀
+
+For a hands-on example of constructing and attaching dynamic generator and inverter components to a system, see the [Adding Dynamic Data](../tutorials/generated_add_dynamic_data.md) tutorial.
+
+```
```
diff --git a/docs/src/explanation/per_unit.md b/docs/src/explanation/per_unit.md
index 511adc445e..493bab4c20 100644
--- a/docs/src/explanation/per_unit.md
+++ b/docs/src/explanation/per_unit.md
@@ -1,6 +1,6 @@
# [Per-unit Conventions](@id per_unit)
-It is often useful to express power systems data in relative terms using per-unit conventions.
+It is often useful to express power systems data in relative terms using per-unit (p.u.) conventions.
`PowerSystems.jl` supports the automatic conversion of data between three different unit systems:
1. `"NATURAL_UNITS"`: The naturally defined units of each parameter (typically MW).
@@ -24,11 +24,13 @@ You can get and set the unit system setting of a `System` with [`get_units_base`
`PowerSystems.jl` provides the `Logging.with_logger`-inspired "context manager"-type
function [`with_units_base`](@ref), which sets the unit system to a particular value,
performs some action, then automatically sets the unit system back to its previous value.
+For a worked example of switching unit systems and reading component values, see
+[Read Component Values in Different Unit Systems](@ref convert_unit_systems).
Conversion between unit systems does not change
the stored parameter values. Instead, unit system conversions are made when accessing
-parameters using the [accessor functions](@ref dot_access), thus making it
-imperative to utilize the accessor functions instead of the "dot" accessor methods to
+parameters using the [getter functions](@ref dot_access), thus making it
+imperative to utilize the getter functions instead of the "dot" accessor methods to
ensure the return of the correct values. The units of the parameter values stored in each
struct are defined in `src/descriptors/power_system_structs.json`.
@@ -41,8 +43,8 @@ defined in `"SYSTEM_BASE"`.
In the future, `PowerSystems.jl` hopes to support defining components in natural units.
For now, if you want to define data in natural units, you must first
set the system units to `"NATURAL_UNITS"`, define an empty component, and then use the
-[accessor functions](@ref dot_access) (e.g., getters and setters), to define each field
-within the component. The accessor functions will then do the data conversion from your
+[getter functions](@ref dot_access) (e.g., getters and setters), to define each field
+within the component. The getter functions will then do the data conversion from your
input data in natural units (e.g., MW or MVA) to per-unit.
By default, `PowerSystems.jl` uses `"SYSTEM_BASE"` because many optimization problems won't
@@ -50,7 +52,39 @@ converge when using natural units. If you change the unit setting, it's suggeste
switch back to `"SYSTEM_BASE"` before solving an optimization problem (for example in
[`PowerSimulations.jl`](https://sienna-platform.github.io/PowerSimulations.jl/stable/)).
+## [Transformer per unit transformations](@id transformers_pu_per_unit)
+
+Per-unit conventions with transformers simplify calculations by normalizing all quantities
+(voltage, current, power, impedance) to a common base. This effectively "retains" the
+ideal transformer from the circuit diagram because the per-unit impedance of a transformer
+remains the same when referred from one side to the other. A more in-depth explanation can
+be found in [this link](https://en.wikipedia.org/wiki/Per-unit_system) or basic power
+systems literature.
+
+Transformer impedance (usually reactive impedance, $X_{pu}$) is typically given on the
+transformer's own nameplate ratings (rated MVA and rated voltages). **The data in
+`PowerSystems.jl` is stored on the device base** and converted to the system base when
+using the getter functions.
+
+The key quantity needed for that conversion is the base impedance of each voltage zone:
+
+$$Z_{base} = \frac{(V_{base,\,LL})^2}{S_{base,\,3\phi}}$$
+
+with $V_{base,\,LL}$ in kV and $S_{base,\,3\phi}$ in MVA. The zone base voltage is
+propagated from the primary side using the transformer's turns ratio:
+
+$$V_{base,\,\text{secondary}} = V_{base,\,\text{primary}} \times \frac{V_{\text{rated,\,secondary}}}{V_{\text{rated,\,primary}}}$$
+
+Note that this value can differ slightly from the attached bus voltage set point. As of
+`PowerSystems.jl` v5, transformer components carry an explicit field for their base
+voltage to make this relationship unambiguous.
+
+For a step-by-step guide to establishing base values and performing the impedance base
+change manually, see
+[Convert Transformer Impedances Between Per-Unit Bases](@ref convert_transformer_impedance).
+
!!! note
- Check the [`Transformers per unit explanation`](@ref transformers_pu) for details on how
- the per-unit is managed
+ The return value of the getter functions, e.g., [`get_x`](@ref) for the transformer
+ impedances will perform these transformations automatically, following the convention
+ described on this page.
diff --git a/docs/src/explanation/plant_attributes.md b/docs/src/explanation/plant_attributes.md
index 80e1485444..84631063ee 100644
--- a/docs/src/explanation/plant_attributes.md
+++ b/docs/src/explanation/plant_attributes.md
@@ -18,13 +18,13 @@ For interoperable analysis across different tools and datasets, it is often nece
3. **Market operations**: ISOs may require unit-level bidding while planning studies use plant-level data
4. **Regulatory reporting**: Different reports require different aggregation levels
-PowerSystems.jl addresses this challenge through **Plant Attributes**, which are
-[`SupplementalAttribute`](@ref supplemental_attributes) types that group individual generator
+`PowerSystems.jl` addresses this challenge through **Plant Attributes**, which are
+[`SupplementalAttribute`](@ref supplemental_attributes_explanation) types that group individual generator
components into logical plant structures while preserving the detailed unit-level information.
## Plant Attribute Types
-PowerSystems.jl provides five specialized plant attribute types, each designed for a specific
+`PowerSystems.jl` provides five specialized plant attribute types, each designed for a specific
generation technology:
```mermaid
@@ -92,7 +92,7 @@ For more information on combined cycle configurations, see the
Represents combined cycle generation when each unit represents a specific configuration with
an aggregate heat rate. Unlike [`CombinedCycleBlock`](@ref), which models the CT/CA relationship
through the HRSG, the fractional representation uses **operation exclusion groups** to define
-which units can operate simultaneously. Only generators with the `CC` (combined cycle)
+which units can operate simultaneously. Only generators with the `CC` (CC, combined cycle)
[prime mover type](@ref pm_list) can be added.
| Field | Type | Description |
@@ -212,7 +212,7 @@ gens_in_group_1 = get_components_in_exclusion_group(sys, cc_fractional, 1)
### Accessing Infrastructure Maps
-Direct access to the mapping dictionaries is available through accessor functions:
+Direct access to the mapping dictionaries is available through getter functions:
```julia
# ThermalPowerPlant
@@ -376,7 +376,7 @@ the plant-unit relationships across save/load cycles.
## See Also
- - [`SupplementalAttribute`](@ref supplemental_attributes) - Base concept for supplemental data
+ - [`SupplementalAttribute`](@ref supplemental_attributes_explanation) - Base concept for supplemental data
- [`ThermalPowerPlant`](@ref) - API reference
- [`CombinedCycleBlock`](@ref) - API reference
- [`CombinedCycleFractional`](@ref) - API reference
diff --git a/docs/src/explanation/power_concepts.md b/docs/src/explanation/power_concepts.md
index 268ed720ac..1137af8952 100644
--- a/docs/src/explanation/power_concepts.md
+++ b/docs/src/explanation/power_concepts.md
@@ -9,7 +9,7 @@ When working with generators in PowerSystems.jl, it's important to understand th
- **Purpose**: Serves as the denominator when converting device parameters to per-unit values
- **Units**: Always stored in **natural units** (MVA)
- **Typical value**: The nameplate capacity of the device
- - **Access**: Retrieved using `get_base_power(device)`
+ - **Access**: Retrieved using [`get_base_power(device)`](@ref)
Base power is a fundamental parameter for the per-unit system and represents the natural scale of the device. For more details on per-unitization, see the [Per-unit Conventions](@ref per_unit) page.
@@ -27,7 +27,7 @@ Base power is a fundamental parameter for the per-unit system and represents the
+ Rotor field winding limits
+ Cooling system capacity
- - **Access**: Retrieved using `get_rating(device)`
+ - **Access**: Retrieved using [`get_rating(device)`](@ref)
The rating is typically determined by the electrical design and thermal limits of the synchronous machine itself. It represents the maximum capability of the electrical generator, independent of the prime mover.
@@ -46,7 +46,7 @@ The rating is typically determined by the electrical design and thermal limits o
+ Boiler capacity (for steam generators)
+ Fuel flow limitations
- - **Access**: Retrieved using `get_max_active_power(device)`
+ - **Access**: Retrieved using [`get_max_active_power(device)`](@ref)
The maximum active power is determined by the mechanical system that drives the generator. This is often less than the rating when considering only real power production.
@@ -54,11 +54,11 @@ The maximum active power is determined by the mechanical system that drives the
### Storage Convention Summary
-| Concept | Storage Units | Accessor Function |
-|:---------------- |:------------------- |:------------------------ |
-| Base Power | Natural units (MVA) | `get_base_power()` |
-| Rating | Device base (p.u.) | `get_rating()` |
-| Max Active Power | Device base (p.u.) | `get_max_active_power()` |
+| Concept | Storage Units | Getter Function |
+|:---------------- |:------------------- |:-------------------------------- |
+| Base Power | Natural units (MVA) | [`get_base_power()`](@ref) |
+| Rating | Device base (p.u.) | [`get_rating()`](@ref) |
+| Max Active Power | Device base (p.u.) | [`get_max_active_power()`](@ref) |
### Physical Interpretation
@@ -84,35 +84,9 @@ In this example:
### Unit System Conversions
-When you access these values through the PowerSystems.jl accessor functions, they are automatically converted based on the current unit system setting:
-
-```julia
-# Assuming base_power = 100 MVA, rating = 1.0 p.u., max_active_power = 0.95 p.u.
-sys = System(100.0) # System base power = 100 MVA
-gen = get_component(ThermalStandard, sys, "gen1")
-
-# In DEVICE_BASE
-set_units_base_system!(sys, "DEVICE_BASE")
-get_base_power(gen) # Returns: 100.0 MVA (always natural units)
-get_rating(gen) # Returns: 1.0 p.u. (on device base)
-get_max_active_power(gen) # Returns: 0.95 p.u. (on device base)
-
-# In NATURAL_UNITS
-set_units_base_system!(sys, "NATURAL_UNITS")
-get_base_power(gen) # Returns: 100.0 MVA (always natural units)
-get_rating(gen) # Returns: 100.0 MVA (converted from p.u.)
-get_max_active_power(gen) # Returns: 95.0 MW (converted from p.u.)
-
-# In SYSTEM_BASE
-set_units_base_system!(sys, "SYSTEM_BASE")
-get_base_power(gen) # Returns: 100.0 MVA (always natural units)
-get_rating(gen) # Returns: 1.0 p.u. (on system base, assuming system base = device base)
-get_max_active_power(gen) # Returns: 0.95 p.u. (on system base)
-```
-
-!!! note
-
- Base power is **always** returned in natural units (MVA) regardless of the unit system setting. The rating and maximum active power are stored in device base but are automatically converted when accessed based on the current unit system setting.
+When you access these values through the `PowerSystems.jl` getter functions, they are
+automatically converted based on the current unit system setting. For a step-by-step
+guide, see [Read Component Values in Different Unit Systems](@ref convert_unit_systems).
## See Also
diff --git a/docs/src/explanation/supplemental_attributes.md b/docs/src/explanation/supplemental_attributes.md
index 0eb5a30025..d2cb9e1f49 100644
--- a/docs/src/explanation/supplemental_attributes.md
+++ b/docs/src/explanation/supplemental_attributes.md
@@ -1,162 +1,82 @@
-# [Supplemental Attributes](@id supplemental_attributes)
+# [About Supplemental Attributes](@id supplemental_attributes_explanation)
-While the [`ext` field is a mechanism](@ref additional_fields) for adding arbitrary metadata. PowerSystems.jl, has moved towards a more structured and formalized way of handling supplemental data using `SupplementalAttribute` structs. This is designed to store metadata in a more organized fashion than a generic dictionary. These attributes are intended to be attached to a [`Component`](@ref) types.
+Supplemental attributes help PowerSystems.jl manage the relationships between power system components and their metadata. Instead of putting everything into basic component definitions, this system keeps electrical data separate from contextual information like location, outages, or plant groupings.
-Supplemmental attributes can be shared between components or have 1-1 relationships. This is particularly
-useful to represent components in the same geographic location or outages for multiple components. Conversely, components can contain many attributes.
+## Why Use Supplemental Attributes?
-```mermaid
-flowchart LR
- A["Attribute A"] --> B["Component 1"]
- A --> C["Component2"]
- D["Attribute B"] --> C["Component 2"]
- E["Attribute C"] --> F["Component 3"]
-```
+Power system components exist in multiple contexts. A generator isn't just defined by its electrical properties—it also has a geographic location, belongs to a plant, and may share infrastructure with other units.
-Supplemental attributes can also contain timeseries in the same fashion that a component can allowing the user to model time varying attributes like outage time series or weather dependent probabilities. See the section [`Working with Time Series Data`](@ref tutorial_time_series) for details on time series handling.
-
-## Getting the attributes in a system
-
-You can retrieve the attributes in a system using the function [`get_supplemental_attributes`](@ref).
-You must pass a supplemental attribute type, which can be concrete or abstract. If you pass an abstract type, all concrete types
-that are subtypes of the abstract type will be returned.
-
-```julia
-for outage in get_supplemental_attributes(FixedForcedOutage, system)
- @show summary(outage)
-end
-```
-
-You can optionally pass a filter function to reduce the returned attributes. This example will
-return only FixedForcedOutage instances that have a mean time to recovery greater than or equal to 0.5.
-
-```julia
-for outage in get_supplemental_attributes(
- x -> get_mean_time_to_recovery(x) >= 0.5,
- FixedForcedOutage,
- system,
-)
- @show summary(outage)
-end
-```
+Traditional approaches used generic dictionary fields to store this extra information. But this created problems:
-## Getting the attributes associated with a component
+ - Data inconsistency across large systems
+ - Maintenance difficulties
+ - No validation of the information stored
-You can retrieve the attributes associated with a component using the function [`get_supplemental_attributes`](@ref).
-This method signatures are identical to the versions above that operate on a system; just swap the system for a component.
+Supplemental attributes solve this by using structured types instead of loose dictionaries. This provides:
-You must pass a supplemental attribute type, which can be concrete or abstract. If you pass an abstract type, all concrete types
-that are subtypes of the abstract type will be returned.
+**Clean separation**: Electrical behavior stays in component definitions. Everything else goes in attributes.
-```julia
-gen1 = get_component(ThermalStandard, system, "gen1")
-for outage in get_supplemental_attributes(FixedForcedOutage, gen)
- @show summary(outage)
-end
-```
+**Clear relationships**: The connections between components and their contexts are explicit and easy to query.
-You can optionally pass a filter function to reduce the returned attributes. This example will
-return only FixedForcedOutage instances that have a mean time to recovery greater than or equal to 0.5.
-
-```julia
-for outage in get_supplemental_attributes(
- x -> get_mean_time_to_recovery(x) >= 0.5,
- gen,
- FixedForcedOutage,
-)
- @show summary(outage)
-end
-```
+**Type safety**: The system validates data and gives helpful error messages when something's wrong.
-## Getting the attributes associated with a component type
+## How Relationships Work
-You can retrieve the attributes associated with any component of a given type
-using the function [`get_associated_supplemental_attributes`](@ref). If one attribute is attached to
-multiple components of the given type, it will still only appear once in the result.
+Supplemental attributes use many-to-many relationships. One attribute can connect to multiple components, and one component can have multiple attributes.
- 1. Get all the attributes associated with all components of a given type.
+For example:
- ```julia
- for outage in get_associated_supplemental_attributes(system, ThermalStandard)
- @show summary(outage)
- end
- ```
+ - Multiple generators at the same plant share geographic coordinates
+ - One weather pattern affects several plants in a region
+ - Each generator might have its own maintenance schedule
- 2. Same as #1, but filter the results by attribute type, which can be concrete or abstract.
+```mermaid
+flowchart LR
+ A["Attribute A"] --> B["Component 1"]
+ A --> C["Component2"]
+ D["Attribute B"] --> C["Component 2"]
+ E["Attribute C"] --> F["Component 3"]
+```
- ```julia
- for outage in
- get_associated_supplemental_attributes(
- system,
- ThermalStandard;
- attribute_type = FixedForcedOutage,
- )
- @show summary(outage)
- end
- ```
+This flexibility matches how power systems actually work, where components share resources and are affected by common factors.
-## Getting the components associated with an attribute
+Supplemental attributes can be concrete or abstract. See the [Julia Types documentation](https://docs.julialang.org/en/v1/manual/ty) for more information on these types. Here is an example using the `PowerSystems.jl` Type Tree.
-You can retrieve the components associated with a single supplemental attribute using the
-function [`get_associated_components`](@ref).
+```@example types
+using PowerSystems #hide
+import TypeTree: tt #hide
+docs_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "utils"); #hide
+include(joinpath(docs_dir, "docs_utils.jl")); #hide
+print(join(tt(PowerSystems.IS.InfrastructureSystemsType), "")) #hide
+```
- 1. Get all components associated with a single supplemental attribute.
+The concrete supplemental attributes are the last ones listed in a section. For example, following the first few lines of the type tree: InfrastructureSystems.InfrastructureSystemsType > InfrastructureSystems.AbstractTimeSeriesParameters > InfrastructureSystems.ForecastParameters . InfrastructureSystems.ForecastParameters is the concrete supplemental attribute, and the abstract supplemental attribute is InfrastructureSystems.AbstractTimeSeriesParameters. Providing another example with: InfrastructureSystems.InfrastructureSystemsType > InfrastructureSystems.DeviceParameter > DynamicComponent > PowerSystems.DynamicGeneratorComponent > AVR > AVRFixed . AVRFixed is the concrete supplemental attributes, and the abstract supplemental attributes are the higher up layers.
- ```julia
- outage = first(get_supplemental_attributes(FixedForcedOutage, system))
- for component in get_associated_components(system, outage)
- @show summary(component)
- end
- ```
+## Time Series Support
- 2. Same as #1, but filter the results by component type, which can be concrete or abstract.
+Attributes can include time series data like weather patterns and planned outages.
- ```julia
- outage = first(get_supplemental_attributes(FixedForcedOutage, system))
- for component in get_associated_components(system, outage; component_type = ThermalStandard)
- @show summary(component)
- end
- ```
+## Benefits for Modelers
-## Getting the components associated with an attribute type
+This design changes how you build power system models:
-You can retrieve the components associated with any supplemental attribute of a given type
-using the function [`get_associated_components`](@ref).
+**Build in layers**: Start with electrical models, then add contextual information separately.
- 1. Get all components associated with any supplemental attribute of a given type.
+**Reuse data**: Geographic info and weather patterns can be applied to multiple systems.
- ```julia
- for component in get_associated_components(system, FixedForcedOutage)
- @show summary(component)
- end
- ```
+**Work in teams**: Different people can work on electrical models and contextual data independently.
- 2. Same as #1, but filter the results by component type, which can be concrete or abstract.
+**Easy updates**: Change outage schedules or weather data without touching electrical models.
- ```julia
- for component in
- get_associated_components(system, FixedForcedOutage; component_type = ThermalStandard)
- @show summary(component)
- end
- ```
+## Compared to Other Approaches
-## Getting component / supplemental attribute pairs
+Other power system tools handle this differently:
-The function [`get_component_supplemental_attribute_pairs`](@ref) returns a vector of component / supplemental
-attribute pairs based on types and optional filters. This can be more efficient than double for loops
-that iterate over components and their associated attributes independently.
+**Heavy objects approach**: Some tools put all contextual data directly into component definitions. This makes objects large and unwieldy for big systems.
-```julia
-for (gen, outage) in get_component_supplemental_attribute_pairs(
- ThermalStandard,
- FixedForcedOutage,
- system,
-)
- @show summary(gen) summary(outage)
-end
-```
+**External database approach**: Others store relationships in separate databases. This can slow things down and complicate deployment.
-## Adding Time Series to an attribute
+**PowerSystems.jl's approach**: Combines the speed of in-memory data with the relationship modeling power typically found only in databases. This works well for interactive analysis.
## Existing Supplemental Attributes in PowerSystems
@@ -180,3 +100,8 @@ for detailed documentation.
- [`CombinedCycleFractional`](@ref) - Combined cycle plants with aggregate heat rate and exclusion groups
- [`HydroPowerPlant`](@ref) - Hydro plants with shared penstocks
- [`RenewablePowerPlant`](@ref) - Renewable plants with shared PCCs
+
+## Learn More
+
+ - [Add Supplemental Attributes to a System](@ref add_supplemental_attributes) -- step-by-step guide for attaching attributes to components
+ - [Supplemental Attributes](@ref) API reference -- complete listing of all supplemental attribute types, their fields, and associated functions
diff --git a/docs/src/explanation/system.md b/docs/src/explanation/system.md
index 4e874a6b34..5d5afe9956 100644
--- a/docs/src/explanation/system.md
+++ b/docs/src/explanation/system.md
@@ -1,96 +1,63 @@
# [System](@id system_doc)
-The `System` is the main container of components and the time series data references.
-`PowerSystems.jl` uses a hybrid approach to data storage, where the component data and time
-series references are stored in volatile memory while the actual time series data is stored
-in an HDF5 file. This design loads into memory the portions of the data that are relevant
-at time of the query, and so avoids overwhelming the memory resources.
+## What is a `System`?
+
+The [`System`](@ref) is the central data container in `PowerSystems.jl`. It holds all
+[`Component`](@ref)s — the objects that describe the physical and logical elements of a
+power network — together with references to any associated time series data. For the most
+basic walkthrough of creating a system from scratch, see the
+[Create and Explore a Power System](@ref "Create and Explore a Power `System`") tutorial.
+
+`PowerSystems.jl` uses a hybrid approach to data storage: component data and time series
+references are stored in volatile memory, while the actual time series data is stored in
+an HDF5 file. This design loads only the portions of the data that are relevant at the
+time of the query, avoiding unnecessary memory overhead for large datasets.
```@raw html
-
+
```
+## What is a `Component`?
+
+A [`Component`](@ref) is any element of a power system model — generators, loads, buses,
+transmission lines, services, and more. Every component in `PowerSystems.jl` belongs to an
+abstract type hierarchy that organizes components by their role in the system (see
+[Type Structure](@ref type_structure) for details).
+
+A key constraint of the data model is that **a component instance can belong to at most
+one [`System`](@ref) at a time**. Adding a component to a second [`System`](@ref) without first removing
+it from the first will raise an error. This ensures that ownership of component data is
+unambiguous and prevents silent aliasing between systems.
+
## Accessing components stored in the `System`
`PowerSystems.jl` implements a wide variety of methods to search for components to
aid in data manipulation. Most of these use the [Type Structure](@ref type_structure) to
retrieve all components of a certain `Type`.
-For example, the most common search function is [`get_components`](@ref), which
-takes a desired device `Type` (concrete or abstract) and retrieves all components in that
-category from the `System`. It also accepts filter functions for a more
-refined search.
+The most common retrieval function is [`get_components`](@ref), which accepts a concrete or
+abstract component `Type` and returns all matching components from the [`System`](@ref). It also
+accepts filter functions for more refined searches.
-Given the potential size of the return,
-`PowerSystems.jl` returns Julia iterators in order to avoid unnecessary memory allocations.
-The container is optimized for iteration over abstract or concrete component
-types as described by the [Type Structure](@ref type_structure).
+Because a system can contain a large number of components, `PowerSystems.jl` returns
+Julia iterators rather than materialized collections, avoiding unnecessary memory
+allocations. The container is internally optimized for iteration over both abstract and
+concrete component types.
## [Accessing data stored in a component](@id dot_access)
-__Using the "dot" access to get a parameter value from a component is actively discouraged, use "getter" functions instead__
+__Using the "dot" access to get a parameter value from a component is actively discouraged, use getter functions instead__
-Using code autogeneration, `PowerSystems.jl` implements accessor (or "getter") functions to
+Using code autogeneration, `PowerSystems.jl` implements getter functions to
enable the retrieval of parameters defined in the component struct fields. Julia syntax enables
access to this data using the "dot" access (e.g. `component.field`), however
_this is actively discouraged_ for two reasons:
- 1. We make no guarantees on the stability of component structure definitions. We will maintain version stability on the accessor methods.
- 2. Per-unit conversions are made in the return of data from the accessor functions. (see the [per-unit section](@ref per_unit) for more details)
-
-## [Using subsystems](@id subsystems)
-
-For certain applications, such as those that employ dispatch coordination methods or decomposition approaches, it is useful to be able to split components into subsystems based upon user-defined criteria. The `System` provides `subsystem` containers for this purpose. Each subsystem is defined by a name and can hold references to any number of components.
+ 1. We make no guarantees on the stability of component structure definitions. We will maintain version stability on the getter methods.
+ 2. Per-unit conversions are made in the return of data from the getter functions. (see the [per-unit section](@ref per_unit) for more details)
-The following commands demonstrate how to create subsystems and add components.
-
-```@repl subsystem
-using PowerSystems;
-using PowerSystemCaseBuilder;
-sys = build_system(PSISystems, "c_sys5_pjm")
-add_subsystem!(sys, "1")
-add_subsystem!(sys, "2")
-```
-
-Devices in the system can be assigned to the subsystems in the following way using the function [`add_component_to_subsystem!`](@ref)
-
-```@repl subsystem
-g = get_component(ThermalStandard, sys, "Alta")
-add_component_to_subsystem!(sys, "1", g)
-
-g = get_component(ThermalStandard, sys, "Sundance")
-add_component_to_subsystem!(sys, "2", g)
-```
-
-To retrieve components assigned to a specific subsystem, add the `subsystem_name` keyword argument to `get_components`.
-
-```@repl subsystem
-gens_1 = get_components(ThermalStandard, sys; subsystem_name = "1")
-get_name.(gens_1)
-
-gens_2 = get_components(ThermalStandard, sys; subsystem_name = "2")
-get_name.(gens_2)
-```
-
-One useful feature that requires care when used is generating a new [`System`](@ref) from a `subsystem` assignment.
-
-The function [`from_subsystem`](@ref) will allow the user to produce a new [`System`](@ref) that can be used or exported.
-This functionality requires careful subsystem assignemnt of the devices and its dependencies. Following from the example in this document, you can export a system as follows:
-
-```@repl subsystem
-from_subsystem(sys, "1")
-```
-
-!!! warning
-
- The system is invalid because the bus connected to the Alta generator is not part of the subsystem. We can add it, and then run [`from_subsystem`](@ref) again
-
-```@repl subsystem
-g = get_component(ThermalStandard, sys, "Alta")
-b = get_bus(g)
-add_component_to_subsystem!(sys, "1", b)
-from_subsystem(sys, "1")
-```
+## Subsystems
-Advanced users can use the keyword `runchecks=false` and avoid any topological check in the process.
-It is highly recommended that users only do this if they clearly understand how to validate the resulting system before using it for modeling.
+The [`System`](@ref) also supports partitioning components into named *subsystems*, which is
+useful for decomposition approaches or dispatch coordination workflows. For a hands-on walkthrough of working with a [`System`](@ref), its components, and the accessor functions, see the
+[Manipulating Datasets](@ref "Manipulating Datasets") tutorial. For a step-by-step guide, see [Use subsystems](@ref use_subsystems).
diff --git a/docs/src/explanation/time_series.md b/docs/src/explanation/time_series.md
index ad393bdd3f..2a8e0f0f97 100644
--- a/docs/src/explanation/time_series.md
+++ b/docs/src/explanation/time_series.md
@@ -12,10 +12,10 @@ process to obtain the data and its interpretation:
- [Static Time Series Data](@ref)
- [Forecasts](@ref)
-These categories are are all subtypes of `TimeSeriesData` and fall within this time series
-type hierarchy:
+These categories are are all subtypes of [`TimeSeriesData`](@ref) and fall within this time series
+[type hierarchy](@ref type_structure):
-```@repl
+```@example
using PowerSystems #hide
import TypeTree: tt #hide
docs_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "utils"); #hide
@@ -26,7 +26,7 @@ print(join(tt(TimeSeriesData), "")) #hide
### Static Time Series Data
A static time series data is a single column of data where each time period has a single
-value assigned to a component field, such as its maximum active power. This data commonly
+value assigned to a component field, such as its [maximum active power](@ref power_concepts). This data commonly
is obtained from historical information or the realization of a time-varying quantity.
Static time series usually comes in the following format, with a set [resolution](@ref R)
@@ -40,7 +40,7 @@ between the time-stamps:
This example is a 1-hour resolution static time-series.
-In PowerSystems, a static time series is represented using [`SingleTimeSeries`](@ref).
+In `PowerSystems.jl`, a static time series is represented using [`SingleTimeSeries`](@ref).
### Forecasts
@@ -65,7 +65,7 @@ represent the forecasted values at each step in the forecast [horizon](@ref H).
This example forecast has a [interval](@ref I) of 1 hour and a [horizon](@ref H) of 8.
-PowerSystems defines the following Julia structs to represent forecasts:
+`PowerSystems.jl` defines the following Julia [structs](@ref S) to represent forecasts:
- [`Deterministic`](@ref): Point forecast without any uncertainty representation.
- [`Probabilistic`](@ref): Stores a discretized cumulative distribution functions
diff --git a/docs/src/explanation/transformer_per_unit_models.md b/docs/src/explanation/transformer_per_unit_models.md
index 9178664dcd..c821469f0d 100644
--- a/docs/src/explanation/transformer_per_unit_models.md
+++ b/docs/src/explanation/transformer_per_unit_models.md
@@ -2,42 +2,9 @@
The per-unit (p.u.) system is a fundamental tool in power system analysis, especially when dealing with transformers. It simplifies calculations by normalizing all quantities (voltage, current, power, impedance) to a common base. This effectively "retains" the ideal transformer from the circuit diagram because the per-unit impedance of a transformer remains the same when referred from one side to the other. This page is not a comprehensive guide on transformer per-unit calculations, a more in depth explanation can be found in [`this link`](https://en.wikipedia.org/wiki/Per-unit_system) or basic power system literature.
-## Establishing Base Values
-
-For a multi-voltage system with transformers, you need to establish consistent base values across different voltage zones.
-
- - Transformer Voltage Base ($V_{base, LL}$):
-
- + The voltage base is determined by the transformer's nominal line-to-line voltage ratio:
- $$V_{base, \text{secondary}} = V_{base, \text{primary}} \times \frac{V_{\text{rated, secondary}}}{V_{\text{rated, primary}}}$$
- Where $V_{\text{rated, secondary}}$ and $V_{\text{rated, primary}}$ are the transformer's nominal (rated) line-to-line voltages on its secondary and primary sides, respectively.
- + This value can be slightly different that the attached bus voltage value. In certain low voltage systems, transformers with a higher base voltage can be connected to buses with lower voltage set points. As of PowerSystems v5 transformers now have field for the base voltage.
-
- - How is the data stored?: Transformer impedance (usually reactive impedance, $X_{pu}$) is typically given on its own nameplate ratings (rated MVA and rated voltages). **The data in PowerSystems.jl is stored in the device base** and transformer to the system base when using the correct getter functions.
-
- - **Derived Base Impedance ($Z_{base}$):**
-
- + Once $S_{base, 3\phi}$ and $V_{base, LL}$ are established for each voltage zone, the base impedance can be calculated as follows:
-
- $$Z_{base} = \frac{(V_{base, LL})^2}{S_{base, 3\phi}}$$
- Where $V_{base, LL}$ is in kV and $S_{base, 3\phi}$ is in MVA, resulting in $Z_{base}$ in Ohms ($\Omega$).
-
-### Transformer Impedance Transformations
-
-The most significant advantage of the per-unit system for transformers is that **the per-unit impedance of a transformer is the same on both sides**, provided the base voltages are chosen according to the transformer's turns ratio and the base power is consistent.
-
- - Changing Base for Transformer Impedance: If the system-wide base $S_{base, 3\phi}$ and the zone-specific voltage bases ($V_{base, \text{primary zone}}$ and $V_{base, \text{secondary zone}}$) differ from the transformer's ratings, you need to convert the transformer's per-unit impedance to the new system base.
-
- The formula for changing base of an impedance is:
-
- $$Z_{pu, \text{new}} = Z_{pu, \text{old}} \times \left(\frac{S_{base, \text{new}}}{S_{rated, \text{old}}}\right) \times \left(\frac{V_{rated, \text{old}}}{V_{base, \text{new}}}\right)^2$$
-
- + Here, $S_{base, \text{new}}$ is your chosen system-wide base MVA.
- + $S_{rated, \text{old}}$ is the transformer's rated MVA (from nameplate).
- + $V_{rated, \text{old}}$ is the transformer's rated voltage on the side you are considering (e.g., if you're transforming the impedance to the primary side's base, use the primary rated voltage).
- + $V_{base, \text{new}}$ is the *new* system base voltage for that side of the transformer.
-
- When calculating the transformer's impedance on the system base, you only need to perform this calculation once. Since the per-unit impedance of a transformer is the same when referred from one side to the other (given correct base voltage selection), the $Z_{pu, \text{new}}$ calculated for the transformer will be used regardless of which side you are viewing it from in the per-unit circuit diagram.
+For a step-by-step guide to establishing base values and converting transformer impedances
+to a new per-unit base, see
+[Convert Transformer Impedances Between Per-Unit Bases](@ref convert_transformer_impedance).
!!! note
diff --git a/docs/src/explanation/type_structure.md b/docs/src/explanation/type_structure.md
index 9112082abf..3c9d637692 100644
--- a/docs/src/explanation/type_structure.md
+++ b/docs/src/explanation/type_structure.md
@@ -1,14 +1,37 @@
# [Type Structure](@id type_structure)
-PowerSystems.jl provides a type hierarchy to contain power system data.
-
-## Types in PowerSystems
-
-In PowerSystems.jl, data that describes infrastructure components is held in `struct`s.
-For example, an `ACBus` is a `struct` with the following parameters to describe a bus
-on an AC network:
-
-```@repl types
+`PowerSystems.jl` organizes power system data through a type hierarchy built around the
+**behavior and role** of each component in the network. Understanding this hierarchy
+explains how to retrieve components effectively and how to write code that works across
+many different component types without modification.
+
+## Why a type hierarchy?
+
+Power systems contain a wide variety of physical equipment — generators, loads, buses,
+transmission lines, transformers, storage devices, and more — each with different data
+requirements and modeling roles. Rather than treating all components as untyped records,
+`PowerSystems.jl` places them into an abstract type hierarchy. This design provides two
+key benefits:
+
+ 1. **Categorization by behavior:** Components that serve the same modeling role share a
+ common abstract supertype. Code can retrieve all components of a given category — all
+ generators, all transmission branches — without enumerating every specific technology
+ type.
+
+ 2. **Generic and extensible model logic:** Downstream packages such as
+ [`PowerSimulations.jl`](https://nrel-sienna.github.io/PowerSimulations.jl/stable/)
+ define optimization formulations against abstract types. A new concrete component type
+ slots into existing model formulations automatically, as long as it implements the
+ expected interface. This means users can define technologies not yet in the package and
+ have them work with existing tools.
+
+## How components are stored
+
+Each infrastructure component is represented as a [`struct`](@ref S) — a composite data
+type that bundles together the fields needed to describe that component. For example, an
+[`ACBus`](@ref) carries fields for its bus number, nominal voltage, bus type, and more:
+
+```@example types
using PowerSystems #hide
import TypeTree: tt #hide
docs_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "utils"); #hide
@@ -16,50 +39,83 @@ include(joinpath(docs_dir, "docs_utils.jl")); #hide
print_struct(ACBus) #hide
```
-## Type Hierarchy
+Getter and setter functions are generated for every field (e.g., `get_name`,
+`get_base_voltage`). Using these functions rather than direct field access is important:
+they apply [per-unit conversions](@ref per_unit) automatically and provide a stable
+interface across package versions. See the [System](@ref system_doc) explanation for more
+details on why direct field access is discouraged.
+
+## The abstract type hierarchy
+
+The hierarchy is rooted at `InfrastructureSystemsType`. The subtypes most relevant to
+`PowerSystems.jl` users are:
-PowerSystems is intended to organize data by the behavior of the devices that
-the data represents. A type hierarchy has been defined with several levels of
-abstract types starting with `InfrastructureSystemsType`. There are a bunch of subtypes of
-`InfrastructureSystemsType`, but the important ones to know about are:
+ - [`System`](@ref): the top-level data container that holds all [`Component`](@ref)s
+ and their associated time series data. See the [System](@ref system_doc) explanation
+ for a full description.
- - `System`: overarching `struct` that collects all of the `Component`s
+ - [`Component`](@ref): the abstract supertype for all power system elements:
- - `Component`: includes all elements of power system data
+ + [`Topology`](@ref): non-physical elements that describe network connectivity,
+ including [`ACBus`](@ref), [`Arc`](@ref), [`Area`](@ref), and
+ [`LoadZone`](@ref). Topology is kept separate from physical devices so that
+ network structure can be defined and manipulated independently of the equipment
+ attached to it.
- + `Topology`: includes non physical elements describing network connectivity
- + `Service`: includes descriptions of system requirements (other than energy balance)
- + `Device`: includes descriptions of all the physical devices in a power system
+ + [`Device`](@ref): physical equipment installed in the network, including
+ generators, loads, storage, and transmission branches.
- - `InfrastructureSystems.DeviceParameter`: includes structs that hold data describing the
- dynamic, or economic capabilities of `Device`.
+ + [`Service`](@ref): system-level requirements beyond energy balance, such as
+ operating reserves, [`AGC`](@ref), and transmission interface limits. Separating
+ services from devices reflects the fact that a service is a requirement that
+ *devices contribute to*, rather than a physical component itself.
- - `TimeSeriesData`: Includes all time series types
+ - `DeviceParameter`: [`struct`](@ref S)s that carry data describing
+ the dynamic or economic characteristics of a `Device`, such as cost function curves or
+ dynamic machine parameters. Decoupling these from the device [`struct`](@ref S) itself allows the
+ same physical device to carry different parameter sets depending on the modeling
+ context.
- + `Forecast`: includes structs to define time series of forecasted data where multiple
- values can represent each time stamp
- + `StaticTimeSeries`: includes structs to define time series with a single value for each
- time stamp
+ - [`TimeSeriesData`](@ref): the abstract supertype for all time-varying data associated
+ with components:
-The abstract hierarchy enables categorization of the devices by their operational
-characteristics and modeling requirements.
+ + [`Forecast`](@ref): time series where multiple values can represent each time
+ stamp, for look-ahead or scenario-based modeling.
+ + [`StaticTimeSeries`](@ref): time series with a single value per time stamp, for
+ historical or deterministic data.
-For instance, generation is classified by the distinctive
-data requirements for modeling in three categories: [`ThermalGen`](@ref), [`RenewableGen`](@ref),
-and [`HydroGen`](@ref).
+## How the generation hierarchy illustrates the design
-`PowerSystems.jl` has a category [`Topology`](@ref) of topological components
-(e.g., [`ACBus`](@ref), [`Arc`](@ref)), separate from the physical components.
+Generation is a useful example of how the hierarchy reflects real modeling distinctions.
+Generators are grouped into three abstract super types based on their dispatch behavior and
+data requirements:
-The hierarchy also includes components absent in standard data models, such as services.
-The services category includes reserves, transfers and [`AGC`](@ref). The power of `PowerSystems.jl`
-lies in providing the abstraction without an implicit mathematical representation of the component.
+ - [`ThermalGen`](@ref): dispatchable units with fuel-based costs and startup/shutdown
+ characteristics.
+ - [`RenewableGen`](@ref): generation driven by a variable resource, with limited or no
+ direct dispatch control.
+ - [`HydroGen`](@ref): hydro units, which share properties of both dispatchable and
+ resource-constrained generation but have unique reservoir and hydrology constraints.
-As a result of this design, developers can define model logic entirely based on abstract
-types and create generic code to support modeling technologies that are not yet
-implemented in the package.
+An optimization formulation written against [`ThermalGen`](@ref) applies to `ThermalStandard`,
+`ThermalMultiStart`, and any user-defined thermal subtype without modification. This is
+the intended extension mechanism: new technologies are introduced by defining a concrete
+type under the appropriate abstract supertype.
-```@raw html
-
-``` ⠀
+## What this means for developers
+
+The `PowerSystems.jl` type hierarchy deliberately provides **abstractions without
+encoding a specific mathematical model** for any component. The [`struct`](@ref S) for a
+`ThermalStandard` generator holds the data describing that unit; it does not prescribe
+how the unit should be represented in a particular simulation. The mathematical
+formulation is entirely the responsibility of the downstream tool.
+
+This separation allows the same data model to simultaneously support power flow analysis,
+production cost modeling, and transient stability simulation through different downstream
+packages, without any modification to the underlying data.
+
+For a hands-on introduction to navigating the type hierarchy, see the
+[Create and Explore a Power System](@ref "Create and Explore a Power `System`") tutorial.
+
+```
```
diff --git a/docs/src/generate_input_config_table.jl b/docs/src/generate_input_config_table.jl
index dc89055770..f3f7753662 100644
--- a/docs/src/generate_input_config_table.jl
+++ b/docs/src/generate_input_config_table.jl
@@ -45,7 +45,7 @@ function create_md()
for item in items
extra_cols = setdiff(keys(item), columns)
if !isempty(extra_cols)
- # make sure that there arent unexpected entries
+ # make sure that there aren't unexpected entries
throw(@error "config file fields not included in header" extra_cols)
end
row = []
diff --git a/docs/src/how_to/add_component_natural_units.md b/docs/src/how_to/add_component_natural_units.md
index 8a54c33f3b..2f7350b1a5 100644
--- a/docs/src/how_to/add_component_natural_units.md
+++ b/docs/src/how_to/add_component_natural_units.md
@@ -1,4 +1,4 @@
-# Add a Component in Natural Units
+# [Add a Component in Natural Units](@id add_component_natural_units)
```@setup add_in_nu
using PowerSystems; #hide
@@ -19,9 +19,9 @@ for users who prefer to define data using `"NATURAL_UNITS"` (e.g., MW, MVA, MVAR
### Step 1: Set Units Base
-Set your (previously-defined) `System`'s units base to `"NATURAL_UNITS"`:
+Set your (previously-defined) [`System`](@ref)'s units base to `"NATURAL_UNITS"`:
-```@repl add_in_nu
+```@example add_in_nu
set_units_base_system!(system, "NATURAL_UNITS")
```
@@ -35,7 +35,7 @@ Define an empty component with `0.0` or `nothing` for all the power-related fiel
For example:
-```@repl add_in_nu
+```@example add_in_nu
gas1 = ThermalStandard(;
name = "gas1",
available = true,
@@ -58,9 +58,9 @@ gas1 = ThermalStandard(;
### Step 3: Attach the Component
-Attach the component to your `System`:
+Attach the component to your [`System`](@ref):
-```@repl add_in_nu
+```@example add_in_nu
add_component!(system, gas1)
```
@@ -69,7 +69,7 @@ add_component!(system, gas1)
Use individual "setter" functions to set each the value of each numeric field in natural
units:
-```@repl add_in_nu
+```@example add_in_nu
set_rating!(gas1, 30.0) #MVA
set_active_power_limits!(gas1, (min = 6.0, max = 30.0)) # MW
set_reactive_power_limits!(gas1, (min = 6.0, max = 30.0)) # MVAR
@@ -88,4 +88,4 @@ done the per-unit conversion into `"DEVICE_BASE"` behind the scenes.
- [Read more to understand per-unitization in PowerSystems.jl](@ref per_unit)
- Learn how to use the default constructors and explore the per-unitization settings in
- [Create and Explore a Power `System`](@ref)
+ [Create and Explore a Power `System`](@ref "Create and Explore a Power `System`")
diff --git a/docs/src/how_to/add_cost_curve.md b/docs/src/how_to/add_cost_curve.md
index 5d01d0d765..bc46928d18 100644
--- a/docs/src/how_to/add_cost_curve.md
+++ b/docs/src/how_to/add_cost_curve.md
@@ -20,8 +20,8 @@ To begin, the user must make 2 or 3 decisions before defining the operating cost
* If you have data in terms of heat rate or water flow, use [`FuelCurve`](@ref).
* If you have data in units of currency, such as \$/MWh, use [`CostCurve`](@ref).
- If you selected another `OperationalCost` type, the variable cost is represented
- as a `CostCurve`.
+ If you selected another [`OperationalCost`](@ref) type, the variable cost is represented
+ as a [`CostCurve`](@ref).
2. Select a [`ValueCurve`](@ref) to represent the variable cost data by comparing the format
of your variable cost data to the [Variable Cost Representations table](@ref curve_table)
@@ -44,10 +44,10 @@ Following the decision steps above:
1. We select [`RenewableGenerationCost`](@ref) to represent this renewable generator.
2. We select a [`LinearCurve`](@ref) to represent the \$22/MWh variable cost.
-Following the implementation steps, we define `RenewableGenerationCost` by nesting the
+Following the implementation steps, we define [`RenewableGenerationCost`](@ref "RenewableGenerationCost") by nesting the
definitions:
-```@repl costcurve
+```@example costcurve
RenewableGenerationCost(; variable = CostCurve(; value_curve = LinearCurve(22.0)))
```
@@ -67,19 +67,19 @@ Following the decision steps above:
This time, we'll define each step individually, beginning with the heat rate curve:
-```@repl costcurve
+```@example costcurve
heat_rate_curve = PiecewisePointCurve([(100.0, 1400.0), (200.0, 2200.0)])
```
Use the heat rate to define the fuel curve, including the cost of fuel:
-```@repl costcurve
+```@example costcurve
fuel_curve = FuelCurve(; value_curve = heat_rate_curve, fuel_cost = 20.0)
```
Finally, define the full operating cost:
-```@repl costcurve
+```@example costcurve
cost = ThermalGenerationCost(;
variable = fuel_curve,
fixed = 6.0,
@@ -101,14 +101,14 @@ heat rate of 8 GJ/MWh across the operating range (100 MW - 200 MW).
In this case, we can specify the heat rate curve with [`PiecewiseIncrementalCurve`](@ref) via the marginal
heat rate directly:
-```@repl costcurve
+```@example costcurve
heat_rate_curve = PiecewiseIncrementalCurve(1400.0, [100.0, 200.0], [8.0])
```
The [`FuelCurve`](@ref) and [`ThermalGenerationCost`](@ref) are specified in the same way despite the
differing representation of the value curve:
-```@repl costcurve
+```@example costcurve
fuel_curve = FuelCurve(; value_curve = heat_rate_curve, fuel_cost = 20.0)
cost = ThermalGenerationCost(;
variable = fuel_curve,
diff --git a/docs/src/how_to/add_fuel_curve_timeseries.md b/docs/src/how_to/add_fuel_curve_timeseries.md
index 003a9d5d81..08e9a68dc1 100644
--- a/docs/src/how_to/add_fuel_curve_timeseries.md
+++ b/docs/src/how_to/add_fuel_curve_timeseries.md
@@ -94,7 +94,7 @@ To add time-varying fuel costs, you need to:
- The generator must use a [`FuelCurve`](@ref) (not a [`CostCurve`](@ref)) in its
[`ThermalGenerationCost`](@ref) to enable time series fuel costs
- - Time series resolution should match your simulation resolution (e.g., Hour(1) for
+ - Time series [resolution](@ref R) should match your simulation resolution (e.g., Hour(1) for
hourly simulations)
- Fuel cost units should be in \$/GJ (or \$/MBtu, etc.) and heat rate in GJ/MWh (or
MBtu/MWh); their product gives the effective cost in \$/MWh
@@ -104,7 +104,7 @@ To add time-varying fuel costs, you need to:
Create time series data representing fuel costs that vary throughout the day. This example
uses hourly natural gas prices with [`SingleTimeSeries`](@ref):
-```@repl fuelcosts
+```@example fuelcosts
# Define the initial time and resolution
initial_time = DateTime("2024-01-01T00:00:00")
resolution = Hour(1)
@@ -137,7 +137,7 @@ fuel_cost_timeseries = SingleTimeSeries(;
Use the [`set_fuel_cost!`](@ref) function to attach the time series data to your
previously defined [`System`](@ref) and generator (e.g., [`ThermalStandard`](@ref)):
-```@repl fuelcosts
+```@example fuelcosts
# Add the time series fuel cost to the generator
set_fuel_cost!(sys, generator, fuel_cost_timeseries)
```
@@ -146,7 +146,7 @@ set_fuel_cost!(sys, generator, fuel_cost_timeseries)
Now the generator has time-varying fuel costs. You can retrieve the time series data:
-```@repl fuelcosts
+```@example fuelcosts
# Get the fuel cost time series starting at the initial time
fuel_forecast = get_fuel_cost(generator; start_time = initial_time)
@@ -156,7 +156,7 @@ first(TimeSeries.values(fuel_forecast), 6)
You can also query for a specific time window:
-```@repl fuelcosts
+```@example fuelcosts
# Get fuel costs for a specific 12-hour period starting at 6 AM
morning_time = DateTime("2024-01-01T06:00:00")
fuel_forecast_morning = get_fuel_cost(generator; start_time = morning_time, len = 12)
@@ -169,6 +169,6 @@ TimeSeries.values(fuel_forecast_morning)
- [Add an Operating Cost](@ref cost_how_to) - General guide for adding operational costs
- [Parse Time Series Data from .csv files](@ref parsing_time_series) - How to load time series from CSV files
- - [Working with Time Series Data](@ref tutorial_time_series) - Tutorial on time series data in PowerSystems
+ - [Working with Time Series Data](@ref "Working with Time Series Data") - Tutorial on time series data in PowerSystems
- [`ThermalGenerationCost`](@ref) - API reference for thermal generation costs
- [`FuelCurve`](@ref) - API reference for fuel curves
diff --git a/docs/src/how_to/add_new_types.md b/docs/src/how_to/add_new_types.md
index f4a97e54da..3d18eb5b11 100644
--- a/docs/src/how_to/add_new_types.md
+++ b/docs/src/how_to/add_new_types.md
@@ -1,12 +1,12 @@
-# Add a New or Custom Type
+# [Add a New or Custom Type](@id add_new_types)
This page describes how developers should add types to `PowerSystems.jl`
## Type Hierarchy
-All structs that correlate to power system components must be subtypes of the
-[`Component`](@ref) abstract type. Browse its type hierachy to choose an appropriate
-supertype for your new struct.
+All [structs](@ref S) that correlate to power system components must be subtypes of the
+[`Component`](@ref) abstract type. Browse its type hierarchy to choose an appropriate
+supertype for your new [struct](@ref S).
## Interfaces
@@ -17,7 +17,7 @@ for component requirements.
In particular, please note the methods `supports_time_series` (default = false) and
`supports_supplemental_attributes` (default = true) that you may need to implement.
-**Note**: `get_internal` and `get_name` are imported into `PowerSystems`, so you should
+**Note**: `get_internal` and `get_name` are imported into `PowerSystems.jl`, so you should
implement your methods as `PowerSystems` methods.
Some abstract types define required interface functions in docstring. Be sure
@@ -33,10 +33,10 @@ duplicate the entire existing type and methods. In programming languages that
support inheritance you would derive a new class from the existing class and
automatically inherit its fields and methods. Julia doesn't support that.
However, you can achieve a similar result with a forwarding macro.
-The basic idea is that you include the existing type within your struct and
+The basic idea is that you include the existing type within your [struct](@ref S) and
then use a macro to automatically forward specific methods to that instance.
-A few PowerSystems structs use the macro `InfrastructureSystems.@forward` to
+A few `PowerSystems.jl` structs use the macro `InfrastructureSystems.@forward` to
do this. Refer to the struct [`RoundRotorQuadratic`](@ref) for an example of how to
use this.
@@ -74,25 +74,25 @@ and `throw_if_not_attached(component, system)`.
### Custom Validation
You can implement three methods to perform custom validation or correction for your type.
-PowerSystems calls all of these functions in `add_component!`.
+`PowerSystems.jl` calls all of these functions in `add_component!`.
- - `sanitize_component!(component::Component, sys::System)`: intended to make standard data corrections (e.g. voltage angle in degrees -> radians)
- - `validate_component(component::Component)`: intended to check component field values for internal consistency
- - `validate_component_with_system(component::Component, sys::System)`: intended to check component field values for consistency with system
+ - [`sanitize_component!(component::Component, sys::System)`](@ref sanitize_component!): intended to make standard data corrections (e.g. voltage angle in degrees -> radians)
+ - [`validate_component(component::Component)`](@ref validate_component): intended to check component field values for internal consistency
+ - [`validate_component_with_system(component::Component, sys::System)`](@ref validate_component_with_system): intended to check component field values for consistency with system
### Struct Requirements for Serialization of custom components
One key feature of `PowerSystems.jl` is the serialization capabilities. Supporting
serialization and de-serialization of custom components requires the implementation of
-several methods. The serialization code converts structs to dictionaries where the struct
+several methods. The serialization code converts [structs](@ref S) to dictionaries where the struct
fields become dictionary keys.
The code imposes these requirements:
1. The InfrastructureSystems methods `serialize` and `deserialize` must be
implemented for the struct. InfrastructureSystems implements a method that
- covers all subtypes of `InfrastructureSystemsType`. All PowerSystems
- components should be subtypes of `PowerSystems.Component` which is a subtype
+ covers all subtypes of `InfrastructureSystemsType`. All `PowerSystems.jl`
+ components should be subtypes of [`PowerSystems.Component`](@ref Component) which is a subtype
`InfrastructureSystemsType`, so any new structs should be covered as well.
2. All struct fields must be able to be encoded in JSON format or be covered be
covered by `serialize` and `deserialize` methods. Basic types, such as
@@ -101,9 +101,9 @@ The code imposes these requirements:
3. Structs relying on the default `deserialize` method must have a kwarg-only
constructor. The deserialization code constructs objects by splatting the
dictionary key/value pairs into the constructor.
- 4. Structs that contain other PowerSystem components (like a generator contains
- a bus) must serialize those components as UUIDs instead of actual values.
- The deserialization code uses the UUIDs as a mechanism to restore a
+ 4. Structs that contain other `PowerSystems.jl`components (like a generator contains
+ a bus) must serialize those components as [UUIDs](@ref U) instead of actual values.
+ The deserialization code uses the [UUIDs](@ref U) as a mechanism to restore a
reference to the actual object rather a new object with identical values. It
also significantly reduces the size of the JSON file.
@@ -136,7 +136,7 @@ type of the struct and extract it in a customized `deserialze` method.
### Adding `PowerSystems.jl` as a dependency in a modeling package
-```julia
+```@example add_new_types
module MyModelingModule
import PowerSystems as PSY
diff --git a/docs/src/how_to/add_supplemental_attributes.md b/docs/src/how_to/add_supplemental_attributes.md
new file mode 100644
index 0000000000..94136d0870
--- /dev/null
+++ b/docs/src/how_to/add_supplemental_attributes.md
@@ -0,0 +1,64 @@
+# [Add Supplemental Attributes to a System](@id add_supplemental_attributes)
+
+This how-to shows how to attach supplemental attributes to components in a [`System`](@ref).
+It uses [`FixedForcedOutage`](@ref) as the example attribute type.
+
+## Prerequisites
+
+```@example add_supplemental_attributes
+using PowerSystems
+using PowerSystemCaseBuilder
+
+sys = build_system(PSISystems, "c_sys5_pjm")
+```
+
+## Add a single supplemental attribute
+
+Retrieve the target component, construct the attribute, then attach it with
+[`add_supplemental_attribute!`](@ref):
+
+```@example add_supplemental_attributes
+gen = first(get_components(ThermalStandard, sys))
+outage = FixedForcedOutage(; outage_status = 0.0) # 0.0 = available, 1.0 = outaged
+add_supplemental_attribute!(sys, gen, outage)
+```
+
+## Add supplemental attributes in bulk
+
+For adding many attributes at once, use [`begin_supplemental_attributes_update`](@ref)
+to batch the operations. This reduces index update overhead and automatically reverts
+all changes if an error occurs:
+
+```@example add_supplemental_attributes
+gens = collect(get_components(ThermalStandard, sys))
+gen1 = gens[1]
+gen2 = gens[2]
+outage1 = FixedForcedOutage(; outage_status = 0.0)
+outage2 = FixedForcedOutage(; outage_status = 1.0)
+
+begin_supplemental_attributes_update(sys) do
+ add_supplemental_attribute!(sys, gen1, outage1)
+ add_supplemental_attribute!(sys, gen2, outage2)
+end
+```
+
+## Share one attribute across multiple components
+
+Attach the same attribute instance to more than one component to model shared properties:
+
+```@example add_supplemental_attributes
+outage = FixedForcedOutage(; outage_status = 1.0)
+gens = collect(get_components(ThermalStandard, sys))
+gen1 = gens[1]
+gen2 = gens[2]
+
+begin_supplemental_attributes_update(sys) do
+ add_supplemental_attribute!(sys, gen1, outage)
+ add_supplemental_attribute!(sys, gen2, outage)
+end
+```
+
+## Next steps
+
+See [How to use supplemental attributes](@ref use_supplemental_attributes_how_to) to query
+and filter the attributes you have added.
diff --git a/docs/src/how_to/adding_additional_fields.md b/docs/src/how_to/adding_additional_fields.md
index 2d2aada3a4..359edbf537 100644
--- a/docs/src/how_to/adding_additional_fields.md
+++ b/docs/src/how_to/adding_additional_fields.md
@@ -15,7 +15,7 @@ system = build_system(PSISystems, "modified_RTS_GMLC_DA_sys"); #hide
__Step 1:__ Use `get_ext` to get the `ext` field of the desired components and assign your data:
-```@repl generated_adding_additional_fields
+```@example generated_adding_additional_fields
for g in get_components(ThermalStandard, system)
external_field = get_ext(g)
external_field["my_data"] = 1.0
@@ -27,14 +27,14 @@ generators in a previously defined [`System`](@ref).
__Step 2:__ Retrieve your data using `get_ext` again
-First, retrieve the first ThermalStandard generator:
+First, retrieve the first [`ThermalStandard`](@ref "ThermalStandard") generator:
-```@repl generated_adding_additional_fields
+```@example generated_adding_additional_fields
gen = collect(get_components(ThermalStandard, system))[1];
```
Then, retrieve `my_data` from the generator and verify it is 1.0, as assigned.
-```@repl generated_adding_additional_fields
+```@example generated_adding_additional_fields
retrieved_data = get_ext(gen)["my_data"]
```
diff --git a/docs/src/how_to/build_system_with_files.md b/docs/src/how_to/build_system_with_files.md
index 08c79da712..36ae947326 100644
--- a/docs/src/how_to/build_system_with_files.md
+++ b/docs/src/how_to/build_system_with_files.md
@@ -14,7 +14,7 @@ manually is:
your dataset. Use [`add_component!`](@ref) to add each component to the [`System`](@ref).
4. Similarly, add cost and time series data either within each `for` loop, or after the
components have been defined using [`begin_time_series_update`](@ref).
- 5. [Save your `System` to a JSON](@ref "Write, View, and Load Data with a JSON") once you are
+ 5. [Save your `System` to a JSON](@ref serialize_data) once you are
finished
The following example demonstrates this process for selected component
@@ -32,7 +32,7 @@ In this example, it is assumed that the CSV files are stored in a directory
called `MyData`. In each section below, ensure your data follows the row-column format
before beginning, and that your data are in the given units.
-These are the depedencies needed for this how-to:
+These are the dependencies needed for this how-to:
```julia
using PowerSystems
@@ -184,9 +184,9 @@ end
!!! warning
- When defining a branch that isn't attached to a `System` yet, you must define the
+ When defining a branch that isn't attached to a [`System`](@ref) yet, you must define the
thermal rating of the transmission line [per-unitized in "SYSTEM_BASE"](@ref per_unit)
- using the base power of the `System` you plan to connect it to -- defined above as
+ using the base power of the [`System`](@ref) you plan to connect it to -- defined above as
`system_base_power`.
## Adding Thermal Generators and their Costs
@@ -233,7 +233,7 @@ constructor and data stored in the `thermal_gens` data frame.
!!! warning
- When you define components that aren't attached to a `System` yet, the constructors
+ When you define components that aren't attached to a [`System`](@ref) yet, the constructors
assume define all fields related to power are
[per-unitized in "DEVICE_BASE"](@ref per_unit). Divide all fields with units such as MW,
MVA, MVAR, or MW/min using the `base_power` of the component (with the exception of
@@ -322,7 +322,7 @@ shut_down_cost = "Shut Down Cost (dollar)"
for row in eachrow(thermal_gens)
thermal = get_component(ThermalStandard, sys, row[gen_name])
heat_rate_curve =
- PieceWiseIncrementalCurve(row[heat_rate_base], row[load_point], row[heat_rate])
+ PiecewiseIncrementalCurve(row[heat_rate_base], row[load_point], row[heat_rate])
fuel_curve =
FuelCurve(;
value_curve = heat_rate_curve,
@@ -497,7 +497,7 @@ values in MW for each hydro generator:
| 1/1/23 1:00 | 0.325386 | 0.325409 | 0.314454 | ... |
| ... | ... | ... | ... | ... |
-Each time series for its respective hydro generator has an hourly resolution,
+Each time series for its respective hydro generator has an hourly [resolution](@ref R),
and is for the year 2023, plus one full day into 2024, for a total of 366 days,
and 8784 values per column.
@@ -604,13 +604,13 @@ region's name, and contain the time series values in MW for each region:
| 1/1/23 1:00 | 4994.53821 | 1726.22134 | 2273.63274 |
| ... | ... | ... | ... |
-Each time series for its respective load region has an hourly resolution, and
+Each time series for its respective load region has an hourly [resolution](@ref R), and
is for the year 2023, plus one full day into 2024, for a total of 366 days, and
8784 values per column. Ensure that your time series file is similarly
formatted.
Create a data frame from `Load_Time_Series.csv` and define variables for the
-aforementioned resolution and time stamps:
+aforementioned [resolution](@ref R) and time stamps:
```julia
load_time_series = CSV.read("MyData/Load_Time_Series.csv", DataFrame)
@@ -680,8 +680,8 @@ Additional resources to help you built your own custom [`System`](@ref):
`for` loop from a .csv, if you don't have PSS/e files available for
[automated parsing](@ref dyr_data)
- See more on how to [Parse Time Series Data from .csv's](@ref parsing_time_series)
- - See how to [Add a New or Custom Type](@ref)
- - See how to [Add a Component in Natural Units](@ref), which is an alternative to the
+ - See how to [Add a New or Custom Type](@ref add_new_types)
+ - See how to [Add a Component in Natural Units](@ref add_component_natural_units), which is an alternative to the
per-unitized `for` loops above, but requires more code
- - See how to [Write, View, and Load Data with a JSON](@ref) to efficiently save your
+ - See how to [Write, View, and Load Data with a JSON](@ref serialize_data) to efficiently save your
[`System`](@ref) once you've built it
diff --git a/docs/src/how_to/convert_transformer_impedance.md b/docs/src/how_to/convert_transformer_impedance.md
new file mode 100644
index 0000000000..c6634f8352
--- /dev/null
+++ b/docs/src/how_to/convert_transformer_impedance.md
@@ -0,0 +1,130 @@
+# [Convert Transformer Impedances Between Per-Unit Bases](@id convert_transformer_impedance)
+
+Transformer impedance data is typically given on the transformer's own nameplate ratings
+(its rated MVA and rated voltages). When building a power system model you usually need
+that impedance expressed on the system-wide base instead. This guide walks through the
+manual calculation and explains how `PowerSystems.jl` handles it automatically through
+getter functions.
+
+For the underlying theory, see
+[Transformer per unit transformations](@ref transformers_pu_per_unit) in the Per-unit Conventions explanation.
+
+## Step 1: Establish base values for each voltage zone
+
+Every voltage zone in the network needs a consistent pair of base values: a base power
+$S_{base, 3\phi}$ (system-wide, in MVA) and a base voltage $V_{base, LL}$ (zone-specific,
+in kV).
+
+The base voltage on the secondary side of a transformer follows from its turns ratio:
+
+$$V_{base,\,\text{secondary}} = V_{base,\,\text{primary}} \times \frac{V_{\text{rated,\,secondary}}}{V_{\text{rated,\,primary}}}$$
+
+where $V_{\text{rated,\,primary}}$ and $V_{\text{rated,\,secondary}}$ are the transformer's
+nameplate line-to-line voltages.
+
+Once both base quantities are known for a zone, the base impedance is:
+
+$$Z_{base} = \frac{(V_{base,\,LL})^2}{S_{base,\,3\phi}}$$
+
+with $V_{base,\,LL}$ in kV and $S_{base,\,3\phi}$ in MVA, giving $Z_{base}$ in Ohms.
+
+## Step 2: Convert the transformer impedance to the new base
+
+If the transformer's nameplate ratings differ from the system base values, convert the
+per-unit impedance using:
+
+$$Z_{pu,\,\text{new}} = Z_{pu,\,\text{old}} \times \frac{S_{base,\,\text{new}}}{S_{rated,\,\text{old}}} \times \left(\frac{V_{rated,\,\text{old}}}{V_{base,\,\text{new}}}\right)^2$$
+
+where:
+
+ - $S_{base,\,\text{new}}$ — your chosen system-wide base MVA.
+ - $S_{rated,\,\text{old}}$ — the transformer's nameplate rated MVA.
+ - $V_{rated,\,\text{old}}$ — the transformer's nameplate rated voltage on the side being
+ considered.
+ - $V_{base,\,\text{new}}$ — the system base voltage for that same side.
+
+This calculation only needs to be done once per transformer. Because the per-unit
+impedance of a transformer is identical when referred from either side (provided base
+voltages are chosen consistently with the turns ratio), the result applies to the
+transformer as a whole.
+
+**Example:** a transformer rated 50 MVA, 115/13.8 kV with $X_{pu} = 0.10$ on its own
+base, on a system with $S_{base} = 100\,\text{MVA}$ and $V_{base} = 115\,\text{kV}$ on
+the primary side:
+
+$$X_{pu,\,\text{new}} = 0.10 \times \frac{100}{50} \times \left(\frac{115}{115}\right)^2 = 0.20\,\text{p.u.}$$
+
+## Step 3: Read impedance values in PowerSystems.jl
+
+`PowerSystems.jl` stores transformer impedance on the **device base** internally.
+Getter functions such as [`get_x`](@ref) automatically apply the base conversion and
+return the value on whichever base the [`System`](@ref) is currently set to, following the
+conventions described in [Per-unit Conventions](@ref per_unit).
+
+The example below builds the same transformer used in Step 2 (50 MVA, 115/13.8 kV,
+$X_{pu} = 0.10$ on its own nameplate base):
+
+```@example transformer_pu
+using PowerSystems
+
+# Two buses — primary at 115 kV, secondary at 13.8 kV.
+bus_primary = ACBus(;
+ number = 1,
+ name = "primary",
+ available = true,
+ bustype = ACBusTypes.REF,
+ angle = 0.0,
+ magnitude = 1.0,
+ voltage_limits = (min = 0.9, max = 1.05),
+ base_voltage = 115.0, # kV
+)
+
+bus_secondary = ACBus(;
+ number = 2,
+ name = "secondary",
+ available = true,
+ bustype = ACBusTypes.PQ,
+ angle = 0.0,
+ magnitude = 1.0,
+ voltage_limits = (min = 0.9, max = 1.05),
+ base_voltage = 13.8, # kV
+)
+
+arc = Arc(; from = bus_primary, to = bus_secondary)
+
+xfmr = Transformer2W(;
+ name = "xfmr_50MVA",
+ available = true,
+ active_power_flow = 0.0,
+ reactive_power_flow = 0.0,
+ arc = arc,
+ r = 0.0,
+ x = 0.10, # p.u. on device base (nameplate: 50 MVA, 115/13.8 kV)
+ primary_shunt = 0.0 + 0.0im,
+ rating = 0.5, # MVA rating in SYSTEM_BASE p.u.: 50 MVA / 100 MVA system base
+ base_power = 50.0, # MVA — transformer nameplate rating
+)
+
+# System base power = 100 MVA (matches the Step 2 example).
+sys = System(100.0)
+add_component!(sys, bus_primary)
+add_component!(sys, bus_secondary)
+add_component!(sys, arc)
+add_component!(sys, xfmr)
+
+# get_x returns the reactance on the current unit base (SYSTEM_BASE by default).
+# Expected: 0.10 × (100/50) × (115/115)² = 0.20 p.u.
+x_system_base = get_x(xfmr)
+```
+
+To inspect the raw device-base value, switch the unit system first:
+
+```@example transformer_pu
+set_units_base_system!(sys, "DEVICE_BASE")
+x_device_base = get_x(xfmr) # returns 0.10 — the original nameplate value
+
+set_units_base_system!(sys, "SYSTEM_BASE") # restore
+```
+
+See [Read Component Values in Different Unit Systems](@ref convert_unit_systems) for a full description of
+the available unit system settings.
diff --git a/docs/src/how_to/convert_unit_systems.md b/docs/src/how_to/convert_unit_systems.md
new file mode 100644
index 0000000000..5c41327185
--- /dev/null
+++ b/docs/src/how_to/convert_unit_systems.md
@@ -0,0 +1,116 @@
+# [Read Component Values in Different Unit Systems](@id convert_unit_systems)
+
+`PowerSystems.jl` stores component parameters internally in per-unit on the device base,
+but getter functions automatically convert values to whatever unit system the [`System`](@ref) is
+currently set to. This page shows how to switch unit systems and interpret the results.
+
+For background on why `PowerSystems.jl` uses per-unit conventions and what each unit system
+means, see [Per-unit Conventions](@ref per_unit).
+
+## Step 1: Check or change the unit system
+
+Use [`get_units_base`](@ref) to check the current setting and
+[`set_units_base_system!`](@ref) to change it:
+
+```@example convert_unit_systems
+using PowerSystems
+
+# Build a minimal system with one bus and one generator.
+bus = ACBus(;
+ number = 1,
+ name = "bus1",
+ available = true,
+ bustype = ACBusTypes.REF,
+ angle = 0.0,
+ magnitude = 1.0,
+ voltage_limits = (min = 0.9, max = 1.05),
+ base_voltage = 230.0,
+)
+
+sys = System(100.0) # System base power = 100 MVA
+add_component!(sys, bus)
+
+get_units_base(sys) # Returns the current unit system, e.g. "SYSTEM_BASE"
+
+set_units_base_system!(sys, "NATURAL_UNITS")
+```
+
+The three supported options are:
+
+| Setting | Meaning |
+|:----------------- |:--------------------------------------------------- |
+| `"SYSTEM_BASE"` | Values divided by the system `base_power` (default) |
+| `"DEVICE_BASE"` | Values divided by the device's own `base_power` |
+| `"NATURAL_UNITS"` | Values in physical units (MW, MVA, etc.) |
+
+## Step 2: Read values with getter functions
+
+Once the unit system is set, all getter functions return values in the corresponding units:
+
+```@example convert_unit_systems
+# Add a 100 MVA thermal generator to the system created in Step 1.
+gen = ThermalStandard(;
+ name = "gen1",
+ available = true,
+ status = true,
+ bus = bus,
+ active_power = 0.0,
+ reactive_power = 0.0,
+ rating = 1.0, # 1.0 p.u. on device base = 100 MVA
+ active_power_limits = (min = 0.2, max = 1.0), # p.u. on device base
+ reactive_power_limits = nothing,
+ ramp_limits = nothing,
+ operation_cost = ThermalGenerationCost(nothing),
+ base_power = 100.0, # MVA — always stored in natural units
+ prime_mover_type = PrimeMovers.CC,
+ fuel = ThermalFuels.NATURAL_GAS,
+)
+add_component!(sys, gen)
+
+# In DEVICE_BASE
+set_units_base_system!(sys, "DEVICE_BASE")
+get_base_power(gen) # Returns: 100.0 MVA (always natural units)
+get_rating(gen) # Returns: 1.0 p.u. (on device base)
+get_max_active_power(gen) # Returns: 1.0 p.u. (on device base)
+
+# In NATURAL_UNITS
+set_units_base_system!(sys, "NATURAL_UNITS")
+get_base_power(gen) # Returns: 100.0 MVA (always natural units)
+get_rating(gen) # Returns: 100.0 MVA (converted from device p.u.)
+get_max_active_power(gen) # Returns: 100.0 MW (converted from device p.u.)
+
+# In SYSTEM_BASE
+set_units_base_system!(sys, "SYSTEM_BASE")
+get_base_power(gen) # Returns: 100.0 MVA (always natural units)
+get_rating(gen) # Returns: 1.0 p.u. (on system base, when system base = device base)
+get_max_active_power(gen) # Returns: 1.0 p.u. (on system base)
+```
+
+!!! note
+
+ `get_base_power` always returns a value in natural units (MVA) regardless of the unit
+ system setting. Only parameters such as `rating` and `max_active_power` are affected
+ by the unit system.
+
+## Using a context manager to avoid permanent changes
+
+If you only need values in a particular unit system temporarily, use
+[`with_units_base`](@ref) instead of `set_units_base_system!`. It restores the original
+unit system automatically after the block completes, even if an error occurs:
+
+```@example convert_unit_systems
+mw_value = with_units_base(sys, "NATURAL_UNITS") do
+ get_max_active_power(gen)
+end
+# Unit system is restored to its previous value here
+```
+
+See [Use Context Managers for Efficient Bulk Operations](@ref use_context_managers) for more examples.
+
+## See Also
+
+ - [Per-unit Conventions](@ref per_unit) — explanation of all three unit systems
+ - [Create and Explore a Power System](@ref "Create and Explore a Power `System`") — tutorial that
+ constructs components in device base and demonstrates unit system conversions in practice
+ - [Add a Component in Natural Units](@ref add_component_natural_units) — how to define component data in MW/MVA
+ - [`with_units_base`](@ref), [`get_units_base`](@ref), [`set_units_base_system!`](@ref)
diff --git a/docs/src/how_to/create_hydro_datasets.md b/docs/src/how_to/create_hydro_datasets.md
index 5886170385..4836e11bf5 100644
--- a/docs/src/how_to/create_hydro_datasets.md
+++ b/docs/src/how_to/create_hydro_datasets.md
@@ -22,7 +22,7 @@ For this model, attach an upstream [`HydroReservoir`](@ref) to any number of [`H
### Example: Single Turbine with Single Reservoir
-```julia
+```@example hydro_resv
using PowerSystems
import PowerSystems as PSY
@@ -89,7 +89,7 @@ set_downstream_turbine!(reservoir, turbine)
### Example: Multiple Turbines with Single Reservoir
-```julia
+```@example hydro_resv
sys = System(100.0)
set_units_base_system!(sys, "NATURAL_UNITS")
@@ -154,7 +154,7 @@ flowchart TB
### Example: Pumped Hydro with Head and Tail Reservoirs
-```julia
+```@example hydro_resv
# Create a HydroPumpTurbine
pump_turbine = HydroPumpTurbine(;
name = "PumpTurbine1",
@@ -223,74 +223,3 @@ set_upstream_turbine!(tail_reservoir, pump_turbine)
@assert length(get_connected_head_reservoirs(sys, pump_turbine)) == 1
@assert length(get_connected_tail_reservoirs(sys, pump_turbine)) == 1
```
-
-## Key Component Fields
-
-### HydroTurbine
-
-Key fields for [`HydroTurbine`](@ref):
-
- - `powerhouse_elevation::Float64`: Height in meters above sea level of the powerhouse
- - `efficiency::Float64`: Turbine efficiency [0, 1.0]
- - `turbine_type::HydroTurbineType`: Type of turbine (e.g., `HydroTurbineType.UNKNOWN`, `HydroTurbineType.FRANCIS`, `HydroTurbineType.PELTON`, `HydroTurbineType.KAPLAN`)
- - `conversion_factor::Float64`: Conversion factor from flow/volume to energy (m³ -> p.u-hr)
- - `outflow_limits::Union{Nothing, MinMax}`: Turbine outflow limits in m³/s
- - `travel_time::Union{Nothing, Float64}`: Downstream travel time in hours from reservoir to turbine
-
-### HydroReservoir
-
-Key fields for [`HydroReservoir`](@ref):
-
- - `storage_level_limits::MinMax`: Storage level limits (in m³, m, or MWh based on `level_data_type`)
- - `initial_level::Float64`: Initial level as fraction of `storage_level_limits.max`
- - `inflow::Float64`: Water refilling the reservoir (m³/h or MW)
- - `outflow::Float64`: Water naturally leaving the reservoir (m³/h or MW)
- - `spillage_limits::Union{Nothing, MinMax}`: Water spillage limits
- - `level_targets::Union{Nothing, Float64}`: Target level at simulation end as fraction of max
- - `intake_elevation::Float64`: Height of intake in meters above sea level
- - `head_to_volume_factor::ValueCurve`: Head to volume relationship
- - `upstream_turbines::Vector{HydroUnit}`: Turbines feeding into this reservoir (tail reservoir)
- - `downstream_turbines::Vector{HydroUnit}`: Turbines fed by this reservoir (head reservoir)
- - `upstream_reservoirs::Vector{Device}`: Reservoirs feeding spillage into this reservoir
- - `level_data_type::ReservoirDataType`: Data type (e.g., `ReservoirDataType.USABLE_VOLUME`, `ReservoirDataType.HEAD`, `ReservoirDataType.ENERGY`)
-
-### HydroPumpTurbine
-
-Key fields specific to [`HydroPumpTurbine`](@ref):
-
- - `active_power_limits::MinMax`: Power limits for turbine (generation) mode
- - `active_power_limits_pump::MinMax`: Power limits for pump mode
- - `status::PumpHydroStatus`: Operating status (`PumpHydroStatus.OFF`, `PumpHydroStatus.PUMP`, `PumpHydroStatus.GEN`)
- - `efficiency::TurbinePump`: Separate efficiencies for turbine and pump modes `(turbine = 0.9, pump = 0.85)`
- - `transition_time::TurbinePump`: Time to switch modes `(turbine = 0.25, pump = 0.25)`
- - `minimum_time::TurbinePump`: Minimum operating time in each mode `(turbine = 1.0, pump = 1.0)`
-
-## Helper Functions
-
-### Linking Turbines to Reservoirs
-
- - `set_downstream_turbine!(reservoir, turbine)`: Link a single turbine as downstream of reservoir
- - `set_downstream_turbines!(reservoir, turbines)`: Link multiple turbines as downstream
- - `set_upstream_turbine!(reservoir, turbine)`: Link a single turbine as upstream of reservoir
- - `set_upstream_turbines!(reservoir, turbines)`: Link multiple turbines as upstream
-
-### Checking Connections
-
- - `has_downstream_turbine(reservoir)`: Check if any downstream turbines are attached
- - `has_downstream_turbine(reservoir, turbine)`: Check if specific turbine is downstream
- - `has_upstream_turbine(reservoir)`: Check if any upstream turbines are attached
- - `has_upstream_turbine(reservoir, turbine)`: Check if specific turbine is upstream
-
-### Retrieving Connected Components
-
- - `get_downstream_turbines(reservoir)`: Get vector of downstream turbines
- - `get_upstream_turbines(reservoir)`: Get vector of upstream turbines
- - `get_connected_head_reservoirs(sys, turbine)`: Get reservoirs where turbine is downstream
- - `get_connected_tail_reservoirs(sys, turbine)`: Get reservoirs where turbine is upstream
- - `get_turbine_head_reservoirs_mapping(sys)`: Get mapping of all turbines to head reservoirs
- - `get_turbine_tail_reservoirs_mapping(sys)`: Get mapping of all turbines to tail reservoirs
-
-### Removing Connections
-
- - `remove_turbine!(reservoir, turbine)`: Remove a specific turbine connection
- - `clear_turbines!(reservoir)`: Remove all turbine connections from reservoir
diff --git a/docs/src/how_to/create_system_with_source_import_export_cost.md b/docs/src/how_to/create_system_with_source_import_export_cost.md
index f33369f512..776f1dc329 100644
--- a/docs/src/how_to/create_system_with_source_import_export_cost.md
+++ b/docs/src/how_to/create_system_with_source_import_export_cost.md
@@ -1,4 +1,4 @@
-# Add costs for imported/exported power
+# [Add costs for imported/exported power](@id import_export_cost)
This how-to guide explains how to add an [`ImportExportCost`](@ref) to a [`Source`](@ref)
component to model imports and exports with neighboring areas or external grids.
@@ -34,7 +34,7 @@ You can define import and export curves in several ways, depending on your data
For a simple constant price over a power range:
-```@repl source_ie_cost
+```@example source_ie_cost
# Import curve: buy power at $25/MWh up to 200 MW
import_curve = make_import_curve(; power = 200.0, price = 25.0)
@@ -46,7 +46,7 @@ export_curve = make_export_curve(; power = 200.0, price = 30.0)
For more complex pricing with multiple segments:
-```@repl source_ie_cost
+```@example source_ie_cost
# Import curve with increasing prices as more power is imported
import_curve = make_import_curve(;
power = [0.0, 100.0, 105.0, 120.0, 200.0],
@@ -70,7 +70,7 @@ export_curve = make_export_curve(;
Use the curves to create an [`ImportExportCost`](@ref):
-```@repl source_ie_cost
+```@example source_ie_cost
ie_cost = ImportExportCost(;
import_offer_curves = import_curve,
export_offer_curves = export_curve,
@@ -84,7 +84,7 @@ ie_cost = ImportExportCost(;
Define a [`Source`](@ref) component with the import/export cost, or alternatively use
[`set_operation_cost!`](@ref) to add the cost to an existing source:
-```@repl source_ie_cost
+```@example source_ie_cost
source = Source(;
name = "external_grid",
available = true,
@@ -111,13 +111,13 @@ source = Source(;
Add the source component to your system:
-```@repl source_ie_cost
+```@example source_ie_cost
add_component!(sys, source)
```
Verify the source was added correctly:
-```@repl source_ie_cost
+```@example source_ie_cost
get_component(Source, sys, "external_grid")
get_operation_cost(get_component(Source, sys, "external_grid"))
```
diff --git a/docs/src/how_to/handle_3W_transformers.md b/docs/src/how_to/handle_3W_transformers.md
index d6e08ddc8c..6c44c0c327 100644
--- a/docs/src/how_to/handle_3W_transformers.md
+++ b/docs/src/how_to/handle_3W_transformers.md
@@ -1,6 +1,6 @@
# [Handle 3-winding transformer data](@id 3wtdata)
-PowerSystems.jl stores the topological data for the [`Transformer3W`](@ref) as the common equivalent circuit in the star (or wye) configuration. In this representation, the series impedances of each winding are transformed into an equivalent star network with a common star bus.
+`PowerSystems.jl` stores the topological data for the [`Transformer3W`](@ref) as the common equivalent circuit in the star (or wye) configuration. In this representation, the series impedances of each winding are transformed into an equivalent star network with a common star bus.
## The "Starbus" Representation
@@ -63,7 +63,7 @@ For transformers with on-load tap changers (OLTCs) or phase shifters, additional
## Deriving the Equivalent Star Impedances from PSSe
-In `PowerSystems.jl`, we explictly represent and store the [`Transformer3W`](@ref) as an equivalent star (wye) circuit with a common neutral point (often referred to conceptually as a "starbus"), we calculate the equivalent series impedance for each winding ($Z_1, Z_2, Z_3$) from the PSS®E Positive Sequence Impedance data (e.g., R1-2, X1-2, etc.) using the following formulas:
+In `PowerSystems.jl`, we explicitly represent and store the [`Transformer3W`](@ref) as an equivalent star (wye) circuit with a common neutral point (often referred to conceptually as a "starbus"), we calculate the equivalent series impedance for each winding ($Z_1, Z_2, Z_3$) from the PSS®E Positive Sequence Impedance data (e.g., R1-2, X1-2, etc.) using the following formulas:
$$\begin{aligned}
Z_1 &= \frac{1}{2} (Z_{12} + Z_{13} - Z_{23}) \\
diff --git a/docs/src/how_to/improve_ts_performance.md b/docs/src/how_to/improve_ts_performance.md
index 40058cb9e0..8f20ab6ad4 100644
--- a/docs/src/how_to/improve_ts_performance.md
+++ b/docs/src/how_to/improve_ts_performance.md
@@ -1,4 +1,8 @@
-# Improve Performance with Time Series Data
+# [Improve Performance with Time Series Data](@id improve_ts_performance)
+
+```@setup improve_ts_performance
+using PowerSystems
+```
Use the steps here to improve performance with small or large data sets, but
particularly large data sets. These improvements can help handle adding
@@ -15,7 +19,7 @@ large datasets from overwhelming system memory. However, you can change its loca
If your dataset will fit in your computer's memory, then you can increase
performance by storing it in memory:
-```julia
+```@example improve_ts_performance
sys = System(100.0; time_series_in_memory = true)
```
@@ -24,8 +28,8 @@ sys = System(100.0; time_series_in_memory = true)
If the system's time series data will be larger than the amount of tmp space available, use
the `time_series_directory` parameter to change its location.
-```julia
-sys = System(100.0; time_series_directory = "bigger_directory")
+```@example improve_ts_performance
+sys = System(100.0; time_series_directory = mktempdir())
```
You can also override the location by setting the environment
@@ -35,7 +39,7 @@ HDF5 compression is not enabled by default, but you can enable
it with `enable_compression` to get significant storage savings at the cost of CPU time.
[`CompressionSettings`](@ref) can be used to customize the HDF5 compression.
-```julia
+```@example improve_ts_performance
sys = System(100.0; enable_compression = true)
sys = System(
100.0;
@@ -51,7 +55,7 @@ sys = System(
## Adding Timeseries To The System
In order to optimize the storage of time series data, time series can be shared
-across devices to avoid duplication. If the same forecast applies to multiple
+across devices to avoid duplication. If the same [forecast](@ref "Forecasts") applies to multiple
components then can call `add_time_series!`, passing the collection of
components that share the time series data.
Time series data can also be shared on a component level. Suppose a time series array applies to
@@ -102,7 +106,7 @@ In the case of production cost modeling or other analyses that access
forecast windows repeatedly, this can slow down processes significantly, especially if the
underlying storage uses spinning disks.
-PowerSystems provides an alternate interface -- the forecast cache -- that pre-fetches data
+`PowerSystems.jl` provides an alternate interface -- the forecast cache -- that pre-fetches data
into the system memory with large reads in order to mitigate this potential problem.
It is highly recommended that you use this interface for modeling implementations. This is
particularly relevant for models using large datasets.
diff --git a/docs/src/how_to/jump.md b/docs/src/how_to/jump.md
index bb09574c1e..6e8932e82f 100644
--- a/docs/src/how_to/jump.md
+++ b/docs/src/how_to/jump.md
@@ -8,11 +8,11 @@ This page shows a minimal example to develop a Economic Dispatch model. The code
2. Serialize the system data,
3. Pass the data and algorithm to the model.
-One of the main uses of `PowerSystems.jl` is not having re-run the data generation for every model execution. The model code shows an example of populating the constraints and cost functions using accessor functions inside the model function. The example concludes by reading the data created earlier and passing the algorithm with the data.
+One of the main uses of `PowerSystems.jl` is not having re-run the data generation for every model execution. The model code shows an example of populating the constraints and cost functions using getter functions inside the model function. The example concludes by reading the data created earlier and passing the algorithm with the data.
Start by loading required packages:
-```@repl using_jump
+```@example using_jump
using PowerSystems
using JuMP
using Ipopt
@@ -27,7 +27,7 @@ Normally you would pass your local files to create the system data instead of ca
We also use [`transform_single_time_series!`](@ref) to format time-series data as forecasts for
this problem:
-```@repl using_jump
+```@example using_jump
system_data = build_system(PSISystems, "c_sys5_pjm")
transform_single_time_series!(system_data, Hour(24), Hour(24))
```
@@ -35,7 +35,7 @@ transform_single_time_series!(system_data, Hour(24), Hour(24))
Next, we define the custom optimization problem using [`JuMP`](https://jump.dev/JuMP.jl/stable/)'s syntax.
The constraints include each generator's minimum and maximum active power output as well as the system power balance equation, minimizing the operating cost for each step in the 24-hour horizon:
-```@repl using_jump
+```@example using_jump
function ed_model(system::System, optimizer, load_scaling_factor::Float64 = 1.0)
ed_m = Model(optimizer)
time_periods = 1:24
@@ -82,6 +82,6 @@ end
Finally, the `PowerSystems.jl` data is combined with this economic dispatch model and solved with the open-source [`Ipopt`](https://github.com/jump-dev/Ipopt.jl) solver:
-```@repl using_jump
+```@example using_jump
results = ed_model(system_data, Ipopt.Optimizer)
```
diff --git a/docs/src/how_to/market_bid_cost.md b/docs/src/how_to/market_bid_cost.md
index 114f97399d..eef4b369eb 100644
--- a/docs/src/how_to/market_bid_cost.md
+++ b/docs/src/how_to/market_bid_cost.md
@@ -1,4 +1,4 @@
-# Add a Market Bid
+# [Add a Market Bid](@id market_bid_cost)
A [`MarketBidCost`](@ref) is an `OperationalCost` data structure that allows the user to run a production
cost model that is very similar to most US electricity market auctions with bids for energy
@@ -10,7 +10,7 @@ and ancillary services jointly. This page showcases how to create data for this
The `make_market_bid_curve` creates an incremental or decremental offer curve from a vector of `n` power values, a vector of `n-1` marginal costs and single initial input. For example, the following code creates an incremental offer curve:
-```@repl market_bid_cost
+```@example market_bid_cost
using PowerSystems, Dates
proposed_offer_curve =
make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0)
@@ -18,7 +18,7 @@ proposed_offer_curve =
Then a device with MarketBidCost can be directly instantiated using:
-```@repl market_bid_cost
+```@example market_bid_cost
using PowerSystems, Dates
bus = ACBus(1, "nodeE", true, "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing)
@@ -48,7 +48,7 @@ generator = ThermalStandard(;
Similarly, a decremental offer curve can also be created directly using the same helper method:
-```@repl market_bid_cost
+```@example market_bid_cost
using PowerSystems, Dates
decremental_offer =
make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [30.0, 28.0, 26.0, 25.0], 50.0)
@@ -65,7 +65,7 @@ only certain elements, at this point the actual energy cost bids don't need to b
The code below shows an example how we can create a thermal device with MarketBidCost.
-```@repl market_bid_cost
+```@example market_bid_cost
using PowerSystems, Dates
bus = ACBus(1, "nodeE", true, "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing)
@@ -94,14 +94,14 @@ generator = ThermalStandard(;
### Step 2: Creating the `TimeSeriesData` for the Market Bid
-The user is expected to pass the `TimeSeriesData` that holds the energy bid data which can be
-of any type (i.e. `SingleTimeSeries` or `Deterministic`) and data must be `PiecewiseStepData`.
+The user is expected to pass the [`TimeSeriesData`](@ref) that holds the energy bid data which can be
+of any type (i.e. [`SingleTimeSeries`](@ref) or [`Deterministic`](@ref D)) and data must be `PiecewiseStepData`.
This data type is created by specifying a vector of `n` powers, and `n-1` marginal costs.
The data must be specified in natural units, that is power in MW and marginal cost in $/MWh
or it will not be accepted when adding to the system.
Code below shows an example of how to build a Deterministic TimeSeries.
-```@repl market_bid_cost
+```@example market_bid_cost
initial_time = Dates.DateTime("2020-01-01")
psd1 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [2.901, 5.8272, 8.941])
psd2 = PiecewiseStepData([5.0, 7.33, 9.67, 12.0], [3.001, 6.0072, 9.001])
@@ -121,17 +121,17 @@ time_series_data = Deterministic(;
### Step 3a: Adding Energy Bid TimeSeriesData to the device
-To add energy market bids time-series to the `MarketBidCost`, use `set_variable_cost!`. The
-arguments for `set_variable_cost!` are:
+To add energy market bids time-series to the `MarketBidCost`, use [`set_variable_cost!`](@ref). The
+arguments for [`set_variable_cost!`](@ref) are:
- `sys::System`: PowerSystem System
- `component::StaticInjection`: Static injection device
- `time_series_data::TimeSeriesData`: TimeSeriesData
- `power_units::UnitSystem`: UnitSystem
-Currently, time series data only supports natural units for time series data, i.e. MW for power and $/MWh for marginal costs.
+Currently, time series data only supports [natural units](@ref per_unit) for time series data, i.e. MW for power and $/MWh for marginal costs.
-```@repl market_bid_cost
+```@example market_bid_cost
sys = System(100.0, [bus], [generator])
set_variable_cost!(sys, generator, time_series_data, UnitSystem.NATURAL_UNITS)
```
@@ -146,7 +146,7 @@ The creation of the TimeSeriesData is similar to Step 2, using `PiecewiseStepDat
Similar to adding energy market bids, for adding bids for ancillary services, use
`set_service_bid!`.
-```@repl market_bid_cost
+```@example market_bid_cost
service = VariableReserve{ReserveUp}("example_reserve", true, 0.6, 2.0)
add_service!(sys, service, get_component(ThermalStandard, sys, "Brighton"))
diff --git a/docs/src/how_to/migrating_to_psy5.md b/docs/src/how_to/migrating_to_psy5.md
index 9da266f386..39ce5af2d2 100644
--- a/docs/src/how_to/migrating_to_psy5.md
+++ b/docs/src/how_to/migrating_to_psy5.md
@@ -1,6 +1,6 @@
# [Migrating from version 4.0 to 5.0](@id psy5_migration)
-This guide outlines the code updates required to upgrade from PowerSystems.jl version 4.0
+This guide outlines the code updates required to upgrade from `PowerSystems.jl` version 4.0
to 5.0, which was released in July 2025 and includes breaking changes. Most the changes are related
to modeling in more detail AC transmission technologies.
@@ -18,7 +18,7 @@ Depth = 2
## AC Branches Type Hierarchy Change
-New abstract type [`ACTransmission`](@ref) and was created to better distinguish between AC transmission objects connected between [`ACBus`](@ref) the new added [`TwoTerminalHVDC`](@ref) abstract type to caputre HVDC links connected between [`ACBus`](@ref).
+New abstract type [`ACTransmission`](@ref) and was created to better distinguish between AC transmission objects connected between [`ACBus`](@ref) the new added [`TwoTerminalHVDC`](@ref) abstract type to capture HVDC links connected between [`ACBus`](@ref).
## Renamed Types and Parameters
@@ -58,7 +58,7 @@ Affected Types are:
- [`SynchronousCondenser`](@ref)
- [`InterruptibleStandardLoad`](@ref)
-These types are no longer part of PowerSystems.jl:
+These types are no longer part of `PowerSystems.jl`:
- `TwoTerminalVSDCLine`
- `HydroPumpedStorage` (see [Updates to Hydro Storage related devices](@ref Hyd_updates))
@@ -66,7 +66,7 @@ These types are no longer part of PowerSystems.jl:
## [Updates to hydro storage related devices](@id Hyd_updates)
-In previous versions of `PowerSystems.jl`, hydropower connected to reservoirs was modeled as a single plant connected to a single reservoir. Further, the model just kept track of the total energy in the reservoir. In this version of `PowerSystems.jl`, new structs [`HydroTurbine`](@ref) and [`HydroReservoir`](@ref) have been included to enable individual unit dispatch modeling as well as a shared reservoir.
+In previous versions of `PowerSystems.jl`, hydropower connected to reservoirs was modeled as a single plant connected to a single reservoir. Further, the model just kept track of the total energy in the reservoir. In this version of `PowerSystems.jl`, new [structs](@ref S) [`HydroTurbine`](@ref) and [`HydroReservoir`](@ref) have been included to enable individual unit dispatch modeling as well as a shared reservoir.
The new [`HydroReservoir`](@ref) is also used by the new [`HydroPumpTurbine`](@ref) to model the head and tail reservoirs for Hydro Pump Storage facilities. Check the section [Define Hydro Generators with Reservoirs](@ref hydro_resv)
@@ -77,9 +77,9 @@ valid and the expanded list can be explored in the documentation [`ThermalFuels`
## Updates to Transformers
-Most of the transformer changes are included to bring PowerSystems.jl closer to the data model employed in PSSe RAW files which tend to be the industry standard. The two notable changes are:
+Most of the transformer changes are included to bring `PowerSystems.jl` closer to the data model employed in PSSe RAW files which tend to be the industry standard. The two notable changes are:
- - All transformers now have additional fields for base quantities needed for the calculation of the impedances in adequate bases. See [`Transformer per unit transformations`](@ref transformers_pu) for more details.
+ - All transformers now have additional fields for base quantities needed for the calculation of the impedances in adequate bases. See [Transformer per unit transformations](@ref transformers_pu) for more details.
- The shunt branch in the transformer now uses a `Complex{Float64}` to model core losses as well as the core inductance.
- Shunt allocation in the transformer between the primary and secondary. We now allocate the shunt to the primary following PSSe's convention. See [`this issue`](https://github.com/Sienna-Platform/PowerSystems.jl/issues/1411) for a description of the discrepancy with Matpower. Note that this mostly affect the results reporting between Matpower and PSSe.
diff --git a/docs/src/how_to/parse_dynamic_data.md b/docs/src/how_to/parse_dynamic_data.md
index 99d8d1d7c3..9ecd407520 100644
--- a/docs/src/how_to/parse_dynamic_data.md
+++ b/docs/src/how_to/parse_dynamic_data.md
@@ -1,10 +1,10 @@
# [Parsing PSS/e dynamic data](@id dyr_data)
-A `PowerSystems.jl` system can be created using a .RAW and a .DYR file. For a complete list of supported models in PowerSystems.jl version 5.0, including machine models, AVR models, turbine governors, PSS models, inverter models, and additional models, see the [Supported PSS/e Models](@ref psse_models_ref) reference page.
+A `PowerSystems.jl` system can be created using a .RAW and a .DYR file. For a complete list of supported models in `PowerSystems.jl` version 5.0, including machine models, AVR models, turbine governors, PSS models, inverter models, and additional models, see the [Supported PSS/e Models](@ref psse_models_ref) reference page.
In this example we will create a three bus system from these example files:
-```@repl raw_dyr_system
+```@example raw_dyr_system
using PowerSystems
file_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "tutorials_data")
RAW_dir = joinpath(file_dir, "ThreeBusNetwork.raw")
@@ -64,9 +64,9 @@ The dynamic data for the generators is provided in the DYR file:
That assigns a GENROU generator and a ESST1A voltage regulator at the generator located at bus 101, while classic machine models for the generators located at bus 102 and 103.
-To create the `System` in `PowerSystems.jl`, we pass both files directories:
+To create the [`System`](@ref) in `PowerSystems.jl`, we pass both files directories:
-```@repl raw_dyr_system
+```@example raw_dyr_system
dyn_system = System(RAW_dir, DYR_dir; runchecks = false)
```
@@ -74,7 +74,7 @@ dyn_system = System(RAW_dir, DYR_dir; runchecks = false)
Please note that while PSS/e does not enforce unique bus names, `PowerSystems.jl` does. To reparse bus names to comply with this requirement the `bus_name_formatter` *kwarg can be used in `System()` as shown in the example below:
-```@repl raw_dyr_system
+```@example raw_dyr_system
dyn_system = System(
RAW_dir,
DYR_dir;
diff --git a/docs/src/how_to/parse_matpower_psse.md b/docs/src/how_to/parse_matpower_psse.md
index cbfa820d99..3f4c544035 100644
--- a/docs/src/how_to/parse_matpower_psse.md
+++ b/docs/src/how_to/parse_matpower_psse.md
@@ -1,8 +1,8 @@
# [Parsing MATPOWER or PSS/e Files](@id pm_data)
-The following code will create a System from a MATPOWER .m or PSS/e .raw file:
+The following code will create a [`System`](@ref) from a MATPOWER .m or PSS/e .raw file:
-```@repl m_system
+```@example m_system
using PowerSystems
file_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "tutorials_data")
sys = System(joinpath(file_dir, "case5.m"))
@@ -14,7 +14,7 @@ has had some divergence due to the need to adapt it to for large industrial case
The PSSe parser tries to handle correctly all the gotchas that result from the diverse modeling practices transmission engineers employ. However, it is impossible to anticipate all possible variations.
-PowerSystems.jl parsing code has been tested and developed using large cases from North America
+`PowerSystems.jl` parsing code has been tested and developed using large cases from North America
like the Western Electricity Coordinating Council (WECC) planning case and the Multiregional Modeling Working Group (MMWG) base case models. This parser has also been adapted to load cases in Latin America and the Caribbean and as many of the open data sets available only.
## [Conventions when parsing MATPOWER or PSS/e Files](@id parse_conventions)
@@ -23,27 +23,27 @@ like the Western Electricity Coordinating Council (WECC) planning case and the M
In PowerSystems v5, the parsing conventions changed from those in PowerSystems v4. You might experience different behaviors when loading MATPOWER or PSS/e Files.
-PowerSystems.jl utilizes a data model that bridges the gap between operational simulations, such as Production Cost Analysis, and electrical engineering simulations, including power flows. Given the different practices in these domains, there are several discrepancies in how to handle data and we have made changes to make the modeling compatible.
+`PowerSystems.jl` utilizes a data model that bridges the gap between operational simulations, such as Production Cost Analysis, and electrical engineering simulations, including power flows. Given the different practices in these domains, there are several discrepancies in how to handle data and we have made changes to make the modeling compatible.
In PowerSystems v5, we have implemented the following conventions for parsing PSSe files:
- **BusType correction**: If a bus has a value set to ISOLATED in PSSe, we will confirm that the bus is not entirely disconnected from the network. If the bus is disconnected, it will be set to ISOLATED and set the field available to false. However, if the bus is connected to a generator, we will infer a bus of type PV and set the field 'available' to false. This correction also applies to Matpower. For any other device connected to the bus, we will set it to PQ and set the 'available' field to false. Check [`Understanding ACBusTypes`](@ref bustypes) for a detailed explanation.
- - **Parsing Synchronous Condensers**: If a generator is connected to a PV Bus with activer power set to 0.0, it will be parsed as a [`SynchronousCondenser`](@ref). This prevents for generators to be modeled as dispatchable [`ThermalStandard`](@ref) when it doesn't apply.
+ - **Parsing Synchronous Condensers**: If a generator is connected to a PV Bus with active power set to 0.0, it will be parsed as a [`SynchronousCondenser`](@ref). This prevents for generators to be modeled as dispatchable [`ThermalStandard`](@ref) when it doesn't apply.
- **Reading and Storing Transformer Data**: The transformer data is always stored in the devices
- ' base. See [`Transformer per unit transformations`](@ref transformers_pu) for additional details.
+ ' base. See [Transformer per unit transformations](@ref transformers_pu) for additional details.
- **Transformer's Susceptance**: When reading from Matpower we split the transformer's susceptance evenly between the `from` and `to` ends to make it a closer approximation to the model in PSSe.
- **Tap transformer settings automated fix**: When the tap values in the RAW file are not withing the ranges defined in the same entry, PSSe performs a correction to the data. However, this correction isn't stored back in the file. The tap correction is done internally and is not exported from PSSE. Changes are not reflected in the exported file.PowerSystems.jl will correct the tap setting **and change the field in the transformer struct.**
- **Reading and Storing Multi-Section Line Data**: `PowerSystems.jl` does not have a explicit multi-section line object. These devices are parsed as individual lines and the "dummy buses" are added to the system. The additional data is stored in the [`Line`](@ref) `ext` field. Further network reductions are performed using [`PowerNetworkMatrices.jl`](https://sienna-platform.github.io/PowerNetworkMatrices.jl/stable/).
- **Reading [`GeographicInfo`](@ref) data from substations (PSSe v35 only)**: If the file contains a substation section. The coordinates will be automatically loaded as a [`GeographicInfo`](@ref) attribute and assigned to the relevant buses.
- **Use [`InterruptibleStandardLoad`](@ref) (PSSe v35 only)**: In newer versions of PSSe there is a flag for interruptible. Since PowerSystems.jl already has structures to model controllable load like [`InterruptiblePowerLoad`](@ref) and [`ShiftablePowerLoad`](@ref) a new type is used when parsing from PSSe to account of the interruptible behavior in economic modeling.
- - **Treatment of conforming and non-conforming flags**: See the section [`Conforming and Non-Conforming Loads`](@ref conf_loads). PowerSystems.jl uses an enum to represent this data but it does not implement specific models for this behavior.
- - **Breakers and Switches**: From the perspective of PowerSystems.jl breakers and switches are modeled as [`DiscreteControlledACBranch`](@ref). We use an enum to separate between the two but from the data structure perspective both use the same object definition.
- - **Rate data correction**: For rates B and C are set as `nothing` if the value in the file is zero. On the other hand, for rating A, the value gets corrected. If the raw file is zero, then set up the rating to infinite bound first and then reduced according to the voltage values. This proceedure still can produce a large amount of warning for situations where a single line is used to model a double circuit or a whole transmission corridor.
- - **Motor Loads**: We included a new device for explictly modeling motor loads. However, PSSe doesn't support explicit representations of these loads. The parser will print a warning in the log when we detect conditions commonly associated to motor load representations but won't be able to capture it directly.
+ - **Treatment of conforming and non-conforming flags**: See the section [`Conforming and Non-Conforming Loads`](@ref conf_loads). `PowerSystems.jl` uses an enum to represent this data but it does not implement specific models for this behavior.
+ - **Breakers and Switches**: From the perspective of `PowerSystems.jl` breakers and switches are modeled as [`DiscreteControlledACBranch`](@ref). We use an enum to separate between the two but from the data structure perspective both use the same object definition.
+ - **Rate data correction**: For rates B and C are set as `nothing` if the value in the file is zero. On the other hand, for rating A, the value gets corrected. If the raw file is zero, then set up the rating to infinite bound first and then reduced according to the voltage values. This procedure still can produce a large amount of warning for situations where a single line is used to model a double circuit or a whole transmission corridor.
+ - **Motor Loads**: We included a new device for explicitly modeling motor loads. However, PSSe doesn't support explicit representations of these loads. The parser will print a warning in the log when we detect conditions commonly associated to motor load representations but won't be able to capture it directly.
### Pending parsing challenges
- - Managing the new format for rate data. In the old PSSe versions, there was Rate A, Rate B and Rate C. However, in newer versions there are 12 possible rates open to intepretation by the modeler. it can still be interpreted as A, B or C rates or a rate per month. PSSe doesn't provide any metada to interpret the rating bands provided.
+ - Managing the new format for rate data. In the old PSSe versions, there was Rate A, Rate B and Rate C. However, in newer versions there are 12 possible rates open to interpretation by the modeler. It can still be interpreted as A, B or C rates or a rate per month. PSSe doesn't provide any metadata to interpret the rating bands provided.
- Detecting motor loads modeled as generators. Same as with the case for the negative loads, motors are known to be modeled as machines with negative injections (i.e., loads) to match modeling them in transient studies as machines.
- Automated transformer direction swaping. See [`this issue`](https://github.com/Sienna-Platform/PowerSystems.jl/issues/1423)
- Parsing outage data.
diff --git a/docs/src/how_to/parse_ts_from_csvs.md b/docs/src/how_to/parse_ts_from_csvs.md
index a61605c345..be4829588e 100644
--- a/docs/src/how_to/parse_ts_from_csvs.md
+++ b/docs/src/how_to/parse_ts_from_csvs.md
@@ -1,9 +1,9 @@
# [Parse Time Series Data from .csv's](@id parsing_time_series)
-This example shows how to parse time series data from .csv files to add to a `System`.
-For example, a `System` created by [parsing a MATPOWER file](@ref pm_data) doesn't contain
-any time series data, so a user may want to add time series to be able to run a production
-cost model.
+This example shows how to parse time series data from .csv files to add to a [`System`](@ref).
+For example, a [`System`](@ref) created by [parsing a MATPOWER file](@ref pm_data) doesn't contain
+any [time series](@ref ts_data) data, so a user may want to add time series to be able to run a production
+cost model. For background on the `System` container, see [About the System](@ref system_doc).
```@setup forecasts
using PowerSystems
@@ -14,16 +14,16 @@ sys = System(joinpath(file_dir, "case5_re.m"));
```
Let's use a predefined 5-bus [`System`](@ref) with some renewable generators and loads that
-we want to add time-series data to:
+we want to add time series data to:
-```@repl forecasts
+```@example forecasts
sys
```
## Define pointers to time series files
-`PowerSystems` requires a metadata file that maps components to their time series
-data in order to be able to automatically construct time_series from .csv data
+`PowerSystems.jl` requires a metadata file that maps components to their time series
+data in order to be able to automatically construct time series from .csv data
files.
For example, if we want to add a bunch of time series files, say one for each load and one
@@ -53,13 +53,13 @@ Notes:
be sufficient to return the scaling factor data using
`${scaling_factor_multiplier_module}.${scaling_factor_multiplier}(component)`.
-`PowerSystems` supports this metadata in either CSV or JSON formats.
+`PowerSystems.jl` supports this metadata in either CSV or JSON formats.
In this example, we will use the JSON format. The example file can be found
[here](https://github.com/Sienna-Platform/PowerSystemsTestData/blob/master/5-Bus/5bus_ts/timeseries_pointers_da.json),
and this is what its pointers look like in the required format:
-```@repl forecasts
+```@example forecasts
using PowerSystemCaseBuilder #hide
DATA_DIR = PowerSystemCaseBuilder.DATA_DIR #hide
FORECASTS_DIR = joinpath(DATA_DIR, "5-Bus", "5bus_ts"); #hide
@@ -71,7 +71,7 @@ end #hide
## Read and assign time series to `System` using these parameters.
-```@repl forecasts
+```@example forecasts
fname = joinpath(FORECASTS_DIR, "timeseries_pointers_da.json")
add_time_series!(sys, fname)
```
@@ -79,13 +79,13 @@ add_time_series!(sys, fname)
You can print the `System` to see a new table summarizing the time series data that has been
added:
-```@repl forecasts
+```@example forecasts
sys
```
### See also:
- - [Improve Performance with Time Series Data](@ref)
+ - [Improve Performance with Time Series Data](@ref improve_ts_performance)
- Parsing [Matpower or PSS/e RAW Files](@ref pm_data)
- Parsing [PSS/e DYR Files](@ref dyr_data)
- [Build a `System` from CSV files](@ref system_from_csv)
diff --git a/docs/src/how_to/reduce_repl_printing.md b/docs/src/how_to/reduce_repl_printing.md
index 8fc5d52ba7..382560e82f 100644
--- a/docs/src/how_to/reduce_repl_printing.md
+++ b/docs/src/how_to/reduce_repl_printing.md
@@ -1,4 +1,4 @@
-# Reduce REPL printing
+# [Reduce REPL printing](@id reduce_repl_printing)
By default `PowerSystems.jl` outputs to the REPL all Logging statements, which can be
overwhelming in some cases.
@@ -8,7 +8,7 @@ statements should be printed to the console or a log file:
**Example**: Set log output to only see error messages in the console
-```julia
+```@example reduce_repl_printing
using PowerSystems
using Logging
configure_logging(; console_level = Logging.Error)
diff --git a/docs/src/how_to/serialize_data.md b/docs/src/how_to/serialize_data.md
index b23f3e32da..d30c46ff24 100644
--- a/docs/src/how_to/serialize_data.md
+++ b/docs/src/how_to/serialize_data.md
@@ -1,4 +1,4 @@
-# Write, View, and Load Data with a JSON
+# [Write, View, and Load Data with a JSON](@id serialize_data)
`PowerSystems.jl` provides functionality to serialize an entire [`System`](@ref) to a JSON
file and then deserialize it back to a `System`. The main benefit is that
@@ -17,7 +17,7 @@ simply to illustrate the process.
First, load the dependencies and a `System` from `PowerSystemCaseBuilder`:
-```@repl serialize_data
+```@example serialize_data
using PowerSystems
using PowerSystemCaseBuilder
sys = build_system(PSISystems, "c_sys5_pjm")
@@ -25,14 +25,14 @@ sys = build_system(PSISystems, "c_sys5_pjm")
Set up your target path, for example in a "mysystems" subfolder:
-```@repl serialize_data
+```@example serialize_data
folder = mkdir("mysystems");
path = joinpath(folder, "system.json")
```
Now write the system to JSON:
-```@repl serialize_data
+```@example serialize_data
to_json(sys, path)
```
@@ -95,18 +95,18 @@ jq '.data.components | .[] | select(.__metadata__.type == "ThermalStandard" and
Finally, you can read the file back in, and verify the new system has the same data as above:
-```@repl serialize_data
+```@example serialize_data
sys2 = System(path)
-rm(folder; recursive = true); #hide
```
!!! tip
- PowerSystems generates UUIDs for the `System` and all components in order to have
+ PowerSystems generates [UUIDs](@ref U) for the `System` and all components in order to have
a way to uniquely identify objects. During deserialization it restores the same
- UUIDs. If you will modify the `System` or components after deserialization then
+ [UUIDs](@ref U). If you will modify the `System` or components after deserialization then
it is recommended that you set this flag to generate new UUIDs.
- ```julia
+ ```@example serialize_data
system2 = System(path; assign_new_uuids = true)
+ rm(folder; recursive = true); #hide
```
diff --git a/docs/src/how_to/use_context_managers.md b/docs/src/how_to/use_context_managers.md
index 497fafc31b..5e1dfdff43 100644
--- a/docs/src/how_to/use_context_managers.md
+++ b/docs/src/how_to/use_context_managers.md
@@ -1,16 +1,16 @@
-# Use Context Managers for Efficient Bulk Operations
+# [Use Context Managers for Efficient Bulk Operations](@id use_context_managers)
`PowerSystems.jl` provides several "context manager" functions that help you perform bulk
operations more efficiently and safely. These functions temporarily change system settings or
optimize batch operations, then automatically restore the original state when complete.
-Context managers in PowerSystems follow a pattern similar to `Logging.with_logger` in Julia.
+Context managers in `PowerSystems.jl` follow a pattern similar to `Logging.with_logger` in Julia.
They accept a function (typically as a `do` block) that executes with modified settings,
ensuring cleanup even if errors occur.
## Available Context Managers
-PowerSystems provides three main context managers:
+`PowerSystems.jl` provides three main context managers:
1. [`with_units_base`](@ref) - Temporarily change unit system for getting/setting component data
2. [`begin_supplemental_attributes_update`](@ref) - Optimize bulk addition/removal of supplemental attributes
@@ -19,7 +19,7 @@ PowerSystems provides three main context managers:
## Using `with_units_base`
The [`with_units_base`](@ref) function temporarily changes the [unit system](@ref per_unit)
-for a `System` or `Component`, executes your code, then automatically restores the original
+for a [`System`](@ref) or [`Component`](@ref), executes your code, then automatically restores the original
unit system. This is useful when you need to retrieve or set values in a specific unit system
without permanently changing the system's configuration.
@@ -31,7 +31,7 @@ without permanently changing the system's configuration.
### Example: Getting Component Data in Natural Units
-```julia
+```@example use_context_managers
using PowerSystems
using PowerSystemCaseBuilder
@@ -49,7 +49,7 @@ end
### Example: Setting Multiple Component Values in Natural Units
-```julia
+```@example use_context_managers
# Temporarily change units to add/modify multiple components in natural units
with_units_base(sys, "NATURAL_UNITS") do
for gen in get_components(ThermalStandard, sys)
@@ -66,7 +66,7 @@ end
You can also use `with_units_base` on individual components:
-```julia
+```@example use_context_managers
active_power_mw = with_units_base(gen, UnitSystem.NATURAL_UNITS) do
get_active_power(gen)
end
@@ -154,7 +154,7 @@ If an error occurs during the update, changes are automatically reverted.
### Example: Adding Multiple Time Series
-```julia
+```@example use_context_managers
using PowerSystems
using Dates
@@ -184,7 +184,7 @@ end
### Example: Adding Time Series from Multiple Sources
-```julia
+```@example use_context_managers
# When you have time series data from multiple sources
begin_time_series_update(sys) do
for component in get_components(Generator, sys)
@@ -196,7 +196,7 @@ begin_time_series_update(sys) do
)
forecast = Deterministic(
- "max_active_power",
+ "max_active_power_2",
component_data,
resolution;
scaling_factor_multiplier = get_max_active_power,
@@ -250,6 +250,6 @@ end
## See Also
- [Per-unit Conventions](@ref per_unit) - Learn more about unit systems
- - [Supplemental Attributes](@ref supplemental_attributes) - Details on supplemental attribute usage
- - [Working with Time Series Data](@ref tutorial_time_series) - Tutorial on time series handling
- - [Improve Performance with Time Series Data](@ref) - Additional time series performance tips
+ - [Supplemental Attributes](@ref supplemental_attributes_explanation) - Details on supplemental attribute usage
+ - [Working with Time Series Data](@ref "Working with Time Series Data") - Tutorial on time series handling
+ - [Improve Performance with Time Series Data](@ref improve_ts_performance) - Additional time series performance tips
diff --git a/docs/src/how_to/use_subsystems.md b/docs/src/how_to/use_subsystems.md
new file mode 100644
index 0000000000..763c9b2bd0
--- /dev/null
+++ b/docs/src/how_to/use_subsystems.md
@@ -0,0 +1,86 @@
+# [Use Subsystems](@id use_subsystems)
+
+For certain applications, such as those that employ dispatch coordination methods or
+decomposition approaches, it is useful to split components into subsystems based on
+user-defined criteria. The [`System`](@ref) provides `subsystem` containers for this
+purpose. Each subsystem is defined by a name and can hold references to any number of
+components. For background on the `System` container, see [System](@ref system_doc).
+
+## Create subsystems and add components
+
+Load a `System`, then call [`add_subsystem!`](@ref) to register named subsystems:
+
+```@example subsystem
+using PowerSystems;
+using PowerSystemCaseBuilder;
+sys = build_system(PSISystems, "c_sys5_pjm")
+add_subsystem!(sys, "1")
+add_subsystem!(sys, "2")
+```
+
+Assign devices to subsystems using [`add_component_to_subsystem!`](@ref):
+
+```@example subsystem
+g = get_component(ThermalStandard, sys, "Alta")
+add_component_to_subsystem!(sys, "1", g)
+
+g = get_component(ThermalStandard, sys, "Sundance")
+add_component_to_subsystem!(sys, "2", g)
+```
+
+## Retrieve components from a subsystem
+
+Pass the `subsystem_name` keyword argument to [`get_components`](@ref) to filter by
+subsystem:
+
+```@example subsystem
+gens_1 = get_components(ThermalStandard, sys; subsystem_name = "1")
+get_name.(gens_1)
+
+gens_2 = get_components(ThermalStandard, sys; subsystem_name = "2")
+get_name.(gens_2)
+```
+
+# !!! tip
+
+# The get_name. command may look like a way to use `.` to access the fields of a component, which is method actively discouraged by the Sienna team, however it is a [broadcast function](https://blog.glcs.io/broadcasting) in Julia.
+
+## Export a subsystem as a new `System`
+
+[`from_subsystem`](@ref) produces a new, standalone [`System`](@ref) from the components
+assigned to a subsystem. This requires careful assignment of all dependencies — not just
+the devices themselves, but also any topology elements (buses, arcs) they reference.
+
+```@example subsystem
+from_subsystem(sys, "1"; runchecks = false)
+```
+
+!!! warning
+
+ The system above was created with `runchecks=false` and is technically invalid: the bus
+ connected to the Alta generator is not part of subsystem "1". Without `runchecks=false`,
+ this call would raise an error. Add the bus first, then re-run [`from_subsystem`](@ref):
+
+A valid exported `System` requires three additional components:
+
+ - **The generator's bus** (`nodeA`) — every device must have its connected bus present in
+ the subsystem.
+ - **A reference (slack) bus** (`nodeD`) — at least one [`ACBus`](@ref) with
+ `bustype = ACBusTypes.REF` must be present for the system to pass validation.
+ - **An [`ElectricLoad`](@ref)** — a subsystem with no load components triggers a
+ validation warning. Adding the [`PowerLoad`](@ref) connected to the slack bus
+ satisfies this requirement.
+
+```@example subsystem
+g = get_component(ThermalStandard, sys, "Alta")
+b = get_bus(g)
+add_component_to_subsystem!(sys, "1", b)
+ref_bus = get_component(ACBus, sys, "nodeD")
+add_component_to_subsystem!(sys, "1", ref_bus)
+load = first(get_components(x -> get_bus(x) === ref_bus, PowerLoad, sys))
+add_component_to_subsystem!(sys, "1", load)
+from_subsystem(sys, "1")
+```
+
+Advanced users can pass `runchecks=false` to skip topological validation. Only do this
+if you are confident you can validate the resulting system before using it for modeling.
diff --git a/docs/src/how_to/use_supplemental_attributes.md b/docs/src/how_to/use_supplemental_attributes.md
new file mode 100644
index 0000000000..3165196df3
--- /dev/null
+++ b/docs/src/how_to/use_supplemental_attributes.md
@@ -0,0 +1,65 @@
+# [How to use supplemental attributes](@id use_supplemental_attributes_how_to)
+
+This how-to assumes you have a [`System`](@ref) named `sys` with at least one [`FixedForcedOutage`](@ref) supplemental attribute attached to a component. See [Add Supplemental Attributes to a System](@ref add_supplemental_attributes) if you need to set that up first.
+
+## Get the attributes in a system
+
+Use [`get_supplemental_attributes`](@ref) with a supplemental attribute type to retrieve all matching attributes from a system.
+
+```julia
+using PowerSystems
+
+for outage in get_supplemental_attributes(FixedForcedOutage, sys)
+ @show summary(outage)
+end
+```
+
+The output includes the attribute type name and its [UUID](@ref U) — a unique identifier automatically assigned when the attribute was created.
+
+## Get the attributes associated with a component
+
+Use [`get_supplemental_attributes`](@ref) with a component instead of a system to retrieve only the attributes attached to that component.
+
+```julia
+using PowerSystems
+
+gen1 = first(get_components(ThermalStandard, sys))
+for outage in get_supplemental_attributes(FixedForcedOutage, gen1)
+ @show summary(outage)
+end
+```
+
+The output includes the attribute type name and its [UUID](@ref U). You can also pass a filter function as the first argument — for example, `x -> PowerSystems.get_outage_status(x) >= 0.5` — to narrow results by field values.
+
+## Get the components associated with an attribute
+
+Use [`get_associated_components`](@ref) to retrieve the components attached to a single supplemental attribute.
+
+```julia
+using PowerSystems
+
+outage = first(get_supplemental_attributes(FixedForcedOutage, sys))
+for component in get_associated_components(sys, outage)
+ @show summary(component)
+end
+```
+
+The output is the [`FixedForcedOutage`](@ref) type and name. You can also pass a `component_type` keyword argument (e.g., `component_type = ThermalStandard`) to filter results to a specific component type.
+
+## Get component / supplemental attribute pairs
+
+Use [`get_component_supplemental_attribute_pairs`](@ref) to retrieve component/attribute pairs by type. Prefer this over nested loops iterating over components and their attributes separately.
+
+```julia
+using PowerSystems
+
+for (gen, outage) in get_component_supplemental_attribute_pairs(
+ ThermalStandard,
+ FixedForcedOutage,
+ sys,
+)
+ @show summary(gen) summary(outage)
+end
+```
+
+The output is a summary of the component_type and the [UUID](@ref U) of the [`FixedForcedOutage`](@ref).
diff --git a/docs/src/tutorials/add_dynamic_data.jl b/docs/src/tutorials/add_dynamic_data.jl
index b0215ada46..6c9d2c0c9d 100644
--- a/docs/src/tutorials/add_dynamic_data.jl
+++ b/docs/src/tutorials/add_dynamic_data.jl
@@ -328,7 +328,7 @@ threebus_sys
# a more complex EMT simulation with the additional dynamic inverter and dynamic lines.
# Next, you might like to:
# - Read more about the static and dynamic data layers and the dynamic data format in
-# [Dynamic Devices](@ref).
+# [Dynamic Devices](@ref dynamic_data).
# - Review the specific subsystem models available in `PowerSystems.jl` for [Machine](@ref),
# [Shaft](@ref), [AVR](@ref), [PSS](@ref),
# [Prime Mover and Turbine Governor](@ref TurbineGov), [Converter](@ref),
diff --git a/docs/src/tutorials/creating_system.jl b/docs/src/tutorials/creating_system.jl
index 2773fc259b..cb0bc299cf 100644
--- a/docs/src/tutorials/creating_system.jl
+++ b/docs/src/tutorials/creating_system.jl
@@ -292,10 +292,10 @@ sys
# In this tutorial, you manually created a power [`System`](@ref), added and then retrieved its components,
# and modified the [`System`](@ref) per-unit settings.
# Next, you might want to:
-# - [Add time series data to components in the `System`](@ref tutorial_time_series)
+# - [Working with Time Series Data](@ref)
# - [Add necessary data for dynamic simulations](@ref "Adding Data for Dynamic Simulations")
# - Import a [`System`](@ref) [from an existing Matpower or PSSE file](@ref pm_data) or
# [with PSSE dynamic data](@ref dyr_data) instead of creating it manually
# - [Create your own `System` from .csv files instead of creating it manually](@ref system_from_csv)
# - [Read more to understand per-unitization in PowerSystems.jl](@ref per_unit)
-# - See a workaround for how to [Add a Component in Natural Units](@ref)
+# - See a workaround for how to [Add a Component in Natural Units](@ref add_component_natural_units)
diff --git a/docs/src/tutorials/manipulating_datasets.jl b/docs/src/tutorials/manipulating_datasets.jl
index 1c3ef64722..67224720f6 100644
--- a/docs/src/tutorials/manipulating_datasets.jl
+++ b/docs/src/tutorials/manipulating_datasets.jl
@@ -254,4 +254,4 @@ get_name.(get_components(ThermalStandard, sys))
# We used specific `get_*` functions and `set_*` functions to see and update the fields in
# [`ThermalStandard`](@ref) and [`ACBus`](@ref) components, but remember that these getters
# and setters are available for each data field for components of all Types in `PowerSystems.jl`.
-# Follow the next tutorials to learn how to [work with time series](@ref tutorial_time_series).
+# Follow the next tutorials to learn more in the [Working with Time Series Data](@ref) tutorial.
diff --git a/docs/src/tutorials/working_with_time_series.jl b/docs/src/tutorials/working_with_time_series.jl
index 6d9d846920..61633b0519 100644
--- a/docs/src/tutorials/working_with_time_series.jl
+++ b/docs/src/tutorials/working_with_time_series.jl
@@ -1,8 +1,10 @@
-# # [Working with Time Series Data](@id tutorial_time_series)
+# # Working with Time Series Data
# In this tutorial, we will manually add, retrieve, and inspect time-series data in
# different formats, including identifying which components in a power [`System`](@ref) have time
# series data. Along the way, we will also use workarounds for missing forecast data and
# reuse identical time series profiles to avoid unnecessary memory usage.
+# For a conceptual overview of how time series data is structured in `PowerSystems.jl`,
+# see [Time Series Data](@ref ts_data).
# ## Example Data and Setup
# We will make an example [`System`](@ref) with a wind generator and two loads, and
@@ -275,56 +277,14 @@ get_time_series_array(
)
# See that `load1`'s scaling factor multiplier is still being applied as expected.
-# # Transform with Multiple Intervals
-# PowerSystems supports creating multiple forecast transforms from the same
-# [`SingleTimeSeries`](@ref), each with a different [interval](@ref I). This is useful when
-# a component needs forecasts updated at different frequencies.
-# Use `delete_existing = false` to preserve the existing transform and add a second one
-# with a different interval:
+# You can also query the system-wide forecast parameters:
-transform_single_time_series!(
- system,
- Dates.Hour(1), # horizon
- Dates.Hour(1); # a longer interval
- delete_existing = false,
-);
-
-# Now `load1` has two [`DeterministicSingleTimeSeries`](@ref) forecasts with different
-# intervals. Let's verify:
-
-show_time_series(load1)
-
-# When multiple intervals exist for the same name, you must specify `interval` to
-# disambiguate retrieval:
-
-get_time_series_array(
- DeterministicSingleTimeSeries,
- load1,
- "max_active_power";
- start_time = DateTime("2020-01-01T08:00:00"),
- interval = Dates.Minute(30),
-)
-
-# You can also query forecast parameters for a specific interval:
-
-get_forecast_horizon(system; interval = Dates.Hour(1))
+get_forecast_horizon(system)
#
-get_forecast_interval(system; interval = Dates.Minute(30))
+get_forecast_interval(system)
-# To selectively remove one interval's forecasts while keeping the other:
-
-remove_time_series!(
- system,
- DeterministicSingleTimeSeries,
- load1,
- "max_active_power";
- interval = Dates.Hour(1),
-);
-show_time_series(load1)
-
-# The 30-minute interval forecast is still present while the 1-hour one has been removed.
# # Finding, Retrieving, and Inspecting Time Series
# Now, let's complete this tutorial by doing a few sanity checks on the data that we've added,
# where are we will also examine components with time series and retrieve
@@ -381,6 +341,54 @@ get_max_active_power(wind1)
# `max_active_power` field, so check
# [`get_max_active_power`](@ref get_max_active_power(d::RenewableGen))
# to see how its calculated.
+# ## Getting Timestamps and Values Separately
+# When working with a retrieved time series object, you can extract the timestamps and
+# values independently rather than always working with the combined `TimeArray`.
+# Use [`get_time_series_timestamps`](@ref) to get just the timestamps for a time series:
+
+get_time_series_timestamps(SingleTimeSeries, load1, "max_active_power")
+
+# Use [`get_time_series_values`](@ref) to get just the numeric values. Note the
+# scaling factor multiplier is still applied:
+
+get_time_series_values(SingleTimeSeries, load1, "max_active_power")
+
+# The same functions work for forecasts -- just pass a `start_time` to select the window:
+
+get_time_series_timestamps(
+ Deterministic,
+ wind1,
+ "max_active_power";
+ start_time = DateTime("2020-01-01T08:00:00"),
+)
+
+get_time_series_values(
+ Deterministic,
+ wind1,
+ "max_active_power";
+ start_time = DateTime("2020-01-01T08:00:00"),
+)
+
+# ## Inspecting Time Series Metadata
+# Rather than retrieving the full data, you can inspect the structural metadata of a time
+# series object returned by [`get_time_series`](@ref).
+# Let's retrieve the wind forecast object again and inspect its properties:
+
+forecast = get_time_series(wind1, keys[1])
+
+# Get the [resolution](@ref R) (time step between values):
+
+get_resolution(forecast)
+
+# Get the [horizon](@ref H) (total duration of one forecast window):
+
+get_horizon(forecast)
+
+# Similarly, for the load [`SingleTimeSeries`](@ref):
+
+sts = get_time_series(SingleTimeSeries, load1, "max_active_power")
+get_resolution(sts)
+
# # Next Steps
# In this tutorial, you defined, added, and retrieved four time series data
# sets, including static time series and deterministic forecasts. Along the way, we
@@ -389,6 +397,6 @@ get_max_active_power(wind1)
# forecast data.
# Next you might like to:
# - [Parse many timeseries data sets from CSV's](@ref parsing_time_series)
-# - [See how to improve performance efficiency with your own time series data](@ref "Improve Performance with Time Series Data")
+# - [See how to improve performance efficiency with your own time series data](@ref improve_ts_performance)
# - [Review the available time series data formats](@ref ts_data)
# - [Learn more about how times series data is stored](@ref "Data Storage")
diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl
index 2861d67dd2..bcfd1dd1bb 100644
--- a/src/PowerSystems.jl
+++ b/src/PowerSystems.jl
@@ -113,6 +113,7 @@ export HydroReservoirCost
export get_fuel_cost, set_fuel_cost!, get_vom_cost
export is_market_bid_curve, make_market_bid_curve
export make_import_curve, make_export_curve
+export StartUpStages
export get_no_load_cost, set_no_load_cost!, get_start_up, set_start_up!
export set_shut_down!
export get_curtailment_cost
@@ -165,6 +166,7 @@ export DynamicInjection
export DynamicGenerator
export DynamicInverter
+export InverterComponent
export DynamicBranch
export HybridSystem
@@ -790,7 +792,17 @@ provide a constructor that allows existing values to be deserialized.
"""
abstract type Component <: IS.InfrastructureSystemsComponent end
-""" Supertype for "devices" (bus, line, etc.) """
+"""
+ Device
+
+Supertype for all physical equipment in a power system.
+
+Subtypes include [`Branch`](@ref) (transmission elements) and [`StaticInjection`](@ref)
+(generators, loads, and storage). All `Device` subtypes support time series and
+supplemental attributes.
+
+See also: [`Branch`](@ref), [`StaticInjection`](@ref), [`Component`](@ref)
+"""
abstract type Device <: Component end
"""
diff --git a/src/base.jl b/src/base.jl
index 2d8943091e..ebac35f8b8 100644
--- a/src/base.jl
+++ b/src/base.jl
@@ -179,12 +179,37 @@ function System(data, base_power::Number, internal; kwargs...)
return System(data, units_settings, internal; kwargs...)
end
-"""Construct an empty `System`. Useful for building a System while parsing raw data."""
+"""
+ System(base_power::Number; kwargs...)
+
+Construct an empty [`System`](@ref) with the given base power. Useful for building a
+`System` incrementally while parsing raw data.
+
+# Arguments
+- `base_power::Number`: The system base power in MVA.
+
+For all keyword arguments, see the main [`System`](@ref) docstring.
+
+See also: [`add_component!`](@ref)
+"""
function System(base_power::Number; kwargs...)
return System(_create_system_data_from_kwargs(; kwargs...), base_power; kwargs...)
end
-"""Construct a `System` from `InfrastructureSystems.SystemData`"""
+"""
+ System(data, base_power::Number; internal, kwargs...)
+
+Construct a [`System`](@ref) from a pre-built `InfrastructureSystems.SystemData` object.
+Primarily used during deserialization and advanced workflows where `SystemData` is
+assembled separately.
+
+# Arguments
+- `data`: An `InfrastructureSystems.SystemData` instance.
+- `base_power::Number`: The system base power in MVA.
+- `internal`: (default: `IS.InfrastructureSystemsInternal()`) Internal IS metadata. **Do not set manually.**
+
+For all keyword arguments, see the main [`System`](@ref) docstring.
+"""
function System(
data,
base_power::Number;
@@ -195,7 +220,19 @@ function System(
end
"""
-System constructor when components are constructed externally.
+ System(base_power::Float64, buses::Vector{ACBus}, components...; kwargs...)
+
+Construct a [`System`](@ref) from pre-built component collections. Buses are added first,
+then all remaining component iterables are flattened and added in order.
+
+# Arguments
+- `base_power::Float64`: The system base power in MVA.
+- `buses::Vector{ACBus}`: The AC buses to add to the system.
+- `components...`: Additional iterables of [`Component`](@ref) subtypes to add.
+
+For all keyword arguments, see the main [`System`](@ref) docstring.
+
+See also: [`add_component!`](@ref), [`add_components!`](@ref)
"""
function System(base_power::Float64, buses::Vector{ACBus}, components...; kwargs...)
data = _create_system_data_from_kwargs(; kwargs...)
@@ -216,7 +253,7 @@ function System(base_power::Float64, buses::Vector{ACBus}, components...; kwargs
return sys
end
-"""Constructs a non-functional System for demo purposes."""
+"""Construct a non-functional System for demo purposes."""
function System(
::Nothing;
buses = [
@@ -268,11 +305,24 @@ function system_via_power_models(file_path::AbstractString; kwargs...)
return System(PowerModelsData(file_path; pm_kwargs...); sys_kwargs...)
end
-"""Constructs a System from a file path ending with .m, .raw, or .json
+"""
+ System(file_path::AbstractString; assign_new_uuids, try_reimport, kwargs...)
+
+Construct a [`System`](@ref) from a file. Supported formats:
+- `.m` — Matpower
+- `.raw` — PSS/e (PTI format)
+- `.json` — PowerSystems.jl serialized system
-If the file is JSON, then `assign_new_uuids = true` will generate new UUIDs for the system
-and all components. If the file is .raw, then `try_reimport = false` will skip searching for
-a `_export_metadata.json` file in the same directory.
+# Arguments
+- `file_path::AbstractString`: Path to the input file.
+- `assign_new_uuids::Bool`: (default: `false`) When loading from JSON, generate new UUIDs
+ for the system and all components.
+- `try_reimport::Bool`: (default: `true`) When loading `.raw` files, search for a
+ `_export_metadata.json` file in the same directory to restore metadata.
+
+For all keyword arguments, see the main [`System`](@ref) docstring.
+
+See also: [`System(sys_file, dyr_file)`](@ref System(::AbstractString, ::AbstractString))
"""
function System(
file_path::AbstractString;
@@ -353,25 +403,31 @@ function _post_deserialize_handling(sys::System; runchecks = true, assign_new_uu
end
"""
-Parse static and dynamic data directly from PSS/e text files. Automatically generates
-all the relationships between the available dynamic injection models and the static counterpart
+ System(sys_file::AbstractString, dyr_file::AbstractString; kwargs...)
-Each dictionary indexed by id contains a vector with 5 of its components:
-* Machine
-* Shaft
-* AVR
-* TurbineGov
-* PSS
+Construct a [`System`](@ref) by parsing static data from a PSS/e `.raw` file and dynamic
+data from a `.dyr` file. Automatically generates all relationships between the dynamic
+injection models and their static counterparts.
+
+Each dynamic injection model includes five sub-components:
+- Machine
+- Shaft
+- AVR
+- TurbineGov
+- PSS
+
+# Arguments
+- `sys_file::AbstractString`: Path to the PSS/e `.raw` file (PTI data format).
+- `dyr_file::AbstractString`: Path to the PSS/e `.dyr` dynamic data file.
-Files must be parsed from a .raw file (PTI data format) and a .dyr file.
+For all keyword arguments, see the main [`System`](@ref) docstring.
-## Examples:
+# Examples
```julia
-raw_file = "Example.raw"
-dyr_file = "Example.dyr"
-sys = System(raw_file, dyr_file)
+sys = System("Example.raw", "Example.dyr")
```
+See also: [`add_dyn_injectors!`](@ref)
"""
function System(sys_file::AbstractString, dyr_file::AbstractString; kwargs...)
ext = splitext(sys_file)[2]
@@ -447,7 +503,7 @@ function filter_components_by_subsystem!(
end
"""
-Serializes a system to a JSON file and saves time series to an HDF5 file.
+Serialize a system to a JSON file and saves time series to an HDF5 file.
# Arguments
- `sys::System`: system
@@ -512,7 +568,7 @@ end
IS.assign_new_uuid!(sys::System) = IS.assign_new_uuid_internal!(sys)
"""
-Return the internal of the system
+Return the internal of the system.
"""
IS.get_internal(sys::System) = sys.internal
@@ -568,7 +624,7 @@ _set_units_base!(system::System, settings::String) =
_set_units_base!(system::System, UNIT_SYSTEM_MAPPING[uppercase(settings)])
"""
-Sets the units base for the getter functions on the devices. It modifies the behavior of all getter functions
+Set the units base for the getter functions on the devices. It modifies the behavior of all getter functions
# Examples
```julia
@@ -587,7 +643,7 @@ end
_get_units_base(system::System) = system.units_settings.unit_system
"""
-Get the system's [unit base](@ref per_unit))
+Return the system's [unit base](@ref per_unit).
"""
function get_units_base(system::System)
return string(_get_units_base(system))
@@ -671,7 +727,9 @@ Set the name of the system.
set_name!(sys::System, name::AbstractString) = sys.metadata.name = name
"""
-Get the name of the system.
+Return the name of the [`System`](@ref).
+
+See also: [`set_name!`](@ref)
"""
get_name(sys::System) = sys.metadata.name
@@ -682,18 +740,26 @@ set_description!(sys::System, description::AbstractString) =
sys.metadata.description = description
"""
-Get the description of the system.
+Return the description of the system.
"""
get_description(sys::System) = sys.metadata.description
"""
+ add_component!(sys::System, component; skip_validation, kwargs...)
+
Add a component to the system.
A component cannot be added to more than one `System`.
-Throws ArgumentError if the component's name is already stored for its concrete type.
-Throws ArgumentError if any Component-specific rule is violated.
-Throws InvalidValue if any of the component's field values are outside of defined valid
-range.
+
+# Arguments
+- `sys::System`: The system to add the component to.
+- `component`: The [`Component`](@ref) to add.
+- `skip_validation::Bool`: (default: `false`) Skip field value range validation.
+
+# Throws
+- `ArgumentError`: if the component's name is already stored for its concrete type.
+- `ArgumentError`: if any component-specific rule is violated.
+- `InvalidValue`: if any field value is outside its defined valid range.
# Examples
```julia
@@ -708,7 +774,7 @@ generators = [gen1, gen2, gen3]
foreach(x -> add_component!(sys, x), Iterators.flatten((buses, generators)))
```
-See also [`add_components!`](@ref).
+See also: [`add_components!`](@ref)
"""
function add_component!(
sys::System,
@@ -753,13 +819,21 @@ function add_component!(
end
"""
-Add many components to the system at once.
+ add_components!(sys::System, components)
+
+Add multiple components to the system at once. Equivalent to calling
+[`add_component!`](@ref) on each element of `components`.
A component cannot be added to more than one `System`.
-Throws ArgumentError if the component's name is already stored for its concrete type.
-Throws ArgumentError if any Component-specific rule is violated.
-Throws InvalidValue if any of the component's field values are outside of defined valid
-range.
+
+# Arguments
+- `sys::System`: The system to add the components to.
+- `components`: An iterable of [`Component`](@ref) subtypes to add.
+
+# Throws
+- `ArgumentError`: if any component's name is already stored for its concrete type.
+- `ArgumentError`: if any component-specific rule is violated.
+- `InvalidValue`: if any field value is outside its defined valid range.
# Examples
```julia
@@ -767,8 +841,10 @@ sys = System(100.0)
buses = [bus1, bus2, bus3]
generators = [gen1, gen2, gen3]
-add_components!(sys, Iterators.flatten((buses, generators))
+add_components!(sys, Iterators.flatten((buses, generators)))
```
+
+See also: [`add_component!`](@ref)
"""
function add_components!(sys::System, components)
foreach(x -> add_component!(sys, x), components)
@@ -950,12 +1026,24 @@ function _add_service!(
end
"""
-Similar to [`add_component!`](@ref) but for services.
+ add_service!(sys::System, service::Service, contributing_devices; kwargs...)
+
+Add a service to the system with multiple contributing devices.
# Arguments
-- `sys::System`: system
-- `service::Service`: service to add
-- `contributing_devices`: Must be an iterable of type Device
+- `sys::System`: The system to add the service to.
+- `service::Service`: The service to add.
+- `contributing_devices`: An iterable of [`Device`](@ref) subtypes that contribute to
+ the service.
+
+# Throws
+- `ArgumentError`: if any contributing device is not of type `Device`.
+
+See also: [`add_service!(device, service, sys)`](@ref add_service!(
+ device::Device,
+ service::Service,
+ sys::System,
+)), [`remove_service!`](@ref)
"""
function add_service!(sys::System, service::Service, contributing_devices; kwargs...)
_add_service!(sys, service, contributing_devices; kwargs...)
@@ -963,12 +1051,20 @@ function add_service!(sys::System, service::Service, contributing_devices; kwarg
end
"""
-Similar to [`add_component!`](@ref) but for services.
+ add_service!(sys::System, service::Service, contributing_device::Device; kwargs...)
+
+Add a service to the system with a single contributing device.
# Arguments
-- `sys::System`: system
-- `service::Service`: service to add
-- `contributing_device::Device`: Valid Device
+- `sys::System`: The system to add the service to.
+- `service::Service`: The service to add.
+- `contributing_device::Device`: The device that contributes to the service.
+
+See also: [`add_service!(device, service, sys)`](@ref add_service!(
+ device::Device,
+ service::Service,
+ sys::System,
+)), [`remove_service!`](@ref)
"""
function add_service!(sys::System, service::Service, contributing_device::Device; kwargs...)
_add_service!(sys, service, [contributing_device]; kwargs...)
@@ -976,13 +1072,26 @@ function add_service!(sys::System, service::Service, contributing_device::Device
end
"""
-Similar to [`add_service!`](@ref) but for Service and Device already stored in the system.
-Performs validation checks on the device and the system
+ add_service!(device::Device, service::Service, sys::System)
+
+Add a service to a device that is already stored in the system. Both the device and the
+service must already be attached to `sys`.
# Arguments
-- `device::Device`: Device
-- `service::Service`: Service
-- `sys::System`: system
+- `device::Device`: The device to add the service to.
+- `service::Service`: The service to add.
+- `sys::System`: The system containing both the device and the service.
+
+# Throws
+- `ArgumentError`: if the service is not attached to the system.
+- `ArgumentError`: if the device is not attached to the system.
+
+See also: [`add_service!(sys, service, contributing_devices)`](@ref add_service!(
+ sys::System,
+ service::Service,
+ contributing_devices;
+ kwargs...,
+)), [`remove_service!`](@ref)
"""
function add_service!(device::Device, service::Service, sys::System)
throw_if_not_attached(service, sys)
@@ -992,11 +1101,26 @@ function add_service!(device::Device, service::Service, sys::System)
end
"""
-Similar to [`add_component!`](@ref) but for ConstantReserveGroup.
+ add_service!(sys::System, service::ConstantReserveGroup; skip_validation, kwargs...)
+
+Add a [`ConstantReserveGroup`](@ref) to the system. All contributing services referenced
+by the group must already be attached to the system.
# Arguments
-- `sys::System`: system
-- `service::ConstantReserveGroup`: service to add
+- `sys::System`: The system to add the service to.
+- `service::ConstantReserveGroup`: The reserve group to add.
+- `skip_validation::Bool`: (default: `false`) Skip field value range validation.
+
+# Throws
+- `ArgumentError`: if any contributing service is not attached to the system.
+
+See also: [`add_service!(sys, service, contributing_services)`](@ref add_service!(
+ sys::System,
+ service::ConstantReserveGroup,
+ contributing_services::Vector{<:Service};
+ skip_validation,
+ kwargs...,
+)), [`remove_service!`](@ref)
"""
function add_service!(
sys::System,
@@ -1029,12 +1153,26 @@ function set_contributing_services!(
end
"""
-Similar to [`add_component!`](@ref) but for ConstantReserveGroup.
+ add_service!(sys::System, service::ConstantReserveGroup, contributing_services::Vector{<:Service}; skip_validation, kwargs...)
+
+Add a [`ConstantReserveGroup`](@ref) to the system with its contributing services.
+All contributing services must already be attached to the system.
# Arguments
-- `sys::System`: system
-- `service::ConstantReserveGroup`: service to add
-- `contributing_services`: contributing services to the group
+- `sys::System`: The system to add the service to.
+- `service::ConstantReserveGroup`: The reserve group to add.
+- `contributing_services::Vector{<:Service}`: The services that contribute to the group.
+- `skip_validation::Bool`: (default: `false`) Skip field value range validation.
+
+# Throws
+- `ArgumentError`: if any contributing service is not attached to the system.
+
+See also: [`add_service!(sys, service::ConstantReserveGroup)`](@ref add_service!(
+ sys::System,
+ service::ConstantReserveGroup;
+ skip_validation,
+ kwargs...,
+)), [`remove_service!`](@ref)
"""
function add_service!(
sys::System,
@@ -1085,12 +1223,20 @@ function open_time_series_store!(
end
"""
-Begin an update of time series. Use this function when adding many time series arrays
-in order to improve performance.
+ begin_time_series_update(func::Function, sys::System)
-If an error occurs during the update, changes will be reverted.
+Batch time series additions or removals within `func` to improve performance. When adding
+or removing many time series arrays, this avoids the overhead of opening and closing the
+underlying HDF5 file on every call.
-Using this function to remove time series is currently not supported.
+If an error occurs during the update, all changes are reverted.
+
+!!! note
+ Using this function to remove time series is currently not supported.
+
+# Arguments
+- `func::Function`: A zero-argument `do`-block containing [`add_time_series!`](@ref) calls.
+- `sys::System`: The system to update.
# Examples
```julia
@@ -1099,18 +1245,29 @@ begin_time_series_update(sys) do
add_time_series!(sys, component2, time_series2)
end
```
+
+See also: [`add_time_series!`](@ref), [`open_time_series_store!`](@ref)
"""
begin_time_series_update(func::Function, sys::System) =
IS.begin_time_series_update(func, sys.data.time_series_manager)
"""
-Add time series data from a metadata file or metadata descriptors.
+ add_time_series!(sys::System, metadata_file::AbstractString; resolution)
+
+Add time series data from a metadata file. The file must contain an array of
+`IS.TimeSeriesFileMetadata` instances describing the time series to load.
# Arguments
-- `sys::System`: system
-- `metadata_file::AbstractString`: metadata file for timeseries
- that includes an array of IS.TimeSeriesFileMetadata instances or a vector.
-- `resolution::DateTime.Period=nothing`: skip time series that don't match this resolution.
+- `sys::System`: The system to add the time series to.
+- `metadata_file::AbstractString`: Path to the metadata file.
+- `resolution::Union{Nothing, Dates.Period}`: (default: `nothing`) If provided, skip time
+ series that do not match this resolution.
+
+See also: [`add_time_series!(sys, file_metadata)`](@ref add_time_series!(
+ sys::System,
+ file_metadata::Vector{IS.TimeSeriesFileMetadata};
+ resolution,
+)), [`begin_time_series_update`](@ref)
"""
function add_time_series!(sys::System, metadata_file::AbstractString; resolution = nothing)
return IS.add_time_series_from_file_metadata!(
@@ -1122,12 +1279,22 @@ function add_time_series!(sys::System, metadata_file::AbstractString; resolution
end
"""
-Add time series data from a metadata file or metadata descriptors.
+ add_time_series!(sys::System, file_metadata::Vector{IS.TimeSeriesFileMetadata}; resolution)
+
+Add time series data from a vector of `IS.TimeSeriesFileMetadata` descriptors.
# Arguments
-- `sys::System`: system
-- `timeseries_metadata::Vector{IS.TimeSeriesFileMetadata}`: metadata for timeseries
-- `resolution::DateTime.Period=nothing`: skip time series that don't match this resolution.
+- `sys::System`: The system to add the time series to.
+- `file_metadata::Vector{IS.TimeSeriesFileMetadata}`: Metadata descriptors for the time
+ series to load.
+- `resolution::Union{Nothing, Dates.Period}`: (default: `nothing`) If provided, skip time
+ series that do not match this resolution.
+
+See also: [`add_time_series!(sys, metadata_file)`](@ref add_time_series!(
+ sys::System,
+ metadata_file::AbstractString;
+ resolution,
+)), [`begin_time_series_update`](@ref)
"""
function add_time_series!(
sys::System,
@@ -1190,7 +1357,7 @@ function IS.add_time_series_from_file_metadata_internal!(
end
"""
-Iterates over all components.
+Iterate over all components.
# Examples
```julia
@@ -1206,17 +1373,19 @@ function iterate_components(sys::System)
end
"""
+ clear_components!(sys::System)
+
Remove all components from the system.
+
+# Arguments
+- `sys::System`: The system to clear.
+
+See also: [`remove_component!`](@ref), [`remove_components!`](@ref)
"""
function clear_components!(sys::System)
return IS.clear_components!(sys.data)
end
-"""
-Remove all components of type T from the system.
-
-Throws ArgumentError if the type is not stored.
-"""
# the argument order in this function is un-julian and should be deprecated in 2.0
function remove_components!(::Type{T}, sys::System) where {T <: Component}
return remove_components!(sys, T)
@@ -1345,10 +1514,10 @@ has_component(T::Type{<:Component}, sys::System, name::AbstractString) =
has_component(sys, T, name)
"""
-Get the component of type T with name. Returns nothing if no component matches. If T is an abstract
+Return the component of type T with name. Returns nothing if no component matches. If T is an abstract
type then the names of components across all subtypes of T must be unique.
-See [`get_components_by_name`](@ref) for abstract types with non-unique names across subtypes.
+See also: [`get_components_by_name`](@ref) for abstract types with non-unique names across subtypes.
Throws ArgumentError if T is not a concrete type and there is more than one component with
requested name
@@ -1407,7 +1576,7 @@ function IS.get_components(sys::System, attribute::SupplementalAttribute)
end
"""
-Get the component by UUID.
+Return the component by UUID.
"""
IS.get_component(sys::System, uuid::Base.UUID) = IS.get_component(sys.data, uuid)
IS.get_component(sys::System, uuid::String) = IS.get_component(sys.data, Base.UUID(uuid))
@@ -1430,10 +1599,10 @@ function _get_components_by_name(abstract_types, data::IS.SystemData, name::Abst
end
"""
-Get the components of abstract type T with name. Note that PowerSystems enforces unique
+Return the components of abstract type T with name. Note that PowerSystems enforces unique
names on each concrete type but not across concrete types.
-See [`get_component`](@ref) if the concrete type is known.
+See also: [`get_component`](@ref) if the concrete type is known.
Throws ArgumentError if T is not an abstract type.
"""
@@ -1743,12 +1912,21 @@ function _get_buses(data::IS.SystemData, aggregator::T) where {T <: AggregationT
end
"""
-Add time series data to a component. Assign optional features to differentiate time series
-of the same type with the same name but with different data.
+ add_time_series!(sys::System, component::Component, time_series::TimeSeriesData; features...)
+
+Add time series data to a component. Optional `features` keyword arguments differentiate
+time series of the same type and name. Returns a key that can be passed to
+[`get_time_series`](@ref) to retrieve the data.
-Returns a key that can later be used to retrieve the time series data.
+# Arguments
+- `sys::System`: The system containing the component.
+- `component::Component`: The component to attach the time series to.
+- `time_series::TimeSeriesData`: The time series data to add.
+- `features...`: Optional keyword arguments (e.g. `scenario = "high"`) used to
+ distinguish multiple time series of the same type and name on the same component.
-Throws ArgumentError if the component is not stored in the system.
+# Throws
+- `ArgumentError`: if the component is not attached to the system.
# Examples
```julia
@@ -1772,6 +1950,13 @@ ts1_b = get_time_series(component, key1)
ts2_b = get_time_series(component, key2)
ts3_b = get_time_series(component, key3)
```
+
+See also: [`add_time_series!(sys, components, time_series)`](@ref add_time_series!(
+ sys::System,
+ components,
+ time_series::TimeSeriesData;
+ features...,
+)), [`begin_time_series_update`](@ref), [`get_time_series`](@ref)
"""
function add_time_series!(
sys::System,
@@ -1783,14 +1968,21 @@ function add_time_series!(
end
"""
-Add time series in bulk.
+ bulk_add_time_series!(sys::System, associations; batch_size)
+
+Add time series data to a system in bulk from an iterable of `IS.TimeSeriesAssociation`
+instances. Prefer [`begin_time_series_update`](@ref) for typical use cases; this function
+is intended for large datasets where explicit batch sizing offers additional control.
-Prefer use of [`begin_time_series_update`](@ref).
+# Arguments
+- `sys::System`: The system to add the time series to.
+- `associations`: An iterable of `IS.TimeSeriesAssociation` instances, each pairing a
+ component with its time series data.
+- `batch_size::Int`: (default: `IS.ADD_TIME_SERIES_BATCH_SIZE`) Number of associations
+ to process per batch.
# Examples
```julia
-# Assumes `read_time_series` will return data appropriate for Deterministic forecasts
-# based on the generator name and the filenames match the component and time series names.
resolution = Dates.Hour(1)
associations = (
IS.TimeSeriesAssociation(
@@ -1798,12 +1990,14 @@ associations = (
Deterministic(
data = read_time_series(get_name(gen) * ".csv"),
name = "get_max_active_power",
- resolution=resolution),
+ resolution = resolution),
)
for gen in get_components(ThermalStandard, sys)
)
bulk_add_time_series!(sys, associations)
```
+
+See also: [`begin_time_series_update`](@ref), [`add_time_series!`](@ref)
"""
function bulk_add_time_series!(
sys::System,
@@ -1814,14 +2008,28 @@ function bulk_add_time_series!(
end
"""
-Add the same time series data to multiple components.
+ add_time_series!(sys::System, components, time_series::TimeSeriesData; features...)
+
+Add the same time series data to multiple components. A single copy of the data is stored;
+each component holds a reference to it. This is significantly more efficient than calling
+[`add_time_series!`](@ref) individually for each component with identical data.
-This function stores a single copy of the data. Each component will store a reference to
-that data. This is significantly more efficent than calling `add_time_series!` for each
-component individually with the same data because in this case, only one time series
-array is stored.
+# Arguments
+- `sys::System`: The system containing the components.
+- `components`: An iterable of [`Component`](@ref) instances to attach the time series to.
+- `time_series::TimeSeriesData`: The time series data to add.
+- `features...`: Optional keyword arguments used to distinguish multiple time series of
+ the same type and name.
-Throws ArgumentError if a component is not stored in the system.
+# Throws
+- `ArgumentError`: if any component is not attached to the system.
+
+See also: [`add_time_series!(sys, component, time_series)`](@ref add_time_series!(
+ sys::System,
+ component::Component,
+ time_series::TimeSeriesData;
+ features...,
+)), [`begin_time_series_update`](@ref)
"""
function add_time_series!(sys::System, components, time_series::TimeSeriesData; features...)
return IS.add_time_series!(sys.data, components, time_series; features...)
@@ -1872,39 +2080,32 @@ Return the compression settings used for system data such as time series arrays.
get_compression_settings(sys::System) = IS.get_compression_settings(sys.data)
"""
-Return the initial times for all forecasts. Use `resolution` and/or `interval` keyword
-arguments to filter when multiple forecast groups exist.
+Return the initial times for all forecasts.
"""
-get_forecast_initial_times(sys::System; kwargs...) =
- IS.get_forecast_initial_times(sys.data; kwargs...)
+get_forecast_initial_times(sys::System) =
+ IS.get_forecast_initial_times(sys.data)
"""
-Return the window count for all forecasts. Use `resolution` and/or `interval` keyword
-arguments to filter when multiple forecast groups exist.
+Return the window count for all forecasts.
"""
-get_forecast_window_count(sys::System; kwargs...) =
- IS.get_forecast_window_count(sys.data; kwargs...)
+get_forecast_window_count(sys::System) =
+ IS.get_forecast_window_count(sys.data)
"""
-Return the horizon for all forecasts. Use `resolution` and/or `interval` keyword
-arguments to filter when multiple forecast groups exist.
+Return the horizon for all forecasts.
"""
-get_forecast_horizon(sys::System; kwargs...) =
- IS.get_forecast_horizon(sys.data; kwargs...)
+get_forecast_horizon(sys::System) = IS.get_forecast_horizon(sys.data)
"""
-Return the initial timestamp for all forecasts. Use `resolution` and/or `interval` keyword
-arguments to filter when multiple forecast groups exist.
+Return the initial timestamp for all forecasts.
"""
-get_forecast_initial_timestamp(sys::System; kwargs...) =
- IS.get_forecast_initial_timestamp(sys.data; kwargs...)
+get_forecast_initial_timestamp(sys::System) =
+ IS.get_forecast_initial_timestamp(sys.data)
"""
-Return the forecast interval. Use `resolution` and/or `interval` keyword arguments to
-select which forecast group to query when multiple exist.
+Return the forecast interval.
"""
-get_forecast_interval(sys::System; kwargs...) =
- IS.get_forecast_interval(sys.data; kwargs...)
+get_forecast_interval(sys::System) = IS.get_forecast_interval(sys.data)
"""
Return a sorted Vector of distinct resolutions for all time series of the given type
@@ -1957,19 +2158,29 @@ function IS.get_time_series_multiple(
type = type,
name = name,
resolution = resolution,
- interval = interval,
)
- put!(channel, time_series)
+ if isnothing(interval) || (
+ time_series isa IS.AbstractDeterministic &&
+ IS.get_interval(time_series) == interval
+ )
+ put!(channel, time_series)
+ end
end
end
end
end
"""
-Clear all time series data from the system.
+ clear_time_series!(sys::System)
+
+Remove all time series data from the system.
-If you are storing time series data in an HDF5 file, this will
-will delete the HDF5 file and create a new one.
+If time series is stored in an HDF5 file, this deletes the file and creates a new one.
+Consider using this instead of individual [`remove_time_series!`](@ref) calls when
+removing most or all time series, as HDF5 does not reclaim file space on removal.
+
+# Arguments
+- `sys::System`: The system whose time series data is cleared.
See also: [`remove_time_series!`](@ref remove_time_series!(sys::System, ::Type{T}) where {T <: TimeSeriesData})
"""
@@ -1980,8 +2191,8 @@ end
"""
Remove the time series data for a component or supplemental attribute and time series type.
-Use `resolution`, `interval`, and `features` keyword arguments to disambiguate when multiple
-time series of the same type and name exist with different resolutions, intervals, or
+Use `resolution` and `features` keyword arguments to disambiguate when multiple
+time series of the same type and name exist with different resolutions or
user-defined feature tags.
"""
function remove_time_series!(
@@ -1990,7 +2201,6 @@ function remove_time_series!(
owner::Union{Component, SupplementalAttribute},
name::String;
resolution::Union{Nothing, Dates.Period} = nothing,
- interval::Union{Nothing, Dates.Period} = nothing,
features...,
) where {T <: TimeSeriesData}
return IS.remove_time_series!(
@@ -1999,7 +2209,6 @@ function remove_time_series!(
owner,
name;
resolution = resolution,
- interval = interval,
features...,
)
end
@@ -2019,9 +2228,8 @@ function remove_time_series!(
sys::System,
::Type{T};
resolution::Union{Nothing, Dates.Period} = nothing,
- interval::Union{Nothing, Dates.Period} = nothing,
) where {T <: TimeSeriesData}
- return IS.remove_time_series!(sys.data, T; resolution = resolution, interval = interval)
+ return IS.remove_time_series!(sys.data, T; resolution = resolution)
end
"""
@@ -2033,10 +2241,8 @@ when actual forecasts are unavailable, without unnecessarily duplicating data.
If all `SingleTimeSeries` instances cannot be transformed then none will be.
-By default, any existing `DeterministicSingleTimeSeries` forecasts will be deleted before the
-transform (`delete_existing = true`). Set `delete_existing = false` to preserve existing
-`DeterministicSingleTimeSeries`; entries with matching name, resolution, features, horizon,
-and interval are skipped, allowing multiple calls with different resolutions to coexist.
+Any existing `DeterministicSingleTimeSeries` forecasts will be deleted before the transform
+when `delete_existing = true` (the default).
# Arguments
- `sys::System`: System containing the components.
@@ -2044,8 +2250,8 @@ and interval are skipped, allowing multiple calls with different resolutions to
- `interval::Dates.Period`: desired [interval](@ref I) between forecast [windows](@ref W)
- `resolution::Union{Nothing, Dates.Period} = nothing`: If set, only transform time series
with this resolution.
-- `delete_existing::Bool = true`: If `true`, delete all existing
- `DeterministicSingleTimeSeries` before transforming.
+- `delete_existing::Bool = true`: If `true`, delete any existing `DeterministicSingleTimeSeries`
+ before transforming.
"""
function transform_single_time_series!(
sys::System,
@@ -2066,8 +2272,21 @@ function transform_single_time_series!(
end
"""
-Add a supplemental attribute to the component. The attribute may already be attached to a
+ add_supplemental_attribute!(sys::System, component::Component, attribute::IS.SupplementalAttribute)
+
+Add a supplemental attribute to a component. The attribute may already be attached to a
different component.
+
+For [`PowerPlant`](@ref) subtypes ([`ThermalPowerPlant`](@ref), [`HydroPowerPlant`](@ref),
+[`RenewablePowerPlant`](@ref), [`CombinedCycleBlock`](@ref), [`CombinedCycleFractional`](@ref)),
+use the specialized overloads which additionally update the plant's internal topology maps.
+
+# Arguments
+- `sys::System`: The system containing the component.
+- `component::Component`: The component to attach the attribute to.
+- `attribute::IS.SupplementalAttribute`: The supplemental attribute to attach.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -2078,10 +2297,17 @@ function add_supplemental_attribute!(
end
"""
-Begin an update of supplemental attributes. Use this function when adding
-or removing many supplemental attributes in order to improve performance.
+ begin_supplemental_attributes_update(func::Function, sys::System)
+
+Batch supplemental attribute additions or removals within `func` to improve performance.
+When adding or removing many supplemental attributes, this avoids per-call overhead.
-If an error occurs during the update, changes will be reverted.
+If an error occurs during the update, all changes are reverted.
+
+# Arguments
+- `func::Function`: A zero-argument `do`-block containing
+ [`add_supplemental_attribute!`](@ref) or [`remove_supplemental_attribute!`](@ref) calls.
+- `sys::System`: The system to update.
# Examples
```julia
@@ -2090,13 +2316,28 @@ begin_supplemental_attributes_update(sys) do
add_supplemental_attribute!(sys, component2, attribute2)
end
```
+
+See also: [`add_supplemental_attribute!`](@ref), [`remove_supplemental_attribute!`](@ref)
"""
begin_supplemental_attributes_update(func::Function, sys::System) =
IS.begin_supplemental_attributes_update(func, sys.data.supplemental_attribute_manager)
"""
-Remove the supplemental attribute from the component. The attribute will be removed from the
-system if it is not attached to any other component.
+ remove_supplemental_attribute!(sys::System, component::Component, attribute::IS.SupplementalAttribute)
+
+Remove a supplemental attribute from a component. The attribute is removed from the system
+entirely if it is not attached to any other component.
+
+For [`PowerPlant`](@ref) subtypes ([`ThermalPowerPlant`](@ref), [`HydroPowerPlant`](@ref),
+[`RenewablePowerPlant`](@ref), [`CombinedCycleBlock`](@ref), [`CombinedCycleFractional`](@ref)),
+use the specialized overloads which additionally update the plant's internal topology maps.
+
+# Arguments
+- `sys::System`: The system containing the component.
+- `component::Component`: The component to detach the attribute from.
+- `attribute::IS.SupplementalAttribute`: The supplemental attribute to remove.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -2117,7 +2358,7 @@ function remove_supplemental_attributes!(
end
"""
-Returns an iterator of supplemental attributes. T can be concrete or abstract.
+Return an iterator of supplemental attributes. T can be concrete or abstract.
Call collect on the result if an array is desired.
# Examples
@@ -2149,19 +2390,12 @@ function get_supplemental_attributes(
end
"""
- get_associated_supplemental_attributes(obj)
-
-Retrieves supplemental attributes associated with the given object.
-
-This function extracts and returns additional metadata or auxiliary information
-that is linked to the specified object, typically used for extended functionality
-or configuration purposes.
+Return the supplemental attributes of type `attribute_type` associated with components of type `T`.
# Arguments
-- `obj`: The object for which to retrieve associated supplemental attributes
-
-# Returns
-- Collection of supplemental attributes associated with the input object
+- `sys::System`: The system to query.
+- `T::Type`: The component type to match.
+- `attribute_type`: Optional supplemental attribute type to filter by.
# Examples
```julia
@@ -2208,7 +2442,7 @@ function get_supplemental_attribute(sys::System, uuid::Base.UUID)
end
"""
-Iterates over all supplemental_attributes.
+Iterate over all supplemental attributes.
# Examples
```julia
@@ -2309,7 +2543,19 @@ Base.@deprecate validate_struct(sys::System, component::Component) validate_comp
IS.validate_struct(component::Component) = validate_component(component)
"""
-Check system consistency and validity.
+ check(sys::System)
+
+Run all consistency and validity checks on the system. Checks include bus slack
+assignment, bus connectivity, critical component presence, generation adequacy,
+and subsystem assignment consistency.
+
+# Arguments
+- `sys::System`: The system to validate.
+
+# Throws
+- `InvalidValue`: if any check fails.
+
+See also: [`check_component`](@ref), [`check_components`](@ref)
"""
function check(sys::System)
buses = get_components(ACBus, sys)
@@ -2322,28 +2568,25 @@ function check(sys::System)
end
"""
-Check the the consistency of subsystems.
-"""
-function check_subsystems(sys::System)
- must_be_assigned_to_subsystem = false
- for (i, component) in enumerate(iterate_components(sys))
- is_assigned = is_assigned_to_subsystem(sys, component)
- if i == 1
- must_be_assigned_to_subsystem = is_assigned
- elseif is_assigned != must_be_assigned_to_subsystem
- throw(
- IS.InvalidValue(
- "If any component is assigned to a subsystem then all " *
- "components must be assigned to a subsystem.",
- ),
- )
- end
- check_subsystems(sys, component)
- end
-end
+ check_components(sys::System; check_masked_components)
-"""
-Check the values of all components. See [`check_component`](@ref) for exceptions thrown.
+Check the field values of all components in the system.
+
+# Arguments
+- `sys::System`: The system whose components are checked.
+- `check_masked_components::Bool`: (default: `true`) Also check components that are
+ masked (e.g. dynamic injectors attached to static injectors).
+
+# Throws
+- `InvalidValue`: if any component's field values are outside the defined valid range
+ or its custom validation fails. See [`check_component`](@ref).
+- `InvalidValue`: if subsystem assignment is inconsistent across components.
+
+See also: [`check_component`](@ref), [`check`](@ref), [`check_components(sys, T)`](@ref check_components(
+ sys::System,
+ ::Type{T};
+ check_masked_components,
+) where {T <: Component})
"""
function check_components(sys::System; check_masked_components = true)
must_be_assigned_to_subsystem = false
@@ -2370,8 +2613,26 @@ function check_components(sys::System; check_masked_components = true)
end
"""
-Check the values of components of a given abstract or concrete type.
-See [`check_component`](@ref) for exceptions thrown.
+ check_components(sys::System, ::Type{T}; check_masked_components) where {T <: Component}
+
+Check the field values of all components of type `T` in the system.
+
+`T` can be a concrete or abstract [`Component`](@ref) type.
+
+# Arguments
+- `sys::System`: The system whose components are checked.
+- `T`: The component type to check. Can be concrete or abstract.
+- `check_masked_components::Bool`: (default: `true`) Also check masked components of
+ type `T`.
+
+# Throws
+- `InvalidValue`: if any component's field values are outside the defined valid range
+ or its custom validation fails. See [`check_component`](@ref).
+
+See also: [`check_component`](@ref), [`check_components(sys)`](@ref check_components(
+ sys::System;
+ check_masked_components,
+))
"""
function check_components(
sys::System,
@@ -2390,8 +2651,22 @@ function check_components(
end
"""
-Check the values of each component in an iterable of components.
-See [`check_component`](@ref) for exceptions thrown.
+ check_components(sys::System, components)
+
+Check the field values of each component in an iterable of components.
+
+# Arguments
+- `sys::System`: The system containing the components.
+- `components`: An iterable of [`Component`](@ref) instances to check.
+
+# Throws
+- `InvalidValue`: if any component's field values are outside the defined valid range
+ or its custom validation fails. See [`check_component`](@ref).
+
+See also: [`check_component`](@ref), [`check_components(sys)`](@ref check_components(
+ sys::System;
+ check_masked_components,
+))
"""
function check_components(sys::System, components)
for component in components
@@ -2400,10 +2675,19 @@ function check_components(sys::System, components)
end
"""
-Check the values of a component.
+ check_component(sys::System, component::Component)
+
+Check the field values of a single component.
-Throws InvalidValue if any of the component's field values are outside of defined valid
-range or if the custom validate method for the type fails its check.
+# Arguments
+- `sys::System`: The system containing the component.
+- `component::Component`: The component to validate.
+
+# Throws
+- `InvalidValue`: if any field value is outside the defined valid range or the
+ component's custom `validate_component_with_system` check fails.
+
+See also: [`check_components`](@ref), [`check`](@ref)
"""
function check_component(sys::System, component::Component)
if !validate_component_with_system(component, sys)
@@ -2417,7 +2701,7 @@ end
Check that all AC transmission [`Line`](@ref) and [`MonitoredLine`](@ref) components
have valid rate values relative to the system base power.
-Returns `true` if all values are valid, `false` otherwise.
+Return `true` if all values are valid, `false` otherwise.
"""
function check_ac_transmission_rate_values(sys::System)
is_valid = true
@@ -2432,7 +2716,7 @@ function check_ac_transmission_rate_values(sys::System)
end
"""
-Serialize a [System](@ref) instance. Returns a `Dict{String, Any}`
+Serialize a [`System`](@ref) instance. Return a `Dict{String, Any}`
of the form `Dict("data_format_version" => "1.0", "field1" => serialize(sys.field1), ...)`,
which can then be written to a JSON3 file.
"""
@@ -2452,7 +2736,7 @@ function IS.serialize(sys::T) where {T <: System}
end
"""
-Deserialize a [System](@ref) instance from a JSON3 file; the reverse of [`IS.serialize`](@ref).
+Deserialize a [`System`](@ref) instance from a JSON3 file; the reverse of [`IS.serialize`](@ref).
"""
function IS.deserialize(
::Type{System},
@@ -3248,8 +3532,23 @@ function _create_system_data_from_kwargs(;
end
"""
-Converts a Line component to a MonitoredLine component and replaces the original in the
-system
+ convert_component!(sys::System, line::Line, linetype::Type{MonitoredLine}; kwargs...)
+
+Convert a [`Line`](@ref) to a [`MonitoredLine`](@ref) in-place. The original component is
+removed and replaced with a new `MonitoredLine` that copies all fields. Time series data is
+preserved.
+
+# Arguments
+- `sys::System`: The system containing the component.
+- `line::Line`: The `Line` component to convert.
+- `linetype::Type{MonitoredLine}`: Must be `MonitoredLine`.
+
+See also: [`convert_component!(sys, line::MonitoredLine, linetype::Type{Line})`](@ref convert_component!(
+ sys::System,
+ line::MonitoredLine,
+ linetype::Type{Line};
+ kwargs...,
+))
"""
function convert_component!(
sys::System,
@@ -3286,8 +3585,28 @@ function convert_component!(
end
"""
-Converts a MonitoredLine component to a Line component and replaces the original in the
-system.
+ convert_component!(sys::System, line::MonitoredLine, linetype::Type{Line}; force, kwargs...)
+
+Convert a [`MonitoredLine`](@ref) to a [`Line`](@ref) in-place. The original component is
+removed and replaced with a new `Line`. Time series data is preserved. This conversion
+may result in data loss (monitoring-specific fields are dropped), so `force = true` must
+be passed explicitly to proceed.
+
+# Arguments
+- `sys::System`: The system containing the component.
+- `line::MonitoredLine`: The `MonitoredLine` component to convert.
+- `linetype::Type{Line}`: Must be `Line`.
+- `force::Bool`: (default: `false`) Must be set to `true` to confirm data loss is acceptable.
+
+# Throws
+- `ErrorException`: if `force = true` is not passed.
+
+See also: [`convert_component!(sys, line::Line, linetype::Type{MonitoredLine})`](@ref convert_component!(
+ sys::System,
+ line::Line,
+ linetype::Type{MonitoredLine};
+ kwargs...,
+))
"""
function convert_component!(
sys::System,
@@ -3332,8 +3651,25 @@ function convert_component!(
end
"""
-Converts a PowerLoad component to a StandardLoad component and replaces the original in the
-system. Does not set any fields in StandardLoad that lack a PowerLoad equivalent.
+ convert_component!(sys::System, old_load::PowerLoad, new_type::Type{StandardLoad}; kwargs...)
+
+Convert a [`PowerLoad`](@ref) to a [`StandardLoad`](@ref) in-place. The original component
+is removed and replaced with a new `StandardLoad`. Active and reactive power values are
+mapped to the constant power fields. Fields that have no `PowerLoad` equivalent (e.g.
+current and impedance power components) are left at their default values. Time series and
+services are preserved.
+
+# Arguments
+- `sys::System`: The system containing the component.
+- `old_load::PowerLoad`: The `PowerLoad` component to convert.
+- `new_type::Type{StandardLoad}`: Must be `StandardLoad`.
+
+See also: [`convert_component!(sys, line::Line, linetype)`](@ref convert_component!(
+ sys::System,
+ line::Line,
+ linetype::Type{MonitoredLine};
+ kwargs...,
+))
"""
function convert_component!(
sys::System,
@@ -3431,13 +3767,13 @@ function _validate_or_skip!(sys, component, skip_validation)
end
"""
-Returns counts of time series including attachments to components and supplemental
+Return counts of time series including attachments to components and supplemental
attributes.
"""
get_time_series_counts(sys::System) = IS.get_time_series_counts(sys.data)
"""
-Checks time series in the system for inconsistencies.
+Check time series in the system for inconsistencies.
For SingleTimeSeries, returns a Tuple of initial_timestamp and length.
diff --git a/src/component_selector_interface.jl b/src/component_selector_interface.jl
index abb48595e7..08feaa8e13 100644
--- a/src/component_selector_interface.jl
+++ b/src/component_selector_interface.jl
@@ -3,7 +3,7 @@
# get_components
"""
-Get the components of the [`System`](@ref) that make up the [`ComponentSelector`](@ref).
+Return the components of the [`System`](@ref) that make up the [`ComponentSelector`](@ref).
Optionally specify a filter function `scope_limiter` as the first argument to limit the
components that should be considered.
@@ -21,7 +21,7 @@ get_components(
IS.get_components(scope_limiter, selector, sys)
"""
-Get the components of the [`System`](@ref) that make up the [`ComponentSelector`](@ref).
+Return the components of the [`System`](@ref) that make up the [`ComponentSelector`](@ref).
# Arguments
@@ -33,7 +33,7 @@ get_components(selector::ComponentSelector, sys::System) =
# get_component
"""
-Get the component of the [`System`](@ref) that makes up the
+Return the component of the [`System`](@ref) that makes up the
[`SingularComponentSelector`](@ref); `nothing` if there is none. Optionally specify a filter
function `scope_limiter` as the first argument to limit the components that should be
considered.
@@ -53,7 +53,7 @@ get_component(
IS.get_component(scope_limiter, selector, sys)
"""
-Get the component of the [`System`](@ref) that makes up the
+Return the component of the [`System`](@ref) that makes up the
[`SingularComponentSelector`](@ref); `nothing` if there is none.
# Arguments
@@ -115,7 +115,7 @@ get_available_component(
# get_groups
"""
-Get the groups that make up the [`ComponentSelector`](@ref). Optionally specify a filter
+Return the groups that make up the [`ComponentSelector`](@ref). Optionally specify a filter
function `scope_limiter` as the first argument to limit the components that should be
considered.
@@ -133,7 +133,7 @@ get_groups(
IS.get_groups(scope_limiter, selector, sys)
"""
-Get the groups that make up the [`ComponentSelector`](@ref).
+Return the groups that make up the [`ComponentSelector`](@ref).
# Arguments
diff --git a/src/contingencies.jl b/src/contingencies.jl
index a49050ef13..cd310ec8ff 100644
--- a/src/contingencies.jl
+++ b/src/contingencies.jl
@@ -1,4 +1,6 @@
"""
+ Contingency
+
Supertype for contingency events that can be attached to components as supplemental
attributes.
diff --git a/src/definitions.jl b/src/definitions.jl
index 3b8773be3f..2c4e0da538 100644
--- a/src/definitions.jl
+++ b/src/definitions.jl
@@ -5,6 +5,19 @@ const FromTo = NamedTuple{(:from, :to), Tuple{Float64, Float64}}
const TurbinePump = NamedTuple{(:turbine, :pump), Tuple{Float64, Float64}}
# Exception to CamelCase convention for aliases due to confusssing reading of FromToToFrom
const FromTo_ToFrom = NamedTuple{(:from_to, :to_from), Tuple{Float64, Float64}}
+"""
+ StartUpStages
+
+`NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}` representing the start-up costs (\$)
+for a multi-start thermal generator at each temperature stage:
+
+- `hot`: cost when the unit is hot (shortest off-time)
+- `warm`: cost when the unit is warm (medium off-time)
+- `cold`: cost when the unit is cold (longest off-time)
+
+For single-stage generators, only the `hot` field is meaningful. See also
+[`single_start_up_to_stages`](@ref).
+"""
const StartUpStages = NamedTuple{(:hot, :warm, :cold), NTuple{3, Float64}}
# Intended for use with generators that are not multi-start (e.g. ThermalStandard).
@@ -24,49 +37,43 @@ IS.@scoped_enum(AngleUnits, DEGREES = 1, RADIANS = 2,)
@doc"
AngleUnits
-An enumeration of angular measurement units used throughout the PowerSystems package.
-
-Values
-- `DEGREES`: Angles expressed in degrees.
-- `RADIANS`: Angles expressed in radians.
-
-Usage
-Use `AngleUnits` to make unit semantics explicit for functions, fields, and APIs that accept or return angular values. When performing trigonometric calculations with Base functions (`sin`, `cos`, etc.), convert degrees to radians (e.g., `θ * π/180`) if the unit is `DEGREES`.
+Enumeration of angular measurement units used throughout PowerSystems.jl.
-Examples
-julia> unit = AngleUnits.DEGREES
-AngleUnits.DEGREES
+- `DEGREES` (1): Angles expressed in degrees.
+- `RADIANS` (2): Angles expressed in radians.
-julia> θ = 30.0
-julia> θ_rad = unit == AngleUnits.DEGREES ? θ * (π/180) : θ
+# Notes
+When performing trigonometric calculations with Julia's built-in functions (`sin`, `cos`,
+etc.), convert degrees to radians first (e.g., `θ * π/180`) if the unit is `DEGREES`.
" AngleUnits
IS.@scoped_enum(ACBusTypes, PQ = 1, PV = 2, REF = 3, ISOLATED = 4, SLACK = 5,)
@doc"
ACBusTypes
-Enumeration of AC power system bus types (MATPOWER Table B‑1).
+Enumeration of AC power system bus types (MATPOWER Table B-1).
Each variant corresponds to a standard bus classification used in power flow
-and steady‑state network models:
+and steady-state network models. Set on an [`ACBus`](@ref) via the `bustype` field.
-- PQ (1): Load bus — active (P) and reactive (Q) power injections are specified;
- the bus voltage magnitude and angle are solved by the power‑flow algorithm.
-- PV (2): Generator (PV) bus — active power (P) and voltage magnitude (V) are
+- `PQ` (1): Load bus — active (P) and reactive (Q) power injections are specified;
+ the bus voltage magnitude and angle are solved by the power-flow algorithm.
+- `PV` (2): Generator bus — active power (P) and voltage magnitude (V) are
specified; reactive power (Q) and voltage angle are solved.
-- REF (3): Reference bus — a named reference for the system voltage angle; often
- equivalent to a slack bus in semantics but provided separately for clarity.
-- ISOLATED (4): Isolated bus — not connected to the main network (islanded or
- disconnected); typically excluded from the global power‑flow solution.
-- SLACK (5): Slack bus — balances the system active and reactive power mismatch
- and sets the reference voltage angle (commonly one per connected network).
+- `REF` (3): Reference bus — provides a named reference for the system voltage
+ angle; often used interchangeably with `SLACK` but kept separate for clarity.
+- `ISOLATED` (4): Isolated bus — not connected to the main network; typically
+ excluded from the global power-flow solution.
+- `SLACK` (5): Slack bus — balances system active and reactive power mismatch and
+ sets the reference voltage angle (typically one per connected network).
-Notes
+# Notes
- Numeric values follow the MATPOWER convention for bus type codes.
- Use the enum members (e.g., `ACBusTypes.PQ`, `ACBusTypes.SLACK`) when
- constructing or interpreting network data structures to ensure clarity and
- compatibility with MATPOWER-based data conventions.
+ constructing or interpreting network data to ensure compatibility with
+ MATPOWER-based data conventions.
-Reference: MATPOWER manual, Table B‑1 (http://www.pserc.cornell.edu/matpower/MATPOWER-manual.pdf).
+# References
+- [MATPOWER manual, Table B-1](http://www.pserc.cornell.edu/matpower/MATPOWER-manual.pdf)
" ACBusTypes
IS.@scoped_enum(
@@ -75,23 +82,24 @@ IS.@scoped_enum(
CONFORMING = 1,
UNDEFINED = 2,
)
-@doc"""
- LoadConformity
+@doc"
+LoadConformity
WECC-defined enumeration for load conformity classification used in dynamic modeling.
Load conformity indicates whether a load follows system voltage and frequency variations
according to WECC modeling standards:
-- `NON_CONFORMING = 0`: Load that does not respond predictably to voltage and frequency changes,
- typically representing constant power loads or loads with complex control systems
-- `CONFORMING = 1`: Load that responds predictably to voltage and frequency variations,
- following standard load modeling practices for dynamic studies
-- `UNDEFINED = 2`: Load conformity status is not specified or unknown
+- `NON_CONFORMING` (0): Load that does not respond predictably to voltage and frequency
+ changes, typically representing constant power loads or loads with complex controls.
+- `CONFORMING` (1): Load that responds predictably to voltage and frequency variations,
+ following standard load modeling practices for dynamic studies.
+- `UNDEFINED` (2): Load conformity status is not specified or unknown.
-This classification is essential for WECC dynamic studies as it determines how loads are
-modeled during system disturbances and stability analysis.
-""" LoadConformity
+# See Also
+- [`MotorLoadTechnology`](@ref): Related enumeration for motor load technology
+ classification.
+" LoadConformity
# "From PSSE POM v33 Manual"
IS.@scoped_enum(
@@ -101,16 +109,18 @@ IS.@scoped_enum(
BYP = 2, # Series link is bypassed (i.e., like a zero impedance line) and Shunt link operates as a STATCOM.
)
@doc"
- FACTSOperationModes
+FACTSOperationModes
-Enumeration defining the operational modes for FACTS (Flexible AC Transmission System) devices.
-Based on PSSE POM v33 Manual specifications.
+Enumeration of operational modes for FACTS (Flexible AC Transmission System) devices,
+as defined in the PSS/E POM v33 Manual.
-# Values
-- `OOS = 0`: Out-of-service mode where both Series and Shunt links are open
-- `NML = 1`: Normal mode of operation where both Series and Shunt links are operating
-- `BYP = 2`: Bypass mode where Series link is bypassed (acts like zero impedance line)
- and Shunt link operates as a STATCOM
+- `OOS` (0): Out-of-service — both Series and Shunt links are open.
+- `NML` (1): Normal operation — both Series and Shunt links are active.
+- `BYP` (2): Bypass mode — Series link is bypassed (acts as a zero-impedance line)
+ and Shunt link operates as a STATCOM.
+
+# References
+- PSS/E Power Operations Manual v33, FACTS device specification.
" FACTSOperationModes
IS.@scoped_enum(
@@ -120,14 +130,19 @@ IS.@scoped_enum(
OTHER = 2,
)
@doc"
- DiscreteControlledBranchType
+DiscreteControlledBranchType
-An enumeration representing different types of discrete controlled branches in power systems.
+Enumeration of discrete controlled branch device types.
-# Values
-- `SWITCH = 0`: Represents a switch device that can be opened or closed
-- `BREAKER = 1`: Represents a circuit breaker that can interrupt current flow
-- `OTHER = 2`: Represents other types of discrete controlled branch devices
+- `SWITCH` (0): Switching device that can be opened or closed to connect or isolate a
+ circuit segment.
+- `BREAKER` (1): Circuit breaker capable of interrupting fault current.
+- `OTHER` (2): Other discrete branch device not covered by the above categories.
+
+# See Also
+- [`DiscreteControlledACBranch`](@ref): Branch type that uses this enumeration.
+- [`DiscreteControlledBranchStatus`](@ref): Enumeration of the open/closed status for
+ these devices.
" DiscreteControlledBranchType
IS.@scoped_enum(
@@ -138,18 +153,15 @@ IS.@scoped_enum(
@doc"
DiscreteControlledBranchStatus
-Enumeration describing the controlled (commanded) status of a branch device
-(such as a breaker or a switch) in a power system model.
+Enumeration describing the controlled (commanded) status of a branch device such as a
+breaker or switch. Used with [`DiscreteControlledACBranch`](@ref).
-Values
-- OPEN = 0: The device is open (interrupting state) — the branch is non-conducting.
-- CLOSED = 1: The device is closed (conducting state) — the branch provides a normal conduction path.
+- `OPEN` (0): The device is open (non-conducting).
+- `CLOSED` (1): The device is closed (conducting).
-Notes
-- This enum represents the intended or commanded state used by control and protection
- logic; it may differ from actual measured/telemetry state during faults or failures.
-- The integer encoding (0/1) is chosen for compact storage and interop with serialization
- or external data formats.
+# Notes
+Represents the intended or commanded state used by control and protection logic; it may
+differ from the actual measured/telemetry state during faults or failures.
" DiscreteControlledBranchStatus
IS.@scoped_enum(
@@ -160,19 +172,23 @@ IS.@scoped_enum(
TERTIARY_WINDING = 3, # Tertiary winding of Trasnformer3W associated with a TICT
)
@doc"
- WindingCategory
+WindingCategory
-An enumeration representing different types of transformer windings used in power system analysis.
-Reflects how to interpret the Transformer Impedance Correction Table (TICT) winding association as described in [`ImpedanceCorrectionData`](@ref).
+Enumeration of transformer winding roles used to interpret a
+[`ImpedanceCorrectionData`](@ref) (Transformer Impedance Correction Table) association.
-# Values
-- `TR2W_WINDING = 0`: Winding associated with a two-winding transformer (Transformer2W) connected to a tap-changing transformer's [`ImpedanceCorrectionData`](@ref)
-- `PRIMARY_WINDING = 1`: Primary winding of a three-winding transformer (Transformer3W) associated with a [`ImpedanceCorrectionData`](@ref)
-- `SECONDARY_WINDING = 2`: Secondary winding of a three-winding transformer (Transformer3W) associated with a [`ImpedanceCorrectionData`](@ref)
-- `TERTIARY_WINDING = 3`: Tertiary winding of a three-winding transformer (Transformer3W) associated with a [`ImpedanceCorrectionData`](@ref)
+- `TR2W_WINDING` (0): The winding of a two-winding transformer connected to an
+ [`ImpedanceCorrectionData`](@ref).
+- `PRIMARY_WINDING` (1): Primary winding of a three-winding transformer connected to an
+ [`ImpedanceCorrectionData`](@ref).
+- `SECONDARY_WINDING` (2): Secondary winding of a three-winding transformer connected to
+ an [`ImpedanceCorrectionData`](@ref).
+- `TERTIARY_WINDING` (3): Tertiary winding of a three-winding transformer connected to an
+ [`ImpedanceCorrectionData`](@ref).
-This enumeration is used to categorize transformer windings based on their role and configuration
-in the power system model, particularly in relation to tap-changing transformers.
+# See Also
+- [`ImpedanceCorrectionTransformerControlMode`](@ref): Enumeration of control modes used
+ alongside winding impedance corrections.
" WindingCategory
IS.@scoped_enum(
@@ -186,25 +202,26 @@ IS.@scoped_enum(
GROUP_11 = 11, # 30 Degrees
)
@doc"
- WindingGroupNumber
+WindingGroupNumber
-Enumeration defining transformer winding group numbers based on IEC 60076-1 standard.
-These numbers represent the phase displacement between primary and secondary windings
-of three-phase transformers.
+Enumeration of transformer winding group numbers representing the phase displacement
+between primary and secondary windings of three-phase transformers, per IEC 60076-1.
-# Valid Values
-- `UNDEFINED = -99`: Undefined or unspecified winding group
-- `GROUP_0 = 0`: 0° phase displacement (Yy0, Dd0, Dz0)
-- `GROUP_1 = 1`: -30° phase displacement (Yy1, Dd1, Dz1)
-- `GROUP_5 = 5`: -150° phase displacement (Yy5, Dd5, Dz5)
-- `GROUP_6 = 6`: 180° phase displacement (Yy6, Dd6, Dz6)
-- `GROUP_7 = 7`: 150° phase displacement (Yy7, Dd7, Dz7)
-- `GROUP_11 = 11`: 30° phase displacement (Yy11, Dd11, Dz11)
+- `UNDEFINED` (-99): Winding group not specified.
+- `GROUP_0` (0): 0° phase displacement (e.g., Yy0, Dd0, Dz0).
+- `GROUP_1` (1): −30° phase displacement (e.g., Dy1, Yd1, Yz1).
+- `GROUP_5` (5): −150° phase displacement (e.g., Dy5, Yd5, Yz5).
+- `GROUP_6` (6): 180° phase displacement (e.g., Yy6, Dd6, Dz6).
+- `GROUP_7` (7): 150° phase displacement (e.g., Dy7, Yd7, Yz7).
+- `GROUP_11` (11): 30° phase displacement (e.g., Dy11, Yd11, Yz11).
# Notes
-The phase displacement is measured from the primary to secondary winding, with
-positive angles representing a lead and negative angles representing a lag.
-Clock notation follows the convention where each hour represents 30°.
+- Phase displacement is measured from primary to secondary winding; positive angles
+ lead and negative angles lag.
+- Clock notation: each clock hour represents 30°.
+
+# References
+- IEC 60076-1: Power transformers — General.
" WindingGroupNumber
IS.@scoped_enum(
@@ -212,25 +229,20 @@ IS.@scoped_enum(
PHASE_SHIFT_ANGLE = 1,
TAP_RATIO = 2,
)
-@doc"""
- ImpedanceCorrectionTransformerControlMode
+@doc"
+ImpedanceCorrectionTransformerControlMode
-Enumeration defining the control modes for impedance correction in transformers,
-based on PSS/E transformer control definitions.
+Enumeration of control modes for impedance correction in transformers, as defined
+in the PSS/E transformer control specifications.
-# Values
-- `PHASE_SHIFT_ANGLE = 1`: Control mode for phase-shifting transformers where the
- impedance correction is applied based on the phase shift angle. Used when the
- transformer primarily controls power flow through phase angle adjustment.
-- `TAP_RATIO = 2`: Control mode for tap-changing transformers where the impedance
- correction is applied based on the tap ratio. Used when the transformer primarily
- controls voltage magnitude through tap position changes.
+- `PHASE_SHIFT_ANGLE` (1): Impedance correction is applied as a function of the phase
+ shift angle. Used for phase-shifting transformers that control active power flow.
+- `TAP_RATIO` (2): Impedance correction is applied as a function of the tap ratio.
+ Used for tap-changing transformers that control voltage magnitude.
-# Notes
-This enumeration corresponds to PSS/E transformer control field definitions for
-determining how impedance corrections are calculated and applied in power flow
-and dynamic simulation studies.
-""" ImpedanceCorrectionTransformerControlMode
+# See Also
+- [`ImpedanceCorrectionData`](@ref): Supplemental attribute that uses this control mode.
+" ImpedanceCorrectionTransformerControlMode
IS.@scoped_enum(
TransformerControlObjective, # COD1 or COD2 in PSS\e
@@ -282,14 +294,18 @@ IS.@scoped_enum(
UNDETERMINED = 3,
)
@doc"
- MotorLoadTechnology
+MotorLoadTechnology
-An enumeration representing different motor load technologies used in industrial applications.
+Enumeration of motor load technology types used in power system dynamic load modeling.
-# Values
-- `INDUCTION`: Induction motor technology, commonly used for general-purpose applications
-- `SYNCHRONOUS`: Synchronous motor technology, used for applications requiring constant speed
-- `UNDETERMINED`: Motor technology type is not specified or unknown
+- `INDUCTION` (1): Induction motor, commonly used for general-purpose industrial
+ applications.
+- `SYNCHRONOUS` (2): Synchronous motor, used for constant-speed or power-factor
+ correction applications.
+- `UNDETERMINED` (3): Motor technology type is not specified or unknown.
+
+# See Also
+- [`LoadConformity`](@ref): Related enumeration for load conformity classification.
" MotorLoadTechnology
IS.@scoped_enum(
@@ -319,23 +335,24 @@ IS.@scoped_enum(
WS = 23, # Wind Turbine, Offshore
)
@doc"
- PrimeMovers
+PrimeMovers
Enumeration of prime mover types used in electric power generation, as defined by the
U.S. Energy Information Administration (EIA) Form 923 instructions.
Prime movers are the engines, turbines, water wheels, or similar machines that drive
-electric generators or provide mechanical energy for other purposes. This enumeration
-provides standardized codes for different types of prime movers used in power plants.
+electric generators or provide mechanical energy for other purposes.
-PVe is used for photovoltaic systems renaming from EIA PV to avoid conflict with BusType.PV
+# Notes
+`PVe` is used for photovoltaic systems, renamed from the EIA code `PV` to avoid a
+naming conflict with [`ACBusTypes`](@ref) `PV`.
# References
- [EIA Form 923 Instructions](https://www.eia.gov/survey/form/eia_923/instructions.pdf)
# See Also
-- [`ThermalStandard`](@ref): Uses prime mover information for generator specifications
-- [`ThermalMultiStart`](@ref): Uses prime mover information for generator specifications
+- [`ThermalStandard`](@ref): Uses prime mover information for generator specifications.
+- [`ThermalMultiStart`](@ref): Uses prime mover information for generator specifications.
" PrimeMovers
IS.@scoped_enum(
@@ -377,23 +394,25 @@ IS.@scoped_enum(
)
@doc"
- ThermalFuels
+ThermalFuels
+
+Enumeration of thermal fuel types, using EIA Form 923 fuel codes for standardized
+reporting of fuel consumption in electric power generation.
-Enumeration of thermal fuel types based on AER (Aggregated Energy Reporting) fuel codes
-as defined by the U.S. Energy Information Administration (EIA) Form 923.
+Categories include: coal and coal-derived fuels, petroleum products, natural gas, nuclear,
+biomass and waste-derived fuels, geothermal, and other thermal energy sources.
-The fuel codes represent standardized categories for reporting fuel consumption in
-electric power generation, covering major thermal fuel types including:
+# Notes
+`COAL` (general coal) and `GEOTHERMAL` codes are not directly from the current EIA 923
+form but are retained for compatibility with older data.
-- Coal and coal-derived fuels
-- Natural gas and petroleum products
-- Nuclear fuel
-- Biomass and waste fuels
-- Other thermal energy sources
+# References
+- [EIA Form 923 Instructions](https://www.eia.gov/survey/form/eia_923/instructions.pdf)
-Reference: EIA Form 923 Instructions (https://www.eia.gov/survey/form/eia_923/instructions.pdf)
-General Coal and Geothermal codes not directly from the current EIA 923 form but kept for compatibility with older versions of the form.
-See also: [`ThermalStandard`](@ref)
+# See Also
+- [`ThermalStandard`](@ref): Generator type that uses this fuel enumeration.
+- [`ThermalMultiStart`](@ref): Generator type that uses this fuel enumeration.
+- [`PrimeMovers`](@ref): Companion enumeration for generator prime mover type.
" ThermalFuels
IS.@scoped_enum(
@@ -411,25 +430,24 @@ IS.@scoped_enum(
OTHER_THERM = 11, # Thermal Storage
)
@doc"
- StorageTech
+StorageTech
+
+Enumeration of energy storage technologies used in power system modeling.
+
+- `PTES` (1): Pumped thermal energy storage.
+- `LIB` (2): Lithium-ion battery.
+- `LAB` (3): Lead-acid battery.
+- `FLWB` (4): Redox flow battery.
+- `SIB` (5): Sodium-ion battery.
+- `ZIB` (6): Zinc-ion battery.
+- `HGS` (7): Hydrogen gas storage.
+- `LAES` (8): Liquid air energy storage.
+- `OTHER_CHEM` (9): Other chemical storage technologies.
+- `OTHER_MECH` (10): Other mechanical storage technologies.
+- `OTHER_THERM` (11): Other thermal storage technologies.
-Enumeration of energy storage technologies used in power systems.
-
-# Values
-- `PTES`: Pumped thermal energy storage
-- `LIB`: Lithium-ion Battery
-- `LAB`: Lead Acid Battery
-- `FLWB`: Redox Flow Battery
-- `SIB`: Sodium Ion Battery
-- `ZIB`: Zinc Ion Battery
-- `HGS`: Hydrogen Gas Storage
-- `LAES`: Liquid Air Energy Storage
-- `OTHER_CHEM`: Chemical Storage (other than specified)
-- `OTHER_MECH`: Mechanical Storage (other than specified)
-- `OTHER_THERM`: Thermal Storage (other than specified)
-
-This enumeration is used to classify different types of energy storage systems
-based on their underlying technology and storage mechanism.
+# See Also
+- [`EnergyReservoirStorage`](@ref): Storage component using this enumeration.
" StorageTech
IS.@scoped_enum(
@@ -454,6 +472,21 @@ Notes
" PumpHydroStatus
IS.@scoped_enum(StateTypes, Differential = 1, Algebraic = 2, Hybrid = 3,)
+@doc"
+StateTypes
+
+Enumeration of state variable types for dynamic components.
+
+- `Differential` (1): State governed by a differential equation (evolves continuously
+ in time).
+- `Algebraic` (2): State determined by an algebraic constraint (no time derivative).
+- `Hybrid` (3): State that can behave as either differential or algebraic depending on
+ operating conditions.
+
+# See Also
+- [`DynamicComponent`](@ref): Abstract base type whose states are classified by this
+ enumeration.
+" StateTypes
@doc """
Categorization of dynamic state variables.
@@ -474,17 +507,16 @@ IS.@scoped_enum(
@doc"
ReservoirDataType
-Enumeration of reservoir accounting unit classes.
+Enumeration of the quantity type used to represent the state of a [`HydroReservoir`](@ref).
-This enum identifies the type of data recorded or tracked for a reservoir. Use these values when specifying
-the kind of measurement or accounting quantity associated with a reservoir (for example in time series,
-storage models, reporting, or data exchange).
+- `USABLE_VOLUME` (1): Volume available for operations and dispatch (active storage),
+ typically in cubic meters (m³).
+- `TOTAL_VOLUME` (2): Total reservoir volume including dead and active storage, in m³.
+- `HEAD` (3): Hydraulic head or water surface elevation relative to a datum, in meters (m).
+- `ENERGY` (4): Stored or deliverable energy associated with the reservoir, in MWh or GWh.
-Values
-- USABLE_VOLUME: Volume available for operations and dispatch (active storage). Typically reported in cubic meters (m³) or other volumetric units.
-- TOTAL_VOLUME: Total reservoir volume including dead and active storage. Reported in the same volumetric units as USABLE_VOLUME.
-- HEAD: Hydraulic head or water surface elevation relative to a datum, typically reported in meters (m).
-- ENERGY: Stored or deliverable energy associated with the reservoir (e.g., potential energy or expected generation), often expressed in MWh, GWh, or joules.
+# See Also
+- [`ReservoirLocation`](@ref): Enumeration of reservoir location relative to the turbine.
" ReservoirDataType
IS.@scoped_enum(
@@ -501,26 +533,24 @@ IS.@scoped_enum(
OTHER = 9 # Catch-all for less common designs
)
@doc"
- HydroTurbineType
-
-Enumeration of hydro turbine types supported in `PowerSystems.jl`.
+HydroTurbineType
+
+Enumeration of hydroelectric turbine designs, used to classify hydro generating units
+by operating head range and flow characteristics.
+
+- `UNKNOWN` (0): Turbine type is not specified.
+- `PELTON` (1): Impulse turbine for high-head, low-flow sites.
+- `FRANCIS` (2): Reaction turbine, widely used for medium-head applications.
+- `KAPLAN` (3): Adjustable-blade propeller turbine for low-head, high-flow sites.
+- `TURGO` (4): Impulse turbine similar to Pelton but suitable for higher flow rates.
+- `CROSSFLOW` (5): Banki-Michell (crossflow) impulse turbine, robust for small hydro.
+- `BULB` (6): Compact Kaplan variant for low-head run-of-river plants.
+- `DERIAZ` (7): Diagonal flow reaction turbine with variable pitch blades.
+- `PROPELLER` (8): Fixed-blade propeller turbine.
+- `OTHER` (9): Placeholder for less common or custom turbine designs.
-This type is used to categorize hydroelectric generators by their
-turbine design and operating head. It provides a standardized set
-of turbine types to ensure consistent modeling and data handling
-across different systems.
-
-# Values
-- `UNKNOWN` : Default value when the turbine type is not specified.
-- `PELTON` : Impulse turbine, typically used for high-head, low-flow sites.
-- `FRANCIS` : Reaction turbine, widely used for medium-head applications.
-- `KAPLAN` : Adjustable-blade propeller turbine for low-head, high-flow sites.
-- `TURGO` : Impulse turbine similar to Pelton but suitable for higher flow rates.
-- `CROSSFLOW` : Banki-Michell (crossflow) impulse turbine, robust for small hydro.
-- `BULB` : Compact Kaplan variant, typically installed in low-head run-of-river plants.
-- `DERIAZ` : Diagonal flow reaction turbine with variable pitch blades.
-- `PROPELLER` : Fixed-blade propeller turbine, simpler than Kaplan but less efficient at part load.
-- `OTHER` : Placeholder for less common or custom turbine designs.
+# See Also
+- [`HydroTurbine`](@ref): Hydro generator component using this enumeration.
" HydroTurbineType
IS.@scoped_enum(
@@ -531,12 +561,15 @@ IS.@scoped_enum(
@doc"
ReservoirLocation
-Enumeration representing the location of a hydro reservoir relative to its associated turbine.
+Enumeration representing the location of a [`HydroReservoir`](@ref) relative to its
+associated turbine unit.
-# Values
-- `HEAD`: The reservoir is located upstream of the turbine, typically at a higher elevation.
-- `TAIL`: The reservoir is located downstream of the turbine at a lower or same elevation.
+- `HEAD` (1): The reservoir is located upstream of the turbine (higher elevation).
+- `TAIL` (2): The reservoir is located downstream of the turbine (lower elevation).
+# See Also
+- [`ReservoirDataType`](@ref): Enumeration of the quantity used to represent reservoir
+ state.
" ReservoirLocation
IS.@scoped_enum(
@@ -547,6 +580,21 @@ IS.@scoped_enum(
TripleCombustionOneSteam = 4,
Other = 5,
)
+@doc"
+ CombinedCycleConfiguration
+
+Enumeration describing the physical layout of a combined cycle power plant.
+
+- `SingleShaftCombustionSteam` (1): Single combustion turbine on a common shaft with one steam turbine.
+- `SeparateShaftCombustionSteam` (2): One combustion turbine and one steam turbine on separate shafts.
+- `DoubleCombustionOneSteam` (3): Two combustion turbines exhausting into one steam turbine.
+- `TripleCombustionOneSteam` (4): Three combustion turbines exhausting into one steam turbine.
+- `Other` (5): Any other combined cycle configuration not covered by the above values.
+
+# See Also
+- [`CombinedCycleBlock`](@ref): Plant attribute for combined cycle block-level configurations.
+- [`CombinedCycleFractional`](@ref): Plant attribute for combined cycle fractional configurations.
+" CombinedCycleConfigurationModule.CombinedCycleConfiguration
@doc """
Configuration types for combined cycle power plants.
diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json
index 1d8f761d0f..650541f0fb 100644
--- a/src/descriptors/power_system_structs.json
+++ b/src/descriptors/power_system_structs.json
@@ -6056,7 +6056,7 @@
},
{
"struct_name": "RenewableDispatch",
- "docstring": "A renewable (e.g., wind or solar) generator whose output can be curtailed to satisfy power system constraints.\n\nThese generators can also participate in reserves markets, including upwards reserves by proactively curtailing some available power (based on its [`max_active_power` time series](@ref ts_data)). Example uses include: a utility-scale wind or solar generator whose PPA allows curtailment. For non-curtailable or must-take renewables, see [`RenewableNonDispatch`](@ref).\n\nRenewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::T) where {T <: RenewableGen})",
+ "docstring": "A renewable (e.g., wind or solar) generator whose output can be curtailed to satisfy power system constraints.\n\nThese generators can also participate in reserves markets, including upwards reserves by proactively curtailing some available power (based on its [`max_active_power` time series](@ref ts_data)). Example uses include: a utility-scale wind or solar generator whose PPA allows curtailment. For non-curtailable or must-take renewables, see [`RenewableNonDispatch`](@ref).\n\nRenewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::RenewableGen))",
"fields": [
{
"null_value": "init",
@@ -6182,7 +6182,7 @@
},
{
"struct_name": "RenewableNonDispatch",
- "docstring": "A non-dispatchable (i.e., non-curtailable or must-take) renewable generator.\n\nIts output is equal to its [`max_active_power` time series](@ref ts_data) by default. Example use: an aggregation of behind-the-meter distributed energy resources like rooftop solar. For curtailable or downward dispatachable generation, see [`RenewableDispatch`](@ref).\n\nRenewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::T) where {T <: RenewableGen})",
+ "docstring": "A non-dispatchable (i.e., non-curtailable or must-take) renewable generator.\n\nIts output is equal to its [`max_active_power` time series](@ref ts_data) by default. Example use: an aggregation of behind-the-meter distributed energy resources like rooftop solar. For curtailable or downward dispatachable generation, see [`RenewableDispatch`](@ref).\n\nRenewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::RenewableGen))",
"fields": [
{
"null_value": "init",
@@ -7845,7 +7845,7 @@
{
"name": "states",
"exclude_setter": true,
- "comment": "(**Do not modify.**) The [states](@ref S) are:\tVf: Voltage field,\tVr: Lead-lag state",
+ "comment": "(**Do not modify.**) The [states](@ref S) are:\n\tVf: Voltage field,\n\tVr: Lead-lag state",
"internal_default": "[:Vf, :Vr]",
"data_type": "Vector{Symbol}"
},
diff --git a/src/get_components_interface.jl b/src/get_components_interface.jl
index 48864a4f44..01013622c4 100644
--- a/src/get_components_interface.jl
+++ b/src/get_components_interface.jl
@@ -34,10 +34,18 @@
# get_components
"""
-Return an iterator of components of a given `Type` from a [`System`](@ref).
+ get_components(::Type{T}, sys::System; subsystem_name) where {T <: Component}
+
+Return an iterator of components of type `T` from a [`System`](@ref).
`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref).
-Call collect on the result if an array is desired.
+Call `collect` on the result if an array is desired.
+
+# Arguments
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `subsystem_name::Union{Nothing, String}`: (default: `nothing`) If provided, restrict
+ results to the named subsystem.
# Examples
```julia
@@ -58,14 +66,22 @@ get_components(::Type{T}, sys::System; subsystem_name = nothing) where {T <: Com
IS.get_components(T, sys; subsystem_name = subsystem_name)
"""
-Return a vector of components that are attached to the supplemental attribute.
+ get_associated_components(sys::System, attribute::SupplementalAttribute; component_type)
+
+Return a vector of components attached to the given supplemental attribute.
# Arguments
-- `sys::System`: the `System` to search
-- `attribute::SupplementalAttribute`: Only return components associated with this attribute.
-- `component_type::Union{Nothing, <:Component}`: Optional type of the components to return.
- Can be concrete or abstract. If not provided, all components associated with the attribute
- will be returned.
+- `sys::System`: The system to search.
+- `attribute::SupplementalAttribute`: The supplemental attribute whose associated components
+ are returned.
+- `component_type::Union{Nothing, Type{<:Component}}`: (default: `nothing`) If provided,
+ only return components of this type. Can be concrete or abstract.
+
+See also: [`get_associated_components(sys, attribute_type)`](@ref get_associated_components(
+ sys::System,
+ attribute_type::Type{<:SupplementalAttribute};
+ component_type,
+)), [`add_supplemental_attribute!`](@ref)
"""
function get_associated_components(
sys::System,
@@ -85,8 +101,23 @@ end
)
"""
-Return a vector of components that are associated to one or more supplemental attributes of
-the given type.
+ get_associated_components(sys::System, attribute_type::Type{<:SupplementalAttribute}; component_type)
+
+Return a vector of components that have at least one supplemental attribute of
+`attribute_type` attached.
+
+# Arguments
+- `sys::System`: The system to search.
+- `attribute_type::Type{<:SupplementalAttribute}`: The supplemental attribute type to
+ filter by. Can be concrete or abstract.
+- `component_type::Union{Nothing, Type{<:Component}}`: (default: `nothing`) If provided,
+ only return components of this type. Can be concrete or abstract.
+
+See also: [`get_associated_components(sys, attribute)`](@ref get_associated_components(
+ sys::System,
+ attribute::SupplementalAttribute;
+ component_type,
+)), [`add_supplemental_attribute!`](@ref)
"""
function get_associated_components(
sys::System,
@@ -101,11 +132,21 @@ function get_associated_components(
end
"""
-Return an iterator of components of a given `Type` from a [`System`](@ref), using an
-additional filter
+ get_components(filter_func::Function, ::Type{T}, sys::System; subsystem_name) where {T <: Component}
+
+Return an iterator of components of type `T` from a [`System`](@ref) that satisfy
+`filter_func`.
`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref).
-Call collect on the result if an array is desired.
+Call `collect` on the result if an array is desired.
+
+# Arguments
+- `filter_func::Function`: A single-argument function returning `true` for components to
+ include.
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `subsystem_name::Union{Nothing, String}`: (default: `nothing`) If provided, restrict
+ results to the named subsystem.
# Examples
```julia
@@ -131,32 +172,74 @@ get_components(
# get_component
"""
-Get the component by UUID.
+ get_component(sys::System, uuid::Union{Base.UUID, String})
+
+Return the component with the given UUID, or `nothing` if not found.
+
+# Arguments
+- `sys::System`: The system to search.
+- `uuid::Union{Base.UUID, String}`: The UUID of the component.
+
+See also: [`get_component(T, sys, name)`](@ref get_component(
+ ::Type{T},
+ sys::System,
+ name::AbstractString,
+) where {T <: Component})
"""
get_component(sys::System, uuid::Base.UUID) = IS.get_component(sys, uuid)
get_component(sys::System, uuid::String) = IS.get_component(sys, uuid)
"""
-Get the component of type T with name. Returns nothing if no component matches. If T is an abstract
-type then the names of components across all subtypes of T must be unique.
+ get_component(::Type{T}, sys::System, name::AbstractString) where {T <: Component}
+
+Return the component of type `T` with the given name, or `nothing` if no match is found.
+
+If `T` is an abstract type, names must be unique across all subtypes. Use
+[`get_components_by_name`](@ref) when names are not unique across subtypes.
-See [`get_components_by_name`](@ref) for abstract types with non-unique names across subtypes.
+# Arguments
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `name::AbstractString`: The name of the component.
+
+# Throws
+- `ArgumentError`: if `T` is abstract and more than one component with the given name
+ exists across subtypes.
-Throws ArgumentError if T is not a concrete type and there is more than one component with
- requested name
+See also: [`get_component(sys, uuid)`](@ref get_component(sys::System, uuid::Base.UUID)),
+[`get_components_by_name`](@ref)
"""
get_component(::Type{T}, sys::System, name::AbstractString) where {T <: Component} =
IS.get_component(T, sys, name)
# get_available_components
"""
-Like [`get_components`](@ref get_components(
+ get_available_components(::Type{T}, sys::System; subsystem_name) where {T <: Component}
+
+Return an iterator of available components of type `T` from a [`System`](@ref). A component
+is available when [`get_available`](@ref) returns `true`. Equivalent to
+[`get_components`](@ref) with a filter on availability.
+
+`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref).
+Call `collect` on the result if an array is desired.
+
+# Arguments
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `subsystem_name::Union{Nothing, String}`: (default: `nothing`) If provided, restrict
+ results to the named subsystem.
+
+# Examples
+```julia
+gens = get_available_components(ThermalStandard, sys)
+gens = get_available_components(Generator, sys)
+```
+
+See also: [`get_components`](@ref get_components(
::Type{T},
sys::System;
subsystem_name = nothing,
- ) where {T <: Component}
-) but returns only components that are [`get_available`](@ref).
-```
+) where {T <: Component}), [`get_available`](@ref)
"""
get_available_components(
::Type{T},
@@ -166,22 +249,49 @@ get_available_components(
IS.get_available_components(T, sys; subsystem_name = subsystem_name)
"""
-Like [`get_components`](@ref get_components(
- sys::System,
- attribute::SupplementalAttribute
-) but returns only components that are [`get_available`](@ref).
+ get_available_components(sys::System, attribute::SupplementalAttribute)
+
+Return an iterator of available components attached to a given supplemental attribute.
+A component is available when [`get_available`](@ref) returns `true`.
+
+# Arguments
+- `sys::System`: The system to search.
+- `attribute::SupplementalAttribute`: The supplemental attribute whose available associated
+ components are returned.
+
+See also: [`get_associated_components`](@ref), [`get_available`](@ref)
"""
get_available_components(sys::System, attribute::SupplementalAttribute) =
IS.get_available_components(sys, attribute)
"""
-Like [`get_components`](@ref get_components(
+ get_available_components(filter_func::Function, ::Type{T}, sys::System; subsystem_name) where {T <: Component}
+
+Return an iterator of available components of type `T` that also satisfy `filter_func`.
+A component is available when [`get_available`](@ref) returns `true`.
+
+`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref).
+Call `collect` on the result if an array is desired.
+
+# Arguments
+- `filter_func::Function`: A single-argument function returning `true` for components to
+ include.
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `subsystem_name::Union{Nothing, String}`: (default: `nothing`) If provided, restrict
+ results to the named subsystem.
+
+# Examples
+```julia
+gens = get_available_components(x -> get_fuel(x) == ThermalFuels.COAL, ThermalStandard, sys)
+```
+
+See also: [`get_components`](@ref get_components(
filter_func::Function,
::Type{T},
sys::System;
subsystem_name = nothing,
- ) where {T <: Component}
-) but returns only components that are [`get_available`](@ref).
+) where {T <: Component}), [`get_available`](@ref)
"""
get_available_components(
filter_func::Function,
@@ -193,14 +303,36 @@ get_available_components(
# get_available_component
"""
-Get the available component by UUID.
+ get_available_component(sys::System, uuid::Union{Base.UUID, String})
+
+Return the component with the given UUID if it is available, otherwise return `nothing`.
+A component is available when [`get_available`](@ref) returns `true`.
+
+# Arguments
+- `sys::System`: The system to search.
+- `uuid::Union{Base.UUID, String}`: The UUID of the component to retrieve.
+
+See also: [`get_component(sys, uuid)`](@ref get_component(sys::System, uuid::Base.UUID)),
+[`get_available`](@ref)
"""
get_available_component(sys::System, uuid::Base.UUID) =
IS.get_available_component(sys, uuid)
get_available_component(sys::System, uuid::String) = IS.get_available_component(sys, uuid)
"""
-Like [`get_component`](@ref) but also returns `nothing` if the component is not [`get_available`](@ref).
+ get_available_component(::Type{T}, sys::System, name::AbstractString) where {T <: Component}
+
+Return the component of type `T` with the given name if it is available, otherwise return
+`nothing`. A component is available when [`get_available`](@ref) returns `true`.
+
+If `T` is an abstract type, names must be unique across all subtypes.
+
+# Arguments
+- `T`: The component type to retrieve. Can be concrete or abstract.
+- `sys::System`: The system to search.
+- `name::AbstractString`: The name of the component.
+
+See also: [`get_component`](@ref), [`get_available`](@ref)
"""
get_available_component(::Type{T}, sys::System, args...; kwargs...) where {T <: Component} =
IS.get_available_component(T, sys, args...; kwargs...)
diff --git a/src/impedance_correction.jl b/src/impedance_correction.jl
index ef5aabaad9..e94a400526 100644
--- a/src/impedance_correction.jl
+++ b/src/impedance_correction.jl
@@ -1,12 +1,33 @@
"""
-Attribute that contains information regarding the Impedance Correction Table (ICT) rows defined in the Table.
+ struct ImpedanceCorrectionData <: SupplementalAttribute
+ table_number::Int64
+ impedance_correction_curve::PiecewiseLinearData
+ transformer_winding::WindingCategory
+ transformer_control_mode::ImpedanceCorrectionTransformerControlMode
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute representing a single row of a Transformer Impedance Correction
+Table (TICT). Adjusts transformer impedance as a piecewise-linear function of tap ratio
+or phase shift angle.
# Arguments
-- `table_number::Int64`: Row number of the ICT to be linked with a specific Transformer component.
-- `impedance_correction_curve::`[`PiecewiseLinearData`](@extref InfrastructureSystems.PiecewiseLinearData): Function to define intervals (tap ratio/angle shift) in the Transformer component.
-- `transformer_winding::`[`WindingCategory`](@ref): Indicates the winding to which the ICT is linked to for a Transformer component.
-- `transformer_control_mode::`[`ImpedanceCorrectionTransformerControlMode`](@ref): Defines the control modes of the Transformer, whether is for off-nominal turns ratio or phase angle shifts.
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `table_number::Int64`: Row number of the TICT, used to link this correction entry to a
+ specific transformer component.
+- `impedance_correction_curve::`[`PiecewiseLinearData`](@ref):
+ Piecewise-linear function defining impedance correction intervals as a function of tap
+ ratio or phase shift angle.
+- `transformer_winding::`[`WindingCategory`](@ref): Winding of the transformer this
+ correction entry is associated with.
+- `transformer_control_mode::`[`ImpedanceCorrectionTransformerControlMode`](@ref): Control
+ mode determining whether correction is applied based on tap ratio or phase shift angle.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`WindingCategory`](@ref): Enumeration of transformer winding roles.
+- [`ImpedanceCorrectionTransformerControlMode`](@ref): Enumeration of impedance correction
+ control modes.
"""
struct ImpedanceCorrectionData <: SupplementalAttribute
table_number::Int64
@@ -22,11 +43,17 @@ end
Construct an [`ImpedanceCorrectionData`](@ref).
# Arguments
-- `table_number::Int64`: Row number of the ICT to be linked with a specific Transformer component.
-- `impedance_correction_curve::`[`PiecewiseLinearData`](@extref InfrastructureSystems.PiecewiseLinearData): Function to define intervals (tap ratio/angle shift) in the Transformer component.
-- `transformer_winding::`[`WindingCategory`](@ref): Indicates the winding to which the ICT is linked to for a Transformer component.
-- `transformer_control_mode::`[`ImpedanceCorrectionTransformerControlMode`](@ref): Defines the control modes of the Transformer, whether is for off-nominal turns ratio or phase angle shifts.
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `table_number::Int64`: Row number of the TICT, used to link this correction entry to a
+ specific transformer component.
+- `impedance_correction_curve::`[`PiecewiseLinearData`](@ref):
+ Piecewise-linear function defining impedance correction intervals as a function of tap
+ ratio or phase shift angle.
+- `transformer_winding::`[`WindingCategory`](@ref): Winding of the transformer this
+ correction entry is associated with.
+- `transformer_control_mode::`[`ImpedanceCorrectionTransformerControlMode`](@ref): Control
+ mode determining whether correction is applied based on tap ratio or phase shift angle.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function ImpedanceCorrectionData(;
table_number,
@@ -44,15 +71,15 @@ function ImpedanceCorrectionData(;
)
end
-"""Get [`ImpedanceCorrectionData`](@ref) `table_number`."""
+"""Return the `table_number` field of [`ImpedanceCorrectionData`](@ref)."""
get_table_number(value::ImpedanceCorrectionData) = value.table_number
-"""Get [`ImpedanceCorrectionData`](@ref) `function_data`."""
+"""Return the `impedance_correction_curve` field of [`ImpedanceCorrectionData`](@ref)."""
get_impedance_correction_curve(value::ImpedanceCorrectionData) =
value.impedance_correction_curve
-"""Get [`ImpedanceCorrectionData`](@ref) `transformer_winding`."""
+"""Return the `transformer_winding` field of [`ImpedanceCorrectionData`](@ref)."""
get_transformer_winding(value::ImpedanceCorrectionData) = value.transformer_winding
-"""Get [`ImpedanceCorrectionData`](@ref) `transformer_control_mode`."""
+"""Return the `transformer_control_mode` field of [`ImpedanceCorrectionData`](@ref)."""
get_transformer_control_mode(value::ImpedanceCorrectionData) =
value.transformer_control_mode
-"""Get [`ImpedanceCorrectionData`](@ref) `internal`."""
+"""Return the `internal` field of [`ImpedanceCorrectionData`](@ref)."""
get_internal(value::ImpedanceCorrectionData) = value.internal
diff --git a/src/models/HybridSystem.jl b/src/models/HybridSystem.jl
index 082c22661d..af7ad4415a 100644
--- a/src/models/HybridSystem.jl
+++ b/src/models/HybridSystem.jl
@@ -31,59 +31,55 @@ A Hybrid System that includes a combination of renewable generation, load, therm
generation and/or energy storage.
# Arguments
-- `name::String`: Name of the component. Components of the same type (e.g., `PowerLoad`) must have unique names, but components of different types (e.g., `PowerLoad` and `ACBus`) can have the same name
-- `available::Bool`: Indicator of whether the component is connected and online (`true`) or disconnected, offline, or down (`false`). Unavailable components are excluded during simulations
-- `status::Bool`: Initial commitment condition at the start of a simulation (`true` = on or `false` = off)
-- `bus::ACBus`: Bus that this component is connected to
-- `active_power::Float64`: Initial active power set point of the unit in MW. For power flow, this is the steady state operating point of the system. For production cost modeling, this may or may not be used as the initial starting point for the solver, depending on the solver used
-- `reactive_power::Float64`: Initial reactive power set point of the unit (MVAR)
-- `base_power::Float64`: Base power of the unit (MVA) for per unitization, which is commonly the same as `rating`
-- `operation_cost::MarketBidCost`: Market bid cost to operate, [`MarketBidCost`](@ref)
-- `thermal_unit::Union{Nothing, ThermalGen}`: A thermal generator with supertype [`ThermalGen`](@ref)
-- `electric_load::Union{Nothing, ElectricLoad}`: A load with supertype [`ElectricLoad`](@ref)
-- `storage::Union{Nothing, Storage}`: An energy storage system with supertype [`Storage`](@ref)
-- `renewable_unit::Union{Nothing, RenewableGen}`: A renewable generator with supertype [`RenewableGen`](@ref)
-- `interconnection_impedance::ComplexF64`: Impedance (typically in p.u.) between the hybrid system and the grid interconnection
-- `interconnection_rating::Union{Nothing, Float64}`: Maximum rating of the hybrid system's interconnection with the transmission network (MVA)
-- `input_active_power_limits::MinMax`: Minimum and maximum stable input active power levels (MW)
-- `output_active_power_limits::MinMax`: Minimum and maximum stable output active power levels (MW)
-- `reactive_power_limits::Union{Nothing, MinMax}`: Minimum and maximum reactive power limits (MVAR). Set to `Nothing` if not applicable.
-- `interconnection_efficiency::Union{Nothing, NamedTuple{(:in, :out), Tuple{Float64, Float64}},}`: Efficiency [0, 1.0] at the grid interconnection to model losses `in` and `out` of the common DC-side conversion
-- `services::Vector{Service}`: (optional) Services that this device contributes to
-- `dynamic_injector::Union{Nothing, DynamicInjection}`: (optional) corresponding dynamic injection device
-- `ext::Dict{String, Any}`: (optional) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation.
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference.
+$(TYPEDFIELDS)
"""
mutable struct HybridSystem <: StaticInjectionSubsystem
+ "Name of the component. Components of the same type (e.g., `PowerLoad`) must have unique names, but components of different types (e.g., `PowerLoad` and `ACBus`) can have the same name"
name::String
+ "Indicator of whether the component is connected and online (`true`) or disconnected, offline, or down (`false`). Unavailable components are excluded during simulations"
available::Bool
+ "Initial commitment condition at the start of a simulation (`true` = on or `false` = off)"
status::Bool
+ "Bus that this component is connected to"
bus::ACBus
+ "Initial active power set point of the unit in MW. For power flow, this is the steady state operating point of the system. For production cost modeling, this may or may not be used as the initial starting point for the solver, depending on the solver used"
active_power::Float64
+ "Initial reactive power set point of the unit (MVAR)"
reactive_power::Float64
+ "Base power of the unit (MVA) for per unitization, which is commonly the same as `rating`"
base_power::Float64
+ "Market bid cost to operate, [`MarketBidCost`](@ref)"
operation_cost::MarketBidCost
+ "A thermal generator with supertype [`ThermalGen`](@ref)"
thermal_unit::Union{Nothing, ThermalGen}
+ "A load with supertype [`ElectricLoad`](@ref)"
electric_load::Union{Nothing, ElectricLoad}
+ "An energy storage system with supertype [`Storage`](@ref)"
storage::Union{Nothing, Storage}
+ "A renewable generator with supertype [`RenewableGen`](@ref)"
renewable_unit::Union{Nothing, RenewableGen}
- # interconnection Data
- "Thermal limited MVA Power Output of the unit. <= Capacity"
+ "Impedance (typically in p.u.) between the hybrid system and the grid interconnection"
interconnection_impedance::ComplexF64
+ "Maximum rating of the hybrid system's interconnection with the transmission network (MVA)"
interconnection_rating::Union{Nothing, Float64}
+ "Minimum and maximum stable input active power levels (MW)"
input_active_power_limits::Union{Nothing, MinMax}
+ "Minimum and maximum stable output active power levels (MW)"
output_active_power_limits::Union{Nothing, MinMax}
+ "Minimum and maximum reactive power limits (MVAR). Set to `Nothing` if not applicable"
reactive_power_limits::Union{Nothing, MinMax}
+ "Efficiency [0, 1.0] at the grid interconnection to model losses `in` and `out` of the common DC-side conversion"
interconnection_efficiency::Union{
Nothing,
NamedTuple{(:in, :out), Tuple{Float64, Float64}},
}
- "corresponding dynamic injection device"
+ "(optional) Services that this device contributes to"
services::Vector{Service}
+ "(optional) Corresponding dynamic injection device"
dynamic_injector::Union{Nothing, DynamicInjection}
+ "(optional) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation"
ext::Dict{String, Any}
- "internal forecast storage"
- "power system internal reference, do not modify"
+ "(**Do not modify.**) PowerSystems.jl internal reference"
internal::InfrastructureSystemsInternal
end
@@ -182,87 +178,87 @@ function set_units_setting!(value::HybridSystem, settings::SystemUnitsSettings)
return
end
-"""Get [`HybridSystem`](@ref) `available`."""
+"""Return the `available` field of [`HybridSystem`](@ref)."""
get_available(value::HybridSystem) = value.available
-"""Get [`HybridSystem`](@ref) `status`."""
+"""Return the `status` field of [`HybridSystem`](@ref)."""
get_status(value::HybridSystem) = value.status
-"""Get [`HybridSystem`](@ref) `bus`."""
+"""Return the `bus` field of [`HybridSystem`](@ref)."""
get_bus(value::HybridSystem) = value.bus
-"""Get [`HybridSystem`](@ref) `active_power`."""
+"""Return the `active_power` field of [`HybridSystem`](@ref)."""
get_active_power(value::HybridSystem) = get_value(value, Val(:active_power), Val(:mva))
-"""Get [`HybridSystem`](@ref) `reactive_power`."""
+"""Return the `reactive_power` field of [`HybridSystem`](@ref)."""
get_reactive_power(value::HybridSystem) = get_value(value, Val(:reactive_power), Val(:mva))
-"""Get [`HybridSystem`](@ref) thermal unit"""
+"""Return the `thermal_unit` field of [`HybridSystem`](@ref)."""
get_thermal_unit(value::HybridSystem) = value.thermal_unit
-"""Get [`HybridSystem`](@ref) load"""
+"""Return the `electric_load` field of [`HybridSystem`](@ref)."""
get_electric_load(value::HybridSystem) = value.electric_load
-"""Get [`HybridSystem`](@ref) storage unit"""
+"""Return the `storage` field of [`HybridSystem`](@ref)."""
get_storage(value::HybridSystem) = value.storage
-"""Get [`HybridSystem`](@ref) renewable unit"""
+"""Return the `renewable_unit` field of [`HybridSystem`](@ref)."""
get_renewable_unit(value::HybridSystem) = value.renewable_unit
-"""Get [`HybridSystem`](@ref) `interconnection_rating`."""
+"""Return the `interconnection_rating` field of [`HybridSystem`](@ref)."""
get_interconnection_rating(value::HybridSystem) =
get_value(value, Val(:interconnection_rating), Val(:mva))
-"""get [`HybridSystem`](@ref) interconnection impedance"""
+"""Return the `interconnection_impedance` field of [`HybridSystem`](@ref)."""
get_interconnection_impedance(value::HybridSystem) = value.interconnection_impedance
-"""Get [`HybridSystem`](@ref) `input_active_power_limits`."""
+"""Return the `input_active_power_limits` field of [`HybridSystem`](@ref)."""
get_input_active_power_limits(value::HybridSystem) =
get_value(value, Val(:input_active_power_limits), Val(:mva))
-"""Get [`HybridSystem`](@ref) `output_active_power_limits`."""
+"""Return the `output_active_power_limits` field of [`HybridSystem`](@ref)."""
get_output_active_power_limits(value::HybridSystem) =
get_value(value, Val(:output_active_power_limits), Val(:mva))
-"""Get [`HybridSystem`](@ref) `reactive_power_limits`."""
+"""Return the `reactive_power_limits` field of [`HybridSystem`](@ref)."""
get_reactive_power_limits(value::HybridSystem) =
get_value(value, Val(:reactive_power_limits), Val(:mva))
-"""get [`HybridSystem`](@ref) interconnection efficiency"""
+"""Return the `interconnection_efficiency` field of [`HybridSystem`](@ref)."""
get_interconnection_efficiency(value::HybridSystem) = value.interconnection_efficiency
-"""Get [`HybridSystem`](@ref) `base_power`."""
+"""Return the `base_power` field of [`HybridSystem`](@ref)."""
get_base_power(value::HybridSystem) = value.base_power
-"""Get [`HybridSystem`](@ref) `operation_cost`."""
+"""Return the `operation_cost` field of [`HybridSystem`](@ref)."""
get_operation_cost(value::HybridSystem) = value.operation_cost
-"""Get [`HybridSystem`](@ref) `services`."""
+"""Return the `services` field of [`HybridSystem`](@ref)."""
get_services(value::HybridSystem) = value.services
-"""Get [`HybridSystem`](@ref) `dynamic_injector`."""
+"""Return the `dynamic_injector` field of [`HybridSystem`](@ref)."""
get_dynamic_injector(value::HybridSystem) = value.dynamic_injector
-"""Get [`HybridSystem`](@ref) `ext`."""
+"""Return the `ext` field of [`HybridSystem`](@ref)."""
get_ext(value::HybridSystem) = value.ext
-"""Get [`HybridSystem`](@ref) `internal`."""
+"""Return the `internal` field of [`HybridSystem`](@ref)."""
get_internal(value::HybridSystem) = value.internal
-"""Set [`HybridSystem`](@ref) `available`."""
+"""Set the `available` field of [`HybridSystem`](@ref)."""
set_available!(value::HybridSystem, val) = value.available = val
-"""Get [`HybridSystem`](@ref) `status`."""
+"""Set the `status` field of [`HybridSystem`](@ref)."""
set_status!(value::HybridSystem, val) = value.status = val
-"""Set [`HybridSystem`](@ref) `bus`."""
+"""Set the `bus` field of [`HybridSystem`](@ref)."""
set_bus!(value::HybridSystem, val) = value.bus = val
-"""Set [`HybridSystem`](@ref) `interconnection_rating`."""
+"""Set the `interconnection_rating` field of [`HybridSystem`](@ref)."""
set_interconnection_rating!(value::HybridSystem, val) = value.interconnection_rating = val
-"""Set [`HybridSystem`](@ref) `active_power`."""
+"""Set the `active_power` field of [`HybridSystem`](@ref)."""
set_active_power!(value::HybridSystem, val) = value.active_power = val
-"""Set [`HybridSystem`](@ref) `reactive_power`."""
+"""Set the `reactive_power` field of [`HybridSystem`](@ref)."""
set_reactive_power!(value::HybridSystem, val) = value.reactive_power = val
-"""set [`HybridSystem`](@ref) interconnection impedance"""
+"""Set the `interconnection_impedance` field of [`HybridSystem`](@ref)."""
set_interconnection_impedance!(value::HybridSystem, val) =
value.interconnection_impedance = val
-"""Set [`HybridSystem`](@ref) `input_active_power_limits`."""
+"""Set the `input_active_power_limits` field of [`HybridSystem`](@ref)."""
set_input_active_power_limits!(value::HybridSystem, val) =
value.input_active_power_limits = val
-"""Set [`HybridSystem`](@ref) `output_active_power_limits`."""
+"""Set the `output_active_power_limits` field of [`HybridSystem`](@ref)."""
set_output_active_power_limits!(value::HybridSystem, val) =
value.output_active_power_limits = val
-"""Set [`HybridSystem`](@ref) `reactive_power_limits`."""
+"""Set the `reactive_power_limits` field of [`HybridSystem`](@ref)."""
set_reactive_power_limits!(value::HybridSystem, val) = value.reactive_power_limits = val
-"""Set [`HybridSystem`](@ref) `interconnection_efficiency`."""
+"""Set the `interconnection_efficiency` field of [`HybridSystem`](@ref)."""
set_interconnection_efficiency!(value::HybridSystem, val) =
value.interconnection_rating = val
-"""Set [`HybridSystem`](@ref) `base_power`."""
+"""Set the `base_power` field of [`HybridSystem`](@ref)."""
set_base_power!(value::HybridSystem, val) = value.base_power = val
-"""Set [`HybridSystem`](@ref) `operation_cost`."""
+"""Set the `operation_cost` field of [`HybridSystem`](@ref)."""
set_operation_cost!(value::HybridSystem, val) = value.operation_cost = val
-"""Set [`HybridSystem`](@ref) `services`."""
+"""Set the `services` field of [`HybridSystem`](@ref)."""
set_services!(value::HybridSystem, val) = value.services = val
-"""Set [`HybridSystem`](@ref) `ext`."""
+"""Set the `ext` field of [`HybridSystem`](@ref)."""
set_ext!(value::HybridSystem, val) = value.ext = val
"""
@@ -287,28 +283,28 @@ function get_subcomponents(hybrid::HybridSystem)
end
end
-"""Set [`HybridSystem`](@ref) thermal unit"""
+"""Set the `thermal_unit` field of [`HybridSystem`](@ref)."""
function set_thermal_unit!(hybrid::HybridSystem, val::ThermalGen)
_raise_if_attached_to_system(hybrid)
hybrid.thermal_unit = val
return
end
-"""Set [`HybridSystem`](@ref) load"""
+"""Set the `electric_load` field of [`HybridSystem`](@ref)."""
function set_electric_load!(hybrid::HybridSystem, val::ElectricLoad)
_raise_if_attached_to_system(hybrid)
value.electric_load = val
return
end
-"""Set [`HybridSystem`](@ref) storage unit"""
+"""Set the `storage` field of [`HybridSystem`](@ref)."""
function set_storage!(hybrid::HybridSystem, val::Storage)
_raise_if_attached_to_system(hybrid)
value.storage = val
return
end
-"""Set [`HybridSystem`](@ref) renewable unit"""
+"""Set the `renewable_unit` field of [`HybridSystem`](@ref)."""
function set_renewable_unit!(hybrid::HybridSystem, val::RenewableGen)
_raise_if_attached_to_system(hybrid)
value.renewable_unit = val
diff --git a/src/models/OuterControl.jl b/src/models/OuterControl.jl
index 10fb9d0cc8..8be39201a8 100644
--- a/src/models/OuterControl.jl
+++ b/src/models/OuterControl.jl
@@ -12,18 +12,19 @@
Parameters of a Outer-Loop controller using a active power controller and a reactive power droop controller.
# Arguments
-- `A <: ActivePowerControl`: Active power controller (typically droop or virtual inertia).
-- `R <: ReactivePowerControl`: Reactive power controller (typically droop).
-- `ext::Dict{String, Any}`
-- `states::Vector{Symbol}`: Vector of states (will depend on the components).
-- `n_states::Int`: Number of states (will depend on the components).
+$(TYPEDFIELDS)
"""
mutable struct OuterControl{A <: ActivePowerControl, R <: ReactivePowerControl} <:
DynamicInverterComponent
+ "Active power controller (a subtype of [`ActivePowerControl`](@ref), typically droop or virtual inertia)"
active_power_control::A
+ "Reactive power controller (a subtype of [`ReactivePowerControl`](@ref), typically droop)"
reactive_power_control::R
+ "(optional) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation"
ext::Dict{String, Any}
+ "(**Do not modify.**) Vector of states (will depend on the active and reactive power control components)"
states::Vector{Symbol}
+ "(**Do not modify.**) Number of states (will depend on the active and reactive power control components)"
n_states::Int
end
@@ -66,11 +67,11 @@ get_ext(value::OuterControl) = value.ext
get_states(value::OuterControl) = value.states
"""Get `n_states` from [`OuterControl`](@ref)."""
get_n_states(value::OuterControl) = value.n_states
-"""Set [`OuterControl`](@ref) `active_power_control`."""
+"""Set the `active_power_control` field of [`OuterControl`](@ref)."""
set_active_power_control!(value::OuterControl, val) =
value.active_power_control = val
-"""Set [`OuterControl`](@ref) `reactive_power_control`."""
+"""Set the `reactive_power_control` field of [`OuterControl`](@ref)."""
set_reactive_power_control!(value::OuterControl, val) =
value.reactive_power_control = val
-"""Set [`OuterControl`](@ref) `ext`."""
+"""Set the `ext` field of [`OuterControl`](@ref)."""
set_ext!(value::OuterControl, val) = value.ext = val
diff --git a/src/models/RoundRotorExponential.jl b/src/models/RoundRotorExponential.jl
index 0fe7a73074..9d2dfd79c1 100644
--- a/src/models/RoundRotorExponential.jl
+++ b/src/models/RoundRotorExponential.jl
@@ -7,11 +7,12 @@
IEEE Std 1110 §5.3.2 (Model 2.2). GENROE model in PSSE and PSLF.
# Arguments
-- `base_machine::RoundRotorMachine`: Round Rotor Machine model.
-- `saturation_coeffs::Tuple{Float64, Float64}``: Saturation coefficients for exponential model.
+$(TYPEDFIELDS)
"""
mutable struct RoundRotorExponential <: Machine
+ "Round Rotor machine parameters"
base_machine::RoundRotorMachine
+ "Derived saturation coefficients for the exponential saturation model, computed from the `Se` input"
saturation_coeffs::Tuple{Float64, Float64}
end
diff --git a/src/models/RoundRotorQuadratic.jl b/src/models/RoundRotorQuadratic.jl
index ed8da7aae4..10e09f4cba 100644
--- a/src/models/RoundRotorQuadratic.jl
+++ b/src/models/RoundRotorQuadratic.jl
@@ -7,11 +7,12 @@
IEEE Std 1110 §5.3.2 (Model 2.2). GENROU model in PSSE and PSLF.
# Arguments
-- `base_machine::RoundRotorMachine`: Round Rotor Machine model.
-- `saturation_coeffs::Tuple{Float64, Float64}``: Saturation coefficients for quadratic model.
+$(TYPEDFIELDS)
"""
mutable struct RoundRotorQuadratic <: Machine
+ "Round Rotor machine parameters"
base_machine::RoundRotorMachine
+ "Derived saturation coefficients for the quadratic saturation model, computed from the `Se` input"
saturation_coeffs::Tuple{Float64, Float64}
end
IS.@forward((RoundRotorQuadratic, :base_machine), RoundRotorMachine)
diff --git a/src/models/SalientPoleExponential.jl b/src/models/SalientPoleExponential.jl
index c1fe798eff..f239e3afd9 100644
--- a/src/models/SalientPoleExponential.jl
+++ b/src/models/SalientPoleExponential.jl
@@ -6,12 +6,13 @@
3-states salient-pole synchronous machine with exponential saturation:
IEEE Std 1110 §5.3.2 (Model 2.1). GENSAE in PSSE and PSLF.
-# Arguments:
-- `base_machine::SalientPoleMachine`: Salient Pole Machine model.
-- `saturation_coeffs::Tuple{Float64, Float64}``: Saturation coefficients for exponential model.
+# Arguments
+$(TYPEDFIELDS)
"""
mutable struct SalientPoleExponential <: Machine
+ "Salient Pole machine parameters"
base_machine::SalientPoleMachine
+ "Derived saturation coefficients for the exponential saturation model, computed from the `Se` input"
saturation_coeffs::Tuple{Float64, Float64}
end
IS.@forward((SalientPoleExponential, :base_machine), SalientPoleMachine)
diff --git a/src/models/SalientPoleQuadratic.jl b/src/models/SalientPoleQuadratic.jl
index 1278ab3cc1..0008638052 100644
--- a/src/models/SalientPoleQuadratic.jl
+++ b/src/models/SalientPoleQuadratic.jl
@@ -3,15 +3,16 @@
base_machine::SalientPoleMachine
saturation_coeffs::Tuple{Float64, Float64}
-3-states salient-pole synchronous machine with exponential saturation:
+3-states salient-pole synchronous machine with quadratic saturation:
IEEE Std 1110 §5.3.2 (Model 2.1). GENSAL in PSSE and PSLF.
-# Arguments:
-- `base_machine::SalientPoleMachine`: Salient Pole Machine model.
-- `saturation_coeffs::Tuple{Float64, Float64}``: Saturation coefficients for quadratic model.
+# Arguments
+$(TYPEDFIELDS)
"""
mutable struct SalientPoleQuadratic <: Machine
+ "Salient Pole machine parameters"
base_machine::SalientPoleMachine
+ "Derived saturation coefficients for the quadratic saturation model, computed from the `Se` input"
saturation_coeffs::Tuple{Float64, Float64}
end
IS.@forward((SalientPoleQuadratic, :base_machine), SalientPoleMachine)
diff --git a/src/models/branches.jl b/src/models/branches.jl
index 61143e6c95..db61fbbe6e 100644
--- a/src/models/branches.jl
+++ b/src/models/branches.jl
@@ -1,27 +1,115 @@
-""" Supertype for all branches"""
+"""
+ Branch
+
+Supertype for all transmission branches in a power system.
+
+Concrete subtypes include [`AreaInterchange`](@ref). Abstract subtypes include
+[`ACBranch`](@ref) (AC transmission) and [`DCBranch`](@ref) (DC transmission).
+
+See also: [`Device`](@ref), [`ACBranch`](@ref), [`DCBranch`](@ref)
+"""
abstract type Branch <: Device end
-""" Supertype for all AC branches (branches connecting AC nodes or Areas)"""
+"""
+ ACBranch
+
+Supertype for all AC branches connecting AC nodes ([`ACBus`](@ref)) or Areas.
+
+Abstract subtypes include [`ACTransmission`](@ref) (AC transmission lines and transformers)
+and [`TwoTerminalHVDC`](@ref) (two-terminal HVDC links between AC buses).
+
+See also: [`Branch`](@ref), [`DCBranch`](@ref), [`ACTransmission`](@ref), [`TwoTerminalHVDC`](@ref)
+"""
abstract type ACBranch <: Branch end
-""" Supertype for all AC transmission devices (devices connecting AC nodes only)"""
+"""
+ ACTransmission
+
+Supertype for all AC transmission devices connecting AC nodes only.
+
+Concrete subtypes include [`Line`](@ref), [`MonitoredLine`](@ref), and
+[`DiscreteControlledACBranch`](@ref). Abstract subtypes include
+[`TwoWindingTransformer`](@ref) and [`ThreeWindingTransformer`](@ref).
+
+See also: [`ACBranch`](@ref), [`TwoWindingTransformer`](@ref), [`ThreeWindingTransformer`](@ref)
+"""
abstract type ACTransmission <: ACBranch end
-""" Supertype for all Two Winding Transformer types"""
+"""
+ TwoWindingTransformer
+
+Supertype for all two-winding transformer types.
+
+Concrete subtypes include [`Transformer2W`](@ref), [`TapTransformer`](@ref), and
+[`PhaseShiftingTransformer`](@ref).
+
+See also: [`ACTransmission`](@ref), [`ThreeWindingTransformer`](@ref)
+"""
abstract type TwoWindingTransformer <: ACTransmission end
-""" Supertype for all Three Winding Transformer types"""
+"""
+ ThreeWindingTransformer
+
+Supertype for all three-winding transformer types.
+
+Concrete subtypes include [`Transformer3W`](@ref) and [`PhaseShiftingTransformer3W`](@ref).
+
+See also: [`ACTransmission`](@ref), [`TwoWindingTransformer`](@ref)
+"""
abstract type ThreeWindingTransformer <: ACTransmission end
-""" Supertype for all Two Terminal HVDC transmission devices between AC Buses. Not to be confused with [DCBranch](@ref)"""
+"""
+ TwoTerminalHVDC
+
+Supertype for all two-terminal HVDC transmission devices between AC buses.
+
+Not to be confused with [`DCBranch`](@ref), which connects DC nodes. Concrete subtypes
+include [`TwoTerminalGenericHVDCLine`](@ref), [`TwoTerminalLCCLine`](@ref), and
+[`TwoTerminalVSCLine`](@ref).
+
+See also: [`ACBranch`](@ref), [`DCBranch`](@ref)
+"""
abstract type TwoTerminalHVDC <: ACBranch end
-""" Supertype for all DC branches (branches that connect only DC nodes)"""
+"""
+ DCBranch
+
+Supertype for all DC branches connecting DC nodes ([`DCBus`](@ref)) only.
+
+Concrete subtypes include [`TModelHVDCLine`](@ref).
+
+See also: [`Branch`](@ref), [`ACBranch`](@ref), [`TwoTerminalHVDC`](@ref)
+"""
abstract type DCBranch <: Branch end
+"""
+Return true since AC branches support services.
+
+See also: [`supports_services` for `Device`](@ref supports_services(::Device)),
+[`supports_services` for `StaticInjection`](@ref supports_services(::StaticInjection)),
+[`supports_services` for `HydroReservoir`](@ref supports_services(::HydroReservoir)),
+[`supports_services` for `DynamicInjection`](@ref supports_services(::DynamicInjection))
+"""
function supports_services(::ACBranch)
return true
end
-get_from_bus(b::T) where {T <: Branch} = b.arc.from
-get_to_bus(b::T) where {T <: Branch} = b.arc.to
+"""
+Return the "from" [`ACBus`](@ref) of the branch.
+
+# Arguments
+- `b::Branch`: The branch.
+
+See also: [`get_to_bus`](@ref), [`get_arc`](@ref)
+"""
+get_from_bus(b::Branch) = b.arc.from
+
+"""
+Return the "to" [`ACBus`](@ref) of the branch.
+
+# Arguments
+- `b::Branch`: The branch.
+
+See also: [`get_from_bus`](@ref), [`get_arc`](@ref)
+"""
+get_to_bus(b::Branch) = b.arc.to
diff --git a/src/models/components.jl b/src/models/components.jl
index e9746a2810..0035246e2f 100644
--- a/src/models/components.jl
+++ b/src/models/components.jl
@@ -1,22 +1,37 @@
+"""
+Return the system base power for the component.
+
+# Arguments
+- `c::Component`: The component.
+
+See also: [`get_base_power`](@ref)
+"""
function get_system_base_power(c::Component)
return get_internal(c).units_info.base_value
end
"""
-Default behavior of a component. If there is no base_power field, assume is in the system's base power.
+Return the base power for the component.
+
+If the component does not have a `base_power` field, returns the system's base power value.
+
+# Arguments
+- `c::Component`: The component.
+
+See also: [`get_base_power` for `System`](@ref get_base_power(::System))
"""
get_base_power(c::Component) = get_system_base_power(c)
-_get_multiplier(c::T, conversion_unit) where {T <: Component} =
+_get_multiplier(c::Component, conversion_unit) =
_get_multiplier(c, get_internal(c).units_info, conversion_unit)
-_get_multiplier(::T, ::Nothing, conversion_unit) where {T <: Component} =
+_get_multiplier(::Component, ::Nothing, conversion_unit) =
1.0
_get_multiplier(
- c::T,
+ c::Component,
setting::IS.SystemUnitsSettings,
conversion_unit,
-) where {T <: Component} =
+) =
_get_multiplier(c, setting, Val(setting.unit_system), conversion_unit)
# PERF: dispatching on the UnitSystem values instead of comparing with if/else avoids the
@@ -24,28 +39,28 @@ _get_multiplier(
# i.e., IS.UnitSystem.NATURAL_UNITS by itself isn't treated as a constant, it's a dictionary
# lookup each time.
_get_multiplier(
- ::T,
+ ::Component,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.DEVICE_BASE},
::Any,
-) where {T <: Component} =
+) =
1.0
###############
#### Power ####
###############
_get_multiplier(
- c::T,
+ c::Component,
setting::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.SYSTEM_BASE},
::Val{:mva},
-) where {T <: Component} =
+) =
get_base_power(c) / setting.base_value
_get_multiplier(
- c::T,
+ c::Component,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
::Val{:mva},
-) where {T <: Component} =
+) =
get_base_power(c)
###############
@@ -53,18 +68,18 @@ _get_multiplier(
###############
# Z_device / Z_sys = (V_device^2 / S_device) / (V_device^2 / S_sys) = S_sys / S_device
_get_multiplier(
- c::T,
+ c::Branch,
setting::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.SYSTEM_BASE},
::Val{:ohm},
-) where {T <: Branch} =
+) =
setting.base_value / get_base_power(c)
function _get_multiplier(
- c::T,
+ c::Branch,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
::Val{:ohm},
-) where {T <: Branch}
+)
base_voltage = get_base_voltage(get_arc(c).from)
if isnothing(base_voltage)
error("Base voltage is not defined for $(summary(c)).")
@@ -72,11 +87,11 @@ function _get_multiplier(
return get_base_voltage(get_arc(c).from)^2 / get_base_power(c)
end
function _get_multiplier(
- c::T,
+ c::TwoWindingTransformer,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
::Val{:ohm},
-) where {T <: TwoWindingTransformer}
+)
base_voltage = get_base_voltage_primary(c)
if isnothing(base_voltage)
error("Base voltage is not defined for $(summary(c)).")
@@ -89,18 +104,18 @@ end
##################
# Y_device / Y_sys = (S_device / V_device^2) / (S_sys / S_sys^2) = S_device / S_sys
_get_multiplier(
- c::T,
+ c::Branch,
setting::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.SYSTEM_BASE},
::Val{:siemens},
-) where {T <: Branch} =
+) =
get_base_power(c) / setting.base_value
function _get_multiplier(
- c::T,
+ c::Branch,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
::Val{:siemens},
-) where {T <: Branch}
+)
base_voltage = get_base_voltage(get_arc(c).from)
if isnothing(base_voltage)
@warn "Base voltage is not set for $(c.name). Returning in DEVICE_BASE units."
@@ -109,11 +124,11 @@ function _get_multiplier(
return get_base_power(c) / get_base_voltage(get_arc(c).from)^2
end
function _get_multiplier(
- c::T,
+ c::TwoWindingTransformer,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
::Val{:siemens},
-) where {T <: TwoWindingTransformer}
+)
base_voltage = get_base_voltage_primary(c)
if isnothing(base_voltage)
@warn "Base voltage is not set for $(c.name). Returning in DEVICE_BASE units."
@@ -122,9 +137,19 @@ function _get_multiplier(
return get_base_power(c) / base_voltage^2
end
-_get_multiplier(::T, ::IS.SystemUnitsSettings, _, _) where {T <: Component} =
+_get_multiplier(::Component, ::IS.SystemUnitsSettings, _, _) =
error("Undefined Conditional")
+"""
+Return the field value of a component, converted using the system's unit settings.
+
+# Arguments
+- `c::Component`: The component.
+- `field::Val{T}`: The field name as a `Val`.
+- `conversion_unit`: The target unit for conversion.
+
+See also: [`set_value`](@ref)
+"""
function get_value(c::Component, ::Val{T}, conversion_unit) where {T}
value = Base.getproperty(c, T)
return _get_value(c, value, conversion_unit)
@@ -180,6 +205,17 @@ function _get_value(::Nothing, _, _)
return
end
+"""
+Set the field value of a component, converting from the system's unit settings.
+
+# Arguments
+- `c::Component`: The component.
+- `field`: The field name.
+- `val`: The value to set.
+- `conversion_unit`: The unit of the value being set.
+
+See also: [`get_value`](@ref)
+"""
function set_value(c::Component, _, val, conversion_unit)
return _set_value(c, val, conversion_unit)
end
diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl
index 0108d99d41..0f97506593 100644
--- a/src/models/cost_function_timeseries.jl
+++ b/src/models/cost_function_timeseries.jl
@@ -31,7 +31,7 @@ function _validate_reserve_demand_curve(
end
end
-function _validate_reserve_demand_curve(cost::T, name::String) where {T <: CostCurve}
+function _validate_reserve_demand_curve(cost::CostCurve, name::String)
throw(
ArgumentError(
"Reserve curve of type $(typeof(cost)) on $name cannot represent an ORDC curve, use CostCurve{PiecewiseIncrementalCurve} instead",
@@ -127,11 +127,15 @@ end
# GETTER IMPLEMENTATIONS
"""
-Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any
-of the relevant fields (`incremental_offer_curves`, `incremental_initial_input`,
-`no_load_cost`) are time series, the user may specify `start_time` and `len` and the
-function returns a `TimeArray` of `CostCurve{PiecewiseIncrementalCurve}`s; if the field is
-not a time series, the function returns a single `CostCurve{PiecewiseIncrementalCurve}`.
+Retrieve the variable cost bid for a [`StaticInjection`](@ref) device with a [`MarketBidCost`](@ref).
+
+If any of the relevant fields (`incremental_offer_curves`, `incremental_initial_input`,
+`no_load_cost`) are time series, `start_time` and `len` may be specified and a
+`TimeArray` of `CostCurve{PiecewiseIncrementalCurve}` values is returned; otherwise
+returns a single `CostCurve{PiecewiseIncrementalCurve}`.
+
+See also: [`get_variable_cost` for `ReserveDemandCurve`](@ref get_variable_cost(::ReserveDemandCurve)),
+[`get_incremental_variable_cost`](@ref), [`get_decremental_variable_cost`](@ref)
"""
function get_variable_cost(
device::StaticInjection,
@@ -190,12 +194,13 @@ function get_variable_cost(
end
"""
-Retrieve the incremental variable cost bid for a `StaticInjection` device with a
-`MarketBidCost`. If any of the relevant fields (`incremental_offer_curves`,
-`incremental_initial_input`, `no_load_cost`) are time series, the user may specify
-`start_time` and `len` and the function returns a `TimeArray` of
-`CostCurve{PiecewiseIncrementalCurve}`s; if the field is not a time series, the function
-returns a single `CostCurve{PiecewiseIncrementalCurve}`.
+Retrieve the incremental variable cost bid for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If any of the relevant fields (`incremental_offer_curves`,
+`incremental_initial_input`, `no_load_cost`) are time series, `start_time` and `len` may
+be specified and a `TimeArray` of `CostCurve{PiecewiseIncrementalCurve}` values is
+returned; otherwise returns a single `CostCurve{PiecewiseIncrementalCurve}`.
+
+See also: [`get_variable_cost`](@ref), [`get_decremental_variable_cost`](@ref)
"""
function get_incremental_variable_cost(
device::StaticInjection,
@@ -212,12 +217,13 @@ function get_incremental_variable_cost(
end
"""
-Retrieve the decremental variable cost bid for a `StaticInjection` device with a
-`MarketBidCost`. If any of the relevant fields (`decremental_offer_curves`,
-`decremental_initial_input`, `no_load_cost`) are time series, the user may specify
-`start_time` and `len` and the function returns a `TimeArray` of
-`CostCurve{PiecewiseIncrementalCurve}`s; if the field is not a time series, the function
-returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the decremental variable cost bid for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If any of the relevant fields (`decremental_offer_curves`,
+`decremental_initial_input`, `no_load_cost`) are time series, `start_time` and `len` may
+be specified and a `TimeArray` of `CostCurve{PiecewiseIncrementalCurve}` values is
+returned; otherwise returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_variable_cost`](@ref), [`get_incremental_variable_cost`](@ref)
"""
function get_decremental_variable_cost(
device::StaticInjection,
@@ -277,11 +283,12 @@ function get_decremental_variable_cost(
end
"""
-Retrieve the import variable cost bid for a `StaticInjection` device with an
-`ImportExportCost`. If `import_offer_curves` is a time series, the user may specify
-`start_time` and `len` and the function returns a `TimeArray` of
-`CostCurve{PiecewiseIncrementalCurve}`s; if the field is not a time series, the function
-returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the import variable cost bid for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref). If `import_offer_curves` is a time series, `start_time` and
+`len` may be specified and a `TimeArray` of `CostCurve{PiecewiseIncrementalCurve}` values
+is returned; otherwise returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_export_variable_cost`](@ref), [`get_import_offer_curves`](@ref)
"""
function get_import_variable_cost(
device::StaticInjection,
@@ -298,11 +305,12 @@ function get_import_variable_cost(
end
"""
-Retrieve the export variable cost bid for a `StaticInjection` device with an
-`ImportExportCost`. If `export_offer_curves` is a time series, the user may specify
-`start_time` and `len` and the function returns a `TimeArray` of
-`CostCurve{PiecewiseIncrementalCurve}`s; if the field is not a time series, the function
-returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the export variable cost bid for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref). If `export_offer_curves` is a time series, `start_time` and
+`len` may be specified and a `TimeArray` of `CostCurve{PiecewiseIncrementalCurve}` values
+is returned; otherwise returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_import_variable_cost`](@ref), [`get_export_offer_curves`](@ref)
"""
function get_export_variable_cost(
device::StaticInjection,
@@ -319,8 +327,12 @@ function get_export_variable_cost(
end
"""
-Retrieve the variable cost data for a `ReserveDemandCurve`. The user may specify
-`start_time` and `len` and the function returns a `TimeArray` of `CostCurve`s.
+Retrieve the variable cost data for a [`ReserveDemandCurve`](@ref).
+
+`start_time` and `len` may be specified and the function returns a `TimeArray` of
+`CostCurve` values.
+
+See also: [`get_variable_cost` for `StaticInjection` with `MarketBidCost`](@ref get_variable_cost(::StaticInjection, ::MarketBidCost))
"""
get_variable_cost(
service::ReserveDemandCurve;
@@ -330,9 +342,11 @@ get_variable_cost(
_make_market_bid_curve, start_time, len)
"""
-Return service bid time series data for a `StaticInjection` device with a `MarketBidCost`.
-The user may specify `start_time` and `len` and the function returns a `TimeArray` of
-`CostCurve`s.
+Return service bid time series data for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). `start_time` and `len` may be specified and a `TimeArray` of
+`CostCurve` values is returned.
+
+See also: [`get_variable_cost`](@ref), [`get_incremental_offer_curves`](@ref)
"""
function get_services_bid(
device::StaticInjection,
@@ -355,7 +369,7 @@ function get_services_bid(
end
"""
-Get the fuel cost of a [`HybridSystem`](@ref)'s thermal subunit.
+Return the fuel cost of a [`HybridSystem`](@ref)'s thermal subunit.
[`HybridSystem`](@ref) is a [`StaticInjectionSubsystem`](@ref) that aggregates subunits; fuel cost
for thermal power comes from the [`ThermalGen`](@ref) subcomponent, not the hybrid's top-level
@@ -405,10 +419,12 @@ function get_fuel_cost(component::StaticInjection;
end
"""
-Retrieve the `incremental_offer_curves` for a `StaticInjection` device with a
-`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len`
-and the function returns a `TimeArray` of `PiecewiseStepData`s; if the field is not a time
-series, the function returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the `incremental_offer_curves` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `PiecewiseStepData` values is returned; otherwise returns
+a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_decremental_offer_curves`](@ref), [`get_incremental_variable_cost`](@ref)
"""
get_incremental_offer_curves(
device::StaticInjection,
@@ -420,10 +436,12 @@ get_incremental_offer_curves(
device, get_incremental_offer_curves(cost), nothing, start_time, len)
"""
-Retrieve the `decremental_offer_curves` for a `StaticInjection` device with a
-`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len`
-and the function returns a `TimeArray` of `PiecewiseStepData`s; if the field is not a time
-series, the function returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the `decremental_offer_curves` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `PiecewiseStepData` values is returned; otherwise returns
+a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_incremental_offer_curves`](@ref), [`get_decremental_variable_cost`](@ref)
"""
get_decremental_offer_curves(
device::StaticInjection,
@@ -435,10 +453,12 @@ get_decremental_offer_curves(
device, get_decremental_offer_curves(cost), nothing, start_time, len)
"""
-Retrieve the `import_offer_curves` for a `StaticInjection` device with a `ImportExportCost`.
-If this field is a time series, the user may specify `start_time` and `len` and the function
-returns a `TimeArray` of `PiecewiseStepData`s; if the field is not a time series, the
-function returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the `import_offer_curves` for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `PiecewiseStepData` values is returned; otherwise returns
+a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_export_offer_curves`](@ref), [`get_import_variable_cost`](@ref)
"""
get_import_offer_curves(
device::StaticInjection,
@@ -449,10 +469,12 @@ get_import_offer_curves(
device, get_import_offer_curves(cost), nothing, start_time, len)
"""
-Retrieve the `export_offer_curves` for a `StaticInjection` device with a `ImportExportCost`.
-If this field is a time series, the user may specify `start_time` and `len` and the function
-returns a `TimeArray` of `PiecewiseStepData`s; if the field is not a time series, the
-function returns a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+Retrieve the `export_offer_curves` for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `PiecewiseStepData` values is returned; otherwise returns
+a single `CostCurve{PiecewiseIncrementalCurve}` or `nothing`.
+
+See also: [`get_import_offer_curves`](@ref), [`get_export_variable_cost`](@ref)
"""
get_export_offer_curves(
device::StaticInjection,
@@ -463,10 +485,12 @@ get_export_offer_curves(
device, get_export_offer_curves(cost), nothing, start_time, len)
"""
-Retrieve the no-load cost data for a `StaticInjection` device with a `MarketBidCost`. If
-this field is a time series, the user may specify `start_time` and `len` and the function
-returns a `TimeArray` of `Float64`s; if the field is not a time series, the function
-returns a single `Float64` or `nothing`.
+Retrieve the no-load cost data for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `Float64` values is returned; otherwise returns a single
+`Float64` or `nothing`.
+
+See also: [`get_start_up`](@ref), [`get_variable_cost`](@ref)
"""
get_no_load_cost(
device::StaticInjection,
@@ -477,10 +501,12 @@ get_no_load_cost(
get_no_load_cost(cost), nothing, start_time, len)
"""
-Retrieve the `incremental_initial_input` for a `StaticInjection` device with a
-`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len`
-and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the
-function returns a single `Float64` or `nothing`.
+Retrieve the `incremental_initial_input` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `Float64` values is returned; otherwise returns a single
+`Float64` or `nothing`.
+
+See also: [`get_decremental_initial_input`](@ref), [`get_incremental_offer_curves`](@ref)
"""
get_incremental_initial_input(
device::StaticInjection,
@@ -491,10 +517,12 @@ get_incremental_initial_input(
get_incremental_initial_input(cost), nothing, start_time, len)
"""
-Retrieve the `decremental_initial_input` for a `StaticInjection` device with a
-`MarketBidCost`. If this field is a time series, the user may specify `start_time` and `len`
-and the function returns a `TimeArray` of `Float64`s; if the field is not a time series, the
-function returns a single `Float64` or `nothing`.
+Retrieve the `decremental_initial_input` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `Float64` values is returned; otherwise returns a single
+`Float64` or `nothing`.
+
+See also: [`get_incremental_initial_input`](@ref), [`get_decremental_offer_curves`](@ref)
"""
get_decremental_initial_input(
device::StaticInjection,
@@ -505,10 +533,12 @@ get_decremental_initial_input(
get_decremental_initial_input(cost), nothing, start_time, len)
"""
-Retrieve the startup cost data for a `StaticInjection` device with a `MarketBidCost`. If
-this field is a time series, the user may specify `start_time` and `len` and the function
-returns a `TimeArray` of `StartUpStages`s; if the field is not a time series, the function
-returns a single `StartUpStages`.
+Retrieve the startup cost data for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `StartUpStages` values is returned; otherwise returns a
+single `StartUpStages`.
+
+See also: [`get_shut_down`](@ref), [`get_no_load_cost`](@ref)
"""
get_start_up(
device::StaticInjection,
@@ -519,10 +549,12 @@ get_start_up(
get_start_up(cost), StartUpStages, start_time, len)
"""
-Retrieve the shutdown cost data for a `StaticInjection` device with a `MarketBidCost`. If
-this field is a time series, the user may specify `start_time` and `len` and the function
-returns a `TimeArray` of `Float64`s; if the field is not a time series, the function
-returns a single `Float64`.
+Retrieve the shutdown cost data for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref). If this field is a time series, `start_time` and `len` may be
+specified and a `TimeArray` of `Float64` values is returned; otherwise returns a single
+`Float64`.
+
+See also: [`get_start_up`](@ref), [`get_no_load_cost`](@ref)
"""
get_shut_down(
device::StaticInjection,
@@ -548,12 +580,12 @@ _process_set_cost(_, _, _, _, ::Nothing) = nothing
_process_set_cost(::Type{T}, _, _, _, cost::T) where {T} = cost
function _process_set_cost(
- ::Type{_},
+ ::Type,
::Type{T},
sys::System,
component::Component,
cost::IS.TimeSeriesData,
-) where {_, T}
+) where {T}
data_type = IS.eltype_data(cost)
!(data_type <: T) && throw(TypeError(_process_set_cost, T, data_type))
key = add_time_series!(sys, component, cost)
@@ -562,16 +594,16 @@ end
# SETTER IMPLEMENTATIONS
"""
-Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`.
+Set the incremental variable cost bid for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Nothing, IS.TimeSeriesData,
- CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype
- `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and
- not accepted for time series data.
-- `power_units::UnitSystem`: Units to be used for data. Must be NATURAL_UNITS.
+`data` may be a single `CostCurve{PiecewiseIncrementalCurve}` or a time series of
+`PiecewiseStepData`. When using a time series, `power_units` must be
+`UnitSystem.NATURAL_UNITS`.
+
+See also: [`set_variable_cost!` for `ReserveDemandCurve` with time series](@ref set_variable_cost!(::System, ::ReserveDemandCurve, ::Union{Nothing, IS.TimeSeriesData})),
+[`set_variable_cost!` for `ReserveDemandCurve` with scalar](@ref set_variable_cost!(::System, ::ReserveDemandCurve, ::CostCurve{PiecewiseIncrementalCurve})),
+[`get_variable_cost`](@ref)
"""
function set_variable_cost!(
sys::System,
@@ -579,6 +611,7 @@ function set_variable_cost!(
data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}},
power_units::UnitSystem,
)
+ # See also: set_variable_cost! for ReserveDemandCurve
market_bid_cost = get_operation_cost(component)
_validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)")
if (typeof(data) <: CostCurve{PiecewiseIncrementalCurve}) &&
@@ -615,16 +648,13 @@ function set_variable_cost!(
end
"""
-Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`.
+Set the incremental variable cost bid for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Nothing, IS.TimeSeriesData,
- CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype
- `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and
- not accepted for time series data.
-- `power_units::UnitSystem`: Units to be used for data.
+`data` may be a single `CostCurve{PiecewiseIncrementalCurve}` or a time series of
+`PiecewiseStepData`.
+
+See also: [`set_decremental_variable_cost!`](@ref), [`get_incremental_variable_cost`](@ref)
"""
function set_incremental_variable_cost!(
sys::System,
@@ -637,16 +667,13 @@ function set_incremental_variable_cost!(
end
"""
-Set the decremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`.
+Set the decremental variable cost bid for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Nothing, IS.TimeSeriesData,
- CostCurve{PiecewiseIncrementalCurve}},`: the data. If using a time series, must be of eltype
- `PiecewiseStepData`. `PiecewiseIncrementalCurve` is only accepted for single CostCurve and
- not accepted for time series data.
-- `power_units::UnitSystem`: Units to be used for data.
+`data` may be a single `CostCurve{PiecewiseIncrementalCurve}` or a time series of
+`PiecewiseStepData`.
+
+See also: [`set_incremental_variable_cost!`](@ref), [`get_decremental_variable_cost`](@ref)
"""
function set_decremental_variable_cost!(
sys::System,
@@ -681,15 +708,14 @@ function set_decremental_variable_cost!(
end
"""
-Set the import variable cost bid for a `StaticInjection` device with an `ImportExportCost`.
+Set the import variable cost bid for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}`: the data.
- If using a time series, must be of eltype `PiecewiseStepData`. `PiecewiseIncrementalCurve`
- is only accepted for single CostCurve and not accepted for time series data.
-- `power_units::UnitSystem`: Units to be used for data.
+`data` may be a single `CostCurve{PiecewiseIncrementalCurve}` or a time series of
+`PiecewiseStepData`. When using a time series, `power_units` must be
+`UnitSystem.NATURAL_UNITS`.
+
+See also: [`set_export_variable_cost!`](@ref), [`get_import_variable_cost`](@ref)
"""
function set_import_variable_cost!(
sys::System,
@@ -728,15 +754,14 @@ function set_import_variable_cost!(
end
"""
-Set the export variable cost bid for a `StaticInjection` device with an `ImportExportCost`.
+Set the export variable cost bid for a [`StaticInjection`](@ref) device with an
+[`ImportExportCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}`: the data.
- If using a time series, must be of eltype `PiecewiseStepData`. `PiecewiseIncrementalCurve`
- is only accepted for single CostCurve and not accepted for time series data.
-- `power_units::UnitSystem`: Units to be used for data.
+`data` may be a single `CostCurve{PiecewiseIncrementalCurve}` or a time series of
+`PiecewiseStepData`. When using a time series, `power_units` must be
+`UnitSystem.NATURAL_UNITS`.
+
+See also: [`set_import_variable_cost!`](@ref), [`get_export_variable_cost`](@ref)
"""
function set_export_variable_cost!(
sys::System,
@@ -775,12 +800,11 @@ function set_export_variable_cost!(
end
"""
-Adds energy market bids time-series to the ReserveDemandCurve.
+Set the variable cost for a [`ReserveDemandCurve`](@ref) using a time series.
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::ReserveDemandCurve`: the curve
-- `time_series_data::IS.TimeSeriesData`: TimeSeriesData
+See also: [`set_variable_cost!` for `StaticInjection`](@ref set_variable_cost!(::System, ::StaticInjection, ::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, ::UnitSystem)),
+[`set_variable_cost!` for `ReserveDemandCurve` with scalar](@ref set_variable_cost!(::System, ::ReserveDemandCurve, ::CostCurve{PiecewiseIncrementalCurve})),
+[`get_variable_cost`](@ref)
"""
function set_variable_cost!(
sys::System,
@@ -793,12 +817,12 @@ function set_variable_cost!(
end
"""
-Adds fixed energy market bids to the ReserveDemandCurve.
+Set the variable cost for a [`ReserveDemandCurve`](@ref) using a scalar
+`CostCurve{PiecewiseIncrementalCurve}`.
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::ReserveDemandCurve`: the curve
-- `time_series_data::CostCurve{PiecewiseIncrementalCurve}
+See also: [`set_variable_cost!` for `StaticInjection`](@ref set_variable_cost!(::System, ::StaticInjection, ::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, ::UnitSystem)),
+[`set_variable_cost!` for `ReserveDemandCurve` with time series](@ref set_variable_cost!(::System, ::ReserveDemandCurve, ::Union{Nothing, IS.TimeSeriesData})),
+[`get_variable_cost`](@ref)
"""
function set_variable_cost!(
::System,
@@ -810,7 +834,14 @@ function set_variable_cost!(
set_variable!(component, data)
end
-"Set the fuel cost of the component's variable cost, which must be a `FuelCurve`."
+"""
+Set the fuel cost of a [`StaticInjection`](@ref) component's variable cost, which must be
+a [`FuelCurve`](@ref). `data` may be a scalar `Float64` or a time series of `Float64` values.
+
+Throws `ArgumentError` if the variable cost is not a [`FuelCurve`](@ref).
+
+See also: [`get_fuel_cost`](@ref), [`set_variable_cost!`](@ref)
+"""
function set_fuel_cost!(
sys::System,
component::StaticInjection,
@@ -831,12 +862,11 @@ function set_fuel_cost!(
end
"""
-Set the no-load cost for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series.
+Set the no-load cost for a [`StaticInjection`](@ref) device with a [`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype `Float64`.
+`data` may be a scalar `Float64` or a time series of `Float64` values.
+
+See also: [`get_no_load_cost`](@ref), [`set_start_up!`](@ref)
"""
function set_no_load_cost!(
sys::System,
@@ -850,12 +880,12 @@ function set_no_load_cost!(
end
"""
-Set the `incremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series.
+Set the `incremental_initial_input` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype `Float64`.
+`data` may be a scalar `Float64` or a time series of `Float64` values.
+
+See also: [`set_decremental_initial_input!`](@ref), [`get_incremental_initial_input`](@ref)
"""
function set_incremental_initial_input!(
sys::System,
@@ -869,12 +899,12 @@ function set_incremental_initial_input!(
end
"""
-Set the `decremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series.
+Set the `decremental_initial_input` for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `time_series_data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype `Float64`.
+`data` may be a scalar `Float64` or a time series of `Float64` values.
+
+See also: [`set_incremental_initial_input!`](@ref), [`get_decremental_initial_input`](@ref)
"""
function set_decremental_initial_input!(
sys::System,
@@ -888,15 +918,12 @@ function set_decremental_initial_input!(
end
"""
-Set the startup cost for a `StaticInjection` device with a `MarketBidCost` to either a
-single number, a single `StartUpStages`, or a time series.
+Set the startup cost for a [`StaticInjection`](@ref) device with a [`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `data::Union{Float64, StartUpStages, IS.TimeSeriesData},`: the data. If a time series,
- must be of eltype `NTuple{3, Float64}` -- to represent a single value in a time series,
- use `(value, 0.0, 0.0)`.
+`data` may be a scalar `Float64`, a single [`StartUpStages`](@ref), or a time series of
+`NTuple{3, Float64}`. To represent a single value in a time series, use `(value, 0.0, 0.0)`.
+
+See also: [`set_shut_down!`](@ref), [`set_no_load_cost!`](@ref), [`get_start_up`](@ref)
"""
function set_start_up!(
sys::System,
@@ -916,14 +943,11 @@ function set_start_up!(
end
"""
-Set the shutdown cost for a `StaticInjection` device with a `MarketBidCost` to either a
-single number or a time series.
+Set the shutdown cost for a [`StaticInjection`](@ref) device with a [`MarketBidCost`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `data::Union{Float64, IS.TimeSeriesData},`: the data. If a time series, must be of eltype
- `Float64`.
+`data` may be a scalar `Float64` or a time series of `Float64` values.
+
+See also: [`set_start_up!`](@ref), [`get_shut_down`](@ref)
"""
function set_shut_down!(
sys::System,
@@ -943,13 +967,13 @@ function set_shut_down!(
end
"""
-Adds service bids time-series data to the MarketBidCost.
+Set service bid time series data for a [`StaticInjection`](@ref) device with a
+[`MarketBidCost`](@ref) for a specific [`Service`](@ref).
-# Arguments
-- `sys::System`: PowerSystem System
-- `component::StaticInjection`: Static injection device
-- `service::Service,`: Service for which the device is eligible to contribute
-- `time_series_data::IS.TimeSeriesData`: TimeSeriesData
+`time_series_data` must be of eltype `PiecewiseStepData` and `power_units` must be
+`UnitSystem.NATURAL_UNITS`. The device must be eligible to contribute to the service.
+
+See also: [`get_services_bid`](@ref), [`set_variable_cost!`](@ref)
"""
function set_service_bid!(
sys::System,
diff --git a/src/models/cost_functions/HydroGenerationCost.jl b/src/models/cost_functions/HydroGenerationCost.jl
index f3d197d5c2..9f0fe07307 100644
--- a/src/models/cost_functions/HydroGenerationCost.jl
+++ b/src/models/cost_functions/HydroGenerationCost.jl
@@ -24,12 +24,12 @@ end
# Constructor for demo purposes; non-functional.
HydroGenerationCost(::Nothing) = HydroGenerationCost(zero(CostCurve), 0.0)
-"""Get [`HydroGenerationCost`](@ref) `variable`."""
+"""Return the `variable` field of [`HydroGenerationCost`](@ref)."""
get_variable(value::HydroGenerationCost) = value.variable
-"""Get [`HydroGenerationCost`](@ref) `fixed`."""
+"""Return the `fixed` field of [`HydroGenerationCost`](@ref)."""
get_fixed(value::HydroGenerationCost) = value.fixed
-"""Set [`HydroGenerationCost`](@ref) `variable`."""
+"""Set the `variable` field of [`HydroGenerationCost`](@ref)."""
set_variable!(value::HydroGenerationCost, val) = value.variable = val
-"""Set [`HydroGenerationCost`](@ref) `fixed`."""
+"""Set the `fixed` field of [`HydroGenerationCost`](@ref)."""
set_fixed!(value::HydroGenerationCost, val) = value.fixed = val
diff --git a/src/models/cost_functions/HydroReservoirCost.jl b/src/models/cost_functions/HydroReservoirCost.jl
index e69f10705c..efe5dc1ac0 100644
--- a/src/models/cost_functions/HydroReservoirCost.jl
+++ b/src/models/cost_functions/HydroReservoirCost.jl
@@ -22,19 +22,19 @@ function HydroReservoirCost(::Nothing)
HydroReservoirCost()
end
-"""Get [`HydroReservoirCost`](@ref) `level_shortage_cost`."""
+"""Return the `level_shortage_cost` field of [`HydroReservoirCost`](@ref)."""
get_level_shortage_cost(value::HydroReservoirCost) = value.level_shortage_cost
-"""Get [`HydroReservoirCost`](@ref) `level_surplus_cost`."""
+"""Return the `level_surplus_cost` field of [`HydroReservoirCost`](@ref)."""
get_level_surplus_cost(value::HydroReservoirCost) = value.level_surplus_cost
-"""Get [`HydroReservoirCost`](@ref) `spillage_cost`."""
+"""Return the `spillage_cost` field of [`HydroReservoirCost`](@ref)."""
get_spillage_cost(value::HydroReservoirCost) = value.spillage_cost
-"""Set [`HydroReservoirCost`](@ref) `level_shortage_cost`."""
+"""Set the `level_shortage_cost` field of [`HydroReservoirCost`](@ref)."""
set_level_shortage_cost!(value::HydroReservoirCost, val) =
value.level_shortage_cost = val
-"""Set [`HydroReservoirCost`](@ref) `level_surplus_cost`."""
+"""Set the `level_surplus_cost` field of [`HydroReservoirCost`](@ref)."""
set_level_surplus_cost!(value::HydroReservoirCost, val) =
value.level_surplus_cost = val
-"""Set [`HydroReservoirCost`](@ref) `spillage_cost`."""
+"""Set the `spillage_cost` field of [`HydroReservoirCost`](@ref)."""
set_spillage_cost!(value::HydroReservoirCost, val) =
value.spillage_cost = val
diff --git a/src/models/cost_functions/ImportExportCost.jl b/src/models/cost_functions/ImportExportCost.jl
index ef74d6d0fb..08d9375b85 100644
--- a/src/models/cost_functions/ImportExportCost.jl
+++ b/src/models/cost_functions/ImportExportCost.jl
@@ -50,30 +50,30 @@ function ImportExportCost(::Nothing)
ImportExportCost()
end
-"""Get [`ImportExportCost`](@ref) `import_offer_curves`."""
+"""Return the `import_offer_curves` field of [`ImportExportCost`](@ref)."""
get_import_offer_curves(value::ImportExportCost) = value.import_offer_curves
-"""Get [`ImportExportCost`](@ref) `export_offer_curves`."""
+"""Return the `export_offer_curves` field of [`ImportExportCost`](@ref)."""
get_export_offer_curves(value::ImportExportCost) = value.export_offer_curves
-"""Get [`ImportExportCost`](@ref) `ancillary_service_offers`."""
+"""Return the `ancillary_service_offers` field of [`ImportExportCost`](@ref)."""
get_ancillary_service_offers(value::ImportExportCost) = value.ancillary_service_offers
-"""Get [`ImportExportCost`](@ref) `energy_import_weekly_limit`."""
+"""Return the `energy_import_weekly_limit` field of [`ImportExportCost`](@ref)."""
get_energy_import_weekly_limit(value::ImportExportCost) = value.energy_import_weekly_limit
-"""Get [`ImportExportCost`](@ref) `energy_export_weekly_limits`."""
+"""Return the `energy_export_weekly_limits` field of [`ImportExportCost`](@ref)."""
get_energy_export_weekly_limit(value::ImportExportCost) = value.energy_export_weekly_limit
-"""Set [`ImportExportCost`](@ref) `import_offer_curves`."""
+"""Set the `import_offer_curves` field of [`ImportExportCost`](@ref)."""
set_import_offer_curves!(value::ImportExportCost, val) =
value.import_offer_curves = val
-"""Set [`ImportExportCost`](@ref) `export_offer_curves`."""
+"""Set the `export_offer_curves` field of [`ImportExportCost`](@ref)."""
set_export_offer_curves!(value::ImportExportCost, val) =
value.export_offer_curves = val
-"""Set [`ImportExportCost`](@ref) `ancillary_service_offers`."""
+"""Set the `ancillary_service_offers` field of [`ImportExportCost`](@ref)."""
set_ancillary_service_offers!(value::ImportExportCost, val) =
value.ancillary_service_offers = val
-"""Set [`ImportExportCost`](@ref) `energy_import_weekly_limit`."""
+"""Set the `energy_import_weekly_limit` field of [`ImportExportCost`](@ref)."""
set_energy_import_weekly_limit!(value::ImportExportCost, val) =
value.energy_import_weekly_limit = val
-"""Set [`ImportExportCost`](@ref) `energy_export_weekly_limits`."""
+"""Set the `energy_export_weekly_limits` field of [`ImportExportCost`](@ref)."""
set_energy_export_weekly_limit!(value::ImportExportCost, val) =
value.energy_export_weekly_limit = val
diff --git a/src/models/cost_functions/LoadCost.jl b/src/models/cost_functions/LoadCost.jl
index 52c089cb4b..edb0ddb984 100644
--- a/src/models/cost_functions/LoadCost.jl
+++ b/src/models/cost_functions/LoadCost.jl
@@ -21,12 +21,12 @@ end
# Constructor for demo purposes; non-functional.
LoadCost(::Nothing) = LoadCost(zero(CostCurve), 0.0)
-"""Get [`LoadCost`](@ref) `variable`."""
+"""Return the `variable` field of [`LoadCost`](@ref)."""
get_variable(value::LoadCost) = value.variable
-"""Get [`LoadCost`](@ref) `fixed`."""
+"""Return the `fixed` field of [`LoadCost`](@ref)."""
get_fixed(value::LoadCost) = value.fixed
-"""Set [`LoadCost`](@ref) `variable`."""
+"""Set the `variable` field of [`LoadCost`](@ref)."""
set_variable!(value::LoadCost, val) = value.variable = val
-"""Set [`LoadCost`](@ref) `fixed`."""
+"""Set the `fixed` field of [`LoadCost`](@ref)."""
set_fixed!(value::LoadCost, val) = value.fixed = val
diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl
index df2965bcdd..d79c898ac7 100644
--- a/src/models/cost_functions/MarketBidCost.jl
+++ b/src/models/cost_functions/MarketBidCost.jl
@@ -84,7 +84,7 @@ MarketBidCost(
ancillary_service_offers,
)
-"""Auxiliary Constructor for TestData"""
+"""Construct a [`MarketBidCost`](@ref) for testing purposes."""
MarketBidCost(
no_load_cost::Float64,
start_up::Union{TimeSeriesKey, StartUpStages},
@@ -157,53 +157,53 @@ function MarketBidCost(
)
end
-"""Get [`MarketBidCost`](@ref) `no_load_cost`."""
+"""Return the `no_load_cost` field of [`MarketBidCost`](@ref)."""
get_no_load_cost(value::MarketBidCost) = value.no_load_cost
-"""Get [`MarketBidCost`](@ref) `start_up`."""
+"""Return the `start_up` field of [`MarketBidCost`](@ref)."""
get_start_up(value::MarketBidCost) = value.start_up
-"""Get [`MarketBidCost`](@ref) `shut_down`."""
+"""Return the `shut_down` field of [`MarketBidCost`](@ref)."""
get_shut_down(value::MarketBidCost) = value.shut_down
-"""Get [`MarketBidCost`](@ref) `incremental_offer_curves`."""
+"""Return the `incremental_offer_curves` field of [`MarketBidCost`](@ref)."""
get_incremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves
-"""Get [`MarketBidCost`](@ref) `decremental_offer_curves`."""
+"""Return the `decremental_offer_curves` field of [`MarketBidCost`](@ref)."""
get_decremental_offer_curves(value::MarketBidCost) = value.decremental_offer_curves
-"""Get [`MarketBidCost`](@ref) `incremental_initial_input`."""
+"""Return the `incremental_initial_input` field of [`MarketBidCost`](@ref)."""
get_incremental_initial_input(value::MarketBidCost) = value.incremental_initial_input
-"""Get [`MarketBidCost`](@ref) `decremental_initial_input`."""
+"""Return the `decremental_initial_input` field of [`MarketBidCost`](@ref)."""
get_decremental_initial_input(value::MarketBidCost) = value.decremental_initial_input
-"""Get [`MarketBidCost`](@ref) `ancillary_service_offers`."""
+"""Return the `ancillary_service_offers` field of [`MarketBidCost`](@ref)."""
get_ancillary_service_offers(value::MarketBidCost) = value.ancillary_service_offers
-"""Set [`MarketBidCost`](@ref) `no_load_cost`."""
+"""Set the `no_load_cost` field of [`MarketBidCost`](@ref)."""
set_no_load_cost!(value::MarketBidCost, val) = value.no_load_cost = val
-"""Set [`MarketBidCost`](@ref) `start_up`."""
+"""Set the `start_up` field of [`MarketBidCost`](@ref)."""
set_start_up!(value::MarketBidCost, val) = value.start_up = val
-"""Set [`MarketBidCost`](@ref) `shut_down`."""
+"""Set the `shut_down` field of [`MarketBidCost`](@ref)."""
set_shut_down!(value::MarketBidCost, val) = value.shut_down = val
-"""Set [`MarketBidCost`](@ref) `incremental_offer_curves`."""
+"""Set the `incremental_offer_curves` field of [`MarketBidCost`](@ref)."""
set_incremental_offer_curves!(value::MarketBidCost, val) =
value.incremental_offer_curves = val
-"""Set [`MarketBidCost`](@ref) `incremental_initial_input`."""
+"""Set the `incremental_initial_input` field of [`MarketBidCost`](@ref)."""
set_incremental_initial_input!(value::MarketBidCost, val) =
value.incremental_initial_input = val
-"""Set [`MarketBidCost`](@ref) `incremental_offer_curves`."""
+"""Set the `decremental_offer_curves` field of [`MarketBidCost`](@ref)."""
set_decremental_offer_curves!(value::MarketBidCost, val) =
value.decremental_offer_curves = val
-"""Set [`MarketBidCost`](@ref) `decremental_initial_input`."""
+"""Set the `decremental_initial_input` field of [`MarketBidCost`](@ref)."""
set_decremental_initial_input!(value::MarketBidCost, val) =
value.decremental_initial_input = val
-"""Set [`MarketBidCost`](@ref) `ancillary_service_offers`."""
+"""Set the `ancillary_service_offers` field of [`MarketBidCost`](@ref)."""
set_ancillary_service_offers!(value::MarketBidCost, val) =
value.ancillary_service_offers = val
-"""Auxiliary Method for setting up start up that are not multi-start"""
+"""Set the `start_up` field of [`MarketBidCost`](@ref) using a single value, with `warm` and `cold` stages set to `0.0`."""
function set_start_up!(value::MarketBidCost, val::Real)
start_up_multi = single_start_up_to_stages(val)
set_start_up!(value, start_up_multi)
end
"""
-Return `true` if the given [`ProductionVariableCostCurve`](@ref) is a market bid curve
+Return `true` if the given `ProductionVariableCostCurve` is a market bid curve
(a `CostCurve{PiecewiseIncrementalCurve}` as used in [`MarketBidCost`](@ref)).
"""
function is_market_bid_curve(curve::ProductionVariableCostCurve)
diff --git a/src/models/cost_functions/RenewableGenerationCost.jl b/src/models/cost_functions/RenewableGenerationCost.jl
index 78a8f30684..b6ddadb698 100644
--- a/src/models/cost_functions/RenewableGenerationCost.jl
+++ b/src/models/cost_functions/RenewableGenerationCost.jl
@@ -25,16 +25,16 @@ RenewableGenerationCost(variable) = RenewableGenerationCost(; variable)
# Constructor for demo purposes; non-functional.
RenewableGenerationCost(::Nothing) = RenewableGenerationCost(zero(CostCurve))
-"""Get [`RenewableGenerationCost`](@ref) `variable`."""
+"""Return the `variable` field of [`RenewableGenerationCost`](@ref)."""
get_variable(value::RenewableGenerationCost) = value.variable
-"""Get [`RenewableGenerationCost`](@ref) `curtailment_cost`."""
+"""Return the `curtailment_cost` field of [`RenewableGenerationCost`](@ref)."""
get_curtailment_cost(value::RenewableGenerationCost) = value.curtailment_cost
-"""Get [`RenewableGenerationCost`](@ref) `fixed`."""
+"""Return the `fixed` field of [`RenewableGenerationCost`](@ref)."""
get_fixed(value::RenewableGenerationCost) = value.fixed
-"""Set [`RenewableGenerationCost`](@ref) `variable`."""
+"""Set the `variable` field of [`RenewableGenerationCost`](@ref)."""
set_variable!(value::RenewableGenerationCost, val) = value.variable = val
-"""Set [`RenewableGenerationCost`](@ref) `curtailment_cost`."""
+"""Set the `curtailment_cost` field of [`RenewableGenerationCost`](@ref)."""
set_curtailment_cost!(value::RenewableGenerationCost, val) = value.curtailment_cost = val
-"""Set [`RenewableGenerationCost`](@ref) `fixed`."""
+"""Set the `fixed` field of [`RenewableGenerationCost`](@ref)."""
set_fixed!(value::RenewableGenerationCost, val) = value.fixed = val
diff --git a/src/models/cost_functions/StorageCost.jl b/src/models/cost_functions/StorageCost.jl
index 68b1d666c1..22916fa837 100644
--- a/src/models/cost_functions/StorageCost.jl
+++ b/src/models/cost_functions/StorageCost.jl
@@ -54,34 +54,34 @@ function StorageCost(::Nothing)
StorageCost()
end
-"""Get [`StorageCost`](@ref) `charge_variable_cost`."""
+"""Return the `charge_variable_cost` field of [`StorageCost`](@ref)."""
get_charge_variable_cost(value::StorageCost) = value.charge_variable_cost
-"""Get [`StorageCost`](@ref) `discharge_variable_cost`."""
+"""Return the `discharge_variable_cost` field of [`StorageCost`](@ref)."""
get_discharge_variable_cost(value::StorageCost) = value.discharge_variable_cost
-"""Get [`StorageCost`](@ref) `fixed`."""
+"""Return the `fixed` field of [`StorageCost`](@ref)."""
get_fixed(value::StorageCost) = value.fixed
-"""Get [`StorageCost`](@ref) `start_up`."""
+"""Return the `start_up` field of [`StorageCost`](@ref)."""
get_start_up(value::StorageCost) = value.start_up
-"""Get [`StorageCost`](@ref) `shut_down`."""
+"""Return the `shut_down` field of [`StorageCost`](@ref)."""
get_shut_down(value::StorageCost) = value.shut_down
-"""Get [`StorageCost`](@ref) `energy_shortage_cost`."""
+"""Return the `energy_shortage_cost` field of [`StorageCost`](@ref)."""
get_energy_shortage_cost(value::StorageCost) = value.energy_shortage_cost
-"""Get [`StorageCost`](@ref) `energy_surplus_cost`."""
+"""Return the `energy_surplus_cost` field of [`StorageCost`](@ref)."""
get_energy_surplus_cost(value::StorageCost) = value.energy_surplus_cost
-"""Set [`StorageCost`](@ref) `charge_variable_cost`."""
+"""Set the `charge_variable_cost` field of [`StorageCost`](@ref)."""
set_charge_variable_cost!(value::StorageCost, val) = value.charge_variable_cost = val
-"""Set [`StorageCost`](@ref) `discharge_variable_cost`."""
+"""Set the `discharge_variable_cost` field of [`StorageCost`](@ref)."""
set_discharge_variable_cost!(value::StorageCost, val) = value.discharge_variable_cost = val
-"""Set [`StorageCost`](@ref) `fixed`."""
+"""Set the `fixed` field of [`StorageCost`](@ref)."""
set_fixed!(value::StorageCost, val) = value.fixed = val
-"""Set [`StorageCost`](@ref) `start_up`."""
+"""Set the `start_up` field of [`StorageCost`](@ref)."""
set_start_up!(value::StorageCost, val) = value.start_up = val
-"""Set [`StorageCost`](@ref) `shut_down`."""
+"""Set the `shut_down` field of [`StorageCost`](@ref)."""
set_shut_down!(value::StorageCost, val) = value.shut_down = val
-"""Set [`StorageCost`](@ref) `energy_shortage_cost`."""
+"""Set the `energy_shortage_cost` field of [`StorageCost`](@ref)."""
set_energy_shortage_cost!(value::StorageCost, val) =
value.energy_shortage_cost = val
-"""Set [`StorageCost`](@ref) `energy_surplus_cost`."""
+"""Set the `energy_surplus_cost` field of [`StorageCost`](@ref)."""
set_energy_surplus_cost!(value::StorageCost, val) =
value.energy_surplus_cost = val
diff --git a/src/models/cost_functions/ThermalGenerationCost.jl b/src/models/cost_functions/ThermalGenerationCost.jl
index d09e8c0586..e48703c687 100644
--- a/src/models/cost_functions/ThermalGenerationCost.jl
+++ b/src/models/cost_functions/ThermalGenerationCost.jl
@@ -33,20 +33,20 @@ function ThermalGenerationCost(::Nothing)
)
end
-"""Get [`ThermalGenerationCost`](@ref) `variable`."""
+"""Return the `variable` field of [`ThermalGenerationCost`](@ref)."""
get_variable(value::ThermalGenerationCost) = value.variable
-"""Get [`ThermalGenerationCost`](@ref) `fixed`."""
+"""Return the `fixed` field of [`ThermalGenerationCost`](@ref)."""
get_fixed(value::ThermalGenerationCost) = value.fixed
-"""Get [`ThermalGenerationCost`](@ref) `start_up`."""
+"""Return the `start_up` field of [`ThermalGenerationCost`](@ref)."""
get_start_up(value::ThermalGenerationCost) = value.start_up
-"""Get [`ThermalGenerationCost`](@ref) `shut_down`."""
+"""Return the `shut_down` field of [`ThermalGenerationCost`](@ref)."""
get_shut_down(value::ThermalGenerationCost) = value.shut_down
-"""Set [`ThermalGenerationCost`](@ref) `variable`."""
+"""Set the `variable` field of [`ThermalGenerationCost`](@ref)."""
set_variable!(value::ThermalGenerationCost, val) = value.variable = val
-"""Set [`ThermalGenerationCost`](@ref) `fixed`."""
+"""Set the `fixed` field of [`ThermalGenerationCost`](@ref)."""
set_fixed!(value::ThermalGenerationCost, val) = value.fixed = val
-"""Set [`ThermalGenerationCost`](@ref) `start_up`."""
+"""Set the `start_up` field of [`ThermalGenerationCost`](@ref)."""
set_start_up!(value::ThermalGenerationCost, val) = value.start_up = val
-"""Set [`ThermalGenerationCost`](@ref) `shut_down`."""
+"""Set the `shut_down` field of [`ThermalGenerationCost`](@ref)."""
set_shut_down!(value::ThermalGenerationCost, val) = value.shut_down = val
diff --git a/src/models/cost_functions/operational_cost.jl b/src/models/cost_functions/operational_cost.jl
index 170b132d8a..f0b55fe303 100644
--- a/src/models/cost_functions/operational_cost.jl
+++ b/src/models/cost_functions/operational_cost.jl
@@ -1,10 +1,9 @@
"""
-Supertype for operational cost representations
+ OperationalCost
-Current abstract type for representing operational costs associated with power system devices.
-- [`OfferCurveCost`](@ref)
+Abstract supertype for all operational cost representations.
-Current concrete types include:
+Concrete subtypes:
- [`ThermalGenerationCost`](@ref)
- [`HydroGenerationCost`](@ref)
- [`RenewableGenerationCost`](@ref)
diff --git a/src/models/devices.jl b/src/models/devices.jl
index 682559a5cf..1333aa8674 100644
--- a/src/models/devices.jl
+++ b/src/models/devices.jl
@@ -1,5 +1,11 @@
"""
-This function add a service to the component without checking if the component and the service are attached to the same system
+Add a [`Service`](@ref) to a [`Device`](@ref) without checking that both are attached to the same [`System`](@ref).
+
+# Arguments
+- `device::Device`: The device to which the service is added.
+- `service::Service`: The service to add.
+
+See also: [`add_service!`](@ref)
"""
function add_service_internal!(device::Device, service::Service)
services = get_services(device)
@@ -36,7 +42,13 @@ end
"""
Remove a service from a device.
-Throws ArgumentError if the service is not attached to the device.
+Throws `ArgumentError` if the service is not attached to the device.
+
+# Arguments
+- `device::Device`: The device from which to remove the service.
+- `service::Service`: The service to remove.
+
+See also: [`clear_services!`](@ref), [`has_service`](@ref)
"""
function remove_service!(device::Device, service::Service)
if !_remove_service!(device, service)
@@ -50,6 +62,12 @@ end
"""
Return true if the service is attached to the device.
+
+# Arguments
+- `device::Device`: The device.
+- `service::Service`: The service to check.
+
+See also: [`has_service` by type](@ref has_service(::Device, ::Type{T}) where {T <: Service})
"""
function has_service(device::Device, service::Service)
for _service in get_services(device)
@@ -62,7 +80,15 @@ function has_service(device::Device, service::Service)
end
"""
-Return true if a service with type T is attached to the device.
+Return true if a service of type `T` is attached to the device.
+
+Returns `false` immediately if the device does not support services.
+
+# Arguments
+- `device::Device`: The device.
+- `T::Type{<:Service}`: The service type to check for.
+
+See also: [`has_service` by instance](@ref has_service(::Device, ::Service))
"""
function has_service(device::Device, ::Type{T}) where {T <: Service}
if !supports_services(device)
@@ -103,6 +129,13 @@ end
"""
Remove all services attached to the device.
+
+This is a no-op if the device does not support services.
+
+# Arguments
+- `device::Device`: The device.
+
+See also: [`remove_service!`](@ref), [`get_services`](@ref)
"""
function clear_services!(device::Device)
if !supports_services(device)
@@ -114,13 +147,26 @@ function clear_services!(device::Device)
return
end
-""" Ensures that reservoirs cannot provide services """
+"""
+Return false since [`HydroReservoir`](@ref) devices cannot provide services.
+
+See also: [`supports_services` for `Device`](@ref supports_services(::Device)),
+[`supports_services` for `StaticInjection`](@ref supports_services(::StaticInjection)),
+[`supports_services` for `ACBranch`](@ref supports_services(::ACBranch)),
+[`supports_services` for `DynamicInjection`](@ref supports_services(::DynamicInjection))
+"""
supports_services(::HydroReservoir) = false
"""
-Remove a reservoir from a device.
+Remove a turbine from a hydro reservoir.
+
+Throws `ArgumentError` if the turbine is not attached to the reservoir.
-Throws ArgumentError if the reservoir is not attached to the device.
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+- `device::HydroTurbine`: The turbine to remove.
+
+See also: [`clear_turbines!`](@ref), [`has_upstream_turbine`](@ref), [`has_downstream_turbine`](@ref)
"""
function remove_turbine!(reservoir::HydroReservoir, device::HydroTurbine)
if !_remove_turbine!(reservoir, device)
@@ -133,7 +179,13 @@ function remove_turbine!(reservoir::HydroReservoir, device::HydroTurbine)
end
"""
-Return true if the reservoir has attached the upstream turbine.
+Return true if `turbine` is among the upstream turbines of `reservoir`.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+- `turbine::HydroUnit`: The turbine to check.
+
+See also: [`has_upstream_turbine` for any turbine](@ref has_upstream_turbine(::HydroReservoir)), [`get_upstream_turbines`](@ref)
"""
function has_upstream_turbine(reservoir::HydroReservoir, turbine::HydroUnit)
for _turbine in get_upstream_turbines(reservoir)
@@ -146,7 +198,13 @@ function has_upstream_turbine(reservoir::HydroReservoir, turbine::HydroUnit)
end
"""
-Return true if the reservoir has attached the upstream turbine.
+Return true if `turbine` is among the downstream turbines of `reservoir`.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+- `turbine::HydroUnit`: The turbine to check.
+
+See also: [`has_downstream_turbine` for any turbine](@ref has_downstream_turbine(::HydroReservoir)), [`get_downstream_turbines`](@ref)
"""
function has_downstream_turbine(reservoir::HydroReservoir, turbine::HydroUnit)
for _turbine in get_downstream_turbines(reservoir)
@@ -160,6 +218,11 @@ end
"""
Return true if any upstream hydro unit is attached to the reservoir.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+
+See also: [`has_upstream_turbine` for a specific turbine](@ref has_upstream_turbine(::HydroReservoir, ::HydroUnit))
"""
function has_upstream_turbine(reservoir::HydroReservoir)
return !isempty(get_upstream_turbines(reservoir))
@@ -167,6 +230,11 @@ end
"""
Return true if any downstream hydro unit is attached to the reservoir.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+
+See also: [`has_downstream_turbine` for a specific turbine](@ref has_downstream_turbine(::HydroReservoir, ::HydroUnit))
"""
function has_downstream_turbine(reservoir::HydroReservoir)
return !isempty(get_downstream_turbines(reservoir))
@@ -208,7 +276,14 @@ function _remove_turbine!(reservoir::HydroReservoir, device::HydroUnit)
end
"""
-Remove all turbines attached to the reservoir.
+Remove all turbines attached to the hydro reservoir.
+
+Clears both upstream and downstream turbine associations from the reservoir.
+
+# Arguments
+- `device::HydroReservoir`: The hydro reservoir.
+
+See also: [`get_upstream_turbines`](@ref), [`get_downstream_turbines`](@ref)
"""
function clear_turbines!(device::HydroReservoir)
turbines = get_upstream_turbines(device)
diff --git a/src/models/dynamic_branch.jl b/src/models/dynamic_branch.jl
index 2fa1511427..688f722616 100644
--- a/src/models/dynamic_branch.jl
+++ b/src/models/dynamic_branch.jl
@@ -1,14 +1,19 @@
"""
-Extends the branch type to add the information required for dynamic modeling of branches. Includes the fields for the states and the number of states
+ DynamicBranch
+Extends an [`ACTransmission`](@ref) branch with the information required for dynamic modeling.
# Arguments
-- `branch::ACTransmission`
+$(TYPEDFIELDS)
"""
mutable struct DynamicBranch <: ACTransmission
+ "The static AC transmission branch that this struct extends with dynamic modeling data"
branch::ACTransmission
+ "Number of dynamic states"
n_states::Int
+ "Names of the dynamic states"
states::Vector{Symbol}
+ "(**Do not modify.**) PowerSystems.jl internal reference"
internal::IS.InfrastructureSystemsInternal
function DynamicBranch(branch, n_states, states, internal)
@@ -41,74 +46,74 @@ function DynamicBranch(::Nothing)
return DynamicBranch(Line(nothing))
end
-"Get branch"
+"""Return the underlying [`ACTransmission`](@ref) branch of a [`DynamicBranch`](@ref)."""
get_branch(value::DynamicBranch) = value.branch
-"Get n_states"
+"""Return the number of dynamic states of a [`DynamicBranch`](@ref)."""
get_n_states(value::DynamicBranch) = value.n_states
-"Get states"
+"""Return the vector of dynamic state symbols of a [`DynamicBranch`](@ref)."""
get_states(value::DynamicBranch) = value.states
-"""Get DynamicBranch internal."""
+"""Return the [`InfrastructureSystemsInternal`](@ref) of a [`DynamicBranch`](@ref)."""
get_internal(value::DynamicBranch) = value.internal
get_name(value::DynamicBranch) = IS.get_name(value.branch)
-"""Get DynamicBranch available."""
+"""Return `available` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_available(value::DynamicBranch) = get_available(value.branch)
-"""Get DynamicBranch active_power_flow."""
+"""Return `active_power_flow` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_active_power_flow(value::DynamicBranch) = get_active_power(value.branch)
-"""Get DynamicBranch reactive_power_flow."""
+"""Return `reactive_power_flow` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_reactive_power_flow(value::DynamicBranch) = get_reactive_power(value.branch)
-"""Get DynamicBranch arc."""
+"""Return the [`Arc`](@ref) from the underlying branch of a [`DynamicBranch`](@ref)."""
get_arc(value::DynamicBranch) = get_arc(value.branch)
-"""Get DynamicBranch r."""
+"""Return resistance `r` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_r(value::DynamicBranch) = get_r(value.branch)
-"""Get DynamicBranch x."""
+"""Return reactance `x` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_x(value::DynamicBranch) = get_x(value.branch)
-"""Get DynamicBranch b."""
+"""Return susceptance `b` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_b(value::DynamicBranch) = get_b(value.branch)
-"""Get DynamicBranch A rating."""
+"""Return the A-side `rating` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_rating(value::DynamicBranch) = get_rating(value.branch)
-"""Get DynamicBranch angle_limits."""
+"""Return `angle_limits` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_angle_limits(value::DynamicBranch) = get_angle_limits(value.branch)
-"""Get DynamicBranch B rating."""
+"""Return the B-side `rating` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_rating_b(value::DynamicBranch) = get_rating_b(value.branch)
-"""Get DynamicBranch C rating."""
+"""Return the C-side `rating` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_rating_c(value::DynamicBranch) = get_rating_c(value.branch)
-"""Get DynamicBranch services."""
+"""Return `services` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_services(value::DynamicBranch) = get_services(value.branch)
-"""Get DynamicBranch ext."""
+"""Return `ext` from the underlying branch of a [`DynamicBranch`](@ref)."""
get_ext(value::DynamicBranch) = get_ext(value.branch)
-"""Set DynamicBranch available."""
+"""Set `available` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_available!(value::DynamicBranch, val::Bool) = set_available!(value.branch, val)
-"""Set DynamicBranch active_power_flow."""
+"""Set `active_power_flow` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_active_power_flow!(value::DynamicBranch, val::Float64) =
set_active_power_flow!(value.branch, val)
-"""Set DynamicBranch reactive_power_flow."""
+"""Set `reactive_power_flow` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_reactive_power_flow!(value::DynamicBranch, val::Float64) =
set_reactive_power_flow!(value.branch, val)
-"""Set DynamicBranch arc."""
+"""Set the [`Arc`](@ref) on the underlying branch of a [`DynamicBranch`](@ref)."""
set_arc!(value::DynamicBranch, val::Arc) = set_arc!(value.branch, val)
-"""Set DynamicBranch r."""
+"""Set resistance `r` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_r!(value::DynamicBranch, val::Float64) = set_r!(value.branch, val)
-"""Set DynamicBranch x."""
+"""Set reactance `x` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_x!(value::DynamicBranch, val::Float64) = set_x!(value.branch, val)
-"""Set DynamicBranch b."""
+"""Set susceptance `b` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_b!(value::DynamicBranch, val) = set_b!(value.branch, val)
-"""Set DynamicBranch rating."""
+"""Set the `rating` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_rating!(value::DynamicBranch, val::Float64) = set_rating!(value.branch, val)
-"""Set DynamicBranch angle_limits."""
+"""Set `angle_limits` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_angle_limits!(
value::DynamicBranch,
val::NamedTuple{(:min, :max), Tuple{Float64, Float64}},
) = set_angle_limits!(value.branch, val)
-"""Set DynamicBranch services."""
+"""Set `services` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_services!(value::DynamicBranch, val::Vector{Service}) = set_services!(value.branch, val)
-"""Set DynamicBranch ext."""
+"""Set `ext` on the underlying branch of a [`DynamicBranch`](@ref)."""
set_ext!(value::DynamicBranch, val::Dict{String, Any}) = set_ext!(value.branch, val)
-"Set branch"
+"""Set the underlying [`ACTransmission`](@ref) branch of a [`DynamicBranch`](@ref)."""
set_branch!(value::DynamicBranch, val::ACTransmission) = value.branch = val
-"Set n_states"
+"""Set the number of dynamic states on a [`DynamicBranch`](@ref)."""
set_n_states!(value::DynamicBranch, val::Int) = value.n_states = val
-"Set states"
+"""Set the vector of dynamic state symbols on a [`DynamicBranch`](@ref)."""
set_states!(value::DynamicBranch, val::Vector{Symbol}) = value.states = val
diff --git a/src/models/dynamic_generator.jl b/src/models/dynamic_generator.jl
index b690f88ca9..5fb9e99f03 100644
--- a/src/models/dynamic_generator.jl
+++ b/src/models/dynamic_generator.jl
@@ -36,18 +36,7 @@ and Power System Stabilizer ([PSS](@ref)). It must be attached to a
response.
# Arguments
-- `name::String`: Name of generator.
-- `ω_ref::Float64`: Frequency reference set-point in pu.
-- `machine <: Machine`: [Machine](@ref) model for modeling the electro-magnetic phenomena.
-- `shaft <: Shaft`: [Shaft](@ref) model for modeling the electro-mechanical phenomena.
-- `avr <: AVR`: [AVR](@ref) model of the excitacion system.
-- `prime_mover <: TurbineGov`: [Prime Mover and Turbine Governor model](@ref "TurbineGov") for mechanical power.
-- `pss <: PSS`: [PSS](@ref) model.
-- `base_power::Float64`: (default: `100.0`) Base power of the unit (MVA) for [per unitization](@ref per_unit). Although this has a default, in almost all cases `base_power` should be updated to equal the `base_power` field of the [`StaticInjection`](@ref) device that this dynamic generator will be attached to.
-- `n_states::Int`: (**Do not modify.**) Number of states (will depend on the inputs above).
-- `states::Vector{Symbol}`: (**Do not modify.**) Vector of states (will depend on the inputs above).
-- `ext::Dict{String, Any}`: (default: `Dict{String, Any}()`) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference
+$(TYPEDFIELDS)
"""
mutable struct DynamicGenerator{
M <: Machine,
@@ -56,17 +45,29 @@ mutable struct DynamicGenerator{
TG <: TurbineGov,
P <: PSS,
} <: DynamicInjection
+ "Name of the generator"
name::String
+ "Frequency reference set-point in pu"
ω_ref::Float64
+ "[Machine](@ref) model for modeling the electro-magnetic phenomena"
machine::M
+ "[Shaft](@ref) model for modeling the electro-mechanical phenomena"
shaft::S
+ "[AVR](@ref) model of the excitation system"
avr::A
+ "[Prime Mover and Turbine Governor](@ref TurbineGov) model for mechanical power"
prime_mover::TG
+ "[PSS](@ref) model"
pss::P
+ "(default: `100.0`) Base power of the unit (MVA) for [per unitization](@ref per_unit). In almost all cases, this should match the `base_power` of the attached [`StaticInjection`](@ref) device"
base_power::Float64
+ "(**Do not modify.**) Number of states (will depend on the components above)"
n_states::Int
+ "(**Do not modify.**) Vector of states (will depend on the components above)"
states::Vector{Symbol}
+ "(default: `Dict{String, Any}()`) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation"
ext::Dict{String, Any}
+ "(**Do not modify.**) PowerSystems.jl internal reference"
internal::InfrastructureSystemsInternal
end
@@ -134,15 +135,15 @@ get_name(device::DynamicGenerator) = device.name
get_states(device::DynamicGenerator) = device.states
get_n_states(device::DynamicGenerator) = device.n_states
get_ω_ref(device::DynamicGenerator) = device.ω_ref
-"""Get the [`Machine`](@ref) component of a [`DynamicGenerator`](@ref)."""
+"""Return the [`Machine`](@ref) component of a [`DynamicGenerator`](@ref)."""
get_machine(device::DynamicGenerator) = device.machine
-"""Get the [`Shaft`](@ref) component of a [`DynamicGenerator`](@ref)."""
+"""Return the [`Shaft`](@ref) component of a [`DynamicGenerator`](@ref)."""
get_shaft(device::DynamicGenerator) = device.shaft
-"""Get the [`AVR`](@ref) component of a [`DynamicGenerator`](@ref)."""
+"""Return the [`AVR`](@ref) component of a [`DynamicGenerator`](@ref)."""
get_avr(device::DynamicGenerator) = device.avr
-"""Get the [`TurbineGov`](@ref) (prime mover) component of a [`DynamicGenerator`](@ref)."""
+"""Return the [`TurbineGov`](@ref) (prime mover) component of a [`DynamicGenerator`](@ref)."""
get_prime_mover(device::DynamicGenerator) = device.prime_mover
-"""Get the [`PSS`](@ref) component of a [`DynamicGenerator`](@ref)."""
+"""Return the [`PSS`](@ref) component of a [`DynamicGenerator`](@ref)."""
get_pss(device::DynamicGenerator) = device.pss
get_base_power(device::DynamicGenerator) = device.base_power
get_ext(device::DynamicGenerator) = device.ext
@@ -181,7 +182,12 @@ function get_degov1_states(droop_flag::Int)
end
"""
-Get the frequency droop parameter from the prime mover of a [`DynamicGenerator`](@ref).
+Return the frequency droop parameter from the prime mover of a [`DynamicGenerator`](@ref).
+
+# Arguments
+- `dyn_gen::DynamicGenerator`: The dynamic generator.
+
+See also: [`get_frequency_droop`](@ref get_frequency_droop(::V) where {V <: DynamicInjection})
"""
function get_frequency_droop(dyn_gen::DynamicGenerator)
return get_frequency_droop(get_prime_mover(dyn_gen))
diff --git a/src/models/dynamic_inverter.jl b/src/models/dynamic_inverter.jl
index be7e6b595c..a5a743479a 100644
--- a/src/models/dynamic_inverter.jl
+++ b/src/models/dynamic_inverter.jl
@@ -1,3 +1,10 @@
+"""
+ InverterComponent
+
+Abstract type for all sub-components used to compose a [`DynamicInverter`](@ref) device.
+
+See also: [`DynamicComponent`](@ref), [`DynamicInverter`](@ref)
+"""
abstract type InverterComponent <: DynamicComponent end
"""
@@ -43,20 +50,7 @@ It must be attached to a
response.
# Arguments
-- `name::String`: Name of inverter.
-- `ω_ref::Float64`: Frequency reference set-point in pu.
-- `converter <: Converter`: Converter model for the PWM transformation.
-- `outer_control <: OuterControl`: An [OuterControl](@ref) controller model.
-- `inner_control <: InnerControl`: An [InnerControl](@ref) controller model.
-- `dc_source <: DCSource`: [DCSource](@ref) model.
-- `freq_estimator <: FrequencyEstimator`: a [FrequencyEstimator](@ref) (typically a [PLL](@ref P)) model.
-- `filter <: Filter`: [Filter](@ref) model.
-- `limiter <: Union{nothing, OutputCurrentLimiter}`: (default: nothing) Inner Control [Current Limiter](@ref OutputCurrentLimiter) model
-- `base_power::Float64`: (default: `100.0`) Base power of the unit (MVA) for [per unitization](@ref per_unit). Although this has a default, in almost all cases `base_power` should be updated to equal the `base_power` field of the [`StaticInjection`](@ref) device that this dynamic generator will be attached to.
-- `n_states::Int`: (**Do not modify.**) Number of states (will depend on the inputs above).
-- `states::Vector{Symbol}`: (**Do not modify.**) Vector of states (will depend on the inputs above).
-- `ext::Dict{String, Any}`: (default: `Dict{String, Any}()`) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference
+$(TYPEDFIELDS)
"""
mutable struct DynamicInverter{
C <: Converter,
@@ -67,19 +61,33 @@ mutable struct DynamicInverter{
F <: Filter,
L <: Union{Nothing, OutputCurrentLimiter},
} <: DynamicInjection
+ "Name of the inverter"
name::String
+ "Frequency reference set-point in pu"
ω_ref::Float64
+ "[Converter](@ref) model for the PWM transformation"
converter::C
+ "[OuterControl](@ref) controller model"
outer_control::O
+ "[InnerControl](@ref) controller model"
inner_control::IC
+ "[DCSource](@ref) model"
dc_source::DC
+ "[FrequencyEstimator](@ref) (typically a phase-locked loop (PLL)) model"
freq_estimator::P
+ "[Filter](@ref) model"
filter::F
+ "(default: `nothing`) Inner Control [Current Limiter](@ref OutputCurrentLimiter) model"
limiter::L
+ "(default: `100.0`) Base power of the unit (MVA) for [per unitization](@ref per_unit). In almost all cases, this should match the `base_power` of the attached [`StaticInjection`](@ref) device"
base_power::Float64
+ "(**Do not modify.**) Number of states (will depend on the components above)"
n_states::Int
+ "(**Do not modify.**) Vector of states (will depend on the components above)"
states::Vector{Symbol}
+ "(default: `Dict{String, Any}()`) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation"
ext::Dict{String, Any}
+ "(**Do not modify.**) PowerSystems.jl internal reference"
internal::InfrastructureSystemsInternal
end
@@ -232,17 +240,17 @@ get_ω_ref(device::DynamicInverter) = device.ω_ref
get_ext(device::DynamicInverter) = device.ext
get_states(device::DynamicInverter) = device.states
get_n_states(device::DynamicInverter) = device.n_states
-"""Get the [`Converter`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`Converter`](@ref) component of a [`DynamicInverter`](@ref)."""
get_converter(device::DynamicInverter) = device.converter
-"""Get the [`OuterControl`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`OuterControl`](@ref) component of a [`DynamicInverter`](@ref)."""
get_outer_control(device::DynamicInverter) = device.outer_control
-"""Get the [`InnerControl`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`InnerControl`](@ref) component of a [`DynamicInverter`](@ref)."""
get_inner_control(device::DynamicInverter) = device.inner_control
-"""Get the [`DCSource`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`DCSource`](@ref) component of a [`DynamicInverter`](@ref)."""
get_dc_source(device::DynamicInverter) = device.dc_source
-"""Get the [`FrequencyEstimator`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`FrequencyEstimator`](@ref) component of a [`DynamicInverter`](@ref)."""
get_freq_estimator(device::DynamicInverter) = device.freq_estimator
-"""Get the [`Filter`](@ref) component of a [`DynamicInverter`](@ref)."""
+"""Return the [`Filter`](@ref) component of a [`DynamicInverter`](@ref)."""
get_filter(device::DynamicInverter) = device.filter
get_limiter(device::DynamicInverter) = device.limiter
get_base_power(device::DynamicInverter) = device.base_power
diff --git a/src/models/dynamic_inverter_components.jl b/src/models/dynamic_inverter_components.jl
index f9adbe4b4c..0d0af5b6b4 100644
--- a/src/models/dynamic_inverter_components.jl
+++ b/src/models/dynamic_inverter_components.jl
@@ -52,5 +52,29 @@ Concrete subtypes include [`MagnitudeOutputCurrentLimiter`](@ref),
"""
abstract type OutputCurrentLimiter <: DynamicInverterComponent end
+"""
+ ActivePowerControl
+
+Supertype for all active power control models used in [`OuterControl`](@ref) of a
+[`DynamicInverter`](@ref).
+
+Concrete subtypes include [`ActivePowerDroop`](@ref), [`ActivePowerPI`](@ref),
+[`VirtualInertia`](@ref), [`ActiveVirtualOscillator`](@ref), and
+[`ActiveRenewableControllerAB`](@ref).
+
+See also: [`ReactivePowerControl`](@ref), [`OuterControl`](@ref)
+"""
abstract type ActivePowerControl <: DeviceParameter end
+
+"""
+ ReactivePowerControl
+
+Supertype for all reactive power control models used in [`OuterControl`](@ref) of a
+[`DynamicInverter`](@ref).
+
+Concrete subtypes include [`ReactivePowerDroop`](@ref), [`ReactivePowerPI`](@ref),
+[`ReactiveVirtualOscillator`](@ref), and [`ReactiveRenewableControllerAB`](@ref).
+
+See also: [`ActivePowerControl`](@ref), [`OuterControl`](@ref)
+"""
abstract type ReactivePowerControl <: DeviceParameter end
diff --git a/src/models/dynamic_loads.jl b/src/models/dynamic_loads.jl
index a472a72c27..9a7597cdea 100644
--- a/src/models/dynamic_loads.jl
+++ b/src/models/dynamic_loads.jl
@@ -1,3 +1,9 @@
+"""
+Return the state names and count for a `GenericDER` model based on `Qref_Flag`.
+
+# Arguments
+- `Qref_Flag::Int`: Reactive power reference flag (1, 2, or 3).
+"""
function get_GenericDER_states(Qref_Flag::Int)
if Qref_Flag == 1
return [:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9], 9
@@ -8,6 +14,12 @@ function get_GenericDER_states(Qref_Flag::Int)
end
end
+"""
+Return the state names and count for an `AggregateDistributedGenerationA` model based on `Freq_Flag`.
+
+# Arguments
+- `Freq_Flag::Int`: Frequency flag (0 or 1).
+"""
function get_AggregateDistributedGenerationA_states(Freq_Flag::Int)
if Freq_Flag == 0
return [:Vmeas, :Pmeas, :Q_V, :Iq, :Mult, :Fmeas, :Ip], 7
@@ -18,6 +30,15 @@ function get_AggregateDistributedGenerationA_states(Freq_Flag::Int)
end
end
+"""
+Calculate the torque constant `C` for an induction motor model from load-torque coefficients.
+
+Throws an error if any of A, B, or C is negative (coefficients must be non-negative and sum to 1).
+
+# Arguments
+- `A::Float64`: Load torque coefficient for the constant component.
+- `B::Float64`: Load torque coefficient for the speed-proportional component.
+"""
function calculate_IM_torque_params(A::Float64, B::Float64)
C = 1.0 - A - B
if A < 0.0 || B < 0.0 || C < 0.0
diff --git a/src/models/dynamic_models.jl b/src/models/dynamic_models.jl
index b18294e783..ceca21b151 100644
--- a/src/models/dynamic_models.jl
+++ b/src/models/dynamic_models.jl
@@ -1,22 +1,36 @@
"""
-Abstract type for all components used to compose a [`DynamicInjection`](@ref) device
+ DynamicComponent
+
+Abstract type for all sub-components used to compose a [`DynamicInjection`](@ref) device.
+
+Examples include machine models ([`BaseMachine`](@ref)), AVR models ([`AVRFixed`](@ref)),
+and turbine governor models ([`TGFixed`](@ref)).
+
+See also: [`DynamicInjection`](@ref)
"""
abstract type DynamicComponent <: DeviceParameter end
"""
-Abstract type for all [Dynamic Devices](@ref)
+ DynamicInjection
+
+Abstract type for all [Dynamic Devices](@ref dynamic_data).
A [dynamic](@ref D) [injection](@ref I) is the continuous time response of a generator,
-typically modeled with differential equations.
-
-`DynamicInjection` components can added on to [`StaticInjection`](@ref) components,
+typically modeled with differential equations.
+
+`DynamicInjection` models are attached to [`StaticInjection`](@ref) components,
which together define all the information needed to model the device in a dynamic
simulation.
+
+See also: [`DynamicComponent`](@ref), [`StaticInjection`](@ref)
"""
abstract type DynamicInjection <: Device end
"""
-Return all the dynamic components of a [`DynamicInjection`](@ref) device
+Return an iterator of all [`DynamicComponent`](@ref) fields of a [`DynamicInjection`](@ref) device.
+
+# Arguments
+- `device::DynamicInjection`: The dynamic injection device.
"""
function get_dynamic_components(device::T) where {T <: DynamicInjection}
return (
@@ -25,16 +39,45 @@ function get_dynamic_components(device::T) where {T <: DynamicInjection}
)
end
+"""
+Return false since dynamic injection devices do not support services.
+
+See also: [`supports_services` for `Device`](@ref supports_services(::Device)),
+[`supports_services` for `StaticInjection`](@ref supports_services(::StaticInjection)),
+[`supports_services` for `ACBranch`](@ref supports_services(::ACBranch)),
+[`supports_services` for `HydroReservoir`](@ref supports_services(::HydroReservoir))
+"""
supports_services(::DynamicInjection) = false
+
+"""
+Return an empty vector of states for a [`DynamicInjection`](@ref) device.
+"""
get_states(::DynamicInjection) = Vector{Symbol}()
+
"""
- Default implementation of get_state_types for dynamic components. Assumes all states are
- Differential
+Return the [`StateTypes`](@ref) for each state of a [`DynamicComponent`](@ref).
+
+The default implementation returns `StateTypes.Differential` for all states.
+Subtypes may override this method to specify different state types.
+
+# Arguments
+- `d::DynamicComponent`: The dynamic component.
"""
function get_states_types(d::DynamicComponent)
return fill(StateTypes.Differential, get_n_states(d))
end
+"""
+Return the frequency droop of a [`DynamicInjection`](@ref) device.
+
+Throws `ArgumentError` if not implemented for the specific subtype.
+
+# Arguments
+- `d::DynamicInjection`: The dynamic injection device.
+
+See also: [`get_frequency_droop` for `StaticInjection`](@ref get_frequency_droop(::StaticInjection)),
+[`get_frequency_droop` for `DynamicGenerator`](@ref get_frequency_droop(::DynamicGenerator))
+"""
function get_frequency_droop(::V) where {V <: DynamicInjection}
throw(
ArgumentError(
diff --git a/src/models/generated/RenewableDispatch.jl b/src/models/generated/RenewableDispatch.jl
index 4784c16f1f..e0cf5ba871 100644
--- a/src/models/generated/RenewableDispatch.jl
+++ b/src/models/generated/RenewableDispatch.jl
@@ -27,7 +27,7 @@ A renewable (e.g., wind or solar) generator whose output can be curtailed to sat
These generators can also participate in reserves markets, including upwards reserves by proactively curtailing some available power (based on its [`max_active_power` time series](@ref ts_data)). Example uses include: a utility-scale wind or solar generator whose PPA allows curtailment. For non-curtailable or must-take renewables, see [`RenewableNonDispatch`](@ref).
-Renewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::T) where {T <: RenewableGen})
+Renewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::RenewableGen))
# Arguments
- `name::String`: Name of the component. Components of the same type (e.g., `PowerLoad`) must have unique names, but components of different types (e.g., `PowerLoad` and `ACBus`) can have the same name
diff --git a/src/models/generated/RenewableNonDispatch.jl b/src/models/generated/RenewableNonDispatch.jl
index 8efed05546..f0a9602885 100644
--- a/src/models/generated/RenewableNonDispatch.jl
+++ b/src/models/generated/RenewableNonDispatch.jl
@@ -25,7 +25,7 @@ A non-dispatchable (i.e., non-curtailable or must-take) renewable generator.
Its output is equal to its [`max_active_power` time series](@ref ts_data) by default. Example use: an aggregation of behind-the-meter distributed energy resources like rooftop solar. For curtailable or downward dispatachable generation, see [`RenewableDispatch`](@ref).
-Renewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::T) where {T <: RenewableGen})
+Renewable generators do not have a `max_active_power` parameter, which is instead calculated when calling [`get_max_active_power()`](@ref get_max_active_power(d::RenewableGen))
# Arguments
- `name::String`: Name of the component. Components of the same type (e.g., `PowerLoad`) must have unique names, but components of different types (e.g., `PowerLoad` and `ACBus`) can have the same name
diff --git a/src/models/generated/SEXS.jl b/src/models/generated/SEXS.jl
index a2a8fc9560..70c4f3337b 100644
--- a/src/models/generated/SEXS.jl
+++ b/src/models/generated/SEXS.jl
@@ -29,7 +29,9 @@ Parameters of Simplified Excitation System Model - SEXS in PSSE
- `V_lim::MinMax`: Field voltage limits
- `V_ref::Float64`: (default: `1.0`) Reference Voltage Set-point (pu), validation range: `(0, nothing)`
- `ext::Dict{String, Any}`: (default: `Dict{String, Any}()`) An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation.
-- `states::Vector{Symbol}`: (**Do not modify.**) The [states](@ref S) are: Vf: Voltage field, Vr: Lead-lag state
+- `states::Vector{Symbol}`: (**Do not modify.**) The [states](@ref S) are:
+ Vf: Voltage field,
+ Vr: Lead-lag state
- `n_states::Int`: (**Do not modify.**) SEXS has 2 states
- `states_types::Vector{StateTypes}`: (**Do not modify.**) SEXS has 2 [differential](@ref states_list) [states](@ref S)
- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference
@@ -49,7 +51,9 @@ mutable struct SEXS <: AVR
V_ref::Float64
"An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation."
ext::Dict{String, Any}
- "(**Do not modify.**) The [states](@ref S) are: Vf: Voltage field, Vr: Lead-lag state"
+ "(**Do not modify.**) The [states](@ref S) are:
+ Vf: Voltage field,
+ Vr: Lead-lag state"
states::Vector{Symbol}
"(**Do not modify.**) SEXS has 2 states"
n_states::Int
diff --git a/src/models/generation.jl b/src/models/generation.jl
index f8be2dd13f..3a5c0f4517 100644
--- a/src/models/generation.jl
+++ b/src/models/generation.jl
@@ -1,21 +1,61 @@
-""" Supertype for all generation technologies"""
+"""
+ Generator
+
+Supertype for all generation technologies.
+
+Abstract subtypes include [`HydroGen`](@ref), [`RenewableGen`](@ref), and
+[`ThermalGen`](@ref).
+
+See also: [`StaticInjection`](@ref), [`HydroGen`](@ref), [`RenewableGen`](@ref), [`ThermalGen`](@ref)
+"""
abstract type Generator <: StaticInjection end
const Generators = Array{<:Generator, 1}
-""" Supertype for all Hydropower generation technologies"""
+"""
+ HydroGen
+
+Supertype for all hydropower generation technologies.
+
+The abstract subtype [`HydroUnit`](@ref) covers turbine-based units
+([`HydroTurbine`](@ref), [`HydroPumpTurbine`](@ref)). The concrete subtype
+[`HydroDispatch`](@ref) also inherits directly from `HydroGen`.
+
+See also: [`Generator`](@ref), [`HydroUnit`](@ref), [`HydroReservoir`](@ref)
+"""
abstract type HydroGen <: Generator end
-""" Supertype for all Hydropower generation technologies that are represented as units (i.e. HydroTurbine and HydroPumpTurbine)"""
+"""
+ HydroUnit
+
+Supertype for all hydropower generation technologies represented as turbine-based units.
+
+Concrete subtypes include [`HydroTurbine`](@ref) and [`HydroPumpTurbine`](@ref).
+
+See also: [`HydroGen`](@ref), [`HydroReservoir`](@ref)
+"""
abstract type HydroUnit <: HydroGen end
"""
-Supertype for all renewable generation technologies
+ RenewableGen
+
+Supertype for all renewable generation technologies.
+
+Concrete subtypes include [`RenewableDispatch`](@ref) and [`RenewableNonDispatch`](@ref).
+All subtypes must implement `get_rating` and `get_power_factor` methods.
-Requires the implementation of `get_rating`and `get_power_factor` methods
+See also: [`Generator`](@ref), [`ThermalGen`](@ref)
"""
abstract type RenewableGen <: Generator end
-""" Supertype for all Thermal generation technologies"""
+"""
+ ThermalGen
+
+Supertype for all thermal generation technologies.
+
+Concrete subtypes include [`ThermalStandard`](@ref) and [`ThermalMultiStart`](@ref).
+
+See also: [`Generator`](@ref), [`HydroGen`](@ref), [`RenewableGen`](@ref)
+"""
abstract type ThermalGen <: Generator end
function IS.get_limits(
@@ -28,15 +68,27 @@ function IS.get_limits(
end
"""
-Return the max active power for the Renewable Generation calculated as the `rating` * `power_factor`
+Return the maximum active power for a [`RenewableGen`](@ref) in per unit on the device base,
+calculated as [`get_rating`](@ref) × [`get_power_factor`](@ref).
+
+# Arguments
+- `d::RenewableGen`: The renewable generation device.
+
+See also: [`get_max_reactive_power`](@ref get_max_reactive_power(d::RenewableGen))
"""
-function get_max_active_power(d::T) where {T <: RenewableGen}
+function get_max_active_power(d::RenewableGen)
return get_rating(d) * get_power_factor(d)
end
"""
-Return the max reactive power for the Renewable Generation calculated as the `rating` * sin(acos(`power_factor`))
+Return the maximum reactive power for a [`RenewableGen`](@ref) in per unit on the device base,
+calculated as [`get_rating`](@ref) × sin(acos([`get_power_factor`](@ref))).
+
+# Arguments
+- `d::RenewableGen`: The renewable generation device.
+
+See also: [`get_max_active_power`](@ref get_max_active_power(d::RenewableGen))
"""
-function get_max_reactive_power(d::T) where {T <: RenewableGen}
+function get_max_reactive_power(d::RenewableGen)
return get_rating(d) * sin(acos(get_power_factor(d)))
end
diff --git a/src/models/injection.jl b/src/models/injection.jl
index 77a5046697..9d17196f1b 100644
--- a/src/models/injection.jl
+++ b/src/models/injection.jl
@@ -1,13 +1,21 @@
"""
-Any StaticInjection struct that wants to support dynamic injectors must implement this
-method to set the value.
+Set the dynamic injector for a static injection device.
-The method is only for internal uses.
+Passes `nothing` to remove an existing dynamic injector from the device. Throws
+`ArgumentError` if the device already has a dynamic injector and a non-`nothing` value
+is provided.
+
+# Arguments
+- `static_injector::StaticInjection`: The static injection device.
+- `dynamic_injector::Union{Nothing, DynamicInjection}`: The dynamic injector to set,
+ or `nothing` to remove it.
+
+See also: [`get_dynamic_injector`](@ref)
"""
function set_dynamic_injector!(
- static_injector::T,
- dynamic_injector::U,
-) where {T <: StaticInjection, U <: Union{Nothing, DynamicInjection}}
+ static_injector::StaticInjection,
+ dynamic_injector::Union{Nothing, DynamicInjection},
+)
current_dynamic_injector = get_dynamic_injector(static_injector)
if !isnothing(current_dynamic_injector) && !isnothing(dynamic_injector)
throw(
diff --git a/src/models/loads.jl b/src/models/loads.jl
index 6410881347..a67347a5d3 100644
--- a/src/models/loads.jl
+++ b/src/models/loads.jl
@@ -1,8 +1,39 @@
-""" Supertype for all electric loads"""
+"""
+ ElectricLoad
+
+Supertype for all electric loads.
+
+Electric loads consume power from the grid. Abstract subtypes include
+[`StaticLoad`](@ref) (fixed consumption) and [`ControllableLoad`](@ref) (adjustable
+consumption). The concrete subtype [`FixedAdmittance`](@ref) and
+[`SwitchedAdmittance`](@ref) also inherit directly from `ElectricLoad`.
+
+See also: [`StaticInjection`](@ref), [`StaticLoad`](@ref), [`ControllableLoad`](@ref)
+"""
abstract type ElectricLoad <: StaticInjection end
-""" Supertype for all [static](@ref S) electric loads"""
+"""
+ StaticLoad
+
+Supertype for all static electric loads.
+
+Static loads have fixed consumption that cannot be controlled or curtailed. Concrete
+subtypes include [`PowerLoad`](@ref), [`StandardLoad`](@ref), [`ExponentialLoad`](@ref),
+and [`MotorLoad`](@ref).
+
+See also: [`ElectricLoad`](@ref), [`ControllableLoad`](@ref)
+"""
abstract type StaticLoad <: ElectricLoad end
-""" Supertype for all controllable loads"""
+"""
+ ControllableLoad
+
+Supertype for all controllable electric loads.
+
+Controllable loads can have their consumption adjusted in response to system conditions
+or operator dispatch. Concrete subtypes include [`InterruptiblePowerLoad`](@ref),
+[`InterruptibleStandardLoad`](@ref), and [`ShiftablePowerLoad`](@ref).
+
+See also: [`StaticLoad`](@ref), [`ElectricLoad`](@ref)
+"""
abstract type ControllableLoad <: StaticLoad end
diff --git a/src/models/reserves.jl b/src/models/reserves.jl
index 86fa61b13a..221e4f6ad8 100644
--- a/src/models/reserves.jl
+++ b/src/models/reserves.jl
@@ -1,41 +1,59 @@
"""
-Used to specify if a [`Reserve`](@ref) is upwards, downwards, or symmetric
+ ReserveDirection
+
+Abstract supertype used to specify if a [`Reserve`](@ref) is upwards, downwards, or symmetric.
+
+Subtypes: [`ReserveUp`](@ref), [`ReserveDown`](@ref), [`ReserveSymmetric`](@ref)
"""
abstract type ReserveDirection end
"""
-An upwards reserve to increase generation or reduce load
+ ReserveUp
+
+An upwards reserve to increase generation or reduce load.
Upwards reserves are used when total load exceeds its expected level,
typically due to forecast errors or contingencies.
-A [`Reserve`](@ref) can be specified as a `ReserveUp` when it is defined.
+A [`Reserve`](@ref) can be specified as a [`ReserveUp`](@ref) when it is defined.
+
+See also: [`ReserveDown`](@ref), [`ReserveSymmetric`](@ref)
"""
abstract type ReserveUp <: ReserveDirection end
"""
-A downwards reserve to decrease generation or increase load
+ ReserveDown
+
+A downwards reserve to decrease generation or increase load.
Downwards reserves are used when total load falls below its expected level,
-typically due to forecast errors or contingencies. Not work
+typically due to forecast errors or contingencies.
+
+A [`Reserve`](@ref) can be specified as a [`ReserveDown`](@ref) when it is defined.
-A [`Reserve`](@ref) can be specified as a `ReserveDown` when it is defined.
+See also: [`ReserveUp`](@ref), [`ReserveSymmetric`](@ref)
"""
abstract type ReserveDown <: ReserveDirection end
"""
+ ReserveSymmetric
+
A symmetric reserve, procuring the same quantity (MW) of both upwards and downwards
-reserves
+reserves.
+
+Unlike [`ReserveUp`](@ref) and [`ReserveDown`](@ref), which can be used to specify
+different quantities of upwards and downwards reserves independently, `ReserveSymmetric`
+requires equal procurement in both directions.
-A symmetric reserve is a special case. [`ReserveUp`](@ref) and [`ReserveDown`](@ref)
-can be used individually to specify different quantities of upwards and downwards
-reserves, respectively.
+A [`Reserve`](@ref) can be specified as a [`ReserveSymmetric`](@ref) when it is defined.
-A [`Reserve`](@ref) can be specified as a `ReserveSymmetric` when it is defined.
+See also: [`ReserveUp`](@ref), [`ReserveDown`](@ref)
"""
abstract type ReserveSymmetric <: ReserveDirection end
"""
+ AbstractReserve
+
Supertype for all reserve products, both spinning and non-spinning.
Concrete subtypes include [`Reserve`](@ref) (parameterized by [`ReserveDirection`](@ref))
@@ -44,12 +62,22 @@ and [`ReserveNonSpinning`](@ref).
abstract type AbstractReserve <: Service end
"""
-A reserve product to be able to respond to unexpected disturbances,
-such as the sudden loss of a transmission line or generator.
+ Reserve{T <: ReserveDirection}
+
+Abstract parametric type for all reserve products, parameterized on direction
+`T <: `[`ReserveDirection`](@ref).
+
+Use the direction subtypes [`ReserveUp`](@ref), [`ReserveDown`](@ref), or
+[`ReserveSymmetric`](@ref) to specify the direction of the reserve product, e.g.,
+`ConstantReserve{ReserveUp}`.
+
+See also: [`ConstantReserve`](@ref), [`VariableReserve`](@ref), [`ReserveDemandCurve`](@ref)
"""
abstract type Reserve{T <: ReserveDirection} <: AbstractReserve end
"""
+ ReserveNonSpinning
+
Supertype for non-spinning (quick-start) reserve products.
Non-spinning reserves can be brought online within a short time but are not
diff --git a/src/models/serialization.jl b/src/models/serialization.jl
index b8add2b154..87b2a91be2 100644
--- a/src/models/serialization.jl
+++ b/src/models/serialization.jl
@@ -28,6 +28,9 @@ const _ENCODE_AS_UUID_B =
)
@assert length(_ENCODE_AS_UUID_A) == length(_ENCODE_AS_UUID_B)
+"""
+Return true if the value should be encoded as a UUID during serialization.
+"""
should_encode_as_uuid(val) = any(x -> val isa x, _ENCODE_AS_UUID_B)
should_encode_as_uuid(::Type{T}) where {T} = any(x -> T <: x, _ENCODE_AS_UUID_A)
@@ -60,7 +63,9 @@ function IS.serialize(component::T) where {T <: _CONTAINS_SHOULD_ENCODE}
end
"""
-Serialize the value, encoding as UUIDs where necessary.
+Serialize `val`, encoding cross-referenced components as UUIDs instead of full objects.
+
+See also: [`deserialize_uuid_handling`](@ref)
"""
function serialize_uuid_handling(val)
if should_encode_as_uuid(val)
@@ -127,7 +132,15 @@ function _check_uuid_in_component_cache(uuid::Base.UUID, component_cache)
end
"""
-Deserialize the value, converting UUIDs to components where necessary.
+Deserialize `val` of `field_type`, replacing UUID values with the corresponding components
+from `component_cache` where applicable.
+
+# Arguments
+- `field_type`: The expected type of the field.
+- `val`: The raw serialized value.
+- `component_cache`: A dictionary mapping UUIDs to already-deserialized components.
+
+See also: [`serialize_uuid_handling`](@ref)
"""
function deserialize_uuid_handling(field_type, val, component_cache)
@debug "deserialize_uuid_handling" _group = IS.LOG_GROUP_SERIALIZATION field_type val
diff --git a/src/models/services.jl b/src/models/services.jl
index 7e01066693..b9cc33db74 100644
--- a/src/models/services.jl
+++ b/src/models/services.jl
@@ -1,21 +1,27 @@
"""
-Supertype for all system services
+ Service
-Services (or ancillary services) include additional requirements and support
-to ensure reliable electricity service to customers. Common services are
-reserve products to be able to respond quickly to unexpected disturbances,
-such as the sudden loss of a transmission line or generator.
+Abstract supertype for all system services (ancillary services).
+
+Services represent additional requirements and support to ensure reliable electricity
+delivery. Examples include reserve products for responding to unexpected disturbances
+(such as the sudden loss of a generator or transmission line), automatic generation
+control, and transmission interface limits.
+
+Subtypes: [`AbstractReserve`](@ref), [`AGC`](@ref), [`TransmissionInterface`](@ref)
"""
abstract type Service <: Component end
"""
-All PowerSystems [Service](@ref) types support time series. This can be overridden for custom
-types that do not support time series.
+Return true since all [`Service`](@ref) types support time series by default.
+
+Override this method for custom types that do not support time series.
"""
supports_time_series(::Service) = true
"""
-All PowerSystems [Service](@ref) types support supplemental attributes. This can be overridden for
-custom service types that do not support supplemental attributes.
+Return true since all [`Service`](@ref) types support supplemental attributes by default.
+
+Override this method for custom service types that do not support supplemental attributes.
"""
supports_supplemental_attributes(::Service) = true
diff --git a/src/models/static_injection_subsystem.jl b/src/models/static_injection_subsystem.jl
index 1b04062821..d6e4fd680d 100644
--- a/src/models/static_injection_subsystem.jl
+++ b/src/models/static_injection_subsystem.jl
@@ -1,16 +1,19 @@
"""
-Abstract type for a subsystem that contains multiple instances of StaticInjection
+Abstract type for a grouped set of [`StaticInjection`](@ref) devices treated as a
+single injection unit.
-Subtypes must implement:
-- get_subcomponents(subsystem::StaticInjectionSubsystem)
+Subtypes must implement `get_subcomponents` to return the individual component members,
+which must be attached to the [`System`](@ref) as masked components.
-The subcomponents in subtypes must be attached to the System as masked components.
+See also: [`StaticInjection`](@ref), [`HybridSystem`](@ref)
"""
abstract type StaticInjectionSubsystem <: StaticInjection end
"""
-Efficiently add all time series data in the subcomponent to the subsystem by copying the
-underlying references.
+Copy all time series data from a subcomponent to the subsystem by copying the underlying
+references rather than duplicating the data.
+
+See also: [`StaticInjectionSubsystem`](@ref)
"""
function copy_subcomponent_time_series!(
subsystem::StaticInjectionSubsystem,
diff --git a/src/models/static_models.jl b/src/models/static_models.jl
index 8d277087dc..4b13afc5f1 100644
--- a/src/models/static_models.jl
+++ b/src/models/static_models.jl
@@ -1,22 +1,61 @@
"""
-Abstract type for devices that [inject](@ref I) power or current
+ StaticInjection
+
+Abstract type for devices that [inject](@ref I) power or current.
A [static](@ref S) injection is a steady state injection, such as modeling
the output power of a generator held constant over a five-minute period.
Many `StaticInjection` models can accept a [`DynamicInjection`](@ref) model
as an optional add-on for conducting [dynamic](@ref D) simulations.
+
+Subtypes: [`Generator`](@ref), [`ElectricLoad`](@ref), [`Storage`](@ref),
+[`StaticInjectionSubsystem`](@ref)
+
+See also: [`Device`](@ref)
"""
abstract type StaticInjection <: Device end
-function supports_services(::T) where {T <: Device}
+"""
+Return false since most devices do not support services by default.
+
+# Arguments
+- `device::Device`: The device.
+
+See also: [`supports_services` for `StaticInjection`](@ref supports_services(::StaticInjection)),
+[`supports_services` for `ACBranch`](@ref supports_services(::ACBranch)),
+[`supports_services` for `HydroReservoir`](@ref supports_services(::HydroReservoir)),
+[`supports_services` for `DynamicInjection`](@ref supports_services(::DynamicInjection))
+"""
+function supports_services(::Device)
return false
end
-function supports_services(::T) where {T <: StaticInjection}
+"""
+Return true since static injection devices support services.
+
+# Arguments
+- `device::StaticInjection`: The device.
+
+See also: [`supports_services` for `Device`](@ref supports_services(::Device)),
+[`supports_services` for `ACBranch`](@ref supports_services(::ACBranch)),
+[`supports_services` for `HydroReservoir`](@ref supports_services(::HydroReservoir)),
+[`supports_services` for `DynamicInjection`](@ref supports_services(::DynamicInjection))
+"""
+function supports_services(::StaticInjection)
return true
end
+"""
+Return the services attached to a device.
+
+Returns an empty vector for devices that do not support services.
+
+# Arguments
+- `device::Device`: The device.
+
+See also: [`add_service!`](@ref), [`remove_service!`](@ref), [`has_service`](@ref)
+"""
function get_services(device::Device)
if !supports_services(device)
error(ArgumentError(
@@ -26,8 +65,26 @@ function get_services(device::Device)
return Vector{Service}()
end
+"""
+Return the [`DynamicInjection`](@ref) component attached to this device,
+or `nothing` if none is attached.
+
+# Arguments
+- `d::StaticInjection`: The static injection device.
+"""
get_dynamic_injector(d::StaticInjection) = nothing
+"""
+Return the frequency droop of the device's [`DynamicInjection`](@ref) model.
+
+Throws `ArgumentError` if no dynamic injector is attached.
+
+# Arguments
+- `static_injector::StaticInjection`: The static injection device.
+
+See also: [`get_frequency_droop` for `DynamicGenerator`](@ref get_frequency_droop(::DynamicGenerator)),
+[`get_frequency_droop` for `DynamicInjection`](@ref get_frequency_droop(::V) where {V <: DynamicInjection})
+"""
function get_frequency_droop(static_injector::StaticInjection)
dynamic_injector = get_dynamic_injector(static_injector)
if isnothing(dynamic_injector)
diff --git a/src/models/storage.jl b/src/models/storage.jl
index b15b047cab..fc9a0219b6 100644
--- a/src/models/storage.jl
+++ b/src/models/storage.jl
@@ -1,2 +1,11 @@
-""" Supertype for energy storage technologies"""
+"""
+ Storage
+
+Abstract supertype for energy storage technologies.
+
+Storage devices can both inject and absorb power from the grid. Concrete subtypes
+include [`EnergyReservoirStorage`](@ref) and [`HybridSystem`](@ref).
+
+See also: [`StaticInjection`](@ref)
+"""
abstract type Storage <: StaticInjection end
diff --git a/src/models/supplemental_accessors.jl b/src/models/supplemental_accessors.jl
index ed8ce4019c..1be37895fc 100644
--- a/src/models/supplemental_accessors.jl
+++ b/src/models/supplemental_accessors.jl
@@ -26,14 +26,23 @@ _remove_aggregration_topology!(bus::ACBus, ::LoadZone) = bus.load_zone = nothing
_remove_aggregration_topology!(bus::ACBus, ::Area) = bus.area = nothing
"""
-Generic method to calculate the susceptance of [`ACTransmission`](@ref) devices.
+Return the series susceptance of [`ACTransmission`](@ref) devices as the inverse of the reactance.
+
+# Arguments
+- `b::ACTransmission`: The AC transmission device.
+
+See also: [`get_series_admittance`](@ref)
"""
get_series_susceptance(b::ACTransmission) = 1 / get_x(b)
"""
-Returns the series susceptance of a controllable 2-winding transformer (e.g., [`TapTransformer`](@ref), [`PhaseShiftingTransformer`](@ref)) following the convention
-in power systems to define susceptance as the inverse of the imaginary part of the impedance.
-In the case of phase shifter transformers the angle is ignored.
+Return the series susceptance of a controllable 2-winding transformer
+([`TapTransformer`](@ref) or [`PhaseShiftingTransformer`](@ref)) as the inverse of the
+imaginary part of the impedance, accounting for the tap ratio. The phase shift angle is
+ignored.
+
+# Arguments
+- `b::Union{TapTransformer, PhaseShiftingTransformer}`: The transformer.
See also: [`get_series_susceptances`](@ref) for 3-winding transformers
"""
@@ -52,11 +61,15 @@ function get_series_susceptance(::Union{PhaseShiftingTransformer3W, Transformer3
end
"""
-Returns the series susceptance of a [`PhaseShiftingTransformer3W`](@ref) as three values
-(for each of the 3 branches) following the convention in power systems to define susceptance as the inverse of the imaginary part of the impedance.
-The phase shift angles are ignored in the susceptance calculation.
+Return the series susceptances of a [`PhaseShiftingTransformer3W`](@ref) as a 3-tuple of
+values (one per winding), each computed as the inverse of the imaginary part of the
+impedance accounting for turns ratios. Phase shift angles are ignored.
+
+# Arguments
+- `b::PhaseShiftingTransformer3W`: The three-winding phase-shifting transformer.
-See also: [`get_series_susceptance`](@ref) for 2-winding transformers and [`get_series_susceptances`](@ref get_series_susceptances(b::Transformer3W)) for [`Transformer3W`](@ref)
+See also: [`get_series_susceptance`](@ref) for 2-winding transformers,
+[`get_series_susceptances`](@ref get_series_susceptances(b::Transformer3W)) for [`Transformer3W`](@ref)
"""
function get_series_susceptances(b::PhaseShiftingTransformer3W)
y1 = 1 / get_x_primary(b)
@@ -71,11 +84,14 @@ function get_series_susceptances(b::PhaseShiftingTransformer3W)
end
"""
-Returns the series susceptance of a [`Transformer3W`](@ref) as three values
-(for each of the 3 branches) following the convention
-in power systems to define susceptance as the inverse of the imaginary part of the impedance.
+Return the series susceptances of a [`Transformer3W`](@ref) as a 3-tuple of values (one
+per winding), each computed as the imaginary part of the inverse of the complex impedance.
-See also: [`get_series_susceptance`](@ref) for 2-winding transformers and [`get_series_susceptances`](@ref get_series_susceptances(b::PhaseShiftingTransformer3W)) for [`PhaseShiftingTransformer3W`](@ref)
+# Arguments
+- `b::Transformer3W`: The three-winding transformer.
+
+See also: [`get_series_susceptance`](@ref) for 2-winding transformers,
+[`get_series_susceptances`](@ref get_series_susceptances(b::PhaseShiftingTransformer3W)) for [`PhaseShiftingTransformer3W`](@ref)
"""
function get_series_susceptances(b::Transformer3W)
Z1s = get_r_primary(b) + get_x_primary(b) * 1im
@@ -90,8 +106,6 @@ function get_series_susceptances(b::Transformer3W)
end
"""
- get_base_voltage(line::Union{Line, MonitoredLine})
-
Return the base voltage (kV) of a [`Line`](@ref) or [`MonitoredLine`](@ref) by reading the
`base_voltage` from both endpoints of the line's [`Arc`](@ref).
@@ -99,6 +113,9 @@ If the two bus voltages are identical, that value is returned directly. If they
are within `BRANCH_BUS_VOLTAGE_DIFFERENCE_TOL` (percent), the value with fewer significant
figures is returned (i.e., the rounder number). If the difference exceeds the tolerance, an
error is thrown.
+
+# Arguments
+- `line::Union{Line, MonitoredLine}`: The transmission line.
"""
function get_base_voltage(line::Union{Line, MonitoredLine})
v_from = get_base_voltage(get_from_bus(line))
@@ -130,10 +147,13 @@ function _select_fewer_significant_figures(a::Float64, b::Float64)
end
"""
- get_high_voltage(t::TwoWindingTransformer)
-
Return the high-side base voltage (kV) of a [`TwoWindingTransformer`](@ref) as the
maximum of `base_voltage_primary` and `base_voltage_secondary`.
+
+# Arguments
+- `t::TwoWindingTransformer`: The transformer.
+
+See also: [`get_low_voltage`](@ref)
"""
function get_high_voltage(t::TwoWindingTransformer)
v_primary = get_base_voltage_primary(t)
@@ -142,10 +162,13 @@ function get_high_voltage(t::TwoWindingTransformer)
end
"""
- get_low_voltage(t::TwoWindingTransformer)
-
Return the low-side base voltage (kV) of a [`TwoWindingTransformer`](@ref) as the
minimum of `base_voltage_primary` and `base_voltage_secondary`.
+
+# Arguments
+- `t::TwoWindingTransformer`: The transformer.
+
+See also: [`get_high_voltage`](@ref)
"""
function get_low_voltage(t::TwoWindingTransformer)
v_primary = get_base_voltage_primary(t)
@@ -154,19 +177,25 @@ function get_low_voltage(t::TwoWindingTransformer)
end
"""
-Calculate the series admittance of a [`ACTransmission`](@ref) as the inverse of the complex impedance.
-Returns 1/(R + jX) where R is resistance and X is reactance.
+Return the series admittance of an [`ACTransmission`](@ref) device as the inverse of
+the complex impedance `1 / (R + jX)`.
+
+# Arguments
+- `b::ACTransmission`: The AC transmission device.
+
+See also: [`get_series_susceptance`](@ref)
"""
get_series_admittance(b::ACTransmission) = 1 / (get_r(b) + get_x(b) * 1im)
"""
-Calculate the series admittance of a [`PhaseShiftingTransformer`](@ref) accounting for the tap ratio.
-For a phase-shifting transformer, the series admittance is calculated as the inverse of the
-complex impedance modified by the tap ratio, following the same pattern as the susceptance calculation:
-Y = 1/(tap * (R + jX)).
-The phase angle α affects the admittance matrix construction but not the series impedance magnitude directly.
+Return the series admittance of a [`PhaseShiftingTransformer`](@ref) as `1 / (tap × (R + jX))`.
-See also: [`get_series_susceptance`](@ref)
+The phase angle α affects the admittance matrix construction but not the series impedance magnitude.
+
+# Arguments
+- `b::PhaseShiftingTransformer`: The phase-shifting transformer.
+
+See also: [`get_series_susceptance`](@ref), [`get_series_admittance`](@ref)
"""
function get_series_admittance(b::PhaseShiftingTransformer)
tap = get_tap(b)
@@ -175,12 +204,12 @@ function get_series_admittance(b::PhaseShiftingTransformer)
end
"""
-Calculate the series admittance of a [`TapTransformer`](@ref) accounting for the tap ratio.
-For a tap transformer, the series admittance is calculated as the inverse of the
-complex impedance modified by the tap ratio, following the same pattern as the susceptance calculation:
-Y = 1/(tap * (R + jX)).
+Return the series admittance of a [`TapTransformer`](@ref) as `1 / (tap × (R + jX))`.
-See also: [`get_series_susceptance`](@ref)
+# Arguments
+- `b::TapTransformer`: The tap transformer.
+
+See also: [`get_series_susceptance`](@ref), [`get_series_admittance`](@ref)
"""
function get_series_admittance(b::TapTransformer)
tap = get_tap(b)
@@ -189,11 +218,12 @@ function get_series_admittance(b::TapTransformer)
end
"""
-Calculate the series admittances of a [`PhaseShiftingTransformer3W`](@ref) as three complex values
-(for each of the 3 branches) accounting for turns ratios.
-For each winding, the series admittance is calculated following the same pattern as the susceptance calculation:
-Yi = 1/(turns_ratio_i * (Ri + jXi)).
-The phase shift angles affect the admittance matrix construction but not the series impedance magnitudes directly.
+Return the series admittances of a [`PhaseShiftingTransformer3W`](@ref) as a 3-tuple of
+complex values (one per winding), each computed as `1 / (turns_ratio_i × (Ri + jXi))`.
+Phase shift angles affect the admittance matrix but not series impedance magnitudes.
+
+# Arguments
+- `b::PhaseShiftingTransformer3W`: The three-winding phase-shifting transformer.
See also: [`get_series_admittance`](@ref) for 2-winding transformers
"""
@@ -225,14 +255,27 @@ function get_series_admittance(::Union{PhaseShiftingTransformer3W, Transformer3W
end
"""
-Return the max active power for a device as the max field in the named tuple returned by [`get_active_power_limits`](@ref).
+Return the maximum active power for a [`StaticInjection`](@ref) device as the `max` field
+of the named tuple returned by [`get_active_power_limits`](@ref).
+
+# Arguments
+- `d::StaticInjection`: The static injection device.
+
+See also: [`get_max_reactive_power`](@ref), [`get_active_power_limits`](@ref)
"""
function get_max_active_power(d::T) where {T <: StaticInjection}
return get_active_power_limits(d).max
end
"""
-Return the max reactive power for a device as the max field in the named tuple returned by [`get_reactive_power_limits`](@ref).
+Return the maximum reactive power for a [`StaticInjection`](@ref) device as the `max` field
+of the named tuple returned by [`get_reactive_power_limits`](@ref). Returns `Inf` if
+`reactive_power_limits` is `nothing`.
+
+# Arguments
+- `d::StaticInjection`: The static injection device.
+
+See also: [`get_max_active_power`](@ref), [`get_reactive_power_limits`](@ref)
"""
function get_max_reactive_power(d::T)::Float64 where {T <: StaticInjection}
if isnothing(get_reactive_power_limits(d))
@@ -242,8 +285,13 @@ function get_max_reactive_power(d::T)::Float64 where {T <: StaticInjection}
end
"""
-Return the max reactive power for a [`RenewableDispatch`](@ref) generator calculated as the `rating` * `power_factor` if
-the field `reactive_power_limits` is `nothing`
+Return the maximum reactive power for a [`RenewableDispatch`](@ref) generator. If
+`reactive_power_limits` is `nothing`, the value is calculated as `rating` × sin(acos(`power_factor`)).
+
+# Arguments
+- `d::RenewableDispatch`: The renewable dispatch generator.
+
+See also: [`get_max_reactive_power`](@ref get_max_reactive_power(d::T) where {T <: StaticInjection})
"""
function get_max_reactive_power(d::RenewableDispatch)
reactive_power_limits = get_reactive_power_limits(d)
@@ -254,33 +302,38 @@ function get_max_reactive_power(d::RenewableDispatch)
end
"""
-Generic fallback function for getting active power limits. Throws `ArgumentError` for devices
-that don't implement this function.
+Generic fallback — throws `ArgumentError` for devices that do not implement `get_active_power_limits`.
+
+See also: [`get_active_power_limits`](@ref)
"""
get_active_power_limits(::T) where {T <: Device} =
throw(ArgumentError("get_active_power_limits not implemented for $T"))
"""
-Generic fallback function for getting reactive power limits. Throws `ArgumentError` for devices
-that don't implement this function.
+Generic fallback — throws `ArgumentError` for devices that do not implement `get_reactive_power_limits`.
+
+See also: [`get_reactive_power_limits`](@ref)
"""
get_reactive_power_limits(::T) where {T <: Device} =
throw(ArgumentError("get_reactive_power_limits not implemented for $T"))
"""
-Generic fallback function for getting device rating. Throws `ArgumentError` for devices
-that don't implement this function.
+Generic fallback — throws `ArgumentError` for devices that do not implement `get_rating`.
"""
get_rating(::T) where {T <: Device} =
throw(ArgumentError("get_rating not implemented for $T"))
"""
-Generic fallback function for getting power factor. Throws `ArgumentError` for devices
-that don't implement this function.
+Generic fallback — throws `ArgumentError` for devices that do not implement `get_power_factor`.
"""
get_power_factor(::T) where {T <: Device} =
throw(ArgumentError("get_power_factor not implemented for $T"))
"""
-Calculate the maximum active power for a [`StandardLoad`](@ref) or [`InterruptibleStandardLoad`](@ref)
- by summing the maximum constant, impedance, and current components assuming a 1.0 voltage magnitude at the bus.
+Return the maximum active power for a [`StandardLoad`](@ref) or [`InterruptibleStandardLoad`](@ref)
+by summing constant, impedance, and current components at unit voltage.
+
+# Arguments
+- `d::Union{InterruptibleStandardLoad, StandardLoad}`: The load device.
+
+See also: [`get_max_active_power`](@ref)
"""
function get_max_active_power(d::Union{InterruptibleStandardLoad, StandardLoad})
total_load = get_max_constant_active_power(d)
@@ -290,43 +343,76 @@ function get_max_active_power(d::Union{InterruptibleStandardLoad, StandardLoad})
end
"""
-Get the maximum storage capacity for HydroReservoir.
+Return the maximum storage level for a [`HydroReservoir`](@ref).
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+
+See also: [`get_storage_level_limits`](@ref)
"""
function get_max_storage_level(reservoir::HydroReservoir)
return get_storage_level_limits(reservoir).max
end
"""
-Get the flow limits from source [`Area`](@ref) to destination [`Area`](@ref) for an [`AreaInterchange`](@ref).
+Return the flow limit from the source [`Area`](@ref) to the destination [`Area`](@ref)
+for an [`AreaInterchange`](@ref).
+
+# Arguments
+- `a::AreaInterchange`: The area interchange.
+
+See also: [`get_to_from_flow_limit`](@ref), [`get_flow_limits`](@ref)
"""
function get_from_to_flow_limit(a::AreaInterchange)
return get_flow_limits(a).from_to
end
"""
-Get the flow limits from destination [`Area`](@ref) to source [`Area`](@ref) for an [`AreaInterchange`](@ref).
+Return the flow limit from the destination [`Area`](@ref) to the source [`Area`](@ref)
+for an [`AreaInterchange`](@ref).
+
+# Arguments
+- `a::AreaInterchange`: The area interchange.
+
+See also: [`get_from_to_flow_limit`](@ref), [`get_flow_limits`](@ref)
"""
function get_to_from_flow_limit(a::AreaInterchange)
return get_flow_limits(a).to_from
end
"""
-Get the minimum active power flow limit for a [`TransmissionInterface`](@ref).
+Return the minimum active power flow limit for a [`TransmissionInterface`](@ref).
+
+# Arguments
+- `tx::TransmissionInterface`: The transmission interface.
+
+See also: [`get_max_active_power_flow_limit`](@ref), [`get_active_power_flow_limits`](@ref)
"""
function get_min_active_power_flow_limit(tx::TransmissionInterface)
return get_active_power_flow_limits(tx).min
end
"""
-Get the maximum active power flow limit for a [`TransmissionInterface`](@ref).
+Return the maximum active power flow limit for a [`TransmissionInterface`](@ref).
+
+# Arguments
+- `tx::TransmissionInterface`: The transmission interface.
+
+See also: [`get_min_active_power_flow_limit`](@ref), [`get_active_power_flow_limits`](@ref)
"""
function get_max_active_power_flow_limit(tx::TransmissionInterface)
return get_active_power_flow_limits(tx).max
end
"""
-Calculate the phase shift angle α for a [`TapTransformer`](@ref) or [`Transformer2W`](@ref) based on its winding group number.
-Returns the angle in radians, calculated as -(π/6) * `winding_group_number`.
-If the `winding_group_number` is `WindingGroupNumber.UNDEFINED`, returns 0.0 and issues a warning.
+Return the phase shift angle α (radians) for a [`TapTransformer`](@ref) or [`Transformer2W`](@ref)
+based on its winding group number, calculated as `-(π/6) × winding_group_number`.
+
+Returns `0.0` and logs a debug message if the winding group number is `WindingGroupNumber.UNDEFINED`.
+
+# Arguments
+- `t::Union{TapTransformer, Transformer2W}`: The two-winding transformer.
+
+See also: [`get_α_primary`](@ref), [`get_winding_group_number`](@ref)
"""
function get_α(t::Union{TapTransformer, Transformer2W})
if get_winding_group_number(t) == WindingGroupNumber.UNDEFINED
@@ -338,9 +424,15 @@ function get_α(t::Union{TapTransformer, Transformer2W})
end
"""
-Calculate the phase shift angle α for the primary winding of a [`Transformer3W`](@ref)
-based on its primary winding group number. Returns the angle in radians, calculated
-as -(π/6) * `primary_group_number`. If `primary_group_number` is `WindingGroupNumber.UNDEFINED`, returns 0.0 and issues a warning.
+Return the primary winding phase shift angle α (radians) for a [`Transformer3W`](@ref)
+based on its primary winding group number, calculated as `-(π/6) × primary_group_number`.
+
+Returns `0.0` and issues a warning if the primary winding group number is `WindingGroupNumber.UNDEFINED`.
+
+# Arguments
+- `t::Transformer3W`: The three-winding transformer.
+
+See also: [`get_α_secondary`](@ref), [`get_α_tertiary`](@ref), [`get_primary_group_number`](@ref)
"""
function get_α_primary(t::Transformer3W)
if get_primary_group_number(t) == WindingGroupNumber.UNDEFINED
@@ -351,9 +443,15 @@ function get_α_primary(t::Transformer3W)
end
end
"""
-Calculate the phase shift angle α for the secondary winding of a [`Transformer3W`](@ref)
-based on its secondary winding group number. Returns the angle in radians, calculated
-as -(π/6) * `secondary_group_number`. If `secondary_group_number` is `WindingGroupNumber.UNDEFINED`, returns 0.0 and issues a warning.
+Return the secondary winding phase shift angle α (radians) for a [`Transformer3W`](@ref)
+based on its secondary winding group number, calculated as `-(π/6) × secondary_group_number`.
+
+Returns `0.0` and issues a warning if the secondary winding group number is `WindingGroupNumber.UNDEFINED`.
+
+# Arguments
+- `t::Transformer3W`: The three-winding transformer.
+
+See also: [`get_α_primary`](@ref), [`get_α_tertiary`](@ref), [`get_secondary_group_number`](@ref)
"""
function get_α_secondary(t::Transformer3W)
if get_secondary_group_number(t) == WindingGroupNumber.UNDEFINED
@@ -364,9 +462,15 @@ function get_α_secondary(t::Transformer3W)
end
end
"""
-Calculate the phase shift angle α for the tertiary winding of a [`Transformer3W`](@ref)
-based on its tertiary winding group number. Returns the angle in radians, calculated
-as -(π/6) * `tertiary_group_number`. If `tertiary_group_number` is `WindingGroupNumber.UNDEFINED`, returns 0.0 and issues a warning.
+Return the tertiary winding phase shift angle α (radians) for a [`Transformer3W`](@ref)
+based on its tertiary winding group number, calculated as `-(π/6) × tertiary_group_number`.
+
+Returns `0.0` and issues a warning if the tertiary winding group number is `WindingGroupNumber.UNDEFINED`.
+
+# Arguments
+- `t::Transformer3W`: The three-winding transformer.
+
+See also: [`get_α_primary`](@ref), [`get_α_secondary`](@ref), [`get_tertiary_group_number`](@ref)
"""
function get_α_tertiary(t::Transformer3W)
if get_tertiary_group_number(t) == WindingGroupNumber.UNDEFINED
@@ -377,6 +481,11 @@ function get_α_tertiary(t::Transformer3W)
end
end
+"""
+Return true since [`AreaInterchange`](@ref) supports services.
+
+See also: [`supports_services`](@ref)
+"""
function supports_services(::AreaInterchange)
return true
end
diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl
index bc1c855955..05ab8ddb13 100644
--- a/src/models/supplemental_constructors.jl
+++ b/src/models/supplemental_constructors.jl
@@ -1,4 +1,7 @@
-"""Accepts angle_limits as a Float64."""
+"""
+Construct a [`Line`](@ref) accepting `angle_limits` as a `Float64`, converting it to a
+symmetric `(min = -angle_limits, max = angle_limits)` named tuple.
+"""
function Line(
name,
available::Bool,
@@ -25,7 +28,11 @@ function Line(
)
end
-"""Allows construction with bus type specified as a string for legacy code."""
+"""
+Construct an [`ACBus`](@ref) with `bustype` specified as a `String` for legacy compatibility.
+
+The string is converted to the corresponding [`ACBusTypes`](@ref) enum value.
+"""
function ACBus(
number,
name,
@@ -55,7 +62,12 @@ function ACBus(
)
end
-"""Allows construction with bus type specified as a string for legacy code."""
+"""
+Construct a [`DiscreteControlledACBranch`](@ref) with enum types specified as strings for legacy compatibility.
+
+The `discrete_branch_type` and `branch_status` strings are converted to the corresponding
+[`DiscreteControlledBranchType`](@ref) and [`DiscreteControlledBranchStatus`](@ref) enum values.
+"""
function DiscreteControlledACBranch(
name,
available,
@@ -86,7 +98,11 @@ function DiscreteControlledACBranch(
)
end
-"""Allows construction of FACT Devices with control modes."""
+"""
+Construct a [`FACTSControlDevice`](@ref) with `control_mode` specified as a string.
+
+The string is converted to the corresponding [`FACTSOperationModes`](@ref) enum value.
+"""
function FACTSControlDevice(
name,
available,
@@ -115,7 +131,9 @@ function FACTSControlDevice(
)
end
-"""Allows construction of a reserve from an iterator."""
+"""
+Construct a [`ConstantReserve`](@ref) from a `contributingdevices` iterator, collecting it into a vector.
+"""
function ConstantReserve(
name,
contributingdevices::IS.FlattenIteratorWrapper,
@@ -134,7 +152,11 @@ function ConstantReserve(
)
end
-"""Allows construction of a EnergyReservoirStorage without the specification of a cost."""
+"""
+Construct an [`EnergyReservoirStorage`](@ref) without an explicit operational cost.
+
+Uses a default [`StorageCost`](@ref) when `operation_cost` is `nothing`.
+"""
function EnergyReservoirStorage(
name::AbstractString,
available::Bool,
diff --git a/src/models/supplemental_setters.jl b/src/models/supplemental_setters.jl
index 444f8921d4..da666e1775 100644
--- a/src/models/supplemental_setters.jl
+++ b/src/models/supplemental_setters.jl
@@ -1,5 +1,13 @@
"""
Set a single upstream turbine for a [`HydroReservoir`](@ref).
+
+This is a convenience wrapper around `set_upstream_turbines!` for the single-turbine case.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+- `turbine::HydroUnit`: The turbine to set as the upstream unit.
+
+See also: [`set_downstream_turbine!`](@ref), [`get_upstream_turbines`](@ref)
"""
function set_upstream_turbine!(reservoir::HydroReservoir, turbine::HydroUnit)
set_upstream_turbines!(reservoir, [turbine])
@@ -8,6 +16,14 @@ end
"""
Set a single downstream turbine for a [`HydroReservoir`](@ref).
+
+This is a convenience wrapper around `set_downstream_turbines!` for the single-turbine case.
+
+# Arguments
+- `reservoir::HydroReservoir`: The hydro reservoir.
+- `turbine::HydroUnit`: The turbine to set as the downstream unit.
+
+See also: [`set_upstream_turbine!`](@ref), [`get_downstream_turbines`](@ref)
"""
function set_downstream_turbine!(reservoir::HydroReservoir, turbine::HydroUnit)
set_downstream_turbines!(reservoir, [turbine])
diff --git a/src/models/topological_elements.jl b/src/models/topological_elements.jl
index d435555bc9..b4e699245b 100644
--- a/src/models/topological_elements.jl
+++ b/src/models/topological_elements.jl
@@ -1,28 +1,50 @@
"""
-Abstract type to represent the structure and interconnectedness of the system
+ Topology
+
+Abstract supertype for network topology elements.
+
+Subtypes: [`AggregationTopology`](@ref) (e.g., [`Area`](@ref), [`LoadZone`](@ref)),
+[`Bus`](@ref) (e.g., [`ACBus`](@ref), [`DCBus`](@ref)), and [`Arc`](@ref)
"""
abstract type Topology <: Component end
"""
-Represents a geographical region of system components.
+ AggregationTopology
+
+Abstract supertype for geographical or electrical aggregation regions.
-All subtypes must implement the method `get_aggregation_topology_accessor`.
+Subtypes: [`Area`](@ref), [`LoadZone`](@ref)
+
+See also: [`Topology`](@ref)
"""
abstract type AggregationTopology <: Topology end
"""
-All PowerSystems [AggregationTopology](@ref) types support time series. This can be overridden for specific custom
-aggregation topology types that do not support time series.
+Return true since all [`AggregationTopology`](@ref) types support time series by default.
+
+Override this method for specific custom aggregation topology types that do not support time series.
"""
supports_time_series(::AggregationTopology) = true
"""
-Abstract type to represent any type of Bus, AC or DC.
+ Bus
+
+Abstract supertype for all bus types in a power system network.
+
+Subtypes: [`ACBus`](@ref), [`DCBus`](@ref)
+
+See also: [`Arc`](@ref), [`Topology`](@ref)
"""
abstract type Bus <: Topology end
"""
-Return the method to be called on a ACBus to get its AggregationTopology value for this type.
+Abstract interface method — return the accessor function to call on an [`ACBus`](@ref)
+to retrieve its [`AggregationTopology`](@ref) value for subtype `T`.
+
+Throws an error if not implemented for `T`. Concrete subtypes of [`AggregationTopology`](@ref)
+must provide a method for this function.
+
+See also: [`get_area`](@ref), [`get_load_zone`](@ref)
"""
function get_aggregation_topology_accessor(::Type{T}) where {T <: AggregationTopology}
error("get_aggregation_topology_accessor must be implemented for $T")
diff --git a/src/outages.jl b/src/outages.jl
index ff2d4540a9..1d47fccd39 100644
--- a/src/outages.jl
+++ b/src/outages.jl
@@ -1,4 +1,6 @@
"""
+ Outage
+
Supertype for outage contingencies representing planned or unplanned equipment outages.
Concrete subtypes include [`GeometricDistributionForcedOutage`](@ref),
@@ -6,26 +8,47 @@ Concrete subtypes include [`GeometricDistributionForcedOutage`](@ref),
"""
abstract type Outage <: Contingency end
+"""
+ UnplannedOutage
+
+Abstract supertype for unplanned (forced) outage events.
+
+See also: [`Outage`](@ref), [`GeometricDistributionForcedOutage`](@ref), [`FixedForcedOutage`](@ref)
+"""
abstract type UnplannedOutage <: Outage end
"""
-All PowerSystems [Outage](@ref) types support time series. This can be overridden for custom
+All PowerSystems [`Outage`](@ref) types support time series. This can be overridden for custom
outage types that do not support time series.
"""
supports_time_series(::Outage) = true
-"""Get `internal`."""
+"""Return the `internal` field of the [`Outage`](@ref)."""
get_internal(x::Outage) = x.internal
"""
-Attribute that contains information regarding forced outages where the transition probabilities
-are modeled with geometric distributions. The outage probabilities and recovery probabilities can be modeled as time
-series.
+ struct GeometricDistributionForcedOutage <: UnplannedOutage
+ mean_time_to_recovery::Float64
+ outage_transition_probability::Float64
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute for unplanned forced outages with transition probabilities modeled
+by geometric distributions. Both the outage probability and the recovery probability can
+vary over time and be attached as time series data.
# Arguments
-- `mean_time_to_recovery::Float64`: Time elapsed to recovery after a failure in Milliseconds.
-- `outage_transition_probability::Float64`: Characterizes the probability of failure (1 - p) in the geometric distribution.
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `mean_time_to_recovery::Float64`: Expected time elapsed to recovery after a failure, in
+ milliseconds.
+- `outage_transition_probability::Float64`: Per-timestep probability of failure;
+ parameterizes the geometric distribution as `(1 - p)`.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`FixedForcedOutage`](@ref): Unplanned outage type with a fixed (deterministic) outage
+ status.
+- [`PlannedOutage`](@ref): Scheduled outage type driven by a time series.
"""
struct GeometricDistributionForcedOutage <: UnplannedOutage
mean_time_to_recovery::Float64
@@ -39,9 +62,12 @@ end
Construct a [`GeometricDistributionForcedOutage`](@ref).
# Arguments
-- `mean_time_to_recovery::Float64`: (default: `0.0`) Time elapsed to recovery after a failure in Milliseconds.
-- `outage_transition_probability::Float64`: (default: `0.0`) Characterizes the probability of failure (1 - p) in the geometric distribution.
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `mean_time_to_recovery::Float64`: (default: `0.0`) Expected time elapsed to recovery
+ after a failure, in milliseconds.
+- `outage_transition_probability::Float64`: (default: `0.0`) Per-timestep probability of
+ failure; parameterizes the geometric distribution as `(1 - p)`.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function GeometricDistributionForcedOutage(;
mean_time_to_recovery = 0.0,
@@ -55,19 +81,31 @@ function GeometricDistributionForcedOutage(;
)
end
-"""Get [`GeometricDistributionForcedOutage`](@ref) `time_to_recovery`."""
+"""Return the `mean_time_to_recovery` field of [`GeometricDistributionForcedOutage`](@ref)."""
get_mean_time_to_recovery(value::GeometricDistributionForcedOutage) =
value.mean_time_to_recovery
-"""Get [`GeometricDistributionForcedOutage`](@ref) `outage_transition_probability`."""
+"""Return the `outage_transition_probability` field of [`GeometricDistributionForcedOutage`](@ref)."""
get_outage_transition_probability(value::GeometricDistributionForcedOutage) =
value.outage_transition_probability
"""
-Attribute that contains information regarding planned outages.
+ struct PlannedOutage <: Outage
+ outage_schedule::String
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute for planned (scheduled) outages. The outage schedule is stored as
+a time series identified by the `outage_schedule` name string.
# Arguments
-- `outage_schedule::String`: String name of the time series used for the scheduled outages
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `outage_schedule::String`: Name of the time series containing the outage schedule.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`GeometricDistributionForcedOutage`](@ref): Unplanned outage type with geometric
+ distribution transition probabilities.
+- [`FixedForcedOutage`](@ref): Unplanned outage type with a fixed outage status.
"""
struct PlannedOutage <: Outage
outage_schedule::String
@@ -80,8 +118,9 @@ end
Construct a [`PlannedOutage`](@ref).
# Arguments
-- `outage_schedule::String`: String name of the time series used for the scheduled outages
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `outage_schedule::String`: Name of the time series containing the outage schedule.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function PlannedOutage(;
outage_schedule,
@@ -93,16 +132,29 @@ function PlannedOutage(;
)
end
-"""Get [`PlannedOutage`](@ref) `outage_schedule`."""
+"""Return the `outage_schedule` field of [`PlannedOutage`](@ref)."""
get_outage_schedule(value::PlannedOutage) = value.outage_schedule
"""
-Attribute that contains the representation of the status of the component forced outage.
-The time series data for fixed outages can be obtained from the simulation of a stochastic process or historical information.
+ struct FixedForcedOutage <: UnplannedOutage
+ outage_status::Float64
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute for forced outages with a deterministic (fixed) outage status.
+The status value can be derived from stochastic process simulations or historical data,
+and may vary over time via attached time series data.
# Arguments
-- `outage_status::Float64`: The forced outage status in the model. 1 represents outaged and 0 represents available.
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `outage_status::Float64`: Forced outage status of the component: `1.0` indicates
+ outaged (unavailable), `0.0` indicates available.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`GeometricDistributionForcedOutage`](@ref): Unplanned outage type with geometric
+ distribution transition probabilities.
+- [`PlannedOutage`](@ref): Scheduled outage type driven by a time series.
"""
struct FixedForcedOutage <: UnplannedOutage
outage_status::Float64
@@ -115,8 +167,10 @@ end
Construct a [`FixedForcedOutage`](@ref).
# Arguments
-- `outage_status::Float64`: The forced outage status in the model. 1 represents outaged and 0 represents available.
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `outage_status::Float64`: Forced outage status of the component: `1.0` indicates
+ outaged (unavailable), `0.0` indicates available.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function FixedForcedOutage(;
outage_status,
@@ -125,5 +179,5 @@ function FixedForcedOutage(;
return FixedForcedOutage(outage_status, internal)
end
-"""Get [`FixedForcedOutage`](@ref) `outage_status`."""
+"""Return the `outage_status` field of [`FixedForcedOutage`](@ref)."""
get_outage_status(value::FixedForcedOutage) = value.outage_status
diff --git a/src/plant_attribute.jl b/src/plant_attribute.jl
index 8fe8f35bea..4d648d18e7 100644
--- a/src/plant_attribute.jl
+++ b/src/plant_attribute.jl
@@ -1,4 +1,6 @@
"""
+ PowerPlant
+
Supertype for power plant supplemental attributes that group generating units.
Concrete subtypes include [`ThermalPowerPlant`](@ref), [`HydroPowerPlant`](@ref),
@@ -7,20 +9,34 @@ Concrete subtypes include [`ThermalPowerPlant`](@ref), [`HydroPowerPlant`](@ref)
"""
abstract type PowerPlant <: SupplementalAttribute end
-"""Get `internal`."""
+"""Return the `internal` field of the [`PowerPlant`](@ref)."""
get_internal(x::PowerPlant) = x.internal
"""
-Attribute to represent [`ThermalGen`](@ref) power plants with synchronous generation.
-For Combined Cycle plants consider using [`CombinedCycleBlock`](@ref).
+ struct ThermalPowerPlant <: PowerPlant
+ name::String
+ shaft_map::Dict{Int, Vector{Base.UUID}}
+ reverse_shaft_map::Dict{Base.UUID, Int}
+ internal::InfrastructureSystemsInternal
+ end
-The shaft map field is used to represent shared shafts between units.
+Supplemental attribute representing a [`ThermalGen`](@ref) power plant where multiple
+generator units share mechanical shafts. The shaft maps capture the unit ↔ shaft topology
+for multi-shaft dispatch and synchronous condensing configurations.
# Arguments
-- `name::String`: Name of the power plant
-- `shaft_map::Dict{Int, Vector{Base.UUID}}`: Mapping of shaft numbers to unit UUIDs (multiple units can share a shaft)
-- `reverse_shaft_map::Dict{Base.UUID, Int}`: Reverse mapping from unit UUID to shaft number
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the power plant.
+- `shaft_map::Dict{Int, Vector{Base.UUID}}`: Mapping from shaft index to the UUIDs of
+ units connected to that shaft (multiple units may share one shaft).
+- `reverse_shaft_map::Dict{Base.UUID, Int}`: Reverse mapping from a unit's UUID to the
+ index of its shaft.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`CombinedCycleBlock`](@ref): Plant attribute for combined cycle block-level
+ representations.
+- [`ThermalGen`](@ref): Abstract type for thermal generating units.
"""
struct ThermalPowerPlant <: PowerPlant
name::String
@@ -52,10 +68,13 @@ end
Construct a [`ThermalPowerPlant`](@ref).
# Arguments
-- `name::String`: Name of the power plant
-- `shaft_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping of shaft numbers to unit UUIDs
-- `reverse_shaft_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping from unit UUID to shaft number
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the power plant.
+- `shaft_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping from shaft
+ index to the UUIDs of units connected to that shaft.
+- `reverse_shaft_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping from
+ a unit's UUID to its shaft index.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function ThermalPowerPlant(;
name::String,
@@ -66,26 +85,50 @@ function ThermalPowerPlant(;
return ThermalPowerPlant(name, shaft_map, reverse_shaft_map, internal)
end
-"""Get [`ThermalPowerPlant`](@ref) `name`."""
+"""Return the `name` field of [`ThermalPowerPlant`](@ref)."""
get_name(value::ThermalPowerPlant) = value.name
-"""Get [`ThermalPowerPlant`](@ref) `shaft_map`."""
+"""Return the `shaft_map` field of [`ThermalPowerPlant`](@ref): mapping from shaft index to the UUIDs of generators connected to that shaft."""
get_shaft_map(value::ThermalPowerPlant) = value.shaft_map
-"""Get [`ThermalPowerPlant`](@ref) `reverse_shaft_map`."""
+"""Return the `reverse_shaft_map` field of [`ThermalPowerPlant`](@ref): reverse mapping from a generator's UUID to its shaft index."""
get_reverse_shaft_map(value::ThermalPowerPlant) = value.reverse_shaft_map
"""
-Attribute to represent combined cycle generation by block configuration that shares heat recovery converstions.
-For aggregate representations consider using [`CombinedCycleFractional`](@ref).
+ struct CombinedCycleBlock <: PowerPlant
+ name::String
+ configuration::CombinedCycleConfiguration
+ heat_recovery_to_steam_factor::Float64
+ hrsg_ct_map::Dict{Int, Vector{Base.UUID}}
+ hrsg_ca_map::Dict{Int, Vector{Base.UUID}}
+ ct_hrsg_map::Dict{Base.UUID, Vector{Int}}
+ ca_hrsg_map::Dict{Base.UUID, Vector{Int}}
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute representing a combined cycle plant modeled at the block level,
+where combustion turbines (CTs) feed heat recovery steam generators (HRSGs) that drive
+combined-cycle steam turbines (CAs). The internal maps capture the CT→HRSG→CA topology.
# Arguments
-- `name::String`: Name of the combined cycle block
-- `configuration::CombinedCycleConfiguration`: Configuration type of the combined cycle
-- `heat_recovery_to_steam_factor::Float64`: Factor for heat recovery to steam conversion
-- `hrsg_ct_map::Dict{Int, Vector{Base.UUID}}`: Mapping of HRSG numbers to CT unit UUIDs (CTs as HRSG inputs)
-- `hrsg_ca_map::Dict{Int, Vector{Base.UUID}}`: Mapping of HRSG numbers to CA unit UUIDs (CAs as HRSG outputs)
-- `ct_hrsg_map::Dict{Base.UUID, Vector{Int}}`: Reverse mapping from CT unit UUID to HRSG numbers (a CT can feed multiple HRSGs)
-- `ca_hrsg_map::Dict{Base.UUID, Vector{Int}}`: Reverse mapping from CA unit UUID to HRSG numbers (a CA can receive from multiple HRSGs)
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the combined cycle block.
+- `configuration::`[`CombinedCycleConfiguration`](@ref): Configuration type of the
+ combined cycle plant.
+- `heat_recovery_to_steam_factor::Float64`: Fraction of CT exhaust heat recovered by the
+ HRSG for steam generation.
+- `hrsg_ct_map::Dict{Int, Vector{Base.UUID}}`: Mapping from HRSG index to the UUIDs of
+ CTs feeding that HRSG.
+- `hrsg_ca_map::Dict{Int, Vector{Base.UUID}}`: Mapping from HRSG index to the UUIDs of
+ CAs driven by that HRSG.
+- `ct_hrsg_map::Dict{Base.UUID, Vector{Int}}`: Reverse mapping from a CT's UUID to the
+ indices of HRSGs it feeds (a CT can feed multiple HRSGs).
+- `ca_hrsg_map::Dict{Base.UUID, Vector{Int}}`: Reverse mapping from a CA's UUID to the
+ indices of HRSGs that supply it (a CA can receive from multiple HRSGs).
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`CombinedCycleFractional`](@ref): Combined cycle attribute for aggregate fractional
+ representations.
+- [`CombinedCycleConfiguration`](@ref): Enumeration of combined cycle plant configurations.
"""
struct CombinedCycleBlock <: PowerPlant
name::String
@@ -161,7 +204,8 @@ Construct a [`CombinedCycleBlock`](@ref).
- `hrsg_ca_map::AbstractDict`: (default: empty dict) Mapping of HRSG numbers to CA unit UUIDs (CAs as HRSG outputs)
- `ct_hrsg_map::AbstractDict`: (default: empty dict) Reverse mapping from CT unit UUID to HRSG numbers
- `ca_hrsg_map::AbstractDict`: (default: empty dict) Reverse mapping from CA unit UUID to HRSG numbers
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function CombinedCycleBlock(;
name,
@@ -185,32 +229,52 @@ function CombinedCycleBlock(;
)
end
-"""Get [`CombinedCycleBlock`](@ref) `name`."""
+"""Return the `name` field of [`CombinedCycleBlock`](@ref)."""
get_name(value::CombinedCycleBlock) = value.name
-"""Get [`CombinedCycleBlock`](@ref) `configuration`."""
+"""Return the `configuration` field of [`CombinedCycleBlock`](@ref)."""
get_configuration(value::CombinedCycleBlock) = value.configuration
-"""Get [`CombinedCycleBlock`](@ref) `heat_recovery_to_steam_factor`."""
+"""Return the `heat_recovery_to_steam_factor` field of [`CombinedCycleBlock`](@ref)."""
get_heat_recovery_to_steam_factor(value::CombinedCycleBlock) =
value.heat_recovery_to_steam_factor
-"""Get [`CombinedCycleBlock`](@ref) `hrsg_ct_map`."""
+"""Return the `hrsg_ct_map` field of [`CombinedCycleBlock`](@ref): mapping from HRSG index to the UUIDs of combustion turbines (CT) feeding that HRSG."""
get_hrsg_ct_map(value::CombinedCycleBlock) = value.hrsg_ct_map
-"""Get [`CombinedCycleBlock`](@ref) `hrsg_ca_map`."""
+"""Return the `hrsg_ca_map` field of [`CombinedCycleBlock`](@ref): mapping from HRSG index to the UUIDs of combined-cycle steam turbines (CA) driven by that HRSG."""
get_hrsg_ca_map(value::CombinedCycleBlock) = value.hrsg_ca_map
-"""Get [`CombinedCycleBlock`](@ref) `ct_hrsg_map`."""
+"""Return the `ct_hrsg_map` field of [`CombinedCycleBlock`](@ref): reverse mapping from a combustion turbine's (CT) UUID to the indices of HRSGs it feeds."""
get_ct_hrsg_map(value::CombinedCycleBlock) = value.ct_hrsg_map
-"""Get [`CombinedCycleBlock`](@ref) `ca_hrsg_map`."""
+"""Return the `ca_hrsg_map` field of [`CombinedCycleBlock`](@ref): reverse mapping from a combined-cycle steam turbine's (CA) UUID to the indices of HRSGs that supply it."""
get_ca_hrsg_map(value::CombinedCycleBlock) = value.ca_hrsg_map
"""
-Attribute to represent combined cycle generation when each unit represents a specific configuration and aggregate heat rate.
-For block-level representations consider using [`CombinedCycleBlock`](@ref).
+ struct CombinedCycleFractional <: PowerPlant
+ name::String
+ configuration::CombinedCycleConfiguration
+ operation_exclusion_map::Dict{Int, Vector{Base.UUID}}
+ inverse_operation_exclusion_map::Dict{Base.UUID, Int}
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute representing a combined cycle plant modeled at the aggregate
+(fractional) level, where each generator unit represents a specific plant configuration
+with an aggregate heat rate. Mutually exclusive operating groups are tracked via the
+operation exclusion maps.
# Arguments
-- `name::String`: Name of the combined cycle fractional plant
-- `configuration::CombinedCycleConfiguration`: Configuration type of the combined cycle
-- `operation_exclusion_map::Dict{Int, Vector{Base.UUID}}`: Mapping of operation exclusion group numbers to unit UUIDs (only units in the same group can operate simultaneously)
-- `inverse_operation_exclusion_map::Dict{Base.UUID, Int}`: Reverse mapping from unit UUID to exclusion group number
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the combined cycle fractional plant.
+- `configuration::`[`CombinedCycleConfiguration`](@ref): Configuration type of the
+ combined cycle plant.
+- `operation_exclusion_map::Dict{Int, Vector{Base.UUID}}`: Mapping from exclusion group
+ index to the UUIDs of units in that group; only one unit per group may operate
+ simultaneously.
+- `inverse_operation_exclusion_map::Dict{Base.UUID, Int}`: Reverse mapping from a unit's
+ UUID to its exclusion group index.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`CombinedCycleBlock`](@ref): Combined cycle attribute for detailed block-level
+ representations.
+- [`CombinedCycleConfiguration`](@ref): Enumeration of combined cycle plant configurations.
"""
struct CombinedCycleFractional <: PowerPlant
name::String
@@ -268,7 +332,8 @@ Construct a [`CombinedCycleFractional`](@ref).
- `configuration::CombinedCycleConfiguration`: Configuration type of the combined cycle
- `operation_exclusion_map::AbstractDict`: (default: empty dict) Mapping of operation exclusion group numbers to unit UUIDs
- `inverse_operation_exclusion_map::AbstractDict`: (default: empty dict) Reverse mapping from unit UUID to exclusion group number
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function CombinedCycleFractional(;
name,
@@ -286,25 +351,40 @@ function CombinedCycleFractional(;
)
end
-"""Get [`CombinedCycleFractional`](@ref) `name`."""
+"""Return the `name` field of [`CombinedCycleFractional`](@ref)."""
get_name(value::CombinedCycleFractional) = value.name
-"""Get [`CombinedCycleFractional`](@ref) `configuration`."""
+"""Return the `configuration` field of [`CombinedCycleFractional`](@ref)."""
get_configuration(value::CombinedCycleFractional) = value.configuration
-"""Get [`CombinedCycleFractional`](@ref) `operation_exclusion_map`."""
+"""Return the `operation_exclusion_map` field of [`CombinedCycleFractional`](@ref): mapping from exclusion group index to the UUIDs of units in that group; only one unit per group may operate simultaneously."""
get_operation_exclusion_map(value::CombinedCycleFractional) =
value.operation_exclusion_map
-"""Get [`CombinedCycleFractional`](@ref) `inverse_operation_exclusion_map`."""
+"""Return the `inverse_operation_exclusion_map` field of [`CombinedCycleFractional`](@ref): reverse mapping from a unit's UUID to its exclusion group index."""
get_inverse_operation_exclusion_map(value::CombinedCycleFractional) =
value.inverse_operation_exclusion_map
"""
-Attribute to represent hydro power plants with shared penstocks.
+ struct HydroPowerPlant <: PowerPlant
+ name::String
+ penstock_map::Dict{Int, Vector{Base.UUID}}
+ reverse_penstock_map::Dict{Base.UUID, Int}
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute representing a [`HydroGen`](@ref) power plant where multiple
+generating units share penstocks. The penstock maps capture the unit ↔ penstock topology
+for hydraulic coupling constraints.
# Arguments
-- `name::String`: Name of the hydro power plant
-- `penstock_map::Dict{Int, Vector{Base.UUID}}`: Mapping of penstock numbers to unit UUIDs (multiple units can share a penstock)
-- `reverse_penstock_map::Dict{Base.UUID, Int}`: Reverse mapping from unit UUID to penstock number
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the hydro power plant.
+- `penstock_map::Dict{Int, Vector{Base.UUID}}`: Mapping from penstock index to the UUIDs
+ of units connected to that penstock (multiple units may share one penstock).
+- `reverse_penstock_map::Dict{Base.UUID, Int}`: Reverse mapping from a unit's UUID to the
+ index of its penstock.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`HydroGen`](@ref): Abstract type for hydroelectric generating units.
"""
struct HydroPowerPlant <: PowerPlant
name::String
@@ -336,10 +416,13 @@ end
Construct a [`HydroPowerPlant`](@ref).
# Arguments
-- `name::String`: Name of the hydro power plant
-- `penstock_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping of penstock numbers to unit UUIDs
-- `reverse_penstock_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping from unit UUID to penstock number
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the hydro power plant.
+- `penstock_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping from
+ penstock index to the UUIDs of units connected to that penstock.
+- `reverse_penstock_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping
+ from a unit's UUID to its penstock index.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function HydroPowerPlant(;
name::String,
@@ -350,21 +433,36 @@ function HydroPowerPlant(;
return HydroPowerPlant(name, penstock_map, reverse_penstock_map, internal)
end
-"""Get [`HydroPowerPlant`](@ref) `name`."""
+"""Return the `name` field of [`HydroPowerPlant`](@ref)."""
get_name(value::HydroPowerPlant) = value.name
-"""Get [`HydroPowerPlant`](@ref) `penstock_map`."""
+"""Return the `penstock_map` field of [`HydroPowerPlant`](@ref): mapping from penstock index to the UUIDs of generators connected to that penstock."""
get_penstock_map(value::HydroPowerPlant) = value.penstock_map
-"""Get [`HydroPowerPlant`](@ref) `reverse_penstock_map`."""
+"""Return the `reverse_penstock_map` field of [`HydroPowerPlant`](@ref): reverse mapping from a generator's UUID to its penstock index."""
get_reverse_penstock_map(value::HydroPowerPlant) = value.reverse_penstock_map
"""
-Attribute to represent renewable power plants.
+ struct RenewablePowerPlant <: PowerPlant
+ name::String
+ pcc_map::Dict{Int, Vector{Base.UUID}}
+ reverse_pcc_map::Dict{Base.UUID, Int}
+ internal::InfrastructureSystemsInternal
+ end
+
+Supplemental attribute representing a [`RenewableGen`](@ref) power plant where multiple
+generating units share a point of common coupling (PCC). The PCC maps capture the
+unit ↔ PCC topology for grid connection constraints.
# Arguments
-- `name::String`: Name of the renewable power plant
-- `pcc_map::Dict{Int, Vector{Base.UUID}}`: Mapping of PCC numbers to unit UUIDs (multiple units can share a PCC)
-- `reverse_pcc_map::Dict{Base.UUID, Int}`: Reverse mapping from unit UUID to PCC number
-- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the renewable power plant.
+- `pcc_map::Dict{Int, Vector{Base.UUID}}`: Mapping from PCC index to the UUIDs of units
+ connected to that PCC (multiple units may share one PCC).
+- `reverse_pcc_map::Dict{Base.UUID, Int}`: Reverse mapping from a unit's UUID to the
+ index of its PCC.
+- `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal
+ reference.
+
+# See Also
+- [`RenewableGen`](@ref): Abstract type for renewable generating units.
"""
struct RenewablePowerPlant <: PowerPlant
name::String
@@ -393,13 +491,16 @@ end
"""
RenewablePowerPlant(; name, pcc_map, reverse_pcc_map, internal)
-Construct a [`RenewablePowerPlant`](@ref). This supports multiple point of common coupling (PCC) connections.
+Construct a [`RenewablePowerPlant`](@ref).
# Arguments
-- `name::String`: Name of the renewable power plant
-- `pcc_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping of PCC numbers to unit UUIDs
-- `reverse_pcc_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping from unit UUID to PCC number
-- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`) (**Do not modify.**) PowerSystems internal reference
+- `name::String`: Name of the renewable power plant.
+- `pcc_map::Dict{Int, Vector{Base.UUID}}`: (default: empty dict) Mapping from PCC index
+ to the UUIDs of units connected to that PCC.
+- `reverse_pcc_map::Dict{Base.UUID, Int}`: (default: empty dict) Reverse mapping from a
+ unit's UUID to its PCC index.
+- `internal::InfrastructureSystemsInternal`: (default: `InfrastructureSystemsInternal()`)
+ (**Do not modify.**) PowerSystems.jl internal reference.
"""
function RenewablePowerPlant(;
name::String,
@@ -410,28 +511,28 @@ function RenewablePowerPlant(;
return RenewablePowerPlant(name, pcc_map, reverse_pcc_map, internal)
end
-"""Get [`RenewablePowerPlant`](@ref) `name`."""
+"""Return the `name` field of [`RenewablePowerPlant`](@ref)."""
get_name(value::RenewablePowerPlant) = value.name
-"""Get [`RenewablePowerPlant`](@ref) `pcc_map`."""
+"""Return the `pcc_map` field of [`RenewablePowerPlant`](@ref): mapping from PCC (point of common coupling) index to the UUIDs of generators and storage devices connected to that PCC."""
get_pcc_map(value::RenewablePowerPlant) = value.pcc_map
-"""Get [`RenewablePowerPlant`](@ref) `reverse_pcc_map`."""
+"""Return the `reverse_pcc_map` field of [`RenewablePowerPlant`](@ref): reverse mapping from a component's UUID to its PCC index."""
get_reverse_pcc_map(value::RenewablePowerPlant) = value.reverse_pcc_map
"""
get_components_in_shaft(sys::System, plant::ThermalPowerPlant, shaft_number::Int)
-Get all thermal generators connected to a specific shaft in a [`ThermalPowerPlant`](@ref).
+Return all thermal generators connected to shaft `shaft_number` in a
+[`ThermalPowerPlant`](@ref).
# Arguments
-- `sys::System`: The system containing the components
-- `plant::ThermalPowerPlant`: The thermal power plant
-- `shaft_number::Int`: The shaft number to query
-
-# Returns
-- `Vector{ThermalGen}`: Vector of thermal generators on the specified shaft
+- `sys::System`: The system containing the components.
+- `plant::ThermalPowerPlant`: The thermal power plant.
+- `shaft_number::Int`: The shaft number to query.
# Throws
-- `ArgumentError`: If the shaft number does not exist in the plant
+- `ArgumentError`: if `shaft_number` does not exist in the plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`get_shaft_map`](@ref)
"""
function get_components_in_shaft(
sys::System,
@@ -456,18 +557,18 @@ end
"""
get_components_in_penstock(sys::System, plant::HydroPowerPlant, penstock_number::Int)
-Get all hydro generators connected to a specific penstock in a [`HydroPowerPlant`](@ref).
+Return all hydro generators connected to penstock `penstock_number` in a
+[`HydroPowerPlant`](@ref).
# Arguments
-- `sys::System`: The system containing the components
-- `plant::HydroPowerPlant`: The hydro power plant
-- `penstock_number::Int`: The penstock number to query
-
-# Returns
-- `Vector{Union{HydroTurbine, HydroPumpTurbine}}`: Vector of hydro generators on the specified penstock
+- `sys::System`: The system containing the components.
+- `plant::HydroPowerPlant`: The hydro power plant.
+- `penstock_number::Int`: The penstock number to query.
# Throws
-- `ArgumentError`: If the penstock number does not exist in the plant
+- `ArgumentError`: if `penstock_number` does not exist in the plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`get_penstock_map`](@ref)
"""
function get_components_in_penstock(
sys::System,
@@ -492,18 +593,18 @@ end
"""
get_components_in_pcc(sys::System, plant::RenewablePowerPlant, pcc_number::Int)
-Get all renewable generators and storage devices connected to a specific PCC in a [`RenewablePowerPlant`](@ref).
+Return all renewable generators and storage devices connected to PCC `pcc_number`
+(point of common coupling) in a [`RenewablePowerPlant`](@ref).
# Arguments
-- `sys::System`: The system containing the components
-- `plant::RenewablePowerPlant`: The renewable power plant
-- `pcc_number::Int`: The PCC (point of common coupling) number to query
-
-# Returns
-- `Vector{Union{RenewableGen, EnergyReservoirStorage}}`: Vector of components on the specified PCC
+- `sys::System`: The system containing the components.
+- `plant::RenewablePowerPlant`: The renewable power plant.
+- `pcc_number::Int`: The PCC number to query.
# Throws
-- `ArgumentError`: If the PCC number does not exist in the plant
+- `ArgumentError`: if `pcc_number` does not exist in the plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`get_pcc_map`](@ref)
"""
function get_components_in_pcc(
sys::System,
@@ -533,10 +634,15 @@ This attaches the plant as a supplemental attribute to the generator and records
generator's UUID in the plant's shaft map.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to add to the plant
-- `attribute::ThermalPowerPlant`: The thermal power plant
-- `shaft_number::Int`: The shaft number to associate with the generator
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to add to the plant.
+- `attribute::ThermalPowerPlant`: The thermal power plant.
+- `shaft_number::Int`: The shaft number to associate with the generator.
+
+# Throws
+- `ArgumentError`: if the generator is already associated with this plant.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -563,17 +669,27 @@ function add_supplemental_attribute!(
end
"""
- add_supplemental_attribute!(sys::System, component::Union{HydroPumpTurbine, HydroTurbine}, attribute::HydroPowerPlant; penstock_number::Int)
+ add_supplemental_attribute!(sys::System, component::Union{HydroPumpTurbine, HydroTurbine}, attribute::HydroPowerPlant, penstock_number::Int)
Add a hydro generator to a [`HydroPowerPlant`](@ref) by associating it with a penstock number.
This attaches the plant as a supplemental attribute to the generator and records the
generator's UUID in the plant's penstock map.
+!!! note
+ `penstock_number` is a positional argument, unlike the keyword `shaft_number` in the
+ [`ThermalPowerPlant`](@ref) overload. This inconsistency is a known API issue.
+
# Arguments
-- `sys::System`: The system containing the generator
-- `component::Union{HydroPumpTurbine, HydroTurbine}`: The hydro generator to add to the plant
-- `attribute::HydroPowerPlant`: The hydro power plant
-- `penstock_number::Int`: The penstock number to associate with the generator
+- `sys::System`: The system containing the generator.
+- `component::Union{HydroPumpTurbine, HydroTurbine}`: The hydro generator to add to the plant.
+- `attribute::HydroPowerPlant`: The hydro power plant.
+- `penstock_number::Int`: The penstock number to associate with the generator.
+
+# Throws
+- `ArgumentError`: if the generator is already associated with this plant.
+- `ArgumentError`: if `component` is a [`HydroDispatch`](@ref) — use [`HydroTurbine`](@ref) instead.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -599,11 +715,8 @@ function add_supplemental_attribute!(
return
end
-"""
- add_supplemental_attribute!(sys::System, component::HydroDispatch, attribute::HydroPowerPlant, args...; kwargs...)
-
-Error-throwing overload. HydroDispatch is not supported in a HydroPowerPlant.
-"""
+# Error guard: HydroDispatch is not supported in a HydroPowerPlant.
+# Included to produce a clear error instead of silently succeeding via the generic overload.
function add_supplemental_attribute!(
::System,
::HydroDispatch,
@@ -619,17 +732,26 @@ function add_supplemental_attribute!(
end
"""
- add_supplemental_attribute!(sys::System, component::Union{RenewableGen, EnergyReservoirStorage}, attribute::RenewablePowerPlant; pcc_number::Int=1)
+ add_supplemental_attribute!(sys::System, component::Union{RenewableGen, EnergyReservoirStorage}, attribute::RenewablePowerPlant, pcc_number::Int)
Add a renewable generator or storage to a [`RenewablePowerPlant`](@ref) by associating it with a PCC number.
This attaches the plant as a supplemental attribute to the generator and records the
generator's UUID in the plant's PCC map.
+!!! note
+ `pcc_number` is a positional argument, unlike the keyword `shaft_number` in the
+ [`ThermalPowerPlant`](@ref) overload. This inconsistency is a known API issue.
+
# Arguments
-- `sys::System`: The system containing the generator
-- `component::Union{RenewableGen, EnergyReservoirStorage}`: The renewable generator or storage to add to the plant
-- `attribute::RenewablePowerPlant`: The renewable power plant
-- `pcc_number::Int`: (default: 1) The PCC (point of common coupling) number to associate with the generator
+- `sys::System`: The system containing the generator.
+- `component::Union{RenewableGen, EnergyReservoirStorage}`: The renewable generator or storage to add to the plant.
+- `attribute::RenewablePowerPlant`: The renewable power plant.
+- `pcc_number::Int`: The PCC (point of common coupling) number to associate with the generator.
+
+# Throws
+- `ArgumentError`: if the component is already associated with this plant.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -659,13 +781,16 @@ end
remove_supplemental_attribute!(sys::System, component::ThermalGen, attribute::ThermalPowerPlant)
Remove a thermal generator from a [`ThermalPowerPlant`](@ref).
-This removes the plant as a supplemental attribute from the generator and removes the
-generator's UUID from the plant's shaft map.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to remove from the plant
-- `attribute::ThermalPowerPlant`: The thermal power plant
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to remove from the plant.
+- `attribute::ThermalPowerPlant`: The thermal power plant.
+
+# Throws
+- `ArgumentError`: if the generator is not associated with this plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -694,13 +819,16 @@ end
remove_supplemental_attribute!(sys::System, component::Union{HydroPumpTurbine, HydroTurbine}, attribute::HydroPowerPlant)
Remove a hydro generator from a [`HydroPowerPlant`](@ref).
-This removes the plant as a supplemental attribute from the generator and removes the
-generator's UUID from the plant's penstock map.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::Union{HydroPumpTurbine, HydroTurbine}`: The hydro generator to remove from the plant
-- `attribute::HydroPowerPlant`: The hydro power plant
+- `sys::System`: The system containing the generator.
+- `component::Union{HydroPumpTurbine, HydroTurbine}`: The hydro generator to remove from the plant.
+- `attribute::HydroPowerPlant`: The hydro power plant.
+
+# Throws
+- `ArgumentError`: if the generator is not associated with this plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -728,14 +856,17 @@ end
"""
remove_supplemental_attribute!(sys::System, component::Union{RenewableGen, EnergyReservoirStorage}, attribute::RenewablePowerPlant)
-Remove a renewable generator or storage from a [`RenewablePowerPlant`](@ref).
-This removes the plant as a supplemental attribute from the generator and removes the
-generator's UUID from the plant's PCC map.
+Remove a renewable generator or storage device from a [`RenewablePowerPlant`](@ref).
# Arguments
-- `sys::System`: The system containing the generator
-- `component::Union{RenewableGen, EnergyReservoirStorage}`: The renewable generator or storage to remove from the plant
-- `attribute::RenewablePowerPlant`: The renewable power plant
+- `sys::System`: The system containing the component.
+- `component::Union{RenewableGen, EnergyReservoirStorage}`: The renewable generator or storage device to remove from the plant.
+- `attribute::RenewablePowerPlant`: The renewable power plant.
+
+# Throws
+- `ArgumentError`: if the component is not associated with this plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -764,14 +895,20 @@ end
add_supplemental_attribute!(sys::System, component::ThermalGen, attribute::CombinedCycleBlock; hrsg_number::Int)
Add a thermal generator to a [`CombinedCycleBlock`](@ref) by associating it with an HRSG number.
-Only generators with CT (combustion turbine as HRSG input) or CA (combined cycle steam part as HRSG output)
-prime mover types can be added.
+Only generators with `CT` (combustion turbine as HRSG input) or `CA` (combined cycle steam part
+as HRSG output) prime mover types can be added.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to add to the block (must have prime mover type CT or CA)
-- `attribute::CombinedCycleBlock`: The combined cycle block
-- `hrsg_number::Int`: The HRSG number to associate with the generator
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to add to the block.
+- `attribute::CombinedCycleBlock`: The combined cycle block.
+- `hrsg_number::Int`: The HRSG number to associate with the generator.
+
+# Throws
+- `ArgumentError`: if the generator is already associated with this block.
+- `ArgumentError`: if the generator's prime mover type is not `CT` or `CA`.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -817,14 +954,18 @@ end
"""
remove_supplemental_attribute!(sys::System, component::ThermalGen, attribute::CombinedCycleBlock)
-Remove a thermal generator from a [`CombinedCycleBlock`](@ref).
-This removes the block as a supplemental attribute from the generator and removes the
-generator's UUID from the block's HRSG maps.
+Remove a thermal generator from a [`CombinedCycleBlock`](@ref). The generator is removed
+from whichever HRSG map corresponds to its prime mover type (`CT` or `CA`).
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to remove from the block
-- `attribute::CombinedCycleBlock`: The combined cycle block
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to remove from the block.
+- `attribute::CombinedCycleBlock`: The combined cycle block.
+
+# Throws
+- `ArgumentError`: if the generator is not associated with this block.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -866,14 +1007,20 @@ end
"""
add_supplemental_attribute!(sys::System, component::ThermalGen, attribute::CombinedCycleFractional; exclusion_group::Int)
-Add a thermal generator to a [`CombinedCycleFractional`](@ref) by associating it with an exclusion group number.
-Only generators with CC (combined cycle) prime mover type can be added.
+Add a thermal generator to a [`CombinedCycleFractional`](@ref) by associating it with an
+exclusion group number. Only generators with `CC` (combined cycle) prime mover type can be added.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to add to the plant (must have prime mover type CC)
-- `attribute::CombinedCycleFractional`: The combined cycle fractional plant
-- `exclusion_group::Int`: The exclusion group number to associate with the generator
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to add to the plant.
+- `attribute::CombinedCycleFractional`: The combined cycle fractional plant.
+- `exclusion_group::Int`: The exclusion group number to associate with the generator.
+
+# Throws
+- `ArgumentError`: if the generator is already associated with this plant.
+- `ArgumentError`: if the generator's prime mover type is not `CC`.
+
+See also: [`remove_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function add_supplemental_attribute!(
sys::System,
@@ -914,13 +1061,16 @@ end
remove_supplemental_attribute!(sys::System, component::ThermalGen, attribute::CombinedCycleFractional)
Remove a thermal generator from a [`CombinedCycleFractional`](@ref).
-This removes the plant as a supplemental attribute from the generator and removes the
-generator's UUID from the plant's exclusion maps.
# Arguments
-- `sys::System`: The system containing the generator
-- `component::ThermalGen`: The thermal generator to remove from the plant
-- `attribute::CombinedCycleFractional`: The combined cycle fractional plant
+- `sys::System`: The system containing the generator.
+- `component::ThermalGen`: The thermal generator to remove from the plant.
+- `attribute::CombinedCycleFractional`: The combined cycle fractional plant.
+
+# Throws
+- `ArgumentError`: if the generator is not associated with this plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`begin_supplemental_attributes_update`](@ref)
"""
function remove_supplemental_attribute!(
sys::System,
@@ -954,18 +1104,19 @@ end
"""
get_components_in_exclusion_group(sys::System, plant::CombinedCycleFractional, exclusion_group::Int)
-Get all thermal generators in a specific exclusion group of a [`CombinedCycleFractional`](@ref).
+Return all thermal generators in exclusion group `exclusion_group` of a
+[`CombinedCycleFractional`](@ref). Only one generator per exclusion group may operate
+simultaneously.
# Arguments
-- `sys::System`: The system containing the components
-- `plant::CombinedCycleFractional`: The combined cycle fractional plant
-- `exclusion_group::Int`: The exclusion group number to query
-
-# Returns
-- `Vector{ThermalGen}`: Vector of thermal generators in the specified exclusion group
+- `sys::System`: The system containing the components.
+- `plant::CombinedCycleFractional`: The combined cycle fractional plant.
+- `exclusion_group::Int`: The exclusion group number to query.
# Throws
-- `ArgumentError`: If the exclusion group does not exist in the plant
+- `ArgumentError`: if `exclusion_group` does not exist in the plant.
+
+See also: [`add_supplemental_attribute!`](@ref), [`get_operation_exclusion_map`](@ref)
"""
function get_components_in_exclusion_group(
sys::System,
diff --git a/src/subsystems.jl b/src/subsystems.jl
index 38a0df2820..6414b446d2 100644
--- a/src/subsystems.jl
+++ b/src/subsystems.jl
@@ -1,5 +1,13 @@
"""
Add a new subsystem to the system.
+
+Subsystems are named groupings of components within a [`System`](@ref), useful for
+representing e.g., regional partitions or study areas. Components must be added
+separately via [`add_component_to_subsystem!`](@ref).
+
+Throws `ArgumentError` if the maximum number of subsystems has been reached.
+
+See also: [`remove_subsystem!`](@ref), [`get_subsystems`](@ref)
"""
function add_subsystem!(sys::System, subsystem_name::AbstractString)
_check_num_subsystems(sys)
@@ -8,24 +16,32 @@ end
"""
Return the number of subsystems stored in the system.
+
+See also: [`get_subsystems`](@ref), [`add_subsystem!`](@ref)
"""
get_num_subsystems(sys::System) = IS.get_num_subsystems(sys.data)
"""
-Return an iterator of all subsystem names in the system.
+Return an iterator of subsystem name strings stored in the system.
+
+See also: [`add_subsystem!`](@ref), [`get_num_subsystems`](@ref), [`get_subsystem_components`](@ref)
"""
get_subsystems(sys::System) = IS.get_subsystems(sys.data)
"""
Remove a subsystem from the system.
-Throws ArgumentError if the subsystem name is not stored.
+Throws `ArgumentError` if the subsystem name is not stored.
+
+See also: [`add_subsystem!`](@ref), [`get_subsystems`](@ref)
"""
remove_subsystem!(sys::System, subsystem_name::AbstractString) =
IS.remove_subsystem!(sys.data, subsystem_name)
"""
Return true if the system has one or more subsystems.
+
+See also: [`get_subsystems`](@ref), [`add_subsystem!`](@ref)
"""
function has_subsystems(sys::System)
for _ in get_subsystems(sys)
@@ -37,6 +53,13 @@ end
"""
Add a component to a subsystem.
+
+The subsystem must already exist (see [`add_subsystem!`](@ref)). If the component is a
+[`StaticInjectionSubsystem`](@ref), all of its subcomponents are also added.
+
+Throws `ArgumentError` if the subsystem name is not stored.
+
+See also: [`remove_component_from_subsystem!`](@ref), [`get_subsystem_components`](@ref)
"""
function add_component_to_subsystem!(
sys::System,
@@ -91,9 +114,11 @@ function handle_component_removal_from_subsystem!(
end
"""
-Return a Generator of all components in the subsystem.
+Return an iterator of all components in the subsystem.
+
+Throws `ArgumentError` if the subsystem name is not stored.
-Throws ArgumentError if the subsystem name is not stored.
+See also: [`add_component_to_subsystem!`](@ref), [`get_subsystems`](@ref)
"""
get_subsystem_components(sys::System, subsystem_name::AbstractString) =
IS.get_subsystem_components(sys.data, subsystem_name)
@@ -101,7 +126,12 @@ get_subsystem_components(sys::System, subsystem_name::AbstractString) =
"""
Remove a component from a subsystem.
-Throws ArgumentError if the subsystem name or component is not stored.
+If the component is a [`StaticInjectionSubsystem`](@ref), all of its subcomponents are
+also removed from the subsystem.
+
+Throws `ArgumentError` if the subsystem name or component is not stored.
+
+See also: [`add_component_to_subsystem!`](@ref), [`get_subsystem_components`](@ref)
"""
function remove_component_from_subsystem!(
sys::System,
@@ -122,7 +152,9 @@ remove_component_from_subsystems!(
) = remove_component_from_subsystems!(sys.data, component)
"""
-Return true if the component is in the subsystem.
+Return true if the component is assigned to the subsystem.
+
+See also: [`is_assigned_to_subsystem`](@ref), [`get_subsystem_components`](@ref), [`add_component_to_subsystem!`](@ref)
"""
has_component(
sys::System,
@@ -131,7 +163,9 @@ has_component(
) = IS.has_component(sys.data, subsystem_name, component)
"""
-Return a Vector of subsystem names that contain the component.
+Return a `Vector` of subsystem name strings that contain the component.
+
+See also: [`is_assigned_to_subsystem`](@ref), [`add_component_to_subsystem!`](@ref)
"""
get_assigned_subsystems(
sys::System,
@@ -139,13 +173,19 @@ get_assigned_subsystems(
) = IS.get_assigned_subsystems(sys.data, component)
"""
-Return true if the component is assigned to any subsystems.
+Return true if the component is assigned to any subsystem.
+
+See also: [`is_assigned_to_subsystem(sys, component, subsystem_name)`](@ref is_assigned_to_subsystem(::System, ::Component, ::AbstractString)),
+[`get_assigned_subsystems`](@ref)
"""
is_assigned_to_subsystem(sys::System, component::Component) =
IS.is_assigned_to_subsystem(sys.data, component)
"""
-Return true if the component is assigned to the subsystem.
+Return true if the component is assigned to the named subsystem.
+
+See also: [`is_assigned_to_subsystem(sys, component)`](@ref is_assigned_to_subsystem(::System, ::Component)),
+[`get_assigned_subsystems`](@ref)
"""
is_assigned_to_subsystem(
sys::System,
@@ -159,6 +199,13 @@ Return the UUIDs of all components in the given subsystem.
get_component_uuids(sys::System, subsystem_name::AbstractString) =
IS.get_component_uuids(sys.data, subsystem_name)
+function check_subsystems(sys::System)
+ for component in get_components(Component, sys)
+ check_subsystems(sys, component)
+ end
+ return
+end
+
function check_subsystems(sys::System, component::Component)
_check_arc_consistency(sys, component)
_check_branch_consistency(sys, component)
diff --git a/test/test_system.jl b/test/test_system.jl
index 967e326c8d..c88bdf4070 100644
--- a/test/test_system.jl
+++ b/test/test_system.jl
@@ -424,7 +424,7 @@ end
@test_throws ArgumentError get_time_series(typeof(forecast), gen, get_name(forecast))
end
-@testset "Test multi-interval DeterministicSingleTimeSeries" begin
+@testset "Test DeterministicSingleTimeSeries transform replaces previous" begin
sys = System(100.0)
bus = ACBus(nothing)
bus.bustype = ACBusTypes.REF
@@ -449,94 +449,38 @@ end
interval1 = Dates.Minute(30)
interval2 = Dates.Hour(1)
- transform_single_time_series!(
- sys,
- horizon,
- interval1;
- delete_existing = false,
- )
- transform_single_time_series!(
- sys,
- horizon,
- interval2;
- delete_existing = false,
- )
-
- @test has_time_series(
- gen,
- DeterministicSingleTimeSeries,
- sts_name;
- interval = interval1,
- )
- @test has_time_series(
- gen,
- DeterministicSingleTimeSeries,
- sts_name;
- interval = interval2,
- )
-
- ts1 = get_time_series(
- DeterministicSingleTimeSeries,
- gen,
- sts_name;
- interval = interval1,
- )
+ # First transform creates DeterministicSingleTimeSeries with interval1
+ transform_single_time_series!(sys, horizon, interval1)
+ @test has_time_series(gen, DeterministicSingleTimeSeries, sts_name)
+ ts1 = get_time_series(DeterministicSingleTimeSeries, gen, sts_name)
@test ts1 isa DeterministicSingleTimeSeries
@test IS.get_interval(ts1) == interval1
+ @test get_forecast_interval(sys) == interval1
+ @test get_forecast_horizon(sys) == horizon
+ @test get_forecast_window_count(sys) > 0
- ts2 = get_time_series(
- DeterministicSingleTimeSeries,
- gen,
- sts_name;
- interval = interval2,
- )
+ # Second transform replaces the first (IS removes all DeterministicSingleTimeSeries
+ # before adding the new ones; multi-interval coexistence is not supported in IS 3.3.x)
+ transform_single_time_series!(sys, horizon, interval2)
+ @test has_time_series(gen, DeterministicSingleTimeSeries, sts_name)
+ ts2 = get_time_series(DeterministicSingleTimeSeries, gen, sts_name)
@test ts2 isa DeterministicSingleTimeSeries
@test IS.get_interval(ts2) == interval2
+ @test get_forecast_interval(sys) == interval2
+ @test get_forecast_window_count(sys) > 0
- @test_throws ArgumentError get_time_series(
- DeterministicSingleTimeSeries,
- gen,
- sts_name,
- )
-
- @test get_forecast_interval(sys; interval = interval1) == interval1
- @test get_forecast_interval(sys; interval = interval2) == interval2
- @test get_forecast_horizon(sys; interval = interval1) == horizon
- @test get_forecast_window_count(sys; interval = interval1) > 0
- @test get_forecast_window_count(sys; interval = interval2) > 0
-
- ts_interval1 = collect(
- get_time_series_multiple(
- sys;
- type = DeterministicSingleTimeSeries,
- interval = interval1,
- ),
- )
- @test length(ts_interval1) > 0
- for ts in ts_interval1
- @test IS.get_interval(ts) == interval1
+ ts_all = collect(get_time_series_multiple(sys; type = DeterministicSingleTimeSeries))
+ @test length(ts_all) > 0
+ for ts in ts_all
+ @test IS.get_interval(ts) == interval2
end
- remove_time_series!(
- sys,
- DeterministicSingleTimeSeries,
- gen,
- sts_name;
- interval = interval1,
- )
- @test !has_time_series(
- gen,
- DeterministicSingleTimeSeries,
- sts_name;
- interval = interval1,
- )
- @test has_time_series(
- gen,
- DeterministicSingleTimeSeries,
- sts_name;
- interval = interval2,
- )
+ # The underlying SingleTimeSeries is still present after the transform
+ @test has_time_series(gen, SingleTimeSeries, sts_name)
+ # Removing DeterministicSingleTimeSeries leaves SingleTimeSeries intact
+ remove_time_series!(sys, DeterministicSingleTimeSeries)
+ @test !has_time_series(gen, DeterministicSingleTimeSeries, sts_name)
@test has_time_series(gen, SingleTimeSeries, sts_name)
end