Skip to content
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

Initial refactor of cost functions #1071

Merged
merged 50 commits into from
Apr 30, 2024
Merged

Initial refactor of cost functions #1071

merged 50 commits into from
Apr 30, 2024

Conversation

jd-lara
Copy link
Member

@jd-lara jd-lara commented Mar 8, 2024

@GabrielKS this is a first pass at a rewrite of all the costs associated with different unit types and the market costs.

@jd-lara jd-lara requested a review from claytonpbarrows March 8, 2024 16:07
src/data_format_conversions.jl Outdated Show resolved Hide resolved
src/models/cost_functions/ValueCurves.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
@rodrigomha
Copy link
Contributor

Do we have methods to unpack the points of PieceWisePointCurves? Or any other InputOutput curve?

We should have a method that wraps:

power_cost_curve = PiecewisePointCurve([(1.0, 1.0), (2.0, 3.0), (3.0, 6.0), (4.0, 10.0), (5.0, 15.0)])
input_points, output_points = (zip(get_points(power_cost_curve)...) .|> collect)

Also, are we expecting users to use the get_points method? I like the presentation of the get_points when you print the curve, so I think it would be good to use this one to print in the REPL

julia> get_points(power_cost_curve)
5-element Vector{@NamedTuple{x::Float64, y::Float64}}:
 (x = 1.0, y = 1.0)
 (x = 2.0, y = 3.0)
 (x = 3.0, y = 6.0)
 (x = 4.0, y = 10.0)
 (x = 5.0, y = 15.0)

instead of:

julia> power_cost_curve
PiecewisePointCurve(PiecewiseLinearData(@NamedTuple{x::Float64, y::Float64}[(x = 1.0, y = 1.0), (x = 2.0, y = 3.0), (x = 3.0, y = 6.0), (x = 4.0, y = 10.0), (x = 5.0, y = 15.0)]))

@GabrielKS
Copy link
Collaborator

@rodrigomha I'm still ironing out the public interface (have taken a few stabs at this but I keep getting the domain-specific terminology wrong) but yes there should ultimately be a way to get all the x-coordinates and a way to get all the y-coordinates. In fact these functions exist at the underlying FunctionData level, I just need to figure out how best to wrap them. Definitely planning a more friendly println/etc. presentation as well.

@rodrigomha
Copy link
Contributor

@rodrigomha I'm still ironing out the public interface (have taken a few stabs at this but I keep getting the domain-specific terminology wrong) but yes there should ultimately be a way to get all the x-coordinates and a way to get all the y-coordinates. In fact these functions exist at the underlying FunctionData level, I just need to figure out how best to wrap them. Definitely planning a more friendly println/etc. presentation as well.

This is good, we can just make a wrapper of get_x_coords, in some form (like get_input_points) such that we can call it directly to the InputOutputCurve, rather than the function_data itself.

Regarding the presentation, we should discuss if using pretty_tables or other form makes sense here or not.

src/models/cost_function_timeseries.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
src/models/cost_functions/variable_cost.jl Outdated Show resolved Hide resolved
src/models/cost_functions/variable_cost.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
test/test_cost_functions.jl Outdated Show resolved Hide resolved
@jd-lara jd-lara assigned GabrielKS and unassigned jd-lara Apr 22, 2024
@jd-lara
Copy link
Member Author

jd-lara commented Apr 22, 2024

Do we have methods to unpack the points of PieceWisePointCurves? Or any other InputOutput curve?

We should have a method that wraps:

power_cost_curve = PiecewisePointCurve([(1.0, 1.0), (2.0, 3.0), (3.0, 6.0), (4.0, 10.0), (5.0, 15.0)])
input_points, output_points = (zip(get_points(power_cost_curve)...) .|> collect)

Also, are we expecting users to use the get_points method? I like the presentation of the get_points when you print the curve, so I think it would be good to use this one to print in the REPL

julia> get_points(power_cost_curve)
5-element Vector{@NamedTuple{x::Float64, y::Float64}}:
 (x = 1.0, y = 1.0)
 (x = 2.0, y = 3.0)
 (x = 3.0, y = 6.0)
 (x = 4.0, y = 10.0)
 (x = 5.0, y = 15.0)

instead of:

julia> power_cost_curve
PiecewisePointCurve(PiecewiseLinearData(@NamedTuple{x::Float64, y::Float64}[(x = 1.0, y = 1.0), (x = 2.0, y = 3.0), (x = 3.0, y = 6.0), (x = 4.0, y = 10.0), (x = 5.0, y = 15.0)]))

