diff --git a/docs/src/format.md b/docs/src/format.md index ce5fc2a..7707b48 100644 --- a/docs/src/format.md +++ b/docs/src/format.md @@ -90,6 +90,7 @@ This section describes all generators in the system, including thermal units, re | `Initial power (MW)` | Amount of power the generator at time step `-1`, immediately before the planning horizon starts. | Required | N | `Must run?` | If `true`, the generator should be committed, even if that is not economical (Boolean). | `false` | Y | `Reserve eligibility` | List of reserve products this generator is eligibe to provide. By default, the generator is not eligible to provide any reserves. | `[]` | N +| `Commitment status` | List of commitment status over the time horizon. At time `t`, if `true`, the generator must be commited at that time period; if `false`, the generator must not be commited at that time period. If `null` at time `t`, the generator's commitment status is then decided by the model. By default, the status is a list of `null` values. | `[null]` | Y #### Profiled Units @@ -151,7 +152,8 @@ Note that this curve also specifies the production limits. Specifically, the fir "Production cost curve ($)": [0.0, 0.0], "Initial status (h)": -100, "Initial power (MW)": 0, - "Reserve eligibility": ["r1", "r2"] + "Reserve eligibility": ["r1", "r2"], + "Commitment status": [true, false, null, true] }, "gen3": { "Bus": "b6", diff --git a/src/instance/read.jl b/src/instance/read.jl index 1fda33b..024e478 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -282,6 +282,12 @@ function _from_json(json; repair = true)::UnitCommitmentScenario initial_status *= time_multiplier end + # Read commitment status + commitment_status = scalar( + dict["Commitment status"], + default = Vector{Union{Bool,Nothing}}(nothing, T), + ) + unit = ThermalUnit( unit_name, bus, @@ -302,6 +308,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario initial_power, startup_categories, unit_reserves, + commitment_status, ) push!(bus.thermal_units, unit) for r in unit_reserves diff --git a/src/instance/structs.jl b/src/instance/structs.jl index bdf729f..15dfef4 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -47,6 +47,7 @@ mutable struct ThermalUnit initial_power::Union{Float64,Nothing} startup_categories::Vector{StartupCategory} reserves::Vector{Reserve} + commitment_status::Vector{Union{Bool,Nothing}} end mutable struct TransmissionLine diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index 06b18a2..83cf543 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -61,6 +61,7 @@ function _add_unit_dispatch!( sc, ) _add_startup_shutdown_limit_eqs!(model, g, sc) + _add_commitment_status_eqs!(model, g, sc) return end @@ -270,6 +271,26 @@ function _add_min_uptime_downtime_eqs!( end end +function _add_commitment_status_eqs!( + model::JuMP.Model, + g::ThermalUnit, + sc::UnitCommitmentScenario, +)::Nothing + is_on = model[:is_on] + T = model[:instance].time + eq_commitment_status = _init(model, :eq_commitment_status) + for t in 1:T + # Fix commitment status + if g.commitment_status[t] !== nothing + eq_commitment_status[sc.name, g.name, t] = @constraint( + model, + is_on[g.name, t] == (g.commitment_status[t] ? 1.0 : 0.0) + ) + end + end + return +end + function _add_net_injection_eqs!( model::JuMP.Model, g::ThermalUnit, diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index 78c3d78..ccde032 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -153,3 +153,14 @@ end @test second_pu.capacity == [120.0 for t in 1:4] @test sc.profiled_units_by_name["g8"].name == "g8" end + +@testset "read_benchmark commitmemt-status" begin + instance = UnitCommitment.read("$FIXTURES/case14-fixed-status.json.gz") + sc = instance.scenarios[1] + + @test sc.thermal_units[1].commitment_status == [nothing for t in 1:4] + @test sc.thermal_units[2].commitment_status == [true for t in 1:4] + @test sc.thermal_units[4].commitment_status == [false for t in 1:4] + @test sc.thermal_units[6].commitment_status == + [false, nothing, true, nothing] +end