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

AttributePropertySet improvements #809

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions resqpy/property/attribute_property_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,14 @@ def local_property_kind_uuid(self):
class AttributePropertySet(rqp.PropertyCollection):
"""Class for set of RESQML properties for any supporting representation, using attribute syntax."""

def __init__(self, model = None, support = None, property_set_uuid = None, realization = None, key_mode = 'pk'):
def __init__(self,
model = None,
support = None,
property_set_uuid = None,
realization = None,
key_mode = 'pk',
indexable = None,
multiple_handling = 'warn'):
"""Initialise an empty property set, optionally populate properties from a supporting representation.

arguments:
Expand All @@ -198,6 +205,11 @@ def __init__(self, model = None, support = None, property_set_uuid = None, reali
if None, then the collection is either covering a whole ensemble (individual properties can each be flagged with a
realisation number), or is for properties that do not have multiple realizations
key_mode (str, default 'pk'): either 'pk' (for property kind) or 'title', identifying the basis of property attribute keys
indexable (str, optional): if present and key_mode is 'pk', properties with indexable element other than this will
have their indexable element included in their key
multiple_handling (str, default 'warn'): either 'ignore', 'warn' ,or 'exception'; if 'warn' or 'ignore', and properties
exist that generate the same key, then only the first is visible in the attribute property set (and a warning is given
for each of the others in the case of 'warn'); if 'exception', a KeyError is raised if there are any duplicate keys

note:
at present, if the collection is being initialised from a property set, the support argument must also be specified;
Expand All @@ -214,9 +226,12 @@ def __init__(self, model = None, support = None, property_set_uuid = None, reali
property_set_root = None
else:
property_set_root = model.root_for_uuid(property_set_uuid)
assert multiple_handling in ['ignore', 'warn', 'exception']

super().__init__(support = support, property_set_root = property_set_root, realization = realization)
self.key_mode = key_mode
self.indexable_mode = indexable
self.multiple_handling = multiple_handling
self._make_attributes()

def keys(self):
Expand All @@ -241,12 +256,21 @@ def _key(self, part):
title = self.citation_title_for_part(part),
facet = self.facet_for_part(part),
time_index = self.time_index_for_part(part),
realization = self.realization_for_part(part))
realization = self.realization_for_part(part),
indexable_mode = self.indexable_mode,
indexable = self.indexable_for_part(part))

def _make_attributes(self):
"""Setup individual properties with attribute style read access to metadata."""
for part in self.parts():
key = self._key(part)
if getattr(self, key, None) is not None:
if self.multiple_handling == 'warn':
log.warning(f'duplicate key in AttributePropertySet; only first instance included: {key}')
continue
if self.multiple_handling == 'ignore':
continue
raise KeyError(f'duplicate key in attribute property set: {key}')
aps_property = ApsProperty(self, part)
setattr(self, key, aps_property)

Expand All @@ -255,11 +279,20 @@ def __len__(self):
return self.number_of_parts()


def make_aps_key(key_mode, property_kind = None, title = None, facet = None, time_index = None, realization = None):
def make_aps_key(key_mode,
property_kind = None,
title = None,
facet = None,
time_index = None,
realization = None,
indexable_mode = None,
indexable = None):
"""Contructs the key (attribute name) for a property based on metadata items."""
if key_mode == 'pk':
assert property_kind is not None
key = property_kind
if indexable_mode is not None and indexable is not None and indexable != indexable_mode:
key += f'_{indexable}'
if facet is not None:
key += f'_{facet}'
else:
Expand Down
51 changes: 51 additions & 0 deletions tests/unit_tests/property/test_attribute_property_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,57 @@ def test_load_attribute_property_collection_pk(example_model_with_prop_ts_rels):
assert np.all(v == aps.saturation_water_t2.array_ref)


def test_load_attribute_property_collection_pk_duplicates(example_model_with_prop_ts_rels, capfd, caplog):
# Arrange
model = example_model_with_prop_ts_rels
grid = model.grid()
pc = grid.extract_property_collection()

# Act
aps = rqp.AttributePropertySet(support = grid)
facies_col = np.max(aps.facies.values, axis = 0)
pc.add_similar_to_imported_list(similar_uuid = aps.facies.uuid,
cached_array = facies_col,
indexable_element = 'columns')
pc.write_hdf5_for_imported_list()
pc.create_xml_for_imported_list_and_add_parts_to_model()

# check indexable used when needed in key
aps = rqp.AttributePropertySet(support = grid, indexable = 'cells')
assert aps is not None
assert len(aps) == 13
assert 'facies' in aps.keys()
assert 'facies_columns' in aps.keys()

# check duplicate ignored
aps = rqp.AttributePropertySet(support = grid, indexable = None, multiple_handling = 'ignore')
assert aps is not None
assert len(aps) == 13
key_list = list(aps.keys())
assert len(key_list) == 13 # all parts still exist in the PropertyCollection
assert len(
set(key_list)) == 12 # but two will have the same aps key (only the first is visible as AttributeProperty)
assert 'facies' in aps.keys()
assert 'facies_columns' not in aps.keys()

# check that multiple handling 'exception' raises key error
with pytest.raises(KeyError) as e_info:
aps = rqp.AttributePropertySet(support = grid, multiple_handling = 'exception')

# check that multiple handling 'warn' generates log warning
aps = rqp.AttributePropertySet(support = grid, multiple_handling = 'warn')
assert len(caplog.records) > 0
assert caplog.records[-1].getMessage().endswith(
"duplicate key in AttributePropertySet; only first instance included: facies")
assert aps is not None
assert len(aps) == 13
key_list = list(aps.keys())
assert len(key_list) == 13
assert len(set(key_list)) == 12
assert 'facies' in aps.keys()
assert 'facies_columns' not in aps.keys()


def test_load_attribute_property_collection_title(example_model_with_prop_ts_rels):
# Arrange
model = example_model_with_prop_ts_rels
Expand Down
Loading