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.
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)
@@ -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())