Semidefinite Relaxation of AC Optimal Power Flow Problems

This notebook demonstrates the use of semidefinite relaxation techniques for AC optimal power flow (ACOPF) problems. A description of the problem formulation and its implementation can be found in the following papers:

M. S. Andersen, A. Hansson, L. Vandenberghe, "Reduced-Complexity Semidefinite Relaxations of Optimal Power Flow Problems", IEEE Transactions on Power Systems, 29 (4), pp. 1855–1863, 2014.

A. Eltved, J. Dahl, M. S. Andersen, "On the Robustness and Scalability of Semidefinite Relaxation for Optimal Power Flow Problems", arXiv (math.OC), 2018.

To run this notebook, you will need CVXOPT, CHOMPACK, MOSEK, and Matplotlib.

Test cases

MATPOWER provides a number of test cases that are basically MATLAB m-files, most of which (those that do not perform any computations) can be loaded directly from a file or from a url. For example, the following code

from opfsdr import opf
prob = opf('https://raw.githubusercontent.com/MATPOWER/matpower/master/data/case300.m')

downloads the case300 test case from the MATPOWER repository on Github and generates a semidefinite relaxation problem. Alternatively, using the Github API, a list of test cases and their urls can be retrieved from the MATPOWER repository as follows:


In [1]:
import json, re
import requests
testcases = {}
clist = []

# Retrieve list of MATPOWER test cases
response = requests.get('https://api.github.com/repos/MATPOWER/matpower/contents/data')
clist += json.loads(response.text)

# Retrieve list of pglib-opf test cases
response = requests.get('https://api.github.com/repos/power-grid-lib/pglib-opf/contents/')
clist += json.loads(response.text)
response = requests.get('https://api.github.com/repos/power-grid-lib/pglib-opf/contents/api')
clist += json.loads(response.text)
response = requests.get('https://api.github.com/repos/power-grid-lib/pglib-opf/contents/sad')
clist += json.loads(response.text)

# Build list of test cases
for c in clist:
    if not c['name'].endswith('.m'): continue
    casename = c['name'].split('.')[0]
    testcases[casename] = c['download_url']

Building and solving ACOPFs

The following example illustrates how to use opfsdr to download a test case, form a semidefinite relaxation problem, and how solve this problem:


In [2]:
from opfsdr import opf

#case = 'pglib_opf_case1354_pegase__sad'
case = 'case1354pegase'
print("Test case: %s" % case)
options = {'conversion': True,          # apply chordal conversion? (default: False)
           'tfill': 16,                 #   use clique merging heuristic based on fill (default: 0)
           'tsize': 0,                  #   use clique merging heuristic based on clique size (default: 0)
           'branch_rmin': 1e-5,         # minimum transmission line resistance (default: -inf)
           'line_constraints': True,    # include apparent power flow constraints in problem formulation
           'pad_constraints': True,     # include phase angle difference constraints
           'truncate_gen_bounds': 1e3,  # reduce large generator bounds (default: None)
           'verbose': 1,                # print info, progress, etc. (default: 0)
           'scale': False               # apply scaling heuristic to cone LP? (default: False)
          }

%time prob = opf(testcases[case], **options)

print(prob)


Test case: case1354pegase
Downloading case file: https://raw.githubusercontent.com/MATPOWER/matpower/master/data/case1354pegase.m.
Extracting data from case file.
Warning: generator at bus 4231 with large reactive bound(s); decreasing bound(s)
Warning: generator at bus 8109 with large reactive bound(s); decreasing bound(s)
Warning: branch (1780:549->5002) with small resistance; enforcing min. resistance
Building cone LP.
Applying chordal conversion to cone LP.
Converting to real-valued cone LP.
CPU times: user 905 ms, sys: 77.1 ms, total: 982 ms
Wall time: 1.04 s
Optimal power flow problem
* busses             :   1354
* generators         :    260
   -  const. gen.    :      0
   -  lin. cost      :    260
   -  quad. cost     :      0
* branches           :   1991
   -  flow constr.   :   1432
   -  phase constr.  :      0
   -  transformer    :    234

The semidefinite relaxation can be solved with MOSEK as follows:


