cvxpy icon indicating copy to clipboard operation
cvxpy copied to clipboard

Presence of certain functions of a matrix Parameter as a constant offset to quadratic objective function leads to compilation failure in QpMatrixStuffing

Open GGooeytoe opened this issue 5 months ago • 7 comments

Describe the bug Objective functions involving constant functions of a matrix parameter sometimes lead to a compilation failure. Specifically, the AddExpression object has no attribute '_lazy_canonical_form.' This is very opaque so I don't really understand what is causing it; it seems to be similar to #2460.

I can workaround by doing some extra logic to realize that the parameter is ending up as a constant offset to the objective and just leaving it out, but the behavior is unexpected (constant offsets change nothing, and the problem is identified as DPP).

To Reproduce This script has a constant function of a scalar parameter as an offset to the objective function and compiles and solves just fine:

n=2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
p=cp.Parameter(())
p.value=1
objective_ok = cp.sum_squares(x + y)-2*p
problem_ok = cp.Problem(cp.Minimize(objective_ok))
problem_ok.solve(verbose=True)

This script is equivalent except the constant offset involves a matrix parameter instead and fails:

n = 2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
P = cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)-2*cp.log_det(P)
problem = cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True)

using cp.lambda_min,cp.lambda_sum_smallest also fails in the same way. cp.lambda_max runs fine, as does cp.sum_squares, cp.sum, cp.log_sum_exp, cp.min. I hypothesize that the issue is concave functions of unknown sign?

Expected behavior The DPP problem should compile down to OSQP and solve.

Output Run:

n = 2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
P = cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)-2*cp.log_det(P)
problem = cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True)

Receive:

===============================================================================
                                     CVXPY
                                     v1.7.2
===============================================================================
(CVXPY) Oct 29 06:29:30 PM: Your problem has 4 variables, 0 constraints, and 4 parameters.
(CVXPY) Oct 29 06:29:30 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Oct 29 06:29:30 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Oct 29 06:29:30 PM: Your problem is compiled with the CPP canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation
-------------------------------------------------------------------------------
(CVXPY) Oct 29 06:29:30 PM: Compiling problem (target solver=OSQP).
(CVXPY) Oct 29 06:29:30 PM: Reduction chain: CvxAttr2Constr -> Qp2SymbolicQp -> QpMatrixStuffing -> OSQP
(CVXPY) Oct 29 06:29:30 PM: Applying reduction CvxAttr2Constr
(CVXPY) Oct 29 06:29:30 PM: Applying reduction Qp2SymbolicQp
(CVXPY) Oct 29 06:29:30 PM: Applying reduction QpMatrixStuffing
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'AddExpression' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'NegExpression' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'multiply' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'log_det' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
Cell In[86], line 8
      6 objective = cp.sum_squares(x + y)-2*cp.log_det(P)
      7 problem = cp.Problem(cp.Minimize(objective))
----> 8 problem.solve(verbose=True)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:609, in Problem.solve(self, *args, **kwargs)
    606         raise ValueError(
    607             "Cannot specify both 'solver' and 'solver_path'. Please choose one.")
    608     return self._solve_solver_path(solve_func,solver_path, args, kwargs)
--> 609 return solve_func(self, *args, **kwargs)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:1191, in Problem._solve(self, solver, warm_start, verbose, bibtex, gp, qcp, requires_grad, enforce_dpp, ignore_dpp, canon_backend, **kwargs)
   1188         self.unpack(chain.retrieve(soln))
   1189         return self.value
-> 1191 data, solving_chain, inverse_data = self.get_problem_data(
   1192     solver, gp, enforce_dpp, ignore_dpp, verbose, canon_backend, kwargs
   1193 )
   1195 if verbose:
   1196     print(_NUM_SOLVER_STR)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:805, in Problem.get_problem_data(self, solver, gp, enforce_dpp, ignore_dpp, verbose, canon_backend, solver_opts)
    802     s.LOGGER.info(
    803              'Compiling problem (target solver=%s).', solver_name)
    804     s.LOGGER.info('Reduction chain: %s', reduction_chain_str)
