Skip to content

Commit 7ab8dc8

Browse files
authored
Enable ACOPF polar formulation (#176)
* Add ACPlocal (default), ACPglobal, ACRlocal, ACRglobal options for opftype * Remove AC and AClocal options * Update formulation to use nonlinear expressions
1 parent 1791944 commit 7ab8dc8

File tree

11 files changed

+323
-280
lines changed

11 files changed

+323
-280
lines changed

docs/source/mods/opf/opf.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,13 @@ argument specifies otherwise.
222222
from gurobi_optimods import datasets
223223

224224
case = datasets.load_opf_example("case9")
225-
result = opf.solve_opf(case, opftype="AC")
225+
result = opf.solve_opf(case, opftype="ACRGLOBAL")
226226

227227
.. testoutput:: opf
228228
:options: +NORMALIZE_WHITESPACE +ELLIPSIS
229229

230230
...
231-
Optimize a model with 19 rows, 107 columns and 28 nonzeros
231+
Optimize a model with 18 rows, 106 columns and 24 nonzeros...
232232
...
233233
Optimal solution found...
234234
...
@@ -242,7 +242,7 @@ optimality within the time limit, the best known solution will be returned.
242242

243243
.. testcode:: opf
244244

245-
result = opf.solve_opf(case, opftype="AC", time_limit=60)
245+
result = opf.solve_opf(case, opftype="ACRGLOBAL", time_limit=60)
246246

247247
.. testoutput:: opf
248248
:hide:
@@ -406,7 +406,7 @@ whether branch switching allows a better solution.
406406

407407
case = datasets.load_opf_example("case9-switching")
408408
result = opf.solve_opf(
409-
case, opftype="AC",
409+
case, opftype="ACRGLOBAL",
410410
branch_switching=True,
411411
min_active_branches=0.1,
412412
time_limit=60,
@@ -417,7 +417,7 @@ whether branch switching allows a better solution.
417417
:options: +NORMALIZE_WHITESPACE +ELLIPSIS
418418

419419
...
420-
Optimize a model with 212 rows, 185 columns and 424 nonzeros
420+
Optimize a model with 211 rows, 184 columns and 420 nonzeros...
421421
...
422422

423423
Plotting the resulting solution shows that one branch has been turned off in the

src/gurobi_optimods/opf/api.py

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import logging
99

10+
import gurobipy as gp
11+
1012
from gurobi_optimods.opf import converters, grbformulator, violations
1113
from gurobi_optimods.utils import optimod
1214

@@ -16,7 +18,7 @@
1618
@optimod()
1719
def solve_opf(
1820
case,
19-
opftype="AC",
21+
opftype="ACPLOCAL",
2022
branch_switching=False,
2123
min_active_branches=0.9,
2224
use_mip_start=False,
@@ -54,7 +56,8 @@ def solve_opf(
5456
case : dict
5557
Dictionary holding case data
5658
opftype : str
57-
Desired OPF model type. One of ``AC``, ``AClocal``, ``ACrelax``, or ``DC``
59+
Desired OPF model type. One of ``ACRlocal``, ``ACPlocal``, ``ACRglobal``,
60+
``ACPglobal``, ``ACrelax``, or ``DC``. Defaults to ``ACPlocal``.
5861
branch_switching : bool, optional
5962
If set to True, enables branch switching.
6063
min_active_branches : float, optional
@@ -74,33 +77,68 @@ def solve_opf(
7477
fields
7578
"""
7679

77-
# use aclocal to run Gurobi as a local solver (stops at the first feasible solution)
78-
if opftype.lower() == "aclocal":
80+
# use acrlocal to run Gurobi as a local solver with the rectangular formulation (QCQP)
81+
if opftype.lower() == "acrlocal":
7982
opftype = "ac"
8083
useef = True
8184
usejabr = False
82-
default_solver_params = {
83-
"Presolve": 0,
84-
"SolutionLimit": 1,
85-
"NodeLimit": 0,
86-
"GURO_PAR_NLBARSLOPPYLIMIT": 2000,
87-
}
88-
elif opftype.lower() == "ac":
85+
polar = False
86+
version = gp.gurobi.version()
87+
if version >= (13, 0, 0):
88+
default_solver_params = {
89+
"OptimalityTarget": 1,
90+
}
91+
else:
92+
default_solver_params = {
93+
"Presolve": 0,
94+
"SolutionLimit": 1,
95+
"NodeLimit": 0,
96+
"GURO_PAR_NLBARSLOPPYLIMIT": 2000,
97+
}
98+
# use acplocal to run Gurobi as a local solver with the polar formulation (trigonometric functions)
99+
elif opftype.lower() == "acplocal":
100+
opftype = "ac"
101+
useef = False
102+
usejabr = False
103+
polar = True
104+
version = gp.gurobi.version()
105+
if version >= (13, 0, 0):
106+
default_solver_params = {
107+
"OptimalityTarget": 1,
108+
}
109+
else:
110+
default_solver_params = {
111+
"Presolve": 0,
112+
"SolutionLimit": 1,
113+
"NodeLimit": 0,
114+
"GURO_PAR_NLBARSLOPPYLIMIT": 2000,
115+
}
116+
elif opftype.lower() == "acrglobal":
89117
opftype = "ac"
90118
useef = True
91119
usejabr = True
120+
polar = False
121+
default_solver_params = {"MIPGap": 1e-3, "OptimalityTol": 1e-3}
122+
# Exact polar AC
123+
elif opftype.lower() == "acpglobal":
124+
opftype = "ac"
125+
useef = False
126+
usejabr = False
127+
polar = True
92128
default_solver_params = {"MIPGap": 1e-3, "OptimalityTol": 1e-3}
93129
# AC relaxation using the JABR inequality
94130
elif opftype.lower() == "acrelax":
95131
opftype = "ac"
96132
useef = False
97133
usejabr = True
134+
polar = False
98135
default_solver_params = {"MIPGap": 1e-3, "OptimalityTol": 1e-3}
99136
# DC linear approximation (ef & jabr are irrelevant)
100137
elif opftype.lower() == "dc":
101138
opftype = "dc"
102139
useef = False
103140
usejabr = False
141+
polar = False
104142
default_solver_params = {"MIPGap": 1e-4, "OptimalityTol": 1e-4}
105143
else:
106144
raise ValueError(f"Unknown opftype '{opftype}'")
@@ -115,8 +153,8 @@ def solve_opf(
115153
branchswitching=branch_switching,
116154
usemipstart=use_mip_start,
117155
minactivebranches=min_active_branches,
118-
polar=False,
119-
ivtype="aggressive",
156+
polar=polar,
157+
ivtype="aggressive", # ignored, no IV formulation used
120158
useactivelossineqs=False,
121159
)
122160

@@ -223,5 +261,6 @@ def compute_violations(case, voltages, polar=False, *, create_env):
223261
converters.grbmap_volts_from_dict(alldata, voltages)
224262

225263
# Compute model violations based on user input voltages
226-
with create_env() as env:
227-
return violations.compute_violations_from_voltages(env, alldata)
264+
if not polar:
265+
with create_env() as env:
266+
return violations.compute_violations_from_voltages(env, alldata)

src/gurobi_optimods/opf/grbformulator.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ def lpformulator_optimize(alldata, model, opftype):
112112
for zvar in alldata["MIP"]["zvar"].values():
113113
zvar.PStart = 1.0
114114
model.update()
115+
elif alldata["dopolar"]:
116+
vvar = alldata["LP"]["vvar"]
117+
buses = alldata["buses"]
118+
numbuses = alldata["numbuses"]
119+
for var in model.getVars():
120+
var.PStart = 0.0
121+
for j in range(1, 1 + numbuses):
122+
bus = buses[j]
123+
vvar[bus].PStart = 1.0
124+
model.update()
115125
model.optimize()
116126

117127
# Check model status and re-optimize if numerical trouble or inconclusive results.

0 commit comments

Comments
 (0)