There is an open issue for the printing. We should merge this to continue with the implementation in PSI.

@GabrielKS
Copy link
Collaborator

Tracking issue is #1092, I'd love some substantive review of the added source files before I merge this.


PowerSystems.jl provides an extensive type hierarchy to explicitly express relationships between power production and cost. This lets the modeler represent cost functions as linear, quadratic, or piecewise input-output curves, potentially piecewise marginal heat rates, average heat rates, and more, as best fits the input data.

To represent a cost for a particular [`Component`](@ref), the modeler first chooses one of the variable cost representations in the table below. Then, they wrap this [`ProductionVariableCost`](@ref) in either a [`CostCurve`](@ref) to indicate a cost in currency or in a [`FuelCurve`](@ref) to indicate a cost per unit of fuel plus a fuel cost. Finally, the user creates a domain-specific [`OperationalCost`](@ref) that contains this variable cost as well as other costs that may exist in that domain, such as a fixed cost that is always incurred when the unit is on. For instance, we may have `RenewableGenerationCost(CostCurve(TODO), 0.0)` to represent the cost of a renewable unit that produces at TODO, or `ThermalGenerationCost(; variable = FuelCurve(TODO), fixed = TODO, start_up = TODO, shut_down = TODO)` to represent the cost of a thermal unit that produces at TODO. Below, we give the options for `ProductionVariableCost`s. Information on what domain-specific cost must be provided for a given component type can be found in that component type's documentation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the TODOs get completed?

op_cost["shut_down"],
)

# TODO implement remaining _convert_op_cost methods
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to happen?

@@ -1,3 +1,24 @@
# MarketBidCost has two variable costs, here we mean the incremental one
get_generation_variable_cost(cost::MarketBidCost) = get_incremental_offer_curves(cost)
# get_generation_variable_cost(cost::OperationalCost) = get_variable_cost(cost)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to stay?

"Variable Cost TimeSeriesKey"
incremental_offer_curves::Union{
Nothing,
IS.TimeSeriesKey,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this become with the new time series features (variable number of key/value pairs)?

get_generation_variable_cost(cost::MarketBidCost) = get_incremental_offer_curves(cost)
# get_generation_variable_cost(cost::OperationalCost) = get_variable_cost(cost)

function _validate_time_series_variable_cost(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we are consistent, but we have often followed a convention where checker functions that throw start with check or _check. validate or _validate functions usually return true/false. I'm sure that isn't documented, but we should do it. It helps the reader to know when an exception can get thrown.

power_units::UnitSystem = UnitSystem.NATURAL_UNITS
end

CostCurve(value_curve) = CostCurve(; value_curve)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it painful to implement all of these positional argument constructors? You could require that all cost structs be constructed with kwargs. It might help with consistency.

@@ -0,0 +1,34 @@
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it help users to make cost_functions a submodule? Maybe it helps searchability.

cost = PolynomialFunctionData(Dict((i, c / sys_mbase^i) for (i, c) in coeffs))
coeffs = Dict((i, c / sys_mbase^i) for (i, c) in coeffs)
quadratic_degrees = [2, 1, 0]
(keys(coeffs) <= Set(quadratic_degrees)) || throw(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this idiomatic Julia? It also seems more natural to me to say if error-condition throw or error-condition && throw. Asserts use this form.

@@ -1267,7 +1280,8 @@ function make_renewable_generator(
base_power = gen.base_mva
var_cost, fixed, fuel_cost =
calculate_variable_cost(data, gen, cost_colnames, base_power)
operation_cost = TwoPartCost(var_cost, fixed)
@assert fixed == 0 "RenewableGenerationCost cannot have a fixed cost, got $fixed with variable cost $varcost"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@assert fixed == 0 "RenewableGenerationCost cannot have a fixed cost, got $fixed with variable cost $varcost"
@assert fixed == 0 "RenewableGenerationCost cannot have a fixed cost, got $fixed with variable cost $var_cost"

for T in subtypes(PSY.OperationalCost)
isabstracttype(T) || (@test T(nothing) isa IS.InfrastructureSystemsType)
end
# TODO add concrete subtypes of ProductionVariableCost?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to get done?

@jd-lara jd-lara merged commit 48472d2 into psy4 Apr 30, 2024
1 of 8 checks passed
@jd-lara jd-lara deleted the jdgk/cost_functions branch May 21, 2024 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants