_MeshIndexSet first pass#7149
Conversation
|
pre-commit.ci autofix |
|
@stephenworsley please can you review the approach I have used with this code, accepting that various |
| raise NotImplementedError() | ||
|
|
||
| def is_view_of(self, other: MeshXY) -> bool: | ||
| """Whether this instance is either itself, or a view of the given :class:`MeshXY`. |
There was a problem hiding this comment.
Have you considered how this ought to behave when you've sliced or indexed a _MeshIndexSet? If I'm interpreting the code correctly, it looks like you ought to end up with another smaller _MeshIndexSet. In such a case, I could imagine you might want this method to also return True.
I also wonder if it might be worth considering two different methods, one for comparing a _MeshIndexSet to a MeshXY, another for comparing two _MeshIndexSets. I haven't entirely thought through which contexts you might expect to call these methods so I've not come to a conclusion on this myself yet.
There was a problem hiding this comment.
This is not intended to be possible, and I've taken some steps to avoid it happening:
iris/lib/iris/mesh/components.py
Lines 3619 to 3630 in ec132b0
| result = [self.to_MeshCoord(location=location, axis=ax) for ax in self.AXES] | ||
| return tuple(result) | ||
|
|
||
| def is_view_of(self, other: "MeshXY") -> bool: |
There was a problem hiding this comment.
Offline conversation with @stephenworsley: is_view_of is not necessary, and any value it might offer is offset by potential confusion. We should remove it.
| result_dict = {k: v for k, v in self._members.items() if id(v) in result_ids} | ||
| return result_dict | ||
|
|
||
| def index( |
There was a problem hiding this comment.
Offline conversation with @stephenworsley: this concept of mutability only really extends to altering the membership of the Manager, there is no decent way 1 to protect the coordinates/connectivities themselves from modification, since users are free to assign those to their own variables, and/or to modify the arrays within them.
If we're comfortable with this 'insecurity', then we could consider generating new constituent coordinates/connectivities that explicitly share the same NumPy array as the ones on the original Mesh (as opposed to directly using the slicing API, since that explicitly creates a copy). That would allow the MeshIndexSet to be a true view, with live updating, potentially avoiding the weirdness of re-indexing every time a Manager is requested.
Footnotes
There was a problem hiding this comment.
Didn't turn out as expected - indexing arrays using another array of integers NEVER returns a view - but I've got something working.
|
EDIT: fixed by ab688cf |
| def cube_dims(self, cube): | ||
| raise NotImplementedError() | ||
|
|
||
| def as_mesh(self) -> MeshXY: |
There was a problem hiding this comment.
It's worth considering if we want to realise the data when we call this. This ought to be the most "robust" way of handling this operation. There's maybe a case for keeping this lazy, in order to minimise memory use, but I'm not convinced this is the correct behaviour or even something we can properly implement.
stephenworsley
left a comment
There was a problem hiding this comment.
I've recorded some stuff to follow up later, but this all looks good in principle. A good start for the feature branch, good work!
| if connectivity.location_axis == 1: | ||
| new_values = new_values.T | ||
| if connectivity.start_index == 1: | ||
| new_values = new_values + 1 |
There was a problem hiding this comment.
Is there a reason this isn't just new_values += connectivity.start_index?
|
|
||
| # Map node indices in "values" to their new zero-based positions | ||
| # in "node_indices". | ||
| order = node_indices.argsort() |
There was a problem hiding this comment.
It looks like this code is focused on ***_node_connectivity. I suspect this might fail with something like face_edge_connectivity. It may be worth just throwing an error in such cases for the time being. With that said, I don't think this is a blocker to moving forward with work on this feature branch.
| if connectivity is not None: | ||
| indexing = indices_dict[connectivity.location] | ||
| if indexing is not None: | ||
| new_values = connectivity.indices_by_location( |
There was a problem hiding this comment.
Doesn't need addressing right now, but I think this bit of code could definitely benefit from thinking about making these variable names clearer. In particular, I think this could benefit from being something other than new_values.
| order = node_indices.argsort() | ||
| old_sorted = da.from_array(node_indices[order]) | ||
| new_ids_sorted = da.arange(len(node_indices))[order] | ||
| positions = functools.partial(da.searchsorted, old_sorted) |
There was a problem hiding this comment.
new_ids_sorted and positions should be the same for every loop so we should probably be calculating these outside the for loop. Maybe worth adding a bit of logic to ensure they aren't called unnecessarily.
| array=new_ids_sorted, | ||
| indexing=new_values, | ||
| pre_index=positions, | ||
| ) |
There was a problem hiding this comment.
I think I finally got my head around what this bit of logic does. While there's bits I think can be tidied up, I'm happy with what this does in principle.
No description provided.