In [3]:
# Set MOSEK tolerances 
from opfsdr import msk
msk.options[msk.mosek.dparam.intpnt_co_tol_pfeas] = 1e-9    # default: 1e-8
msk.options[msk.mosek.dparam.intpnt_co_tol_dfeas] = 1e-9    # default: 1e-8
msk.options[msk.mosek.dparam.intpnt_co_tol_rel_gap] = 1e-8  # default: 1e-7

In [4]:
# Solve semidefinite relaxation
%time sol = prob.solve(solver="mosek")


Problem
  Name                   :                 
  Objective sense        : max             
  Type                   : CONIC (conic optimization problem)
  Constraints            : 17345           
  Cones                  : 2864            
  Scalar variables       : 12340           
  Matrix variables       : 233             
  Integer variables      : 0               

Optimizer started.
Presolve started.
Linear dependency checker started.
Linear dependency checker terminated.
Eliminator - tries                  : 0                 time                   : 0.00            
Lin. dep.  - tries                  : 1                 time                   : 0.00            
Lin. dep.  - number                 : 0               
Presolve terminated. Time: 0.02    
Problem
  Name                   :                 
  Objective sense        : max             
  Type                   : CONIC (conic optimization problem)
  Constraints            : 17345           
  Cones                  : 2864            
  Scalar variables       : 12340           
  Matrix variables       : 233             
  Integer variables      : 0               

