diff --git a/Project.toml b/Project.toml index 6570a70..ce2d6a6 100644 --- a/Project.toml +++ b/Project.toml @@ -17,6 +17,7 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] diff --git a/src/model/formulations/base/unit.jl b/src/model/formulations/base/unit.jl index 1e08a90..1ed6571 100644 --- a/src/model/formulations/base/unit.jl +++ b/src/model/formulations/base/unit.jl @@ -212,7 +212,7 @@ end function _total_reserves(model, g)::Vector T = model[:instance].time - reserve = 0.0 + reserve = [0.0 for _ in 1:T] if !isempty(g.reserves) reserve += [ sum(model[:reserve][r.name, g.name, t] for r in g.reserves) for diff --git a/src/solution/fix.jl b/src/solution/fix.jl index f9deacb..114e3ce 100644 --- a/src/solution/fix.jl +++ b/src/solution/fix.jl @@ -18,15 +18,27 @@ function fix!(model::JuMP.Model, solution::AbstractDict)::Nothing is_on_value = round(solution["Is on"][g.name][t]) prod_value = round(solution["Production (MW)"][g.name][t], digits = 5) - reserve_value = - round(solution["Reserve (MW)"][g.name][t], digits = 5) JuMP.fix(is_on[g.name, t], is_on_value, force = true) JuMP.fix( prod_above[g.name, t], prod_value - is_on_value * g.min_power[t], force = true, ) - JuMP.fix(reserve[g.name, t], reserve_value, force = true) + end + end + for r in instance.reserves + for g in r.units + for t in 1:T + reserve_value = round( + solution["Reserve (MW)"][r.name][g.name][t], + digits = 5, + ) + JuMP.fix( + reserve[r.name, g.name, t], + reserve_value, + force = true, + ) + end end end return diff --git a/src/transform/slice.jl b/src/transform/slice.jl index 582d47e..336817f 100644 --- a/src/transform/slice.jl +++ b/src/transform/slice.jl @@ -24,13 +24,14 @@ function slice( modified = deepcopy(instance) modified.time = length(range) modified.power_balance_penalty = modified.power_balance_penalty[range] - modified.reserves.spinning = modified.reserves.spinning[range] + for r in modified.reserves + r.amount = r.amount[range] + end for u in modified.units u.max_power = u.max_power[range] u.min_power = u.min_power[range] u.must_run = u.must_run[range] u.min_power_cost = u.min_power_cost[range] - u.provides_spinning_reserves = u.provides_spinning_reserves[range] for s in u.cost_segments s.mw = s.mw[range] s.cost = s.cost[range] diff --git a/src/validation/validate.jl b/src/validation/validate.jl index 5d69bc7..27638b1 100644 --- a/src/validation/validate.jl +++ b/src/validation/validate.jl @@ -40,15 +40,15 @@ function validate( return true end -function _validate_units(instance, solution; tol = 0.01) +function _validate_units(instance::UnitCommitmentInstance, solution; tol = 0.01) err_count = 0 for unit in instance.units production = solution["Production (MW)"][unit.name] - reserve = solution["Reserve (MW)"][unit.name] + reserve = [0.0 for _ in 1:instance.time] if !isempty(unit.reserves) reserve += sum( - solution["Reserve 2 (MW)"][r.name][unit.name] for + solution["Reserve (MW)"][r.name][unit.name] for r in unit.reserves ) end @@ -105,13 +105,16 @@ function _validate_units(instance, solution; tol = 0.01) end # Verify reserve eligibility - if !unit.provides_spinning_reserves[t] && reserve[t] > tol - @error @sprintf( - "Unit %s is not eligible to provide spinning reserves at time %d", - unit.name, - t - ) - err_count += 1 + for r in instance.reserves + if unit ∉ r.units && + (unit in keys(solution["Reserve (MW)"][r.name])) + @error @sprintf( + "Unit %s is not eligible to provide reserve %s", + unit.name, + r.name, + ) + err_count += 1 + end end # If unit is on, must produce at least its minimum power diff --git a/test/fixtures/case118-initcond.json.gz b/test/fixtures/case118-initcond.json.gz index a81a787..fabcbc8 100644 Binary files a/test/fixtures/case118-initcond.json.gz and b/test/fixtures/case118-initcond.json.gz differ diff --git a/test/instance/read_test.jl b/test/instance/read_test.jl index 6984435..ecf7f81 100644 --- a/test/instance/read_test.jl +++ b/test/instance/read_test.jl @@ -12,7 +12,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test length(instance.units) == 6 @test length(instance.contingencies) == 19 @test length(instance.price_sensitive_loads) == 1 - @test length(instance.reserves2) == 1 @test instance.time == 4 @test instance.lines[5].name == "l5" @@ -38,9 +37,9 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test instance.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] @test instance.buses_by_name["b9"].name == "b9" - @test instance.reserves2[1].name == "r1" - @test instance.reserves2[1].type == "spinning" - @test instance.reserves2[1].amount == [100.0, 100.0, 100.0, 100.0] + @test instance.reserves[1].name == "r1" + @test instance.reserves[1].type == "spinning" + @test instance.reserves[1].amount == [100.0, 100.0, 100.0, 100.0] @test instance.reserves_by_name["r1"].name == "r1" unit = instance.units[1] @@ -54,7 +53,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.min_power_cost == [1400.0 for t in 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t in 1:4] for t in 1:1 @test unit.cost_segments[1].mw[t] == 10.0 @test unit.cost_segments[2].mw[t] == 20.0 @@ -89,7 +87,6 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.min_power_cost == [0.0 for t in 1:4] @test unit.min_uptime == 1 @test unit.min_downtime == 1 - @test unit.provides_spinning_reserves == [true for t in 1:4] for t in 1:4 @test unit.cost_segments[1].mw[t] ≈ 33 @test unit.cost_segments[2].mw[t] ≈ 33 @@ -99,8 +96,7 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip @test unit.cost_segments[3].cost[t] ≈ 44.77853 end @test length(unit.reserves) == 1 - - @test instance.reserves.spinning == zeros(4) + @test unit.reserves[1].name == "r1" @test instance.contingencies[1].lines == [instance.lines[1]] @test instance.contingencies[1].units == [] diff --git a/test/transform/slice_test.jl b/test/transform/slice_test.jl index f330bda..84b0f84 100644 --- a/test/transform/slice_test.jl +++ b/test/transform/slice_test.jl @@ -11,13 +11,12 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip # Should update all time-dependent fields @test modified.time == 2 @test length(modified.power_balance_penalty) == 2 - @test length(modified.reserves.spinning) == 2 + @test length(modified.reserves_by_name["r1"].amount) == 2 for u in modified.units @test length(u.max_power) == 2 @test length(u.min_power) == 2 @test length(u.must_run) == 2 @test length(u.min_power_cost) == 2 - @test length(u.provides_spinning_reserves) == 2 for s in u.cost_segments @test length(s.mw) == 2 @test length(s.cost) == 2