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

Add magic functions #19

Merged
merged 12 commits into from
May 14, 2024
Merged

Add magic functions #19

merged 12 commits into from
May 14, 2024

Conversation

Teschl
Copy link
Contributor

@Teschl Teschl commented May 1, 2024

In this version, I implemented the previously discussed "magic" functions. Turns out the r and i versions (as noted in #17 ) are not needed, but instead of __cmp__ all the functions for comparing had to be implemented on their own.

I am using a copy.deepcopy() when returning the new dem (after computing and, for example) since only doing a shallow copy resulted in overwriting dem1 when doing this, for example: dem3 = dem1 & dem2

  • for __len__ we need to decide, if we want to return the number of elements in the first dimension (like it works for the np.array). or i we want to return the shape of the dem (rows, cols).
  • for __delitem__ I am questioning if we need it at all. I can't think of a situation, where the user would want to delete a specific row of their dem.

I included the notebook examples/example_magic_funcs.ipynb which provides simple examples that showcase functionality. Feel free to play around with those a bit.

@Teschl
Copy link
Contributor Author

Teschl commented May 2, 2024

I should add, right now dem1 == dem2 returns a GridObject with element wise results instead of returning just True or False. This is probably very unexpected, so it might be smarter to implement a eq(dem1, dem2) function that returns another dem

@wschwanghart
Copy link

This is an element-wise operation in Matlab's TT, too. And I think this is fine. One could just implement an isequal-function to compare objects. This would require checking the equality of the array of elevations, coordinates, etc.

Copy link
Contributor

@wkearn wkearn left a comment

Choose a reason for hiding this comment

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

This all seems to work. I left a few questions and very minor changes in comments on specific lines.

Is there a better way to make GridObject behave like a NumPy ndarray with some extra metadata -- the cellsize -- that follows it around? I have looked at Subclassing ndarray. I haven't completely understood it yet, but it seems like it might be better in the long run to get something like that working than to have to implement our own wrappers around whatever NumPy functionality we need.

examples/example_magic_funcs.ipynb Outdated Show resolved Hide resolved
Comment on lines +6 to +20
def __eq__(self, other):
dem = copy.deepcopy(self)

if not isinstance(other, self.__class__):
raise TypeError("Can only compare two GridObjects.")

if self.columns != other.columns or self.rows != other.rows:
raise ValueError("Both GridObjects have to be the same size.")

for x in range(0, self.columns):
for y in range(0, self.rows):

dem.z[x][y] = self.z[x][y] == other.z[x][y]

return dem
Copy link
Contributor

Choose a reason for hiding this comment

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

Noting for myself: You need the deepcopy here because a shallow copy copies the pointer to self.z into dem.z, and then when you write to it in the loop, the pointers are the same and they get overwritten. This doesn't happen with the functions for add, etc., because we use the builtin numpy addition of the two arrays, which allocates a new destination array and assigns a pointer to that new array into dem.z.

Why do we not do something like:

dem.z = self.z == other.z

or something similar for the other boolean operations?

src/topotoolbox/gridmixins/magic.py Outdated Show resolved Hide resolved
src/topotoolbox/gridmixins/magic.py Outdated Show resolved Hide resolved
@Teschl
Copy link
Contributor Author

Teschl commented May 3, 2024

I played around with subclassing a bit, it generally seems to work for our use case. I will create a new branch in my fork to try to implement it. Then we can decide if we want to go this route and merge it.

We will have to see if we run into any issues when overwriting functions such as __add__. Also, inheriting functions like .astype() from np.ndarray may be dangerous (could lead users to changing data types and then running into issues). I'll check if we can limit how much is inherited from ndarray.

@wkearn wkearn added the enhancement New feature or request label May 7, 2024
@Teschl
Copy link
Contributor Author

Teschl commented May 13, 2024

I think I would be against subclassing ndarray.

Since

def __array__(self):
        return self.z

ensures that you don't need to use dem.z when passing an instance to Matplotlib, for example (just dem works). There is no need to subclass for plotting.

In my opinion the issue with subclassing is, that we also inherit functions like: astype, sort, byteswap, dump or tofile. It's not like these functions should not exist for the GridObject, I think giving the user access to most of them will do more harm than good. If the user really wants to use them, they can still do dem.z.some_function.

I think we should go through the ndarray functions and choose which we want to keep. Writing a wrapper for each will probably be no big task.

Otherwise, I fear we would add a lot of functions that see no use. When overwriting a function in a subclass, we could remove the functionality, but not the existence of the function. So for example in an IDE the autocomplete would have many "empty" functions, which could prove annoying.

If you think we should still go the subclass route, I'm open to it. But as of right now, I don't see the immediate advantage.

@wschwanghart
Copy link

wschwanghart commented May 13, 2024 via email

@wkearn
Copy link
Contributor

wkearn commented May 14, 2024

I think that makes sense for now. @Teschl: Is there anything left to do here or should I go ahead and merge this PR?

@Teschl
Copy link
Contributor Author

Teschl commented May 14, 2024

I added the str magic function so print(dem) is possible. And changed dem.zto demwhen plotting in the example. You should be able to merge it now.

@wkearn wkearn merged commit 10ecc10 into TopoToolbox:main May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants