def set_face_set_gcs_list_from_dict( grid, face_set_dict=None, create_organizing_objects_where_needed=False): """Creates a grid connection set for each feature in the face set dictionary, based on kelp list pairs.""" if face_set_dict is None: face_set_dict = grid.face_set_dict grid.face_set_gcs_list = [] for feature, kelp_values in face_set_dict.items(): gcs = rqf.GridConnectionSet(grid.model, grid=grid) if len(kelp_values) == 2: kelp_j, kelp_i = kelp_values axis = 'K' elif len(kelp_values) == 3: kelp_j, kelp_i, axis = kelp_values else: raise ValueError('grid face set dictionary item messed up') log.debug(f'creating gcs for: {feature} {axis}') gcs.set_pairs_from_kelp(kelp_j, kelp_i, feature, create_organizing_objects_where_needed, axis=axis) grid.face_set_gcs_list.append(gcs)
def test_two_grid_gcs(tmp_path): epc = make_epc_with_abutting_grids(tmp_path) # re-open the model and establish the abutting grid connection set model = rq.Model(epc) gcs_uuid = model.uuid(obj_type='GridConnectionSetRepresentation') assert gcs_uuid is not None gcs = rqf.GridConnectionSet(model, uuid=gcs_uuid) assert gcs is not None gcs.cache_arrays() # check that attributes have been preserved assert gcs.number_of_grids() == 2 assert len(gcs.grid_list) == 2 assert not bu.matching_uuids(gcs.grid_list[0].uuid, gcs.grid_list[1].uuid) assert gcs.count == 6 assert gcs.grid_index_pairs.shape == (6, 2) assert np.all(gcs.grid_index_pairs[:, 0] == 0) assert np.all(gcs.grid_index_pairs[:, 1] == 1) assert gcs.face_index_pairs.shape == (6, 2) assert np.all(gcs.face_index_pairs[:, 0] == gcs.face_index_map[1, 1]) # J+ assert np.all(gcs.face_index_pairs[:, 1] == gcs.face_index_map[1, 0]) # J- assert tuple(gcs.face_index_inverse_map[gcs.face_index_pairs[0, 0]]) == (1, 1) assert tuple(gcs.face_index_inverse_map[gcs.face_index_pairs[0, 1]]) == (1, 0) assert gcs.cell_index_pairs.shape == (6, 2) assert np.all(gcs.cell_index_pairs >= 0) assert np.all(gcs.cell_index_pairs < 60)
def test_two_fault_gcs(tmp_path): epc = make_epc_with_gcs(tmp_path) # re-open the model and check the gcs model = rq.Model(epc) gcs_uuid = model.uuid(obj_type='GridConnectionSetRepresentation') assert gcs_uuid is not None gcs = rqf.GridConnectionSet(model, uuid=gcs_uuid) assert gcs is not None assert gcs.number_of_features() == 2 feature_names = gcs.list_of_feature_names() assert len(feature_names) == 2 assert 'F1' in feature_names and 'F2' in feature_names fault_names = gcs.list_of_fault_names() assert fault_names == feature_names for fi in (0, 1): assert gcs.feature_name_for_feature_index(fi) in ('F1', 'F2') assert gcs.feature_name_for_feature_index( 0) != gcs.feature_name_for_feature_index(1) fi, f_uuid = gcs.feature_index_and_uuid_for_fault_name('F1') assert fi is not None and fi in (0, 1) assert f_uuid is not None assert gcs.fault_name_for_feature_index(fi) == 'F1' assert gcs.feature_index_for_cell_face((1, 1, 1), 0, 1) is None fi_a = gcs.feature_index_for_cell_face((1, 1, 1), 2, 1) assert fi_a in (0, 1) fi_b = gcs.feature_index_for_cell_face((1, 1, 1), 1, 1) assert fi_b in (0, 1) assert fi_a != fi_b gcs.rework_face_pairs()
def _iter_grid_connection_sets(model): """Yields grid connection set objects, one for each gcs in this model.""" import resqpy.fault as rqf # imported here for speed, module is not always needed gcs_uuids = _uuids(model, obj_type='GridConnectionSetRepresentation') for gcs_uuid in gcs_uuids: yield rqf.GridConnectionSet(model, uuid=gcs_uuid)
def make_epc_with_gcs(tmp_path): epc = os.path.join(tmp_path, 'two_fault.epc') model = rq.new_model(epc) # create a grid g = grr.RegularGrid(model, extent_kji=(5, 4, 3), dxyz=(100.0, 100.0, 10.0)) g.create_xml() # create an empty grid connection set gcs = rqf.GridConnectionSet(model, grid=g) # prepare two named faults as a dataframe data = { 'name': ['F1', 'F2'], 'face': ['I+', 'J-'], 'i1': [1, 0], 'i2': [1, 2], 'j1': [0, 2], 'j2': [3, 2], 'k1': [0, 0], 'k2': [4, 4], 'mult': [0.1, 0.05] } df = pd.DataFrame(data) # set grid connection set from dataframe gcs.set_pairs_from_faces_df(df, create_organizing_objects_where_needed=True, create_mult_prop=True, fault_tmult_dict=None, one_based_indexing=False) # save the grid connection set gcs.write_hdf5() gcs.create_xml(title='two fault gcs') model.store_epc() # add some basic grid properties porosity_uuid = rqdm.add_one_grid_property_array(epc, np.full( g.extent_kji, 0.27), property_kind='porosity', title='porosity', uom='m3/m3') assert porosity_uuid is not None perm_uuid = rqdm.add_one_grid_property_array( epc, np.full(g.extent_kji, 152.0), property_kind='rock permeability', uom='mD', facet_type='direction', facet='IJK', title='permeability') assert perm_uuid is not None return epc
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 make_epc_with_abutting_grids(tmp_path): epc = os.path.join(tmp_path, 'abutting_grids.epc') model = rq.new_model(epc) # create a grid g0 = grr.RegularGrid(model, extent_kji=(5, 4, 3), dxyz=(100.0, 100.0, 10.0)) g0.create_xml() g1 = grr.RegularGrid(model, extent_kji=(5, 4, 3), dxyz=(100.0, 100.0, 10.0), origin=(100.0, 400.0, 20.0)) g1.create_xml() # create an empty grid connection set gcs = rqf.GridConnectionSet(model, title='abut') # populate the grid connection set at low level due to lack of multi-grid methods gcs.grid_list = [g0, g1] gcs.count = 6 gcs.grid_index_pairs = np.zeros((6, 2), dtype=int) gcs.grid_index_pairs[:, 1] = 1 gcs.face_index_pairs = np.empty((6, 2), dtype=int) gcs.face_index_pairs[:, 0] = gcs.face_index_map[1, 1] # J+ gcs.face_index_pairs[:, 1] = gcs.face_index_map[1, 0] # J- gcs.cell_index_pairs = np.empty((6, 2), dtype=int) cell = 0 for k in range(3): for i in range(2): gcs.cell_index_pairs[cell, 0] = g0.natural_cell_index( (k + 2, 3, i + 1)) gcs.cell_index_pairs[cell, 1] = g1.natural_cell_index((k, 0, i)) cell += 1 # leave optional feature list & indices as None # save the grid connection set gcs.write_hdf5() gcs.create_xml() model.store_epc() return epc
def _set_support_uuid_notnone_supportnone(collection, support_uuid, model): import resqpy.fault as rqf import resqpy.grid as grr import resqpy.surface as rqs import resqpy.unstructured as rug import resqpy.well as rqw support_part = model.part_for_uuid(support_uuid) assert support_part is not None, 'supporting representation part missing in model' collection.support_root = model.root_for_part(support_part) support_type = model.type_of_part(support_part) assert support_type is not None if support_type == 'obj_IjkGridRepresentation': collection.support = grr.any_grid(model, uuid=collection.support_uuid, find_properties=False) elif support_type == 'obj_WellboreFrameRepresentation': collection.support = rqw.WellboreFrame(model, uuid=collection.support_uuid) elif support_type == 'obj_BlockedWellboreRepresentation': collection.support = rqw.BlockedWell(model, uuid=collection.support_uuid) elif support_type == 'obj_Grid2dRepresentation': collection.support = rqs.Mesh(model, uuid=collection.support_uuid) elif support_type == 'obj_GridConnectionSetRepresentation': collection.support = rqf.GridConnectionSet( model, uuid=collection.support_uuid) elif support_type == 'obj_TriangulatedSetRepresentation': collection.support = rqs.Surface(model, uuid=collection.support_uuid) elif support_type == 'obj_UnstructuredGridRepresentation': collection.support = rug.UnstructuredGrid(model, uuid=collection.support_uuid, geometry_required=False, find_properties=False) elif support_type == 'obj_WellboreMarkerFrameRepresentation': collection.support = rqw.WellboreMarkerFrame( model, uuid=collection.support_uuid) else: raise TypeError( 'unsupported property supporting representation class: ' + str(support_type))
def fault_throw_scaling(epc_file, source_grid=None, scaling_factor=None, connection_set=None, scaling_dict=None, ref_k0=0, ref_k_faces='top', cell_range=0, offset_decay=0.5, store_displacement=False, inherit_properties=False, inherit_realization=None, inherit_all_realizations=False, inherit_gcs=True, new_grid_title=None, new_epc_file=None): """Extends epc with a new grid with fault throws multiplied by scaling factors. arguments: epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object (or one 'ROOT' grid) which is used as the source grid scaling_factor (float, optional): if present, the default scaling factor to apply to split pillars which do not appear in any of the faults in the scaling dictionary; if None, such pillars are left unchanged connection_set (fault.GridConnectionSet object): the connection set with associated fault feature list, used to identify which faces (and hence pillars) belong to which named fault scaling_dict (dictionary mapping string to float): the scaling factor to apply to each named fault; any faults not included in the dictionary will be left unadjusted (unless a default scaling factor is given as scaling_factor arg) ref_k0 (integer, default 0): the reference layer (zero based) to use when determining the pre-existing throws ref_k_faces (string, default 'top'): 'top' or 'base' identifying which bounding interface to use as the reference cell_range (integer, default 0): the number of cells away from faults which will have depths adjusted to spatially smooth the effect of the throw scaling (ie. reduce sudden changes in gradient due to the scaling) offset_decay (float, default 0.5): DEPRECATED; ignored store_displacement (boolean, default False): if True, 3 grid property parts are created, one each for x, y, & z displacement of cells' centres brought about by the fault throw scaling inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated with the source grid inherit_realization (int, optional): realization number for which properties will be inherited; ignored if inherit_properties is False inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if inherit_properties is False or inherit_realization is not None inherit_gcs (boolean, default True): if True, any grid connection set objects related to the source grid will be inherited by the modified grid new_grid_title (string): used as the citation title text for the new grid object new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present, a new epc file (& associated h5 file) is created to contain the derived grid (& crs) returns: new grid (grid.Grid object), with fault throws scaled according to values in the scaling dictionary notes: grid points are moved along pillar lines; stretch is towards or away from mid-point of throw; same shift is applied to all layers along pillar; pillar lines assumed to be straight; the offset decay argument might be changed in a future version to give improved smoothing; if a large fault is represented by a series of parallel minor faults 'stepping' down, each minor fault will have the scaling factor applied independently, leading to some unrealistic results """ assert epc_file or new_epc_file, 'epc file name not specified' if new_epc_file and epc_file and ( (new_epc_file == epc_file) or (os.path.exists(new_epc_file) and os.path.exists(epc_file) and os.path.samefile(new_epc_file, epc_file))): new_epc_file = None model, source_grid = _establish_model_and_source_grid( epc_file, source_grid) assert source_grid.grid_representation == 'IjkGrid' assert model is not None assert source_grid.has_split_coordinate_lines, 'cannot scale fault throws in unfaulted grid' assert scaling_factor is not None or (connection_set is not None and scaling_dict is not None) if ref_k_faces == 'base': ref_k0 += 1 assert ref_k0 >= 0 and ref_k0 <= source_grid.nk, 'reference layer out of range' # take a copy of the grid log.debug('copying grid') grid = copy_grid(source_grid, model) grid.cache_all_geometry_arrays() # probably already cached anyway # todo: handle pillars with no geometry defined, and cells without geometry defined assert grid.geometry_defined_for_all_pillars( ), 'not all pillars have defined geometry' primaries = (grid.nj + 1) * (grid.ni + 1) offsets = np.zeros(grid.points_cached.shape[1:]) if scaling_factor is not None: # apply global scaling to throws _set_offsets_based_on_scaling_factor(grid, scaling_factor, offsets, ref_k0, primaries) if connection_set is not None and scaling_dict is not None: # overwrite any global offsets with named fault throw adjustments _set_offsets_based_on_scaling_dict(grid, connection_set, scaling_dict, offsets, ref_k0) # initialise flag array for adjustments adjusted = np.zeros((primaries, ), dtype=bool) # insert adjusted throws to all layers of split pillars grid.points_cached[:, grid.split_pillar_indices_cached, :] += offsets[ grid.split_pillar_indices_cached, :].reshape(1, -1, 3) adjusted[grid.split_pillar_indices_cached] = True grid.points_cached[:, primaries:, :] += offsets[primaries:, :].reshape( 1, -1, 3) # iteratively look for pillars neighbouring adjusted pillars, adjusting by a decayed amount adjusted = adjusted.reshape((grid.nj + 1, grid.ni + 1)) while cell_range > 0: newly_adjusted = _neighbourly_adjustment(grid, offsets, adjusted, cell_range) adjusted = np.logical_or(adjusted, newly_adjusted) cell_range -= 1 # check cell edge relative directions (in x,y) to ensure geometry is still coherent log.debug('checking grid geometry coherence') grid.check_top_and_base_cell_edge_directions() # build cell displacement property array(s) if store_displacement: log.debug('generating cell displacement property arrays') displacement_collection = _displacement_properties(grid, source_grid) else: displacement_collection = None collection = _prepare_simple_inheritance(grid, source_grid, inherit_properties, inherit_realization, inherit_all_realizations) if collection is None: collection = displacement_collection elif displacement_collection is not None: collection.inherit_imported_list_from_other_collection( displacement_collection, copy_cached_arrays=False) if new_grid_title is None or len(new_grid_title) == 0: new_grid_title = 'grid with fault throws scaled by ' + str(scaling_factor) + ' from ' + \ str(rqet.citation_title_for_node(source_grid.root)) gcs_list = [] if inherit_gcs: gcs_uuids = model.uuids(obj_type='GridConnectionSetRepresentation', related_uuid=source_grid.uuid) for gcs_uuid in gcs_uuids: gcs = rqf.GridConnectionSet(model, uuid=gcs_uuid) gcs.cache_arrays() gcs_list.append((gcs, gcs.title)) log.debug(f'{len(gcs_list)} grid connection sets to be inherited') # write model model.h5_release() if new_epc_file: _write_grid(new_epc_file, grid, property_collection=collection, grid_title=new_grid_title, mode='w') epc_file = new_epc_file else: ext_uuid, _ = model.h5_uuid_and_path_for_node( rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']), 'Coordinates') _write_grid(epc_file, grid, ext_uuid=ext_uuid, property_collection=collection, grid_title=new_grid_title, mode='a') if len(gcs_list): log.debug( f'inheriting grid connection sets related to source grid: {source_grid.uuid}' ) _inherit_gcs_list(epc_file, gcs_list, source_grid, grid) return grid
def test_gcs_property_inheritance(tmp_path): epc = os.path.join(tmp_path, 'gcs_prop_inherit.epc') model = rq.Model(epc, new_epc=True, create_basics=True, create_hdf5_ext=True) # create a grid g = grr.RegularGrid(model, extent_kji=(5, 3, 3), dxyz=(10.0, 10.0, 1.0)) g.write_hdf5() g.create_xml(title='unsplit grid') # define an L shaped (in plan view) fault j_faces = np.zeros((g.nk, g.nj - 1, g.ni), dtype=bool) j_faces[:, 0, 1:] = True i_faces = np.zeros((g.nk, g.nj, g.ni - 1), dtype=bool) i_faces[:, 1:, 0] = True gcs = rqf.GridConnectionSet( model, grid=g, j_faces=j_faces, i_faces=i_faces, feature_name='L fault', create_organizing_objects_where_needed=True, create_transmissibility_multiplier_property=False) # check that connection set has the right number of cell face pairs assert gcs.count == g.nk * ((g.nj - 1) + (g.ni - 1)) # create a transmissibility multiplier property tm = np.arange(gcs.count).astype(float) if gcs.property_collection is None: gcs.property_collection = rqp.PropertyCollection() gcs.property_collection.set_support(support=gcs) pc = gcs.property_collection pc.add_cached_array_to_imported_list( tm, 'unit test', 'TMULT', uom='Euc', # actually a ratio of transmissibilities property_kind='transmissibility multiplier', local_property_kind_uuid=None, realization=None, indexable_element='faces') # write gcs which should also write property collection and create a local property kind gcs.write_hdf5() gcs.create_xml(write_new_properties=True) # check that a local property kind has materialised pk_uuid = model.uuid(obj_type='PropertyKind', title='transmissibility multiplier') assert pk_uuid is not None # check that we can create a surface object for the gcs surf = gcs.surface() assert surf is not None t, _ = surf.triangles_and_points() assert t.shape == (2 * gcs.count, 3) # create a derived grid connection set using a layer range thin_gcs, thin_indices = gcs.filtered_by_layer_range(min_k0=1, max_k0=3, return_indices=True) assert thin_gcs is not None and thin_indices is not None assert thin_gcs.count == 3 * ((g.nj - 1) + (g.ni - 1)) # inherit the transmissibility multiplier property thin_gcs.inherit_properties_for_selected_indices(gcs, thin_indices) thin_gcs.write_hdf5() thin_gcs.create_xml() # by default will include write of new properties # check that the inheritance has worked assert thin_gcs.property_collection is not None and thin_gcs.property_collection.number_of_parts( ) > 0 thin_pc = thin_gcs.property_collection tm_part = thin_pc.singleton(property_kind='transmissibility multiplier') assert tm_part is not None thin_tm = thin_pc.cached_part_array_ref(tm_part) assert thin_tm is not None and thin_tm.ndim == 1 assert thin_tm.size == thin_gcs.count assert_array_almost_equal(thin_tm, tm[thin_indices]) # check that get_combined...() method can execute using property collection b_a, i_a, f_a = gcs.get_combined_fault_mask_index_value_arrays( min_k=1, max_k=3, property_name='Transmissibility multiplier', ref_k=2) assert b_a is not None and i_a is not None and f_a is not None # check that transmissibility multiplier values have been sampled correctly from property array assert f_a.shape == (g.nj, g.ni, 2, 2) assert np.count_nonzero( np.isnan(f_a)) == 4 * g.nj * g.ni - 2 * ((g.nj - 1) + (g.ni - 1)) assert np.nanmax(f_a) > np.nanmin(f_a) restore = np.seterr(all='ignore') assert np.all(np.logical_or(np.isnan(f_a), f_a >= np.nanmin(thin_tm))) assert np.all(np.logical_or(np.isnan(f_a), f_a <= np.nanmax(thin_tm))) np.seterr(**restore)
def test_feature_inheritance(tmp_path): epc = make_epc_with_gcs(tmp_path) # introduce a split version of the grid model = rq.Model(epc) simple_gcs_uuid = model.uuid(obj_type='GridConnectionSetRepresentation') assert simple_gcs_uuid is not None crs_uuid = model.uuid(obj_type='LocalDepth3dCrs') assert crs_uuid is not None line_a = rql.Polyline(model, set_bool=False, set_crs=crs_uuid, title='line a', set_coord=np.array([[200.0, -50.0, 0.0], [200.0, 450.0, 0.0]])) line_b = rql.Polyline(model, set_bool=False, set_crs=crs_uuid, title='line b', set_coord=np.array([[-50.0, 200.0, 0.0], [350.0, 200.0, 0.0]])) fault_lines = rql.PolylineSet(model, polylines=[line_a, line_b], title='fault lines') assert fault_lines is not None fault_lines.write_hdf5() fault_lines.create_xml() model.store_epc() model.h5_release() i_grid = rqdm.add_faults(epc, polylines=fault_lines, source_grid=None, inherit_properties=True, new_grid_title='interim', create_gcs=False) assert i_grid is not None # increase the throw on the faults rqdm.global_fault_throw_scaling(epc, source_grid=i_grid, scaling_factor=10.0, cell_range=1, inherit_properties=True, new_grid_title='faulted') # re-open the model and load the faulted grid model = rq.Model(epc) grid = model.grid(title='faulted') assert grid is not None # establish the original simple gcs (even though it relates to a different grid?) simple_gcs = rqf.GridConnectionSet(model, uuid=simple_gcs_uuid) # derive a grid connection set from fault juxtaposition, inheriting features from simple gcs juxta_gcs, tr = grid.fault_connection_set(compute_transmissibility=True, add_to_model=True, inherit_features_from=simple_gcs, title='juxtaposed') assert juxta_gcs is not None assert tr is not None assert tr.ndim == 1 and len(tr) == juxta_gcs.count # check that features have been inherited correctly assert juxta_gcs.number_of_features() == 2 feature_names = juxta_gcs.list_of_feature_names() for fi in range(2): if 'F1' in feature_names[fi]: axis = 2 else: assert 'F2' in feature_names[fi] axis = 1 cfp_lists = juxta_gcs.list_of_cell_face_pairs_for_feature_index(fi) assert len(cfp_lists) == 2 and len(cfp_lists[0]) == len( cfp_lists[1]) and len(cfp_lists[1]) > 0 for fip in cfp_lists[1]: assert fip.shape == (2, 2) assert np.all(fip[:, 0] == axis) assert np.all(fip[0, 1] == 1 - fip[1, 1]) # check that transmissibility property has been created okay tr_uuid = model.uuid(obj_type='ContinuousProperty', related_uuid=juxta_gcs.uuid) assert tr_uuid is not None trp = rqp.Property(model, uuid=tr_uuid) assert trp is not None assert trp.is_continuous() assert trp.property_kind() == 'transmissibility' assert_array_almost_equal(tr, trp.array_ref())
def test_pinchout_and_k_gap_gcs(tmp_path): epc = os.path.join(tmp_path, 'gcs_pinchout_k_gap.epc') model = rq.new_model(epc) # create a grid g = grr.RegularGrid(model, extent_kji=(5, 5, 5), dxyz=(100.0, 100.0, 10.0), as_irregular_grid=True) # patch points to generate a pinchout p = g.points_cached assert p.shape == (6, 6, 6, 3) p[2, :3, :3] = p[1, :3, :3] # convert one layer to a K gap with pinchout p[4, 3:, 3:] = p[3, 3:, 3:] g.nk -= 1 g.extent_kji = np.array((g.nk, g.nj, g.ni), dtype=int) g.k_gaps = 1 g.k_gap_after_array = np.zeros(g.nk - 1, dtype=bool) g.k_gap_after_array[2] = True g._set_k_raw_index_array() g.write_hdf5() g.create_xml(title='pinchout k gap grid') model.store_epc() # reload the grid model = rq.Model(epc) grid = model.grid() assert grid is not None assert grid.k_gaps == 1 assert tuple(grid.extent_kji) == (4, 5, 5) # create a pinchout connection set po_gcs = rqf.pinchout_connection_set(grid) assert po_gcs is not None po_gcs.write_hdf5() po_gcs.create_xml() po_uuid = po_gcs.uuid # create a K gap connection set kg_gcs = rqf.k_gap_connection_set(grid) assert kg_gcs is not None kg_gcs.write_hdf5() kg_gcs.create_xml() kg_uuid = kg_gcs.uuid model.store_epc() # re-open the model and load the connection sets model = rq.Model(epc) po_gcs = rqf.GridConnectionSet(model, uuid=po_uuid) assert po_gcs is not None po_gcs.cache_arrays() kg_gcs = rqf.GridConnectionSet(model, uuid=kg_uuid) assert kg_gcs is not None kg_gcs.cache_arrays() # check face pairs in the pinchout connection set assert po_gcs.count == 4 assert po_gcs.cell_index_pairs.shape == (4, 2) assert po_gcs.face_index_pairs.shape == (4, 2) assert np.all( po_gcs.cell_index_pairs[:, 0] != po_gcs.cell_index_pairs[:, 1]) assert np.all( po_gcs.face_index_pairs[:, 0] != po_gcs.cell_index_pairs[:, 1]) assert np.all( np.logical_or(po_gcs.face_index_pairs == 0, po_gcs.face_index_pairs == 1)) for cell in po_gcs.cell_index_pairs.flatten(): assert cell in [0, 1, 5, 6, 50, 51, 55, 56] assert np.all( np.abs(po_gcs.cell_index_pairs[:, 1] - po_gcs.cell_index_pairs[:, 0]) == 50) # check face pairs in K gap connection set assert kg_gcs.count == 4 assert kg_gcs.cell_index_pairs.shape == (4, 2) assert kg_gcs.face_index_pairs.shape == (4, 2) assert np.all( kg_gcs.cell_index_pairs[:, 0] != kg_gcs.cell_index_pairs[:, 1]) assert np.all( kg_gcs.face_index_pairs[:, 0] != kg_gcs.cell_index_pairs[:, 1]) assert np.all( np.logical_or(kg_gcs.face_index_pairs == 0, kg_gcs.face_index_pairs == 1)) for cell in kg_gcs.cell_index_pairs.flatten(): assert cell in [74, 73, 69, 68, 99, 98, 94, 93] assert np.all( np.abs(kg_gcs.cell_index_pairs[:, 1] - kg_gcs.cell_index_pairs[:, 0]) == 25) # test compact indices method ci = po_gcs.compact_indices() assert ci.shape == (4, 2) for cf in ci.flatten(): assert cf in [1, 7, 31, 37, 300, 306, 330, 336] assert np.all(np.abs(ci[:, 1] - ci[:, 0]) == 299) # test write simulator method files = ('fault_ff.dat', 'fault_tf.dat', 'fault_ft.dat') both_sides = (False, True, False) minus = (False, False, True) for filename, inc_both_sides, use_minus in zip(files, both_sides, minus): dat_path = os.path.join(tmp_path, filename) po_gcs.write_simulator(dat_path, include_both_sides=inc_both_sides, use_minus=use_minus) assert os.path.exists(dat_path)
def grid_columns_property_from_gcs_property(model, gcs_property_uuid, null_value=np.nan, title=None, multiple_handling='default'): """Derives a new grid columns property (map) from a single-grid gcs property using values for k faces. arguments: model (Model): the model in which the existing objects are to be found and the new property added gcs_property_uuid (UUID): the uuid of the existing grid connection set property null_value (float or int, default NaN): the value to use in columns where no K faces are present in the gcs title (str, optional): the title for the new grid property; defaults to that of the gcs property multiple_handling (str, default 'mean'): one of 'default', 'mean', 'min', 'max', 'min_k', 'max_k', 'exception'; determines how a value is generated when more than one K face is present in the gcs for a column returns: uuid of the newly created Property (RESQML ContinuousProperty, DiscreteProperty, CategoricalProperty or PointsProperty) notes: the grid connection set which is the support for gcs_property must involve only one grid; the resulting columns grid property is of the same class as the original gcs property; the write_hdf() and create_xml() methods are called by this function, for the new property, which is added to the model; the default multiple handling mode is mean for continuous data, any for discrete (inc categorical); in the case of discrete (including categorical) data, a null_value of NaN will be changed to -1 """ assert multiple_handling in [ 'default', 'mean', 'min', 'max', 'any', 'exception' ] assert gcs_property_uuid is not None and bu.is_uuid(gcs_property_uuid) gcs_property = rqp.Property(model, uuid=gcs_property_uuid) assert gcs_property is not None support_uuid = gcs_property.collection.support_uuid assert support_uuid is not None assert model.type_of_uuid( support_uuid, strip_obj=True) == 'GridConnectionSetRepresentation' gcs = rqf.GridConnectionSet(model, uuid=support_uuid) gcs_prop_array = gcs_property.array_ref() if gcs_property.is_continuous(): dtype = float if multiple_handling == 'default': multiple_handling = 'mean' else: dtype = int if null_value == np.nan: null_value = -1 elif type(null_value) is float: null_value = int(null_value) if multiple_handling == 'default': multiple_handling = 'any' assert multiple_handling != 'mean', 'mean specified as multiple handling for non-continuous property' assert gcs.number_of_grids( ) == 1, 'only single grid gcs supported for grid columns property derivation' grid = gcs.grid_list[0] if gcs_property.is_points(): map_shape = (grid.nj, grid.ni, 3) else: map_shape = (grid.nj, grid.ni) assert gcs_property.count() == 1 map = np.full(map_shape, null_value, dtype=dtype) count_per_col = np.zeros((grid.nj, grid.ni), dtype=int) cells_and_faces = gcs.list_of_cell_face_pairs_for_feature_index(None) assert cells_and_faces is not None and len(cells_and_faces) == 2 cell_pairs, face_pairs = cells_and_faces if cell_pairs is None or len(cell_pairs) == 0: log.warning( 'no faces found for grid connection set property {gcs_property.title}' ) return None assert len(cell_pairs) == len(face_pairs) assert len(cell_pairs) == len(gcs_prop_array) for index in range(len(cell_pairs)): if face_pairs[index, 0, 0] != 0 or face_pairs[index, 1, 0] != 0: continue # not a K face col_j, col_i = cell_pairs[index, 0, 1:] # assume paired cell is in same column! if count_per_col[col_j, col_i] == 0 or multiple_handling == 'any': map[col_j, col_i] = gcs_prop_array[index] elif multiple_handling == 'mean': map[col_j, col_i] = (((map[col_j, col_i] * count_per_col[col_j, col_i]) + gcs_prop_array[index]) / float(count_per_col[col_j, col_i] + 1)) elif multiple_handling == 'min': if gcs_prop_array[index] < map[col_j, col_i]: map[col_j, col_i] = gcs_prop_array[index] elif multiple_handling == 'max': if gcs_prop_array[index] > map[col_j, col_i]: map[col_j, col_i] = gcs_prop_array[index] else: raise ValueError( 'multiple grid connection set K faces found for column') count_per_col[col_j, col_i] += 1 # create an empty PropertyCollection and add the map data as a new property time_index = gcs_property.time_index() pc = rqp.PropertyCollection() pc.set_support(support=grid) pc.add_cached_array_to_imported_list( map, source_info=f'{gcs.title} property {gcs_property.title} map', keyword=title if title else gcs_property.title, discrete=not gcs_property.is_continuous(), uom=gcs_property.uom(), time_index=time_index, null_value=None if gcs_property.is_continuous() else null_value, property_kind=gcs_property.property_kind(), local_property_kind_uuid=gcs_property.local_property_kind_uuid(), facet_type=gcs_property.facet_type(), facet=gcs_property.facet(), realization=None, # do we want to preserve the realisation number? indexable_element='columns', points=gcs_property.is_points()) time_series_uuid = pc.write_hdf5_for_imported_list() string_lookup_uuid = gcs_property.string_lookup_uuid() time_series_uuid = None if time_index is None else gcs_property.time_series_uuid( ) new_uuids = pc.create_xml_for_imported_list_and_add_parts_to_model( time_series_uuid=time_series_uuid, string_lookup_uuid=string_lookup_uuid) assert len(new_uuids) == 1 return new_uuids[0]