Skip to content

Allow array denominator in quad_over_lin#3262

Open
gugar20 wants to merge 6 commits into
cvxpy:masterfrom
gugar20:gugar20/issue-1197-vectorized-quad-over-lin
Open

Allow array denominator in quad_over_lin#3262
gugar20 wants to merge 6 commits into
cvxpy:masterfrom
gugar20:gugar20/issue-1197-vectorized-quad-over-lin

Conversation

@gugar20
Copy link
Copy Markdown

@gugar20 gugar20 commented Mar 26, 2026

Description

Closes #1197.

  • Allow quad_over_lin(x, y) where x and y are broadcast-compatible.
  • Broadcasts, computes element-wise x_ij^2 / y_ij, then reduces along axis.
  • Follow the AxisAtom convention: axis=None (default) sums all elements to a scalar. axis=() gives element-wise output with no reduction. axis=k reduces along axis k.
  • Update all four canonicalizers: SOC (dcp2cone), QP (quad), DGP (dgp2dcp), and DNLP (dnlp2smooth).

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.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 26, 2026

CLA assistant check
All committers have signed the CLA.

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 26, 2026

Please use our PR template

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 26, 2026

Can you explain why we wouldn't include broadcasting between x and y? What do you mean by reduce?

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 26, 2026

Please install and run the pre commit

@PTNobel
Copy link
Copy Markdown
Collaborator

PTNobel commented Mar 26, 2026

My first thought in my review is that the tests seem way more complicated than for any comparable atoms. Can you compress them?

@gugar20
Copy link
Copy Markdown
Author

gugar20 commented Mar 26, 2026

I will look into broadcasting, by reduce I meant sum

I thought I had set up pre-commit properly, everything is green locally, I'll resolve.

I will simplify the tests to match other similar atoms.

@gugar20
Copy link
Copy Markdown
Author

gugar20 commented Mar 26, 2026

I implemented broadcasting, cut down on the tests and checked my setup of pre-commit (but didn't find any failures).

else:
axes = normalize_axis_tuple(axis, ndim)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I had thought this would be prohibited my the pre-commit... I need to debug why it isn't firing

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Claude flagged that as rule W293; I tried applying it but got a lot of changes so left it out and removed the whitespace

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Okay we just merged W293 in as part of #3264, thanks for ctching it wasn't enforced!

@gugar20 gugar20 force-pushed the gugar20/issue-1197-vectorized-quad-over-lin branch from 1af97c8 to 19079b6 Compare March 30, 2026 16:17
@gugar20 gugar20 requested a review from PTNobel March 30, 2026 16:20
Comment thread cvxpy/atoms/affine/broadcast_to.py Outdated

def lazy_broadcast_to(expr, shape):
"""Broadcast expr to shape only if necessary."""
return expr if expr.shape == shape else broadcast_to(expr, shape)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

broadcast_to is a No-Op in many cases, I'm surprised this is doing anything. What inspired you to add it?

Copy link
Copy Markdown
Author

@gugar20 gugar20 Mar 30, 2026

Choose a reason for hiding this comment

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

I initially added it because I broke 2 tests* and thought it caused a benchmark regression. I just reran the affected benchmark a few times with/without and it actually looked like noise, so I removed it (and updated affected tests). I should mention that since this PR would always call broadcast_to in SOC and QP canonicalizers and it doesn't support cpp, these problems will be canonicalized with Scipy. Using lazy_broadcast_to prevented that (returned the variable itself, so didn't add a cpp blocker to quad_over_lin).

Another notable difference is that even when broadcast_to is a no-op, the QP canonicalizer adds an extra expression here, so e.g. users with array x, scalar y (the existing supported behavior) would see different problem_data. This is why I changed the tests which used problem data to adjust for the larger P. I don't know if it affects performance though.

Comment thread cvxpy/atoms/quad_over_lin.py Outdated
normalize_axis_tuple(axes, len(bshape))
Atom.validate_arguments(self)

def shape_from_args(self) -> Tuple[int, ...]:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't AxisAtom do all this for us?

Copy link
Copy Markdown
Author

@gugar20 gugar20 Mar 30, 2026

Choose a reason for hiding this comment

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

AxisAtom infers the shape from the first argument, but with broadcasting in quad_over_lin we need both x and y to determine the full broadcast shape, and apply the axis reductions to this shape. For example, if x.shape = (3,), y.shape = (5,4,3); broadcast_shape = (5,4,3) and axis=0 gives (4,3). We'd get () from AxisAtom (scalar result).

Edit: Maybe I misunderstood, did you mean that this behavior should be handled by AxisAtom? I think quad_over_lin is the only multi-argument subclass to AxisAtom

"""
return self.args[0].is_pwl() and self.args[1].is_constant()

def _unbroadcast(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this the transpose of broadcasting?

Copy link
Copy Markdown
Author

@gugar20 gugar20 Mar 30, 2026

Choose a reason for hiding this comment

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

It sums on the extra axes between arr and the target shape, the unbroadcast name doesn't exactly convey this but it's used for lack of a better name. (And because it applies the logic of np.broadcast backwards to determine the axes to sum on)

@gugar20 gugar20 requested a review from PTNobel March 30, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vectorized quad-over-lin or similar?

3 participants