diff --git a/docs/src/concept_reference/number_of_units.md b/docs/src/concept_reference/number_of_units.md index 3ca6e8ad01..5d009ec972 100644 --- a/docs/src/concept_reference/number_of_units.md +++ b/docs/src/concept_reference/number_of_units.md @@ -1,3 +1,3 @@ Defines how many members a certain [unit](@ref) object represents. Typically this parameter takes a binary (UC) or integer (clustered UC) value. Together with the [unit\_availability\_factor](@ref) and [units\_unavailable](@ref), this will determine the maximum number of members that can be online at any given time. (Thus restricting the [units\_on](@ref) variable). It is possible to allow the model to increase the `number_of_units` itself, through [Investment Optimization](@ref). It is also possible to schedule maintenance outages using [outage\_variable\_type](@ref) and [scheduled\_outage\_duration](@ref). -The default value for this parameter is 1. +The default value for this parameter is 1. The default value is 0 when [candidate\_units](@ref) has been defined for the unit in question. diff --git a/src/constraints/constraint_units_available.jl b/src/constraints/constraint_units_available.jl index 8f2f6d357a..e33f56ea2b 100644 --- a/src/constraints/constraint_units_available.jl +++ b/src/constraints/constraint_units_available.jl @@ -53,7 +53,10 @@ function _build_constraint_units_available(m, u, s, t) init=0, ) <= - + number_of_units(m; unit=u, stochastic_scenario=s, t=t) + # Change the default number of units so that it is zero when candidate units are present + # and otherwise 1. + + ifelse(is_candidate(unit=u), number_of_units(m; unit=u, stochastic_scenario=s, t=t, _default=0), + number_of_units(m; unit=u, stochastic_scenario=s, t=t) ) - units_unavailable(m; unit=u, stochastic_scenario=s, t=t) ) end diff --git a/templates/spineopt_template.json b/templates/spineopt_template.json index 81db9e946b..f1456f22cb 100644 --- a/templates/spineopt_template.json +++ b/templates/spineopt_template.json @@ -289,7 +289,7 @@ ["unit", "is_renewable", false, "boolean_value_list", "Whether the unit is renewable - used in the minimum renewable generation constraint within the Benders master problem"], ["unit", "min_down_time", null, null, "Minimum downtime of a `unit` after it shuts down."], ["unit", "min_up_time", null, null, "Minimum uptime of a `unit` after it starts up."], - ["unit", "number_of_units", 1.0, null, "Denotes the number of 'sub units' aggregated to form the modelled `unit`."], + ["unit", "number_of_units", 1.0, null, "Denotes the number of 'sub units' aggregated to form the modelled `unit`. The default value becomes zero if `candidate_units` has been defined."], ["unit", "online_variable_type", "unit_online_variable_type_linear", "unit_online_variable_type_list", "A selector for how the `units_on` variable is represented within the model."], ["unit", "outage_variable_type", "unit_online_variable_type_none", "unit_online_variable_type_list", "Determines whether the outage variable is integer or continuous or none(no optimisation of maintenance outages)."], ["unit", "scheduled_outage_duration", null, null, "Specifies the amount of time a unit must be out of service for maintenance as a single block over the course of the optimisation window"], diff --git a/test/constraints/constraint_unit.jl b/test/constraints/constraint_unit.jl index d1cf504da0..c4725d464d 100644 --- a/test/constraints/constraint_unit.jl +++ b/test/constraints/constraint_unit.jl @@ -155,11 +155,12 @@ function test_constraint_units_available() end end end + function test_constraint_units_available_units_unavailable() @testset "constraint_units_available_units_unavailable" begin url_in = _test_constraint_unit_setup() number_of_units = 4 - candidate_units = 3 + candidate_units = 3 units_unavailable = 1 unit_availability_factor = 0.5 object_parameter_values = [ @@ -191,6 +192,40 @@ function test_constraint_units_available_units_unavailable() @test _is_constraint_equal(observed_con, expected_con) end end + @testset "constraint_units_available_units_unavailable_default" begin + url_in = _test_constraint_unit_setup() + candidate_units = 3 + number_of_units_when_candidates_units = 0 + units_unavailable = 1 + unit_availability_factor = 0.5 + object_parameter_values = [ + ["unit", "unit_ab", "candidate_units", candidate_units], + ["unit", "unit_ab", "units_unavailable", units_unavailable], + ["unit", "unit_ab", "unit_availability_factor", unit_availability_factor], + ] + relationships = [ + ["unit__investment_temporal_block", ["unit_ab", "hourly"]], + ["unit__investment_stochastic_structure", ["unit_ab", "stochastic"]], + ] + SpineInterface.import_data(url_in; relationships=relationships, object_parameter_values=object_parameter_values) + m = run_spineopt(url_in; log_level=0, optimize=false) + var_units_on = m.ext[:spineopt].variables[:units_on] + var_units_invested_available = m.ext[:spineopt].variables[:units_invested_available] + constraint = m.ext[:spineopt].constraints[:units_available] + @test length(constraint) == 2 + scenarios = (stochastic_scenario(:parent), stochastic_scenario(:child)) + time_slices = time_slice(m; temporal_block=temporal_block(:hourly)) + @testset for (s, t) in zip(scenarios, time_slices) + key = (unit(:unit_ab), s, t) + var_u_on = var_units_on[key...] + var_u_inv_av = var_units_invested_available[key...] + expected_con = @build_constraint(var_u_on <= number_of_units_when_candidates_units + var_u_inv_av - units_unavailable) + con_key = (unit(:unit_ab), s, t) + con = constraint[con_key...] + observed_con = constraint_object(con) + @test _is_constraint_equal(observed_con, expected_con) + end + end end function test_constraint_unit_state_transition()