--> 805 data, inverse_data = solving_chain.apply(self, verbose)
    806 safe_to_cache = (
    807     isinstance(data, dict)
    808     and s.PARAM_PROB in data
    809     and not any(isinstance(reduction, EvalParams)
    810                 for reduction in solving_chain.reductions)
    811 )
    812 self._compilation_time = time.time() - start

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\chain.py:76, in Chain.apply(self, problem, verbose)
     74     if verbose:
     75         s.LOGGER.info('Applying reduction %s', type(r).__name__)
---> 76     problem, inv = r.apply(problem)
     77     inverse_data.append(inv)
     78 return problem, inverse_data

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\qp2quad_form\qp_matrix_stuffing.py:272, in QpMatrixStuffing.apply(self, problem)
    270 # Form the constraints
    271 extractor = CoeffExtractor(inverse_data, canon_backend)
--> 272 params_to_P, params_to_q, flattened_variable = self.stuffed_objective(
    273     problem, extractor)
    275 # Reorder constraints to Zero, NonNeg.
    276 constr_map = group_constraints(cons)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\qp2quad_form\qp_matrix_stuffing.py:244, in QpMatrixStuffing.stuffed_objective(self, problem, extractor)
    241 def stuffed_objective(self, problem, extractor):
    242     # extract to 0.5 * x.T * P * x + q.T * x + r
    243     expr = problem.objective.expr.copy()
--> 244     params_to_P, params_to_q = extractor.quad_form(expr)
    245     # Handle 0.5 factor.
    246     params_to_P = 2*params_to_P

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\coeff_extractor.py:222, in CoeffExtractor.quad_form(self, expr)
    218 quad_forms = replace_quad_forms(root, {})
    220 # Calculate affine parts and combine them with quadratic forms to get
    221 # the coefficients.
--> 222 coeffs, constant = self.extract_quadratic_coeffs(root.args[0],
    223                                                  quad_forms)
    224 # Restore expression.
    225 restore_quad_forms(root.args[0], quad_forms)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\coeff_extractor.py:95, in CoeffExtractor.extract_quadratic_coeffs(self, affine_expr, quad_forms)
     85 # Here we take the problem objective, replace all the SymbolicQuadForm
     86 # atoms with variables of the same dimensions.
     87 # We then apply the canonInterface to reduce the "affine head"
   (...)     91 # [d1 d2 ...]
     92 # where ci,di are the vector and constant for the ith parameter.
     93 affine_id_map, affine_offsets, x_length, affine_var_shapes = \
     94     InverseData.get_var_offsets(affine_expr.variables())
---> 95 op_list = [affine_expr.canonical_form[0]]
     96 param_coeffs = canonInterface.get_problem_matrix(op_list,
     97                                                  x_length,
     98                                                  affine_offsets,
   (...)    101                                                  affine_expr.size,
    102                                                  self.canon_backend)
    104 # Iterates over every entry of the parameters vector,
    105 # and obtains the Pi and qi for that entry i.
    106 # These are then combined into matrices [P1.flatten(), P2.flatten(), ...]
    107 # and [q1, q2, ...]

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:327, in Atom.canonicalize(self)
    325 # Special info required by the graph implementation.
    326 data = self.get_data()
--> 327 graph_obj, graph_constr = self.graph_implementation(arg_objs,
    328                                                     self.shape,
    329                                                     data)
    330 return graph_obj, constraints + graph_constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:351, in Atom.graph_implementation(self, arg_objs, shape, data)
    332 def graph_implementation(
    333     self, arg_objs, shape: Tuple[int, ...], data=None
    334 ) -> Tuple[lo.LinOp, List['Constraint']]:
    335     """Reduces the atom to an affine expression and list of constraints.
    336
    337     Parameters
   (...)    349         (LinOp for objective, list of constraints)
    350     """
--> 351     raise NotImplementedError()

NotImplementedError:

Version

  • OS: Windows 11
  • CVXPY Version: v1.7.2

GGooeytoe avatar Oct 29 '25 22:10 GGooeytoe

