-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move ValueCurve
s, cost aliases, CostCurve
, FuelCurve
, and associated tests from PSY to IS (IS version)
#392
Merged
Merged
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
eb95334
Move `ValueCurve`s and cost aliases from PSY to IS
GabrielKS 2bd40e9
Move `CostCurve` and `FuelCurve` from PSY to IS
GabrielKS c6bee23
Move relevant cost function tests from PSY to IS
GabrielKS b996e91
Merge branch 'main' into gks/costs_to_is
jd-lara File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jd-lara you won't love this, but exporting the aliases is the only way to get them to register as aliases for the purposes of printing and such, and I think overriding the printing of these types is uglier than adding them as exports. @daniel-thom agrees that this is fairly harmless — a name collision is unlikely because these would be imported rather than redefined in any package that also exports them, and also we are (possibly accidentally) already exporting some things from IS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uggg. Fine