Skip to content

Commit

Permalink
Merge pull request #1178 from NREL-Sienna/ps/quadratic_curves
Browse files Browse the repository at this point in the history
fix: Adding support for quadratic functions using the table data parser
  • Loading branch information
jd-lara authored Aug 28, 2024
2 parents c2335d7 + 319f8a9 commit deb98f3
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/PowerSystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ export get_compression_settings
export CompressionSettings
export CompressionTypes

# Parsing functions
export create_poly_cost

#export make_time_series
export get_bus_numbers
export get_name
Expand Down
15 changes: 15 additions & 0 deletions src/descriptors/power_system_inputs.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,21 @@
"description": "Heat required to startup from cold",
"default_value": null
},
{
"name": "heat_rate_a0",
"description": "Heat rate constant term",
"default_value": null
},
{
"name": "heat_rate_a1",
"description": "Heat rate proportional term",
"default_value": null
},
{
"name": "heat_rate_a2",
"description": "Heat rate quadratic term",
"default_value": null
},
{
"name": "heat_rate_avg_0",
"description": "Heat rate Average 0 TODO",
Expand Down
51 changes: 49 additions & 2 deletions src/parsers/power_system_table_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -823,8 +823,16 @@ function make_cost(
) where {T <: ThermalGen}
fuel_price = gen.fuel_price / 1000.0

cost_pairs = get_cost_pairs(gen, cost_colnames)
var_cost, fixed = create_pwinc_cost(cost_pairs)
# We check if there is any Quadratic or Linear Data defined. If not we fall back to create PiecewiseIncrementalCurve
quadratic_fields = (gen.heat_rate_a0, gen.heat_rate_a1, gen.heat_rate_a2)

if any(field -> field != nothing, quadratic_fields)
var_cost, fixed =
create_poly_cost(gen, ["heat_rate_a0", "heat_rate_a1", "heat_rate_a2"])
else
cost_pairs = get_cost_pairs(gen, cost_colnames)
var_cost, fixed = create_pwinc_cost(cost_pairs)
end

startup_cost, shutdown_cost = calculate_uc_cost(data, gen, fuel_price)

Expand Down Expand Up @@ -956,6 +964,45 @@ function create_pwl_cost(
return var_cost
end

"""
create_poly_cost(gen, cost_colnames)
Return a Polynomial function cost based on the coeffiecients provided on gen.
Three supported cases,
1. If three values are passed then we have data looking like: `a2 * x^2 + a1 * x + a0`,
2. If `a1` and `a0` are passed then we have data looking like: `a1 * x + a0`,
3. If only `a1` is passed then we have data looking like: `a1 * x`.
"""
function create_poly_cost(
gen, cost_colnames,
)
fixed_cost = 0.0
parse_maybe_nothing(x) = isnothing(x) ? nothing : tryparse(Float64, x)
a2 = parse_maybe_nothing(getfield(gen, Symbol("heat_rate_a2")))
a1 = parse_maybe_nothing(getfield(gen, Symbol("heat_rate_a1")))
a0 = parse_maybe_nothing(getfield(gen, Symbol("heat_rate_a0")))

if !isnothing(a2) && (isnothing(a1) || isnothing(a0))
throw(
DataFormatError(
"All coefficients must be passed if quadratic term is passed.",
),
)
end

if !any(isnothing.([a2, a1, a0]))
@debug "QuadraticCurve created for $(gen.name)"
return QuadraticCurve(a2, a1, a0), fixed_cost
end
if all(isnothing.([a2, a0])) && !isnothing(a1)
@debug "LinearCurve created for $(gen.name)"
return LinearCurve(a1), fixed_cost
end
@debug "LinearCurve created for $(gen.name)"
return LinearCurve(a1, a0), fixed_cost
end

function create_pwinc_cost(
cost_pairs,
)
Expand Down
82 changes: 82 additions & 0 deletions test/test_power_system_table_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,85 @@ end
g = get_components(ThermalStandard, sys)
@test get_variable.(get_operation_cost.(g)) == get_variable.(get_operation_cost.(g))
end

@testset "Test create_poly_cost function" begin
cost_colnames = ["heat_rate_a0", "heat_rate_a1", "heat_rate_a2"]

# Coefficients for a CC using natural gas
a2 = -0.000531607
a1 = 0.060554675
a0 = 8.951100118

# First test that return quadratic if all coefficients are provided.
# We convert the coefficients to string to mimic parsing from csv
example_generator = (
name = "test-gen",
heat_rate_a0 = string(a0),
heat_rate_a1 = string(a1),
heat_rate_a2 = string(a2),
)
cost_curve, fixed_cost = create_poly_cost(example_generator, cost_colnames)
@assert cost_curve isa QuadraticCurve
@assert isapprox(get_quadratic_term(cost_curve), a2, atol = 0.01)
@assert isapprox(get_proportional_term(cost_curve), a1, atol = 0.01)
@assert isapprox(get_constant_term(cost_curve), a0, atol = 0.01)

# Test return linear with both proportional and constant term
example_generator = (
name = "test-gen",
heat_rate_a0 = string(a0),
heat_rate_a1 = string(a1),
heat_rate_a2 = nothing,
)
cost_curve, fixed_cost = create_poly_cost(example_generator, cost_colnames)
@assert cost_curve isa LinearCurve
@assert isapprox(get_proportional_term(cost_curve), a1, atol = 0.01)
@assert isapprox(get_constant_term(cost_curve), a0, atol = 0.01)

# Test return linear with just proportional term
example_generator = (
name = "test-gen",
heat_rate_a0 = nothing,
heat_rate_a1 = string(a1),
heat_rate_a2 = nothing,
)
cost_curve, fixed_cost = create_poly_cost(example_generator, cost_colnames)
@assert cost_curve isa LinearCurve
@assert isapprox(get_proportional_term(cost_curve), a1, atol = 0.01)

# Test raises error if a2 is passed but other coefficients are nothing
example_generator = (
name = "test-gen",
heat_rate_a0 = nothing,
heat_rate_a1 = nothing,
heat_rate_a2 = string(a2),
)
@test_throws IS.DataFormatError create_poly_cost(example_generator, cost_colnames)
example_generator = (
name = "test-gen",
heat_rate_a0 = nothing,
heat_rate_a1 = string(a1),
heat_rate_a2 = string(a2),
)
@test_throws IS.DataFormatError create_poly_cost(example_generator, cost_colnames)
example_generator = (
name = "test-gen",
heat_rate_a0 = string(a0),
heat_rate_a1 = nothing,
heat_rate_a2 = string(a2),
)
@test_throws IS.DataFormatError create_poly_cost(example_generator, cost_colnames)

# Test that it works with zero proportional and constant term
example_generator = (
name = "test-gen",
heat_rate_a0 = string(0.0),
heat_rate_a1 = string(0.0),
heat_rate_a2 = string(a2),
)
cost_curve, fixed_cost = create_poly_cost(example_generator, cost_colnames)
@assert cost_curve isa QuadraticCurve
@assert isapprox(get_quadratic_term(cost_curve), a2, atol = 0.01)
@assert isapprox(get_proportional_term(cost_curve), 0.0, atol = 0.01)
@assert isapprox(get_constant_term(cost_curve), 0.0, atol = 0.01)
end

0 comments on commit deb98f3

Please sign in to comment.