@GGooeytoe sorry it took so long to review your issue. Could you try with the SCIPY backend? by specifying canon_backend=cp.SCIPY_CANON_BACKEND? Graph implementations (in my understanding) are only for the CPP backend and it might be the cause of the issue? It is really weird that you end up calling this method from the atom superclass though.. I feel like that shouldn't really happen.

Transurgeon avatar Nov 19 '25 14:11 Transurgeon

Doesn't look like it matters. In fact, in the actual scenario I encountered the bug the scipy backend was getting picked automatically anyway.

Reproducer: Running:

import numpy as np
import cvxpy as cp
n=2
x=cp.Variable(n,nonneg=True)
y=cp.Variable(n,nonneg=True)
P=cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)-2*cp.log_det(P)
problem=cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True,canon_backend=cp.SCIPY_CANON_BACKEND)

Yields:

===============================================================================
                                     CVXPY
                                     v1.7.2
===============================================================================
(CVXPY) Nov 19 10:03:17 AM: Your problem has 4 variables, 0 constraints, and 4 parameters.
(CVXPY) Nov 19 10:03:17 AM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Nov 19 10:03:17 AM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Nov 19 10:03:17 AM: Your problem is compiled with the SCIPY canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation
-------------------------------------------------------------------------------
(CVXPY) Nov 19 10:03:17 AM: Compiling problem (target solver=OSQP).
(CVXPY) Nov 19 10:03:17 AM: Reduction chain: CvxAttr2Constr -> Qp2SymbolicQp -> QpMatrixStuffing -> OSQP
(CVXPY) Nov 19 10:03:17 AM: Applying reduction CvxAttr2Constr
(CVXPY) Nov 19 10:03:17 AM: Applying reduction Qp2SymbolicQp
(CVXPY) Nov 19 10:03:17 AM: Applying reduction QpMatrixStuffing
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'AddExpression' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'NegExpression' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'multiply' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:37, in lazyprop.<locals>._lazyprop(self)
     36 try:
---> 37     return getattr(self, attr_name)
     38 except AttributeError:

AttributeError: 'log_det' object has no attribute '_lazy_canonical_form'

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
Cell In[11], line 1
----> 1 problem.solve(verbose=True,canon_backend=cp.SCIPY_CANON_BACKEND)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:609, in Problem.solve(self, *args, **kwargs)
    606         raise ValueError(
    607             "Cannot specify both 'solver' and 'solver_path'. Please choose one.")
    608     return self._solve_solver_path(solve_func,solver_path, args, kwargs)
--> 609 return solve_func(self, *args, **kwargs)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:1191, in Problem._solve(self, solver, warm_start, verbose, bibtex, gp, qcp, requires_grad, enforce_dpp, ignore_dpp, canon_backend, **kwargs)
   1188         self.unpack(chain.retrieve(soln))
   1189         return self.value
-> 1191 data, solving_chain, inverse_data = self.get_problem_data(
   1192     solver, gp, enforce_dpp, ignore_dpp, verbose, canon_backend, kwargs
   1193 )
   1195 if verbose:
   1196     print(_NUM_SOLVER_STR)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\problems\problem.py:805, in Problem.get_problem_data(self, solver, gp, enforce_dpp, ignore_dpp, verbose, canon_backend, solver_opts)
    802     s.LOGGER.info(
    803              'Compiling problem (target solver=%s).', solver_name)
    804     s.LOGGER.info('Reduction chain: %s', reduction_chain_str)
--> 805 data, inverse_data = solving_chain.apply(self, verbose)
    806 safe_to_cache = (
    807     isinstance(data, dict)
    808     and s.PARAM_PROB in data
    809     and not any(isinstance(reduction, EvalParams)
    810                 for reduction in solving_chain.reductions)
    811 )
    812 self._compilation_time = time.time() - start

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\chain.py:76, in Chain.apply(self, problem, verbose)
     74     if verbose:
     75         s.LOGGER.info('Applying reduction %s', type(r).__name__)
---> 76     problem, inv = r.apply(problem)
     77     inverse_data.append(inv)
     78 return problem, inverse_data

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\qp2quad_form\qp_matrix_stuffing.py:272, in QpMatrixStuffing.apply(self, problem)
    270 # Form the constraints
    271 extractor = CoeffExtractor(inverse_data, canon_backend)
--> 272 params_to_P, params_to_q, flattened_variable = self.stuffed_objective(
    273     problem, extractor)
    275 # Reorder constraints to Zero, NonNeg.
    276 constr_map = group_constraints(cons)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\reductions\qp2quad_form\qp_matrix_stuffing.py:244, in QpMatrixStuffing.stuffed_objective(self, problem, extractor)
    241 def stuffed_objective(self, problem, extractor):
    242     # extract to 0.5 * x.T * P * x + q.T * x + r
    243     expr = problem.objective.expr.copy()
--> 244     params_to_P, params_to_q = extractor.quad_form(expr)
    245     # Handle 0.5 factor.
    246     params_to_P = 2*params_to_P

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\coeff_extractor.py:222, in CoeffExtractor.quad_form(self, expr)
    218 quad_forms = replace_quad_forms(root, {})
    220 # Calculate affine parts and combine them with quadratic forms to get
    221 # the coefficients.
--> 222 coeffs, constant = self.extract_quadratic_coeffs(root.args[0],
    223                                                  quad_forms)
    224 # Restore expression.
    225 restore_quad_forms(root.args[0], quad_forms)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\coeff_extractor.py:95, in CoeffExtractor.extract_quadratic_coeffs(self, affine_expr, quad_forms)
     85 # Here we take the problem objective, replace all the SymbolicQuadForm
     86 # atoms with variables of the same dimensions.
     87 # We then apply the canonInterface to reduce the "affine head"
   (...)     91 # [d1 d2 ...]
     92 # where ci,di are the vector and constant for the ith parameter.
     93 affine_id_map, affine_offsets, x_length, affine_var_shapes = \
     94     InverseData.get_var_offsets(affine_expr.variables())
---> 95 op_list = [affine_expr.canonical_form[0]]
     96 param_coeffs = canonInterface.get_problem_matrix(op_list,
     97                                                  x_length,
     98                                                  affine_offsets,
   (...)    101                                                  affine_expr.size,
    102                                                  self.canon_backend)
    104 # Iterates over every entry of the parameters vector,
    105 # and obtains the Pi and qi for that entry i.
    106 # These are then combined into matrices [P1.flatten(), P2.flatten(), ...]
    107 # and [q1, q2, ...]

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:322, in Atom.canonicalize(self)
    320 constraints = []
    321 for arg in self.args:
--> 322     obj, constr = arg.canonical_form
    323     arg_objs.append(obj)
    324     constraints += constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\performance_utils.py:39, in lazyprop.<locals>._lazyprop(self)
     37     return getattr(self, attr_name)
     38 except AttributeError:
---> 39     setattr(self, attr_name, func(self))
     40 return getattr(self, attr_name)

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\utilities\canonical.py:43, in Canonical.canonical_form(self)
     36 @pu.lazyprop
     37 def canonical_form(self):
     38     """The graph implementation of the object stored as a property.
     39
     40     Returns:
     41         A tuple of (affine expression, [constraints]).
     42     """
---> 43     return self.canonicalize()

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:327, in Atom.canonicalize(self)
    325 # Special info required by the graph implementation.
    326 data = self.get_data()
--> 327 graph_obj, graph_constr = self.graph_implementation(arg_objs,
    328                                                     self.shape,
    329                                                     data)
    330 return graph_obj, constraints + graph_constr

File ~\.local\share\mamba\envs\unsupervised_covariances\Lib\site-packages\cvxpy\atoms\atom.py:351, in Atom.graph_implementation(self, arg_objs, shape, data)
    332 def graph_implementation(
    333     self, arg_objs, shape: Tuple[int, ...], data=None
    334 ) -> Tuple[lo.LinOp, List['Constraint']]:
    335     """Reduces the atom to an affine expression and list of constraints.
    336
    337     Parameters
   (...)    349         (LinOp for objective, list of constraints)
    350     """
--> 351     raise NotImplementedError()

NotImplementedError:

GGooeytoe avatar Nov 19 '25 15:11 GGooeytoe

