diff --git a/Project.toml b/Project.toml index ed92579..f65fc12 100644 --- a/Project.toml +++ b/Project.toml @@ -39,9 +39,9 @@ DocStringExtensions = "~0.8, ~0.9" InfrastructureSystems = "3" InteractiveUtils = "1.11.0" JuMP = "^1.28" +PowerSystems = "5.10" PowerFlows = "0.21" PowerNetworkMatrices = "^0.24" -PowerSystems = "5.3" PrettyTables = "3" ProgressMeter = "1.11.0" TimerOutputs = "~0.5" diff --git a/src/common_models/market_bid_plumbing.jl b/src/common_models/market_bid_plumbing.jl index a09e23e..7a23895 100644 --- a/src/common_models/market_bid_plumbing.jl +++ b/src/common_models/market_bid_plumbing.jl @@ -571,6 +571,9 @@ IOM.get_base_power(c::PSY.Component) = PSY.get_base_power(c, PSY.NU) IOM.get_operation_cost(c::PSY.Component) = PSY.get_operation_cost(c) IOM.get_must_run(c::PSY.Component) = PSY.get_must_run(c) IOM.get_active_power_limits(c::PSY.Component) = PSY.get_active_power_limits(c, PSY.SU) +# `RenewableGen` has no `active_power_limits` field: return (0.0, max_active_power) +IOM.get_active_power_limits(c::PSY.RenewableGen) = + (min = 0.0, max = PSY.get_max_active_power(c, PSY.SU)) IOM.get_max_active_power(c::PSY.Component) = PSY.get_max_active_power(c, PSY.SU) IOM.get_ramp_limits(c::PSY.Component) = PSY.get_ramp_limits(c, PSY.SU) IOM.get_start_up(op_cost) = PSY.get_start_up(op_cost) diff --git a/test/test_device_renewable_generation_constructors.jl b/test/test_device_renewable_generation_constructors.jl index 0047ead..e25a644 100644 --- a/test/test_device_renewable_generation_constructors.jl +++ b/test/test_device_renewable_generation_constructors.jl @@ -147,3 +147,35 @@ end IOM.ModelBuildStatus.BUILT @test solve!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED end + +@testset "Renewable with quadratic variable cost builds objective (issue #9)" begin + # Regression test: a RenewableDispatch with a quadratic cost curve used to call + # `PSY.get_active_power_limits`, but renewables only have a max active power field, + # not active power limits. + c_sys5_re = PSB.build_system(PSITestSystems, "c_sys5_re") + + quad_re = get_component(RenewableDispatch, c_sys5_re, "WindBusA") + base_cost = get_operation_cost(quad_re) + # Renewable ActivePowerVariable costs use OBJECTIVE_FUNCTION_NEGATIVE, so the negated + # objective term `-cost(p)` is convex (and free of the non-monotonicity warning) when + # the raw curve is concave, i.e. negative coefficients. + set_operation_cost!( + quad_re, + RenewableGenerationCost(; + variable = CostCurve(QuadraticCurve(-2.0, -1.0, 0.0)), + curtailment_cost = base_cost.curtailment_cost, + fixed = base_cost.fixed, + ), + ) + + device_model = DeviceModel(RenewableDispatch, RenewableFullDispatch) + model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_re) + # Before the fix this threw `ArgumentError: get_active_power_limits not implemented + # for RenewableDispatch`; now it constructs a quadratic objective. + mock_construct_device!(model, device_model) + psi_checkobjfun_test(model, GQEVF) + + # Direct check of the bridge that the issue is about. + @test IOM.get_active_power_limits(quad_re) == + (min = 0.0, max = PSY.get_max_active_power(quad_re, PSY.SU)) +end