Skip to content

Conversation

@Ni2002ka
Copy link
Contributor

@Ni2002ka Ni2002ka commented Nov 18, 2025

Description

Building on PR #2999, we prefer to canonicalize the power atom without any approximations. In this PR, we automatically canonicalize to a 3D power cone (instead of the approximated SOC) if the chosen solver supports 3D power cones, unless approximation is being enforced by the user.
Similar to the discussion of PR #2987, we introduce a new class, SolverInfo, which propagates the solver's supported constraints through the solving chain. During canonicalization, the solver's supported constraints are checked and the canonicalization defaults to 3D power cones if possible.
In future PRs, the SolverInfo class can be enhanced with any additional fields that need to be passed throughout the solving chain.

Type of change

  • New feature (backwards compatible)
  • New feature (breaking API changes)
  • Bug fix
  • Other (Documentation, CI, ...)

Contribution checklist

  • Add our license to new files.
  • Check that your code adheres to our coding style.
  • Write unittests.
  • Run the unittests and check that they’re passing.
  • Run the benchmarks to make sure your change doesn’t introduce a regression.

@github-actions
Copy link

github-actions bot commented Nov 18, 2025

Benchmarks that have stayed the same:

   before           after         ratio
 [51b507e9]       [7f1cf787]
      1.36±0s          1.50±0s     1.10  matrix_stuffing.ParamConeMatrixStuffing.time_compile_problem
      566±0ms          597±0ms     1.05  simple_QP_benchmarks.ParametrizedQPBenchmark.time_compile_problem
      5.16±0s          5.34±0s     1.04  svm_l1_regularization.SVMWithL1Regularization.time_compile_problem
      22.3±0s          23.1±0s     1.04  sdp_segfault_1132_benchmark.SDPSegfault1132Benchmark.time_compile_problem
      289±0ms          298±0ms     1.03  matrix_stuffing.ParamSmallMatrixStuffing.time_compile_problem
      536±0ms          549±0ms     1.02  semidefinite_programming.SemidefiniteProgramming.time_compile_problem
      13.0±0s          13.3±0s     1.02  finance.CVaRBenchmark.time_compile_problem
      235±0ms          239±0ms     1.02  gini_portfolio.Murray.time_compile_problem
      721±0ms          732±0ms     1.01  matrix_stuffing.ConeMatrixStuffingBench.time_compile_problem
      2.34±0s          2.38±0s     1.01  simple_LP_benchmarks.SimpleFullyParametrizedLPBenchmark.time_compile_problem
      836±0ms          847±0ms     1.01  simple_QP_benchmarks.LeastSquares.time_compile_problem
      4.60±0s          4.62±0s     1.01  huber_regression.HuberRegression.time_compile_problem
     44.3±0ms         44.6±0ms     1.01  matrix_stuffing.SmallMatrixStuffing.time_compile_problem
      300±0ms          301±0ms     1.01  slow_pruning_1668_benchmark.SlowPruningBenchmark.time_compile_problem
      1.66±0s          1.67±0s     1.00  tv_inpainting.TvInpainting.time_compile_problem
      11.4±0s          11.4±0s     1.00  simple_LP_benchmarks.SimpleLPBenchmark.time_compile_problem
      1.86±0s          1.87±0s     1.00  simple_QP_benchmarks.UnconstrainedQP.time_compile_problem
      1.14±0s          1.15±0s     1.00  gini_portfolio.Cajas.time_compile_problem
      942±0ms          944±0ms     1.00  simple_LP_benchmarks.SimpleScalarParametrizedLPBenchmark.time_compile_problem
      4.99±0s          4.99±0s     1.00  optimal_advertising.OptimalAdvertising.time_compile_problem
      243±0ms          243±0ms     1.00  high_dim_convex_plasticity.ConvexPlasticity.time_compile_problem
      253±0ms          252±0ms     0.99  simple_QP_benchmarks.SimpleQPBenchmark.time_compile_problem
      1.09±0s          1.09±0s     0.99  finance.FactorCovarianceModel.time_compile_problem
      3.02±0s          2.92±0s     0.97  quantum_hilbert_matrix.QuantumHilbertMatrix.time_compile_problem
      361±0ms          342±0ms     0.95  gini_portfolio.Yitzhaki.time_compile_problem