@GGooeytoe this might be fixed with the recent cleanup of the QP pathway (#3015). Could you install cvxpy from master and try again to see if your issue is fixed now?

Transurgeon avatar Nov 27 '25 16:11 Transurgeon

Sorry for the very delayed response; lost the message in my inbox.

TL;DR is that master does compile successfully (progress), but gives the wrong answer (uh-oh).

I ran:

import cvxpy as cp
import numpy as np
n = 2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
P = cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)-2*cp.log_det(P)
problem = cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True)

and received:

===============================================================================
                                     CVXPY
                            v1.8.0.dev0+171.7206dcc
===============================================================================
(CVXPY) Dec 08 07:20:08 PM: Your problem has 4 variables, 0 constraints, and 4 parameters.
(CVXPY) Dec 08 07:20:08 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Dec 08 07:20:08 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Dec 08 07:20:08 PM: Your problem is compiled with the CPP canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:20:08 PM: Compiling problem (target solver=OSQP).
(CVXPY) Dec 08 07:20:08 PM: Reduction chain: Dcp2Cone -> CvxAttr2Constr -> ConeMatrixStuffing -> OSQP
(CVXPY) Dec 08 07:20:08 PM: Applying reduction Dcp2Cone
(CVXPY) Dec 08 07:20:08 PM: Applying reduction CvxAttr2Constr
(CVXPY) Dec 08 07:20:08 PM: Applying reduction ConeMatrixStuffing
(CVXPY) Dec 08 07:20:08 PM: Applying reduction OSQP
(CVXPY) Dec 08 07:20:08 PM: Finished problem compilation (took 4.585e-02 seconds).
(CVXPY) Dec 08 07:20:08 PM: (Subsequent compilations of this problem, using the same arguments, should take less time.)
-------------------------------------------------------------------------------
                                Numerical solver
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:20:08 PM: Invoking solver OSQP  to obtain a solution.
-----------------------------------------------------------------
           OSQP v1.0.0  -  Operator Splitting QP Solver
              (c) The OSQP Developer Team
-----------------------------------------------------------------
problem:  variables n = 11, constraints m = 28
          nnz(P) + nnz(A) = 24
settings: algebra = Built-in,
          OSQPInt = 4 bytes, OSQPFloat = 8 bytes,
          linear system solver = QDLDL v0.1.8,
          eps_abs = 1.0e-05, eps_rel = 1.0e-05,
          eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
          rho = 1.00e-01 (adaptive: 50 iterations),
          sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
          check_termination: on (interval 25, duality gap: on),
          time_limit: 1.00e+10 sec,
          scaling: on (10 iterations), scaled_termination: off
          warm starting: on, polishing: on,
iter   objective    prim res   dual res   gap        rel kkt    rho         time
   1  -6.3999e+01   1.62e-10   2.00e+00  -6.40e+01   2.00e+00   1.00e-01    1.23e-04s
  25  -1.0000e+30   1.13e-10   2.00e+00  -1.60e+03   2.00e+00   1.00e-01    2.43e-04s

status:               dual infeasible
number of iterations: 25
run time:             3.21e-04s
optimal rho estimate: 1.00e-06

-------------------------------------------------------------------------------
                                    Summary
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:20:08 PM: Problem status: unbounded
(CVXPY) Dec 08 07:20:08 PM: Optimal value: -inf
(CVXPY) Dec 08 07:20:08 PM: Compilation took 4.585e-02 seconds
(CVXPY) Dec 08 07:20:08 PM: Solver (including time spent in interface) took 2.707e-03 seconds

Which is great, it compiles and runs, but bad in that the solution is wrong. The cost function, for the given parameters, can NEVER be negative (sum_squares is non negative, x and y are constrained non negative anyway, and log_det(P) is 0). CVXPY even recognizes that the cp.sum_squares(x+y) term is nonnegative. I get the same, incorrect, answer using the SCIPY backend:

import cvxpy as cp
import numpy as np
n = 2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
P = cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)-2*cp.log_det(P)
problem = cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True,canon_backend=cp.SCIPY_CANON_BACKEND)

yields:

===============================================================================
                                     CVXPY
                            v1.8.0.dev0+171.7206dcc
