@@ -322,6 +322,53 @@ def split_solution(self, sltn, active_vars=None):
322322 value , var .shape , order = 'F' )
323323 return sltn_dict
324324
325+ def apply_restruct_mat (self , restruct_mat , restruct_mat_op ):
326+ """Apply restructuring matrix to parametric tensor A.
327+
328+ Parameters
329+ ----------
330+ restruct_mat : list
331+ List of sparse matrices or linear operators (unused for this path).
332+ restruct_mat_op : LinearOperator
333+ Block diagonal linear operator for restructuring.
334+
335+ Returns
336+ -------
337+ ParamConeProg
338+ New program with restructured A tensor.
339+ """
340+ if restruct_mat_op is not None :
341+ unspecified , _ = np .divmod (self .A .shape [0 ] * self .A .shape [1 ],
342+ restruct_mat_op .shape [1 ], dtype = np .int64 )
343+ reshaped_A = self .A .reshape (restruct_mat_op .shape [1 ],
344+ unspecified , order = 'F' ).tocsr ()
345+ restructured_A = restruct_mat_op (reshaped_A ).tocoo ()
346+ # Because of a bug in scipy versions < 1.20, `reshape`
347+ # can overflow if indices are int32s.
348+ restructured_A .row = restructured_A .row .astype (np .int64 )
349+ restructured_A .col = restructured_A .col .astype (np .int64 )
350+ restructured_A = restructured_A .reshape (
351+ np .int64 (restruct_mat_op .shape [0 ]) * (np .int64 (self .x .size ) + 1 ),
352+ self .A .shape [1 ], order = 'F' )
353+ else :
354+ restructured_A = self .A
355+ return ParamConeProg (
356+ self .q ,
357+ self .x ,
358+ restructured_A ,
359+ self .variables ,
360+ self .var_id_to_col ,
361+ self .constraints ,
362+ self .parameters ,
363+ self .param_id_to_col ,
364+ P = self .P ,
365+ formatted = True ,
366+ lower_bounds = self .lower_bounds ,
367+ upper_bounds = self .upper_bounds ,
368+ lb_tensor = self .lb_tensor ,
369+ ub_tensor = self .ub_tensor ,
370+ )
371+
325372 def split_adjoint (self , del_vars = None ):
326373 """Adjoint of split_solution.
327374 """
@@ -339,6 +386,65 @@ def split_adjoint(self, del_vars=None):
339386 return var_vec
340387
341388
389+ def lower_and_order_constraints (constraints ):
390+ """Lower equality/inequality constraints and reorder by cone type.
391+
392+ Converts Equality -> Zero, Inequality/NonPos -> NonNeg, and normalizes
393+ SOC/PowCone/ExpCone axes. Returns constraints ordered as:
394+ Zero, NonNeg, SOC, PSD, ExpCone, PowCone3D, PowConeND.
395+
396+ Parameters
397+ ----------
398+ constraints : list
399+ The problem constraints to lower and reorder.
400+
401+ Returns
402+ -------
403+ ordered_cons : list
404+ The lowered and reordered constraints.
405+ cons_id_map : dict
406+ Mapping from constraint id to constraint id (identity map).
407+ """
408+ cons = []
409+ for con in constraints :
410+ if isinstance (con , Equality ):
411+ con = lower_equality (con )
412+ elif isinstance (con , Inequality ):
413+ con = lower_ineq_to_nonneg (con )
414+ elif isinstance (con , NonPos ):
415+ con = nonpos2nonneg (con )
416+ elif isinstance (con , SOC ) and con .axis == 1 :
417+ con = SOC (con .args [0 ], con .args [1 ].T , axis = 0 ,
418+ constr_id = con .constr_id )
419+ elif isinstance (con , PowCone3D ) and con .args [0 ].ndim > 1 :
420+ x , y , z = con .args
421+ alpha = con .alpha
422+ con = PowCone3D (x .flatten (order = 'F' ),
423+ y .flatten (order = 'F' ),
424+ z .flatten (order = 'F' ),
425+ alpha .flatten (order = 'F' ),
426+ constr_id = con .constr_id )
427+ elif isinstance (con , PowConeND ) and con .axis == 1 :
428+ alpha = con .alpha .T
429+ W = con .W .T
430+ con = PowConeND (W , con .z .flatten (order = 'F' ),
431+ alpha ,
432+ axis = 0 ,
433+ constr_id = con .constr_id )
434+ elif isinstance (con , ExpCone ) and con .args [0 ].ndim > 1 :
435+ x , y , z = con .args
436+ con = ExpCone (x .flatten (order = 'F' ), y .flatten (order = 'F' ), z .flatten (order = 'F' ),
437+ constr_id = con .constr_id )
438+ cons .append (con )
439+ # Reorder constraints to Zero, NonNeg, SOC, PSD, EXP, PowCone3D, PowConeND
440+ constr_map = group_constraints (cons )
441+ ordered_cons = constr_map [Zero ] + constr_map [NonNeg ] + \
442+ constr_map [SOC ] + constr_map [PSD ] + constr_map [ExpCone ] + \
443+ constr_map [PowCone3D ] + constr_map [PowConeND ]
444+ cons_id_map = {con .id : con .id for con in ordered_cons }
445+ return ordered_cons , cons_id_map
446+
447+
342448class ConeMatrixStuffing (MatrixStuffing ):
343449 """Construct matrices for linear cone problems.
344450
@@ -380,51 +486,20 @@ def stuffed_objective(self, problem, extractor):
380486
381487 def apply (self , problem ):
382488 inverse_data = InverseData (problem )
383- # Lower equality and inequality to Zero and NonNeg.
384- cons = []
385- for con in problem .constraints :
386- if isinstance (con , Equality ):
387- con = lower_equality (con )
388- elif isinstance (con , Inequality ):
389- con = lower_ineq_to_nonneg (con )
390- elif isinstance (con , SOC ) and con .axis == 1 :
391- con = SOC (con .args [0 ], con .args [1 ].T , axis = 0 ,
392- constr_id = con .constr_id )
393- elif isinstance (con , PowCone3D ) and con .args [0 ].ndim > 1 :
394- x , y , z = con .args
395- alpha = con .alpha
396- con = PowCone3D (x .flatten (order = 'F' ),
397- y .flatten (order = 'F' ),
398- z .flatten (order = 'F' ),
399- alpha .flatten (order = 'F' ),
400- constr_id = con .constr_id )
401- elif isinstance (con , PowConeND ) and con .axis == 1 :
402- alpha = con .alpha .T
403- W = con .W .T
404- con = PowConeND (W , con .z .flatten (order = 'F' ),
405- alpha ,
406- axis = 0 ,
407- constr_id = con .constr_id )
408- elif isinstance (con , ExpCone ) and con .args [0 ].ndim > 1 :
409- x , y , z = con .args
410- con = ExpCone (x .flatten (order = 'F' ), y .flatten (order = 'F' ), z .flatten (order = 'F' ),
411- constr_id = con .constr_id )
412- cons .append (con )
489+ # Lower equality and inequality constraints and reorder by cone type.
490+ ordered_cons , cons_id_map = lower_and_order_constraints (problem .constraints )
491+ inverse_data .cons_id_map = cons_id_map
492+ inverse_data .constraints = ordered_cons
493+
413494 # Need to check that intended canonicalization backend still works.
414- lowered_con_problem = problem .copy ([problem .objective , cons ])
495+ lowered_con_problem = problem .copy (
496+ [problem .objective , ordered_cons ])
415497 canon_backend = get_canon_backend (lowered_con_problem , self .canon_backend )
416498 # Form the constraints
417499 extractor = CoeffExtractor (inverse_data , canon_backend )
418500 params_to_P , params_to_c , flattened_variable = self .stuffed_objective (
419501 problem , extractor )
420- # Reorder constraints to Zero, NonNeg, SOC, PSD, EXP, PowCone3D, PowConeND
421- constr_map = group_constraints (cons )
422- ordered_cons = constr_map [Zero ] + constr_map [NonNeg ] + \
423- constr_map [SOC ] + constr_map [PSD ] + constr_map [ExpCone ] + \
424- constr_map [PowCone3D ] + constr_map [PowConeND ]
425- inverse_data .cons_id_map = {con .id : con .id for con in ordered_cons }
426502
427- inverse_data .constraints = ordered_cons
428503 # Batch expressions together, then split apart.
429504 expr_list = [arg for c in ordered_cons for arg in c .args ]
430505 params_to_problem_data = extractor .affine (expr_list )
0 commit comments