Skip to content
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

MAINT: rename TensorGrid->RectGrid and remove RegularGrid #841

Merged
merged 5 commits into from
Jan 22, 2017

Conversation

kohr-h
Copy link
Member

@kohr-h kohr-h commented Jan 20, 2017

Closes #670.

Also some maintenance changes regarding the ideg and inondeg properties, now replaced by a single nondegen_byaxis property that returns a full boolean array.

[5.0],
[2.0],
min_pt=[-1.0, 1.0, 4.0, 2.0], max_pt=[-0.5, 3.0, 5.0, 3.0]
)
Copy link
Member Author

Choose a reason for hiding this comment

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

This now became a uniform partition and printed out an enormous line, had to remove it.

Copy link
Member

Choose a reason for hiding this comment

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

I dont get it, why did it change and how does it look now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Now by definition, any axis with two cells or less is uniform, which is kind of hard to argue against. The printed return value was roughly

uniform_partition([-1.0, 1.0, 4.0, 2.0], [-1.0, 1.0, 4.0, 2.0], (1, 1, 1, 1), nodes_on_bdry=((True, False), False, (False, True), (True, False))

modulo error in the booleans.

Copy link
Member

Choose a reason for hiding this comment

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

I see why that was removed.

dpart_0 = odl.RectPartition(odl.IntervalProd(0, 0), odl.TensorGrid([0]))
geom_p2d = odl.tomo.Parallel2dGeometry(apart, dpart=dpart_0)
with pytest.raises(ValueError):
odl.tomo.astra_projection_geometry(geom_p2d)
Copy link
Member Author

Choose a reason for hiding this comment

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

This check no longer made sense since the grid was in fact uniform, hence the error condition was gone.

Copy link
Member

Choose a reason for hiding this comment

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

Well, perhaps we should change the grid then?

Copy link
Member Author

Choose a reason for hiding this comment

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

Too lazy :-)
Okay I'll do it. I had to get it out because gates close at 7, didn't want to be locked in.

Copy link
Member

@adler-j adler-j left a comment

Choose a reason for hiding this comment

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

Great change. Minor review and go ahead.

.. math::
G = \{(-1, 0), (-1, 1), (0, 0), (0, 1), (2, 0), (2, 1)\}

Here is a graphical representation::
Copy link
Member

Choose a reason for hiding this comment

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

Does this typeset well?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it looks decent. It's treated as code, so it uses a monospace font.

Copy link
Member

Choose a reason for hiding this comment

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

Nice then!

if not self.is_uniform:
raise NotImplementedError('`stride` not defined for non-uniform '
'grid')
strd = np.zeros(self.ndim)
Copy link
Member

Choose a reason for hiding this comment

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

might as well write out stride instead of strd

Copy link
Member Author

Choose a reason for hiding this comment

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

Hesitant to shadow the function name, feels wrong.

Copy link
Member

Choose a reason for hiding this comment

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

The function name isn't shadowed by it since it wasn't available in the first place? The only way to access it is via self.stride

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, "shadow" was the wrong term, "simultaneously highlighted by the editor" would be more precise.

Copy link
Member

Choose a reason for hiding this comment

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

I guess editor confusion is also a reason, stay with this then.