===============================================================================
(CVXPY) Dec 08 07:29:20 PM: Your problem has 4 variables, 0 constraints, and 4 parameters.
(CVXPY) Dec 08 07:29:20 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Dec 08 07:29:20 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Dec 08 07:29:20 PM: Your problem is compiled with the SCIPY canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:29:20 PM: Compiling problem (target solver=OSQP).
(CVXPY) Dec 08 07:29:20 PM: Reduction chain: Dcp2Cone -> CvxAttr2Constr -> ConeMatrixStuffing -> OSQP
(CVXPY) Dec 08 07:29:20 PM: Applying reduction Dcp2Cone
(CVXPY) Dec 08 07:29:20 PM: Applying reduction CvxAttr2Constr
(CVXPY) Dec 08 07:29:20 PM: Applying reduction ConeMatrixStuffing
(CVXPY) Dec 08 07:29:20 PM: Applying reduction OSQP
(CVXPY) Dec 08 07:29:20 PM: Finished problem compilation (took 1.181e-02 seconds).
(CVXPY) Dec 08 07:29:20 PM: (Subsequent compilations of this problem, using the same arguments, should take less time.)
-------------------------------------------------------------------------------
                                Numerical solver
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:29:20 PM: Invoking solver OSQP  to obtain a solution.
-----------------------------------------------------------------
           OSQP v1.0.0  -  Operator Splitting QP Solver
              (c) The OSQP Developer Team
-----------------------------------------------------------------
problem:  variables n = 11, constraints m = 28
          nnz(P) + nnz(A) = 24
settings: algebra = Built-in,
          OSQPInt = 4 bytes, OSQPFloat = 8 bytes,
          linear system solver = QDLDL v0.1.8,
          eps_abs = 1.0e-05, eps_rel = 1.0e-05,
          eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
          rho = 1.00e-01 (adaptive: 50 iterations),
          sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
          check_termination: on (interval 25, duality gap: on),
          time_limit: 1.00e+10 sec,
          scaling: on (10 iterations), scaled_termination: off
          warm starting: on, polishing: on,
iter   objective    prim res   dual res   gap        rel kkt    rho         time
   1  -6.3999e+01   1.62e-10   2.00e+00  -6.40e+01   2.00e+00   1.00e-01    1.01e-04s
  25  -1.0000e+30   1.13e-10   2.00e+00  -1.60e+03   2.00e+00   1.00e-01    1.78e-04s

status:               dual infeasible
number of iterations: 25
run time:             2.65e-04s
optimal rho estimate: 1.00e-06

-------------------------------------------------------------------------------
                                    Summary
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:29:20 PM: Problem status: unbounded
(CVXPY) Dec 08 07:29:20 PM: Optimal value: -inf
(CVXPY) Dec 08 07:29:20 PM: Compilation took 1.181e-02 seconds
(CVXPY) Dec 08 07:29:20 PM: Solver (including time spent in interface) took 2.642e-03 seconds

Removing the parameter results in the correct answer:

import cvxpy as cp
import numpy as np
n = 2
x = cp.Variable(n, nonneg=True)
y = cp.Variable(n, nonneg=True)
P = cp.Parameter((n,n))
P.value=np.array([[1.0,0.0],[0.0,1.0]])
objective = cp.sum_squares(x + y)
problem = cp.Problem(cp.Minimize(objective))
problem.solve(verbose=True)

yields:

===============================================================================
                                     CVXPY
                            v1.8.0.dev0+171.7206dcc
