diff --git a/test/test_linear_curve.jl b/test/test_linear_curve.jl index dfcca1e..31566e5 100644 --- a/test/test_linear_curve.jl +++ b/test/test_linear_curve.jl @@ -296,6 +296,53 @@ end end end + @testset "CostCurve{LinearCurve} unit-system invariance" begin + # Check that if we provide the same physical cost in different unit systems, + # we get the same objective coefficient. + time_steps = 1:3 + system_base = 100.0 + device_base = 50.0 + rate = 30.0 # $/MWh, the physical cost rate represented in all three curves + + curve_variants = ( + NATURAL_UNITS = IS.CostCurve(IS.LinearCurve(rate), IS.NaturalUnit()), + SYSTEM_BASE = IS.CostCurve( + IS.LinearCurve(rate * system_base), + IS.SystemBaseUnit(), + ), + DEVICE_BASE = IS.CostCurve( + IS.LinearCurve(rate * device_base), + IS.DeviceBaseUnit(), + ), + ) + + coefs = Dict{Symbol, Float64}() + for (label, cost_curve) in pairs(curve_variants) + device = make_mock_thermal("gen1"; base_power = device_base) + container = setup_container_with_variables( + time_steps, device; resolution = Dates.Hour(1), + ) + InfrastructureOptimizationModels.add_variable_cost_to_objective!( + container, + TestActivePowerVariable, + device, + cost_curve, + TestLinearFormulation, + ) + coefs[label] = get_objective_coefficient( + container, + TestActivePowerVariable, + MockThermalGen, + "gen1", + first(time_steps), + ) + end + + @test coefs[:NATURAL_UNITS] ≈ coefs[:SYSTEM_BASE] atol = 1e-10 + @test coefs[:NATURAL_UNITS] ≈ coefs[:DEVICE_BASE] atol = 1e-10 + @test coefs[:NATURAL_UNITS] ≈ rate * system_base atol = 1e-10 + end + @testset "add_variable_cost_to_objective! with FuelCurve{LinearCurve}" begin time_steps = 1:3 device = make_mock_thermal("gen1"; base_power = 50.0) diff --git a/test/test_quadratic_curve.jl b/test/test_quadratic_curve.jl index 671d26b..48aefb8 100644 --- a/test/test_quadratic_curve.jl +++ b/test/test_quadratic_curve.jl @@ -387,6 +387,69 @@ end end end + @testset "CostCurve{QuadraticCurve} unit-system invariance" begin + # Check that if we provide the same physical cost in different unit systems, + # we get the same objective coefficient. + time_steps = 1:3 + system_base = 100.0 + device_base = 50.0 + a = 0.5 # $/MW^2h, physical quadratic rate + b = 20.0 # $/MWh, physical linear rate + + curve_variants = ( + NATURAL_UNITS = IS.CostCurve(IS.QuadraticCurve(a, b, 0.0), IS.NaturalUnit()), + SYSTEM_BASE = IS.CostCurve( + IS.QuadraticCurve(a * system_base^2, b * system_base, 0.0), + IS.SystemBaseUnit(), + ), + DEVICE_BASE = IS.CostCurve( + IS.QuadraticCurve(a * device_base^2, b * device_base, 0.0), + IS.DeviceBaseUnit(), + ), + ) + + lin_coefs = Dict{Symbol, Float64}() + quad_coefs = Dict{Symbol, Float64}() + for (label, cost_curve) in pairs(curve_variants) + device = make_mock_thermal( + "gen1"; + base_power = device_base, + limits = (min = 0.0, max = 100.0), + ) + container = setup_quadratic_test_container( + time_steps, device; resolution = Dates.Hour(1), + ) + InfrastructureOptimizationModels.add_variable_cost_to_objective!( + container, + TestActivePowerVariable, + device, + cost_curve, + TestQuadraticFormulation, + ) + lin_coefs[label] = get_objective_coefficient( + container, + TestActivePowerVariable, + MockThermalGen, + "gen1", + first(time_steps), + ) + quad_coefs[label] = get_objective_quadratic_coefficient( + container, + TestActivePowerVariable, + MockThermalGen, + "gen1", + first(time_steps), + ) + end + + @test lin_coefs[:NATURAL_UNITS] ≈ lin_coefs[:SYSTEM_BASE] atol = 1e-10 + @test lin_coefs[:NATURAL_UNITS] ≈ lin_coefs[:DEVICE_BASE] atol = 1e-10 + @test quad_coefs[:NATURAL_UNITS] ≈ quad_coefs[:SYSTEM_BASE] atol = 1e-10 + @test quad_coefs[:NATURAL_UNITS] ≈ quad_coefs[:DEVICE_BASE] atol = 1e-10 + @test lin_coefs[:NATURAL_UNITS] ≈ b * system_base atol = 1e-10 + @test quad_coefs[:NATURAL_UNITS] ≈ a * system_base^2 atol = 1e-10 + end + @testset "quadratic fallback to linear when quadratic term is zero" begin time_steps = 1:2 device =