@SteveDiamond
Copy link
Collaborator

I thought we were handling this by converting power to an exotic cone and then converting later to the cones supported by the solver?

Comment on lines 191 to 194
# solver_context : The solver context: supported constrains and bounds.
# NOTE: solver_context is currently only passed to power_canon.
self.solver_context = None

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a type annotation formalizing this. If it's non-None, what can it be? Here's an example that says "it's none or it's a dict keyed by strings":

self.solver_context : Optional[dict[str,Any]] = None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! I added the optional type annotation. The solver_context is a new SolverInfo class that we use to propagate the solver's supported constraints and bounds throughout the solving chain.

@PTNobel
Copy link
Collaborator

PTNobel commented Nov 21, 2025

I thought we were handling this by converting power to an exotic cone and then converting later to the cones supported by the solver?

That approach is definitely possible, but I encouraged Nika to go with this design because I think it generalizes to more features in the canonicalization better.

For example, I am encouraging Nika to work on adding what I'm calling "derived bounds":

This would change the abs canoncalization to:

if ctx.solver.SUPPORTS_BOUNDS and expr.bounds is not None:
     if expr.lower_bounds < 0 and expr.upper_bounds > 0:
         bounds = [0, max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
     else:
         bounds = [min(expr.lower_bounds.abs(), expr.upper_bounds.abs()), max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
else:
    bounds = None
t = cp.Variable(bounds=bounds)
return t, [-expr <= t, expr <= t]

this will help solvers like CUOPT and a future Clarabel version that take explicit bounds on x, by giving bounds on more variables.

@SteveDiamond
Copy link
Collaborator

I thought we were handling this by converting power to an exotic cone and then converting later to the cones supported by the solver?

That approach is definitely possible, but I encouraged Nika to go with this design because I think it generalizes to more features in the canonicalization better.

For example, I am encouraging Nika to work on adding what I'm calling "derived bounds":

This would change the abs canoncalization to:

if ctx.solver.SUPPORTS_BOUNDS and expr.bounds is not None:
     if expr.lower_bounds < 0 and expr.upper_bounds > 0:
         bounds = [0, max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
     else:
         bounds = [min(expr.lower_bounds.abs(), expr.upper_bounds.abs()), max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
else:
    bounds = None
t = cp.Variable(bounds=bounds)
return t, [-expr <= t, expr <= t]

this will help solvers like CUOPT and a future Clarabel version that take explicit bounds on x, by giving bounds on more variables.

Thanks for clarifying. The approach in this MR is fine with me then.

@Transurgeon
Copy link
Collaborator

Transurgeon commented Nov 22, 2025

I thought we were handling this by converting power to an exotic cone and then converting later to the cones supported by the solver?

That approach is definitely possible, but I encouraged Nika to go with this design because I think it generalizes to more features in the canonicalization better.

For example, I am encouraging Nika to work on adding what I'm calling "derived bounds":

This would change the abs canoncalization to:

if ctx.solver.SUPPORTS_BOUNDS and expr.bounds is not None:
     if expr.lower_bounds < 0 and expr.upper_bounds > 0:
         bounds = [0, max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
     else:
         bounds = [min(expr.lower_bounds.abs(), expr.upper_bounds.abs()), max(expr.lower_bounds.abs(), expr.upper_bounds.abs())]
else:
    bounds = None
t = cp.Variable(bounds=bounds)
return t, [-expr <= t, expr <= t]

this will help solvers like CUOPT and a future Clarabel version that take explicit bounds on x, by giving bounds on more variables.

agreed, that is going to be a super nice addition :). One quick question though, what is expr.upper_bounds? Is that an attribute to add later on? How would we compute that?

@Ni2002ka Ni2002ka marked this pull request as ready for review November 23, 2025 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants