odl icon indicating copy to clipboard operation
odl copied to clipboard

What semantics should comparison operators have on ODL 1.0 space-elements?

Open leftaroundabout opened this issue 4 months ago • 4 comments

In current ODL, == is always a proper equality operator, i.e. it returns a boolean saying whether the left and right operands are equal.

Although I would say this is generally the correct thing to do for such operators, it cannot be denied that it is actually fairly useless in practice (two given floating-point arrays have a very low chance of being equal), does not generalize to inequalities <, >= etc., and contradicts what most array libraries and also the Python Array API prescribe.

Namely, to perform a point-wise comparison and give back an array saying for each element whether the elements at the same index agree in both of the input arrays.

@ozanoktem @Emvlt @janden we discussed this topic already, but the only clear conclusion was that ODL needs to be capable of representing such pointwise-boolean values. We should come to a consensus for how the comparison operations shall behave going forward. In old ODL it is at least consistent; in the 1.0 candidate it is currently a mix of several different approaches, which cannot stay like that. Below I list the possible options for what types the operations could be, and the tradeoffs; please vote and/or comment.

leftaroundabout avatar Aug 22 '25 12:08 leftaroundabout

Status quo: plain booleans

Summarized by the signature (I'm treating ODL spaces as if they were types here)

space0: some odl.TensorSpace
space1: some odl.TensorSpace
def __eq__(x: space0, y: space1) -> bool

This is very clear and easy. It is a total function (different-space inputs simply give False, no need to raise exceptions) and one can rely on that the result is always a proper Python boolean, which is particularly convenient for testing purposes.

Disadvantages as mentioned above: inconsistent with NumPy, PyTorch and Array API (however, I would argue ODL is not an array library), and quite useless in the real world. Perhaps not useless for education though.

leftaroundabout avatar Aug 22 '25 12:08 leftaroundabout

Boolean spaces

space: some odl.TensorSpace
def __eq__(x: space, y: space) -> space.astype(bool)

This is in principle the cleanest solution that is consistent with the Array API. The main problem is that boolean spaces are somewhat kludgy in ODL - most of the machinery assumes a vector space structure, much of it even continuity. So we would need to fix some edge cases to make this work, and even then you could not use it in every way one can use Matlab-style comparisons on plain arrays (which don't have a problem with boolean entries).

There is also the matter of how situations where x and y live in different spaces should be handled:

  • Always raise a type error? (I think safest, but can be annoying for users)
  • Try to convert to common dtype?
  • Allow different weightings, discretization etc.?

leftaroundabout avatar Aug 22 '25 12:08 leftaroundabout

Boolean arrays

space0: some odl.TensorSpace
space1: some odl.TensorSpace
array: plain array type underlying both spaces
def __eq__(x: space0, y: space1) -> array[bool]

This is how e.g. isclose in the WIP version behaves. It is fairly straightforward to do, as we can just delegate the actual comparison logic to the backend, after unwrapping the data of both elements. It could be made usable with indexing operators, at least for some backends.

A big downside is that the user gets thrown out of the mathematical Python/ODL setting and presented with an entirely different kind of object, and what particular type will depend on the choice of backend.

leftaroundabout avatar Aug 22 '25 12:08 leftaroundabout

Thanks for this detailed breakdown!

I am in favour of the "Boolean Array" behaviour, as it can always be helpful to inspect element of the output array and behaves like the computational backends expect.

Emvlt avatar Aug 28 '25 12:08 Emvlt