===============================================================================
(CVXPY) Dec 08 07:26:07 PM: Your problem has 4 variables, 0 constraints, and 0 parameters.
(CVXPY) Dec 08 07:26:07 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Dec 08 07:26:07 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)
(CVXPY) Dec 08 07:26:07 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
(CVXPY) Dec 08 07:26:07 PM: Your problem is compiled with the CPP canonicalization backend.
-------------------------------------------------------------------------------
                                  Compilation
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:26:07 PM: Compiling problem (target solver=OSQP).
(CVXPY) Dec 08 07:26:07 PM: Reduction chain: Dcp2Cone -> CvxAttr2Constr -> ConeMatrixStuffing -> OSQP
(CVXPY) Dec 08 07:26:07 PM: Applying reduction Dcp2Cone
(CVXPY) Dec 08 07:26:07 PM: Applying reduction CvxAttr2Constr
(CVXPY) Dec 08 07:26:07 PM: Applying reduction ConeMatrixStuffing
(CVXPY) Dec 08 07:26:07 PM: Applying reduction OSQP
(CVXPY) Dec 08 07:26:07 PM: Finished problem compilation (took 5.926e-03 seconds).
-------------------------------------------------------------------------------
                                Numerical solver
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:26:07 PM: Invoking solver OSQP  to obtain a solution.
-----------------------------------------------------------------
           OSQP v1.0.0  -  Operator Splitting QP Solver
              (c) The OSQP Developer Team
-----------------------------------------------------------------
problem:  variables n = 6, constraints m = 6
          nnz(P) + nnz(A) = 12
settings: algebra = Built-in,
          OSQPInt = 4 bytes, OSQPFloat = 8 bytes,
          linear system solver = QDLDL v0.1.8,
          eps_abs = 1.0e-05, eps_rel = 1.0e-05,
          eps_prim_inf = 1.0e-04, eps_dual_inf = 1.0e-04,
          rho = 1.00e-01 (adaptive: 50 iterations),
          sigma = 1.00e-06, alpha = 1.60, max_iter = 10000
          check_termination: on (interval 25, duality gap: on),
          time_limit: 1.00e+10 sec,
          scaling: on (10 iterations), scaled_termination: off
          warm starting: on, polishing: on,
iter   objective    prim res   dual res   gap        rel kkt    rho         time
   1   0.0000e+00   0.00e+00   0.00e+00   0.00e+00   0.00e+00   1.00e-01    1.05e-04s
  25   0.0000e+00   0.00e+00   0.00e+00   0.00e+00   0.00e+00   1.00e-01    1.82e-04s

status:               solved
solution polishing:   unsuccessful
number of iterations: 25
optimal objective:    0.0000
dual objective:       -0.0000
duality gap:          0.0000e+00
primal-dual integral: 0.0000e+00
run time:             2.62e-04s
optimal rho estimate: 1.00e-06

-------------------------------------------------------------------------------
                                    Summary
-------------------------------------------------------------------------------
(CVXPY) Dec 08 07:26:07 PM: Problem status: optimal
(CVXPY) Dec 08 07:26:07 PM: Optimal value: 0.000e+00
(CVXPY) Dec 08 07:26:07 PM: Compilation took 5.926e-03 seconds
(CVXPY) Dec 08 07:26:07 PM: Solver (including time spent in interface) took 2.929e-03 seconds

GGooeytoe avatar Dec 09 '25 00:12 GGooeytoe

@GGooeytoe this turned out to be a very complex bug https://github.com/cvxpy/cvxpy/pull/3027 Fixed now though.

SteveDiamond avatar Dec 09 '25 01:12 SteveDiamond

Thank you!

Long term it might be nice to introduce some presolve-like operations to try to simplify problems like this; the example problem really can be solved as a QP and handling it via exponential and PSD constraints is kind of overkill. Detecting this very simple case of the parameter just being a constant offset probably isn't too hard, but I'm not sure how you would go about more general cases.

GGooeytoe avatar Dec 09 '25 14:12 GGooeytoe

@GGooeytoe if you set ignore_dpp=True in solve it will simplify to a QP and in general evaluate all constant expressions and eliminate the atoms involved. The complexity is around DPP (https://www.cvxpy.org/tutorial/dpp/index.html), where the parameter needs to enter the solver problem data linearly. Your example is definitely a strange case where it would probably be better to flag it as non-DPP, even though it can theoretically be canonicalized into a DPP compliant format. Something for us to think about.

SteveDiamond avatar Dec 09 '25 16:12 SteveDiamond

thanks for fixing @SteveDiamond , this was indeed a very weird bug. I think we can close this for now as #3027 raises proper error messages for handling this DPP + non-supported cones in QP solvers case.

Transurgeon avatar Dec 11 '25 12:12 Transurgeon