Optimizer  - threads                : 4               
Optimizer  - solved problem         : the primal      
Optimizer  - Constraints            : 13961
Optimizer  - Cones                  : 2864
Optimizer  - Scalar variables       : 11820             conic                  : 8592            
Optimizer  - Semi-definite variables: 233               scalarized             : 42951           
Factor     - setup time             : 0.17              dense det. time        : 0.00            
Factor     - ML order time          : 0.05              GP order time          : 0.00            
Factor     - nonzeros before factor : 1.16e+06          after factor           : 1.32e+06        
Factor     - dense dim.             : 0                 flops                  : 2.40e+08        
ITE PFEAS    DFEAS    GFEAS    PRSTATUS   POBJ              DOBJ              MU       TIME  
0   2.0e+00  2.6e+02  1.0e+00  0.00e+00   2.303769000e+02   2.303769000e+02   1.0e+00  0.24  
1   1.6e+00  2.0e+02  8.4e-01  -4.82e-01  4.984532114e+04   4.983775046e+04   8.0e-01  0.45  
2   1.2e+00  1.5e+02  1.1e+00  7.57e-01   2.019070196e+04   2.024149935e+04   6.0e-01  0.60  
3   6.2e-01  8.0e+01  1.1e+00  1.46e+00   1.536147295e+04   1.539061940e+04   3.1e-01  0.72  
4   4.9e-01  6.2e+01  1.0e+00  1.60e+00   1.244874634e+04   1.246879832e+04   2.4e-01  0.85  
5   2.7e-01  3.4e+01  7.8e-01  1.50e+00   9.110870356e+03   9.119821558e+03   1.3e-01  1.00  
6   1.9e-01  2.5e+01  6.7e-01  1.28e+00   7.126592995e+03   7.132704301e+03   9.7e-02  1.14  
7   9.9e-02  1.3e+01  4.8e-01  1.19e+00   4.516307800e+03   4.519226830e+03   5.0e-02  1.26  
8   8.9e-02  1.1e+01  4.5e-01  1.07e+00   4.111217789e+03   4.113817780e+03   4.4e-02  1.39  
9   5.1e-02  6.5e+00  3.3e-01  1.06e+00   2.662729721e+03   2.664183274e+03   2.5e-02  1.52  
10  2.9e-02  3.8e+00  2.5e-01  1.02e+00   1.740699218e+03   1.741547921e+03   1.5e-02  1.68  
11  2.3e-02  3.0e+00  2.3e-01  9.72e-01   1.457963194e+03   1.458643917e+03   1.2e-02  1.83  
12  1.4e-02  1.7e+00  1.7e-01  9.69e-01   9.591328179e+02   9.595368818e+02   6.8e-03  1.95  
13  8.9e-03  1.1e+00  1.3e-01  9.26e-01   6.786937407e+02   6.789619033e+02   4.5e-03  2.08  
14  7.7e-03  9.8e-01  1.2e-01  8.71e-01   5.941827103e+02   5.944153478e+02   3.8e-03  2.21  
15  6.2e-03  7.9e-01  1.1e-01  8.51e-01   4.877623510e+02   4.879536217e+02   3.1e-03  2.35  
16  5.1e-03  6.6e-01  9.5e-02  8.25e-01   4.018727027e+02   4.020333167e+02   2.6e-03  2.48  
17  3.8e-03  4.9e-01  8.0e-02  8.12e-01   2.834811309e+02   2.836028018e+02   1.9e-03  2.60  
18  3.4e-03  4.4e-01  7.4e-02  7.72e-01   2.475446113e+02   2.476555022e+02   1.7e-03  2.73  
19  3.0e-03  3.8e-01  6.7e-02  7.52e-01   1.964253381e+02   1.965222476e+02   1.5e-03  2.86  
20  2.6e-03  3.3e-01  6.1e-02  7.52e-01   1.510431280e+02   1.511279182e+02   1.3e-03  2.98  
21  2.2e-03  2.8e-01  5.5e-02  7.16e-01   1.062516689e+02   1.063251453e+02   1.1e-03  3.11  
22  2.0e-03  2.6e-01  5.2e-02  6.91e-01   8.127784325e+01   8.134577449e+01   1.0e-03  3.23  
23  1.8e-03  2.3e-01  4.9e-02  6.93e-01   5.327870822e+01   5.334054483e+01   9.1e-04  3.38  
24  1.5e-03  2.0e-01  4.3e-02  6.99e-01   1.070185998e+01   1.075485028e+01   7.7e-04  3.52  
25  1.3e-03  1.7e-01  3.9e-02  6.70e-01   -2.638583679e+01  -2.634033683e+01  6.6e-04  3.64  
26  1.3e-03  1.6e-01  3.8e-02  6.45e-01   -4.024658521e+01  -4.020263698e+01  6.3e-04  3.76  
27  1.0e-03  1.3e-01  3.3e-02  6.88e-01   -9.195792503e+01  -9.192287771e+01  5.0e-04  3.89  
28  8.1e-04  1.0e-01  2.9e-02  7.01e-01   -1.306441229e+02  -1.306155134e+02  4.1e-04  4.04  
29  7.6e-04  9.7e-02  2.7e-02  6.76e-01   -1.432846456e+02  -1.432580585e+02  3.8e-04  4.18  
30  6.8e-04  8.7e-02  2.5e-02  6.90e-01   -1.605281345e+02  -1.605042100e+02  3.4e-04  4.30  
31  5.6e-04  7.1e-02  2.2e-02  6.83e-01   -1.931575966e+02  -1.931383232e+02  2.8e-04  4.43  
32  4.7e-04  6.1e-02  2.0e-02  6.62e-01   -2.159108412e+02  -2.158947150e+02  2.4e-04  4.55  
33  4.3e-04  5.4e-02  1.8e-02  6.50e-01   -2.312965919e+02  -2.312823145e+02  2.1e-04  4.68  
34  3.9e-04  5.0e-02  1.7e-02  6.30e-01   -2.415804295e+02  -2.415674536e+02  2.0e-04  4.80  
35  3.3e-04  4.2e-02  1.5e-02  6.47e-01   -2.650038763e+02  -2.649935572e+02  1.6e-04  4.93  
36  2.9e-04  3.7e-02  1.4e-02  5.94e-01   -2.784135917e+02  -2.784048249e+02  1.5e-04  5.08  
37  2.5e-04  3.2e-02  1.2e-02  5.72e-01   -2.970208668e+02  -2.970138686e+02  1.2e-04  5.22  
38  2.4e-04  3.0e-02  1.2e-02  5.71e-01   -3.028472597e+02  -3.028407597e+02  1.2e-04  5.37  
39  2.3e-04  2.9e-02  1.1e-02  6.05e-01   -3.084237742e+02  -3.084175796e+02  1.1e-04  5.52  
40  1.9e-04  2.5e-02  1.0e-02  6.02e-01   -3.271228638e+02  -3.271180169e+02  9.6e-05  5.67  
41  1.8e-04  2.3e-02  9.7e-03  6.11e-01   -3.337313159e+02  -3.337268841e+02  9.0e-05  5.81  
42  1.6e-04  2.0e-02  8.7e-03  6.04e-01   -3.480127869e+02  -3.480093744e+02  7.8e-05  5.95  
43  1.4e-04  1.8e-02  8.2e-03  6.08e-01   -3.573020735e+02  -3.572991431e+02  7.1e-05  6.08  
44  1.2e-04  1.6e-02  7.3e-03  6.27e-01   -3.719991043e+02  -3.719969900e+02  6.1e-05  6.20  
45  1.2e-04  1.5e-02  7.2e-03  6.40e-01   -3.754300373e+02  -3.754279760e+02  5.8e-05  6.34  
46  1.0e-04  1.3e-02  6.6e-03  6.56e-01   -3.863551712e+02  -3.863536213e+02  5.1e-05  6.49  
47  8.9e-05  1.1e-02  5.9e-03  6.57e-01   -3.970607613e+02  -3.970597750e+02  4.4e-05  6.64  
48  8.2e-05  1.1e-02  5.6e-03  6.56e-01   -4.022994445e+02  -4.022987161e+02  4.1e-05  6.76  
49  7.4e-05  9.5e-03  5.2e-03  6.62e-01   -4.096133026e+02  -4.096128756e+02  3.7e-05  6.89  
50  6.0e-05  7.7e-03  4.5e-03  6.66e-01   -4.233256072e+02  -4.233256949e+02  3.0e-05  7.01  
51  5.5e-05  7.0e-03  4.2e-03  6.62e-01   -4.292625433e+02  -4.292628196e+02  2.7e-05  7.13  
52  5.0e-05  6.4e-03  4.0e-03  7.05e-01   -4.347213854e+02  -4.347217712e+02  2.5e-05  7.26  
53  4.3e-05  5.5e-03  3.6e-03  7.23e-01   -4.428613357e+02  -4.428618722e+02  2.2e-05  7.38  
54  3.7e-05  4.7e-03  3.3e-03  7.29e-01   -4.508230556e+02  -4.508237261e+02  1.8e-05  7.50  
55  2.9e-05  3.7e-03  2.8e-03  7.65e-01   -4.621674085e+02  -4.621681002e+02  1.4e-05  7.62  
56  2.8e-05  3.6e-03  2.8e-03  8.10e-01   -4.637127268e+02  -4.637133844e+02  1.4e-05  7.75  
57  1.9e-05  2.4e-03  2.2e-03  8.37e-01   -4.770091388e+02  -4.770097682e+02  9.4e-06  7.88  
58  1.8e-05  2.3e-03  2.1e-03  8.43e-01   -4.789954438e+02  -4.789960993e+02  8.8e-06  8.00  
59  1.1e-05  1.3e-03  1.6e-03  8.71e-01   -4.907992178e+02  -4.907997650e+02  5.3e-06  8.13  
60  3.8e-06  4.9e-04  9.1e-04  9.38e-01   -5.029113119e+02  -5.029115679e+02  1.9e-06  8.25  
61  2.6e-06  3.3e-04  7.3e-04  9.81e-01   -5.051639515e+02  -5.051641551e+02  1.3e-06  8.37  
62  1.9e-06  2.4e-04  6.2e-04  9.88e-01   -5.064300000e+02  -5.064301681e+02  9.5e-07  8.50  
63  8.8e-07  1.1e-04  4.1e-04  9.92e-01   -5.083961868e+02  -5.083962827e+02  4.4e-07  8.62  
64  6.8e-07  8.7e-05  3.6e-04  9.97e-01   -5.088021399e+02  -5.088022178e+02  3.4e-07  8.74  
65  2.8e-07  3.6e-05  2.2e-04  9.98e-01   -5.096321962e+02  -5.096322334e+02  1.4e-07  8.86  
66  2.1e-07  2.6e-05  1.9e-04  1.00e+00   -5.097875422e+02  -5.097875707e+02  1.0e-07  8.99  
67  8.4e-08  1.1e-05  1.2e-04  1.00e+00   -5.100550584e+02  -5.100550707e+02  4.2e-08  9.11  
68  6.2e-08  7.9e-06  1.0e-04  1.00e+00   -5.101041749e+02  -5.101041841e+02  3.1e-08  9.24  
69  1.1e-08  1.4e-06  4.2e-05  1.00e+00   -5.102177528e+02  -5.102177547e+02  5.6e-09  9.36  
70  2.7e-09  7.1e-07  2.1e-05  1.00e+00   -5.102375133e+02  -5.102375137e+02  1.3e-09  9.48  
71  4.4e-10  4.4e-06  7.8e-06  1.00e+00   -5.102428642e+02  -5.102428642e+02  1.9e-10  9.61  
Optimizer terminated. Time: 9.63    


