Skip to content

Commit

Permalink
Merge pull request #1125 from NREL-Sienna/gks/add_costs_printing
Browse files Browse the repository at this point in the history
Add Pretty Cost Printing
  • Loading branch information
jd-lara authored Jun 14, 2024
2 parents c3fd9ae + 65f8bea commit 497d690
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 10 deletions.
26 changes: 26 additions & 0 deletions src/models/cost_functions/ValueCurves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,29 @@ IncrementalCurve(data::AverageRateCurve) = IncrementalCurve(InputOutputCurve(dat
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, " with function: ")
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 initial value is $(get_initial_input(curve))")
print(io, " and 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 initial value is $(get_initial_input(curve))")
print(io, " and average rate function f is: ")
show(IOContext(io, :compact => true), "text/plain", get_function_data(curve))
end
58 changes: 52 additions & 6 deletions src/models/cost_functions/cost_aliases.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# 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 and a
# constructor and methods to interact with it without having to think about `FunctionData`.
# Everything here is properly speaking mere syntactic sugar for the underlying
# 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, constant_term::Float64)
Expand All @@ -19,12 +23,20 @@ cost (i.e., constant average rate) or not.
"""
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))

function Base.show(io::IO, data::LinearCurve)
fd = get_function_data(data)
p, c = get_proportional_term(fd), get_constant_term(fd)
print(io, "$(typeof(data))($p, $c)")
end

"""
QuadraticCurve(quadratic_term::Float64, proportional_term::Float64, constant_term::Float64)
Expand All @@ -37,11 +49,19 @@ A quadratic input-output curve, may have nonzero no-load cost.
"""
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),
)

function Base.show(io::IO, data::QuadraticCurve)
fd = get_function_data(data)
q, p, c = get_quadratic_term(fd), get_proportional_term(fd), get_constant_term(fd)
print(io, "$(typeof(data))($q, $p, $c)")
end

"""
PiecewisePointCurve(points::Vector{Tuple{Float64, Float64}})
Expand All @@ -52,11 +72,17 @@ A piecewise linear curve specified by cost values at production points.
"""
const PiecewisePointCurve = InputOutputCurve{PiecewiseLinearData}

is_cost_alias(::Union{PiecewisePointCurve, Type{PiecewisePointCurve}}) = true

InputOutputCurve{PiecewiseLinearData}(points::Vector) =
InputOutputCurve(PiecewiseLinearData(points))

get_points(curve::PiecewisePointCurve) = get_points(get_function_data(curve))

# Here we manually circumvent the @NamedTuple{x::Float64, y::Float64} annotation, but we keep things looking like named tuples
Base.show(io::IO, data::PiecewisePointCurve) =
print(io, "$(typeof(data))([$(join(get_points(data), ", "))])")

"""
PiecewiseIncrementalCurve(initial_input::Float64, x_coords::Vector{Float64}, slopes::Vector{Float64})
Expand All @@ -71,11 +97,21 @@ have nonzero initial value.
"""
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)

get_slopes(curve::PiecewiseIncrementalCurve) = get_y_coords(get_function_data(curve))

function Base.show(io::IO, data::PiecewiseIncrementalCurve)
initial_input = get_initial_input(data)
fd = get_function_data(data)
x_coords = get_x_coords(fd)
slopes = get_slopes(data)
print(io, "$(typeof(data))($initial_input, $x_coords, $slopes)")
end

"""
PiecewiseAverageCurve(initial_input::Float64, x_coords::Vector{Float64}, slopes::Vector{Float64})
Expand All @@ -90,7 +126,17 @@ nonzero initial value.
"""
const PiecewiseAverageCurve = AverageRateCurve{PiecewiseStepData}

AverageRateCurve{PiecewiseStepData}(initial_input, x_coords::Vector, slopes::Vector) =
AverageRateCurve(PiecewiseStepData(x_coords, slopes), initial_input)
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)

function Base.show(io::IO, data::PiecewiseAverageCurve)
initial_input = get_initial_input(data)
fd = get_function_data(data)
x_coords = get_x_coords(fd)
y_coords = get_y_coords(fd)
print(io, "$(typeof(data))($initial_input, $x_coords, $y_coords)")
end

# TODO documentation, more getters, custom printing so it always shows the type alias (like Vector does)
# TODO documentation, more getters
32 changes: 32 additions & 0 deletions src/models/cost_functions/variable_cost.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,35 @@ 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::ProductionVariableCost) =
(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::ProductionVariableCost)
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
25 changes: 21 additions & 4 deletions src/utils/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,32 @@ function show_components_table(io::IO, sys::System; kwargs...)
end
end

function Base.summary(tech::DeviceParameter)
return "$(typeof(tech))"
function Base.summary(io::IO, tech::DeviceParameter)
print(io, "$(typeof(tech))")
end

function Base.summary(io::IO, data::OperationalCost)
field_msgs = []
for field_name in fieldnames(typeof(data))
val = getproperty(data, field_name)
# Only the most important fields
(val isa ProductionVariableCost) &&
push!(field_msgs, "$(field_name): $(typeof(val))")
(val isa TimeSeriesKey) &&
push!(field_msgs, "$(field_name): time series \"$(get_name(val))\"")
end
isempty(field_msgs) && return
print(io, "$(typeof(data)) composed of ")
join(io, field_msgs, ", ")
end

function Base.show(io::IO, ::MIME"text/plain", data::OperationalCost)
println(io, "$(typeof(data)): ")
print(io, "$(typeof(data)): ")
for field_name in fieldnames(typeof(data))
val = getproperty(data, field_name)
print(io, " $(field_name): $val\n")
val_printout =
replace(sprint(show, "text/plain", val; context = io), "\n" => "\n ")
print(io, "\n $(field_name): $val_printout")
end
end

Expand Down
51 changes: 51 additions & 0 deletions test/test_cost_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
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 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) with function: f(x) = 3.0 x^2 + 2.0 x + 1.0"

io_linear = InputOutputCurve(LinearFunctionData(2, 1))
@test io_linear isa InputOutputCurve{LinearFunctionData}
Expand All @@ -19,6 +24,11 @@
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 repr(io_linear) == sprint(show, io_linear) ==
"LinearCurve(2.0, 1.0)"
@test sprint(show, "text/plain", io_linear) ==
"LinearCurve (a type of InputOutputCurve) with function: f(x) = 2.0 x + 1.0"

io_piecewise = InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]))
@test io_piecewise isa InputOutputCurve{PiecewiseLinearData}
Expand All @@ -28,6 +38,11 @@
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 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) with function: 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)
Expand All @@ -42,6 +57,11 @@
AverageRateCurve(LinearFunctionData(3, 2), 1.0)
@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 repr(inc_linear) == sprint(show, inc_linear) ==
"IncrementalCurve{LinearFunctionData}(LinearFunctionData(6.0, 2.0), 1.0)"
@test sprint(show, "text/plain", inc_linear) ==
"IncrementalCurve where initial value is 1.0 and derivative function f is: f(x) = 6.0 x + 2.0"

inc_piecewise = IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0)
@test inc_piecewise isa IncrementalCurve{PiecewiseStepData}
Expand All @@ -51,6 +71,12 @@
InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]))
@test AverageRateCurve(inc_piecewise) ==
AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0)
@test PSY.is_cost_alias(inc_piecewise) == PSY.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 and 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)
Expand All @@ -65,6 +91,11 @@
IncrementalCurve(LinearFunctionData(6, 2), 1.0)
@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 repr(ar_linear) == sprint(show, ar_linear) ==
"AverageRateCurve{LinearFunctionData}(LinearFunctionData(3.0, 2.0), 1.0)"
@test sprint(show, "text/plain", ar_linear) ==
"AverageRateCurve where initial value is 1.0 and average rate function f is: f(x) = 3.0 x + 2.0"

ar_piecewise = AverageRateCurve(PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0)
@test get_function_data(ar_piecewise) == PiecewiseStepData([1, 3, 5], [3, 2.6])
Expand All @@ -73,6 +104,11 @@
InputOutputCurve(PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]))
@test IncrementalCurve(ar_piecewise) ==
IncrementalCurve(PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0)
@test PSY.is_cost_alias(ar_piecewise) == PSY.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 and 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
Expand Down Expand Up @@ -126,6 +162,21 @@ end
@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, 0.0)"
@test repr(fc) == sprint(show, fc) ==
"FuelCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), UnitSystem.NATURAL_UNITS = 2, 4.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) with function: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: UnitSystem.NATURAL_UNITS = 2\n vom_cost: 0.0"
@test sprint(show, "text/plain", fc) ==
sprint(show, "text/plain", fc; context = :compact => false) ==
"FuelCurve:\n value_curve: QuadraticCurve (a type of InputOutputCurve) with function: 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: 0.0"
@test sprint(show, "text/plain", cc; context = :compact => true) ==
"CostCurve with power_units UnitSystem.NATURAL_UNITS = 2, vom_cost 0.0, and value_curve:\n QuadraticCurve (a type of InputOutputCurve) with function: 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 0.0, and value_curve:\n QuadraticCurve (a type of InputOutputCurve) with function: 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)) ==
Expand Down

0 comments on commit 497d690

Please sign in to comment.