sdpa-python 0.2.2__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdpa_python-0.2.2.dist-info/LICENSE +339 -0
- sdpa_python-0.2.2.dist-info/METADATA +69 -0
- sdpa_python-0.2.2.dist-info/RECORD +24 -0
- sdpa_python-0.2.2.dist-info/WHEEL +5 -0
- sdpa_python-0.2.2.dist-info/top_level.txt +1 -0
- sdpap/__init__.py +28 -0
- sdpap/convert.py +348 -0
- sdpap/fileio.py +836 -0
- sdpap/fvelim/__init__.py +24 -0
- sdpap/fvelim/fvelim.py +256 -0
- sdpap/fvelim/fvelimext.cp313-win_amd64.pyd +0 -0
- sdpap/matdata.py +80 -0
- sdpap/param.py +366 -0
- sdpap/sdpacall/__init__.py +24 -0
- sdpap/sdpacall/sdpa.cp313-win_amd64.pyd +0 -0
- sdpap/sdpacall/sdpacall.py +71 -0
- sdpap/sdpap.py +450 -0
- sdpap/sdpaputils.py +140 -0
- sdpap/spcolo/__init__.py +26 -0
- sdpap/spcolo/asputils.py +182 -0
- sdpap/spcolo/clique.py +68 -0
- sdpap/spcolo/spcolo.py +356 -0
- sdpap/spcolo/spcoloext.cp313-win_amd64.pyd +0 -0
- sdpap/symcone.py +157 -0
sdpap/sdpap.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
SDPAP (SDPA Python Interface)
|
|
4
|
+
SDPAP solves a Conic-form Linear Optimization Problem (CLP) of the form:
|
|
5
|
+
minimize c^T x
|
|
6
|
+
subject to Ax - b \in J, x \in K
|
|
7
|
+
K = (K.f, K.l, K.q, K.s)
|
|
8
|
+
J = (J.f, J.l, J.q, J.s)
|
|
9
|
+
|
|
10
|
+
Copyright (C) 2010-2022 SDPA Project
|
|
11
|
+
|
|
12
|
+
This program is free software; you can redistribute it and/or modify
|
|
13
|
+
it under the terms of the GNU General Public License as published by
|
|
14
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
15
|
+
(at your option) any later version.
|
|
16
|
+
|
|
17
|
+
This program is distributed in the hope that it will be useful,
|
|
18
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
19
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
20
|
+
GNU General Public License for more details.
|
|
21
|
+
|
|
22
|
+
You should have received a copy of the GNU General Public License along
|
|
23
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
24
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
25
|
+
|
|
26
|
+
September 2010: Originally written by Kenta Kato
|
|
27
|
+
December 2010: Modified for SciPy
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
__all__ = ['solve']
|
|
31
|
+
|
|
32
|
+
from . import convert
|
|
33
|
+
from . import sdpaputils
|
|
34
|
+
from . import fileio
|
|
35
|
+
from .param import param
|
|
36
|
+
from .symcone import SymCone
|
|
37
|
+
from .sdpacall import sdpacall
|
|
38
|
+
from .spcolo import spcolo
|
|
39
|
+
from .fvelim import fvelim
|
|
40
|
+
from scipy import sparse
|
|
41
|
+
import numpy as np
|
|
42
|
+
import copy
|
|
43
|
+
import time
|
|
44
|
+
import warnings
|
|
45
|
+
|
|
46
|
+
def solve(A, b, c, K, J, option={}):
|
|
47
|
+
"""Solve CLP by SDPA
|
|
48
|
+
|
|
49
|
+
If J.l or J.q or J.s > 0, clp_toLMI() or clp_toEQ() is called before solve.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
A, b, c: Scipy matrices to denote the CLP.
|
|
53
|
+
K, J: Symcone object to denote the CLP.
|
|
54
|
+
option: Parameters. If None, default parameters is used.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A tuple (x, y, sdpapinfo, timeinfo, sdpainfo).
|
|
58
|
+
x, y: Primal and Dual solutions
|
|
59
|
+
sdpapinfo, timeinfo, sdpainfo: Result information
|
|
60
|
+
"""
|
|
61
|
+
timeinfo = dict()
|
|
62
|
+
timeinfo['total'] = time.time()
|
|
63
|
+
|
|
64
|
+
if 'print' not in option:
|
|
65
|
+
option['print'] = 'display'
|
|
66
|
+
verbose = len(option['print']) != 0 and option['print'] != 'no'
|
|
67
|
+
maybe_print = print if verbose else lambda *a, **k: None
|
|
68
|
+
|
|
69
|
+
# --------------------------------------------------
|
|
70
|
+
# Set parameter
|
|
71
|
+
# --------------------------------------------------
|
|
72
|
+
backend_info = sdpacall.get_backend_info()
|
|
73
|
+
option = param(option, backend_info["gmp"])
|
|
74
|
+
maybe_print('---------- SDPAP Start ----------')
|
|
75
|
+
|
|
76
|
+
# Write to output file
|
|
77
|
+
if option['resultFile']:
|
|
78
|
+
fpout = open(option['resultFile'], 'w')
|
|
79
|
+
fileio.write_version(fpout)
|
|
80
|
+
fileio.write_parameter(fpout, option)
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------
|
|
83
|
+
# Check validity
|
|
84
|
+
# --------------------------------------------------
|
|
85
|
+
if not K.check_validity() or not J.check_validity():
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
if not isinstance(b, np.ndarray) and not sparse.issparse(b):
|
|
89
|
+
raise TypeError('sdpap.solve(): b must be a np.ndarray or a sparse matrix.')
|
|
90
|
+
if not isinstance(c, np.ndarray) and not sparse.issparse(c):
|
|
91
|
+
raise TypeError('sdpap.solve(): c must be a np.ndarray or a sparse matrix.')
|
|
92
|
+
if not isinstance(A, np.ndarray) and not sparse.issparse(A):
|
|
93
|
+
raise TypeError('sdpap.solve(): A must be a np.ndarray or a sparse matrix.')
|
|
94
|
+
|
|
95
|
+
if isinstance(b, np.ndarray) and len(b.shape) > 2:
|
|
96
|
+
raise ValueError('sdpap.solve(): Expected 1D or 2D ndarray for b')
|
|
97
|
+
if isinstance(c, np.ndarray) and len(c.shape) > 2:
|
|
98
|
+
raise ValueError('sdpap.solve(): Expected 1D or 2D ndarray for c')
|
|
99
|
+
if isinstance(A, np.ndarray) and len(A.shape) != 2:
|
|
100
|
+
raise ValueError('sdpap.solve(): Expected 2D ndarray for A')
|
|
101
|
+
|
|
102
|
+
if not sparse.isspmatrix_csc(b):
|
|
103
|
+
b = sparse.csc_matrix(b)
|
|
104
|
+
if b.shape[1] != 1:
|
|
105
|
+
b = (b.T).tocsc()
|
|
106
|
+
|
|
107
|
+
if not sparse.isspmatrix_csc(c):
|
|
108
|
+
c = sparse.csc_matrix(c)
|
|
109
|
+
if c.shape[1] != 1:
|
|
110
|
+
c = (c.T).tocsc()
|
|
111
|
+
|
|
112
|
+
if not sparse.isspmatrix_csc(A):
|
|
113
|
+
A = sparse.csc_matrix(A)
|
|
114
|
+
|
|
115
|
+
size_row = max(b.shape)
|
|
116
|
+
size_col = max(c.shape)
|
|
117
|
+
mA, nA = A.shape
|
|
118
|
+
|
|
119
|
+
totalSize_n = K.f + K.l + sum(K.q) + sum(z ** 2 for z in K.s)
|
|
120
|
+
totalSize_m = J.f + J.l + sum(J.q) + sum(z ** 2 for z in J.s)
|
|
121
|
+
if size_row != mA or size_col != nA:
|
|
122
|
+
maybe_print("Size A[m = %d, n = %d], b[m = %d], c[n = %d] ::" %
|
|
123
|
+
(mA, nA, size_row, size_col))
|
|
124
|
+
maybe_print("nnz(A) = %d, nnz(c) = %d" % (A.nnz, c.nnz))
|
|
125
|
+
raise ValueError('Inconsistent Size')
|
|
126
|
+
if size_col != totalSize_n:
|
|
127
|
+
maybe_print("Size A[m = %d, n = %d], b[m = %d], c[n = %d] ::" %
|
|
128
|
+
(mA, nA, size_row, size_col))
|
|
129
|
+
maybe_print("nnz(A) = %d, nnz(c) = %d" % (A.nnz, c.nnz))
|
|
130
|
+
raise ValueError("Inconsistent Size c[n = %d], K[%d]"
|
|
131
|
+
% (size_col, totalSize_n))
|
|
132
|
+
if size_row != totalSize_m:
|
|
133
|
+
maybe_print("Size A[m = %d, n = %d], b[m = %d], c[n = %d] ::" %
|
|
134
|
+
(mA, nA, size_row, size_col))
|
|
135
|
+
maybe_print("nnz(A) = %d, nnz(c) = %d" % (A.nnz, c.nnz))
|
|
136
|
+
raise ValueError("Inconsistent Size b[n = %d], J[%d]"
|
|
137
|
+
% (size_row, totalSize_m))
|
|
138
|
+
|
|
139
|
+
if option['resultFile']:
|
|
140
|
+
fpout.write("----- Input Problem -----\n")
|
|
141
|
+
fileio.write_symcone(fpout, K, J)
|
|
142
|
+
|
|
143
|
+
# --------------------------------------------------
|
|
144
|
+
# Exploiting sparsity conversion
|
|
145
|
+
# --------------------------------------------------
|
|
146
|
+
timeinfo['conv_domain'] = time.time()
|
|
147
|
+
|
|
148
|
+
# Convert domain space sparsity
|
|
149
|
+
if len(K.s) > 0:
|
|
150
|
+
if option['domainMethod'] == 'clique':
|
|
151
|
+
maybe_print('Applying the d-space conversion method '
|
|
152
|
+
'using clique trees...')
|
|
153
|
+
dom_A, dom_b, dom_c, dom_K, dom_J, cliqueD = \
|
|
154
|
+
spcolo.dconv_cliquetree(A, b, c, K, J)
|
|
155
|
+
############################################################
|
|
156
|
+
# Under construction
|
|
157
|
+
############################################################
|
|
158
|
+
return
|
|
159
|
+
elif option['domainMethod'] == 'basis':
|
|
160
|
+
maybe_print('Applying the d-space conversion method '
|
|
161
|
+
'using basis representation...')
|
|
162
|
+
dom_A, dom_b, dom_c, dom_K, dom_J, cliqueD = \
|
|
163
|
+
spcolo.dconv_basisrep(A, b, c, K, J)
|
|
164
|
+
else:
|
|
165
|
+
dom_A = copy.deepcopy(A)
|
|
166
|
+
dom_b = copy.deepcopy(b)
|
|
167
|
+
dom_c = copy.deepcopy(c)
|
|
168
|
+
dom_K = copy.deepcopy(K)
|
|
169
|
+
dom_J = copy.deepcopy(J)
|
|
170
|
+
else:
|
|
171
|
+
dom_A = copy.deepcopy(A)
|
|
172
|
+
dom_b = copy.deepcopy(b)
|
|
173
|
+
dom_c = copy.deepcopy(c)
|
|
174
|
+
dom_K = copy.deepcopy(K)
|
|
175
|
+
dom_J = copy.deepcopy(J)
|
|
176
|
+
|
|
177
|
+
timeinfo['conv_domain'] = time.time() - timeinfo['conv_domain']
|
|
178
|
+
|
|
179
|
+
if option['resultFile'] and option['domainMethod'] != 'none':
|
|
180
|
+
fpout.write("----- Domain Space Sparsity Converted Problem-----\n")
|
|
181
|
+
fileio.write_symcone(fpout, dom_K, dom_J)
|
|
182
|
+
|
|
183
|
+
# Convert range space sparsity
|
|
184
|
+
timeinfo['conv_range'] = time.time()
|
|
185
|
+
if len(dom_J.s) > 0:
|
|
186
|
+
if option['rangeMethod'] == 'clique':
|
|
187
|
+
maybe_print('Applying the r-space conversion method '
|
|
188
|
+
'using clique trees...')
|
|
189
|
+
ran_A, ran_b, ran_c, ran_K, ran_J, cliqueR = \
|
|
190
|
+
spcolo.rconv_cliquetree(dom_A, dom_b, dom_c, dom_K, dom_J)
|
|
191
|
+
############################################################
|
|
192
|
+
# Under construction
|
|
193
|
+
############################################################
|
|
194
|
+
elif option['rangeMethod'] == 'decomp':
|
|
195
|
+
maybe_print('Applying the r-space conversion method '
|
|
196
|
+
'using matrix decomposition...')
|
|
197
|
+
ran_A, ran_b, ran_c, ran_K, ran_J, cliqueR = \
|
|
198
|
+
spcolo.rconv_matdecomp(dom_A, dom_b, dom_c, dom_K, dom_J)
|
|
199
|
+
else:
|
|
200
|
+
ran_A, ran_b, ran_c = dom_A, dom_b, dom_c
|
|
201
|
+
ran_K = copy.deepcopy(dom_K)
|
|
202
|
+
ran_J = copy.deepcopy(dom_J)
|
|
203
|
+
else:
|
|
204
|
+
ran_A, ran_b, ran_c = dom_A, dom_b, dom_c
|
|
205
|
+
ran_K = copy.deepcopy(dom_K)
|
|
206
|
+
ran_J = copy.deepcopy(dom_J)
|
|
207
|
+
|
|
208
|
+
timeinfo['conv_range'] = time.time() - timeinfo['conv_range']
|
|
209
|
+
|
|
210
|
+
if option['resultFile'] and option['rangeMethod'] != 'none':
|
|
211
|
+
fpout.write("----- Range Space Sparsity Converted Problem-----\n")
|
|
212
|
+
fileio.write_symcone(fpout, ran_K, ran_J)
|
|
213
|
+
|
|
214
|
+
# --------------------------------------------------
|
|
215
|
+
# Convert to SeDuMi standard form
|
|
216
|
+
# --------------------------------------------------
|
|
217
|
+
timeinfo['conv_std'] = time.time()
|
|
218
|
+
|
|
219
|
+
useConvert = False
|
|
220
|
+
if ran_J.l > 0 or len(ran_J.q) > 0 or len(ran_J.s) > 0:
|
|
221
|
+
useConvert = True
|
|
222
|
+
if option['convMethod'] == 'LMI':
|
|
223
|
+
maybe_print('Converting CLP format to LMI standard form...')
|
|
224
|
+
A2, b2, c2, K2, J2, map_sdpIndex = \
|
|
225
|
+
convert.clp_toLMI(ran_A, ran_b, ran_c, ran_K, ran_J)
|
|
226
|
+
elif option['convMethod'] == 'EQ':
|
|
227
|
+
maybe_print('Converting CLP format to EQ standard form.')
|
|
228
|
+
##################################################
|
|
229
|
+
# This method is under construction
|
|
230
|
+
##################################################
|
|
231
|
+
A2, b2, c2, K2, J2 = \
|
|
232
|
+
convert.clp_toEQ(ran_A, ran_b, ran_c, ran_K, ran_J)
|
|
233
|
+
else:
|
|
234
|
+
raise ValueError("convMethod must be 'LMI' or 'EQ'")
|
|
235
|
+
else:
|
|
236
|
+
A2, b2, c2 = ran_A, ran_b, ran_c
|
|
237
|
+
K2 = copy.deepcopy(ran_K)
|
|
238
|
+
J2 = copy.deepcopy(ran_J)
|
|
239
|
+
|
|
240
|
+
timeinfo['conv_std'] = time.time() - timeinfo['conv_std']
|
|
241
|
+
|
|
242
|
+
if option['resultFile'] and \
|
|
243
|
+
(ran_J.l > 0 or len(ran_J.q) > 0 or len(ran_J.s) > 0):
|
|
244
|
+
fpout.write("----- SeDuMi format Converted Problem-----\n")
|
|
245
|
+
fileio.write_symcone(fpout, K2)
|
|
246
|
+
|
|
247
|
+
# --------------------------------------------------
|
|
248
|
+
# Eliminate free variables
|
|
249
|
+
# --------------------------------------------------
|
|
250
|
+
timeinfo['conv_fv'] = time.time()
|
|
251
|
+
|
|
252
|
+
if K2.f > 0:
|
|
253
|
+
if option['frvMethod'] == 'split':
|
|
254
|
+
maybe_print('Eliminating free variables with split method...')
|
|
255
|
+
A3, b3, c3, K3 = fvelim.split(A2, b2, c2, K2, option['rho'])
|
|
256
|
+
elif option['frvMethod'] == 'elimination':
|
|
257
|
+
maybe_print('Eliminationg free variables with elimination method...')
|
|
258
|
+
(A3, b3, c3, K3,
|
|
259
|
+
LiP, U, Q, LPA_B, LPb_B, cfQU, gamma, rank_Af) = \
|
|
260
|
+
fvelim.eliminate(A2, b2, c2, K2,
|
|
261
|
+
option['rho'], option['zeroPoint'])
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError("frvMethod must be 'split' or 'elimination'")
|
|
264
|
+
else:
|
|
265
|
+
A3, b3, c3, K3 = A2, b2, c2, K2
|
|
266
|
+
|
|
267
|
+
timeinfo['conv_fv'] = time.time() - timeinfo['conv_fv']
|
|
268
|
+
|
|
269
|
+
if option['resultFile'] and K2.f > 0:
|
|
270
|
+
fpout.write("----- Free Variables Eliminated Problem -----\n")
|
|
271
|
+
fileio.write_symcone(fpout, K3)
|
|
272
|
+
|
|
273
|
+
# --------------------------------------------------
|
|
274
|
+
# Solve by SDPA
|
|
275
|
+
# --------------------------------------------------
|
|
276
|
+
timeinfo['sdpa'] = time.time()
|
|
277
|
+
x3, y3, s3, sdpainfo = sdpacall.solve_sdpa(A3, b3, c3, K3, option)
|
|
278
|
+
timeinfo['sdpa'] = time.time() - timeinfo['sdpa']
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# --------------------------------------------------
|
|
282
|
+
# Get Result
|
|
283
|
+
# --------------------------------------------------
|
|
284
|
+
maybe_print('Start: getCLPresult')
|
|
285
|
+
|
|
286
|
+
# Retrieve result of fvelim
|
|
287
|
+
timeinfo['ret_fv'] = time.time();
|
|
288
|
+
if K2.f > 0:
|
|
289
|
+
if option['frvMethod'] == 'split':
|
|
290
|
+
maybe_print('Retrieving result with split method...')
|
|
291
|
+
x2, y2, s2 = fvelim.result_split(x3, y3, s3, K2)
|
|
292
|
+
elif option['frvMethod'] == 'elimination':
|
|
293
|
+
maybe_print('Retrieving result with elimination method...')
|
|
294
|
+
x2, y2, s2 = fvelim.result_elimination(x3, y3, s3, K2,
|
|
295
|
+
LiP, U, Q,
|
|
296
|
+
LPA_B, LPb_B, cfQU, rank_Af)
|
|
297
|
+
else:
|
|
298
|
+
raise ValueError("frvMethod must be 'split' or 'elimination'")
|
|
299
|
+
else:
|
|
300
|
+
x2, y2, s2 = x3, y3, s3
|
|
301
|
+
|
|
302
|
+
timeinfo['ret_fv'] = time.time() - timeinfo['ret_fv']
|
|
303
|
+
|
|
304
|
+
# Retrieve result from LMI or EQ
|
|
305
|
+
timeinfo['ret_std'] = time.time()
|
|
306
|
+
if useConvert:
|
|
307
|
+
if option['convMethod'] == 'LMI':
|
|
308
|
+
maybe_print('Retrieving result from LMI standard form...')
|
|
309
|
+
x, y = convert.result_fromLMI(x2, y2, ran_K, ran_J, map_sdpIndex)
|
|
310
|
+
tmp = -sdpainfo['primalObj']
|
|
311
|
+
sdpainfo['primalObj'] = -sdpainfo['dualObj']
|
|
312
|
+
sdpainfo['dualObj'] = tmp
|
|
313
|
+
elif option['convMethod'] == 'EQ':
|
|
314
|
+
maybe_print('Retrieving result from EQ standard form...')
|
|
315
|
+
##################################################
|
|
316
|
+
# This method is under construction
|
|
317
|
+
##################################################
|
|
318
|
+
x, y = result_fromEQ(x2, y2, ran_K, ran_J)
|
|
319
|
+
else:
|
|
320
|
+
raise ValueError("Something wrong about option['convMethod']")
|
|
321
|
+
else:
|
|
322
|
+
x, y = x2, y2
|
|
323
|
+
|
|
324
|
+
timeinfo['ret_std'] = time.time() - timeinfo['ret_std']
|
|
325
|
+
|
|
326
|
+
# Retrieve an optiomal solution from range space sparsity converted problem
|
|
327
|
+
timeinfo['ret_range'] = time.time()
|
|
328
|
+
if option['rangeMethod'] != 'none' and len(J.s) > 0:
|
|
329
|
+
if option['rangeMethod'] == 'clique':
|
|
330
|
+
maybe_print('Retrieving result with r-space conversion method '
|
|
331
|
+
'using clique trees...')
|
|
332
|
+
############################################################
|
|
333
|
+
# Under construction
|
|
334
|
+
############################################################
|
|
335
|
+
x, y = spcolo.rconv_cliqueresult(x, y, dom_K, dom_J, ran_K, cliqueR)
|
|
336
|
+
elif option['rangeMethod'] == 'decomp':
|
|
337
|
+
maybe_print('Retrieving result with r-space conversion method '
|
|
338
|
+
'using matrix decomposition...')
|
|
339
|
+
x, y = spcolo.rconv_decompresult(x, y, dom_K, dom_J, ran_J, cliqueR)
|
|
340
|
+
|
|
341
|
+
timeinfo['ret_range'] = time.time() - timeinfo['ret_range']
|
|
342
|
+
|
|
343
|
+
# Retrieve an optiomal solution from domain space sparsity converted problem
|
|
344
|
+
timeinfo['ret_domain'] = time.time()
|
|
345
|
+
if option['domainMethod'] != 'none' and len(K.s) > 0:
|
|
346
|
+
if option['domainMethod'] == 'clique':
|
|
347
|
+
maybe_print('Retrieving result with d-space conversion method '
|
|
348
|
+
'using clique trees...')
|
|
349
|
+
############################################################
|
|
350
|
+
# Under construction
|
|
351
|
+
############################################################
|
|
352
|
+
x, y = spcolo.dconv_cliqueresult(x, y, K, J, dom_J, cliqueD)
|
|
353
|
+
elif option['domainMethod'] == 'basis':
|
|
354
|
+
maybe_print('Retrieving result with d-space conversion method '
|
|
355
|
+
'using basis representation...')
|
|
356
|
+
x, y = spcolo.dconv_basisresult(x, y, K, J, dom_K, cliqueD)
|
|
357
|
+
|
|
358
|
+
timeinfo['ret_domain'] = time.time() - timeinfo['ret_domain']
|
|
359
|
+
timeinfo['total'] = time.time() - timeinfo['total']
|
|
360
|
+
|
|
361
|
+
# --------------------------------------------------
|
|
362
|
+
# Make dictionary 'info'
|
|
363
|
+
# --------------------------------------------------
|
|
364
|
+
maybe_print('Making result infomation...')
|
|
365
|
+
timeinfo['convert'] = (timeinfo['conv_domain'] + timeinfo['conv_range'] +
|
|
366
|
+
timeinfo['conv_std'] + timeinfo['conv_fv'])
|
|
367
|
+
timeinfo['retrieve'] = (timeinfo['ret_fv'] + timeinfo['ret_std'] +
|
|
368
|
+
timeinfo['ret_range'] + timeinfo['ret_domain'])
|
|
369
|
+
|
|
370
|
+
if ran_K.f > 0 and option['frvMethod'] == 'elimination':
|
|
371
|
+
sdpainfo['primalObj'] += gamma
|
|
372
|
+
sdpainfo['dualObj'] += gamma
|
|
373
|
+
|
|
374
|
+
sdpapinfo = dict()
|
|
375
|
+
sdpapinfo['primalObj'] = (c.T * x)[0,0]
|
|
376
|
+
sdpapinfo['dualObj'] = (b.T * y)[0,0]
|
|
377
|
+
sdpapinfo['dualityGap'] = sdpaputils.get_dualitygap(x, y, b, c)
|
|
378
|
+
maybe_print('(Re)calculating feasibility errors for CLP converted solution.')
|
|
379
|
+
try:
|
|
380
|
+
sdpapinfo['primalError'] = sdpaputils.get_primalerror(x, A, b, J)
|
|
381
|
+
except RuntimeError as e:
|
|
382
|
+
print(e)
|
|
383
|
+
sdpapinfo['primalError'] = None
|
|
384
|
+
try:
|
|
385
|
+
sdpapinfo['dualError'] = sdpaputils.get_dualerror(y, A, c, K)
|
|
386
|
+
except RuntimeError as e:
|
|
387
|
+
print(e)
|
|
388
|
+
sdpapinfo['dualError'] = None
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
SDPAP input is in CLP format (generalization of SeDuMi)
|
|
392
|
+
|
|
393
|
+
Get the status of the primal of the original CLP
|
|
394
|
+
The status returned to us is that of the dual of the SeDuMi
|
|
395
|
+
"""
|
|
396
|
+
GET_PRIMAL_STATUS_FROM_DUAL = {
|
|
397
|
+
"noINFO": "noINFO",
|
|
398
|
+
"pFEAS": "dFEAS", # flip
|
|
399
|
+
"dFEAS": "pFEAS", # flip
|
|
400
|
+
"pdFEAS": "pdFEAS",
|
|
401
|
+
"pdINF": "pdINF",
|
|
402
|
+
"pFEAS_dINF": "pINF_dFEAS", # flip
|
|
403
|
+
"pINF_dFEAS": "pFEAS_dINF", # flip
|
|
404
|
+
"pdOPT": "pdOPT",
|
|
405
|
+
"pUNBD": "dUNBD", # flip
|
|
406
|
+
"dUNBD": "pUNBD" # flip
|
|
407
|
+
}
|
|
408
|
+
sdpapinfo['phasevalue'] = GET_PRIMAL_STATUS_FROM_DUAL[sdpainfo['phasevalue']]
|
|
409
|
+
|
|
410
|
+
# --------------------------------------------------
|
|
411
|
+
# Print result
|
|
412
|
+
# --------------------------------------------------
|
|
413
|
+
if option['resultFile']:
|
|
414
|
+
fileio.write_info(fpout, sdpapinfo, sdpainfo, timeinfo)
|
|
415
|
+
fileio.write_result(fpout, x, y)
|
|
416
|
+
fpout.close()
|
|
417
|
+
|
|
418
|
+
maybe_print('========================================')
|
|
419
|
+
maybe_print(' SDPAP: Result')
|
|
420
|
+
maybe_print('========================================')
|
|
421
|
+
maybe_print(" SDPA.phase = %s" % sdpainfo['phasevalue'])
|
|
422
|
+
maybe_print(" iteration = %d" % sdpainfo['iteration'])
|
|
423
|
+
maybe_print(" convMethod = %s" % option['convMethod'])
|
|
424
|
+
maybe_print(" frvMethod = %s" % option['frvMethod'])
|
|
425
|
+
maybe_print(" domainMethod = %s" % option['domainMethod'])
|
|
426
|
+
maybe_print(" rangeMethod = %s" % option['rangeMethod'])
|
|
427
|
+
maybe_print(" primalObj = %+10.16e" % sdpapinfo['primalObj'])
|
|
428
|
+
maybe_print(" dualObj = %+10.16e" % sdpapinfo['dualObj'])
|
|
429
|
+
maybe_print(" dualityGap = %+10.16e" % sdpapinfo['dualityGap'])
|
|
430
|
+
if sdpapinfo['primalError'] is not None:
|
|
431
|
+
maybe_print(" primalError = %+10.16e" % sdpapinfo['primalError'])
|
|
432
|
+
else:
|
|
433
|
+
maybe_print(" primalError = ")
|
|
434
|
+
if sdpapinfo['dualError'] is not None:
|
|
435
|
+
maybe_print(" dualError = %+10.16e" % sdpapinfo['dualError'])
|
|
436
|
+
else:
|
|
437
|
+
maybe_print(" dualError = ")
|
|
438
|
+
maybe_print(" convertTime = %f" % timeinfo['convert'])
|
|
439
|
+
maybe_print(" solveTime = %f" % timeinfo['sdpa'])
|
|
440
|
+
maybe_print("retrievingTime = %f" % timeinfo['retrieve'])
|
|
441
|
+
maybe_print(" totalTime = %f" % timeinfo['total'])
|
|
442
|
+
maybe_print('---------- SDPAP End ----------')
|
|
443
|
+
|
|
444
|
+
if (sdpapinfo['primalError'] is None) or (sdpapinfo['dualError'] is None):
|
|
445
|
+
warnings.warn("Python recalculation of primal and/or dual feasibility "
|
|
446
|
+
"error failed due to numerical issues in eigenvalue computation. SDPA "
|
|
447
|
+
"for Python is only able to report the feasibility errors computed by "
|
|
448
|
+
"the backend solver.", RuntimeWarning, stacklevel=2)
|
|
449
|
+
|
|
450
|
+
return x, y, sdpapinfo, timeinfo, sdpainfo
|
sdpap/sdpaputils.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
This file is a component of SDPAP
|
|
4
|
+
Copyright (C) 2010-2022 SDPA Project
|
|
5
|
+
|
|
6
|
+
This program is free software; you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License along
|
|
17
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
18
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
19
|
+
|
|
20
|
+
December 2010: Originally written by Kenta Kato
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__all__ = ['get_dualitygap', 'get_primalerror', 'get_dualerror']
|
|
24
|
+
|
|
25
|
+
from .symcone import SymCone
|
|
26
|
+
from scipy.sparse.linalg import eigs
|
|
27
|
+
from scipy.sparse import csc_matrix
|
|
28
|
+
from scipy import sparse
|
|
29
|
+
|
|
30
|
+
def get_dualitygap(x, y, b, c):
|
|
31
|
+
"""Get duality gap of result
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
x: A primal result
|
|
35
|
+
y: A dual result
|
|
36
|
+
b: A constraint vector of primal
|
|
37
|
+
c: A constraint vector of dual
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A float value of duality gap
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if not sparse.isspmatrix_csc(x):
|
|
44
|
+
x = x.tocsc()
|
|
45
|
+
if not sparse.isspmatrix_csc(y):
|
|
46
|
+
y = y.tocsc()
|
|
47
|
+
if not sparse.isspmatrix_csc(b):
|
|
48
|
+
b = b.tocsc()
|
|
49
|
+
if not sparse.isspmatrix_csc(c):
|
|
50
|
+
c = c.tocsc()
|
|
51
|
+
|
|
52
|
+
objP = (c.T * x)[0,0]
|
|
53
|
+
objD = (b.T * y)[0,0]
|
|
54
|
+
|
|
55
|
+
return abs(objP - objD) / max(1.0, (abs(objP) + abs(objD)) / 2)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_primalerror(x, A, b, J):
|
|
59
|
+
"""Get primal feasible error
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
x: A primal result
|
|
63
|
+
A: A constraint matrix
|
|
64
|
+
b: A constraint vector of primal
|
|
65
|
+
J: A SymCone
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A float value of primal feasible error
|
|
69
|
+
"""
|
|
70
|
+
if not sparse.isspmatrix_csc(x):
|
|
71
|
+
x = x.tocsc()
|
|
72
|
+
if not sparse.isspmatrix_csc(A):
|
|
73
|
+
A = A.tocsc()
|
|
74
|
+
if not sparse.isspmatrix_csc(b):
|
|
75
|
+
b = b.tocsc()
|
|
76
|
+
|
|
77
|
+
delta = A * x - b
|
|
78
|
+
maxerr = 0.0
|
|
79
|
+
|
|
80
|
+
# Feasible error for K.f
|
|
81
|
+
if J.f > 0:
|
|
82
|
+
err = max(abs(delta[0:J.f, :]))[0, 0]
|
|
83
|
+
maxerr = max(maxerr, err)
|
|
84
|
+
|
|
85
|
+
if J.l > 0:
|
|
86
|
+
err = min(delta[J.f:(J.f + J.l), :])[0,0]
|
|
87
|
+
if err < 0:
|
|
88
|
+
maxerr = max(maxerr, abs(err))
|
|
89
|
+
|
|
90
|
+
if len(J.q) > 0:
|
|
91
|
+
offset = J.f + J.l
|
|
92
|
+
for k in J.q:
|
|
93
|
+
vec = delta[offset:(offset + k), :]
|
|
94
|
+
vec0 = vec[0, 0]
|
|
95
|
+
vec1 = vec[1:, 0]
|
|
96
|
+
err = vec0 ** 2 - (vec1.T * vec1)[0,0]
|
|
97
|
+
if err < 0:
|
|
98
|
+
maxerr = max(maxerr, abs(err))
|
|
99
|
+
|
|
100
|
+
offset += k
|
|
101
|
+
|
|
102
|
+
if len(J.s) > 0:
|
|
103
|
+
offset = J.f + J.l + sum(J.q)
|
|
104
|
+
s_err = 0.0
|
|
105
|
+
for k in J.s:
|
|
106
|
+
vec = delta[offset:(offset + k ** 2), :]
|
|
107
|
+
mat_row = [i // k for i in vec.indices]
|
|
108
|
+
mat_col = [i % k for i in vec.indices]
|
|
109
|
+
mat_val = vec.data
|
|
110
|
+
mat = csc_matrix((mat_val, (mat_row, mat_col)), shape=(k, k))
|
|
111
|
+
# scipy.sparse.linalg.eigs raises a TypeError if mat is sparse and
|
|
112
|
+
# k (1 in this case) is >= n (no. of rows of mat) - 1
|
|
113
|
+
if mat.shape[0] <= 2:
|
|
114
|
+
eig = eigs(mat.toarray(), k=1, which='SM',
|
|
115
|
+
return_eigenvectors=False)[0]
|
|
116
|
+
else:
|
|
117
|
+
eig = eigs(mat, k=1, which='SM',
|
|
118
|
+
return_eigenvectors=False)[0]
|
|
119
|
+
|
|
120
|
+
if eig < 0:
|
|
121
|
+
maxerr = max(maxerr, abs(eig))
|
|
122
|
+
|
|
123
|
+
offset += k ** 2
|
|
124
|
+
|
|
125
|
+
return maxerr
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_dualerror(y, A, c, K):
|
|
129
|
+
"""Get dual feasible error
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
y: A dual result
|
|
133
|
+
A: A constraint matrix
|
|
134
|
+
c: A constraint vector of dual
|
|
135
|
+
K: A SymCone
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A float value of primal feasible error
|
|
139
|
+
"""
|
|
140
|
+
return get_primalerror(y, -A.T, -c, K)
|
sdpap/spcolo/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file is a component of SDPAP
|
|
3
|
+
Copyright (C) 2010-2022 SDPA Project
|
|
4
|
+
|
|
5
|
+
This program is free software; you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License along
|
|
16
|
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
17
|
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
18
|
+
|
|
19
|
+
December 2010: Originally written by Kenta Kato
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .spcolo import *
|
|
23
|
+
from .asputils import *
|
|
24
|
+
from .clique import *
|
|
25
|
+
|
|
26
|
+
__all__ = filter(lambda s:not s.startswith('_'),dir())
|