diff --git a/src/PowerSystems.jl b/src/PowerSystems.jl index 79097c2c34..66fa9f00ac 100644 --- a/src/PowerSystems.jl +++ b/src/PowerSystems.jl @@ -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 diff --git a/src/descriptors/power_system_inputs.json b/src/descriptors/power_system_inputs.json index 7d114a1c4c..3a2ac9220e 100644 --- a/src/descriptors/power_system_inputs.json +++ b/src/descriptors/power_system_inputs.json @@ -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", diff --git a/src/parsers/power_system_table_data.jl b/src/parsers/power_system_table_data.jl index 7c9ea3f464..39b2340811 100644 --- a/src/parsers/power_system_table_data.jl +++ b/src/parsers/power_system_table_data.jl @@ -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) @@ -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, ) diff --git a/test/test_power_system_table_data.jl b/test/test_power_system_table_data.jl index fc668cf0c8..04b3fac1ff 100644 --- a/test/test_power_system_table_data.jl +++ b/test/test_power_system_table_data.jl @@ -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