@@ -223,6 +223,7 @@ def solve_dsdp_dual(
223
223
tol (float, optional): Tolerance for solver. Defaults to TOL.
224
224
adjust (bool, optional): If true, adjust the cost matrix. Defaults to False.
225
225
"""
226
+ T0 = time ()
226
227
# List of constraints with homogenizing constraint.
227
228
A_h = PolyMatrix ()
228
229
A_h [problem .h , problem .h ] = 1
@@ -233,62 +234,65 @@ def solve_dsdp_dual(
233
234
234
235
# CLIQUE VARIABLES
235
236
cliques = problem .cliques
236
- zvars = [M .variable (fu .Domain .inPSDCone (c .size )) for c in cliques ]
237
+ cvars = [M .variable (fu .Domain .inPSDCone (c .size )) for c in cliques ]
237
238
238
239
# LAGRANGE VARIABLES
239
- y = [M .variable (fu . Domain . unbounded ( 1 ) ) for i in range (len (As ))]
240
+ y = [M .variable (f"y { i } " ) for i in range (len (As ))]
240
241
241
242
# OBJECTIVE
242
243
if verbose :
243
244
print ("Adding Objective" )
244
245
M .objective (fu .ObjectiveSense .Minimize , y [- 1 ])
245
246
246
- # AFFINE CONSTRAINTS: C + sum(Ai*y_i) - sum(Z_k) = 0
247
+ # CONSTRUCT CERTIFICATE
248
+ if verbose :
249
+ print ("Building Certificate Matrix" )
250
+ cert_mat_list = []
251
+ # get constant cost matrix
252
+ C_mat = problem .C .get_matrix (problem .var_sizes )
253
+ C_fusion = fu .Expr .constTerm (sparse_to_fusion (C_mat ))
254
+ cert_mat_list .append (C_fusion )
255
+ # get constraint-multiplier products
256
+ for i , A in enumerate (As ):
257
+ A_mat = A .get_matrix (problem .var_sizes )
258
+ A_fusion = sparse_to_fusion (A_mat )
259
+ cert_mat_list .append (fu .Expr .mul (A_fusion , y [i ]))
260
+ # sum into certificate
261
+ H = fu .Expr .add (cert_mat_list )
262
+
263
+ # AFFINE CONSTRAINTS:
264
+ # H_ij = C_ij + sum(Ai*y_i)_ij - sum(Z_k)_ij = 0
247
265
# Get a list of edges in the aggregate sparsity pattern (including main diagonal)
248
266
if verbose :
249
267
print ("Generating Affine Constraints" )
250
268
edges = [e .tuple for e in problem .asg .es ]
251
269
edges += [(v .index , v .index ) for v in problem .asg .vs ]
270
+
252
271
# Generate one matrix constraint per edge. This links the cliques to the
253
272
for edge_id in edges :
254
273
# Get variables in edge from graph
255
274
var0 = problem .asg .vs ["name" ][edge_id [0 ]]
256
275
var1 = problem .asg .vs ["name" ][edge_id [1 ]]
257
- mat_list = []
258
- # Get component of Cost matrix
259
- C_mat = problem .C [var0 , var1 ]
260
- if not np .all (C_mat == 0 ):
261
- c_mat = - C_mat .reshape (- 1 )
262
- else :
263
- c_mat = 0.0
264
-
265
- # Get component of Constraint matrices
266
- for i , A in enumerate (As ):
267
- A_mat = A [var0 , var1 ]
268
- if not np .all (A_mat == 0 ):
269
- if not sp .issparse (A_mat ):
270
- A_mat = sp .coo_array (A_mat )
271
- a_mat = sparse_to_fusion (A_mat .reshape (- 1 , 1 ))
272
- mat_list .append (fu .Expr .mul (a_mat , y [i ]))
273
-
274
- # Component of clique variables
275
- for k , clique in enumerate (problem .cliques ):
276
+ # Get component of certificate matrix
277
+ row_inds = problem ._get_indices (var0 )
278
+ col_inds = problem ._get_indices (var1 )
279
+ inds = get_block_inds (row_inds , col_inds , var0 == var1 )
280
+ sum_list = [H .pick (inds )]
281
+ # Find the cliques that are involved with these variables
282
+ clique_inds = problem .var_clique_map [var0 ] & problem .var_clique_map [var1 ]
283
+ cliques = [problem .cliques [i ] for i in clique_inds ]
284
+ # get components of clique variables
285
+ for clique in cliques :
276
286
if var0 in clique .var_list and var1 in clique .var_list :
277
- ind1 = clique ._get_indices (var0 )
278
- ind2 = clique ._get_indices (var1 )
279
- inds = []
280
- for i in ind1 :
281
- for j in ind2 :
282
- inds .append ([i , j ])
283
- mat_list .append (- zvars [k ].pick (inds ))
284
-
287
+ row_inds = clique ._get_indices (var0 )
288
+ col_inds = clique ._get_indices (var1 )
289
+ inds = get_block_inds (row_inds , col_inds , var0 == var1 )
290
+ sum_list .append (- cvars [clique .index ].pick (inds ))
285
291
# Add the list together
286
- matsum = fu .Expr .add (mat_list )
287
- M .constraint (f"e_{ var0 } _{ var1 } " , matsum , fu .Domain .equalsTo (c_mat ))
292
+ matsumvec = fu .Expr .add (sum_list )
293
+ M .constraint (f"e_{ var0 } _{ var1 } " , matsumvec , fu .Domain .equalsTo (0.0 ))
288
294
289
295
# SOLVE
290
- if verbose :
291
- print ("Starting problem solve" )
292
296
M .setSolverParam ("intpntSolveForm" , "dual" )
293
297
# record problem
294
298
if verbose :
@@ -306,50 +310,49 @@ def solve_dsdp_dual(
306
310
M .setLogHandler (f )
307
311
308
312
M .acceptedSolutionStatus (fu .AccSolutionStatus .Anything )
309
- T0 = time ()
310
- M .solve ()
311
313
T1 = time ()
314
+ M .solve ()
315
+ T2 = time ()
316
+
317
+ # Store information
318
+ info = {
319
+ "success" : False ,
320
+ "cost" : - np .inf ,
321
+ "runtime" : T2 - T1 ,
322
+ "preprocess_time" : T1 - T0 ,
323
+ "msg" : str (M .getProblemStatus ()),
324
+ }
312
325
313
326
# EXTRACT SOLN
314
- if M .getProblemStatus () in [
315
- fu .ProblemStatus .PrimalAndDualFeasible ,
316
- fu .ProblemStatus .Unknown ,
317
- ]:
327
+ status = M .getProblemStatus ()
328
+ if status == fu .ProblemStatus .PrimalAndDualFeasible :
318
329
# Get MOSEK cost
319
330
cost = M .primalObjValue ()
320
- if cost < 0 :
321
- print ("cost is negative! sanity check:" )
322
- clq_list = [zvar .dual ().reshape (zvar .shape ) for zvar in zvars ]
323
- dual = [zvar .level ().reshape (zvar .shape ) for zvar in zvars ]
331
+ clq_list = [cvar .dual ().reshape (cvar .shape ) for cvar in cvars ]
332
+ dual = [cvar .level ().reshape (cvar .shape ) for cvar in cvars ]
324
333
mults = [y_i .level () for y_i in y ]
325
- info = {
326
- "success" : True ,
327
- "cost" : cost ,
328
- "time" : T1 - T0 ,
329
- "msg" : M .getProblemStatus (),
330
- "dual" : dual ,
331
- "mults" : mults ,
332
- }
333
- elif M .getProblemStatus () is fu .ProblemStatus .DualInfeasible :
334
- clq_list = []
335
- info = {
336
- "success" : False ,
337
- "cost" : - np .inf ,
338
- "time" : T1 - T0 ,
339
- "msg" : "dual infeasible" ,
340
- }
334
+ info ["success" ] = True
335
+ info ["dual" ] = dual
336
+ info ["cost" ] = cost
337
+ info ["mults" ] = mults
341
338
else :
342
- print ("Unknown status:" , M .getProblemStatus ())
343
- clq_list = []
344
- info = {
345
- "success" : False ,
346
- "cost" : - np .inf ,
347
- "time" : T1 - T0 ,
348
- "msg" : M .getProblemStatus (),
349
- }
339
+ print ("Solve Failed - Mosek Status: " + str (status ))
350
340
return clq_list , info
351
341
352
342
343
+ def get_block_inds (row_inds , col_inds , triu = False ):
344
+ """Helper function for getting a grid of indices based on row and column indices. Only selects upper triangle if triu is set to true"""
345
+ inds = []
346
+ for row in range (len (row_inds )):
347
+ if triu :
348
+ colstart = row
349
+ else :
350
+ colstart = 0
351
+ for col in range (colstart , len (col_inds )):
352
+ inds .append ([row_inds [row ], col_inds [col ]])
353
+ return np .array (inds )
354
+
355
+
353
356
def solve_dsdp_primal (
354
357
problem : HomQCQP ,
355
358
reduce_constrs = None ,
0 commit comments