-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #392 from NREL-Sienna/gks/costs_to_is
Move `ValueCurve`s, cost aliases, `CostCurve`, `FuelCurve`, and associated tests from PSY to IS (IS version)
- Loading branch information
Showing
5 changed files
with
967 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# Cost aliases: a simplified interface to the portion of the parametric | ||
# `ValueCurve{FunctionData}` design that the user is likely to interact with. Each alias | ||
# consists of a simple name for a particular `ValueCurve{FunctionData}` type, a constructor | ||
# and methods to interact with it without having to think about `FunctionData`, and | ||
# overridden printing behavior to complete the illusion. Everything here (aside from the | ||
# overridden printing) is properly speaking mere syntactic sugar for the underlying | ||
# `ValueCurve{FunctionData}` design. One could imagine similar convenience constructors and | ||
# methods being defined for all the `ValueCurve{FunctionData}` types, not just the ones we | ||
# have here nicely packaged and presented to the user. | ||
|
||
"Whether there is a cost alias for the instance or type under consideration" | ||
is_cost_alias(::Union{ValueCurve, Type{<:ValueCurve}}) = false | ||
|
||
""" | ||
LinearCurve(proportional_term::Float64) | ||
LinearCurve(proportional_term::Float64, constant_term::Float64) | ||
A linear input-output curve, representing a constant marginal rate. May have zero no-load | ||
cost (i.e., constant average rate) or not. | ||
# Arguments | ||
- `proportional_term::Float64`: marginal rate | ||
- `constant_term::Float64`: optional, cost at zero production, defaults to `0.0` | ||
""" | ||
const LinearCurve = InputOutputCurve{LinearFunctionData} | ||
|
||
is_cost_alias(::Union{LinearCurve, Type{LinearCurve}}) = true | ||
|
||
InputOutputCurve{LinearFunctionData}(proportional_term::Real) = | ||
InputOutputCurve(LinearFunctionData(proportional_term)) | ||
|
||
InputOutputCurve{LinearFunctionData}(proportional_term::Real, constant_term::Real) = | ||
InputOutputCurve(LinearFunctionData(proportional_term, constant_term)) | ||
|
||
"Get the proportional term (i.e., slope) of the `LinearCurve`" | ||
get_proportional_term(vc::LinearCurve) = get_proportional_term(get_function_data(vc)) | ||
|
||
"Get the constant term (i.e., intercept) of the `LinearCurve`" | ||
get_constant_term(vc::LinearCurve) = get_constant_term(get_function_data(vc)) | ||
|
||
Base.show(io::IO, vc::LinearCurve) = | ||
if isnothing(get_input_at_zero(vc)) | ||
print(io, "$(typeof(vc))($(get_proportional_term(vc)), $(get_constant_term(vc)))") | ||
else | ||
Base.show_default(io, vc) | ||
end | ||
|
||
""" | ||
QuadraticCurve(quadratic_term::Float64, proportional_term::Float64, constant_term::Float64) | ||
A quadratic input-output curve, may have nonzero no-load cost. | ||
# Arguments | ||
- `quadratic_term::Float64`: quadratic term of the curve | ||
- `proportional_term::Float64`: proportional term of the curve | ||
- `constant_term::Float64`: constant term of the curve | ||
""" | ||
const QuadraticCurve = InputOutputCurve{QuadraticFunctionData} | ||
|
||
is_cost_alias(::Union{QuadraticCurve, Type{QuadraticCurve}}) = true | ||
|
||
InputOutputCurve{QuadraticFunctionData}(quadratic_term, proportional_term, constant_term) = | ||
InputOutputCurve( | ||
QuadraticFunctionData(quadratic_term, proportional_term, constant_term), | ||
) | ||
|
||
"Get the quadratic term of the `QuadraticCurve`" | ||
get_quadratic_term(vc::QuadraticCurve) = get_quadratic_term(get_function_data(vc)) | ||
|
||
"Get the proportional (i.e., linear) term of the `QuadraticCurve`" | ||
get_proportional_term(vc::QuadraticCurve) = get_proportional_term(get_function_data(vc)) | ||
|
||
"Get the constant term of the `QuadraticCurve`" | ||
get_constant_term(vc::QuadraticCurve) = get_constant_term(get_function_data(vc)) | ||
|
||
Base.show(io::IO, vc::QuadraticCurve) = | ||
if isnothing(get_input_at_zero(vc)) | ||
print( | ||
io, | ||
"$(typeof(vc))($(get_quadratic_term(vc)), $(get_proportional_term(vc)), $(get_constant_term(vc)))", | ||
) | ||
else | ||
Base.show_default(io, vc) | ||
end | ||
|
||
""" | ||
PiecewisePointCurve(points::Vector{Tuple{Float64, Float64}}) | ||
A piecewise linear curve specified by cost values at production points. | ||
# Arguments | ||
- `points::Vector{Tuple{Float64, Float64}}` or similar: vector of `(production, cost)` pairs | ||
""" | ||
const PiecewisePointCurve = InputOutputCurve{PiecewiseLinearData} | ||
|
||
is_cost_alias(::Union{PiecewisePointCurve, Type{PiecewisePointCurve}}) = true | ||
|
||
InputOutputCurve{PiecewiseLinearData}(points::Vector) = | ||
InputOutputCurve(PiecewiseLinearData(points)) | ||
|
||
"Get the points that define the `PiecewisePointCurve`" | ||
get_points(vc::PiecewisePointCurve) = get_points(get_function_data(vc)) | ||
|
||
"Get the x-coordinates of the points that define the `PiecewisePointCurve`" | ||
get_x_coords(vc::PiecewisePointCurve) = get_x_coords(get_function_data(vc)) | ||
|
||
"Get the y-coordinates of the points that define the `PiecewisePointCurve`" | ||
get_y_coords(vc::PiecewisePointCurve) = get_y_coords(get_function_data(vc)) | ||
|
||
"Calculate the slopes of the line segments defined by the `PiecewisePointCurve`" | ||
get_slopes(vc::PiecewisePointCurve) = get_slopes(get_function_data(vc)) | ||
|
||
# Here we manually circumvent the @NamedTuple{x::Float64, y::Float64} type annotation, but we keep things looking like named tuples | ||
Base.show(io::IO, vc::PiecewisePointCurve) = | ||
if isnothing(get_input_at_zero(vc)) | ||
print(io, "$(typeof(vc))([$(join(get_points(vc), ", "))])") | ||
else | ||
Base.show_default(io, vc) | ||
end | ||
|
||
""" | ||
PiecewiseIncrementalCurve(initial_input::Union{Float64, Nothing}, x_coords::Vector{Float64}, slopes::Vector{Float64}) | ||
PiecewiseIncrementalCurve(input_at_zero::Union{Nothing, Float64}, initial_input::Union{Float64, Nothing}, x_coords::Vector{Float64}, slopes::Vector{Float64}) | ||
A piecewise linear curve specified by marginal rates (slopes) between production points. May | ||
have nonzero initial value. | ||
# Arguments | ||
- `input_at_zero::Union{Nothing, Float64}`: (optional, defaults to `nothing`) cost at zero production, does NOT represent a part of the curve | ||
- `initial_input::Union{Float64, Nothing}`: cost at minimum production point `first(x_coords)` (NOT at zero production), defines the start of the curve | ||
- `x_coords::Vector{Float64}`: vector of `n` production points | ||
- `slopes::Vector{Float64}`: vector of `n-1` marginal rates/slopes of the curve segments between | ||
the points | ||
""" | ||
const PiecewiseIncrementalCurve = IncrementalCurve{PiecewiseStepData} | ||
|
||
is_cost_alias(::Union{PiecewiseIncrementalCurve, Type{PiecewiseIncrementalCurve}}) = true | ||
|
||
IncrementalCurve{PiecewiseStepData}(initial_input, x_coords::Vector, slopes::Vector) = | ||
IncrementalCurve(PiecewiseStepData(x_coords, slopes), initial_input) | ||
|
||
IncrementalCurve{PiecewiseStepData}( | ||
input_at_zero, | ||
initial_input, | ||
x_coords::Vector, | ||
slopes::Vector, | ||
) = | ||
IncrementalCurve(PiecewiseStepData(x_coords, slopes), initial_input, input_at_zero) | ||
|
||
"Get the x-coordinates that define the `PiecewiseIncrementalCurve`" | ||
get_x_coords(vc::PiecewiseIncrementalCurve) = get_x_coords(get_function_data(vc)) | ||
|
||
"Fetch the slopes that define the `PiecewiseIncrementalCurve`" | ||
get_slopes(vc::PiecewiseIncrementalCurve) = get_y_coords(get_function_data(vc)) | ||
|
||
Base.show(io::IO, vc::PiecewiseIncrementalCurve) = | ||
print( | ||
io, | ||
if isnothing(get_input_at_zero(vc)) | ||
"$(typeof(vc))($(get_initial_input(vc)), $(get_x_coords(vc)), $(get_slopes(vc)))" | ||
else | ||
"$(typeof(vc))($(get_input_at_zero(vc)), $(get_initial_input(vc)), $(get_x_coords(vc)), $(get_slopes(vc)))" | ||
end, | ||
) | ||
|
||
""" | ||
PiecewiseAverageCurve(initial_input::Union{Float64, Nothing}, x_coords::Vector{Float64}, slopes::Vector{Float64}) | ||
A piecewise linear curve specified by average rates between production points. May have | ||
nonzero initial value. | ||
# Arguments | ||
- `initial_input::Union{Float64, Nothing}`: cost at minimum production point `first(x_coords)` (NOT at zero production), defines the start of the curve | ||
- `x_coords::Vector{Float64}`: vector of `n` production points | ||
- `slopes::Vector{Float64}`: vector of `n-1` average rates/slopes of the curve segments between | ||
the points | ||
""" | ||
const PiecewiseAverageCurve = AverageRateCurve{PiecewiseStepData} | ||
|
||
is_cost_alias(::Union{PiecewiseAverageCurve, Type{PiecewiseAverageCurve}}) = true | ||
|
||
AverageRateCurve{PiecewiseStepData}(initial_input, x_coords::Vector, y_coords::Vector) = | ||
AverageRateCurve(PiecewiseStepData(x_coords, y_coords), initial_input) | ||
|
||
"Get the x-coordinates that define the `PiecewiseAverageCurve`" | ||
get_x_coords(vc::PiecewiseAverageCurve) = get_x_coords(get_function_data(vc)) | ||
|
||
"Get the average rates that define the `PiecewiseAverageCurve`" | ||
get_average_rates(vc::PiecewiseAverageCurve) = get_y_coords(get_function_data(vc)) | ||
|
||
Base.show(io::IO, vc::PiecewiseAverageCurve) = | ||
if isnothing(get_input_at_zero(vc)) | ||
print( | ||
io, | ||
"$(typeof(vc))($(get_initial_input(vc)), $(get_x_coords(vc)), $(get_average_rates(vc)))", | ||
) | ||
else | ||
Base.show_default(io, vc) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
abstract type ProductionVariableCostCurve{T <: ValueCurve} end | ||
|
||
serialize(val::ProductionVariableCostCurve) = serialize_struct(val) | ||
deserialize(T::Type{<:ProductionVariableCostCurve}, val::Dict) = | ||
deserialize_struct(T, val) | ||
|
||
"Get the underlying `ValueCurve` representation of this `ProductionVariableCostCurve`" | ||
get_value_curve(cost::ProductionVariableCostCurve) = cost.value_curve | ||
"Get the variable operation and maintenance cost in currency/(power_units h)" | ||
get_vom_cost(cost::ProductionVariableCostCurve) = cost.vom_cost | ||
"Get the units for the x-axis of the curve" | ||
get_power_units(cost::ProductionVariableCostCurve) = cost.power_units | ||
"Get the `FunctionData` representation of this `ProductionVariableCostCurve`'s `ValueCurve`" | ||
get_function_data(cost::ProductionVariableCostCurve) = | ||
get_function_data(get_value_curve(cost)) | ||
"Get the `initial_input` field of this `ProductionVariableCostCurve`'s `ValueCurve` (not defined for input-output data)" | ||
get_initial_input(cost::ProductionVariableCostCurve) = | ||
get_initial_input(get_value_curve(cost)) | ||
"Calculate the convexity of the underlying data" | ||
is_convex(cost::ProductionVariableCostCurve) = is_convex(get_value_curve(cost)) | ||
|
||
Base.:(==)(a::T, b::T) where {T <: ProductionVariableCostCurve} = | ||
double_equals_from_fields(a, b) | ||
|
||
Base.isequal(a::T, b::T) where {T <: ProductionVariableCostCurve} = | ||
isequal_from_fields(a, b) | ||
|
||
Base.hash(a::ProductionVariableCostCurve) = hash_from_fields(a) | ||
|
||
""" | ||
$(TYPEDEF) | ||
$(TYPEDFIELDS) | ||
CostCurve(value_curve, power_units, vom_cost) | ||
CostCurve(; value_curve, power_units, vom_cost) | ||
Direct representation of the variable operation cost of a power plant in currency. Composed | ||
of a [`ValueCurve`](@ref) that may represent input-output, incremental, or average rate | ||
data. The default units for the x-axis are MW and can be specified with | ||
`power_units`. | ||
""" | ||
@kwdef struct CostCurve{T <: ValueCurve} <: ProductionVariableCostCurve{T} | ||
"The underlying `ValueCurve` representation of this `ProductionVariableCostCurve`" | ||
value_curve::T | ||
"(default: natural units (MW)) The units for the x-axis of the curve" | ||
power_units::UnitSystem = UnitSystem.NATURAL_UNITS | ||
"(default of 0) Additional proportional Variable Operation and Maintenance Cost in | ||
\$/(power_unit h), represented as a [`LinearCurve`](@ref)" | ||
vom_cost::LinearCurve = LinearCurve(0.0) | ||
end | ||
|
||
CostCurve(value_curve) = CostCurve(; value_curve) | ||
CostCurve(value_curve, vom_cost::LinearCurve) = | ||
CostCurve(; value_curve, vom_cost = vom_cost) | ||
CostCurve(value_curve, power_units::UnitSystem) = | ||
CostCurve(; value_curve, power_units = power_units) | ||
|
||
Base.:(==)(a::CostCurve, b::CostCurve) = | ||
(get_value_curve(a) == get_value_curve(b)) && | ||
(get_power_units(a) == get_power_units(b)) && | ||
(get_vom_cost(a) == get_vom_cost(b)) | ||
|
||
"Get a `CostCurve` representing zero variable cost" | ||
Base.zero(::Union{CostCurve, Type{CostCurve}}) = CostCurve(zero(ValueCurve)) | ||
|
||
""" | ||
$(TYPEDEF) | ||
$(TYPEDFIELDS) | ||
FuelCurve(value_curve, power_units, fuel_cost, vom_cost) | ||
FuelCurve(value_curve, fuel_cost) | ||
FuelCurve(value_curve, fuel_cost, vom_cost) | ||
FuelCurve(value_curve, power_units, fuel_cost) | ||
FuelCurve(; value_curve, power_units, fuel_cost, vom_cost) | ||
Representation of the variable operation cost of a power plant in terms of fuel (MBTU, | ||
liters, m^3, etc.), coupled with a conversion factor between fuel and currency. Composed of | ||
a [`ValueCurve`](@ref) that may represent input-output, incremental, or average rate data. | ||
The default units for the x-axis are MW and can be specified with `power_units`. | ||
""" | ||
@kwdef struct FuelCurve{T <: ValueCurve} <: ProductionVariableCostCurve{T} | ||
"The underlying `ValueCurve` representation of this `ProductionVariableCostCurve`" | ||
value_curve::T | ||
"(default: natural units (MW)) The units for the x-axis of the curve" | ||
power_units::UnitSystem = UnitSystem.NATURAL_UNITS | ||
"Either a fixed value for fuel cost or the key to a fuel cost time series" | ||
fuel_cost::Union{Float64, TimeSeriesKey} | ||
"(default of 0) Additional proportional Variable Operation and Maintenance Cost in \$/(power_unit h) | ||
represented as a [`LinearCurve`](@ref)" | ||
vom_cost::LinearCurve = LinearCurve(0.0) | ||
end | ||
|
||
FuelCurve( | ||
value_curve::ValueCurve, | ||
power_units::UnitSystem, | ||
fuel_cost::Real, | ||
vom_cost::LinearCurve, | ||
) = | ||
FuelCurve(value_curve, power_units, Float64(fuel_cost), vom_cost) | ||
|
||
FuelCurve(value_curve, fuel_cost) = FuelCurve(; value_curve, fuel_cost) | ||
FuelCurve(value_curve, fuel_cost::Union{Float64, TimeSeriesKey}, vom_cost::LinearCurve) = | ||
FuelCurve(; value_curve, fuel_cost, vom_cost = vom_cost) | ||
FuelCurve(value_curve, power_units::UnitSystem, fuel_cost::Union{Float64, TimeSeriesKey}) = | ||
FuelCurve(; value_curve, power_units = power_units, fuel_cost = fuel_cost) | ||
|
||
Base.:(==)(a::FuelCurve, b::FuelCurve) = | ||
(get_value_curve(a) == get_value_curve(b)) && | ||
(get_power_units(a) == get_power_units(b)) && | ||
(get_fuel_cost(a) == get_fuel_cost(b)) && | ||
(get_vom_cost(a) == get_vom_cost(b)) | ||
|
||
"Get a `FuelCurve` representing zero fuel usage and zero fuel cost" | ||
Base.zero(::Union{FuelCurve, Type{FuelCurve}}) = FuelCurve(zero(ValueCurve), 0.0) | ||
|
||
"Get the fuel cost or the name of the fuel cost time series" | ||
get_fuel_cost(cost::FuelCurve) = cost.fuel_cost | ||
|
||
Base.show(io::IO, m::MIME"text/plain", curve::ProductionVariableCostCurve) = | ||
(get(io, :compact, false)::Bool ? _show_compact : _show_expanded)(io, m, curve) | ||
|
||
# The strategy here is to put all the short stuff on the first line, then break and let the value_curve take more space | ||
function _show_compact(io::IO, ::MIME"text/plain", curve::CostCurve) | ||
print( | ||
io, | ||
"$(nameof(typeof(curve))) with power_units $(curve.power_units), vom_cost $(curve.vom_cost), and value_curve:\n ", | ||
) | ||
vc_printout = sprint(show, "text/plain", curve.value_curve; context = io) # Capture the value_curve `show` so we can indent it | ||
print(io, replace(vc_printout, "\n" => "\n ")) | ||
end | ||
|
||
function _show_compact(io::IO, ::MIME"text/plain", curve::FuelCurve) | ||
print( | ||
io, | ||
"$(nameof(typeof(curve))) with power_units $(curve.power_units), fuel_cost $(curve.fuel_cost), vom_cost $(curve.vom_cost), and value_curve:\n ", | ||
) | ||
vc_printout = sprint(show, "text/plain", curve.value_curve; context = io) | ||
print(io, replace(vc_printout, "\n" => "\n ")) | ||
end | ||
|
||
function _show_expanded(io::IO, ::MIME"text/plain", curve::ProductionVariableCostCurve) | ||
print(io, "$(nameof(typeof(curve))):") | ||
for field_name in fieldnames(typeof(curve)) | ||
val = getproperty(curve, field_name) | ||
val_printout = | ||
replace(sprint(show, "text/plain", val; context = io), "\n" => "\n ") | ||
print(io, "\n $(field_name): $val_printout") | ||
end | ||
end |
Oops, something went wrong.