From 1562032850bfccf2cc7a5a7e4f9c9a59146fd218 Mon Sep 17 00:00:00 2001 From: koraidon Date: Sun, 27 Jul 2025 00:41:51 -0400 Subject: [PATCH 1/3] tep instance --- docs/src/guides/instances.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/src/guides/instances.md b/docs/src/guides/instances.md index 9cb5a76..6e8f5fb 100644 --- a/docs/src/guides/instances.md +++ b/docs/src/guides/instances.md @@ -264,6 +264,20 @@ Test cases used in [TeLuSa19]. These instances are similar to OR-LIB/UC, in the | `tejada19/UC_168h_192g` | 168 | 1 | 192 | 0 | 0 | [TeLuSa19] | | `tejada19/UC_168h_199g` | 168 | 1 | 199 | 0 | 0 | [TeLuSa19] | +### Transmission and Expansion Planning Instances + +We curate a list of popular [MuSe21] transmission and expansion planning (TEP) instances used in academia paper. These TEP instances follow [UCJL format](format.md), with information on candidate transmission lines and investment costs. + +| Name | Buses | Generators | Generator Cost? | Lines | Line Cost? | References | +| ----------------------- | ----- | ---------- | --------------- | ----- | ---------- | ----------- | +| `tep/garver6` | 6 | 3 | Default | 6 | Yes | [Ga70] | +| `tep/ieee24` | 24 | 32 | Default | 35 | Yes | [FaHi03] | +| `tep/ieee30` | 30 | 6 | Default | 41 | Yes | [AlDeAm13] | +| `tep/ieee118` | 118 | 70 | Yes | 186 | Yes | [KhShKa10] | +| `tep/south_brazilian` | 242 | 53 | Default | 467 | Yes | [HaMoGa00] | +| `tep/south_brazilian` | 85 | 14 | Default | 299 | Yes | [RoMoGa02] | + + ## References - [UCJL] **Alinson S. Xavier, Aleksandr M. Kazachkov, Ogün Yurdakul, Feng Qiu.** "UnitCommitment.jl: A Julia/JuMP Optimization Package for Security-Constrained Unit Commitment (Version 0.3)". Zenodo (2022). [DOI: 10.5281/zenodo.4269874](https://doi.org/10.5281/zenodo.4269874) @@ -286,4 +300,18 @@ Test cases used in [TeLuSa19]. These instances are similar to OR-LIB/UC, in the - [FrGe06] **A. Frangioni, C. Gentile.** "Solving nonlinear single-unit commitment problems with ramping constraints" Operations Research 54(4), p. 767 - 775, 2006. [DOI: 10.1287/opre.1060.0309](https://doi.org/10.1287/opre.1060.0309) -- [TeLuSa19] **D. A. Tejada-Arango, S. Lumbreras, P. Sanchez-Martin and A. Ramos.** "Which Unit-Commitment Formulation is Best? A Systematic Comparison," in IEEE Transactions on Power Systems. [DOI: 10.1109/TPWRS.2019.2962024](https://ieeexplore.ieee.org/document/8941313/). +- [TeLuSa19] **D. A. Tejada-Arango, S. Lumbreras, P. Sanchez-Martin and A. Ramos.** "Which unit-commitment formulation is best? A systematic comparison," in IEEE Transactions on Power Systems. [DOI: 10.1109/TPWRS.2019.2962024](https://ieeexplore.ieee.org/document/8941313/). + +- [MuSe21] **Selçuk Mutlu, Ercan Şenyiğit.** "Literature review of transmission expansion planning problem test systems: detailed analysis of IEEE-24," in Electric Power Systems Research 201 (2021). [DOI: 10.1016/j.epsr.2021.107543](https://doi.org/10.1016/j.epsr.2021.107543) + +- [Ga70] **Len L. Garver.** "Transmission network estimation using linear programming," in IEEE Transactions on Power Apparatus and Systems, vol. PAS-89, no. 7, pp. 1688-1697, Sept. 1970. [DOI: 10.1109/TPAS.1970.292825](https://ieeexplore.ieee.org/abstract/document/4074249) + +- [FaHi03] **Risheng Fang, David J. Hill.** "A new strategy for transmission expansion in competitive electricity markets." IEEE Transactions on power systems 18.1 (2003): 374-380. [DOI: 10.1109/TPWRS.2002.807083](https://ieeexplore.ieee.org/document/1178822) + +- [AlDeAm13] **Behnam Alizadeh, Shahab Dehghan, Nima Amjady, Shahram Jadid and Ahad Kazemi.** "Robust transmission system expansion considering planning uncertainties," IET Generation, Transmission & Distribution (2013). [DOI: 10.1049/iet-gtd.2012.0137](https://doi.org/10.1049/iet-gtd.2012.0137) + +- [KhShKa10] **Amin Khodaei, Mohammad Shahidehpour and Saeed Kamalinia.** "Transmission switching in expansion planning," IEEE TRANSACTIONS ON POWER SYSTEMS, VOL. 25, NO. 3, AUGUST 2010. [DOI: 10.1109/TPWRS.2009.2039946](https://doi.org/10.1109/TPWRS.2009.2039946) + +- [HaMoGa00] **S. Haffner, A. Monticelli, A. Garcia, J. Mantovani and R. Romero.** "Branch and bound algorithm for transmission system expansion planning using a transportation model," IEE Proceedings - Generation Transmission and Distribution, Vol. 147, No. 3, May 2000. [DOI: 10.1049/ip-gtd:20000337](https://doi.org/10.1049/ip-gtd:20000337) + +- [RoMoGa02] **R. Romero, A. Monticelli, A. Garcia and S. Haffner.** "Test systems and mathematical models for transmission network expansion planning," IEEE Proceeding - Generation Transmission and Distribution, Vol. 149, No. 1, January 2002. [DOI: 10.1049/ip-gtd:20020026](https://doi.org/10.1049/ip-gtd:20020026) \ No newline at end of file From d06765a792f5cc49c84a137600646b6b089d8dd0 Mon Sep 17 00:00:00 2001 From: koraidon Date: Tue, 2 Sep 2025 22:09:07 -0400 Subject: [PATCH 2/3] doc --- docs/src/guides/format.md | 18 +++++++++++++----- docs/src/guides/instances.md | 29 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/docs/src/guides/format.md b/docs/src/guides/format.md index 2543ff6..4cf383c 100644 --- a/docs/src/guides/format.md +++ b/docs/src/guides/format.md @@ -24,7 +24,8 @@ This section describes system-wide parameters, such as power balance penalty, an | `Time step (min)` | Length of each time step (in minutes). Must be a divisor of 60 (e.g. 60, 30, 20, 15, etc). | `60` | No | No | | `Power balance penalty ($/MW)` | Penalty for system-wide shortage or surplus in production (in $/MW). This is charged per time step. For example, if there is a shortage of 1 MW for three time steps, three times this amount will be charged. | `1000.0` | No | Yes | | `Scenario name` | Name of the scenario. | `"s1"` | No | --- | -| `Scenario weight` | Weight of the scenario. The scenario weight can be any positive real number, that is, it does not have to be between zero and one. The package normalizes the weights to ensure that the probability of all scenarios sum up to one. | 1.0 | No | --- | +| `Scenario weight` | Weight of the scenario. The scenario weight can be any positive real number, that is, it does not have to be between zero and one. The package normalizes the weights to ensure that the probability of all scenarios sum up to one. | `1.0` | No | --- | +| `Operation cost weight` | Weighting factor to make operation costs comparable to investment costs. Required for transmission expansion planning problem. | `1.0` | No | --- | #### Example @@ -35,7 +36,8 @@ This section describes system-wide parameters, such as power balance penalty, an "Time horizon (h)": 4, "Power balance penalty ($/MW)": 1000.0, "Scenario name": "s1", - "Scenario weight": 0.5 + "Scenario weight": 0.5, + "Operation cost weight": 1.0 } } ``` @@ -89,6 +91,7 @@ This section describes all generators in the system. Two types of units can be s | `Must run?` | If `true`, the generator should be committed, even if that is not economical (Boolean). | `false` | Yes | Yes | | `Reserve eligibility` | List of reserve products this generator is eligibe to provide. By default, the generator is not eligible to provide any reserves. | `[]` | No | Yes | | `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` | Yes | Yes | +| `Investment cost ($)` | Cost to build a candidate generation unit. $0.0 for existing units. | `0.0` | No | No | #### Profiled Units @@ -99,7 +102,7 @@ This section describes all generators in the system. Two types of units can be s | `Cost ($/MW)` | Cost incurred for serving each MW of power by this generator. | Required | Yes | Yes | | `Minimum power (MW)` | Minimum amount of power this generator may supply. | `0.0` | Yes | Yes | | `Maximum power (MW)` | Maximum amount of power this generator may supply. | Required | Yes | Yes | - +| `Investment cost ($)`| Cost to build a candidate generation unit. $0.0 for existing units. | `0.0`. | No | No | #### Production costs and limits Production costs are represented as piecewise-linear curves. Figure 1 shows an example cost curve with three segments, where it costs \$1400, \$1600, \$2200 and \$2400 to generate, respectively, 100, 110, 130 and 135 MW of power. To model this generator, `Production cost curve (MW)` should be set to `[100, 110, 130, 135]`, and `Production cost curve ($)` should be set to `[1400, 1600, 2200, 2400]`. @@ -158,7 +161,8 @@ Note that this curve also specifies the production limits. Specifically, the fir "Type": "Profiled", "Minimum power (MW)": 10.0, "Maximum power (MW)": 120.0, - "Cost ($/MW)": 100.0 + "Cost ($/MW)": 100.0, + "Investment cost ($)": 3000000.0 } } } @@ -276,6 +280,8 @@ This section describes the characteristics of transmission system, such as its t | `Normal flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in its regular, fully-operational state. | `+inf` | Yes | Yes | | `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Y | Yes | | `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the transmission line (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes | +| `Investment cost ($)` | Cost to build a candidate transmission line. $0 for existing lines. | `0.0` | No | No | +| `Max number of parallel circuits`| Maximum number of lines can be built in this corridor. | `0` | No | No | #### Example @@ -288,7 +294,9 @@ This section describes the characteristics of transmission system, such as its t "Susceptance (S)": 29.49686, "Normal flow limit (MW)": 15000.0, "Emergency flow limit (MW)": 20000.0, - "Flow limit penalty ($/MW)": 5000.0 + "Flow limit penalty ($/MW)": 5000.0, + "Investment cost ($)": 3000000.0, + "Max number of parallel circuits": 2 } } } diff --git a/docs/src/guides/instances.md b/docs/src/guides/instances.md index 6e8f5fb..28f7008 100644 --- a/docs/src/guides/instances.md +++ b/docs/src/guides/instances.md @@ -268,15 +268,18 @@ Test cases used in [TeLuSa19]. These instances are similar to OR-LIB/UC, in the We curate a list of popular [MuSe21] transmission and expansion planning (TEP) instances used in academia paper. These TEP instances follow [UCJL format](format.md), with information on candidate transmission lines and investment costs. -| Name | Buses | Generators | Generator Cost? | Lines | Line Cost? | References | -| ----------------------- | ----- | ---------- | --------------- | ----- | ---------- | ----------- | -| `tep/garver6` | 6 | 3 | Default | 6 | Yes | [Ga70] | -| `tep/ieee24` | 24 | 32 | Default | 35 | Yes | [FaHi03] | -| `tep/ieee30` | 30 | 6 | Default | 41 | Yes | [AlDeAm13] | -| `tep/ieee118` | 118 | 70 | Yes | 186 | Yes | [KhShKa10] | -| `tep/south_brazilian` | 242 | 53 | Default | 467 | Yes | [HaMoGa00] | -| `tep/south_brazilian` | 85 | 14 | Default | 299 | Yes | [RoMoGa02] | - +| Name | Buses | Generators | Lines | Generator Expension? | Transmission Expension? | Power Balance Penalty | References | +| ---------------------- | ----- | ---------- | ----- | -------------------- | ----------------------- | --------------------- | ---------- | +| `tep/garver6` | 6 | 3 | 6 | No | Yes | No | [Ga70] | +| `tep/ieee14` | 14 | 2 | 20 | No | Yes | Yes | [ZhDoLi09] | +| `tep/ieee24` | 24 | 32 | 35 | No | Yes | Yes | [FaHi03] | +| `tep/ieee30` | 30 | 6 | 41 | No | Yes | No | [AlDeAm13] | +| `tep/ieee118` | 118 | 70 | 186 | Yes | Yes | 1 | [KhShKa10] | +| `tep/ieee300` | 300 | 60 | 411 | No | Yes | Yes | [ZhChZa17] | +| `tep/south_brazilian` | 242 | 53 | 467 | No | Yes | 1000 | [HaMoGa00] | +| `tep/north_brazilian` | 87 | 14 | 299 | No | Yes | 1000 | [RoMoGa02] | +| `tep/polish2383` | 2383 | 326 | 2896 | No | Yes | Yes | [ZhChZa17] | + ## References @@ -302,15 +305,19 @@ We curate a list of popular [MuSe21] transmission and expansion planning (TEP) i - [TeLuSa19] **D. A. Tejada-Arango, S. Lumbreras, P. Sanchez-Martin and A. Ramos.** "Which unit-commitment formulation is best? A systematic comparison," in IEEE Transactions on Power Systems. [DOI: 10.1109/TPWRS.2019.2962024](https://ieeexplore.ieee.org/document/8941313/). -- [MuSe21] **Selçuk Mutlu, Ercan Şenyiğit.** "Literature review of transmission expansion planning problem test systems: detailed analysis of IEEE-24," in Electric Power Systems Research 201 (2021). [DOI: 10.1016/j.epsr.2021.107543](https://doi.org/10.1016/j.epsr.2021.107543) +- [MuSe21] **Selçuk Mutlu and Ercan Şenyiğit.** "Literature review of transmission expansion planning problem test systems: detailed analysis of IEEE-24," in Electric Power Systems Research 201 (2021). [DOI: 10.1016/j.epsr.2021.107543](https://doi.org/10.1016/j.epsr.2021.107543) - [Ga70] **Len L. Garver.** "Transmission network estimation using linear programming," in IEEE Transactions on Power Apparatus and Systems, vol. PAS-89, no. 7, pp. 1688-1697, Sept. 1970. [DOI: 10.1109/TPAS.1970.292825](https://ieeexplore.ieee.org/abstract/document/4074249) +- [ZhDoLi09] **J. Zhao, Z. Dong, P. Lindsay and K. Wong.** "Flexible transmission expansion planning with uncertainties in an electricity market," IEEE Transactions on Power Systems, Vol. 24, No. 1, February 2009. [DOI: 10.1109/TPWRS.2008.2008681](https://doi.org/10.1109/TPWRS.2008.2008681) + - [FaHi03] **Risheng Fang, David J. Hill.** "A new strategy for transmission expansion in competitive electricity markets." IEEE Transactions on power systems 18.1 (2003): 374-380. [DOI: 10.1109/TPWRS.2002.807083](https://ieeexplore.ieee.org/document/1178822) - [AlDeAm13] **Behnam Alizadeh, Shahab Dehghan, Nima Amjady, Shahram Jadid and Ahad Kazemi.** "Robust transmission system expansion considering planning uncertainties," IET Generation, Transmission & Distribution (2013). [DOI: 10.1049/iet-gtd.2012.0137](https://doi.org/10.1049/iet-gtd.2012.0137) -- [KhShKa10] **Amin Khodaei, Mohammad Shahidehpour and Saeed Kamalinia.** "Transmission switching in expansion planning," IEEE TRANSACTIONS ON POWER SYSTEMS, VOL. 25, NO. 3, AUGUST 2010. [DOI: 10.1109/TPWRS.2009.2039946](https://doi.org/10.1109/TPWRS.2009.2039946) +- [KhShKa10] **Amin Khodaei, Mohammad Shahidehpour and Saeed Kamalinia.** "Transmission switching in expansion planning," IEEE Transactions on Power Systems, VOL. 25, NO. 3, AUGUST 2010. [DOI: 10.1109/TPWRS.2009.2039946](https://doi.org/10.1109/TPWRS.2009.2039946) + +- [ZhChZa17] **J. Zhan, C. Chung and A. Zare.** "A fast solution method for stochastic transmission expansion planning," IEEE Transactions on Power Systems, Vol. 32, No. 6, Novermber 2017. [DOI: 10.1109/TPWRS.2017.2665695](https://doi.org/10.1109/TPWRS.2017.2665695) - [HaMoGa00] **S. Haffner, A. Monticelli, A. Garcia, J. Mantovani and R. Romero.** "Branch and bound algorithm for transmission system expansion planning using a transportation model," IEE Proceedings - Generation Transmission and Distribution, Vol. 147, No. 3, May 2000. [DOI: 10.1049/ip-gtd:20000337](https://doi.org/10.1049/ip-gtd:20000337) From 1681badf8459e5cb7bd7c1e07c5b2275ce42cb12 Mon Sep 17 00:00:00 2001 From: koraidon Date: Tue, 16 Sep 2025 15:50:50 -0400 Subject: [PATCH 3/3] tep start --- docs/src/guides/format.md | 2 +- src/instance/read.jl | 3 + src/instance/structs.jl | 3 + src/model/formulations/base/line.jl | 6 + src/model/formulations/base/phaseangle.jl | 110 ++++++ src/model/formulations/base/structs.jl | 11 + test/fixtures/garver6.json | 298 +++++++++++++++ test/src/UnitCommitmentT.jl | 36 +- test/src/instance/read_test.jl | 444 ++++++++++++---------- test/src/model/formulations_test.jl | 16 +- 10 files changed, 701 insertions(+), 228 deletions(-) create mode 100644 src/model/formulations/base/phaseangle.jl create mode 100644 test/fixtures/garver6.json diff --git a/docs/src/guides/format.md b/docs/src/guides/format.md index 4cf383c..8b62a41 100644 --- a/docs/src/guides/format.md +++ b/docs/src/guides/format.md @@ -281,7 +281,7 @@ This section describes the characteristics of transmission system, such as its t | `Emergency flow limit (MW)` | Maximum amount of power (in MW) allowed to flow through the line when the system is in degraded state (for example, after the failure of another transmission line). | `+inf` | Y | Yes | | `Flow limit penalty ($/MW)` | Penalty for violating the flow limits of the transmission line (in $/MW). This is charged per time step. For example, if there is a thermal violation of 1 MW for three time steps, then three times this amount will be charged. | `5000.0` | Yes | Yes | | `Investment cost ($)` | Cost to build a candidate transmission line. $0 for existing lines. | `0.0` | No | No | -| `Max number of parallel circuits`| Maximum number of lines can be built in this corridor. | `0` | No | No | +| `Max number of parallel circuits`| Maximum number of lines can be built in this corridor. | `1` | No | No | #### Example diff --git a/src/instance/read.jl b/src/instance/read.jl index 0f828d8..00dd87e 100644 --- a/src/instance/read.jl +++ b/src/instance/read.jl @@ -338,6 +338,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario timeseries(scalar(dict["Minimum power (MW)"], default = 0.0)), timeseries(dict["Maximum power (MW)"]), timeseries(dict["Cost (\$/MW)"]), + scalar(dict["Investment cost (\$)"], default = 0.0), ) push!(bus.profiled_units, pu) push!(profiled_units, pu) @@ -367,6 +368,8 @@ function _from_json(json; repair = true)::UnitCommitmentScenario dict["Flow limit penalty (\$/MW)"], default = [5000.0 for t in 1:T], ), + scalar(dict["Investment cost (\$)"], default = 0.0), + scalar(dict["Maximum number of copies"], default = 1), ) name_to_line[line_name] = line push!(lines, line) diff --git a/src/instance/structs.jl b/src/instance/structs.jl index 4082fb0..a6c939f 100644 --- a/src/instance/structs.jl +++ b/src/instance/structs.jl @@ -60,6 +60,8 @@ mutable struct TransmissionLine normal_flow_limit::Vector{Float64} emergency_flow_limit::Vector{Float64} flow_limit_penalty::Vector{Float64} + invest::Float64 #TODO: make this a vector + max_copy::Int end mutable struct Contingency @@ -81,6 +83,7 @@ mutable struct ProfiledUnit min_power::Vector{Float64} max_power::Vector{Float64} cost::Vector{Float64} + invest::Float64 end mutable struct StorageUnit diff --git a/src/model/formulations/base/line.jl b/src/model/formulations/base/line.jl index 98d5f2c..0546f8b 100644 --- a/src/model/formulations/base/line.jl +++ b/src/model/formulations/base/line.jl @@ -59,3 +59,9 @@ function _setup_transmission( sc.lodf = lodf return end + + +_setup_transmission( + ::PhaseAngleFormulation, + ::UnitCommitmentScenario, +) = nothing \ No newline at end of file diff --git a/src/model/formulations/base/phaseangle.jl b/src/model/formulations/base/phaseangle.jl new file mode 100644 index 0000000..990d8fc --- /dev/null +++ b/src/model/formulations/base/phaseangle.jl @@ -0,0 +1,110 @@ +# UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment +# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. +# Released under the modified BSD license. See COPYING.md for more details. + +function _add_planning_unified!( + model::JuMP.Model, + pu::ProfiledUnit, + lm::TransmissionLine, + sc::UnitCommitmentScenario, +)::Nothing + invest_unit = _init(model, :invest_unit) + invest_line = _init(model, :invest_line) + eq_invest_unit_history = _init(model, :eq_invest_unit_history) + eq_invest_line_history = _init(model, :eq_invest_line_history) + eq_invest_unit_capacity = _init(model, :eq_invest_unit_capacity) + + # t == 0 + # TODO: If investment cost is zero, skip creating these investment variables + invest_unit[sc.name, pu.name, 0] = @variable( + model, + binary = true, + fixed = pu.invest == 0.0 + ) + invest_line[sc.name, lm.name, 0] = Int(lm.invest == 0.0) + + for t in 1:model[:instance].time + # Decision variable + invest_unit[sc.name, pu.name, t] = @variable(model, binary = true) + invest_line[sc.name, lm.name, t] = @variable(model, binary = true) + + # Objective function terms + add_to_expression!( + model[:obj], + invest_unit[sc.name, pu.name, t] - invest_unit[sc.name, pu.name, t-1], + pu.invest[t] * sc.probability, + ) + add_to_expression!( + model[:obj], + invest_line[sc.name, lm.name, t] - invest_line[sc.name, lm.name, t-1], + lm.invest[t] * sc.probability, + ) + + # Investment constraints + # (1c) in the paper + eq_invest_unit_history[sc.name, pu.name, t] = @constraint( + model, + invest_unit[sc.name, pu.name, t-1] <= invest_unit[sc.name, pu.name, t] + ) + # (1d) in the paper + eq_invest_line_history[sc.name, lm.name, t] = @constraint( + model, + invest_line[sc.name, lm.name, t-1] <= invest_line[sc.name, lm.name, t] + ) + # (1h) in the paper TODO: separate this to two halves + eq_invest_unit_capacity[sc.name, pu.name, t] = @constraint( + model, + pu.min_power[t] * invest_unit[sc.name, pu.name, t] <= + model[:prod_above][sc.name, pu.name, t] <= + pu.max_power[t] * invest_unit[sc.name, pu.name, t] + ) + end + return +end + +# TODO: add max_copy constraint +function _add_planning_new_lines!( + model::JuMP.Model, + lm::TransmissionLine, + bs::Bus, + f::PhaseAngleFormulation, + sc::UnitCommitmentScenario, +)::Nothing + θ = _init(model, :theta) + flow = _init(model, :flow) + invest_line = model[:invest_line] + eq_invest_line_flow_a = _init(model, :eq_invest_line_flow_a) + eq_invest_line_flow_b = _init(model, :eq_invest_line_flow_b) + eq_invest_line_flow_limit = _init(model, :eq_invest_line_flow_limit) + + bigM = 1e6 # TODO: make this a parameter + + for t in 1:model[:instance].time + # TODO: add bus field: phase_angle_limit? give default + θ[sc.name, bs.name, t] = @variable(model, lower_bound = -pi, upper_bound = pi) + flow[sc.name, lm.name, t] = @variable(model) + + # (2b) + eq_invest_line_flow_a[sc.name, lm.name, t] = @constraint( + model, + model[:flow][sc.name, lm.name, t] <= + lm.susceptance * (θ[sc.name, lm.source.name, t] - θ[sc.name, lm.target.name, t]) + + bigM * (1 - invest_line[sc.name, lm.name, t]) + ) + # (2c) + eq_invest_line_flow_b[sc.name, lm.name, t] = @constraint( + model, + model[:flow][sc.name, lm.name, t] >= + lm.susceptance * (θ[sc.name, lm.source.name, t] - θ[sc.name, lm.target.name, t]) + - bigM * (1 - invest_line[sc.name, lm.name, t]) + ) + # (2d) + eq_invest_line_flow_limit[sc.name, lm.name, t] = @constraint( + model, + -lm.normal_flow_limit[t] * invest_line[sc.name, lm.name, t] <= + model[:flow][sc.name, lm.name, t] <= + lm.normal_flow_limit[t] * invest_line[sc.name, lm.name, t] + ) + end + return +end \ No newline at end of file diff --git a/src/model/formulations/base/structs.jl b/src/model/formulations/base/structs.jl index 09b8481..3d315d3 100644 --- a/src/model/formulations/base/structs.jl +++ b/src/model/formulations/base/structs.jl @@ -97,3 +97,14 @@ struct ShiftFactorsFormulation <: TransmissionFormulation return new(isf_cutoff, lodf_cutoff, precomputed_isf, precomputed_lodf) end end + +""" + struct PhaseAngleFormulation <: TransmissionFormulation + end + +Transmission formulation based on susceptance (b). +Constraints are enforced in a lazy way. +""" +struct PhaseAngleFormulation <: TransmissionFormulation + +end diff --git a/test/fixtures/garver6.json b/test/fixtures/garver6.json new file mode 100644 index 0000000..fe35eca --- /dev/null +++ b/test/fixtures/garver6.json @@ -0,0 +1,298 @@ +{ + "Parameters": { + "Version": "0.4", + "Time horizon (h)": 1, + "Power balance penalty ($/MW)": 1000.0, + "Scenario name": "s1", + "Scenario weight": 1.0, + "Operation cost weight": 1.0 + }, + "Buses": { + "b1": { + "Load (MW)": 80.0 + }, + "b2": { + "Load (MW)": 240.0 + }, + "b3": { + "Load (MW)": 40.0 + }, + "b4": { + "Load (MW)": 160.0 + }, + "b5": { + "Load (MW)": 240.0 + }, + "b6": { + "Load (MW)": 0.0 + } + }, + "Generators": { + "gen1": { + "Bus": "b1", + "Type": "Profiled", + "Maximum power (MW)": 150.0, + "Cost ($/MW)": 0.0 + }, + "gen2": { + "Bus": "b3", + "Type": "Profiled", + "Maximum power (MW)": 360.0, + "Cost ($/MW)": 0.0 + }, + "gen3": { + "Bus": "b6", + "Type": "Profiled", + "Maximum power (MW)": 600.0, + "Cost ($/MW)": 0.0 + } + }, + "Transmission lines": { + "l1": { + "Source bus": "b1", + "Target bus": "b2", + "Susceptance (S)": 2.5, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 0.0, + "Guide Number": 40.0, + "Reactance (p.u.)": 0.4, + "Resistance (p.u.)": 0.1, + "Length (miles)": 40.0 + }, + "l2": { + "Source bus": "b1", + "Target bus": "b4", + "Susceptance (S)": 1.6666666666666667, + "Normal flow limit (MW)": 80.0, + "Investment cost ($)": 0.0, + "Guide Number": 60.0, + "Reactance (p.u.)": 0.6, + "Resistance (p.u.)": 0.15, + "Length (miles)": 60.0 + }, + "l3": { + "Source bus": "b1", + "Target bus": "b5", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 0.0, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "l4": { + "Source bus": "b2", + "Target bus": "b3", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 0.0, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "l5": { + "Source bus": "b2", + "Target bus": "b4", + "Susceptance (S)": 2.5, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 0.0, + "Guide Number": 40.0, + "Reactance (p.u.)": 0.4, + "Resistance (p.u.)": 0.1, + "Length (miles)": 40.0 + }, + "l6": { + "Source bus": "b3", + "Target bus": "b5", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 0.0, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "new_l7": { + "Source bus": "b1", + "Target bus": "b2", + "Susceptance (S)": 2.5, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 40000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 40.0, + "Reactance (p.u.)": 0.4, + "Resistance (p.u.)": 0.1, + "Length (miles)": 40.0 + }, + "new_l8": { + "Source bus": "b1", + "Target bus": "b3", + "Susceptance (S)": 2.6315789473684212, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 38000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 38.0, + "Reactance (p.u.)": 0.38, + "Resistance (p.u.)": 0.09, + "Length (miles)": 38.0 + }, + "new_l9": { + "Source bus": "b1", + "Target bus": "b4", + "Susceptance (S)": 1.6666666666666667, + "Normal flow limit (MW)": 80.0, + "Investment cost ($)": 60000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 60.0, + "Reactance (p.u.)": 0.6, + "Resistance (p.u.)": 0.15, + "Length (miles)": 60.0 + }, + "new_l10": { + "Source bus": "b1", + "Target bus": "b5", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 20000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "new_l11": { + "Source bus": "b1", + "Target bus": "b6", + "Susceptance (S)": 1.4705882352941175, + "Normal flow limit (MW)": 70.0, + "Investment cost ($)": 68000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 68.0, + "Reactance (p.u.)": 0.68, + "Resistance (p.u.)": 0.17, + "Length (miles)": 68.0 + }, + "new_l12": { + "Source bus": "b2", + "Target bus": "b3", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 20000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "new_l13": { + "Source bus": "b2", + "Target bus": "b4", + "Susceptance (S)": 2.5, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 40000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 40.0, + "Reactance (p.u.)": 0.4, + "Resistance (p.u.)": 0.1, + "Length (miles)": 40.0 + }, + "new_l14": { + "Source bus": "b2", + "Target bus": "b5", + "Susceptance (S)": 3.2258064516129035, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 31000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 31.0, + "Reactance (p.u.)": 0.31, + "Resistance (p.u.)": 0.08, + "Length (miles)": 31.0 + }, + "new_l15": { + "Source bus": "b2", + "Target bus": "b6", + "Susceptance (S)": 3.3333333333333335, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 30000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 30.0, + "Reactance (p.u.)": 0.3, + "Resistance (p.u.)": 0.08, + "Length (miles)": 30.0 + }, + "new_l16": { + "Source bus": "b3", + "Target bus": "b4", + "Susceptance (S)": 1.6949152542372883, + "Normal flow limit (MW)": 82.0, + "Investment cost ($)": 59000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 59.0, + "Reactance (p.u.)": 0.59, + "Resistance (p.u.)": 0.15, + "Length (miles)": 59.0 + }, + "new_l17": { + "Source bus": "b3", + "Target bus": "b5", + "Susceptance (S)": 5.0, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 20000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 20.0, + "Reactance (p.u.)": 0.2, + "Resistance (p.u.)": 0.05, + "Length (miles)": 20.0 + }, + "new_l18": { + "Source bus": "b3", + "Target bus": "b6", + "Susceptance (S)": 2.0833333333333335, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 48000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 48.0, + "Reactance (p.u.)": 0.48, + "Resistance (p.u.)": 0.12, + "Length (miles)": 48.0 + }, + "new_l19": { + "Source bus": "b4", + "Target bus": "b5", + "Susceptance (S)": 1.5873015873015872, + "Normal flow limit (MW)": 75.0, + "Investment cost ($)": 63000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 63.0, + "Reactance (p.u.)": 0.63, + "Resistance (p.u.)": 0.16, + "Length (miles)": 63.0 + }, + "new_l20": { + "Source bus": "b4", + "Target bus": "b6", + "Susceptance (S)": 3.3333333333333335, + "Normal flow limit (MW)": 100.0, + "Investment cost ($)": 30000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 30.0, + "Reactance (p.u.)": 0.3, + "Resistance (p.u.)": 0.08, + "Length (miles)": 30.0 + }, + "new_l21": { + "Source bus": "b5", + "Target bus": "b6", + "Susceptance (S)": 1.639344262295082, + "Normal flow limit (MW)": 78.0, + "Investment cost ($)": 61000000.0, + "Max number of parallel circuits": 4, + "Guide Number": 61.0, + "Reactance (p.u.)": 0.61, + "Resistance (p.u.)": 0.15, + "Length (miles)": 61.0 + } + } +} \ No newline at end of file diff --git a/test/src/UnitCommitmentT.jl b/test/src/UnitCommitmentT.jl index 50bf1a7..2d6866b 100644 --- a/test/src/UnitCommitmentT.jl +++ b/test/src/UnitCommitmentT.jl @@ -34,26 +34,26 @@ function runtests() println("Running tests...") UnitCommitment._setup_logger(level = Base.CoreLogging.Error) @testset "UnitCommitment" begin - usage_test() - import_egret_test() + # usage_test() + # import_egret_test() instance_read_test() - instance_migrate_test() + # instance_migrate_test() model_formulations_test() - solution_methods_XavQiuWanThi19_filter_test() - solution_methods_XavQiuWanThi19_find_test() - solution_methods_XavQiuWanThi19_sensitivity_test() - solution_methods_ProgressiveHedging_usage_test() - solution_methods_TimeDecomposition_initial_status_test() - solution_methods_TimeDecomposition_optimize_test() - solution_methods_TimeDecomposition_update_solution_test() - transform_initcond_test() - transform_slice_test() - transform_randomize_XavQiuAhm2021_test() - validation_repair_test() - lmp_conventional_test() - lmp_aelmp_test() - simple_market_test() - stochastic_market_test() + # solution_methods_XavQiuWanThi19_filter_test() + # solution_methods_XavQiuWanThi19_find_test() + # solution_methods_XavQiuWanThi19_sensitivity_test() + # solution_methods_ProgressiveHedging_usage_test() + # solution_methods_TimeDecomposition_initial_status_test() + # solution_methods_TimeDecomposition_optimize_test() + # solution_methods_TimeDecomposition_update_solution_test() + # transform_initcond_test() + # transform_slice_test() + # transform_randomize_XavQiuAhm2021_test() + # validation_repair_test() + # lmp_conventional_test() + # lmp_aelmp_test() + # simple_market_test() + # stochastic_market_test() end return end diff --git a/test/src/instance/read_test.jl b/test/src/instance/read_test.jl index bcb3eb4..e8b2f20 100644 --- a/test/src/instance/read_test.jl +++ b/test/src/instance/read_test.jl @@ -5,223 +5,257 @@ using UnitCommitment, LinearAlgebra, Cbc, JuMP, JSON, GZip function instance_read_test() - @testset "read_benchmark" begin - instance = UnitCommitment.read(fixture("case14.json.gz")) + # @testset "read_benchmark" begin + # instance = UnitCommitment.read(fixture("case14.json.gz")) + + # @test repr(instance) == ( + # "UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " * + # "20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)" + # ) + + # @test length(instance.scenarios) == 1 + # sc = instance.scenarios[1] + # @test length(sc.lines) == 20 + # @test length(sc.buses) == 14 + # @test length(sc.thermal_units) == 6 + # @test length(sc.contingencies) == 19 + # @test length(sc.price_sensitive_loads) == 1 + # @test instance.time == 4 + # @test sc.time_step == 60 + + # @test sc.lines[5].name == "l5" + # @test sc.lines[5].source.name == "b2" + # @test sc.lines[5].target.name == "b5" + # @test sc.lines[5].susceptance ≈ 10.037550333 + # @test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4] + # @test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4] + # @test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4] + # @test sc.lines_by_name["l5"].name == "l5" + + # @test sc.lines[1].name == "l1" + # @test sc.lines[1].source.name == "b1" + # @test sc.lines[1].target.name == "b2" + # @test sc.lines[1].susceptance ≈ 29.496860773945 + # @test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4] + # @test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4] + # @test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4] + + # @test sc.buses[9].name == "b9" + # @test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] + # @test sc.buses_by_name["b9"].name == "b9" + + # @test sc.reserves[1].name == "r1" + # @test sc.reserves[1].type == "spinning" + # @test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0] + # @test sc.reserves_by_name["r1"].name == "r1" + + # unit = sc.thermal_units[1] + # @test unit.name == "g1" + # @test unit.bus.name == "b1" + # @test unit.ramp_up_limit == 1e6 + # @test unit.ramp_down_limit == 1e6 + # @test unit.startup_limit == 1e6 + # @test unit.shutdown_limit == 1e6 + # @test unit.must_run == [false for t in 1:4] + # @test unit.min_power_cost == [1400.0 for t in 1:4] + # @test unit.min_uptime == 1 + # @test unit.min_downtime == 1 + # for t in 1:1 + # @test unit.cost_segments[1].mw[t] == 10.0 + # @test unit.cost_segments[2].mw[t] == 20.0 + # @test unit.cost_segments[3].mw[t] == 5.0 + # @test unit.cost_segments[1].cost[t] ≈ 20.0 + # @test unit.cost_segments[2].cost[t] ≈ 30.0 + # @test unit.cost_segments[3].cost[t] ≈ 40.0 + # end + # @test length(unit.startup_categories) == 3 + # @test unit.startup_categories[1].delay == 1 + # @test unit.startup_categories[2].delay == 2 + # @test unit.startup_categories[3].delay == 3 + # @test unit.startup_categories[1].cost == 1000.0 + # @test unit.startup_categories[2].cost == 1500.0 + # @test unit.startup_categories[3].cost == 2000.0 + # @test length(unit.reserves) == 0 + # @test sc.thermal_units_by_name["g1"].name == "g1" + + # unit = sc.thermal_units[2] + # @test unit.name == "g2" + # @test unit.must_run == [false for t in 1:4] + # @test length(unit.reserves) == 1 + + # unit = sc.thermal_units[3] + # @test unit.name == "g3" + # @test unit.bus.name == "b3" + # @test unit.ramp_up_limit == 70.0 + # @test unit.ramp_down_limit == 70.0 + # @test unit.startup_limit == 70.0 + # @test unit.shutdown_limit == 70.0 + # @test unit.must_run == [true for t in 1:4] + # @test unit.min_power_cost == [0.0 for t in 1:4] + # @test unit.min_uptime == 1 + # @test unit.min_downtime == 1 + # for t in 1:4 + # @test unit.cost_segments[1].mw[t] ≈ 33 + # @test unit.cost_segments[2].mw[t] ≈ 33 + # @test unit.cost_segments[3].mw[t] ≈ 34 + # @test unit.cost_segments[1].cost[t] ≈ 33.75 + # @test unit.cost_segments[2].cost[t] ≈ 38.04 + # @test unit.cost_segments[3].cost[t] ≈ 44.77853 + # end + # @test length(unit.reserves) == 1 + # @test unit.reserves[1].name == "r1" + + # @test sc.contingencies[1].lines == [sc.lines[1]] + # @test sc.contingencies[1].thermal_units == [] + # @test sc.contingencies[1].name == "c1" + # @test sc.contingencies_by_name["c1"].name == "c1" + + # load = sc.price_sensitive_loads[1] + # @test load.name == "ps1" + # @test load.bus.name == "b3" + # @test load.revenue == [100.0 for t in 1:4] + # @test load.demand == [50.0 for t in 1:4] + # @test sc.price_sensitive_loads_by_name["ps1"].name == "ps1" + # end + + # @testset "read_benchmark sub-hourly" begin + # instance = UnitCommitment.read(fixture("case14-sub-hourly.json.gz")) + # @test instance.time == 4 + # unit = instance.scenarios[1].thermal_units[1] + # @test unit.name == "g1" + # @test unit.min_uptime == 2 + # @test unit.min_downtime == 2 + # @test length(unit.startup_categories) == 3 + # @test unit.startup_categories[1].delay == 2 + # @test unit.startup_categories[2].delay == 4 + # @test unit.startup_categories[3].delay == 6 + # @test unit.initial_status == -200 + # end + + # @testset "read_benchmark profiled-units" begin + # instance = UnitCommitment.read(fixture("case14-profiled.json.gz")) + # sc = instance.scenarios[1] + # @test length(sc.profiled_units) == 2 + + # pu1 = sc.profiled_units[1] + # @test pu1.name == "g7" + # @test pu1.bus.name == "b4" + # @test pu1.cost == [100.0 for t in 1:4] + # @test pu1.min_power == [60.0 for t in 1:4] + # @test pu1.max_power == [100.0 for t in 1:4] + # @test sc.profiled_units_by_name["g7"].name == "g7" + + # pu2 = sc.profiled_units[2] + # @test pu2.name == "g8" + # @test pu2.bus.name == "b5" + # @test pu2.cost == [50.0 for t in 1:4] + # @test pu2.min_power == [0.0 for t in 1:4] + # @test pu2.max_power == [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(fixture("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 + + # @testset "read_benchmark storage" begin + # instance = UnitCommitment.read(fixture("case14-storage.json.gz")) + # sc = instance.scenarios[1] + # @test length(sc.storage_units) == 4 + + # su1 = sc.storage_units[1] + # @test su1.name == "su1" + # @test su1.bus.name == "b2" + # @test su1.min_level == [0.0 for t in 1:4] + # @test su1.max_level == [100.0 for t in 1:4] + # @test su1.simultaneous_charge_and_discharge == [true for t in 1:4] + # @test su1.charge_cost == [2.0 for t in 1:4] + # @test su1.discharge_cost == [2.5 for t in 1:4] + # @test su1.charge_efficiency == [1.0 for t in 1:4] + # @test su1.discharge_efficiency == [1.0 for t in 1:4] + # @test su1.loss_factor == [0.0 for t in 1:4] + # @test su1.min_charge_rate == [0.0 for t in 1:4] + # @test su1.max_charge_rate == [10.0 for t in 1:4] + # @test su1.min_discharge_rate == [0.0 for t in 1:4] + # @test su1.max_discharge_rate == [8.0 for t in 1:4] + # @test su1.initial_level == 0.0 + # @test su1.min_ending_level == 0.0 + # @test su1.max_ending_level == 100.0 + # @test sc.storage_units_by_name["su1"].name == "su1" + + # su2 = sc.storage_units[2] + # @test su2.name == "su2" + # @test su2.bus.name == "b2" + # @test su2.min_level == [10.0 for t in 1:4] + # @test su2.simultaneous_charge_and_discharge == [false for t in 1:4] + # @test su2.charge_cost == [3.0 for t in 1:4] + # @test su2.discharge_cost == [3.5 for t in 1:4] + # @test su2.charge_efficiency == [0.8 for t in 1:4] + # @test su2.discharge_efficiency == [0.85 for t in 1:4] + # @test su2.loss_factor == [0.01 for t in 1:4] + # @test su2.min_charge_rate == [5.0 for t in 1:4] + # @test su2.min_discharge_rate == [2.0 for t in 1:4] + # @test su2.initial_level == 70.0 + # @test su2.min_ending_level == 80.0 + # @test su2.max_ending_level == 85.0 + # @test sc.storage_units_by_name["su2"].name == "su2" + + # su3 = sc.storage_units[3] + # @test su3.bus.name == "b9" + # @test su3.min_level == [10.0, 11.0, 12.0, 13.0] + # @test su3.max_level == [100.0, 110.0, 120.0, 130.0] + # @test su3.charge_cost == [2.0, 2.1, 2.2, 2.3] + # @test su3.discharge_cost == [1.0, 1.1, 1.2, 1.3] + # @test su3.charge_efficiency == [0.8, 0.81, 0.82, 0.82] + # @test su3.discharge_efficiency == [0.85, 0.86, 0.87, 0.88] + # @test su3.min_charge_rate == [5.0, 5.1, 5.2, 5.3] + # @test su3.max_charge_rate == [10.0, 10.1, 10.2, 10.3] + # @test su3.min_discharge_rate == [4.0, 4.1, 4.2, 4.3] + # @test su3.max_discharge_rate == [8.0, 8.1, 8.2, 8.3] + + # su4 = sc.storage_units[4] + # @test su4.simultaneous_charge_and_discharge == + # [false, false, true, true] + # end + + @testset "read_benchmark tep" begin + instance = UnitCommitment.read(fixture("garver6.json")) @test repr(instance) == ( - "UnitCommitmentInstance(1 scenarios, 6 thermal units, 0 profiled units, 14 buses, " * - "20 lines, 19 contingencies, 1 price sensitive loads, 4 time steps)" + "UnitCommitmentInstance(1 scenarios, 0 thermal units, 3 profiled units, 6 buses, " * + "21 lines, 0 contingencies, 0 price sensitive loads, 1 time steps)" ) @test length(instance.scenarios) == 1 sc = instance.scenarios[1] - @test length(sc.lines) == 20 - @test length(sc.buses) == 14 - @test length(sc.thermal_units) == 6 - @test length(sc.contingencies) == 19 - @test length(sc.price_sensitive_loads) == 1 - @test instance.time == 4 + @test length(sc.lines) == 21 + @test length(sc.buses) == 6 + @test length(sc.profiled_units) == 3 + @test instance.time == 1 @test sc.time_step == 60 - @test sc.lines[5].name == "l5" - @test sc.lines[5].source.name == "b2" - @test sc.lines[5].target.name == "b5" - @test sc.lines[5].susceptance ≈ 10.037550333 - @test sc.lines[5].normal_flow_limit == [1e8 for t in 1:4] - @test sc.lines[5].emergency_flow_limit == [1e8 for t in 1:4] - @test sc.lines[5].flow_limit_penalty == [5e3 for t in 1:4] - @test sc.lines_by_name["l5"].name == "l5" - @test sc.lines[1].name == "l1" @test sc.lines[1].source.name == "b1" @test sc.lines[1].target.name == "b2" - @test sc.lines[1].susceptance ≈ 29.496860773945 - @test sc.lines[1].normal_flow_limit == [300.0 for t in 1:4] - @test sc.lines[1].emergency_flow_limit == [400.0 for t in 1:4] - @test sc.lines[1].flow_limit_penalty == [1e3 for t in 1:4] - - @test sc.buses[9].name == "b9" - @test sc.buses[9].load == [35.36638, 33.25495, 31.67138, 31.14353] - @test sc.buses_by_name["b9"].name == "b9" - - @test sc.reserves[1].name == "r1" - @test sc.reserves[1].type == "spinning" - @test sc.reserves[1].amount == [100.0, 100.0, 100.0, 100.0] - @test sc.reserves_by_name["r1"].name == "r1" - - unit = sc.thermal_units[1] - @test unit.name == "g1" - @test unit.bus.name == "b1" - @test unit.ramp_up_limit == 1e6 - @test unit.ramp_down_limit == 1e6 - @test unit.startup_limit == 1e6 - @test unit.shutdown_limit == 1e6 - @test unit.must_run == [false for t in 1:4] - @test unit.min_power_cost == [1400.0 for t in 1:4] - @test unit.min_uptime == 1 - @test unit.min_downtime == 1 - for t in 1:1 - @test unit.cost_segments[1].mw[t] == 10.0 - @test unit.cost_segments[2].mw[t] == 20.0 - @test unit.cost_segments[3].mw[t] == 5.0 - @test unit.cost_segments[1].cost[t] ≈ 20.0 - @test unit.cost_segments[2].cost[t] ≈ 30.0 - @test unit.cost_segments[3].cost[t] ≈ 40.0 - end - @test length(unit.startup_categories) == 3 - @test unit.startup_categories[1].delay == 1 - @test unit.startup_categories[2].delay == 2 - @test unit.startup_categories[3].delay == 3 - @test unit.startup_categories[1].cost == 1000.0 - @test unit.startup_categories[2].cost == 1500.0 - @test unit.startup_categories[3].cost == 2000.0 - @test length(unit.reserves) == 0 - @test sc.thermal_units_by_name["g1"].name == "g1" - - unit = sc.thermal_units[2] - @test unit.name == "g2" - @test unit.must_run == [false for t in 1:4] - @test length(unit.reserves) == 1 - - unit = sc.thermal_units[3] - @test unit.name == "g3" - @test unit.bus.name == "b3" - @test unit.ramp_up_limit == 70.0 - @test unit.ramp_down_limit == 70.0 - @test unit.startup_limit == 70.0 - @test unit.shutdown_limit == 70.0 - @test unit.must_run == [true for t in 1:4] - @test unit.min_power_cost == [0.0 for t in 1:4] - @test unit.min_uptime == 1 - @test unit.min_downtime == 1 - for t in 1:4 - @test unit.cost_segments[1].mw[t] ≈ 33 - @test unit.cost_segments[2].mw[t] ≈ 33 - @test unit.cost_segments[3].mw[t] ≈ 34 - @test unit.cost_segments[1].cost[t] ≈ 33.75 - @test unit.cost_segments[2].cost[t] ≈ 38.04 - @test unit.cost_segments[3].cost[t] ≈ 44.77853 - end - @test length(unit.reserves) == 1 - @test unit.reserves[1].name == "r1" - - @test sc.contingencies[1].lines == [sc.lines[1]] - @test sc.contingencies[1].thermal_units == [] - @test sc.contingencies[1].name == "c1" - @test sc.contingencies_by_name["c1"].name == "c1" - - load = sc.price_sensitive_loads[1] - @test load.name == "ps1" - @test load.bus.name == "b3" - @test load.revenue == [100.0 for t in 1:4] - @test load.demand == [50.0 for t in 1:4] - @test sc.price_sensitive_loads_by_name["ps1"].name == "ps1" - end - - @testset "read_benchmark sub-hourly" begin - instance = UnitCommitment.read(fixture("case14-sub-hourly.json.gz")) - @test instance.time == 4 - unit = instance.scenarios[1].thermal_units[1] - @test unit.name == "g1" - @test unit.min_uptime == 2 - @test unit.min_downtime == 2 - @test length(unit.startup_categories) == 3 - @test unit.startup_categories[1].delay == 2 - @test unit.startup_categories[2].delay == 4 - @test unit.startup_categories[3].delay == 6 - @test unit.initial_status == -200 - end + @test sc.lines[1].susceptance ≈ 2.5 + @test sc.lines[1].normal_flow_limit == [100] + # TODO: add new fields + # Modify garver6.json for testing - @testset "read_benchmark profiled-units" begin - instance = UnitCommitment.read(fixture("case14-profiled.json.gz")) - sc = instance.scenarios[1] - @test length(sc.profiled_units) == 2 - - pu1 = sc.profiled_units[1] - @test pu1.name == "g7" - @test pu1.bus.name == "b4" - @test pu1.cost == [100.0 for t in 1:4] - @test pu1.min_power == [60.0 for t in 1:4] - @test pu1.max_power == [100.0 for t in 1:4] - @test sc.profiled_units_by_name["g7"].name == "g7" - - pu2 = sc.profiled_units[2] - @test pu2.name == "g8" - @test pu2.bus.name == "b5" - @test pu2.cost == [50.0 for t in 1:4] - @test pu2.min_power == [0.0 for t in 1:4] - @test pu2.max_power == [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(fixture("case14-fixed-status.json.gz")) - sc = instance.scenarios[1] + @test sc.buses[3].name == "b3" + @test sc.buses[3].load == [40.0] + @test sc.buses_by_name["b3"].name == "b3" - @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 - - @testset "read_benchmark storage" begin - instance = UnitCommitment.read(fixture("case14-storage.json.gz")) - sc = instance.scenarios[1] - @test length(sc.storage_units) == 4 - - su1 = sc.storage_units[1] - @test su1.name == "su1" - @test su1.bus.name == "b2" - @test su1.min_level == [0.0 for t in 1:4] - @test su1.max_level == [100.0 for t in 1:4] - @test su1.simultaneous_charge_and_discharge == [true for t in 1:4] - @test su1.charge_cost == [2.0 for t in 1:4] - @test su1.discharge_cost == [2.5 for t in 1:4] - @test su1.charge_efficiency == [1.0 for t in 1:4] - @test su1.discharge_efficiency == [1.0 for t in 1:4] - @test su1.loss_factor == [0.0 for t in 1:4] - @test su1.min_charge_rate == [0.0 for t in 1:4] - @test su1.max_charge_rate == [10.0 for t in 1:4] - @test su1.min_discharge_rate == [0.0 for t in 1:4] - @test su1.max_discharge_rate == [8.0 for t in 1:4] - @test su1.initial_level == 0.0 - @test su1.min_ending_level == 0.0 - @test su1.max_ending_level == 100.0 - @test sc.storage_units_by_name["su1"].name == "su1" - - su2 = sc.storage_units[2] - @test su2.name == "su2" - @test su2.bus.name == "b2" - @test su2.min_level == [10.0 for t in 1:4] - @test su2.simultaneous_charge_and_discharge == [false for t in 1:4] - @test su2.charge_cost == [3.0 for t in 1:4] - @test su2.discharge_cost == [3.5 for t in 1:4] - @test su2.charge_efficiency == [0.8 for t in 1:4] - @test su2.discharge_efficiency == [0.85 for t in 1:4] - @test su2.loss_factor == [0.01 for t in 1:4] - @test su2.min_charge_rate == [5.0 for t in 1:4] - @test su2.min_discharge_rate == [2.0 for t in 1:4] - @test su2.initial_level == 70.0 - @test su2.min_ending_level == 80.0 - @test su2.max_ending_level == 85.0 - @test sc.storage_units_by_name["su2"].name == "su2" - - su3 = sc.storage_units[3] - @test su3.bus.name == "b9" - @test su3.min_level == [10.0, 11.0, 12.0, 13.0] - @test su3.max_level == [100.0, 110.0, 120.0, 130.0] - @test su3.charge_cost == [2.0, 2.1, 2.2, 2.3] - @test su3.discharge_cost == [1.0, 1.1, 1.2, 1.3] - @test su3.charge_efficiency == [0.8, 0.81, 0.82, 0.82] - @test su3.discharge_efficiency == [0.85, 0.86, 0.87, 0.88] - @test su3.min_charge_rate == [5.0, 5.1, 5.2, 5.3] - @test su3.max_charge_rate == [10.0, 10.1, 10.2, 10.3] - @test su3.min_discharge_rate == [4.0, 4.1, 4.2, 4.3] - @test su3.max_discharge_rate == [8.0, 8.1, 8.2, 8.3] - - su4 = sc.storage_units[4] - @test su4.simultaneous_charge_and_discharge == - [false, false, true, true] + unit = sc.profiled_units[1] + @test unit.name == "gen1" + @test unit.bus.name == "b1" + @test sc.profiled_units_by_name["gen1"].name == "gen1" end end diff --git a/test/src/model/formulations_test.jl b/test/src/model/formulations_test.jl index 2b9307d..880b2c4 100644 --- a/test/src/model/formulations_test.jl +++ b/test/src/model/formulations_test.jl @@ -4,7 +4,7 @@ using UnitCommitment using JuMP -using Cbc +using HiGHS using JSON import UnitCommitment: ArrCon2000, @@ -16,7 +16,8 @@ import UnitCommitment: MorLatRam2013, PanGua2016, XavQiuWanThi2019, - WanHob2016 + WanHob2016, + PhaseAngleFormulation function _test( formulation::Formulation; @@ -24,11 +25,12 @@ function _test( dump::Bool = false, )::Nothing for instance_name in instances - instance = UnitCommitment.read(fixture("$(instance_name).json.gz")) + # instance = UnitCommitment.read(fixture("$(instance_name).json.gz")) + instance = UnitCommitment.read(fixture("$(instance_name).json")) model = UnitCommitment.build_model( instance = instance, formulation = formulation, - optimizer = Cbc.Optimizer, + optimizer = HiGHS.Optimizer, variable_names = true, ) set_silent(model) @@ -82,5 +84,11 @@ function model_formulations_test() instances = ["case14-flex"], ) end + @testset "Planning" begin + _test( + Formulation(transmission=PhaseAngleFormulation()), + instances = ["garver6"], + ) + end end end