Interior-point solution summary
  Problem status  : PRIMAL_AND_DUAL_FEASIBLE
  Solution status : OPTIMAL
  Primal.  obj: -5.1024286416e+02   nrm: 2e+03    Viol.  con: 8e-05    var: 7e-06    barvar: 0e+00    cones: 0e+00  
  Dual.    obj: -5.1024286467e+02   nrm: 4e+03    Viol.  con: 0e+00    var: 4e-10    barvar: 1e-10    cones: 3e-10  
CPU times: user 18.8 s, sys: 914 ms, total: 19.7 s
Wall time: 10.3 s

In [5]:
print("Solver status: %s" % sol['status'])


Solver status: optimal

The objective value associated with the semidefinite relaxation does not include constant cost, and hence it is not necessarily equal to the cost of generation (the cost of generation is available as sol['cost']):


In [6]:
print("Generation cost: %.2f USD/hour (fixed cost: %.2f USD/hour)"%(sol['cost'],prob.const_cost))


Generation cost: 74061.98 USD/hour (fixed cost: 23037.69 USD/hour)

Eigenvalue ratio(s)


In [7]:
%pylab inline
from pylab import plot,xlabel,ylabel,arange,linspace,hist,xscale,rcParams
rcParams['figure.figsize'] = (10, 6)

