-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: Tensor-valued DiscreteLp #1238
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments. Much needed functionality that should go in soon if possible! Looking great to me
odl/discr/lp_discr.py
Outdated
|
||
x[indices] in space[indices] | ||
|
||
Space indexing does not work with index arrays, boolean arrays and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not index arrays?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Index arrays produce flat arrays when used with Numpy arrays, and I don't know the right space for such a flat array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works in some cases anyway:
arr =np.ones([5, 5])
arr[[1, 2], :]
Out[3]:
array([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.]])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works here, too. What I mean by index arrays (seems to need clarification) is
>>> arr[[0, 1, 4], [3, 0, 0]]
array([ 1., 1., 1.])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know what the name of that one either :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about it, that should actually (however weird it feels) be a flat TensorSpace
with the correct dtype. I.e. we should exactly mirror the numpy behaviour.
Edit: This is not a requirement for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it should actually be an easy change. Decent suggestion anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only headache with this is that DiscreteLp
does not necessarily know how to construct a TensorSpace
. We can, of course, assume that shape
and dtype
should always be enough, but in the case of CuPy spaces (see #1231) we would also match the device, or for pyTorch spaces (#1229) usage of pinned versus virtual memory should be the same.
So I don't think threre's a better way to do it than to drop down to the tensors and let them handle the space wrapping.
odl/discr/lp_discr.py
Outdated
self.tensor[indices] = values.tensor | ||
if isinstance(indices, type(self)): | ||
indices = indices.tensor.data | ||
res_tens = self.tensor.__getitem__(indices) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why getitem instead of actual indexing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well yeah 🙄
odl/discr/lp_discr.py
Outdated
try: | ||
res_space = self.space[indices] | ||
except (ValueError, TypeError): | ||
# TODO: adapt error type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be quite careful here, i guess this is OK for now, but we need to document it (similar to numpy arrays returning flat arrays in some cases, which is confusing). We also expect stuff like
vec[complicated_indices] = 2 * vec[complicated_indices]
to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That code would call __setitem__
anyway and not be affected by this. But yeah, catching to too many exceptions is a bit dangerous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any tests that verifies that the above code actually works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are unit tests with "complicated" indices for __setitem__
, but I'll check again.
odl/space/base_tensors.py
Outdated
rn(2) | ||
""" | ||
new_shape, _, _ = simulate_slicing(self.shape, indices) | ||
return type(self)(shape=new_shape, dtype=self.dtype) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure we want a default implementation here, this could be very implementation specific (see e.g. weighting)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, I'll remove the default implementation.
odl/space/base_tensors.py
Outdated
@@ -496,6 +568,11 @@ def available_dtypes(): | |||
""" | |||
raise NotImplementedError('abstract method') | |||
|
|||
@property | |||
def array_type(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, I used it in DiscreteLp.__getitem__
, but that can be done in a different way. Removed.
odl/space/npy_tensors.py
Outdated
@@ -2159,6 +2190,73 @@ def norm(self, x): | |||
return float(_pnorm_diagweight(x, self.exponent, self.array)) | |||
|
|||
|
|||
class PerAxisWeighting(Weighting): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave out partial implementation from this PR if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I do it here already, but no, I'll make a separate PR.
odl/test/discr/lp_discr_test.py
Outdated
assert discr.axis_labels == () | ||
assert discr.tangent_bundle == odl.ProductSpace(field=odl.RealNumbers()) | ||
assert discr.complex_space == odl.uniform_discr([], [], (), dtype=complex) | ||
hash(discr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well check repeatability assert hash(discr) == hash(discr)
odl/util/numerics.py
Outdated
>>> # arr[[2, 0], [3, 3], [0, 1], [5, 2]] | ||
>>> simulate_slicing(shape, ([2, 0], [3, 3], [0, 1], [5, 2])) | ||
((2,), (1, 2, 3), 0) | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Show usage with np.s_
which makes stuff like np.s_[..., 2]
much easier to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I'd done this ...
7af758f
to
6e1c24e
Compare
Checking updated PR... No PEP8 issues. Comment last updated on July 02, 2018 at 23:13 Hours UTC |
53d02c4
to
bb31079
Compare
Bump |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review. More to come.
odl/discr/lp_discr.py
Outdated
For spaces of discretized vector- or tensor-valued functions, | ||
this includes the output components as :: | ||
|
||
shape = fspace.out_shape + partition.shape |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make this an Examples
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
odl/discr/lp_discr.py
Outdated
indices = normalized_index_expression(indices, self.shape) | ||
# Avoid array comparison with `==` if `indices.contains()` is used | ||
if any(idx is None for idx in indices): | ||
raise ValueError('creating new axes is not supported.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this should be a feature for the future? Perhaps we could create something like min_pt=[None, 0, 0], max_pt=[None, 1, 1]
for this case and call it a degenerate axis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about having "invalid" entries in min_pt
and max_pt
. As an alternative, we could have a method newaxis
that takes an index (or a bunch of indices) and min and max.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the TODOs regarding this. IMO we can allow adding axes to the output
part of the dimensions, i.e. users can do
>>> space = odl.uniform_discr(0, 1, 10)
>>> space[None, ...]
uniform_discr(0.0, 1.0, 10, dtype=(float, (1,)))
or
>>> space = odl.uniform_discr(0, 1, 10, dtype=(float, (2, 3)))
>>> space[None, ...]
uniform_discr(0.0, 1.0, 10, dtype=(float, (1, 2, 3)))
>>> space[:, None, None, ...]
uniform_discr(0.0, 1.0, 10, dtype=(float, (2, 1, 1, 3)))
For new axes in the "in" part of the dimensions, I'd prefer an explicit methods that takes min and max in the new axis (axes), as I wrote above.
odl/discr/lp_discr.py
Outdated
if any(idx is None for idx in indices): | ||
raise ValueError('creating new axes is not supported.') | ||
|
||
indices_out = indices[:len(self.shape_out)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ndim_out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course
odl/discr/lp_discr.py
Outdated
if values in self.space: | ||
self.tensor[indices] = values.tensor | ||
if isinstance(indices, type(self)): | ||
indices = indices.tensor.data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we're truly starting to assume quite a bit about the backend. Is there no way to push this behavior down into the Tensor
backend?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll see what I can do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion here is to make data
abstract in Tensor
and therefore safe to rely on.
odl/discr/lp_discr.py
Outdated
try: | ||
iter(indices) | ||
except TypeError: | ||
if indices is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be done before the try?
odl/discr/lp_discr.py
Outdated
try: | ||
res_space = self.space[indices] | ||
except (ValueError, TypeError): | ||
# TODO: adapt error type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any tests that verifies that the above code actually works?
odl/discr/lp_discr.py
Outdated
weighting=weighting) | ||
if (weighting is None and | ||
is_numeric_dtype(dtype) and | ||
exponent != float('inf')): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about -inf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that should be covered as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some stuff left to do, but looks great!
odl/space/npy_tensors.py
Outdated
|
||
x[indices] in space[indices] | ||
|
||
is ``True``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps too "legal" of a formulation, why not
For all supported cases, indexing is implemented such that for an element ``x in space`` ::
x[indices] in space[indices]
odl/space/npy_tensors.py
Outdated
# broadcast, i.e. between scalar and full-blown in dimensionality? | ||
|
||
def slice_weighting(weighting, space_shape, indices): | ||
"""Return a weighting for a space after indexing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be implemented via indexing of the weighting? It seems this style is not scalable nor extendable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had this in mind as well. That would mean putting __getitem__
and byaxis
on the weighting classes, and to raise an exception where it doesn't make sense. I'd be okay with that, it would also be more extensible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel it should be done that way, much more future proof
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code again regarding this one, I'm having doubts about __getitem__
. The issue is that in quite a few cases, the result after slicing is not the same class as before. That would seem okay for such simple things like (array of 1 element -> scalar) transformations, but here we make much bigger changes, like ArrayWeighting
-> None
. This would feel wrong in a __getitem__
.
odl/space/npy_tensors.py
Outdated
conjugate and ":math:`\odot`" for pointwise product. | ||
|
||
- For other exponents, only norm and dist are defined. In the | ||
case of exponent :math:`\\infty`, the weighted norm is defined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we allow -inf?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add that, it should behave the same.
odl/space/npy_tensors.py
Outdated
.. math:: | ||
\| a \|_{v, \\infty} := \| a \|_{\\infty}, | ||
|
||
otherwise it is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change the order of this docstring, e.g. default first then special case
# Copy data to GPU memory | ||
# Get adjoint weighting functions, using an extra correction | ||
# factor accounting for inconsistencies in certain ASTRA versions | ||
extra_factor = astra_cuda_bp_scaling_factor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer calling it "astra_correction" or similar
def simulate_slicing(shape, indices): | ||
"""Simulate slicing into a Numpy array with given indices. | ||
|
||
This function is intended to simulate indexing of a Numpy array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "simulate" mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like "pretending to do it, but really only looking at the resulting shape and stuff".
odl/util/numerics.py
Outdated
>>> # arr[[2, 0], [3, 3], [0, 1], [5, 2]] | ||
>>> simulate_slicing(shape, ([2, 0], [3, 3], [0, 1], [5, 2])) | ||
((2,), (1, 2, 3), 0) | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump on this
a3be5df
to
f3d8880
Compare
So, this would be a lovely addition, whats the status? |
I guess I'm also gonna have my rebasing adventure... I'll try to bring it a bit up to speed this weekend. |
a3a03f7
to
90671c1
Compare
We're back on track here again. Rebased and loads of issues fixed. Major points are the weighting propagation and the tests yet to be written as mentioned in the TODOs. I also kept the |
Here's a question: If we call
So for |
Excellent question. To me,
But these are all good:
|
Thanks for the answer. I think that's the most reasonable suggestions, too. In Numpy, you can't do So I'll go with what you write above. I was already about to implement that, but realized that removing that capability would break |
Changes in detail: - Add dtype with shape to DiscreteLp (mostly __repr__, factory functions and some downstream methods). As a consequence, `shape_[in,out]` and `ndim_[in,out]` are added for the different types of axes, as well as `scalar_dtype`. - Add `PerAxisWeighting` and make it the default for `DiscreteLp` type spaces. Reason: this way the `tspace` knows how to deal with removed axes etc. This is important for a smooth experience with indexing and reductions over axes. Helpers for slicing weightings help structure this task. - Implement `__getitem__` for `TensorSpace` and `DiscreteLp`, including (hopefully) reasonable propagation of weights. The new `simulate_slicing` utility function simplifies this task. - Allow indexing with ODL tensors of boolean or integer dtype. - Implement correct weighting for backprojections with non-uniform angles, using per-axis weighting and a new helper `adjoint_weightings` to apply the weightings in an efficient way. The correct weighting from the range of `RayTransform` is determined by the new `proj_space_weighting` helper. - Change the space `_*_impl` methods to always expect and return Numpy arrays, and adapt the calling code. - Change behavior of `norm` and `dist` to ignoring weights for `exponent=inf`, in accordance with math. - Improve speed of `all_equal` for comparison of arrays. - Account for `None` entries in indices in the `normalized_index_expression` helper, thus allowing creation of new axes. - Remove `dicsr_sequence_space`, it was largely unused and just a maintenance burden. Use a regular `uniform-discr` from zero to `shape` instead. - Remove `Weighting.equiv()` mehtods, never used and hard to maintain (n^2 possibilities). - Remove the (largely useless) `_weighting` helper to create weighting instances since it would have been ambiguous with sequences of scalars (array or per axis?). Also remove the `npy_weighted_*` functions, they were useless, too. - Remove some dead code from tomo/util. - A bunch of minor fixes, as usual. Closes: odlgroup#908 Closes: odlgroup#907 Closes: odlgroup#1113 Closes: odlgroup#965 Closes: odlgroup#286 Closes: odlgroup#267 Closes: odlgroup#1001
- Fix issue with labels for new axes - Add shape and ndim properties to DiscreteLpElement - Add examples to a couple of new methods - Fix typos
07443c0
to
0a93baf
Compare
8b99801
to
b9d03b0
Compare
TODOs:
Rebase on
master
and resolve conflictsSettle on a reasonable behavior of weights when removing/adding/slicing axes. Currently we have this (I need to check that this is correct):
ConstWeighting
PerAxisWeighting
ArrayWeighting
(1) only applies if no axes are added/removed.
Add
byaxis_out
inDiscreteLp
Add support for
dtype
with shape inTensorSpace.astype
Use
Weighting.__getitem__
to slice, rather than an external function.Having my doubts, see below.Maybe I'll do this after all.Add support for
None
(new axis) in various__getitem__
methods.TensorSpace
and its elements: no restrictionWeighting
: array -> fallback toNone
, others should work w/o problemsDiscreteLp
and its elements: new axes only in the "output" part of the dimensions, if left of everything they turn e.g. scalar functions into(1, ..., 1)
tensor-valued ones.Add methods
newaxis
anddropaxis
for fine-grained control w.r.t. weighting and domain min/maxChange behavior of
astype
with shaped dtype as discussedMigrate to default
PerAxisWeighting
everywhereFix large-scale tests
Decide
'threshold'
vs.'edgeitems'
for printing, or make a separate issue.Rename
astra_cuda_bp_scaling_factor
tocorrection_factor
or soClarify meaning of "simulate" in
simulate_slicing
More tests, in particular for the last-minute added functionality
__getitem__
and__setitem__
with more complex inputs than in the doctestsreduce
andouter
in ufuncs)ProductSpace.astype