Skip to content

Commit

Permalink
Merge pull request #809 from bp/aps_improvements
Browse files Browse the repository at this point in the history
AttributePropertySet improvements
  • Loading branch information
andy-beer committed Jul 24, 2024
2 parents 8394bb0 + a2c657a commit 68976a6
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 3 deletions.
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

0 comments on commit 68976a6

Please sign in to comment.