if len(sol['eigratio']) == 1:
    print("Eigenvalue ratio: %.2e" % (sol['eigratio'][0]))
else:
    import numpy as np
    hist(sol['eigratio'],bins=[10.**a for a in linspace(0,9,100)])
    xscale('log')
    xlabel('Clique eigenvalue ratio')
    ylabel('Frequency')
    print("Cliques with eigenvalue ratio less than 1e5: %i of %i"\
          % (len([1 for evr in sol['eigratio'] if evr < 1e5]),len(sol['eigratio'])))


Populating the interactive namespace from numpy and matplotlib
Cliques with eigenvalue ratio less than 1e5: 2 of 233

Generation


In [8]:
pmin = np.array([gen['Pmin'] for gen in prob.generators])*prob.baseMVA
pmax = np.array([gen['Pmax'] for gen in prob.generators])*prob.baseMVA
hist((np.array(sol['Sg'].real().T).squeeze()-pmin)/(pmax-pmin),20)
xlabel(r'$\frac{P_{\mathrm{g}}-P_{\mathrm{g}}^{\min}}{P_{\mathrm{g}}^{\max} - P_{\mathrm{g}}^{\min}}$')
ylabel('Number of generators');



In [9]:
pmin = np.array([gen['Qmin'] for gen in prob.generators])*prob.baseMVA
pmax = np.array([gen['Qmax'] for gen in prob.generators])*prob.baseMVA
hist((np.array(sol['Sg'].imag().T).squeeze()-pmin)/(pmax-pmin),20)
xlabel(r'$\frac{Q_{\mathrm{g}}-Q_{\mathrm{g}}^{\min}}{Q_{\mathrm{g}}^{\max} - Q_{\mathrm{g}}^{\min}}$')
ylabel('Number of generators');


Voltage magnitude constraints


In [10]:
minvm = np.array([b['minVm'] for b in prob.busses])
maxvm = np.array([b['maxVm'] for b in prob.busses])
hist((np.array(sol['Vm']).squeeze()-minvm)/(maxvm-minvm),20)
xlabel(r'$\frac{|V| - V_{\min}}{V_{\max}-V_{\min}}$')
ylabel('Number of busses');


Flow constraints


In [11]:
if prob.branches_with_flow_constraints():
    Sapp = np.array(abs(sol['St'])).squeeze()
    Smax = np.array([br['rateA'] for _,br in prob.branches_with_flow_constraints()])
    hist(Sapp/Smax,20)
    
    pyplot.xlabel(r'$\frac{|S_{i,j}|}{S_{i,j}^{\max}}$')
    pyplot.ylabel('Number of transmission lines')
else:
    print("No flow constraints.")