diff --git a/Project.toml b/Project.toml index a915a44253..c10f4a1aaa 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PowerSystems" uuid = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" authors = ["Jose Daniel Lara", "Daniel Thom", "Clayton Barrows", "Sourabh Dalvi", "Dheepak Krishnamurthy"] -version = "4.4.0" +version = "4.3.1" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" @@ -27,7 +27,7 @@ DataFrames = "1" DataStructures = "~0.18" Dates = "1" DocStringExtensions = "0.8, 0.9.2" -InfrastructureSystems = "^2.3" +InfrastructureSystems = "^2.2" InteractiveUtils = "1" JSON3 = "1" LinearAlgebra = "1" @@ -35,7 +35,7 @@ Logging = "1" PowerFlowData = "^1.5" PrettyTables = "1, 2" TimeSeries = "0.24" -YAML = "~0.4" UUIDs = "1" Unicode = "1" +YAML = "~0.4" julia = "^1.6" diff --git a/README.md b/README.md index efaf6b7e94..522103bd85 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The `PowerSystems.jl` package provides a rigorous data model using Julia structu - Dynamic Generators Models - Dynamic Inverter Models -For information on using the package and a more extensive list of device data enabled, see the [stable documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/). Use the [in-development documentation](https://nrel-sienna.github.io/PowerSystems.jl/dev/) for the version of the documentation which contains the unreleased features. +For a more exhaustive list, check the [Documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable). ## Parsing capabilities in PowerSystems diff --git a/docs/Project.toml b/docs/Project.toml index 3bd9f31917..65ea304550 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -10,6 +10,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" PowerSystemCaseBuilder = "f00506e0-b84f-492a-93c2-c0a9afc4364e" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" TimeSeries = "9e3dc215-6440-5c97-bce1-76c03772f85e" TypeTree = "04da0e3b-1cad-4b2c-a963-fc1602baf1af" diff --git a/docs/make.jl b/docs/make.jl index 22d9d288d6..a603304670 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,7 +13,9 @@ pages = OrderedDict( "Create and Explore a Power `System`" => "tutorials/creating_system.md", "Working with Time Series" => "tutorials/working_with_time_series.md", "Adding Data for Dynamic Simulations" => "tutorials/add_dynamic_data.md", + "Manipulating Data Sets" => "tutorials/manipulating_datasets.md" ], + "How to..." => Any[ "...install PowerSystems.jl" => "how_to/install.md", "...load a `system` from `PowerSystemCaseBuilder`" => "how_to/powersystembuilder.md", @@ -45,7 +47,7 @@ pages = OrderedDict( "Reference" => Any["Public API" => "api/public.md", "Glossary and Acronyms" => "api/glossary.md", - "Type Tree" => "api/type_tree.md", + "Type Hierarchy" => "api/type_tree.md", "`ValueCurve` Options" => "api/valuecurve_options.md", "Specifying the category of..." => "api/enumerated_types.md", "Citation" => "api/citation.md", @@ -89,6 +91,7 @@ pages["Model Library"] = make_model_library( ) ) + # postprocess function to insert md function insert_md(content) m = match(r"APPEND_MARKDOWN\(\"(.*)\"\)", content) @@ -106,7 +109,8 @@ folders = Dict( "Model Library" => filter(julia_file_filter, readdir("docs/src/model_library")), "Explanation" => filter(julia_file_filter, readdir("docs/src/explanation")), "How to..." => filter(julia_file_filter, readdir("docs/src/how_to")), -) + "Tutorials" => filter(julia_file_filter, readdir("docs/src/tutorials")) +) for (section, folder) in folders for file in folder @show file diff --git a/docs/src/api/type_tree.md b/docs/src/api/type_tree.md index 62b03c8562..68fbbb183b 100644 --- a/docs/src/api/type_tree.md +++ b/docs/src/api/type_tree.md @@ -1,4 +1,4 @@ -# Type Tree +# Type Hierarchy Here is the complete `PowerSystems.jl` type hierarchy: diff --git a/docs/src/how_to/market_bid_cost.md b/docs/src/how_to/market_bid_cost.md index 33174af881..4a1bd8ce13 100644 --- a/docs/src/how_to/market_bid_cost.md +++ b/docs/src/how_to/market_bid_cost.md @@ -4,61 +4,9 @@ A [`MarketBidCost`](@ref) is an `OperationalCost` data structure that allows the cost model that is very similar to most US electricity market auctions with bids for energy and ancillary services jointly. This page showcases how to create data for this cost function. -## Adding a Single Incremental Energy bids to MarketBidCost +## Adding Energy bids to MarketBidCost -### Construct directly the MarketBidCost using the `make_market_bid_curve` method. - -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 -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) -``` - -Then a device with MarketBidCost can be directly instantiated using: - -```@repl market_bid_cost -using PowerSystems, Dates -bus = ACBus(1, "nodeE", "REF", 0, 1.0, (min = 0.9, max = 1.05), 230, nothing, nothing) - -generator = ThermalStandard(; - name = "Brighton", - available = true, - status = true, - bus = bus, - active_power = 6.0, - reactive_power = 1.50, - rating = 0.75, - prime_mover_type = PrimeMovers.ST, - fuel = ThermalFuels.COAL, - active_power_limits = (min = 0.0, max = 6.0), - reactive_power_limits = (min = -4.50, max = 4.50), - time_limits = (up = 0.015, down = 0.015), - ramp_limits = (up = 5.0, down = 3.0), - operation_cost = MarketBidCost(; - no_load_cost = 0.0, - start_up = (hot = 0.0, warm = 0.0, cold = 0.0), - shut_down = 0.0, - incremental_offer_curves = proposed_offer_curve, - ), - base_power = 100.0, -) -``` - -Similarly, a decremental offer curve can also be created directly using the same helper method: - -```@repl 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) -``` - -and can be added to a `MarketBidCost` using the field `decremental_offer_curves`. - -## Adding Time Series Energy bids to MarketBidCost - -### Step 1: Constructing device with MarketBidCost +### Step 1: Constructiong device with MarketBidCost When using [`MarketBidCost`](@ref), the user can add the cost struct to the device specifying only certain elements, at this point the actual energy cost bids don't need to be populated/passed. @@ -95,22 +43,19 @@ 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`. -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. +of any type (i.e. `SingleTimeSeries` or `Deterministic`) and data can be `Array{Float64}`, +`Array{Tuple{Float64, Float64}}` or `Array{Array{Tuple{Float64,Float64}}`. If the data is +just floats then the cost in the optimization is seen as a constant variable cost, but if +data is a Tuple or `Array{Tuple}` then the model expects the tuples to be cost & power-point +pairs (cost in $/p.u-hr & power-point in p.u-hr), which is modeled same as TwoPartCost or +ThreePartCost. Code below shows an example of how to build a TimeSeriesData. ```@repl 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]) data = Dict( - initial_time => [ - psd1, - psd2, - ], + Dates.DateTime("2020-01-01") => [ + [(0.0, 0.05), (290.1, 0.0733), (582.72, 0.0967), (894.1, 0.120)], + [(0.0, 0.05), (300.1, 0.0733), (600.72, 0.0967), (900.1, 0.120)]], ) time_series_data = Deterministic(; name = "variable_cost", @@ -119,6 +64,20 @@ time_series_data = Deterministic(; ) ``` +**NOTE:** Due to [limitations in DataStructures.jl](https://github.com/JuliaCollections/DataStructures.jl/issues/239), +in `PowerSystems.jl` when creating Forecasts or TimeSeries for your MarketBidCost, you need +to define your data as in the example or with a very explicit container. Otherwise, it won't +discern the types properly in the constructor and will return `SortedDict{Any,Any,Base.Order.ForwardOrdering}` which causes the constructor in `PowerSystems.jl` to fail. For instance, you need to define +the `Dict` with the data as follows: + +```julia +# Very verbose dict definition +data = Dict{DateTime, Array{Array{Tuple{Float64, Float64}, 1}, 1}}() +for t in range(initial_time_sys; step = Hour(1), length = window_count) + data[t] = MY_BID_DATA +end +``` + ### Step 3a: Adding Energy Bid TimeSeriesData to the device To add energy market bids time-series to the `MarketBidCost`, use `set_variable_cost!`. The @@ -127,20 +86,12 @@ arguments for `set_variable_cost!` 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. ```@repl market_bid_cost sys = System(100.0, [bus], [generator]) -set_variable_cost!(sys, generator, time_series_data, UnitSystem.NATURAL_UNITS) +set_variable_cost!(sys, generator, time_series_data) ``` -**Note:** `set_variable_cost!` add curves to the `incremental_offer_curves` in the MarketBidCost. -Similarly, `set_incremental_variable_cost!` can be used to add curves to the `incremental_offer_curves`. -On the other hand, `set_decremental_variable_cost!` must be used to decremental curves (usually for storage or demand). -The creation of the TimeSeriesData is similar to Step 2, using `PiecewiseStepData` - ### Step 3b: Adding Service Bid TimeSeriesData to the device Similar to adding energy market bids, for adding bids for ancillary services, use @@ -149,14 +100,12 @@ Similar to adding energy market bids, for adding bids for ancillary services, us ```@repl market_bid_cost service = VariableReserve{ReserveUp}("example_reserve", true, 0.6, 2.0) add_service!(sys, service, get_component(ThermalStandard, sys, "Brighton")) - -psd3 = PiecewiseStepData([0.0, 10.0], [650.3]) -psd4 = PiecewiseStepData([0.0, 10.0], [750.0]) -data = Dict(Dates.DateTime("2020-01-01") => [psd3, psd4]) +data = + Dict(Dates.DateTime("2020-01-01") => [650.3, 750.0]) time_series_data = Deterministic(; name = get_name(service), data = data, resolution = Dates.Hour(1), ) -set_service_bid!(sys, generator, service, time_series_data, UnitSystem.NATURAL_UNITS) +set_service_bid!(sys, generator, service, time_series_data) ``` diff --git a/docs/src/tutorials/get_component_data.md b/docs/src/tutorials/get_component_data.md deleted file mode 100644 index 8aa4403795..0000000000 --- a/docs/src/tutorials/get_component_data.md +++ /dev/null @@ -1,122 +0,0 @@ -# [Getting, Setting, and Viewing Data](@id get_components_tutorial ) - -In this tutorial, we will explore the data in a `System`, including looking at a summary of -the system and getting both its components and their data. We will also start checking for -time-series data, which we will explore more in the tutorial on -[Working with Time Series Data](@ref tutorial_time_series). - -In [Create and Explore a Power `System`](@ref), we created a basic `System` with nodes, a transmission -line, and a few generators. Let's recreate that system if you don't have it already: - -```@setup get_component_data -using PowerSystems; -sys = System(100.0); -bus1 = ACBus(1, "bus1", ACBusTypes.REF, 0.0, 1.0, (min = 0.9, max = 1.05), 230.0); -bus2 = ACBus(2, "bus2", ACBusTypes.PV, 0.0, 1.0, (min = 0.9, max = 1.05), 230.0); - -``` - -PowerSystems provides functional interfaces to all data. The following examples outline -the intended approach to accessing data expressed using PowerSystems. - -PowerSystems enforces unique `name` fields between components of a particular concrete type. -So, in order to retrieve a specific component, the user must specify the type of the component -along with the name and system - -#### Accessing components and their data - -```@repl get_components -get_component(ACBus, sys, "nodeA") -get_component(Line, sys, "1") -``` - -Similarly, you can access all the components of a particular type: *note: the return type -of get_components is a `FlattenIteratorWrapper`, so call `collect` to get an `Array` - -```@repl get_components -get_components(ACBus, sys) |> collect -``` - -`get_components` also works on abstract types: - -```@repl get_components -get_components(Branch, sys) |> collect -``` - -The fields within a component can be accessed using the `get_*` functions: -*It's highly recommended that users avoid using the `.` to access fields since we make no -guarantees on the stability field names and locations. We do however promise to keep the -accessor functions stable.* - -```@repl get_components -bus1 = get_component(ACBus, sys, "nodeA") -@show get_name(bus1); -@show get_magnitude(bus1); -nothing #hide -``` - -## Accessing components stored in the system - - -```@repl system -using PowerSystems -file_dir = joinpath(pkgdir(PowerSystems), "docs", "src", "tutorials", "tutorials_data") -system = System(joinpath(file_dir, "RTS_GMLC.m")); -thermal_gens = get_components(ThermalStandard, system) -``` - -It is also possible to execute [`get_components`](@ref) with abstract types from the -[abstract tree](@ref type_structure). For instance, it is possible to retrieve all renewable -generators - -```@repl system -thermal_gens = get_components(RenewableGen, system) -``` - -The most common filtering requirement is by component name and for this case the method -[`get_component`](@ref) returns a single component taking the device type, system and name as arguments. - -```@repl system -my_thermal_gen = get_component(ThermalStandard, system, "323_CC_1") -``` - -## Accessing data stored in a component - -__Using the "dot" access to get a parameter value from a component is actively discouraged, use "getter" functions instead__ - - -For example, the `my_thermal_gen.active_power_limits` parameter of a thermal generator should be accessed as follows: - -```@repl system -get_active_power_limits(my_thermal_gen) -``` - -You can also view data from all instances of a concrete type in one table with the function `show_components`. It provides a few options: - - 1. View the standard fields by accepting the defaults. - 2. Pass a dictionary where the keys are column names and the values are functions that accept a component as a single argument. - 3. Pass a vector of symbols that are field names of the type. - -```@repl system -show_components(system, ThermalStandard) -show_components(system, ThermalStandard, Dict("has_time_series" => x -> has_time_series(x))) -show_components(system, ThermalStandard, [:active_power, :reactive_power]) -``` - -# to do: add a link in the system that MD explanation to these examples diff --git a/docs/src/tutorials/manipulating_datasets.md b/docs/src/tutorials/manipulating_datasets.md new file mode 100644 index 0000000000..99c7ef9437 --- /dev/null +++ b/docs/src/tutorials/manipulating_datasets.md @@ -0,0 +1,216 @@ +# Manipulating Data Sets + +`PowerSystems` provides function interfaces to all data, and in this tutorial we will explore how to do this using the `get_*`, `set_*`, and `show_components` functions. + +!!! note "Understanding the Behavior of Getters and Setters" + `PowerSystems` returns Julia iterators in order to avoid unnecessary memory allocations. The `get_components` function returns an iterator that loops through data fields to access specific parameters. The `set_*` function uses an iterator like `get_components` to manipulate specific parameters of components. + +## Viewing Components in the System +We are going to begin by loading in a test case from the [`PowerSystemCaseBuilder.jl`](https://nrel-sienna.github.io/PowerSystems.jl/stable/how_to/powersystembuilder/#psb): +```@repl system +using PowerSystems; +using PowerSystemCaseBuilder; +sys = build_system(PSISystems, "c_sys5_pjm") +``` + +We can use the [`show_components`](@ref) function to view data in our system. Let's start by viewing all of the [`ThermalStandard`](@ref) components in the system. + +```@repl system +show_components(ThermalStandard, sys) +``` +We can see there are five thermal generators in the system. +Similarly we can see all of the [`ACBus`](@ref) components in our system. +```@repl system +show_components(ACBus, sys) +``` +Notice in both the [`ACBus`](@ref) example and [`ThermalStandard`](@ref) example, a table with the name and availability are returned. The availability is the standard parameter returned when using [`show_components`](@ref). + +We can also view specific parameters within components using the [`show_components`](@ref) function. For example, we can view the type of [`fuel`](@reftf_list) the thermal generators are using: +```@repl system +show_components(ThermalStandard, sys, [:fuel]) +``` +If we were interested in viewing more than one parameter, like the `active power` and `reactive power` of the thermal generators: +```@repl system +show_components(ThermalStandard, sys, [:active_power, :reactive_power]) +``` +We can see a table is returned with both `active_power` and `reactive_power`. +## Accessing and Updating a Component in a System +We can access a component in our system using the [`get_component`](@ref) function. For example, if we are interested in accessing a [`ThermalStandard`](@ref) component in the system we can do so using the component [`type`](@id type_structure) and name. From above we know the names of the thermal generators. +```@repl system +solitude = get_component(ThermalStandard, sys, "Solitude") +``` + + +The parameters associated with that generator should be returned. If we are interested in accessing a particular parameter of that generator we can use a `get_*` function. For example, if we are interested in the [`fuel`](@reftf_list) we can use [`get_fuel`](@ref) +```@repl system +get_fuel(solitude) +``` +You should see the [`fuel`](@reftf_list) parameter returned. + +To recap, [`get_component`](@ref) will return all parameters of a specific componenet, but we can use a specific `get_*` function to return a particular parameter. + +To update a parameter we can use a specific `set_*` function. We can use [`set_fuel!`](@ref) to update the fuel parameter of Solitude to natural gas. +```@repl system +set_fuel!(solitude, ThermalFuels.NATURAL_GAS) +``` +We can check that the `Solitude` fuel has been updated to `ThermalFuels.AG_BIPRODUCT`: +```@repl system +show_components(ThermalStandard, sys, [:fuel]) +``` +Another example of using a specific `get_*` function and `set_*` function is when you are updating the `active_power` parameter. We can access this parameter by using the [`get_active_power`](@ref): +```@repl system +get_active_power(solitude) +``` +We can then update it using [`set_active_power!`](@ref): +```@repl system +set_active_power!(solitude, 4.0) +``` +We can see that our `active_power` parameter has been updated to 4.0. + + +## Accessing and Updating Multiple Components in the System at Once +We can also update more than one component at a time using the [`get_components`](@ref) and `set_*` functions. + +Let's say we were interested in updating the `base_voltage` parameter for all of the [`ACBus`](@ref) components. We can see that currently the `base_voltages` are: +```@repl system +show_components(ACBus, sys, [:base_voltage]) +``` +But what if we are looking to change them to 250.0? Let's start by getting an iterator for all the buses using [`get_components`](@ref): +```@repl system +buses = get_components(ACBus, sys) +``` +Now using the [`set_base_voltage!`](@ref) function and a `for loop` we can update the voltage: +```@repl system +for i in buses +set_base_voltage!(i, 250.0) +end +``` +We can see that all of the buses now have a `base_voltage` of 250.0: +```@repl system +show_components(ACBus, sys, [:base_voltage]) +``` +If we were interested in updating the fuel in all the thermal generators, we would use a similar approach. We begin by grabbing an iterator for all the components in `ThermalStandard`. +```@repl system +thermal_gens = get_components(ThermalStandard, sys) +``` +Now, using a the [`set_fuel!`](https://nrel-sienna.github.io/PowerSystems.jl/stable/model_library/generated_ThermalStandard/#PowerSystems.set_fuel!-Tuple{ThermalStandard,%20Any}) and a for loop, we will update the fuel to `NATURAL_GAS`. +```@repl system +for i in thermal_gens +set_fuel!(i, ThermalFuels.NATURAL_GAS) +end +``` +We can see that all the fuel type for all the thermal gens has been updated. +```@repl system +show_components(ThermalStandard, sys, [:fuel]) +``` +We can also use a dot operator with a specific `get_*` function and the [`get_components`](@ref) function to return a vector of parameters for multiple components: +```@repl system +get_fuel.(get_components(ThermalStandard, sys)) +``` + +## Filtering Specific Data +We have seen how to update a single component, and all the components of a specific type, but what if we are interested in updating particular components? We can do this using filter functions. For example, let's say we are interested in updating all the `active_power` parameters of the thermal generators except `Solitude`. + +Let's start by seeing the current `active_power` parameters. +```@repl system +show_components(ThermalStandard, sys, [:active_power]) +``` +Let's grab an iterator for the all the thermal generators except `Solitdue` using a filter function: +```@repl system +thermal_not_solitude = get_components(x -> get_name(x) != "Solitude", ThermalStandard, sys) +``` +We can see that four `ThermalStandard` components are returned. Now let's update the `active_power` parameter of these four thermal generators using the [`set_active_power`](https://nrel-sienna.github.io/PowerSystems.jl/stable/model_library/generated_EnergyReservoirStorage/#PowerSystems.set_active_power!-Tuple{EnergyReservoirStorage,%20Any}) function. +```@repl system +for i in thermal_not_solitude +set_active_power!(i, 0.0) +end +``` +Let's check the update using `show_components`: +```@repl system +show_components(ThermalStandard, sys, [:active_power]) +``` +We can see that all the `active_power` parameters are 0.0, except sundance. + +We can also filter using parameter values. For example, we can access all the thermal generators that have ratings of either 5.2 or 0.5: +```@repl system +show_components(ThermalStandard, sys, [:rating]) +``` +We can see that `Solitude` has a rating of 5.2 and `Alta` has a rating of 0.5. +```@repl system +thermal_rating = get_components(x -> get_rating(x) == 5.2 || get_rating(x) == 0.5, ThermalStandard, sys ) +``` +We can see that two components are returned. If we wanted to update those ratings to 4.0: +```@repl system +for i in thermal_rating +set_rating!(i, 4.0) +end +``` +The rating parameters of `Solitude` and `Alta` are now 4.0: +```@repl system +show_components(ThermalStandard, sys, [:rating]) +``` + +## Getting Available Components +The [`get_available_components`](@ref) function allows us to grab all the components of a particular type that are available. For example, if we were interested in grabbing all the available renewable generators: +```@repl system +get_available_components(RenewableDispatch, sys) +``` +The two `RenewableDispatch` components are returned because they are available. + +We can update the availability of components using the [`set_available!`](@ref) function. Let's filter all of the thermal generators that have an `active_power` of 0.0, and set their availability to false. +```@repl system +not_available = get_components(x -> get_active_power(x) == 0.0, ThermalStandard, sys) +``` +```@repl system +for i in not_available +set_available!(i, 0) +end +``` +Let's check the availability of our [`ThermalStandard`](@ref) components. +```@repl system +show_components(ThermalStandard, sys) +``` +Recall that the default columns returned when using [`show_components`](@ref) are the name and availability. + +## Getting Buses +We can access the [`ACbus`](@ref) components in the system using the [`get_buses`](@ref) function. There are multiple approaches to using this function, but in this tutorial we are going to focus on getting buses using the ID numbers. +Let's begin by accessing the ID numbers associated with the [`ACBus`](@ref) components using the [`get_bus_numbers`](@ref) function. +```@repl system +get_bus_numbers(sys) +``` +We can see that a vector of values 1:5 is returned. Now let's grab the buses using the [`get_buses`](@ref) function. +```@repl system +get_buses(sys, Set(1:5)) +``` +The 5 buses in the system are returned. +## Updating Component Names +When we are updating a component name using the function [`set_name!`](@ref) it is important to note that this not only changes the field `name`, but also changes the container itself. + +Recall that we created an iterator called `thermal_gens` for all the thermal generators. We can use the [`get_name`](@ref) function to access the `name` parameter for these components. +```@repl system +for i in thermal_gens +@show get_name(i) +end +``` +To update the names we will use the [`set_name`](@ref) function. However, it is important to note that using [`set_name`](@ref) not only changes the field `name`, but also changes the container itself. Therefore, it is important that it is important to do the following: +```@repl system +for thermal_gen in collect(get_components(ThermalStandard, sys)) + set_name!(sys, thermal_gen, get_name(thermal_gen)*"-renamed") +end +``` +Now we can check the names using the [`get_name`](@ref) function again. +```@repl system +get_name.(get_components(ThermalStandard,sys)) +``` +!!! warning + Using the "dot" access to get a parameter value from a component is actively discouraged, use `get_*` functions instead. Julia syntax enables access to this data using the "dot" access, however this is 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](https://nrel-sienna.github.io/PowerSystems.jl/stable/explanation/per_unit/#per_unit) section for more details) + + +## Next Steps & Links +So far we have seen that we can view different data types in our system using the `show_components` function, we can can access those types using the `get_*` function, and we can manipulate them using `set_*`. +Follow the next tutorials to learn how to [work with time series](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/working_with_time_series/). + + + diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index 103d62df3a..b311860c42 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -380,18 +380,14 @@ export get_data export iterate_components export get_time_series_multiple export get_variable_cost -export get_incremental_variable_cost, get_decremental_variable_cost export get_no_load_cost export get_start_up export get_shut_down export get_incremental_offer_curves, set_incremental_offer_curves! export get_decremental_offer_curves, set_decremental_offer_curves! -export get_incremental_initial_input, set_incremental_initial_input! -export get_decremental_initial_input, set_decremental_initial_input! export get_ancillary_service_offers, set_ancillary_service_offers! export get_services_bid export set_variable_cost! -export set_incremental_variable_cost!, set_decremental_variable_cost! export set_service_bid! export iterate_windows export get_window diff --git a/src/base.jl b/src/base.jl index 31d38a9f77..335f34a1ea 100644 --- a/src/base.jl +++ b/src/base.jl @@ -73,7 +73,7 @@ System(; kwargs...) `"DEVICE_BASE"`, or `"NATURAL_UNITS"`) By default, time series data is stored in an HDF5 file in the tmp file system to prevent -large datasets from overwhelming system memory (see [Data Storage](@ref)). +large datasets from overwhelming system memory (see [Data Storage](@ref)). **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. @@ -1080,25 +1080,22 @@ function get_component(::Type{T}, sys::System, name::AbstractString) where {T <: end """ -Return an iterator of components of a given `Type` from a [`System`](@ref). - -`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref). +Returns an iterator of components. T can be concrete or abstract. Call collect on the result if an array is desired. # Examples ```julia -iter = get_components(ThermalStandard, sys) -iter = get_components(Generator, sys) -generators = collect(get_components(Generator, sys)) +iter = PowerSystems.get_components(ThermalStandard, sys) +iter = PowerSystems.get_components(Generator, sys) +iter = PowerSystems.get_components(x -> PowerSystems.get_available(x), Generator, sys) +thermal_gens = get_components(ThermalStandard, sys) do gen + get_available(gen) +end +generators = collect(PowerSystems.get_components(Generator, sys)) + ``` -See also: [`iterate_components`](@ref), [`get_components` with a filter](@ref get_components( - filter_func::Function, - ::Type{T}, - sys::System; - subsystem_name = nothing, -) where {T <: Component}), -[`get_available_components`](@ref), [`get_buses`](@ref) +See also: [`iterate_components`](@ref) """ function get_components( ::Type{T}, @@ -1108,27 +1105,6 @@ function get_components( return IS.get_components(T, sys.data; subsystem_name = subsystem_name) end -""" -Return an iterator of components of a given `Type` from a [`System`](@ref), using an -additional filter - -`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. - -# Examples -```julia -iter_coal = get_components(x -> get_fuel(x) == ThermalFuels.COAL, Generator, sys) -pv_gens = - collect(get_components(x -> get_prime_mover_type(x) == PrimeMovers.PVe, Generator, sys)) -``` - -See also: [`get_components`](@ref get_components( - ::Type{T}, - sys::System; - subsystem_name = nothing, -) where {T <: Component}), [`get_available_components`](@ref), -[`get_buses`](@ref) -""" function get_components( filter_func::Function, ::Type{T}, @@ -1185,11 +1161,7 @@ function get_components_by_name( end """ -Returns iterator of available components in a [`System`](@ref). - -`T` can be a concrete or abstract [`Component`](@ref) type from the [Type Tree](@ref) -and must have the method `get_available` implemented. -Call collect on the result if an array is desired. +Gets components availability. Requires type T to have the method get_available implemented. """ function get_available_components(::Type{T}, sys::System) where {T <: Component} return get_components(get_available, T, sys) @@ -1325,7 +1297,7 @@ function _get_buses(data::IS.SystemData, aggregator::T) where {T <: AggregationT buses = Vector{ACBus}() for bus in IS.get_components(ACBus, data) _aggregator = accessor_func(bus) - if !isnothing(_aggregator) && IS.get_uuid(_aggregator) == IS.get_uuid(aggregator) + if IS.get_uuid(_aggregator) == IS.get_uuid(aggregator) push!(buses, bus) end end diff --git a/src/descriptors/power_system_structs.json b/src/descriptors/power_system_structs.json index bcc7ca38f0..1cbe1ed4d5 100644 --- a/src/descriptors/power_system_structs.json +++ b/src/descriptors/power_system_structs.json @@ -1070,10 +1070,9 @@ }, { "name": "loss", - "comment": "Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments.", - "null_value": "LinearCurve(0.0)", - "data_type": "Union{LinearCurve, PiecewiseIncrementalCurve}", - "default": "LinearCurve(0.0)" + "comment": "Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow)", + "null_value": "(l0=0.0, l1=0.0)", + "data_type": "NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}" }, { "name": "services", @@ -2601,20 +2600,6 @@ }, "validation_action": "warn" }, - { - "name": "dc_current", - "comment": "DC current (A) on the converter", - "null_value": "0.0", - "data_type": "Float64", - "default": "0.0" - }, - { - "name": "max_dc_current", - "comment": "Maximum stable dc current limits (A)", - "null_value": "0.0", - "data_type": "Float64", - "default": "1e8" - }, { "name": "loss_function", "comment": "Linear or quadratic loss function with respect to the converter current", diff --git a/src/models/cost_function_timeseries.jl b/src/models/cost_function_timeseries.jl index 2e83a81cd2..dbd2bf0db3 100644 --- a/src/models/cost_function_timeseries.jl +++ b/src/models/cost_function_timeseries.jl @@ -4,6 +4,7 @@ function _validate_market_bid_cost(cost, context) StackTraces.stacktrace()[2].func, context, MarketBidCost, cost)) end +# VALIDATORS function _validate_reserve_demand_curve(cost, name) !(cost isa CostCurve{PiecewiseIncrementalCurve}) && throw( ArgumentError( @@ -93,6 +94,12 @@ Helper function for cost getters. - `start_time`: as in `get_time_series` - `len`: as in `get_time_series` """ +_process_get_cost(_, _, cost::Nothing, _, _, _, _) = throw( + ArgumentError( + "This cost component is empty, please use the corresponding setter to add cost data.", + ), +) + function _process_get_cost(::Type{T}, _, cost::T, transform_fn, start_time::Union{Nothing, Dates.DateTime}, len::Union{Nothing, Int}, @@ -116,149 +123,18 @@ end # GETTER IMPLEMENTATIONS """ -Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any -of the relevant fields (`incremental_offer_curves`, `initial_input`, `no_load_cost`) are -time series, the user may specify `start_time` and `len` and the function returns a -`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a -single `CostCurve`. -""" -function get_variable_cost( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) - if typeof(get_incremental_offer_curves(cost)) <: CostCurve - return get_incremental_offer_curves(cost) - end - function_data = if (get_incremental_offer_curves(cost) isa TimeSeriesKey) - get_incremental_offer_curves(device, cost; start_time = start_time, len = len) - else - get_incremental_offer_curves(device, cost) - end - initial_input = if (get_incremental_initial_input(cost) isa TimeSeriesKey) - get_incremental_initial_input(device, cost; start_time = start_time, len = len) - else - get_incremental_initial_input(device, cost) - end - input_at_zero = if (get_no_load_cost(cost) isa TimeSeriesKey) - get_no_load_cost(device, cost; start_time = start_time, len = len) - else - get_no_load_cost(device, cost) - end - params::Vector{Any} = [function_data, initial_input, input_at_zero] - first_time_series = findfirst(isa.(params, TimeSeries.TimeArray)) - if !isnothing(first_time_series) - timestamps = TimeSeries.timestamp(params[first_time_series]) - for (i, param) in enumerate(params) - if !(param isa TimeSeries.TimeArray) - params[i] = - TimeSeries.TimeArray(timestamps, fill(param, length(timestamps))) - end - end - !allequal(TimeSeries.timestamp.(params)) && - throw( - ArgumentError( - "Time series mismatch between incremental_offer_curves, incremental_initial_input, and no_load_cost", - ), - ) - #@show collect(zip(collect.(TimeSeries.values.(params))...)) |> length - #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) - return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), - [ - _make_market_bid_curve(fd; initial_input = ii, input_at_zero = iaz) for - (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) - ]) - end - return make_market_bid_curve( - function_data, - initial_input; - input_at_zero = input_at_zero, - ) -end - -""" -Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any -of the relevant fields (`incremental_offer_curves`, `initial_input`, `no_load_cost`) are -time series, the user may specify `start_time` and `len` and the function returns a -`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a -single `CostCurve`. -""" -function get_incremental_variable_cost( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) - return get_variable_cost( - device, - cost; - start_time = start_time, - len = len, - ) -end - -""" -Retrieve the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. If any -of the relevant fields (`decremental_offer_curves`, `initial_input`, `no_load_cost`) are -time series, the user may specify `start_time` and `len` and the function returns a -`TimeArray` of `CostCurve`s; if the field is not a time series, the function returns a -single `CostCurve`. +Retrieve the variable cost bid 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 `CostCurve`s; if the field is not a time series, the function +returns a single `CostCurve`. """ -function get_decremental_variable_cost( +get_variable_cost( device::StaticInjection, cost::MarketBidCost; start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, -) - if typeof(get_decremental_offer_curves(cost)) <: CostCurve - return get_decremental_offer_curves(cost) - end - function_data = if (get_decremental_offer_curves(cost) isa TimeSeriesKey) - get_decremental_offer_curves(device, cost; start_time = start_time, len = len) - else - get_decremental_offer_curves(device, cost) - end - initial_input = if (get_decremental_initial_input(cost) isa TimeSeriesKey) - get_decremental_initial_input(device, cost; start_time = start_time, len = len) - else - get_decremental_initial_input(device, cost) - end - input_at_zero = if (get_no_load_cost(cost) isa TimeSeriesKey) - get_no_load_cost(device, cost; start_time = start_time, len = len) - else - get_no_load_cost(device, cost) - end - params::Vector{Any} = [function_data, initial_input, input_at_zero] - first_time_series = findfirst(isa.(params, TimeSeries.TimeArray)) - if !isnothing(first_time_series) - timestamps = TimeSeries.timestamp(params[first_time_series]) - for (i, param) in enumerate(params) - if !(param isa TimeSeries.TimeArray) - params[i] = - TimeSeries.TimeArray(timestamps, fill(param, length(timestamps))) - end - end - !allequal(TimeSeries.timestamp.(params)) && - throw( - ArgumentError( - "Time series mismatch between incremental_offer_curves, incremental_initial_input, and no_load_cost", - ), - ) - #@show collect(zip(collect.(TimeSeries.values.(params))...)) |> length - #@show first(collect(zip(collect.(TimeSeries.values.(params))...))) - return TimeSeries.TimeArray(TimeSeries.timestamp(function_data), - [ - _make_market_bid_curve(fd; initial_input = ii, input_at_zero = iaz) for - (fd, ii, iaz) in collect(zip(collect.(TimeSeries.values.(params))...)) - ]) - end - return make_market_bid_curve( - function_data, - initial_input; - input_at_zero = input_at_zero, - ) -end +) = _process_get_cost(CostCurve{PiecewiseIncrementalCurve}, device, + get_incremental_offer_curves(cost), make_market_bid_curve, start_time, len) """ Retrieve the variable cost data for a `ReserveDemandCurve`. The user may specify @@ -269,7 +145,7 @@ get_variable_cost( start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, ) = _process_get_cost(CostCurve{PiecewiseIncrementalCurve}, service, get_variable(service), - _make_market_bid_curve, start_time, len) + make_market_bid_curve, start_time, len) """ Return service bid time series data for a `StaticInjection` device with a `MarketBidCost`. @@ -292,7 +168,7 @@ function get_services_bid( len = len, count = 1, ) - converted = read_and_convert_ts(ts, service, start_time, len, _make_market_bid_curve) + converted = read_and_convert_ts(ts, service, start_time, len, make_market_bid_curve) return converted end @@ -312,72 +188,22 @@ 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 `Float64`s; if the field is not a time series, the -function returns a single `Float64` or `Nothing`. -""" -get_incremental_offer_curves( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Union{PiecewiseStepData, CostCurve{PiecewiseIncrementalCurve}}, - 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 `Float64`s; if the field is not a time series, the -function returns a single `Float64` or `Nothing`. -""" -get_decremental_offer_curves( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Union{PiecewiseStepData, CostCurve{PiecewiseIncrementalCurve}}, - device, get_decremental_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`. +returns a single `Float64`. """ get_no_load_cost( device::StaticInjection, cost::MarketBidCost; start_time::Union{Nothing, Dates.DateTime} = nothing, len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Union{Nothing, Float64}, device, +) = _process_get_cost(Float64, device, get_no_load_cost(cost), nothing, start_time, len) """ -Retrieve the `incremental_initial_input` for a `StaticInjection` device with a `MarketBidCost`. -""" -get_incremental_initial_input( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Union{Nothing, Float64}, device, - get_incremental_initial_input(cost), nothing, start_time, len) - -""" -Retrieve the `decremental_initial_input` for a `StaticInjection` device with a `MarketBidCost`. -""" -get_decremental_initial_input( - device::StaticInjection, - cost::MarketBidCost; - start_time::Union{Nothing, Dates.DateTime} = nothing, - len::Union{Nothing, Int} = nothing, -) = _process_get_cost(Union{Nothing, Float64}, device, - get_decremental_initial_input(cost), nothing, start_time, len) - -""" -Retrieve the startup cost data for a `StaticInjection` device with a `MarketBidCost`. If +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`. @@ -420,36 +246,22 @@ end # SETTER IMPLEMENTATIONS """ -Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. +Set the variable cost bid for a `StaticInjection` device with a `MarketBidCost`. # 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. + CostCurve{PiecewiseIncrementalCurve}},`: the data. If a time series, must be of eltype + `PiecewiseStepData`. """ function set_variable_cost!( sys::System, component::StaticInjection, data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, - power_units::UnitSystem, ) market_bid_cost = get_operation_cost(component) _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - if (typeof(data) <: CostCurve{PiecewiseIncrementalCurve}) && - (data.power_units != power_units) - throw( - ArgumentError( - "Units specified in CostCurve data differs from the units specified in the set cost.", - ), - ) - end - if (typeof(data) <: IS.TimeSeriesData) && (power_units != UnitSystem.NATURAL_UNITS) - throw(ArgumentError("Time Series data for MarketBidCost must be in NATURAL_UNITS.")) - end to_set = _process_set_cost( CostCurve{PiecewiseIncrementalCurve}, PiecewiseStepData, @@ -457,85 +269,7 @@ function set_variable_cost!( component, data, ) - set_incremental_offer_curves!(market_bid_cost, to_set) - return -end - -function set_variable_cost!( - sys::System, - component::StaticInjection, - data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, -) - @warn "Variable Cost UnitSystem not specificied for $(get_name(component)). set_variable_cost! assumes data is in UnitSystem.NATURAL_UNITS" - set_variable_cost!(sys, component, data, UnitSystem.NATURAL_UNITS) - return -end - -""" -Set the incremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. - -# 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. -""" -function set_incremental_variable_cost!( - sys::System, - component::StaticInjection, - data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, - power_units::UnitSystem, -) - set_variable_cost!(sys, component, data, power_units) - return -end - -""" -Set the decremental variable cost bid for a `StaticInjection` device with a `MarketBidCost`. - -# 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. -""" -function set_decremental_variable_cost!( - sys::System, - component::StaticInjection, - data::Union{Nothing, IS.TimeSeriesData, CostCurve{PiecewiseIncrementalCurve}}, - power_units::UnitSystem, -) - market_bid_cost = get_operation_cost(component) - _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - - if (typeof(data) <: CostCurve{PiecewiseIncrementalCurve}) && - (data.power_units != power_units) - throw( - ArgumentError( - "Units specified in CostCurve data differs from the units specified in the set cost.", - ), - ) - end - if (typeof(data) <: IS.TimeSeriesData) && (power_units != UnitSystem.NATURAL_UNITS) - throw(ArgumentError("Time Series data for MarketBidCost must be in NATURAL_UNITS.")) - end - to_set = _process_set_cost( - CostCurve{PiecewiseIncrementalCurve}, - PiecewiseStepData, - sys, - component, - data, - ) - - set_decremental_offer_curves!(market_bid_cost, to_set) - return end """ @@ -589,7 +323,7 @@ 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` device with a `MarketBidCost` to either a single number or a time series. # Arguments - `sys::System`: PowerSystem System @@ -603,48 +337,10 @@ function set_no_load_cost!( ) market_bid_cost = get_operation_cost(component) _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) + to_set = _process_set_cost(Float64, Float64, sys, component, data) set_no_load_cost!(market_bid_cost, to_set) end -""" -Set the `incremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series. - -# 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`. -""" -function set_incremental_initial_input!( - sys::System, - component::StaticInjection, - data::Union{Float64, IS.TimeSeriesData}, -) - market_bid_cost = get_operation_cost(component) - _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) - set_incremental_initial_input!(market_bid_cost, to_set) -end - -""" -Set the `decremental_initial_input` for a `StaticInjection` device with a `MarketBidCost` to either a scalar or a time series. - -# 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`. -""" -function set_decremental_initial_input!( - sys::System, - component::StaticInjection, - data::Union{Float64, IS.TimeSeriesData}, -) - market_bid_cost = get_operation_cost(component) - _validate_market_bid_cost(market_bid_cost, "get_operation_cost(component)") - to_set = _process_set_cost(Union{Float64, Nothing}, Float64, sys, component, data) - set_decremental_initial_input!(market_bid_cost, to_set) -end - """ Set the startup cost for a `StaticInjection` device with a `MarketBidCost` to either a single `StartUpStages` or a time series. @@ -678,7 +374,6 @@ function set_service_bid!( component::StaticInjection, service::Service, time_series_data::IS.TimeSeriesData, - power_units::UnitSystem, ) data_type = IS.eltype_data(time_series_data) !(data_type <: PiecewiseStepData) && @@ -692,13 +387,6 @@ function set_service_bid!( "Name provided in the TimeSeries Data $(get_name(time_series_data)), doesn't match the Service $(get_name(service)).", ) end - if power_units != UnitSystem.NATURAL_UNITS - throw( - ArgumentError( - "Power Unit specified for service market bids must be NATURAL_UNITS", - ), - ) - end verify_device_eligibility(sys, component, service) add_time_series!(sys, component, time_series_data) ancillary_service_offers = get_ancillary_service_offers(get_operation_cost(component)) diff --git a/src/models/cost_functions/MarketBidCost.jl b/src/models/cost_functions/MarketBidCost.jl index b184ee0fe5..a26751f9c2 100644 --- a/src/models/cost_functions/MarketBidCost.jl +++ b/src/models/cost_functions/MarketBidCost.jl @@ -11,7 +11,7 @@ Compatible with most US Market bidding mechanisms that support demand and genera """ @kwdef mutable struct MarketBidCost <: OperationalCost "No load cost" - no_load_cost::Union{TimeSeriesKey, Nothing, Float64} = nothing + no_load_cost::Union{TimeSeriesKey, Float64} "Start-up cost at different stages of the thermal cycle as the unit cools after a shutdown (e.g., *hot*, *warm*, or *cold* starts). Warm is also referred to as intermediate in some markets. Can also accept a single value if there is only one @@ -19,57 +19,30 @@ Compatible with most US Market bidding mechanisms that support demand and genera start_up::Union{TimeSeriesKey, StartUpStages} "Shut-down cost" shut_down::Float64 - "Sell Offer Curves data, which can be a time series of `PiecewiseStepData` or a - [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" + "Sell Offer Curves data, which can be a time series or a [`CostCurve`](@ref) using + [`PiecewiseIncrementalCurve`](@ref)" incremental_offer_curves::Union{ Nothing, - TimeSeriesKey, # piecewise step data + TimeSeriesKey, CostCurve{PiecewiseIncrementalCurve}, } = nothing - "Buy Offer Curves data, which can be a time series of `PiecewiseStepData` or a - [`CostCurve`](@ref) of [`PiecewiseIncrementalCurve`](@ref)" + "Buy Offer Curves data, can be a time series or a [`CostCurve`](@ref) using + [`PiecewiseIncrementalCurve`](@ref)" decremental_offer_curves::Union{ Nothing, TimeSeriesKey, CostCurve{PiecewiseIncrementalCurve}, } = nothing - "If using a time series for incremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" - incremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing - "If using a time series for decremental_offer_curves, this is a time series of `Float64` representing the `initial_input`" - decremental_initial_input::Union{Nothing, TimeSeriesKey} = nothing "Bids for the ancillary services" ancillary_service_offers::Vector{Service} = Vector{Service}() end -"Auxiliary Constructor for Deserialization with Integer at no load cost" MarketBidCost( no_load_cost::Integer, start_up::Union{TimeSeriesKey, StartUpStages}, shut_down, incremental_offer_curves, decremental_offer_curves, - incremental_initial_input, - decremental_initial_input, - ancillary_service_offers, -) = - MarketBidCost( - Float64(no_load_cost), - start_up, - shut_down, - incremental_offer_curves, - decremental_offer_curves, - incremental_initial_input, - decremental_initial_input, - ancillary_service_offers, - ) - -"""Auxiliary Constructor for TestData""" -MarketBidCost( - no_load_cost::Float64, - start_up::Union{TimeSeriesKey, StartUpStages}, - shut_down, - incremental_offer_curves, - decremental_offer_curves, ancillary_service_offers, ) = MarketBidCost( @@ -78,15 +51,13 @@ MarketBidCost( shut_down, incremental_offer_curves, decremental_offer_curves, - nothing, - nothing, ancillary_service_offers, ) # Constructor for demo purposes; non-functional. function MarketBidCost(::Nothing) MarketBidCost(; - no_load_cost = nothing, + no_load_cost = 0.0, start_up = (hot = START_COST, warm = START_COST, cold = START_COST), shut_down = 0.0, ) @@ -99,25 +70,21 @@ Accepts a single `start_up` value to use as the `hot` value, with `warm` and `co function MarketBidCost( no_load_cost, start_up::Real, - shut_down; + shut_down, incremental_offer_curves = nothing, decremental_offer_curves = nothing, - incremental_initial_input = nothing, - decremental_initial_input = nothing, ancillary_service_offers = Vector{Service}(), ) # Intended for use with generators that are not multi-start (e.g. ThermalStandard). # Operators use `hot` when they don’t have multiple stages. start_up_multi = (hot = Float64(start_up), warm = 0.0, cold = 0.0) - return MarketBidCost(; - no_load_cost = no_load_cost, - start_up = start_up_multi, - shut_down = shut_down, - incremental_offer_curves = incremental_offer_curves, - decremental_offer_curves = decremental_offer_curves, - incremental_initial_input = incremental_initial_input, - decremental_initial_input = decremental_initial_input, - ancillary_service_offers = ancillary_service_offers, + return MarketBidCost( + no_load_cost, + start_up_multi, + shut_down, + incremental_offer_curves, + decremental_offer_curves, + ancillary_service_offers, ) end @@ -129,12 +96,8 @@ get_start_up(value::MarketBidCost) = value.start_up get_shut_down(value::MarketBidCost) = value.shut_down """Get [`MarketBidCost`](@ref) `incremental_offer_curves`.""" get_incremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves -"""Get [`MarketBidCost`](@ref) `decremental_offer_curves`.""" -get_decremental_offer_curves(value::MarketBidCost) = value.decremental_offer_curves -"""Get [`MarketBidCost`](@ref) `incremental_initial_input`.""" -get_incremental_initial_input(value::MarketBidCost) = value.incremental_initial_input -"""Get [`MarketBidCost`](@ref) `decremental_initial_input`.""" -get_decremental_initial_input(value::MarketBidCost) = value.decremental_initial_input +"""Get [`MarketBidCost`](@ref) `incremental_offer_curves`.""" +get_decremental_offer_curves(value::MarketBidCost) = value.incremental_offer_curves """Get [`MarketBidCost`](@ref) `ancillary_service_offers`.""" get_ancillary_service_offers(value::MarketBidCost) = value.ancillary_service_offers @@ -147,86 +110,49 @@ set_shut_down!(value::MarketBidCost, val) = value.shut_down = val """Set [`MarketBidCost`](@ref) `incremental_offer_curves`.""" set_incremental_offer_curves!(value::MarketBidCost, val) = value.incremental_offer_curves = val -"""Set [`MarketBidCost`](@ref) `incremental_initial_input`.""" -set_incremental_initial_input!(value::MarketBidCost, val) = - value.incremental_initial_input = val """Set [`MarketBidCost`](@ref) `incremental_offer_curves`.""" set_decremental_offer_curves!(value::MarketBidCost, val) = value.decremental_offer_curves = val -"""Set [`MarketBidCost`](@ref) `decremental_initial_input`.""" -set_decremental_initial_input!(value::MarketBidCost, val) = - value.decremental_initial_input = val """Set [`MarketBidCost`](@ref) `ancillary_service_offers`.""" set_ancillary_service_offers!(value::MarketBidCost, val) = value.ancillary_service_offers = val -"""Auxiliary Method for setting up start up that are not multi-start""" -function set_start_up!(value::MarketBidCost, val::Real) - start_up_multi = (hot = Float64(val), warm = 0.0, cold = 0.0) - set_start_up!(value, start_up_multi) -end - # Each market bid curve (the elements that make up the incremental and decremental offer # curves in MarketBidCost) is a CostCurve{PiecewiseIncrementalCurve} with NaN initial input # and first x-coordinate function is_market_bid_curve(curve::ProductionVariableCostCurve) - return (curve isa CostCurve{PiecewiseIncrementalCurve}) + (curve isa CostCurve{PiecewiseIncrementalCurve}) || return false + value_curve = get_value_curve(curve) + return isnan(get_initial_input(value_curve)) && + isnan(first(get_x_coords(get_function_data(value_curve)))) end """ Make a CostCurve{PiecewiseIncrementalCurve} suitable for inclusion in a MarketBidCost from a -vector of power values, a vector of marginal costs, a float of initial input, and an optional units system and input at zero. - -# Examples -```julia -mbc = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0) -mbc2 = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0; input_at_zero = 10.0) -mbc3 = make_market_bid_curve([0.0, 100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0], 10.0; power_inputs = UnitSystem.NATURAL_UNITS) -``` +vector of power values, a vector of marginal costs, and an optional units system. The +minimum power, and cost at minimum power, are not represented. """ function make_market_bid_curve(powers::Vector{Float64}, - marginal_costs::Vector{Float64}, - initial_input::Float64; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS, - input_at_zero::Union{Nothing, Float64} = nothing) - if length(powers) == length(marginal_costs) + 1 - fd = PiecewiseStepData(powers, marginal_costs) - return make_market_bid_curve( - fd, - initial_input; - power_units = power_units, - input_at_zero, - ) - else - throw( - ArgumentError( - "Must specify exactly one more number of powers ($(length(powers))) than marginal_costs ($(length(marginal_costs)))", - ), - ) - end + marginal_costs::Vector{Float64}; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) + (length(powers) != length(marginal_costs)) && + throw(ArgumentError("Must specify an equal number of powers and marginal_costs")) + fd = PiecewiseStepData(vcat(NaN, powers), marginal_costs) + return make_market_bid_curve(fd; power_units = power_units) end """ Make a CostCurve{PiecewiseIncrementalCurve} suitable for inclusion in a MarketBidCost from the FunctionData that might be used to store such a cost curve in a time series. """ -function make_market_bid_curve(data::PiecewiseStepData, - initial_input::Float64; - power_units::UnitSystem = UnitSystem.NATURAL_UNITS, - input_at_zero::Union{Nothing, Float64} = nothing) - cc = CostCurve(IncrementalCurve(data, initial_input, input_at_zero), power_units) - @assert is_market_bid_curve(cc) - return cc -end - -""" -Auxiliary make market bid curve for timeseries with nothing inputs. -""" -function _make_market_bid_curve(data::PiecewiseStepData; - initial_input::Union{Nothing, Float64} = nothing, - power_units::UnitSystem = UnitSystem.NATURAL_UNITS, - input_at_zero::Union{Nothing, Float64} = nothing) - cc = CostCurve(IncrementalCurve(data, initial_input, input_at_zero), power_units) +function make_market_bid_curve(data::PiecewiseStepData; + power_units::UnitSystem = UnitSystem.NATURAL_UNITS) + !isnan(first(get_x_coords(data))) && throw( + ArgumentError( + "The first x-coordinate in the PiecewiseStepData representation must be NaN", + ), + ) + cc = CostCurve(IncrementalCurve(data, NaN), power_units) @assert is_market_bid_curve(cc) return cc end diff --git a/src/models/generated/InterconnectingConverter.jl b/src/models/generated/InterconnectingConverter.jl index f8e00bd7ca..35fa74be48 100644 --- a/src/models/generated/InterconnectingConverter.jl +++ b/src/models/generated/InterconnectingConverter.jl @@ -14,8 +14,6 @@ This file is auto-generated. Do not edit. rating::Float64 active_power_limits::MinMax base_power::Float64 - dc_current::Float64 - max_dc_current::Float64 loss_function::Union{LinearCurve, QuadraticCurve} services::Vector{Service} dynamic_injector::Union{Nothing, DynamicInjection} @@ -34,8 +32,6 @@ Interconnecting Power Converter (IPC) for transforming power from an ACBus to a - `rating::Float64`: Maximum output power rating of the converter (MVA), validation range: `(0, nothing)` - `active_power_limits::MinMax`: Minimum and maximum stable active power levels (MW) - `base_power::Float64`: Base power of the converter in MVA, validation range: `(0, nothing)` -- `dc_current::Float64`: (default: `0.0`) DC current (A) on the converter -- `max_dc_current::Float64`: (default: `1e8`) Maximum stable dc current limits (A) - `loss_function::Union{LinearCurve, QuadraticCurve}`: (default: `LinearCurve(0.0)`) Linear or quadratic loss function with respect to the converter current - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to - `dynamic_injector::Union{Nothing, DynamicInjection}`: (default: `nothing`) corresponding dynamic injection device @@ -59,10 +55,6 @@ mutable struct InterconnectingConverter <: StaticInjection active_power_limits::MinMax "Base power of the converter in MVA" base_power::Float64 - "DC current (A) on the converter" - dc_current::Float64 - "Maximum stable dc current limits (A)" - max_dc_current::Float64 "Linear or quadratic loss function with respect to the converter current" loss_function::Union{LinearCurve, QuadraticCurve} "Services that this device contributes to" @@ -75,12 +67,12 @@ mutable struct InterconnectingConverter <: StaticInjection internal::InfrastructureSystemsInternal end -function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=1e8, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) +function InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function, services, dynamic_injector, ext, InfrastructureSystemsInternal(), ) end -function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current=0.0, max_dc_current=1e8, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) - InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, dc_current, max_dc_current, loss_function, services, dynamic_injector, ext, internal, ) +function InterconnectingConverter(; name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) + InterconnectingConverter(name, available, bus, dc_bus, active_power, rating, active_power_limits, base_power, loss_function, services, dynamic_injector, ext, internal, ) end # Constructor for demo purposes; non-functional. @@ -94,8 +86,6 @@ function InterconnectingConverter(::Nothing) rating=0.0, active_power_limits=(min=0.0, max=0.0), base_power=0.0, - dc_current=0.0, - max_dc_current=0.0, loss_function=LinearCurve(0.0), services=Device[], dynamic_injector=nothing, @@ -119,10 +109,6 @@ get_rating(value::InterconnectingConverter) = get_value(value, value.rating) get_active_power_limits(value::InterconnectingConverter) = get_value(value, value.active_power_limits) """Get [`InterconnectingConverter`](@ref) `base_power`.""" get_base_power(value::InterconnectingConverter) = value.base_power -"""Get [`InterconnectingConverter`](@ref) `dc_current`.""" -get_dc_current(value::InterconnectingConverter) = value.dc_current -"""Get [`InterconnectingConverter`](@ref) `max_dc_current`.""" -get_max_dc_current(value::InterconnectingConverter) = value.max_dc_current """Get [`InterconnectingConverter`](@ref) `loss_function`.""" get_loss_function(value::InterconnectingConverter) = value.loss_function """Get [`InterconnectingConverter`](@ref) `services`.""" @@ -148,10 +134,6 @@ set_rating!(value::InterconnectingConverter, val) = value.rating = set_value(val set_active_power_limits!(value::InterconnectingConverter, val) = value.active_power_limits = set_value(value, val) """Set [`InterconnectingConverter`](@ref) `base_power`.""" set_base_power!(value::InterconnectingConverter, val) = value.base_power = val -"""Set [`InterconnectingConverter`](@ref) `dc_current`.""" -set_dc_current!(value::InterconnectingConverter, val) = value.dc_current = val -"""Set [`InterconnectingConverter`](@ref) `max_dc_current`.""" -set_max_dc_current!(value::InterconnectingConverter, val) = value.max_dc_current = val """Set [`InterconnectingConverter`](@ref) `loss_function`.""" set_loss_function!(value::InterconnectingConverter, val) = value.loss_function = val """Set [`InterconnectingConverter`](@ref) `services`.""" diff --git a/src/models/generated/TwoTerminalHVDCLine.jl b/src/models/generated/TwoTerminalHVDCLine.jl index dc5c1f43c0..b7ad389343 100644 --- a/src/models/generated/TwoTerminalHVDCLine.jl +++ b/src/models/generated/TwoTerminalHVDCLine.jl @@ -14,7 +14,7 @@ This file is auto-generated. Do not edit. active_power_limits_to::MinMax reactive_power_limits_from::MinMax reactive_power_limits_to::MinMax - loss::Union{LinearCurve, PiecewiseIncrementalCurve} + loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}} services::Vector{Service} ext::Dict{String, Any} internal::InfrastructureSystemsInternal @@ -33,7 +33,7 @@ This model is appropriate for operational simulations with a linearized DC power - `active_power_limits_to::MinMax`: Minimum and maximum active power flows to the TO node (MW) - `reactive_power_limits_from::MinMax`: Minimum and maximum reactive power limits to the FROM node (MVAR) - `reactive_power_limits_to::MinMax`: Minimum and maximum reactive power limits to the TO node (MVAR) -- `loss::Union{LinearCurve, PiecewiseIncrementalCurve}`: (default: `LinearCurve(0.0)`) Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments. +- `loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}`: Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow) - `services::Vector{Service}`: (default: `Device[]`) Services that this device contributes to - `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, such as latitude and longitude. - `internal::InfrastructureSystemsInternal`: (**Do not modify.**) PowerSystems.jl internal reference @@ -55,8 +55,8 @@ mutable struct TwoTerminalHVDCLine <: ACBranch reactive_power_limits_from::MinMax "Minimum and maximum reactive power limits to the TO node (MVAR)" reactive_power_limits_to::MinMax - "Loss model coefficients. It accepts a linear model with a constant loss (MW) and a proportional loss rate (MW of loss per MW of flow). It also accepts a Piecewise loss, with N segments to specify different proportional losses for different segments." - loss::Union{LinearCurve, PiecewiseIncrementalCurve} + "Linear loss model coefficients, where `l0` = constant loss (MW) and `l1` = linearly proportional loss rate (MW of loss per MW of flow)" + loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}} "Services that this device contributes to" services::Vector{Service} "An [*ext*ra dictionary](@ref additional_fields) for users to add metadata that are not used in simulation, such as latitude and longitude." @@ -65,11 +65,11 @@ mutable struct TwoTerminalHVDCLine <: ACBranch internal::InfrastructureSystemsInternal end -function TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss=LinearCurve(0.0), services=Device[], ext=Dict{String, Any}(), ) +function TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services=Device[], ext=Dict{String, Any}(), ) TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services, ext, InfrastructureSystemsInternal(), ) end -function TwoTerminalHVDCLine(; name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss=LinearCurve(0.0), services=Device[], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) +function TwoTerminalHVDCLine(; name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services=Device[], ext=Dict{String, Any}(), internal=InfrastructureSystemsInternal(), ) TwoTerminalHVDCLine(name, available, active_power_flow, arc, active_power_limits_from, active_power_limits_to, reactive_power_limits_from, reactive_power_limits_to, loss, services, ext, internal, ) end @@ -84,7 +84,7 @@ function TwoTerminalHVDCLine(::Nothing) active_power_limits_to=(min=0.0, max=0.0), reactive_power_limits_from=(min=0.0, max=0.0), reactive_power_limits_to=(min=0.0, max=0.0), - loss=LinearCurve(0.0), + loss=(l0=0.0, l1=0.0), services=Device[], ext=Dict{String, Any}(), ) diff --git a/src/models/generated/includes.jl b/src/models/generated/includes.jl index 298ae42649..a0400f4e7b 100644 --- a/src/models/generated/includes.jl +++ b/src/models/generated/includes.jl @@ -506,7 +506,6 @@ export get_d_t export get_db export get_dbd_pnts export get_dc_bus -export get_dc_current export get_dc_dc_inductor export get_dc_link_capacitance export get_delta_t @@ -592,7 +591,6 @@ export get_max_constant_active_power export get_max_constant_reactive_power export get_max_current_active_power export get_max_current_reactive_power -export get_max_dc_current export get_max_impedance_active_power export get_max_impedance_reactive_power export get_max_output_fraction @@ -1076,7 +1074,6 @@ export set_d_t! export set_db! export set_dbd_pnts! export set_dc_bus! -export set_dc_current! export set_dc_dc_inductor! export set_dc_link_capacitance! export set_delta_t! @@ -1162,7 +1159,6 @@ export set_max_constant_active_power! export set_max_constant_reactive_power! export set_max_current_active_power! export set_max_current_reactive_power! -export set_max_dc_current! export set_max_impedance_active_power! export set_max_impedance_reactive_power! export set_max_output_fraction! diff --git a/src/models/supplemental_constructors.jl b/src/models/supplemental_constructors.jl index 85e658a307..c53a206f8a 100644 --- a/src/models/supplemental_constructors.jl +++ b/src/models/supplemental_constructors.jl @@ -192,67 +192,3 @@ function EnergyReservoirStorage( internal = internal, ) end - -""" -Deprecated method for TwoTerminalHVDCLine -""" -function TwoTerminalHVDCLine( - name, - available, - active_power_flow, - arc, - active_power_limits_from, - active_power_limits_to, - reactive_power_limits_from, - reactive_power_limits_to, - loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}, - services, - ext, - internal, -) - new_loss = LinearCurve(loss.l0, loss.l1) - TwoTerminalHVDCLine( - name, - available, - active_power_flow, - arc, - active_power_limits_from, - active_power_limits_to, - reactive_power_limits_from, - reactive_power_limits_to, - new_loss, - services, - ext, - internal, - ) -end - -function TwoTerminalHVDCLine( - name, - available, - active_power_flow, - arc, - active_power_limits_from, - active_power_limits_to, - reactive_power_limits_from, - reactive_power_limits_to, - loss::NamedTuple{(:l0, :l1), Tuple{Float64, Float64}}, - services = Device[], - ext = Dict{String, Any}(), -) - new_loss = LinearCurve(loss.l0, loss.l1) - TwoTerminalHVDCLine( - name, - available, - active_power_flow, - arc, - active_power_limits_from, - active_power_limits_to, - reactive_power_limits_from, - reactive_power_limits_to, - new_loss, - services, - ext, - InfrastructureSystemsInternal(), - ) -end diff --git a/src/parsers/power_models_data.jl b/src/parsers/power_models_data.jl index ae3812993e..0973f3338e 100644 --- a/src/parsers/power_models_data.jl +++ b/src/parsers/power_models_data.jl @@ -789,7 +789,7 @@ function make_dcline(name::String, d::Dict, bus_f::ACBus, bus_t::ACBus) active_power_limits_to = (min = d["pmint"], max = d["pmaxt"]), reactive_power_limits_from = (min = d["qminf"], max = d["qmaxf"]), reactive_power_limits_to = (min = d["qmint"], max = d["qmaxt"]), - loss = LinearCurve(d["loss1"], d["loss0"]), + loss = (l0 = d["loss0"], l1 = d["loss1"]), ) end diff --git a/src/parsers/power_system_table_data.jl b/src/parsers/power_system_table_data.jl index 19a73cd030..9c90423456 100644 --- a/src/parsers/power_system_table_data.jl +++ b/src/parsers/power_system_table_data.jl @@ -275,7 +275,7 @@ function System( ) for (val, parser) in parsers - if !isempty(val) + if !isnothing(val) parser(sys, data) end end @@ -467,7 +467,7 @@ function dc_branch_csv_parser!(sys::System, data::PowerSystemTableData) :max_reactive_power_limit_to, ) - loss = LinearCurve(dc_branch.loss) #TODO: Can we infer this from the other data?, + loss = (l0 = 0.0, l1 = dc_branch.loss) #TODO: Can we infer this from the other data?, value = TwoTerminalHVDCLine(; name = dc_branch.name, diff --git a/test/data_14bus_pu.jl b/test/data_14bus_pu.jl index 5e05a65e49..6c5abd2306 100644 --- a/test/data_14bus_pu.jl +++ b/test/data_14bus_pu.jl @@ -189,7 +189,7 @@ branches14_dc(nodes14) = [ (min = -600.0, max = 600), (min = -600.0, max = 600), (min = -600.0, max = 600), - LinearCurve(0.001, 0.01), + (l0 = 0.01, l1 = 0.001), ), TwoTerminalHVDCLine( "DCLine4", @@ -200,7 +200,7 @@ branches14_dc(nodes14) = [ (min = -600.0, max = 600), (min = -600.0, max = 600), (min = -600.0, max = 600), - LinearCurve(0.001, 0.01), + (l0 = 0.01, l1 = 0.001), ), #Line("Line3", true, 0.0, 0.0, Arc(from=nodes14[2],to=nodes14[3]), 0.04699, 0.19797, (from=0.0219, to=0.0219), 5.522, 1.04), #Line("Line4", true, 0.0, 0.0, Arc(from=nodes14[2],to=nodes14[4]), 0.05811, 0.17632, (from=0.017, to=0.017), 6.052, 1.04), diff --git a/test/data_5bus_pu.jl b/test/data_5bus_pu.jl index 77999cc72f..5e071e6669 100644 --- a/test/data_5bus_pu.jl +++ b/test/data_5bus_pu.jl @@ -41,7 +41,7 @@ branches5_dc(nodes5) = [ (min = -3000, max = 3000), (min = -3000.0, max = 3000.0), (min = -3000.0, max = 3000.0), - LinearCurve(0.01), + (l0 = 0.0, l1 = 0.01), ), Line( "3", diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 2698571c0d..08161df397 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -12,81 +12,19 @@ "FuelCurve with power_units UnitSystem.NATURAL_UNITS = 2, fuel_cost 4.0, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" end -@testset "Test MarketBidCost direct struct creation" begin - sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") - generator = get_component(ThermalStandard, sys, "322_CT_6") - #Update generator cost to MarketBidCost using Natural Units - powers = [22.0, 33.0, 44.0, 55.0] # MW - marginal_costs = [25.0, 26.0, 28.0] # $/MWh - initial_input = 50.0 # $/h - mbc = MarketBidCost(; - start_up = (hot = 0.0, warm = 0.0, cold = 0.0), - shut_down = 0.0, - incremental_offer_curves = CostCurve( - PiecewiseIncrementalCurve( - initial_input, - powers, - marginal_costs, - ), - ), - ) - set_operation_cost!(generator, mbc) - @test get_operation_cost(generator) isa MarketBidCost -end - -@testset "Test Make market bid curve interface" begin - mbc = make_market_bid_curve( - [0.0, 100.0, 105.0, 120.0, 130.0], - [25.0, 26.0, 28.0, 30.0], - 10.0, - ) +@testset "Test market bid cost interface" begin + mbc = make_market_bid_curve([100.0, 105.0, 120.0, 130.0], [25.0, 26.0, 28.0, 30.0]) @test is_market_bid_curve(mbc) - @test is_market_bid_curve( - make_market_bid_curve(get_function_data(mbc), get_initial_input(mbc)), - ) + @test is_market_bid_curve(make_market_bid_curve(get_function_data(mbc))) @test_throws ArgumentError make_market_bid_curve( - [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0, 40.0], 10.0) - - mbc2 = make_market_bid_curve([1.0, 2.0, 3.0], [4.0, 6.0], 10.0; input_at_zero = 2.0) - @test is_market_bid_curve(mbc2) - @test is_market_bid_curve( - make_market_bid_curve(get_function_data(mbc2), get_initial_input(mbc2)), - ) - - sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") - generator = get_component(ThermalStandard, sys, "322_CT_6") - market_bid = MarketBidCost(nothing) - mbc3 = make_market_bid_curve([22.0, 33.0, 44.0, 55.0], [25.0, 26.0, 28.0], 50.0) - set_incremental_offer_curves!(market_bid, mbc3) - set_start_up!(market_bid, 0.0) - set_operation_cost!(generator, market_bid) - @test get_operation_cost(generator) isa MarketBidCost + [100.0, 105.0, 120.0, 130.0], [26.0, 28.0, 30.0]) end test_costs = Dict( CostCurve{QuadraticCurve} => repeat([CostCurve(QuadraticCurve(999.0, 2.0, 1.0))], 24), - PiecewiseStepData => - repeat( - [ - PSY._make_market_bid_curve( - PiecewiseStepData([0.0, 2.0, 3.0], [4.0, 6.0]), - ), - ], - 24, - ), - PiecewiseIncrementalCurve => - repeat( - [ - make_market_bid_curve( - [1.0, 2.0, 3.0], - [4.0, 6.0], - 18.0; - input_at_zero = 20.0, - ), - ], - 24, - ), + CostCurve{PiecewiseIncrementalCurve} => + repeat([make_market_bid_curve([2.0, 3.0], [4.0, 6.0])], 24), Float64 => collect(11.0:34.0), PSY.StartUpStages => @@ -107,16 +45,15 @@ test_costs = Dict( generator = get_component(ThermalStandard, sys, "322_CT_6") market_bid = MarketBidCost(nothing) set_operation_cost!(generator, market_bid) - forecast_fd = IS.Deterministic( + forecast = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_quadratic)), resolution, ) - power_units = UnitSystem.NATURAL_UNITS - @test_throws TypeError set_variable_cost!(sys, generator, forecast_fd, power_units) + @test_throws TypeError set_variable_cost!(sys, generator, forecast) for s in generator.services - forecast_fd = IS.Deterministic(get_name(s), service_data, resolution) - @test_throws TypeError set_service_bid!(sys, generator, s, forecast_fd, power_units) + forecast = IS.Deterministic(get_name(s), service_data, resolution) + @test_throws TypeError set_service_bid!(sys, generator, s, forecast) end end @@ -125,44 +62,27 @@ end resolution = Dates.Hour(1) name = "test" horizon = 24 - power_units = UnitSystem.NATURAL_UNITS - data_pwl = SortedDict(initial_time => test_costs[PiecewiseStepData]) + data_pwl = SortedDict(initial_time => test_costs[CostCurve{PiecewiseIncrementalCurve}]) service_data = data_pwl sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") generator = get_component(ThermalStandard, sys, "322_CT_6") market_bid = MarketBidCost(nothing) set_operation_cost!(generator, market_bid) - forecast_fd = Deterministic( + forecast = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - @test_throws ArgumentError set_variable_cost!( - sys, - generator, - forecast_fd, - UnitSystem.SYSTEM_BASE, - ) - set_variable_cost!(sys, generator, forecast_fd, power_units) - + set_variable_cost!(sys, generator, forecast) for s in generator.services - forecast_fd = Deterministic( + forecast = IS.Deterministic( get_name(s), Dict(k => get_function_data.(v) for (k, v) in pairs(service_data)), resolution, ) - @test_throws ArgumentError set_service_bid!( - sys, - generator, - s, - forecast_fd, - UnitSystem.SYSTEM_BASE, - ) - set_service_bid!(sys, generator, s, forecast_fd, power_units) + set_service_bid!(sys, generator, s, forecast) end - iocs = get_incremental_offer_curves(generator, market_bid) - isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time) @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) @@ -175,91 +95,9 @@ end end end -@testset "Test MarketBidCost with PiecewiseLinearData Cost Timeseries, initial_input, and no_load_cost" begin - initial_time = Dates.DateTime("2020-01-01") - resolution = Dates.Hour(1) - name = "test" - horizon = 24 - power_units = UnitSystem.NATURAL_UNITS - data_pwl = SortedDict(initial_time => test_costs[PiecewiseIncrementalCurve]) - service_data = data_pwl - sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") - generator = get_component(ThermalStandard, sys, "322_CT_6") - market_bid = MarketBidCost(nothing) - set_operation_cost!(generator, market_bid) - forecast_fd = IS.Deterministic( - "variable_cost_function_data", - Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), - resolution, - ) - set_variable_cost!(sys, generator, forecast_fd, power_units) - - forecast_ii = IS.Deterministic( - "variable_cost_initial_input", - Dict(k => get_initial_input.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), - resolution, - ) - PSY.set_incremental_initial_input!(sys, generator, forecast_ii) - - forecast_iaz = IS.Deterministic( - "variable_cost_input_at_zero", - Dict(k => get_input_at_zero.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), - resolution, - ) - set_no_load_cost!(sys, generator, forecast_iaz) - - iocs = get_incremental_offer_curves(generator, market_bid) - isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) - cost_forecast = get_variable_cost(generator, market_bid; start_time = initial_time) - @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) -end - -@testset "Test MarketBidCost with Decremental PiecewiseLinearData Cost Timeseries, initial_input, and no_load_cost" begin - initial_time = Dates.DateTime("2020-01-01") - resolution = Dates.Hour(1) - name = "test" - horizon = 24 - power_units = UnitSystem.NATURAL_UNITS - data_pwl = SortedDict(initial_time => test_costs[PiecewiseIncrementalCurve]) - service_data = data_pwl - sys = PSB.build_system(PSITestSystems, "test_RTS_GMLC_sys") - generator = get_component(ThermalStandard, sys, "322_CT_6") - market_bid = MarketBidCost(nothing) - set_operation_cost!(generator, market_bid) - forecast_fd = IS.Deterministic( - "decremental_variable_cost_function_data", - Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), - resolution, - ) - set_decremental_variable_cost!(sys, generator, forecast_fd, power_units) - - forecast_ii = IS.Deterministic( - "decremental_variable_cost_initial_input", - Dict(k => get_initial_input.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), - resolution, - ) - PSY.set_decremental_initial_input!(sys, generator, forecast_ii) - - forecast_iaz = IS.Deterministic( - "variable_cost_input_at_zero", - Dict(k => get_input_at_zero.(get_value_curve.(v)) for (k, v) in pairs(data_pwl)), - resolution, - ) - set_no_load_cost!(sys, generator, forecast_iaz) - - iocs = get_decremental_offer_curves(generator, market_bid) - isequal(first(TimeSeries.values(iocs)), first(data_pwl[initial_time])) - cost_forecast = - get_decremental_variable_cost(generator, market_bid; start_time = initial_time) - @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) -end - @testset "Test MarketBidCost with single `start_up::Number` value" begin expected = (hot = 1.0, warm = 0.0, cold = 0.0) # should only be used for the `hot` value. - no_load_cost = rand() - start_up = 1.0 - shut_down = rand() - cost = MarketBidCost(no_load_cost, start_up, shut_down) + cost = MarketBidCost(; start_up = 1, no_load_cost = rand(), shut_down = rand()) @test get_start_up(cost) == expected end @@ -269,17 +107,17 @@ end other_time = initial_time + resolution name = "test" horizon = 24 - data_pwl = SortedDict(initial_time => test_costs[PiecewiseStepData], - other_time => test_costs[PiecewiseStepData]) + data_pwl = SortedDict(initial_time => test_costs[CostCurve{PiecewiseIncrementalCurve}], + other_time => test_costs[CostCurve{PiecewiseIncrementalCurve}]) sys = System(100.0) reserve = ReserveDemandCurve{ReserveUp}(nothing) add_component!(sys, reserve) - forecast_fd = IS.Deterministic( + forecast = IS.Deterministic( "variable_cost", Dict(k => get_function_data.(v) for (k, v) in pairs(data_pwl)), resolution, ) - set_variable_cost!(sys, reserve, forecast_fd) + set_variable_cost!(sys, reserve, forecast) cost_forecast = get_variable_cost(reserve; start_time = initial_time) @test isequal(first(TimeSeries.values(cost_forecast)), first(data_pwl[initial_time])) end @@ -302,8 +140,8 @@ end resolution = Dates.Hour(1) horizon = 24 data_float = SortedDict(initial_time => test_costs[Float64]) - forecast_fd = IS.Deterministic("fuel_cost", data_float, resolution) - set_fuel_cost!(sys, generator, forecast_fd) + forecast = IS.Deterministic("fuel_cost", data_float, resolution) + set_fuel_cost!(sys, generator, forecast) fuel_forecast = get_fuel_cost(generator; start_time = initial_time) @test first(TimeSeries.values(fuel_forecast)) == first(data_float[initial_time]) fuel_forecast = get_fuel_cost(generator) # missing start_time filled in with initial time @@ -317,7 +155,7 @@ end set_operation_cost!(generator, market_bid) op_cost = get_operation_cost(generator) - @test get_no_load_cost(generator, op_cost) === nothing + @test get_no_load_cost(generator, op_cost) == 0.0 set_no_load_cost!(sys, generator, 1.23) @test get_no_load_cost(generator, op_cost) == 1.23 @@ -326,9 +164,9 @@ end resolution = Dates.Hour(1) horizon = 24 data_float = SortedDict(initial_time => test_costs[Float64]) - forecast_fd = IS.Deterministic("no_load_cost", data_float, resolution) + forecast = IS.Deterministic("no_load_cost", data_float, resolution) - set_no_load_cost!(sys, generator, forecast_fd) + set_no_load_cost!(sys, generator, forecast) @test first(TimeSeries.values(get_no_load_cost(generator, op_cost))) == first(data_float[initial_time]) end @@ -351,13 +189,13 @@ end resolution = Dates.Hour(1) horizon = 24 data_sus = SortedDict(initial_time => test_costs[PSY.StartUpStages]) - forecast_fd = IS.Deterministic( + forecast = IS.Deterministic( "start_up", Dict(k => Tuple.(v) for (k, v) in pairs(data_sus)), resolution, ) - set_start_up!(sys, generator, forecast_fd) + set_start_up!(sys, generator, forecast) @test first(TimeSeries.values(get_start_up(generator, op_cost))) == first(data_sus[initial_time]) end diff --git a/test/test_serialization.jl b/test/test_serialization.jl index e30624695c..50fdce8724 100644 --- a/test/test_serialization.jl +++ b/test/test_serialization.jl @@ -106,8 +106,7 @@ end add_component!(sys, gen) ta = TimeSeries.TimeArray(dates, data) time_series = IS.SingleTimeSeries(; name = "variable_cost", data = ta) - power_units = UnitSystem.NATURAL_UNITS - set_variable_cost!(sys, gen, time_series, power_units) + set_variable_cost!(sys, gen, time_series) service = ConstantReserve{ReserveDown}(; name = "init_$i", available = false, @@ -126,7 +125,6 @@ end gen, service, IS.SingleTimeSeries(; name = "init_$i", data = ta), - power_units, ) end _, result = validate_serialization(sys)