@property
def stride(self):
"""Step per axis between neighboring points of a uniform grid.

Copy link
Member

Choose a reason for hiding this comment

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

raises ...

True
"""
# Optimization for some common cases
if other is self:
return True
if not (isinstance(other, TensorGrid) and
if not (isinstance(other, RectGrid) and
Copy link
Member

Choose a reason for hiding this comment

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

Why is this if statement still there?

Copy link
Member Author

Choose a reason for hiding this comment

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

To return False for wrong type instead of raising an AttributeError?

Copy link
Member

Choose a reason for hiding this comment

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

well any non rect grid should be false, I was thinking about the following lines but perhaps I missed the logic (hard to read with changelogs)

Copy link
Member Author

Choose a reason for hiding this comment

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

It would probably be easier to read as

if not isinstance(other, RectGrid) or
not ...

It's easy to mismatch parens I guess.

Copy link
Member

Choose a reason for hiding this comment

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

Well

if not isinstance(other, RectGrid):
    return False

# other logic here

is our standard pattern.

# vec_s. If there is no almost zero entry in each row,
# return False.
vec_o_mg, vec_s_mg = sparse_meshgrid(vec_o, vec_s)
if not np.all(np.any(np.abs(vec_s_mg - vec_o_mg) <= atol,
Copy link
Member

Choose a reason for hiding this comment

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

np allclose?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh well yeah, that was some old code I just copied.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, allclose is not suitable since the two vectors are broadcast and form a matrix (like a distance matrix). Then it is checked that in each column there is at least one entry close enough to zero.
AFAICS the only thing I can do is replace the boolean abs expression with np.isclose.


def insert(self, index, other):
"""Insert another regular grid before the given index.
def uniform_grid(min_pt, max_pt, shape):
Copy link
Member

Choose a reason for hiding this comment

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

Good addition.

return '{}({})'.format(self.__class__.__name__, inner_str)
coord_vecs = [np.linspace(mi, ma, num, endpoint=True, dtype=np.float64)
for mi, ma, num in zip(min_pt, max_pt, shape)]
return RectGrid(*coord_vecs)


def uniform_sampling_fromintv(intv_prod, shape, nodes_on_bdry=True):
Copy link
Member

Choose a reason for hiding this comment

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

uniform_grid_fromintv now I guess

Copy link
Member Author

Choose a reason for hiding this comment

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

Better name eh? I'll change it.

@@ -1264,7 +1168,7 @@ def uniform_sampling_fromintv(intv_prod, shape, nodes_on_bdry=True):
gmin.append(xmin + (xmax - xmin) / (2 * n))
gmax.append(xmax - (xmax - xmin) / (2 * n))

return RegularGrid(gmin, gmax, shape)
return uniform_grid(gmin, gmax, shape)


def uniform_sampling(min_pt, max_pt, shape, nodes_on_bdry=True):
Copy link
Member

Choose a reason for hiding this comment

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

How does this compare to uniform_grid?

Copy link
Member Author

Choose a reason for hiding this comment

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

This uses the interval product min/max, while the other uses the grid min/max directly. Subtle difference.

Copy link
Member

Choose a reason for hiding this comment

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

Oh god you're right, but with nodes_on_bdry=True they are equal, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yes, of course. So perhaps one of them can go. I'll look into it.

[5.0],
[2.0],
min_pt=[-1.0, 1.0, 4.0, 2.0], max_pt=[-0.5, 3.0, 5.0, 3.0]
)
Copy link
Member

Choose a reason for hiding this comment

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

I dont get it, why did it change and how does it look now?

dpart_0 = odl.RectPartition(odl.IntervalProd(0, 0), odl.TensorGrid([0]))
geom_p2d = odl.tomo.Parallel2dGeometry(apart, dpart=dpart_0)
with pytest.raises(ValueError):
odl.tomo.astra_projection_geometry(geom_p2d)
Copy link
Member

Choose a reason for hiding this comment

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

Well, perhaps we should change the grid then?

@kohr-h
Copy link
Member Author

kohr-h commented Jan 21, 2017

@adler-j you may want to look at 670864e and check if that's okay. I find it nice because it's now the package that solely defines the version.

@kohr-h kohr-h force-pushed the issue-670__rect_grid branch 2 times, most recently from 5ca2195 to 61a29db Compare January 21, 2017 19:58
Copy link
Member

@adler-j adler-j left a comment

Choose a reason for hiding this comment

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

Minor comments, go ahead after.

"""

from __future__ import absolute_import

__version__ = '0.5.4.dev0'
__all__ = ('diagnostics', 'discr', 'operator', 'set', 'space', 'solvers',
'tomo', 'trafos', 'util', 'phantom', 'deform', 'ufunc_ops')
__all__ = ()
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure all of them should be removed? How about stuff like util that is not default imported?

Copy link
Member Author

Choose a reason for hiding this comment

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

As I understand it, the imports further down put all the subpackages in the module dictionary, so that's not the purpose of adding them to __all__ anyway. The __all__ here is only effective if someone does from odl import *, and then I guess we don't want the subpackages themselves to be included, only their __all__ contents. Or do we?

Copy link
Member

Choose a reason for hiding this comment

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

Well if someone does from odl import all I guess they should get something called util? Otherwise they cant access everything. For subpackages that are included via import *, we don't need to import them specifically.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, right. But that basically means that everything has to stay, since the same applies to the other subpackages. Currently you get everything from the subpackages' __all__ but the other stuff is not accessible, at least not directly.

That also means that we have to be careful not to include any module or package into __all__ that conflicts with another name somewhere in the library, or at least make sure that they're not imported into the same namespace.

the product of these lengths. This class makes use of that
sparse storage scheme.

See ``Notes`` for details.
Copy link
Member

Choose a reason for hiding this comment

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

Where did notes go?

Copy link
Member

Choose a reason for hiding this comment

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

Edit: Into Init, i'd keep it at class level.

Copy link
Member Author

Choose a reason for hiding this comment

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

But then in IDEs you get class docstring -> Notes -> Parameters, which IMO is not the best possible order. As I've argued earlier, and that's of course debatable, I think parameters should come up as quickly as possible, with all the extra stuff afterwards. That's why I moved the notes into __init__ after parameters and examples.

Copy link
Member

Choose a reason for hiding this comment

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

Still not convinced that we should change the logical structure of the doc in order to please a specific IDE typesetting, other IDEs may do this "properly" (i.e. distinguish between a class and its initializer in a better way than spyder). Very few will read this anyway.

Copy link
Member

Choose a reason for hiding this comment

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

To continue on the above, this is mostly dev doc and for developers those notes are very important, more so than the init doc.

Copy link
Member Author

Choose a reason for hiding this comment

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

That may be true, but how often is that important compared to the parameter explanations?

The math notes are important to understand what this represents, and that's usually when you read the doc for the first time. After that, you perhaps occasionally check the math but not often.

On the other hand, the parameters are important on a day-to-day basis when you use the function or class in practice. Of course the signature is printed first (usually, sometimes it doesn't work but that's nothing to count in), but when there are kwargs or if you need details about the parameters you need that section. And then at least I find it annoying if there are too many other things in the way that make me scroll every time I need info about the arguments.

So even from a dev perspective I feel that in the long run, parameters are more important than other details in the notes.

other IDEs may do this "properly" (i.e. distinguish between a class and its initializer in a better way than spyder)

Not sure what a better way would look like, or how other IDEs handle this. To me it seems reasonable to show the class docstring and the initialization doc if a user asks for documentation on a class.
The alternative would be to let the user choose, with pop-up menus like (apparently) in PyCharm. I personally find that annoying.

Copy link
Member Author

Choose a reason for hiding this comment

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

Made a new issue #854.

# Uniformity, setting True in degenerate axes
diffs = [np.diff(v) for v in self.coord_vectors]
self.__is_uniform_byaxis = tuple(
True if diff.size == 0 else np.allclose(diff, diff[0])
Copy link
Member

Choose a reason for hiding this comment

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

diff.size == 0 or np.allclose(diff, diff[0])

>>> rg = uniform_grid([-1.5, -1], [-0.5, 3], (2, 3))
>>> rg.stride
array([ 1., 2.])

Copy link
Member

Choose a reason for hiding this comment

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

remove blankline

np.all(self.max_pt <= other.max_pt + atol)):
if not isinstance(other, RectGrid):
return False
if not(np.all(self.shape <= other.shape) and
Copy link
Member

Choose a reason for hiding this comment

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

little reason to use np.all for length ~3 arrays, more readable with simply all (and probably faster for this small case)

Copy link
Member Author

Choose a reason for hiding this comment

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

It's also wrong since the statement in parenthesis is already a boolean (compares tuples).

IntervalProd.squeeze
"""
newset = self.set[self.grid.inondeg]
nondegen_indcs = np.where(self.grid.nondegen_byaxis)[0]
Copy link
Member

Choose a reason for hiding this comment

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

np.flatnonzero to skip the [0]

@@ -102,7 +101,7 @@ def ndim(self):
@property
def true_ndim(self):
"""Number of non-degenerate (positive-length) intervals."""
return len(self.inondeg)
return sum(self.nondegen_byaxis.astype(int))
Copy link
Member

Choose a reason for hiding this comment

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

return np.count_nonzero(self.nondegen_byaxis)

@@ -431,7 +432,7 @@ def measure(self, ndim=None):
elif ndim > self.true_ndim:
return 0.0
else:
return np.prod((self.max_pt - self.min_pt)[self.inondeg])
return np.prod(self.extent()[self.nondegen_byaxis])
Copy link
Member

Choose a reason for hiding this comment

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

Remind me, why is extent not a property?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably because the thought was that for some grid or domain structures in the future, this might be costly to compute. If we're sure that it's always gonna be cheap, we can make it a property (but then what to do with min and max which are methods, too?).

Copy link
Member

Choose a reason for hiding this comment

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

min and max are methods in order to conform to the numpy interface, which allows these to be used with numpy in a duck-typing style. Otherwise we provide min_pt etc which do the same.

I'll propose a change.

Copy link
Member

Choose a reason for hiding this comment

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

Issue: #847

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.

2 participants