Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@Transurgeon
Copy link
Collaborator

@Transurgeon Transurgeon commented Feb 21, 2025

Description

Please include a short summary of the change.
This PR introduces the broadcast_to atom in hopes of fixing issues with broadcasting in higher dimensions.
The broadcast_to atom is used internally inside of the expression class in its broadcast method.
This method is called in many places, including binary ops, basic ops and others.

The implementation of the atom follows other similar shape manipulation atoms such as vstack and concatenate. The idea is to form a set of indices which are reshaped to the expression's shape, then using numpy's API we are able to mimic the behavior of np.broadcast_to on cvxpy's tensor views in the backend.

CPP backend compatibility

In the #2728 discussion, I brought up some issues with the difference in behavior for the backends. Those issues were resolved as follows:

  • only call cp.broadcast_to when one of the operands has dim > 2, which ensures that the SCIPY backend is used.
  • full broadcasting is supported in those cases, even the edge cases mentioned in the discussion above.
  • in hopes of keeping the CPP backend behavior the same, both the SCIPY and CPP backends don't do full broadcasting when both operands are <= 2. However, this only omits some edge cases, as most practical scenarios are covered by existing code.

Improved error messages

Recent changes to the codebase (notably adding the cp.concatenate atom) showed that we are able to instantiate empty numpy arrays (using dtype=np.empty([])) and then perform analogous operations on those arrays to get the error handling from numpy for free. This technique has been reused here to improve on the existing multiply atom error handling.

Issue link (if applicable): #2739

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

CLAassistant commented Feb 21, 2025

CLA assistant check
All committers have signed the CLA.

@Transurgeon Transurgeon marked this pull request as ready for review February 22, 2025 07:20
@Transurgeon Transurgeon changed the title BUGFIX: Adds new broadcast_to atom for nd broadcasting Adds new broadcast_to atom for nd broadcasting Feb 22, 2025
Copy link
Collaborator Author

@Transurgeon Transurgeon left a comment

Choose a reason for hiding this comment

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

I think this is ready for a first round of reviews. I have still yet to add backend specific tests which could help understand the operator (although it is mostly about duplicating existing tensors along the rows).
Things that are open for discussion, do we want to support broadcasting for both operands? (i.e. cp.multiply((3,1), (5)) is allowed and will give an (3,5) expression)
this means we will need to broadcast both the constant data and the variable/expr which differs from the current behavior (although matches the NumPy API for broadcasting)

@pytest.mark.parametrize("shapes", [((5),(3, 1)),
((15, 1),(8)),
((3), (2, 1))])
def test_segfault_multiply(self, shapes) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you say more about this test? Would these shapes be compatible with numpy broadcasting rules?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes they are compatible with numpy broadcasting. And these tests would pass for the SCIPY backend. However, I was not able to enforce the SCIPY backend to be used in these cases. (CPP would raise errors because it doesn't have the broadcast_to atom implemented).

@SteveDiamond
Copy link
Collaborator

This is a really elegant solution for the backend! To be clear, in this MR we are not handling cases such as (5) + (5,1) -> (5,5)? We should still handle these cases but it can be in a separate MR.

Comment on lines +35 to +36
def _supports_cpp(self) -> bool:
return False
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've added this method, but I was still getting tests going through the CPP backend.
I think it's because the broadcast_to atom is being called from another atom (i.e. multiply), but not sure.
Ideally we should fix this, but maybe for another PR.

@SteveDiamond
Copy link
Collaborator

There is a bug in _aggregate_metrics. It's not recursive. It should be

    @pu.compute_once
    def _aggregate_metrics(self) -> dict:
        """
        Aggregates and caches metrics for expression trees in self.args. So far
        metrics include the maximum dimensionality ('max_ndim') and whether
        all sub-expressions support C++ ('all_support_cpp').

        """
        max_ndim = self.ndim
        cpp_support = self._supports_cpp()

        for arg in self.args:
            max_ndim = max(max_ndim, arg._max_ndim())
            cpp_support = cpp_support and arg._all_support_cpp()

        metrics = {
            "max_ndim": max_ndim,
            "all_support_cpp": cpp_support
        }
        return metrics

@Transurgeon Transurgeon merged commit ba14f1a into cvxpy:master Mar 9, 2025
51 of 52 checks passed
Transurgeon added a commit that referenced this pull request Mar 9, 2025
* adding some initial ideas to fix broadcasting

* reverting changes and adding new tests/broadcast atoms

* add more boilerplate for broadcasting operator

* updating methods for the broadcast_to atom

* some improvements

* add more tests for broadcasting, raise exception

* update shape from args and validate args

* fixing some tests, but still issues with CPP backend not implemented

* adding test from eric's code snippet

* fixing all tests and adding new ones for multiply broadcast

* revert these changes and remove failing case as separate test

* reverting changes to typing

* some updates and progress towards a truly working backend

* truly reverting np.typing issue now

* fixing implementation for broadcast_to atom, and cleaning up tests

* final changes for broadcasting to be fully working in nd

* adding hypothesis tests and renaming some other stuff

* fix bug

* maybe fixed all the tests?

* remove breakpoint

* final touches, changing test name and case in expr.broadcast

* revert changes to expr.broadcast

---------

Co-authored-by: William Zijie Zhang <[email protected]>
Co-authored-by: Steven Diamond <[email protected]>
Transurgeon added a commit that referenced this pull request Mar 9, 2025
* patch empty breaking empty rownames for xpress>=9.5 (#2745)

Co-authored-by: Marc Bataillou Almagro <[email protected]>

* Adds new broadcast_to atom for nd broadcasting (#2724)

* adding some initial ideas to fix broadcasting

* reverting changes and adding new tests/broadcast atoms

* add more boilerplate for broadcasting operator

* updating methods for the broadcast_to atom

* some improvements

* add more tests for broadcasting, raise exception

* update shape from args and validate args

* fixing some tests, but still issues with CPP backend not implemented

* adding test from eric's code snippet

* fixing all tests and adding new ones for multiply broadcast

* revert these changes and remove failing case as separate test

* reverting changes to typing

* some updates and progress towards a truly working backend

* truly reverting np.typing issue now

* fixing implementation for broadcast_to atom, and cleaning up tests

* final changes for broadcasting to be fully working in nd

* adding hypothesis tests and renaming some other stuff

* fix bug

* maybe fixed all the tests?

* remove breakpoint

* final touches, changing test name and case in expr.broadcast

* revert changes to expr.broadcast

---------

Co-authored-by: William Zijie Zhang <[email protected]>
Co-authored-by: Steven Diamond <[email protected]>

* change is_released to true

---------

Co-authored-by: Marc Bataillou Almagro <[email protected]>
Co-authored-by: Marc Bataillou Almagro <[email protected]>
Co-authored-by: William Zijie Zhang <[email protected]>
Co-authored-by: Steven Diamond <[email protected]>
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.

3 participants