def __init__(self, model, uuid=None, title=None, originator=None, extra_metadata=None): """Load an existing resqml object, or create new. Args: model (resqpy.model.Model): Parent model uuid (str, optional): Load from existing uuid (if given), else create new. title (str, optional): Citation title originator (str, optional): Creator of object. By default, uses user id. """ self.model = model self.title = title #: Citation title self.originator = originator #: Creator of object. By default, user id. self.extra_metadata = {} if extra_metadata: self.extra_metadata = extra_metadata self._standardise_extra_metadata( ) # has side effect of making a copy if uuid is None: self.uuid = bu.new_uuid() #: Unique identifier else: self.uuid = uuid root_node = self.root citation_node = rqet.find_tag(root_node, 'Citation') if citation_node is not None: self.title = rqet.find_tag_text(citation_node, 'Title') self.originator = rqet.find_tag_text(citation_node, 'Originator') self.extra_metadata = rqet.load_metadata_from_xml(root_node) self._load_from_xml()
def grid_flavour(grid_root): """Returns a string indicating type of grid geometry, currently 'IjkGrid' or 'IjkBlockGrid'.""" if grid_root is None: return None em = rqet.load_metadata_from_xml(grid_root) flavour = em.get('grid_flavour') if flavour is None: node_type = rqet.node_type(grid_root, strip_obj = True) if node_type == 'IjkGridRepresentation': if rqet.find_tag(grid_root, 'Geometry') is not None: flavour = 'IjkGrid' else: flavour = 'IjkBlockGrid' # this might cause issues elif node_type == 'UnstructuredGridRepresentation': cell_shape = rqet.find_nested_tags_text(grid_root, ['Geometry', 'CellShape']) if cell_shape is None or cell_shape == 'polyhedral': flavour = 'UnstructuredGrid' elif cell_shape == 'tetrahedral': flavour = 'TetraGrid' elif cell_shape == 'hexahedral': flavour = 'HexaGrid' elif cell_shape == 'pyramidal': flavour = 'PyramidGrid' elif cell_shape == 'prism': flavour = 'PrismGrid' return flavour
def _load_from_xml(self): assert self.root is not None # polyline xml node is specified poly_root = self.root self.title = rqet.citation_title_for_node(poly_root) self.extra_metadata = rqet.load_metadata_from_xml(self.root) self.isclosed = rqet.bool_from_text( rqet.node_text(rqet.find_tag(poly_root, 'IsClosed'))) assert self.isclosed is not None # Required field patch_node = rqet.find_tag(poly_root, 'NodePatch') assert patch_node is not None # Required field geometry_node = rqet.find_tag(patch_node, 'Geometry') assert geometry_node is not None # Required field self.crs_uuid = bu.uuid_from_string( rqet.find_nested_tags_text(geometry_node, ['LocalCrs', 'UUID'])) assert self.crs_uuid is not None # Required field points_node = rqet.find_tag(geometry_node, 'Points') assert points_node is not None # Required field load_hdf5_array(self, points_node, 'coordinates', tag='Coordinates') self.nodepatch = (rqet.find_tag_int(patch_node, 'PatchIndex'), rqet.find_tag_int(patch_node, 'Count')) assert not any( map(lambda x: x is None, self.nodepatch)) # Required fields - assert neither are None self.rep_int_root = self.model.referenced_node( rqet.find_tag(poly_root, 'RepresentedInterpretation'))
def equivalent_extra_metadata(a, b): """Returns True if the two objects have identical extra metadata""" a_has = hasattr(a, 'extra_metadata') b_has = hasattr(b, 'extra_metadata') if a_has: a_em = a.extra_metadata a_has = len(a_em) > 0 else: a_em = rqet.load_metadata_from_xml(a.root) a_has = a_em is not None and len(a_em) > 0 if b_has: b_em = b.extra_metadata b_has = len(b_em) > 0 else: b_em = rqet.load_metadata_from_xml(b.root) b_has = b_em is not None and len(b_em) > 0 if a_has != b_has: return False if not a_has: return True return a_em == b_em
def _filtered_by_extra(model, parts_list, extra): filtered_list = [] for part in parts_list: part_extra = rqet.load_metadata_from_xml(_root_for_part(model, part)) if not part_extra: continue match = True for key, value in extra.items(): if key not in part_extra or part_extra[key] != value: match = False break if match: filtered_list.append(part) return filtered_list
def test_add_connection_set_and_tmults(example_model_with_properties, test_data_path, inc_list, tmult_dict, expected_mult): model = example_model_with_properties inc_list = [os.path.join(test_data_path, inc) for inc in inc_list] gcs_uuid = rqf.add_connection_set_and_tmults(model, inc_list, tmult_dict) assert gcs_uuid is not None, 'Grid connection set not generated' reload_model = rq.Model(epc_file=model.epc_file) faults = reload_model.parts_list_of_type('obj_FaultInterpretation') assert len(faults) == len(expected_mult.keys()), \ f'Expected a {len(expected_mult.keys())} faults, found {len(faults)}' for fault in faults: metadata = rqet.load_metadata_from_xml( reload_model.root_for_part(fault)) title = reload_model.citation_title_for_part(fault) expected_str = str(float(expected_mult[title])) assert metadata["Transmissibility multiplier"] == expected_str, \ f'Expected mult for fault {title} to be {expected_str}, found {metadata["Transmissibility multiplier"]}' # check that a transmissibility multiplier property has been created gcs = rqf.GridConnectionSet(reload_model, uuid=gcs_uuid, find_properties=True) assert gcs is not None pc = gcs.property_collection assert pc is not None and pc.number_of_parts() > 0 part = pc.singleton(property_kind='transmissibility multiplier') assert part is not None # check property values are in expected set a = pc.cached_part_array_ref(part) assert a is not None and a.ndim == 1 expect = [x for x in expected_mult.values()] assert all([v in expect for v in a]) # see if a local property kind has been set up correctly pku = pc.local_property_kind_uuid(part) assert pku is not None pk = rqp.PropertyKind(reload_model, uuid=pku) assert pk is not None assert pk.title == 'transmissibility multiplier'
def _load_from_xml(self, marker_node): """Load attributes from xml. This is invoked as part of the init method when an existing uuid is given. Returns: [bool]: True if successful """ assert marker_node is not None # Load XML data uuid_str = marker_node.attrib.get('uuid') if uuid_str: self.uuid = bu.uuid_from_string(uuid_str) citation_tag = rqet.find_nested_tags(root=marker_node, tag_list=['Citation']) assert citation_tag is not None self.title = rqet.find_tag_text(root=citation_tag, tag_name='Title') self.originator = rqet.find_tag_text(root=citation_tag, tag_name='Originator') self.marker_type = None for boundary_feature_type in [ 'GeologicBoundaryKind', 'FluidMarker', 'FluidContact' ]: found_tag_text = rqet.find_tag_text(root=marker_node, tag_name=boundary_feature_type) if found_tag_text is not None: self.marker_type = found_tag_text break assert self.marker_type is not None self.interpretation_uuid = bu.uuid_from_string( rqet.find_nested_tags_text(root=marker_node, tag_list=['Interpretation', 'UUID'])) self.extra_metadata = rqet.load_metadata_from_xml(node=marker_node) return True
def __init__(self, model, uuid = None, df = None, uom_list = None, realization = None, phase_combo = None, low_sal = False, table_index = None, title = 'relperm_table', column_lookup_uuid = None, uom_lookup_uuid = None): """Create a new RelPerm object from either a previously stored object or a pandas dataframe. arguments: phase_combo (str, optional): the combination of phases whose relative permeability behaviour is described. Options include 'water-oil', 'gas-oil' and 'gas-water' low_sal (boolean, optional): if True, indicates that the water-oil table contains the low-salinity data for relative permeability and capillary pressure table_index (int, optional): the index of the relative permeability table when multiple relative permeability tables are present. Note, indices should start at 1. note: see DataFrame class docstring for details of other arguments """ # check that either a uuid OR dataframe has been provided if df is None and uuid is None: raise ValueError('either a uuid or a dataframe must be provided') # check extra_metadata for arguments if uuid is provided if uuid is not None: model_root = model.root(uuid = uuid) uuid_metadata_dict = rqet.load_metadata_from_xml(model_root) self.phase_combo = uuid_metadata_dict['phase_combo'] self.low_sal = uuid_metadata_dict['low_sal'] self.table_index = uuid_metadata_dict['table_index'] else: # check that 'phase_combo' parameter is valid self.phase_combo = phase_combo self.low_sal = low_sal self.table_index = table_index df.columns = [x.capitalize() for x in df.columns] if 'Pc' in df.columns and df.columns[-1] != 'Pc': raise ValueError('Pc', 'capillary pressure data should be in the last column of the dataframe') processed_phase_combo_checks = { ('oil', 'water'): self.__water_oil_error_check, ('gas', 'oil'): self.__gas_oil_error_check, ('gas', 'water'): self.__gas_water_error_check, ('None',): self.__no_phase_combo_error_check } processed_phase_combo = tuple(sorted(set([x.strip() for x in str(self.phase_combo).split('-')]))) if processed_phase_combo not in processed_phase_combo_checks.keys(): raise ValueError('invalid phase_combo provided') # check that table_index is >= 1 if table_index is not None and table_index < 1: raise ValueError('table_index cannot be less than 1') # check that the column names and order are as expected processed_phase_combo_checks.get(processed_phase_combo)(df) # ensure that missing capillary pressure values are stored as np.nan if 'Pc' in df.columns: df['Pc'].replace('None', np.nan, inplace = True) # convert all values in the dataframe to numeric type df[df.columns] = df[df.columns].apply(pd.to_numeric, errors = 'coerce') # ensure that no other column besides Pc has missing values cols_no_pc = [x for x in df.columns if 'Pc' != x] for col in cols_no_pc: if (df[col].isnull().sum() > 0) or ('None' in list(df[col])): raise Exception(f'missing values found in {col} column') # check that Sw, Kr and Pc values are monotonic and that the Sw and Kr values are within the range 0-1 sat_col = [x for x in df.columns if x[0] == 'S'][0] relp_corr_col = [x for x in df.columns if x == 'Kr' + sat_col[-1]][0] relp_opp_col = [x for x in df.columns if (x[0:2] == 'Kr') & (x[-1] != sat_col[-1])][0] if not (df[sat_col].is_monotonic and df[relp_corr_col].is_monotonic and df[ relp_opp_col].is_monotonic_decreasing) and not \ (df[sat_col].is_monotonic_decreasing and df[relp_corr_col].is_monotonic_decreasing and df[relp_opp_col].is_monotonic): raise ValueError(f'{sat_col, relp_corr_col, relp_opp_col} combo is not monotonic') if 'Pc' in df.columns: if not df['Pc'].dropna().is_monotonic and not df['Pc'].dropna().is_monotonic_decreasing: raise ValueError('Pc values are not monotonic') for col in ['Sw', 'Sg', 'So', 'Krw', 'Krg', 'Kro']: if col in df.columns: if df[col].min() < 0 or df[col].max() > 1: raise ValueError(f'{col} is not within the range 0-1') super().__init__(model, uuid = uuid, support_root = None, df = df, uom_list = uom_list, realization = realization, title = title, column_lookup_uuid = column_lookup_uuid, uom_lookup_uuid = uom_lookup_uuid, extra_metadata = { 'relperm_table': 'true', 'phase_combo': self.phase_combo, 'low_sal': str(self.low_sal).lower(), 'table_index': str(self.table_index) })