From aedda0b65c1c76e8e4c351c9edf36b3cca56303a Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:10:58 -0600 Subject: [PATCH 1/3] Move `ValueCurve`s and cost aliases from PSY to IS --- src/PowerSystems.jl | 16 +- src/models/cost_functions/ValueCurves.jl | 287 ---------------------- src/models/cost_functions/cost_aliases.jl | 199 --------------- test/test_cost_functions.jl | 14 +- 4 files changed, 21 insertions(+), 495 deletions(-) delete mode 100644 src/models/cost_functions/ValueCurves.jl delete mode 100644 src/models/cost_functions/cost_aliases.jl diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index 4f59df6121..b8f25c5185 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -586,6 +586,20 @@ import InfrastructureSystems: get_raw_data_type, supports_time_series, supports_supplemental_attributes +import InfrastructureSystems: + ValueCurve, + InputOutputCurve, + IncrementalCurve, + AverageRateCurve, + LinearCurve, + QuadraticCurve, + PiecewisePointCurve, + PiecewiseIncrementalCurve, + PiecewiseAverageCurve, + get_function_data, + get_initial_input, + get_input_at_zero, + get_average_rates const IS = InfrastructureSystems @@ -642,8 +656,6 @@ include("models/dynamic_inverter_components.jl") include("models/OuterControl.jl") # Costs -include("models/cost_functions/ValueCurves.jl") -include("models/cost_functions/cost_aliases.jl") include("models/cost_functions/variable_cost.jl") include("models/cost_functions/operational_cost.jl") include("models/cost_functions/MarketBidCost.jl") diff --git a/src/models/cost_functions/ValueCurves.jl b/src/models/cost_functions/ValueCurves.jl deleted file mode 100644 index b0c3a53e53..0000000000 --- a/src/models/cost_functions/ValueCurves.jl +++ /dev/null @@ -1,287 +0,0 @@ -""" -Supertype that represents a unitless cost curve - -Concrete options are [listed here.](@ref value_curve_library) -""" -abstract type ValueCurve{T <: FunctionData} end - -# JSON SERIALIZATION -IS.serialize(val::ValueCurve) = IS.serialize_struct(val) -IS.deserialize(T::Type{<:ValueCurve}, val::Dict) = IS.deserialize_struct(T, val) - -"Get the underlying `FunctionData` representation of this `ValueCurve`" -get_function_data(curve::ValueCurve) = curve.function_data - -"Get the `input_at_zero` field of this `ValueCurve`" -get_input_at_zero(curve::ValueCurve) = curve.input_at_zero - -""" -An input-output curve, directly relating the production quantity to the cost: `y = f(x)`. -Can be used, for instance, in the representation of a [`CostCurve`](@ref) where `x` is MW -and `y` is currency/hr, or in the representation of a [`FuelCurve`](@ref) where `x` is MW -and `y` is fuel/hr. -""" -@kwdef struct InputOutputCurve{ - T <: Union{QuadraticFunctionData, LinearFunctionData, PiecewiseLinearData}, -} <: ValueCurve{T} - "The underlying `FunctionData` representation of this `ValueCurve`" - function_data::T - "Optional, an explicit representation of the input value at zero output." - input_at_zero::Union{Nothing, Float64} = nothing -end - -InputOutputCurve(function_data) = InputOutputCurve(function_data, nothing) -InputOutputCurve{T}( - function_data, -) where {(T <: Union{QuadraticFunctionData, LinearFunctionData, PiecewiseLinearData})} = - InputOutputCurve{T}(function_data, nothing) - -""" -An incremental (or 'marginal') curve, relating the production quantity to the derivative of -cost: `y = f'(x)`. Can be used, for instance, in the representation of a [`CostCurve`](@ref) -where `x` is MW and `y` is currency/MWh, or in the representation of a [`FuelCurve`](@ref) -where `x` is MW and `y` is fuel/MWh. -""" -@kwdef struct IncrementalCurve{T <: Union{LinearFunctionData, PiecewiseStepData}} <: - ValueCurve{T} - "The underlying `FunctionData` representation of this `ValueCurve`" - function_data::T - "The value of f(x) at the least x for which the function is defined, or the origin for functions with no left endpoint, used for conversion to `InputOutputCurve`" - initial_input::Union{Float64, Nothing} - "Optional, an explicit representation of the input value at zero output." - input_at_zero::Union{Nothing, Float64} = nothing -end - -IncrementalCurve(function_data, initial_input) = - IncrementalCurve(function_data, initial_input, nothing) -IncrementalCurve{T}( - function_data, - initial_input, -) where {(T <: Union{QuadraticFunctionData, LinearFunctionData, PiecewiseLinearData})} = - IncrementalCurve{T}(function_data, initial_input, nothing) - -""" -An average rate curve, relating the production quantity to the average cost rate from the -origin: `y = f(x)/x`. Can be used, for instance, in the representation of a -[`CostCurve`](@ref) where `x` is MW and `y` is currency/MWh, or in the representation of a -[`FuelCurve`](@ref) where `x` is MW and `y` is fuel/MWh. Typically calculated by dividing -absolute values of cost rate or fuel input rate by absolute values of electric power. -""" -@kwdef struct AverageRateCurve{T <: Union{LinearFunctionData, PiecewiseStepData}} <: - ValueCurve{T} - "The underlying `FunctionData` representation of this `ValueCurve`, in the case of `AverageRateCurve{LinearFunctionData}` representing only the oblique asymptote" - function_data::T - "The value of f(x) at the least x for which the function is defined, or the origin for functions with no left endpoint, used for conversion to `InputOutputCurve`" - initial_input::Union{Float64, Nothing} - "Optional, an explicit representation of the input value at zero output." - input_at_zero::Union{Nothing, Float64} = nothing -end - -AverageRateCurve(function_data, initial_input) = - AverageRateCurve(function_data, initial_input, nothing) -AverageRateCurve{T}( - function_data, - initial_input, -) where {(T <: Union{QuadraticFunctionData, LinearFunctionData, PiecewiseLinearData})} = - AverageRateCurve{T}(function_data, initial_input, nothing) - -"Get the `initial_input` field of this `ValueCurve` (not defined for `InputOutputCurve`)" -get_initial_input(curve::Union{IncrementalCurve, AverageRateCurve}) = curve.initial_input - -# BASE METHODS -Base.:(==)(a::T, b::T) where {T <: ValueCurve} = IS.double_equals_from_fields(a, b) - -Base.isequal(a::T, b::T) where {T <: ValueCurve} = IS.isequal_from_fields(a, b) - -Base.hash(a::ValueCurve) = IS.hash_from_fields(a) - -"Get an `InputOutputCurve` representing `f(x) = 0`" -Base.zero(::Union{InputOutputCurve, Type{InputOutputCurve}}) = - InputOutputCurve(zero(FunctionData)) - -"Get an `IncrementalCurve` representing `f'(x) = 0` with zero `initial_input`" -Base.zero(::Union{IncrementalCurve, Type{IncrementalCurve}}) = - IncrementalCurve(zero(FunctionData), 0.0) - -"Get an `AverageRateCurve` representing `f(x)/x = 0` with zero `initial_input`" -Base.zero(::Union{AverageRateCurve, Type{AverageRateCurve}}) = - AverageRateCurve(zero(FunctionData), 0.0) - -"Get a `ValueCurve` representing zero variable cost" -Base.zero(::Union{ValueCurve, Type{ValueCurve}}) = - Base.zero(InputOutputCurve) - -# CONVERSIONS: InputOutputCurve{LinearFunctionData} to InputOutputCurve{QuadraticFunctionData} -InputOutputCurve{QuadraticFunctionData}(data::InputOutputCurve{LinearFunctionData}) = - InputOutputCurve{QuadraticFunctionData}( - get_function_data(data), - get_input_at_zero(data), - ) - -Base.convert( - ::Type{InputOutputCurve{QuadraticFunctionData}}, - data::InputOutputCurve{LinearFunctionData}, -) = InputOutputCurve{QuadraticFunctionData}(data) - -# CONVERSIONS: InputOutputCurve to X -function IncrementalCurve(data::InputOutputCurve{QuadraticFunctionData}) - fd = get_function_data(data) - q, p, c = get_quadratic_term(fd), get_proportional_term(fd), get_constant_term(fd) - return IncrementalCurve(LinearFunctionData(2q, p), c, get_input_at_zero(data)) -end - -function AverageRateCurve(data::InputOutputCurve{QuadraticFunctionData}) - fd = get_function_data(data) - q, p, c = get_quadratic_term(fd), get_proportional_term(fd), get_constant_term(fd) - return AverageRateCurve(LinearFunctionData(q, p), c, get_input_at_zero(data)) -end - -IncrementalCurve(data::InputOutputCurve{LinearFunctionData}) = - IncrementalCurve(InputOutputCurve{QuadraticFunctionData}(data)) - -AverageRateCurve(data::InputOutputCurve{LinearFunctionData}) = - AverageRateCurve(InputOutputCurve{QuadraticFunctionData}(data)) - -function IncrementalCurve(data::InputOutputCurve{PiecewiseLinearData}) - fd = get_function_data(data) - return IncrementalCurve( - PiecewiseStepData(get_x_coords(fd), get_slopes(fd)), - first(get_points(fd)).y, get_input_at_zero(data), - ) -end - -function AverageRateCurve(data::InputOutputCurve{PiecewiseLinearData}) - fd = get_function_data(data) - points = get_points(fd) - slopes_from_origin = [p.y / p.x for p in points[2:end]] - return AverageRateCurve( - PiecewiseStepData(get_x_coords(fd), slopes_from_origin), - first(points).y, get_input_at_zero(data), - ) -end - -# CONVERSIONS: IncrementalCurve to X -function InputOutputCurve(data::IncrementalCurve{LinearFunctionData}) - fd = get_function_data(data) - p = get_proportional_term(fd) - c = get_initial_input(data) - isnothing(c) && throw( - ArgumentError("Cannot convert `IncrementalCurve` with undefined `initial_input`"), - ) - (p == 0) && return InputOutputCurve( - LinearFunctionData(get_constant_term(fd), c), - ) - return InputOutputCurve( - QuadraticFunctionData(p / 2, get_constant_term(fd), c), - get_input_at_zero(data), - ) -end - -function InputOutputCurve(data::IncrementalCurve{PiecewiseStepData}) - fd = get_function_data(data) - c = get_initial_input(data) - isnothing(c) && throw( - ArgumentError("Cannot convert `IncrementalCurve` with undefined `initial_input`"), - ) - points = running_sum(fd) - return InputOutputCurve( - PiecewiseLinearData([(p.x, p.y + c) for p in points]), - get_input_at_zero(data), - ) -end - -AverageRateCurve(data::IncrementalCurve) = AverageRateCurve(InputOutputCurve(data)) - -# CONVERSIONS: AverageRateCurve to X -function InputOutputCurve(data::AverageRateCurve{LinearFunctionData}) - fd = get_function_data(data) - p = get_proportional_term(fd) - c = get_initial_input(data) - isnothing(c) && throw( - ArgumentError("Cannot convert `AverageRateCurve` with undefined `initial_input`"), - ) - (p == 0) && return InputOutputCurve( - LinearFunctionData(get_constant_term(fd), c), - get_input_at_zero(data), - ) - return InputOutputCurve( - QuadraticFunctionData(p, get_constant_term(fd), c), - get_input_at_zero(data), - ) -end - -function InputOutputCurve(data::AverageRateCurve{PiecewiseStepData}) - fd = get_function_data(data) - c = get_initial_input(data) - isnothing(c) && throw( - ArgumentError("Cannot convert `AverageRateCurve` with undefined `initial_input`"), - ) - xs = get_x_coords(fd) - ys = xs[2:end] .* get_y_coords(fd) - return InputOutputCurve( - PiecewiseLinearData(collect(zip(xs, vcat(c, ys)))), - get_input_at_zero(data), - ) -end - -IncrementalCurve(data::AverageRateCurve) = IncrementalCurve(InputOutputCurve(data)) - -# CALCULATIONS -is_convex(curve::InputOutputCurve) = is_convex(get_function_data(curve)) -"Calculate the convexity of the underlying data" -is_convex(curve::ValueCurve) = is_convex(InputOutputCurve(curve)) - -# PRINTING -# For cost aliases, return the alias name; otherwise, return the type name without the parameter -simple_type_name(curve::ValueCurve) = - string(is_cost_alias(curve) ? typeof(curve) : nameof(typeof(curve))) - -function Base.show(io::IO, ::MIME"text/plain", curve::InputOutputCurve) - print(io, simple_type_name(curve)) - is_cost_alias(curve) && print(io, " (a type of $InputOutputCurve)") - print(io, " where ") - !isnothing(get_input_at_zero(curve)) && - print(io, "value at zero is $(get_input_at_zero(curve)), ") - print(io, "function is: ") - show(IOContext(io, :compact => true), "text/plain", get_function_data(curve)) -end - -function Base.show(io::IO, ::MIME"text/plain", curve::IncrementalCurve) - print(io, simple_type_name(curve)) - print(io, " where ") - !isnothing(get_input_at_zero(curve)) && - print(io, "value at zero is $(get_input_at_zero(curve)), ") - print(io, "initial value is $(get_initial_input(curve))") - print(io, ", derivative function f is: ") - show(IOContext(io, :compact => true), "text/plain", get_function_data(curve)) -end - -function Base.show(io::IO, ::MIME"text/plain", curve::AverageRateCurve) - print(io, simple_type_name(curve)) - print(io, " where ") - !isnothing(get_input_at_zero(curve)) && - print(io, "value at zero is $(get_input_at_zero(curve)), ") - print(io, "initial value is $(get_initial_input(curve))") - print(io, ", average rate function f is: ") - show(IOContext(io, :compact => true), "text/plain", get_function_data(curve)) -end - -# MORE GENERIC CONSTRUCTORS -# These manually do what https://github.com/JuliaLang/julia/issues/35053 (open at time of writing) proposes to automatically provide -InputOutputCurve( - function_data::T, - input_at_zero, -) where {T <: Union{LinearFunctionData, QuadraticFunctionData, PiecewiseLinearData}} = - InputOutputCurve{T}(function_data, input_at_zero) -IncrementalCurve( - function_data::T, - initial_input, - input_at_zero, -) where {T <: Union{LinearFunctionData, PiecewiseStepData}} = - IncrementalCurve{T}(function_data, initial_input, input_at_zero) -AverageRateCurve( - function_data::T, - initial_input, - input_at_zero, -) where {T <: Union{LinearFunctionData, PiecewiseStepData}} = - AverageRateCurve{T}(function_data, initial_input, input_at_zero) diff --git a/src/models/cost_functions/cost_aliases.jl b/src/models/cost_functions/cost_aliases.jl deleted file mode 100644 index 011450fba3..0000000000 --- a/src/models/cost_functions/cost_aliases.jl +++ /dev/null @@ -1,199 +0,0 @@ -# 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 diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 9a73427109..74d793e45f 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -20,7 +20,7 @@ end AverageRateCurve(LinearFunctionData(3, 2), 1.0) @test zero(io_quadratic) == InputOutputCurve(LinearFunctionData(0, 0)) @test zero(InputOutputCurve) == InputOutputCurve(LinearFunctionData(0, 0)) - @test PSY.is_cost_alias(io_quadratic) == PSY.is_cost_alias(typeof(io_quadratic)) == true + @test IS.is_cost_alias(io_quadratic) == IS.is_cost_alias(typeof(io_quadratic)) == true @test repr(io_quadratic) == sprint(show, io_quadratic) == "QuadraticCurve(3.0, 2.0, 1.0)" @test sprint(show, "text/plain", io_quadratic) == @@ -35,7 +35,7 @@ end IncrementalCurve(LinearFunctionData(0, 2), 1.0) @test AverageRateCurve(io_linear) == AverageRateCurve(LinearFunctionData(0, 2), 1.0) - @test PSY.is_cost_alias(io_linear) == PSY.is_cost_alias(typeof(io_linear)) == true + @test IS.is_cost_alias(io_linear) == IS.is_cost_alias(typeof(io_linear)) == true @test repr(io_linear) == sprint(show, io_linear) == "LinearCurve(2.0, 1.0)" @test sprint(show, "text/plain", io_linear) == @@ -49,7 +49,7 @@ end IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) @test AverageRateCurve(io_piecewise) == AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - @test PSY.is_cost_alias(io_piecewise) == PSY.is_cost_alias(typeof(io_piecewise)) == true + @test IS.is_cost_alias(io_piecewise) == IS.is_cost_alias(typeof(io_piecewise)) == true @test repr(io_piecewise) == sprint(show, io_piecewise) == "PiecewisePointCurve([(x = 1.0, y = 6.0), (x = 3.0, y = 9.0), (x = 5.0, y = 13.0)])" @test sprint(show, "text/plain", io_piecewise) == @@ -72,7 +72,7 @@ end @test_throws ArgumentError AverageRateCurve(inc_linear_no_initial) @test zero(inc_linear) == IncrementalCurve(LinearFunctionData(0, 0), 0.0) @test zero(IncrementalCurve) == IncrementalCurve(LinearFunctionData(0, 0), 0.0) - @test PSY.is_cost_alias(inc_linear) == PSY.is_cost_alias(typeof(inc_linear)) == false + @test IS.is_cost_alias(inc_linear) == IS.is_cost_alias(typeof(inc_linear)) == false @test repr(inc_linear) == sprint(show, inc_linear) == "IncrementalCurve{LinearFunctionData}(LinearFunctionData(6.0, 2.0), 1.0, nothing)" @test sprint(show, "text/plain", inc_linear) == @@ -91,7 +91,7 @@ end AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) @test_throws ArgumentError InputOutputCurve(inc_piecewise_no_initial) @test_throws ArgumentError AverageRateCurve(inc_piecewise_no_initial) - @test PSY.is_cost_alias(inc_piecewise) == PSY.is_cost_alias(typeof(inc_piecewise)) == + @test IS.is_cost_alias(inc_piecewise) == IS.is_cost_alias(typeof(inc_piecewise)) == true @test repr(inc_piecewise) == sprint(show, inc_piecewise) == "PiecewiseIncrementalCurve(6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" @@ -115,7 +115,7 @@ end @test_throws ArgumentError IncrementalCurve(ar_linear_no_initial) @test zero(ar_linear) == AverageRateCurve(LinearFunctionData(0, 0), 0.0) @test zero(AverageRateCurve) == AverageRateCurve(LinearFunctionData(0, 0), 0.0) - @test PSY.is_cost_alias(ar_linear) == PSY.is_cost_alias(typeof(ar_linear)) == false + @test IS.is_cost_alias(ar_linear) == IS.is_cost_alias(typeof(ar_linear)) == false @test repr(ar_linear) == sprint(show, ar_linear) == "AverageRateCurve{LinearFunctionData}(LinearFunctionData(3.0, 2.0), 1.0, nothing)" @test sprint(show, "text/plain", ar_linear) == @@ -134,7 +134,7 @@ end IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) @test_throws ArgumentError InputOutputCurve(ar_piecewise_no_initial) @test_throws ArgumentError IncrementalCurve(ar_piecewise_no_initial) - @test PSY.is_cost_alias(ar_piecewise) == PSY.is_cost_alias(typeof(ar_piecewise)) == true + @test IS.is_cost_alias(ar_piecewise) == IS.is_cost_alias(typeof(ar_piecewise)) == true @test repr(ar_piecewise) == sprint(show, ar_piecewise) == "PiecewiseAverageCurve(6.0, [1.0, 3.0, 5.0], [3.0, 2.6])" @test sprint(show, "text/plain", ar_piecewise) == From 7b306dd8505844d07bd3381d2adaca6cc51f5109 Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:33:04 -0600 Subject: [PATCH 2/3] Move `CostCurve` and `FuelCurve` from PSY to IS --- src/PowerSystems.jl | 10 +- src/models/cost_functions/variable_cost.jl | 149 --------------------- 2 files changed, 8 insertions(+), 151 deletions(-) delete mode 100644 src/models/cost_functions/variable_cost.jl diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index b8f25c5185..5dc4f3b9e7 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -599,7 +599,14 @@ import InfrastructureSystems: get_function_data, get_initial_input, get_input_at_zero, - get_average_rates + get_average_rates, + ProductionVariableCostCurve, + CostCurve, + FuelCurve, + get_value_curve, + get_vom_cost, + get_power_units, + get_fuel_cost const IS = InfrastructureSystems @@ -656,7 +663,6 @@ include("models/dynamic_inverter_components.jl") include("models/OuterControl.jl") # Costs -include("models/cost_functions/variable_cost.jl") include("models/cost_functions/operational_cost.jl") include("models/cost_functions/MarketBidCost.jl") include("models/cost_functions/HydroGenerationCost.jl") diff --git a/src/models/cost_functions/variable_cost.jl b/src/models/cost_functions/variable_cost.jl deleted file mode 100644 index 592958472f..0000000000 --- a/src/models/cost_functions/variable_cost.jl +++ /dev/null @@ -1,149 +0,0 @@ -abstract type ProductionVariableCostCurve{T <: ValueCurve} end - -IS.serialize(val::ProductionVariableCostCurve) = IS.serialize_struct(val) -IS.deserialize(T::Type{<:ProductionVariableCostCurve}, val::Dict) = - IS.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} = - IS.double_equals_from_fields(a, b) - -Base.isequal(a::T, b::T) where {T <: ProductionVariableCostCurve} = - IS.isequal_from_fields(a, b) - -Base.hash(a::ProductionVariableCostCurve) = IS.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 From 243ed63dbdb98eb98024c60dd2e92c21bb4c31a1 Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Fri, 2 Aug 2024 11:21:09 -0600 Subject: [PATCH 3/3] Move relevant cost function tests from PSY to IS --- test/test_cost_functions.jl | 305 +----------------------------------- 1 file changed, 6 insertions(+), 299 deletions(-) diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 74d793e45f..08161df397 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -1,308 +1,15 @@ -# Get all possible isomorphic representations of the given `ValueCurve` -function all_conversions(vc::ValueCurve; - universe = (InputOutputCurve, IncrementalCurve, AverageRateCurve), -) - convert_to = filter(!=(nameof(typeof(vc))) ∘ nameof, universe) # x -> nameof(x) != nameof(typeof(vc)) - result = Set{ValueCurve}(constructor(vc) for constructor in convert_to) - (vc isa InputOutputCurve{LinearFunctionData}) && - push!(result, InputOutputCurve{QuadraticFunctionData}(vc)) - return result -end - -@testset "Test ValueCurves" begin - # InputOutputCurve - io_quadratic = InputOutputCurve(QuadraticFunctionData(3, 2, 1)) - @test io_quadratic isa InputOutputCurve{QuadraticFunctionData} - @test get_function_data(io_quadratic) == QuadraticFunctionData(3, 2, 1) - @test IncrementalCurve(io_quadratic) == - IncrementalCurve(LinearFunctionData(6, 2), 1.0) - @test AverageRateCurve(io_quadratic) == - AverageRateCurve(LinearFunctionData(3, 2), 1.0) - @test zero(io_quadratic) == InputOutputCurve(LinearFunctionData(0, 0)) - @test zero(InputOutputCurve) == InputOutputCurve(LinearFunctionData(0, 0)) - @test IS.is_cost_alias(io_quadratic) == IS.is_cost_alias(typeof(io_quadratic)) == true - @test repr(io_quadratic) == sprint(show, io_quadratic) == - "QuadraticCurve(3.0, 2.0, 1.0)" - @test sprint(show, "text/plain", io_quadratic) == - "QuadraticCurve (a type of InputOutputCurve) where function is: f(x) = 3.0 x^2 + 2.0 x + 1.0" - - io_linear = InputOutputCurve(LinearFunctionData(2, 1)) - @test io_linear isa InputOutputCurve{LinearFunctionData} - @test get_function_data(io_linear) == LinearFunctionData(2, 1) - @test InputOutputCurve{QuadraticFunctionData}(io_linear) == - InputOutputCurve(QuadraticFunctionData(0, 2, 1)) - @test IncrementalCurve(io_linear) == - IncrementalCurve(LinearFunctionData(0, 2), 1.0) - @test AverageRateCurve(io_linear) == - AverageRateCurve(LinearFunctionData(0, 2), 1.0) - @test IS.is_cost_alias(io_linear) == IS.is_cost_alias(typeof(io_linear)) == true - @test repr(io_linear) == sprint(show, io_linear) == - "LinearCurve(2.0, 1.0)" - @test sprint(show, "text/plain", io_linear) == - "LinearCurve (a type of InputOutputCurve) where function is: f(x) = 2.0 x + 1.0" - - io_piecewise = InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test io_piecewise isa InputOutputCurve{PiecewiseLinearData} - @test get_function_data(io_piecewise) == - PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]) - @test IncrementalCurve(io_piecewise) == - IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - @test AverageRateCurve(io_piecewise) == - AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - @test IS.is_cost_alias(io_piecewise) == IS.is_cost_alias(typeof(io_piecewise)) == true - @test repr(io_piecewise) == sprint(show, io_piecewise) == - "PiecewisePointCurve([(x = 1.0, y = 6.0), (x = 3.0, y = 9.0), (x = 5.0, y = 13.0)])" - @test sprint(show, "text/plain", io_piecewise) == - "PiecewisePointCurve (a type of InputOutputCurve) where function is: piecewise linear y = f(x) connecting points:\n (x = 1.0, y = 6.0)\n (x = 3.0, y = 9.0)\n (x = 5.0, y = 13.0)" - - # IncrementalCurve - inc_linear = IncrementalCurve(LinearFunctionData(6, 2), 1.0) - inc_linear_no_initial = IncrementalCurve(LinearFunctionData(6, 2), nothing) - @test inc_linear isa IncrementalCurve{LinearFunctionData} - @test inc_linear_no_initial isa IncrementalCurve{LinearFunctionData} - @test get_function_data(inc_linear) == LinearFunctionData(6, 2) - @test get_initial_input(inc_linear) == 1 - @test InputOutputCurve(inc_linear) == - InputOutputCurve(QuadraticFunctionData(3, 2, 1)) - @test InputOutputCurve(IncrementalCurve(LinearFunctionData(0, 2), 1.0)) == - InputOutputCurve(LinearFunctionData(2, 1)) - @test AverageRateCurve(inc_linear) == - AverageRateCurve(LinearFunctionData(3, 2), 1.0) - @test_throws ArgumentError InputOutputCurve(inc_linear_no_initial) - @test_throws ArgumentError AverageRateCurve(inc_linear_no_initial) - @test zero(inc_linear) == IncrementalCurve(LinearFunctionData(0, 0), 0.0) - @test zero(IncrementalCurve) == IncrementalCurve(LinearFunctionData(0, 0), 0.0) - @test IS.is_cost_alias(inc_linear) == IS.is_cost_alias(typeof(inc_linear)) == false - @test repr(inc_linear) == sprint(show, inc_linear) == - "IncrementalCurve{LinearFunctionData}(LinearFunctionData(6.0, 2.0), 1.0, nothing)" - @test sprint(show, "text/plain", inc_linear) == - "IncrementalCurve where initial value is 1.0, derivative function f is: f(x) = 6.0 x + 2.0" - - inc_piecewise = IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - inc_piecewise_no_initial = - IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), nothing) - @test inc_piecewise isa IncrementalCurve{PiecewiseStepData} - @test inc_piecewise_no_initial isa IncrementalCurve{PiecewiseStepData} - @test get_function_data(inc_piecewise) == PiecewiseStepData([1, 3, 5], [1.5, 2]) - @test get_initial_input(inc_piecewise) == 6 - @test InputOutputCurve(inc_piecewise) == - InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test AverageRateCurve(inc_piecewise) == - AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - @test_throws ArgumentError InputOutputCurve(inc_piecewise_no_initial) - @test_throws ArgumentError AverageRateCurve(inc_piecewise_no_initial) - @test IS.is_cost_alias(inc_piecewise) == IS.is_cost_alias(typeof(inc_piecewise)) == - true - @test repr(inc_piecewise) == sprint(show, inc_piecewise) == - "PiecewiseIncrementalCurve(6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" - @test sprint(show, "text/plain", inc_piecewise) == - "PiecewiseIncrementalCurve where initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" - - # AverageRateCurve - ar_linear = AverageRateCurve(LinearFunctionData(3, 2), 1.0) - ar_linear_no_initial = AverageRateCurve(LinearFunctionData(3, 2), nothing) - @test ar_linear isa AverageRateCurve{LinearFunctionData} - @test ar_linear_no_initial isa AverageRateCurve{LinearFunctionData} - @test get_function_data(ar_linear) == LinearFunctionData(3, 2) - @test get_initial_input(ar_linear) == 1 - @test InputOutputCurve(ar_linear) == - InputOutputCurve(QuadraticFunctionData(3, 2, 1)) - @test InputOutputCurve(AverageRateCurve(LinearFunctionData(0, 2), 1.0)) == - InputOutputCurve(LinearFunctionData(2, 1)) - @test IncrementalCurve(ar_linear) == - IncrementalCurve(LinearFunctionData(6, 2), 1.0) - @test_throws ArgumentError InputOutputCurve(ar_linear_no_initial) - @test_throws ArgumentError IncrementalCurve(ar_linear_no_initial) - @test zero(ar_linear) == AverageRateCurve(LinearFunctionData(0, 0), 0.0) - @test zero(AverageRateCurve) == AverageRateCurve(LinearFunctionData(0, 0), 0.0) - @test IS.is_cost_alias(ar_linear) == IS.is_cost_alias(typeof(ar_linear)) == false - @test repr(ar_linear) == sprint(show, ar_linear) == - "AverageRateCurve{LinearFunctionData}(LinearFunctionData(3.0, 2.0), 1.0, nothing)" - @test sprint(show, "text/plain", ar_linear) == - "AverageRateCurve where initial value is 1.0, average rate function f is: f(x) = 3.0 x + 2.0" - - ar_piecewise = AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - ar_piecewise_no_initial = - AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), nothing) - @test ar_piecewise isa AverageRateCurve{PiecewiseStepData} - @test ar_piecewise_no_initial isa AverageRateCurve{PiecewiseStepData} - @test get_function_data(ar_piecewise) == PiecewiseStepData([1, 3, 5], [3, 2.6]) - @test get_initial_input(ar_piecewise) == 6 - @test InputOutputCurve(ar_piecewise) == - InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test IncrementalCurve(ar_piecewise) == - IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - @test_throws ArgumentError InputOutputCurve(ar_piecewise_no_initial) - @test_throws ArgumentError IncrementalCurve(ar_piecewise_no_initial) - @test IS.is_cost_alias(ar_piecewise) == IS.is_cost_alias(typeof(ar_piecewise)) == true - @test repr(ar_piecewise) == sprint(show, ar_piecewise) == - "PiecewiseAverageCurve(6.0, [1.0, 3.0, 5.0], [3.0, 2.6])" - @test sprint(show, "text/plain", ar_piecewise) == - "PiecewiseAverageCurve where initial value is 6.0, average rate function f is: f(x) =\n 3.0 for x in [1.0, 3.0)\n 2.6 for x in [3.0, 5.0)" - - # Serialization round trip - curves_by_type = [ # typeof() gives parameterized types - (io_quadratic, InputOutputCurve), - (io_linear, InputOutputCurve), - (io_piecewise, InputOutputCurve), - (inc_linear, IncrementalCurve), - (inc_piecewise, IncrementalCurve), - (ar_linear, AverageRateCurve), - (ar_piecewise, AverageRateCurve), - (inc_linear_no_initial, IncrementalCurve), - (inc_piecewise_no_initial, IncrementalCurve), - (ar_linear_no_initial, AverageRateCurve), - (ar_piecewise_no_initial, AverageRateCurve), - ] - for (curve, curve_type) in curves_by_type - @test IS.serialize(curve) isa AbstractDict - @test IS.deserialize(curve_type, IS.serialize(curve)) == curve - end - - @test zero(PSY.ValueCurve) == InputOutputCurve(LinearFunctionData(0, 0)) -end - -@testset "Test ValueCurve type conversion constructors" begin - @test InputOutputCurve(QuadraticFunctionData(3, 2, 1), 1) == - InputOutputCurve(QuadraticFunctionData(3, 2, 1), 1.0) - @test IncrementalCurve(LinearFunctionData(6, 2), 1) == - IncrementalCurve(LinearFunctionData(6, 2), 1.0) - @test AverageRateCurve(LinearFunctionData(3, 2), 1) == - AverageRateCurve(LinearFunctionData(3, 2), 1.0) -end - -@testset "Test cost aliases" begin - lc = LinearCurve(3.0, 5.0) - @test lc == InputOutputCurve(LinearFunctionData(3.0, 5.0)) - @test LinearCurve(3.0) == InputOutputCurve(LinearFunctionData(3.0, 0.0)) - @test get_proportional_term(lc) == 3.0 - @test get_constant_term(lc) == 5.0 - - qc = QuadraticCurve(1.0, 2.0, 18.0) - @test qc == InputOutputCurve(QuadraticFunctionData(1.0, 2.0, 18.0)) - @test get_quadratic_term(qc) == 1.0 - @test get_proportional_term(qc) == 2.0 - @test get_constant_term(qc) == 18.0 - - ppc = PiecewisePointCurve([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)]) - @test ppc == - InputOutputCurve(PiecewiseLinearData([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)])) - @test get_points(ppc) == [(x = 1.0, y = 20.0), (x = 2.0, y = 24.0), (x = 3.0, y = 30.0)] - @test get_x_coords(ppc) == [1.0, 2.0, 3.0] - @test get_y_coords(ppc) == [20.0, 24.0, 30.0] - @test get_slopes(ppc) == [4.0, 6.0] +@testset "Test scope-sensitive printing of IS cost functions" begin + # Make sure the aliases get registered properly + @test sprint(show, "text/plain", QuadraticCurve) == + "QuadraticCurve (alias for InputOutputCurve{QuadraticFunctionData})" - pic = PiecewiseIncrementalCurve(20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) - @test pic == IncrementalCurve(PiecewiseStepData([1.0, 2.0, 3.0], [4.0, 6.0]), 20.0) - @test get_x_coords(pic) == [1.0, 2.0, 3.0] - @test get_slopes(pic) == [4.0, 6.0] - - pac = PiecewiseAverageCurve(20.0, [1.0, 2.0, 3.0], [12.0, 10.0]) - @test pac == AverageRateCurve(PiecewiseStepData([1.0, 2.0, 3.0], [12.0, 10.0]), 20.0) - @test get_x_coords(pac) == [1.0, 2.0, 3.0] - @test get_average_rates(pac) == [12.0, 10.0] -end - -@testset "Test input_at_zero" begin - iaz = 1234.5 - pwinc_without_iaz = - IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, nothing) - pwinc_with_iaz = IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, iaz) - all_without_iaz = [ - InputOutputCurve(QuadraticFunctionData(3, 2, 1), nothing), - InputOutputCurve(LinearFunctionData(2, 1), nothing), - InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), nothing), - IncrementalCurve(LinearFunctionData(6, 2), 1.0, nothing), - pwinc_without_iaz, - AverageRateCurve(LinearFunctionData(3, 2), 1.0, nothing), - AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, nothing), - ] - all_with_iaz = [ - InputOutputCurve(QuadraticFunctionData(3, 2, 1), iaz), - InputOutputCurve(LinearFunctionData(2, 1), iaz), - InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), iaz), - IncrementalCurve(LinearFunctionData(6, 2), 1.0, iaz), - pwinc_with_iaz, - AverageRateCurve(LinearFunctionData(3, 2), 1.0, iaz), - AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, iaz), - ] - - # Alias constructors - @test PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0]) == - pwinc_with_iaz - - # Getters and printouts - for (without_iaz, with_iaz) in zip(all_without_iaz, all_with_iaz) - @test get_input_at_zero(without_iaz) === nothing - @test get_input_at_zero(with_iaz) == iaz - @test occursin(string(iaz), repr(with_iaz)) - @test sprint(show, with_iaz) == repr(with_iaz) - @test occursin(string(iaz), sprint(show, "text/plain", with_iaz)) - end - - @test repr(pwinc_with_iaz) == sprint(show, pwinc_with_iaz) == - "PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" - @test sprint(show, "text/plain", pwinc_with_iaz) == - "PiecewiseIncrementalCurve where value at zero is 1234.5, initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" - - # Preserved under conversion - for without_iaz in Iterators.flatten(all_conversions.(all_without_iaz)) - @test get_input_at_zero(without_iaz) === nothing - end - for with_iaz in Iterators.flatten(all_conversions.(all_with_iaz)) - @test get_input_at_zero(with_iaz) == iaz - end -end - -@testset "Test CostCurve and FuelCurve" begin - cc = CostCurve(InputOutputCurve(PSY.QuadraticFunctionData(1, 2, 3))) - fc = FuelCurve(InputOutputCurve(PSY.QuadraticFunctionData(1, 2, 3)), 4.0) - # TODO also test fuel curves with time series - - @test get_value_curve(cc) == InputOutputCurve(PSY.QuadraticFunctionData(1, 2, 3)) - @test get_value_curve(fc) == InputOutputCurve(PSY.QuadraticFunctionData(1, 2, 3)) - @test get_fuel_cost(fc) == 4 - - @test IS.serialize(cc) isa AbstractDict - @test IS.serialize(fc) isa AbstractDict - @test IS.deserialize(CostCurve, IS.serialize(cc)) == cc - @test IS.deserialize(FuelCurve, IS.serialize(fc)) == fc - - @test zero(cc) == CostCurve(InputOutputCurve(PSY.LinearFunctionData(0.0, 0.0))) - @test zero(CostCurve) == CostCurve(InputOutputCurve(PSY.LinearFunctionData(0.0, 0.0))) - @test zero(fc) == - FuelCurve(InputOutputCurve(PSY.LinearFunctionData(0.0, 0.0)), 0.0) - @test zero(FuelCurve) == - FuelCurve(InputOutputCurve(PSY.LinearFunctionData(0.0, 0.0)), 0.0) - - @test repr(cc) == sprint(show, cc) == - "CostCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), UnitSystem.NATURAL_UNITS = 2, LinearCurve(0.0, 0.0))" - @test repr(fc) == sprint(show, fc) == - "FuelCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), UnitSystem.NATURAL_UNITS = 2, 4.0, LinearCurve(0.0, 0.0))" - @test sprint(show, "text/plain", cc) == - sprint(show, "text/plain", cc; context = :compact => false) == - "CostCurve:\n value_curve: QuadraticCurve (a type of InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: UnitSystem.NATURAL_UNITS = 2\n vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" + # Make sure there are no IS-related prefixes in the printouts + fc = FuelCurve(InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)), 4.0) @test sprint(show, "text/plain", fc) == sprint(show, "text/plain", fc; context = :compact => false) == "FuelCurve:\n value_curve: QuadraticCurve (a type of InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: UnitSystem.NATURAL_UNITS = 2\n fuel_cost: 4.0\n vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" - @test sprint(show, "text/plain", cc; context = :compact => true) == - "CostCurve with power_units UnitSystem.NATURAL_UNITS = 2, 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" @test sprint(show, "text/plain", fc; context = :compact => true) == "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" - - @test get_power_units(cc) == UnitSystem.NATURAL_UNITS - @test get_power_units(fc) == UnitSystem.NATURAL_UNITS - @test get_power_units(CostCurve(zero(InputOutputCurve), UnitSystem.SYSTEM_BASE)) == - UnitSystem.SYSTEM_BASE - @test get_power_units(FuelCurve(zero(InputOutputCurve), UnitSystem.DEVICE_BASE, 1.0)) == - UnitSystem.DEVICE_BASE - - @test get_vom_cost(cc) == LinearCurve(0.0) - @test get_vom_cost(fc) == LinearCurve(0.0) - @test get_vom_cost(CostCurve(zero(InputOutputCurve), LinearCurve(1.0, 2.0))) == - LinearCurve(1.0, 2.0) - @test get_vom_cost(FuelCurve(zero(InputOutputCurve), 1.0, LinearCurve(3.0, 4.0))) == - LinearCurve(3.0, 4.0) end @testset "Test market bid cost interface" begin