77
88import logging
99
10+ import gurobipy as gp
11+
1012from gurobi_optimods .opf import converters , grbformulator , violations
1113from gurobi_optimods .utils import optimod
1214
1618@optimod ()
1719def 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 )
0 commit comments