-
Notifications
You must be signed in to change notification settings - Fork 284
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
Implement dataless cubes #6253
base: main
Are you sure you want to change the base?
Implement dataless cubes #6253
Changes from 38 commits
96a3f2d
a2e0da9
7114f0f
84ea908
44afb25
cdc51d5
7b402e0
bc1ee6f
17e0b30
16779aa
f42c19d
b4ffc31
6d62c7d
cc13e6d
71c7ae8
e59d5c9
95c09e1
b2fb2f8
c6510a5
af7d727
144e164
ba84553
ea3c150
0cec4ca
6ed270d
d79ad27
2bb6e61
063e45b
79d7d67
9b52067
2f2b56a
c8d2c07
67fb787
379b273
a042451
088bb94
21489d5
a059c5e
19e956d
8178809
2123b8f
703bbb6
33649c1
021bd31
f1bfe7b
abb5a57
94d02ea
5be6a1f
5c5bbf8
de348f9
7c74460
0ceed69
8bddec7
2a67050
f8c2dad
ed75485
9221917
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -10,32 +10,42 @@ | |||||||||||||||||||||||||||||||||||||||||||
import numpy.ma as ma | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data | ||||||||||||||||||||||||||||||||||||||||||||
import iris.exceptions | ||||||||||||||||||||||||||||||||||||||||||||
import iris.warnings | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
class DataManager: | ||||||||||||||||||||||||||||||||||||||||||||
"""Provides a well defined API for management of real or lazy data.""" | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def __init__(self, data): | ||||||||||||||||||||||||||||||||||||||||||||
def __init__(self, data, shape=None): | ||||||||||||||||||||||||||||||||||||||||||||
"""Create a data manager for the specified data. | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
Parameters | ||||||||||||||||||||||||||||||||||||||||||||
---------- | ||||||||||||||||||||||||||||||||||||||||||||
data : | ||||||||||||||||||||||||||||||||||||||||||||
The :class:`~numpy.ndarray` or :class:`~numpy.ma.core.MaskedArray` | ||||||||||||||||||||||||||||||||||||||||||||
real data, or :class:`~dask.array.core.Array` lazy data to be | ||||||||||||||||||||||||||||||||||||||||||||
managed. | ||||||||||||||||||||||||||||||||||||||||||||
managed. If a value of None is given, the data manager will be | ||||||||||||||||||||||||||||||||||||||||||||
considered dataless. | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
shape : | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO We've been adopting the following numpydoc standard for specifying the type of parameters i.e., Same standard applies to the |
||||||||||||||||||||||||||||||||||||||||||||
A tuple, representing the shape of the data manager. This can only | ||||||||||||||||||||||||||||||||||||||||||||
be used in the case of `data=None`, and will render the data manager | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use double-ticks, see here i.e., |
||||||||||||||||||||||||||||||||||||||||||||
dataless. | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
if (shape is not None) and (data is not None): | ||||||||||||||||||||||||||||||||||||||||||||
msg = "`shape` should only be provided if `data is None`" | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
raise ValueError(msg) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Initialise the instance. | ||||||||||||||||||||||||||||||||||||||||||||
self._lazy_array = None | ||||||||||||||||||||||||||||||||||||||||||||
self._real_array = None | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Assign the data payload to be managed. | ||||||||||||||||||||||||||||||||||||||||||||
self._shape = shape | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO The comment on line+45 applies to Could you move |
||||||||||||||||||||||||||||||||||||||||||||
self.data = data | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Enforce the manager contract. | ||||||||||||||||||||||||||||||||||||||||||||
self._assert_axioms() | ||||||||||||||||||||||||||||||||||||||||||||
bjlittle marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def __copy__(self): | ||||||||||||||||||||||||||||||||||||||||||||
"""Forbid :class:`~iris._data_manager.DataManager` instance shallow-copy support.""" | ||||||||||||||||||||||||||||||||||||||||||||
name = type(self).__name__ | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -126,11 +136,16 @@ def __repr__(self): | |||||||||||||||||||||||||||||||||||||||||||
def _assert_axioms(self): | ||||||||||||||||||||||||||||||||||||||||||||
"""Definition of the manager state, that should never be violated.""" | ||||||||||||||||||||||||||||||||||||||||||||
# Ensure there is a valid data state. | ||||||||||||||||||||||||||||||||||||||||||||
is_lazy = self._lazy_array is not None | ||||||||||||||||||||||||||||||||||||||||||||
is_real = self._real_array is not None | ||||||||||||||||||||||||||||||||||||||||||||
emsg = "Unexpected data state, got {}lazy and {}real data." | ||||||||||||||||||||||||||||||||||||||||||||
state = is_lazy ^ is_real | ||||||||||||||||||||||||||||||||||||||||||||
assert state, emsg.format("" if is_lazy else "no ", "" if is_real else "no ") | ||||||||||||||||||||||||||||||||||||||||||||
empty = self._lazy_array is None and self._real_array is None | ||||||||||||||||||||||||||||||||||||||||||||
overfilled = self._lazy_array is not None and self._real_array is not None | ||||||||||||||||||||||||||||||||||||||||||||
if overfilled: | ||||||||||||||||||||||||||||||||||||||||||||
msg = "Unexpected data state, got both lazy and real data." | ||||||||||||||||||||||||||||||||||||||||||||
raise ValueError(msg) | ||||||||||||||||||||||||||||||||||||||||||||
elif ( | ||||||||||||||||||||||||||||||||||||||||||||
empty and self._shape is None | ||||||||||||||||||||||||||||||||||||||||||||
): # if I remove the second check, allows empty arrays, like old behaviour | ||||||||||||||||||||||||||||||||||||||||||||
msg = "Unexpected data state, got no lazy or real data, and no shape." | ||||||||||||||||||||||||||||||||||||||||||||
raise ValueError(msg) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm so glad of this correction, I couldn't get my head around the correct way to do this and was pulling out what precious hair I have left. |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def _deepcopy(self, memo, data=None): | ||||||||||||||||||||||||||||||||||||||||||||
"""Perform a deepcopy of the :class:`~iris._data_manager.DataManager` instance. | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -148,25 +163,30 @@ def _deepcopy(self, memo, data=None): | |||||||||||||||||||||||||||||||||||||||||||
:class:`~iris._data_manager.DataManager` instance. | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
shape = None | ||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||
if data is None: | ||||||||||||||||||||||||||||||||||||||||||||
# Copy the managed data. | ||||||||||||||||||||||||||||||||||||||||||||
if self.has_lazy_data(): | ||||||||||||||||||||||||||||||||||||||||||||
data = copy.deepcopy(self._lazy_array, memo) | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
elif self._real_array is not None: | ||||||||||||||||||||||||||||||||||||||||||||
data = self._real_array.copy() | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
shape = self.shape | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll come back to this one |
||||||||||||||||||||||||||||||||||||||||||||
elif type(data) is str and data == iris.DATALESS: | ||||||||||||||||||||||||||||||||||||||||||||
shape = self.shape | ||||||||||||||||||||||||||||||||||||||||||||
data = None | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
# Check that the replacement data is valid relative to | ||||||||||||||||||||||||||||||||||||||||||||
# the currently managed data. | ||||||||||||||||||||||||||||||||||||||||||||
dm_check = DataManager(self.core_data()) | ||||||||||||||||||||||||||||||||||||||||||||
dm_check.data = data | ||||||||||||||||||||||||||||||||||||||||||||
# If the replacement data is valid, then use it but | ||||||||||||||||||||||||||||||||||||||||||||
# without copying it. | ||||||||||||||||||||||||||||||||||||||||||||
result = DataManager(data) | ||||||||||||||||||||||||||||||||||||||||||||
result = DataManager(data=data, shape=shape) | ||||||||||||||||||||||||||||||||||||||||||||
except ValueError as error: | ||||||||||||||||||||||||||||||||||||||||||||
emsg = "Cannot copy {!r} - {}" | ||||||||||||||||||||||||||||||||||||||||||||
raise ValueError(emsg.format(type(self).__name__, error)) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
return result | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO Require to update the As it happens, you're getting that behaviour for free i.e., when we're dataless then |
||||||||||||||||||||||||||||||||||||||||||||
|
@@ -219,15 +239,20 @@ def data(self, data): | |||||||||||||||||||||||||||||||||||||||||||
managed. | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||
# If data is None, ensure previous shape is maintained, and that it is | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO Require to update the doc-string for the dataless |
||||||||||||||||||||||||||||||||||||||||||||
# not wrapped in an np.array | ||||||||||||||||||||||||||||||||||||||||||||
dataless = data is None | ||||||||||||||||||||||||||||||||||||||||||||
if dataless: | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
self._shape = self.shape | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO Don't we require to set i.e., do that and nothing further. |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Ensure we have numpy-like data. | ||||||||||||||||||||||||||||||||||||||||||||
if not (hasattr(data, "shape") and hasattr(data, "dtype")): | ||||||||||||||||||||||||||||||||||||||||||||
elif not (hasattr(data, "shape") and hasattr(data, "dtype")): | ||||||||||||||||||||||||||||||||||||||||||||
data = np.asanyarray(data) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
# Determine whether the class instance has been created, | ||||||||||||||||||||||||||||||||||||||||||||
# as this method is called from within the __init__. | ||||||||||||||||||||||||||||||||||||||||||||
init_done = self._lazy_array is not None or self._real_array is not None | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
if init_done and self.shape != data.shape: | ||||||||||||||||||||||||||||||||||||||||||||
# Determine whether the class already has a defined shape, | ||||||||||||||||||||||||||||||||||||||||||||
# as this method is called from __init__. | ||||||||||||||||||||||||||||||||||||||||||||
has_shape = self.shape is not None | ||||||||||||||||||||||||||||||||||||||||||||
if has_shape and not dataless and self.shape != data.shape: | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO In the dataless case, we shouldn't get here. |
||||||||||||||||||||||||||||||||||||||||||||
# The _ONLY_ data reshape permitted is converting a 0-dimensional | ||||||||||||||||||||||||||||||||||||||||||||
# array i.e. self.shape == () into a 1-dimensional array of length | ||||||||||||||||||||||||||||||||||||||||||||
# one i.e. data.shape == (1,) | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -242,7 +267,8 @@ def data(self, data): | |||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
if not ma.isMaskedArray(data): | ||||||||||||||||||||||||||||||||||||||||||||
# Coerce input data to ndarray (including ndarray subclasses). | ||||||||||||||||||||||||||||||||||||||||||||
data = np.asarray(data) | ||||||||||||||||||||||||||||||||||||||||||||
if not dataless: | ||||||||||||||||||||||||||||||||||||||||||||
data = np.asarray(data) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO We shouldn't need this defensive code for the dataless case i.e., we shouldn't get here. |
||||||||||||||||||||||||||||||||||||||||||||
if isinstance(data, ma.core.MaskedConstant): | ||||||||||||||||||||||||||||||||||||||||||||
# Promote to a masked array so that the fill-value is | ||||||||||||||||||||||||||||||||||||||||||||
# writeable to the data owner. | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -261,12 +287,19 @@ def dtype(self): | |||||||||||||||||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||||||||||||||||||
def ndim(self): | ||||||||||||||||||||||||||||||||||||||||||||
"""The number of dimensions covered by the data being managed.""" | ||||||||||||||||||||||||||||||||||||||||||||
return self.core_data().ndim | ||||||||||||||||||||||||||||||||||||||||||||
return len(self.shape) | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||||||||||||||||||||
def shape(self): | ||||||||||||||||||||||||||||||||||||||||||||
"""The shape of the data being managed.""" | ||||||||||||||||||||||||||||||||||||||||||||
return self.core_data().shape | ||||||||||||||||||||||||||||||||||||||||||||
if self.core_data() is None: | ||||||||||||||||||||||||||||||||||||||||||||
result = self._shape | ||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||
result = self.core_data().shape | ||||||||||||||||||||||||||||||||||||||||||||
return result | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def is_dataless(self): | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
return (self.core_data() is None) and (self.shape is not None) | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ESadek-MO Given our axiom (and if this isn't true then something is wrong) it must always be the case that:
Therefore, |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
def copy(self, data=None): | ||||||||||||||||||||||||||||||||||||||||||||
"""Return a deep copy of this :class:`~iris._data_manager.DataManager` instance. | ||||||||||||||||||||||||||||||||||||||||||||
|
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.
@ESadek-MO Move this definition to the top of the module please and include in the
__all__
.