Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
95fee33
Initial commit of Benders implementation mid debugging
dlcole3 Jun 3, 2026
54fde4b
Fixed bug in allam cycle
dlcole3 Jun 3, 2026
fba3c37
Added support for VRE STOR pre-debugging
dlcole3 Jun 9, 2026
3d8eaa3
Debugged VRE Stor with LDES and non LDES
dlcole3 Jun 12, 2026
9d73440
Debugging LDS CAPRES with VRE STOR
dlcole3 Jun 15, 2026
2e818f3
updated changelog, updated examples
dlcole3 Jun 16, 2026
ded9de9
Fixed bug in docs, updated Benders tests
dlcole3 Jun 17, 2026
a211609
Made benders tests faster via TDR, added doc strings
dlcole3 Jun 17, 2026
0e38082
removed extra yaml file, added line for debugging tests
dlcole3 Jun 18, 2026
060be79
built benders test cases to use prerun TDR files
dlcole3 Jun 18, 2026
b961555
Updated API for accessing more workers
dlcole3 Jun 18, 2026
5282a28
Fixed @everywhere call in case runner for Benders
dlcole3 Jun 18, 2026
e6e384c
Updated tests to avoid numerical issues with HiGHS
dlcole3 Jun 22, 2026
807631d
Switched to simplex to avoid hanging in Julia 1.9 and increased toler…
dlcole3 Jun 22, 2026
0f419be
attempt to fix error in writing outputs tests workflow
dlcole3 Jun 22, 2026
51ba878
Tightened multistage convergence tolerance
dlcole3 Jun 22, 2026
2a4dd7e
added fallback on multistage testing for degenerate solutions in DDP
dlcole3 Jun 22, 2026
411d5cc
Reverted to IPM for writing outputs
dlcole3 Jun 22, 2026
ff14589
Trying parameter scale = 1
dlcole3 Jun 22, 2026
2a6b63d
added fallback
dlcole3 Jun 22, 2026
6c8ddaa
Initial integer flag addition
dlcole3 Jun 26, 2026
f0d8bed
Removed outdated comments
dlcole3 Jun 26, 2026
ec567fe
Removed println debug statements
dlcole3 Jun 30, 2026
babcd9b
Updated loading functions to handle new DCOPF case for networks; adde…
dlcole3 Jul 8, 2026
a281dca
Added initial DCOPF src code for vFLOW
dlcole3 Jul 9, 2026
d371230
Added bilinear, example, tests, model construction
dlcole3 Jul 10, 2026
00425e7
Changed names to discrete_builds, added test cases, added LP relaxati…
dlcole3 Jul 14, 2026
c7374d5
Docs pages from Claude before readthrough
dlcole3 Jul 14, 2026
d421c7b
Read through and edited Claude's documentation
dlcole3 Jul 15, 2026
abdb5fe
Updated Changelong and added another note to docs
dlcole3 Jul 15, 2026
a4256a3
Cleaned up run files
dlcole3 Jul 15, 2026
52229df
Added notes on parallel lines on same corridor to docs
dlcole3 Jul 15, 2026
72945e5
Added note on reconductoring
dlcole3 Jul 15, 2026
f35a601
Updated tests to use a different solver method in HiGHS for DCOPF exp…
dlcole3 Jul 16, 2026
11f2475
Skipping Benders test for Julia v1.6 due to MES issue
dlcole3 Jul 16, 2026
d84ed49
Updated HiGHS settings to pass test
dlcole3 Jul 17, 2026
6c0ffc9
Turned off HiGSH presolve on multifuels
dlcole3 Jul 17, 2026
f09df51
Adding print line to debug Github tests
dlcole3 Jul 17, 2026
a80a2c3
Updated debug call
dlcole3 Jul 17, 2026
ac76f78
Reverted highs settings
dlcole3 Jul 17, 2026
98b46c1
Fixed compute_conflicts test so it handles cases where HiGHS does ret…
dlcole3 Jul 21, 2026
5739d54
fixed big M constraint on phantom lines
dlcole3 Jul 28, 2026
0e5e77b
Removed legacy model code
dlcole3 Aug 7, 2026
2fc88f1
Added solve time recoery and key recovery
dlcole3 Aug 7, 2026
a8a8eb9
Fixed phantom line construction
dlcole3 Aug 7, 2026
2a73908
Added changelog, updated settings to speed up test
dlcole3 Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `Run_benders.jl` entry point scripts to example systems 1–5, 7, 10, and 11, enabling Benders decomposition runs for each of those cases.
- A generalized hourly matching policy module.
- Benders decomposition output now writes additional files matching the monolithic solver: `prices.csv`, `reliability.csv`, `storagebal_duals.csv`, `capacityfactor.csv`, `time_weights.csv`, `EnergyRevenue.csv`, `ChargingCost.csv`, `commit.csv`, `start.csv`, `shutdown.csv` (when UCommit≥1), `CO2_prices_and_penalties.csv` (when CO2Cap>0), and `SubsidyRevenue.csv`/`RegSubsidyRevenue.csv` (when MinCap/MinCapReq is active).
- DC-OPF transmission expansion: `NetworkExpansion` can now be combined with `DC_OPF: 1`, so line capacity is a decision variable while the linearized DC power-flow physics (phase angles, flow–angle coupling, angle-difference limits) are still enforced. New `src/model/core/transmission/dcopf_transmission.jl` splits lines into discrete-candidate and fixed sets and applies the flow–angle coupling and angle limits appropriately to each.
- Discrete (integer) transmission builds via a new `DiscreteInvestments` setting flag and a `Discrete_Build` column in `Network.csv`. A corridor flagged `Discrete_Build = 1` is expanded as one or more fixed-size new circuits (`New_Line_Cap_Size_MW`), each with a binary build decision (`vNEW_TRANS_LINES`), instead of the continuous `vNEW_TRANS_CAP` reinforcement. The number of candidate circuits is derived from `floor(Line_Max_Reinforcement_MW / New_Line_Cap_Size_MW)` at load time, with each candidate becoming its own internal line.
- Build-gated DC-OPF physics for discrete candidate lines: an unbuilt candidate line imposes no flow–angle coupling, so a greenfield corridor constrains phase angles only once at least one circuit is actually built. Candidates on a corridor with no fixed line (`NEW_CORRIDOR_DISCRETE_LINES`) additionally carry a build-gated angle-difference limit, since nothing else can supply the corridor's limit; candidates parallel to a fixed line inherit that line's always-on limit via the shared angle difference. A greenfield discrete corridor emits no zero-capacity residual row at all, preventing an unbuilt corridor from pinning the bus-angle difference of the two zones it would connect.
- Two flow–angle coupling formulations for discrete builds, selected with the `Bilinear_DC_OPF` flag: a big-M relaxation (`Bilinear_DC_OPF: 0`, default) that keeps the model a MILP, and an exact bilinear formulation (`Bilinear_DC_OPF: 1`) that requires a solver supporting bilinear terms (e.g. Gurobi). Per-line big-M values are read from an optional `BigM` column of `Network.csv`.
- Symmetry-breaking constraints on groups of identical parallel discrete candidate lines (`DISCRETE_BUILD_LINE_GROUPS`), imposing a build order to reduce branch-and-bound degeneracy.
- `DiscreteInvestments` additionally enables integer capacity builds for *resources* via a `Discrete_Build` column in the resource CSVs (e.g. `Thermal.csv`), setting the corresponding `vCAP` (and retirement) variables to integer.
- Benders support for DC-OPF expansion, driven by new `benders_settings.yml` flags: `RunTransportModel` (warm-start the cuts with a transport-model relaxation before enforcing DC-OPF), `LPTransportHotstart` and `LPDCOPFHotstart` (LP-relax the planning problem's integer/binary variables for the transport and DC-OPF passes, then restore integrality), and `RegularizationPostHotstart` (keep level-set regularization active after the hot-start passes). When any LP hot-start is enabled, GenX now forces MacroEnergySolvers' own `IntegerInvestment` routine off to avoid a redundant, conflicting integer relaxation.
- New example system `example_systems/12_IEEE_9_bus_DC_OPF_expansion/` and test suite `test/test_DCOPF_expansion.jl` covering monolithic and Benders solves of DC-OPF with continuous and discrete transmission expansion.
- User Guide page `docs/src/User_Guide/dcopf_transmission_expansion.md` documenting the transport vs. DC-OPF models, continuous vs. discrete expansion, the five `Network.csv` corridor scenarios, the Benders hot-start flags, and the recommendation to use discrete builds for greenfield corridors under DC-OPF.

### Changed
- `capacity_decisions.jl` refactored to support both monolithic and Benders decomposition modes, cleanly separating capacity (master problem) and operational (subproblem) variables.
Expand Down
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.4.6"

[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
ClusterManagers = "34f1f09b-3a8b-5176-ab39-66d58a4d544e"
Clustering = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Expand All @@ -14,6 +15,7 @@ Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Expand Down
3 changes: 2 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ pages = OrderedDict(
"Model Inputs" => "User_Guide/model_input.md",
"Time-domain Reduction Inputs" => "User_Guide/TDR_input.md",
"Running the Time-domain Reduction" => "User_Guide/running_TDR.md",
"Benders Decomposition" => "User_Guide/benders_decomposition.md",
"DC-OPF and Transmission Expansion" => "User_Guide/dcopf_transmission_expansion.md",
"Benders Decompositiion" => "User_Guide/benders_decomposition.md",
"MGA package" => "User_Guide/generate_alternatives.md",
"Multi-stage Model" => "User_Guide/multi_stage_input.md",
"Slack Variables for Policies" => "User_Guide/slack_variables_overview.md",
Expand Down
13 changes: 13 additions & 0 deletions docs/src/Model_Reference/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ Pages = ["operational_reserves.jl"]
```

## Transmission
This section covers the three transmission modules, whose docstrings follow below:

- `transmission!` — line flows, flow limits, and losses.
- `investment_transmission!` — transmission expansion: continuous line reinforcement
(`vNEW_TRANS_CAP`) and, when `DiscreteInvestments = 1`, discrete fixed-size new lines with binary
build decisions (`vNEW_TRANS_LINES`). `discrete_build_symmetry!` breaks the symmetry between
identical parallel candidate lines.
- `dcopf_transmission!` — the linearized DC power-flow constraints relating line flows to zonal
voltage phase angles, including the build-gated formulation applied to discrete candidate lines.

See [DC-OPF and Transmission Expansion](@ref) for a user-facing discussion of the two expansion
mechanisms, how they interact with DC-OPF, and how to choose between them.

```@autodocs
Modules = [GenX]
Pages = ["transmission.jl"]
Expand Down
11 changes: 11 additions & 0 deletions docs/src/Model_Reference/write_outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ GenX.write_allam_capacity
GenX.write_allam_output
```

## Write Transmission Outputs
When discrete new lines are present (`DiscreteInvestments = 1`), `load_network_data!` gives each
candidate line its own row, so the solved model has more lines than the user's `Network.csv`. These
writers fold the expanded lines back onto the original corridors using
`inputs["LINE_MAP_ORIGINAL"]`, so the outputs are indexed by the corridors the user supplied. See
[DC-OPF and Transmission Expansion](@ref).
```@autodocs
Modules = [GenX]
Pages = ["write_transmission_flows.jl", "write_transmission_losses.jl", "write_nw_expansion.jl"]
```

## Write Benders Decomposition Outputs
```@autodocs
Modules = [GenX]
Expand Down
63 changes: 61 additions & 2 deletions docs/src/User_Guide/benders_decomposition.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
## Benders Decomposition

Benders Decomposition is a powerful optimization technique used to solve large-scale problems by breaking them into a smaller master problem and one or more subproblems The master problem contains a set of "complicating variables" that, once fixed in the subproblems, makes the subproblems easier to solve. The algorithm works iteratively: it solves the master problem, fixes that solution in the subproblems, solves the subproblems, and then returns cuts (typically dual information) back to the master problem that refine the solution space. This process of passing information (primal and dual variables) between the master and subproblems continues until an upper and lower bound converge. For further details on Benders decomposition in capacity expansion models, please see this paper by [Jacobson et al.](https://pubsonline.informs.org/doi/abs/10.1287/ijoo.2023.0005) ([preprint](https://arxiv.org/abs/2302.10037)) or this paper by [Pecci and Jenkins](https://ieeexplore.ieee.org/abstract/document/10829583) ([preprint](https://arxiv.org/abs/2403.02559)). The mathematical formulation for the capacity expansion model and how it is decomposed is shown in more detail in the [Benders Decomposition Overview](@ref).
Benders Decomposition is a powerful optimization technique used to solve large-scale problems by breaking them into a smaller master problem and one or more subproblems. The master problem contains a set of "complicating variables" that, once fixed in the subproblems, makes the subproblems easier to solve. The algorithm works iteratively: it solves the master problem, fixes that solution in the subproblems, solves the subproblems, and then returns cuts (typically dual information) back to the master problem that refine the solution space. This process of passing information (primal and dual variables) between the master and subproblems continues until an upper and lower bound converge. For further details on Benders decomposition in capacity expansion models, please see this paper by [Jacobson et al.](https://pubsonline.informs.org/doi/abs/10.1287/ijoo.2023.0005) ([preprint](https://arxiv.org/abs/2302.10037)) or this paper by [Pecci and Jenkins](https://ieeexplore.ieee.org/abstract/document/10829583) ([preprint](https://arxiv.org/abs/2403.02559)). The mathematical formulation for the capacity expansion model and how it is decomposed is shown in more detail in the [Benders Decomposition Overview](@ref).

Benders decomposition can be especially useful when the problem can be decomposed with several independent subproblems. This is because the subproblems can each be solved in parallel after the master problem solve is complete. GenX exploits this ability by decomposing the problem in time and solving with representative time periods, such that the linking between time periods is only captured inside the master problem. This allows for each representative period to be solved independently in parallel during a single iteration of Benders. For this reason, it is generally advised that the user provide their data as representative periods or to use the time domain reduction capability of GenX, which automatically creates the representative periods for use in Benders.


Benders decomposition is accessed by setting `Benders: 1` in the `genx_settings.yml` file. In addition, the user must planning problem and subproblem solver settings as `[solver_name]_benders_planning_settings.yml` and `[solver_name]__benders_subprob_settings.yml` as well as a settings files for Benders called `benders_settings.yml`. Internally, GenX calls [MacroEnergySolvers.jl](https://github.com/macroenergy/MacroEnergySolvers.jl/tree/main) to run the Benders algorithm. The settings which can be passed to the `benders_settings.yml` file are shown in the following table.
Benders decomposition is accessed by setting `Benders: 1` in the `genx_settings.yml` file. In addition, the user must planning problem and subproblem solver settings as `[solver_name]_benders_planning_settings.yml` and `[solver_name]__benders_subprob_settings.yml` as well as a settings files for Benders called `benders_settings.yml`. Internally, GenX calls [MacroEnergySolvers.jl](https://github.com/macroenergy/MacroEnergySolvers.jl/tree/main) to run the Benders algorithm. The settings which can be passed to the `benders_settings.yml` file are shown in the following table and are frequently equivalent to the MacroEnergySolvers settings.

|**Parameter** | **Allowed Values** |**Description**|
| :------------ | :-----------|:-----------|
Expand All @@ -19,6 +19,65 @@ Benders decomposition is accessed by setting `Benders: 1` in the `genx_settings.
| Distributed | $\{true, false\}$ | Whether to distribute subproblems to remote workers. When `true`, GenX will automatically launch the required worker processes. |
| NWorkers | $-1$ or $\in \mathbb{Z}_+$ | Number of Julia worker processes to use for parallel subproblem solving. Only used when `Distributed: true`. `-1` (the default) sizes the worker pool automatically; a value greater than 1 requests that many workers explicitly; `0` or `1` disables parallel execution. |
| ExpectFeasibleSubproblems | $\{true, false\}$ | # If true, skip feasibility cuts (assumes subproblems are always feasible); safe to leave false|
| RunTransportModel | $\{true, false\}$ | Only for DC-OPF expansion. Solve the transport relaxation first and reuse its cuts as a warm start. Requires `DC_OPF: 1`. |
| LPTransportHotstart | $\{true, false\}$ | Only with `RunTransportModel: true`. LP-relax the planning problem's integer/binary variables for the transport pass, then restore them and run it again. |
| LPDCOPFHotstart | $\{true, false\}$ | Only for DC-OPF expansion. LP-relax the planning problem's integer/binary variables for the DC-OPF pass, then restore them and run it again. |
| RegularizationPostHotstart | $\{true, false\}$ | If true, keep level-set regularization (`StabParam`) on for the final pass. If false, `StabParam` is forced to 0 once the hot-start passes are done. |

### Benders for DC-OPF with Transmission Expansion

When transmission expansion is combined with DC-OPF (see [DC-OPF and Transmission Expansion](@ref)),
the line-build decisions live in the planning problem, but the DC-OPF constraints can slow convergence. Fruther, the problem
can be infeasible for some early planning solutions, so the first iterations may produce poor cuts.

The last four settings above stage the solve to address this. GenX runs a sequence of `benders` passes
over the *same* planning problem, so cuts accumulate across passes:

1. **Transport pass** (`RunTransportModel: true`). The subproblems are built **without** the DC-OPF
flow–angle constraints, i.e. as a transport model. Because the transport model is a relaxation
of DC-OPF on the same system, the optimality cuts it generates are valid underestimators of the
true DC-OPF recourse cost. They remain valid when DC-OPF is switched on, and the
planning problem is carried over as-is.
2. **DC-OPF pass**. The subproblems gain the DC-OPF constraints and Benders continues against the
warm-started planning problem.
3. **LP hot-starts** (`LPTransportHotstart`, `LPDCOPFHotstart`). Each of the above passes can be
preceded by a pass in which the planning problem's integer and binary variables are relaxed. The
LP relaxation is driven to convergence, generating cuts, and integrality is restored
and the pass re-run against the accumulated cuts.
4. **Regularization** (`RegularizationPostHotstart`). In practice, level-set regularization (`StabParam > 0`)
may help most in the early, cut-poor iterations and when the problem is an LP. By default GenX turns it off (`StabParam = 0`) once
the hot-start passes are complete. Set `RegularizationPostHotstart: true` to keep it on throughout.


These ideas are outlined in the manuscript available [here](https://arxiv.org/abs/2603.29867).

A typical configuration:

```yaml
DC_OPF: 1 # in genx_settings.yml
NetworkExpansion: 1 # in genx_settings.yml
DiscreteInvestments: 1 # in genx_settings.yml

# benders_settings.yml
RunTransportModel: true
LPTransportHotstart: true
LPDCOPFHotstart: true
RegularizationPostHotstart: false
StabParam: 0.5
ExpectFeasibleSubproblems: false
```


!!! warning "`IntegerInvestment` and the LP hot-starts are mutually exclusive"
`IntegerInvestment: true` asks *MacroEnergySolvers* to run its own integer routine: it relaxes
the planning problem's integer variables on entry to `benders` and restores them once the
relaxation has converged. `LPTransportHotstart` / `LPDCOPFHotstart` do the same thing, at the
level of a whole `benders` pass and give slightly more flexibility to the user. `IntegerInvestment = true` is reset to `false` in the DC_OPF case and the user must use the GenX settings flag in this case.
GenX detects this combination, warns, and forces `IntegerInvestment: false`. If you want
MacroEnergySolvers to own the integer relaxation instead, set both hot-start flags to `false`.

!!! note "`ExpectFeasibleSubproblems` set to `false` can require longer build times when hot starting with the transport model."
Currently, MacroEnergySolvers with `ExpectFeasibleSubproblems=false` adds a slack variable that facilitates feasibility cuts. When hot-starting with the transport model, the slacks are added to the subproblems under the transport model, solved to optimality, and then DCOPF constraints are added to the subproblems. MacroEnergySolvers would throw an error if it were asked to resolve with the slacks already in place. Consequently, the subproblems are regenerated between each hot-starting step. To avoid this, the user can set `ExpectFeasibleSubproblems=true`, but it is possible that this results in infeasible solutions.

### Running Benders in Parallel

Expand Down
Loading
Loading