def test_merge_two_grids(self): # Merge two grids that have a common face. Check that global indices are # updated to match, and that they point to the same point coordinates data = np.ones(3) rows = np.array([0, 1, 2]) cols = np.array([0, 0, 0]) cf = sps.coo_matrix((data, (rows, cols))) data = np.ones(6) rows = np.array([0, 1, 1, 2, 2, 0]) cols = np.array([0, 0, 1, 1, 2, 2]) fn = sps.coo_matrix((data, (rows, cols))) nodes_1 = np.array([[0, 1, 0], [0, 0, 1], [0, 0, 0]]) nodes_2 = np.array([[0, 1, 0], [0, 0, -1], [0, 0, 0]]) g1 = MockGrid(2, num_faces=3, face_nodes=fn, cell_faces=cf, num_cells=1, nodes=nodes_1) g2 = MockGrid(2, num_faces=3, face_nodes=fn, cell_faces=cf, num_cells=1, nodes=nodes_2) g_11 = TensorGrid(np.array([0, 1])) g_11.global_point_ind = np.arange(2) g_22 = TensorGrid(np.array([0, 1])) g_22.global_point_ind = np.arange(2) gl = [[[g1], [g_11]], [[g2], [g_22]]] intersections = [np.array([1]), np.array([0])] list_of_grids, glob_ind = non_conforming.init_global_ind(gl) grid_list_1d = non_conforming.process_intersections(gl, intersections, glob_ind, list_of_grids, tol=1e-4) g_1d = grid_list_1d[0] ismem, maps = ismember_rows(g_1d.global_point_ind, g1.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g1.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, g2.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g2.nodes[:, maps], g_1d.nodes)
def make_maps(g0, g1, n_digits=8, offset=0.11): """ Given two grid with the same nodes, faces and cells, the mappings between these entities are concstructed. Handles non-unique nodes and faces on next to fractures by exploiting neighbour information. Builds maps from g1 to g0, so g1.x[x_map]=g0.x, e.g. g1.tags[some_key][face_map] = g0.tags[some_key]. g0 Reference grid g1 Other grid n_digits Tolerance in rounding before coordinate comparison offset: Weight determining how far the fracture neighbour nodes and faces are shifted (normally away from fracture) to ensure unique coordinates. """ cell_map = sm.ismember_rows( np.around(g0.cell_centers, n_digits), np.around(g1.cell_centers, n_digits), sort=False, )[1] # Make face_centers unique by dragging them slightly away from the fracture fc0 = g0.face_centers.copy() fc1 = g1.face_centers.copy() n0 = g0.nodes.copy() n1 = g1.nodes.copy() fi0 = g0.tags["fracture_faces"] if np.any(fi0): fi1 = g1.tags["fracture_faces"] d0 = np.reshape(np.tile(g0.cell_faces[fi0, :].data, 3), (3, sum(fi0))) fn0 = g0.face_normals[:, fi0] * d0 d1 = np.reshape(np.tile(g1.cell_faces[fi1, :].data, 3), (3, sum(fi1))) fn1 = g1.face_normals[:, fi1] * d1 fc0[:, fi0] += fn0 * offset fc1[:, fi1] += fn1 * offset (ni0, fid0) = g0.face_nodes[:, fi0].nonzero() (ni1, fid1) = g1.face_nodes[:, fi1].nonzero() un, inv = np.unique(ni0, return_inverse=True) for i, node in enumerate(un): n0[:, node] += offset * np.mean(fn0[:, fid0[inv == i]], axis=1) un, inv = np.unique(ni1, return_inverse=True) for i, node in enumerate(un): n1[:, node] += offset * np.mean(fn1[:, fid1[inv == i]], axis=1) face_map = sm.ismember_rows(np.around(fc0, n_digits), np.around(fc1, n_digits), sort=False)[1] node_map = sm.ismember_rows(np.around(n0, n_digits), np.around(n1, n_digits), sort=False)[1] return cell_map, face_map, node_map
def test_face_centers_areas(self): face_nodes = self.g.face_nodes.indices.reshape((3, self.g.num_faces), order="F") ismem, ind_map = setmembership.ismember_rows(self.fn, face_nodes) self.assertTrue(np.all(ismem)) self.assertTrue(np.allclose(self.face_areas, self.g.face_areas[ind_map])) self.assertTrue(np.allclose(self.face_center, self.g.face_centers[:, ind_map]))
def face_to_cell_map(g_2d, g_1d, loc_faces, loc_nodes): # Match faces in a 2d grid and cells in a 1d grid by identifying # face-nodes and cell-node relations. # loc_faces are faces in 2d grid that are known to coincide with # cells. # loc_nodes are indices of 2d nodes along the segment, sorted so that # the ordering coincides with nodes in 1d grid # face-node relation in higher dimensional grid fn = g_2d.face_nodes.indices.reshape((g_2d.dim, g_2d.num_faces), order="F") # Reduce to faces along segment fn_loc = fn[:, loc_faces] # Mapping from global (2d) indices to the local indices used in 1d # grid. This also account for a sorting of the nodes, so that the # nodes. ind_map = np.zeros(g_2d.num_faces) ind_map[loc_nodes] = np.arange(loc_nodes.size) # Face-node in local indices fn_loc = ind_map[fn_loc] # Handle special case if loc_faces.size == 1: fn_loc = fn_loc.reshape((2, 1)) # Cell-node relation in 1d cn = g_1d.cell_nodes().indices.reshape((2, g_1d.num_cells), order="F") # Find cell index of each face ismem, ind = ismember_rows(fn_loc, cn) # Quality check, the grids should be conforming if not np.all(ismem): raise ValueError return ind
def test_merge_1d_permuted_nodes(self): g = TensorGrid(np.array([0, 1, 2])) g.nodes = np.array([[1, -1, 0], [0, 0, 0], [0, 0, 0]]) g.global_point_ind = np.array([2, 0, 1]) g.face_nodes.indices = np.array([1, 2, 0]) h = TensorGrid(np.array([-1, 0, 1])) h.global_point_ind = np.array([0, 1, 2]) g.compute_geometry() h.compute_geometry() c, _, _, _, _, _ = non_conforming.merge_1d_grids(g, h) ismem, maps = ismember_rows(c.global_point_ind, g.global_point_ind) assert ismem.sum() == c.num_nodes assert np.allclose(g.nodes[:, maps], c.nodes) ismem, maps = ismember_rows(c.global_point_ind, h.global_point_ind) assert ismem.sum() == c.num_nodes assert np.allclose(h.nodes[:, maps], c.nodes)
def test_ismember_rows_with_sort(self): a = np.array([[1, 3, 3, 1, 7], [3, 3, 2, 3, 0]]) b = np.array([[3, 1, 3, 5, 3], [3, 3, 2, 1, 2]]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([1, 1, 1, 1, 0], dtype=bool) ia_known = np.array([1, 0, 2, 1]) self.assertTrue(np.allclose(ma, ma_known)) self.assertTrue(np.allclose(ia, ia_known))
def test_ismember_rows_no_sort(self): a = np.array([[1, 3, 3, 1, 7], [3, 3, 2, 3, 0]]) b = np.array([[3, 1, 2, 5, 3], [3, 3, 3, 1, 1]]) ma, ia = setmembership.ismember_rows(a, b, sort=False) ma_known = np.array([1, 1, 0, 1, 0], dtype=bool) ia_known = np.array([1, 0, 1]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def cells_from_faces(g, fi): # Find cells of faces, specified by face indices fi. # It is assumed that fi is on the boundary, e.g. there is a single # cell for each element in fi. f, ci, _ = sps.find(g.cell_faces[fi]) assert f.size == fi.size, "We assume fi are boundary faces" ismem, ind_map = ismember_rows(fi, fi[f], sort=False) assert np.all(ismem) return ci[ind_map]
def test_ismember_rows_1d(self): a = np.array([0, 2, 1, 13, 0]) b = np.array([2, 4, 13, 0]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([1, 1, 0, 1, 1], dtype=bool) ia_known = np.array([3, 0, 2, 3]) self.assertTrue(np.allclose(ma, ma_known)) self.assertTrue(np.allclose(ia, ia_known))
def test_ismember_rows_unqual_sizes_1(self): # b larger than b a = np.array([[1, 3, 3, 1, 7], [3, 3, 2, 3, 0]]) b = np.array([[3, 1, 2, 5, 3, 4, 7], [3, 3, 3, 1, 9, 9, 9]]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([1, 1, 1, 1, 0], dtype=bool) ia_known = np.array([1, 0, 2, 1]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def test_ismember_rows_double_occurence_a_no_b(self): # There are duplicate occurences in a that are not found in b a = np.array([[1, 3, 3, 1, 7], [3, 3, 2, 3, 0]]) b = np.array([[3, 2, 5], [3, 3, 1]]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([0, 1, 1, 0, 0], dtype=bool) ia_known = np.array([0, 1]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def test_ismember_rows_double_occurence_a_and_b(self): # There are duplicate occurences in a, and the same item is found in b a = np.array([[1, 3, 3, 1, 7], [3, 3, 2, 3, 0]]) b = np.array([[3, 1, 2, 5, 3], [3, 3, 3, 1, 1]]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([1, 1, 1, 1, 0], dtype=bool) ia_known = np.array([1, 0, 2, 1]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def test_ismember_rows_1d(self): a = np.array([0, 2, 1, 3, 0]) b = np.array([2, 4, 3]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([0, 1, 0, 1, 0], dtype=bool) ia_known = np.array([0, 2]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def duplicate_without_dimension( self, dim: int ) -> Tuple["GridBucket", Dict[str, Dict]]: """ Remove all the nodes of dimension dim and add new edges between their neighbors by calls to remove_node. Parameters: dim (int): Dimension for which all grids should be removed. Returns: pp.GridBucket: Copy of this GridBucket, with all grids of dimension dim removed, and new edges between the neighbors of removed grids. Dict: Information on removed grids. Keys: "eliminated_nodes": List of all grids that were removed. "neighbours": List with neighbors of the eliminated grids. Sorted in the same order as eliminated_nodes. Node ordering is updated. "neigbours_old": Same as neighbours, but with the original node ordering. """ gb_copy = self.copy() grids_of_dim = gb_copy.grids_of_dimension(dim) grids_of_dim_old = self.grids_of_dimension(dim) # The node numbers are copied for each grid, so they can be used to # make sure we use the same grids (g and g_old) below. nn_new = [gb_copy.node_props(g, "node_number") for g in grids_of_dim] nn_old = [self.node_props(g, "node_number") for g in grids_of_dim_old] _, old_in_new = setmembership.ismember_rows( np.array(nn_new), np.array(nn_old), sort=False ) neighbours_dict = {} neighbours_dict_old = {} eliminated_nodes = {} for i, g in enumerate(grids_of_dim): # Eliminate the node and add new gb edges: neighbours = gb_copy.eliminate_node(g) # Keep track of which nodes were connected to each of the eliminated # nodes. Note that the key is the node number in the old gb, whereas # the neighbours lists refer to the copy grids. g_old = grids_of_dim_old[old_in_new[i]] neighbours_dict[i] = neighbours neighbours_dict_old[i] = self.node_neighbors(g_old) eliminated_nodes[i] = g_old elimination_data = { "neighbours": neighbours_dict, "neighbours_old": neighbours_dict_old, "eliminated_nodes": eliminated_nodes, } return gb_copy, elimination_data # type: ignore
def test_issue_123(self): # Reported bug #123. # Referred to an implementation of ismember which is now replaced. a = np.array([[0., 1., 1., 0., 0.2, 0.8, 0.5, 0.5, 0.5], [0., 0., 1., 1., 0.5, 0.5, 0.8, 0.2, 0.5]]) b = np.array([[0, 1, 1, 0], [0, 0, 1, 1]]) ma, ia = setmembership.ismember_rows(a, b) ma_known = np.array([1, 1, 1, 1, 0, 0, 0, 0, 0], dtype=bool) ia_known = np.array([0, 1, 2, 1]) assert np.allclose(ma, ma_known) assert np.allclose(ia, ia_known)
def target_2_source_nodes(self, g_src, g_trg): """ Find the local node mapping from a source grid to a target grid. target_2_source_nodes(..) returns the mapping from g_src -> g_trg such that g_trg.nodes[:, map] == g_src.nodes. E.g., if the target grid is the highest dim grid, target_2_source_nodes will equal the global node numbering. """ node_source = np.atleast_2d(g_src.global_point_ind) node_target = np.atleast_2d(g_trg.global_point_ind) _, trg_2_src_nodes = setmembership.ismember_rows( node_source.astype(np.int32), node_target.astype(np.int32)) return trg_2_src_nodes
def update_global_point_ind(grid_list, old_ind, new_ind): """Update global point indices in a list of grids. The method replaces indices in the attribute global_point_ind in the grid. The update is done in place. Parameters: grid_list (list of grids): Grids to be updated. old_ind (np.array): Old global indices, to be replaced. new_ind (np.array): New indices. """ for g in grid_list: ismem, o2n = ismember_rows(old_ind, g.global_point_ind) g.global_point_ind[o2n] = new_ind[ismem]
def obtain_interdim_mappings(lg, fn, n_per_face, ensure_matching_face_cell=True, **kwargs): """ Find mappings between faces in higher dimension and cells in the lower dimension Parameters: lg: Lower dimensional grid. fn: Higher dimensional face-node relation. n_per_face: Number of nodes per face in the higher-dimensional grid. ensure_matching_face_cell: Boolean, defaults to True. If True, an assertion is made that all lower-dimensional cells corresponds to a higher dimensional cell. """ if lg.dim > 0: cn_loc = lg.cell_nodes().indices.reshape((n_per_face, lg.num_cells), order='F') cn = lg.global_point_ind[cn_loc] cn = np.sort(cn, axis=0) else: cn = np.array([lg.global_point_ind]) # We also know that the higher-dimensional grid has faces # of a single node. This sometimes fails, so enforce it. if cn.ndim == 1: fn = fn.ravel() is_mem, cell_2_face = setmembership.ismember_rows(cn.astype(np.int32), fn.astype(np.int32), sort=False) # An element in cell_2_face gives, for all cells in the # lower-dimensional grid, the index of the corresponding face # in the higher-dimensional structure. if not (np.all(is_mem) or np.all(~is_mem)): if ensure_matching_face_cell: raise ValueError( '''Either all cells should have a corresponding face in a higher dim grid or no cells should have a corresponding face in a higher dim grid. This likely is related to gmsh behavior. ''') else: warnings.warn('''Found inconsistency between cells and higher dimensional faces. Continuing, fingers crossed''') low_dim_cell = np.where(is_mem)[0] return cell_2_face, low_dim_cell
def compare_discretizations( g_1, lhs_0, lhs_1, rhs_0, rhs_1, cell_maps, face_maps, phys="flow", fractured_mpsa=False, ): """ Assumes dofs sorted as cell_maps. Not neccessarily true for multiple fractures. """ if fractured_mpsa: # The dofs are at the cell centers dof_map_cells = fvutils.expand_indices_nd(cell_maps[0], g_1.dim) # and faces on either side of the fracture. Find the order of the g_1 # frac faces among the g_0 frac faces. frac_faces_loc = sm.ismember_rows(face_maps[0], g_1.frac_pairs.ravel("C"), sort=False)[1] # And expand to the dofs, one for each dimension for each face. For two # faces f0 and f1 in 3d, the order is # u(f0). v(f0), w(f0), u(f1). v(f1), w(f1) frac_indices = fvutils.expand_indices_nd(frac_faces_loc, g_1.dim) # Account for the cells frac_indices += g_1.num_cells * g_1.dim global_dof_map = np.concatenate((dof_map_cells, frac_indices)) elif phys == "mechanics": global_dof_map = fvutils.expand_indices_nd(cell_maps[0], g_1.dim) global_dof_map = np.array(global_dof_map, dtype=int) else: global_dof_map = np.concatenate( (cell_maps[0], cell_maps[1] + cell_maps[0].size)) mapped_lhs = lhs_1[global_dof_map][:, global_dof_map] assert np.isclose(np.sum(np.absolute(lhs_0 - mapped_lhs)), 0) assert np.all(np.isclose(rhs_0, rhs_1[global_dof_map]))
def duplicate_without_dimension(self, dim): """ Remove all the nodes of dimension dim and add new edges between their neighbors by calls to remove_node. """ gb_copy = self.copy() grids_of_dim = gb_copy.grids_of_dimension(dim) grids_of_dim_old = self.grids_of_dimension(dim) # The node numbers are copied for each grid, so they can be used to # make sure we use the same grids (g and g_old) below. nn_new = gb_copy.nodes_prop(grids_of_dim, 'node_number') nn_old = self.nodes_prop(grids_of_dim_old, 'node_number') _, old_in_new = setmembership.ismember_rows(np.array(nn_new), np.array(nn_old), sort=False) neighbours_dict = {} neighbours_dict_old = {} eliminated_nodes = {} for i, g in enumerate(grids_of_dim): # Eliminate the node and add new gb edges: neighbours = gb_copy.eliminate_node(g) # Keep track of which nodes were connected to each of the eliminated # nodes. Note that the key is the node number in the old gb, whereas # the neighbours lists refer to the copy grids. g_old = grids_of_dim_old[old_in_new[i]] neighbours_dict[i] = neighbours neighbours_dict_old[i] = self.node_neighbors(g_old) eliminated_nodes[i] = g_old elimination_data = { 'neighbours': neighbours_dict, 'neighbours_old': neighbours_dict_old, 'eliminated_nodes': eliminated_nodes } return gb_copy, elimination_data
def triangle_grid(points, edges, domain, constraints=None, **kwargs): """ Generate a gmsh grid in a 2D domain with fractures. The function uses modified versions of pygmsh and mesh_io, both downloaded from github. To be added: Functionality for tuning gmsh, including grid size, refinements, etc. Parameters ---------- fracs: (dictionary) Two fields: points (2 x num_points) np.ndarray, edges (2 x num_lines) connections between points, defines fractures. box: (dictionary) keys xmin, xmax, ymin, ymax, [together bounding box for the domain] constraints (np.array, optional): Index of edges that only act as constraints in meshing, but do not generate lower-dimensional grids. **kwargs: To be explored. Returns ------- list (length 3): For each dimension (2 -> 0), a list of all grids in that dimension. Examples p = np.array([[-1, 1, 0, 0], [0, 0, -1, 1]]) lines = np.array([[0, 2], [1, 3]]) char_h = 0.5 * np.ones(p.shape[1]) tags = np.array([1, 3]) fracs = {'points': p, 'edges': lines} box = {'xmin': -2, 'xmax': 2, 'ymin': -2, 'ymax': 2} g = triangle_grid(fracs, box) """ logger.info("Create 2d mesh") # Unified description of points and lines for domain, and fractures pts_all, lines, domain_pts = _merge_domain_fracs_2d( domain, points, edges, constraints) assert np.all(np.diff(lines[:2], axis=0) != 0) tol = kwargs.get("tol", 1e-4) pts_split, lines_split = _segment_2d_split(pts_all, lines, tol) # We find the end points that are shared by more than one intersection intersections = _find_intersection_points(lines_split) # Gridding size if "mesh_size_frac" in kwargs.keys(): # Tag points at the domain corners logger.info("Determine mesh size") tm = time.time() boundary_pt_ind = ismember_rows(pts_split, domain_pts, sort=False)[0] mesh_size, pts_split, lines_split = tools.determine_mesh_size( pts_split, boundary_pt_ind, lines_split, **kwargs) logger.info("Done. Elapsed time " + str(time.time() - tm)) else: mesh_size = None # gmsh options meshing_algorithm = kwargs.get("meshing_algorithm") # Create a writer of gmsh .geo-files gw = gmsh_interface.GmshWriter( pts_split, lines_split, domain=domain, mesh_size=mesh_size, intersection_points=intersections, meshing_algorithm=meshing_algorithm, ) # File name for communication with gmsh file_name = kwargs.get("file_name", "gmsh_frac_file") kwargs.pop("file_name", str()) in_file = file_name + ".geo" gw.write_geo(in_file) _run_gmsh(file_name, **kwargs) return triangle_grid_from_gmsh(file_name, **kwargs)
def test_merge_three_grids_no_common_point(self): # Merge three grids: One in the mid data = np.ones(3) rows = np.array([0, 1, 2]) cols = np.array([0, 0, 0]) cf_1 = sps.coo_matrix((data, (rows, cols))) data = np.ones(6) rows = np.array([0, 1, 2, 1, 3, 4]) cols = np.array([0, 0, 0, 1, 1, 1]) cf_2 = sps.coo_matrix((data, (rows, cols))) data = np.ones(6) rows = np.array([0, 1, 1, 2, 2, 0]) cols = np.array([0, 0, 1, 1, 2, 2]) fn_1 = sps.coo_matrix((data, (rows, cols))) data = np.ones(10) rows = np.array([0, 1, 1, 3, 3, 0, 1, 2, 2, 3]) cols = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) fn_2 = sps.coo_matrix((data, (rows, cols))) nodes_1 = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]]) nodes_2 = np.array([[0, 1, 0], [0, 0, -1], [0, 0, 0]]) nodes_3 = np.array([[0, 1, 0], [2, 1, 1], [0, 0, 0]]) # Middle grid, unit square divided into two. Will have neighbors on top and # bottom. g1 = MockGrid(2, num_faces=5, face_nodes=fn_2, cell_faces=cf_2, num_cells=2, nodes=nodes_1) # Neighbor on bottom g2 = MockGrid(2, num_faces=3, face_nodes=fn_1, cell_faces=cf_1, num_cells=1, nodes=nodes_2) # Neighbor on top. g3 = MockGrid(2, num_faces=3, face_nodes=fn_1, cell_faces=cf_1, num_cells=1, nodes=nodes_3) # Bottom 1d grid, as seen from g1 g_11 = TensorGrid(np.array([0, 1])) g_11.global_point_ind = np.arange(2) # Top 1d grid, as seen from g1 g_13 = TensorGrid(np.array([0, 1])) g_13.nodes = np.array([[0, 1], [1, 1], [0, 0]]) # Note global point indices here, in accordance with the ordering in # nodes_1 g_13.global_point_ind = np.array([2, 3]) # Bottom 1d grid, as seen from g2 g_22 = TensorGrid(np.array([0, 1])) g_22.global_point_ind = np.arange(2) # Top 1d grid, as seen from g3 g_33 = TensorGrid(np.array([1, 2])) g_33.nodes = np.array([[0, 1], [1, 1], [0, 0]]) # Global point indices, as ordered in nodes_3 g_33.global_point_ind = np.array([1, 2]) gl = [[[g1], [g_11, g_13]], [[g2], [g_22]], [[g3], [g_33]]] intersections = [np.array([1, 2]), np.array([0]), np.array([0])] list_of_grids, glob_ind = non_conforming.init_global_ind(gl) grid_list_1d = non_conforming.process_intersections(gl, intersections, glob_ind, list_of_grids, tol=1e-4) assert len(grid_list_1d) == 2 g_1d = grid_list_1d[0] ismem, maps = ismember_rows(g_1d.global_point_ind, g1.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g1.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, g2.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g2.nodes[:, maps], g_1d.nodes) g_1d = grid_list_1d[1] ismem, maps = ismember_rows(g_1d.global_point_ind, g1.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g1.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, g3.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g3.nodes[:, maps], g_1d.nodes)
def test_merge_three_grids_hanging_node_shared_node(self): # Merge three grids, where a central cell share one face each with the two # other. Importantly, one node will be involved in both shared faces. data = np.ones(10) rows = np.array([0, 1, 1, 2, 2, 3, 3, 0, 3, 1]) cols = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) fn_1 = sps.coo_matrix((data, (rows, cols))) data = np.ones(6) rows = np.array([0, 1, 1, 2, 2, 0]) cols = np.array([0, 0, 1, 1, 2, 2]) fn_2 = sps.coo_matrix((data, (rows, cols))) data = np.ones(6) rows = np.array([0, 4, 3, 1, 2, 4]) cols = np.array([0, 0, 0, 1, 1, 1]) cf_1 = sps.coo_matrix((data, (rows, cols))) data = np.ones(3) rows = np.array([0, 1, 2]) cols = np.array([0, 0, 0]) cf_2 = sps.coo_matrix((data, (rows, cols))) nodes_1 = np.array([[0, 1, 2, 1], [0, 0, 0, 1], [0, 0, 0, 0]]) nodes_2 = np.array([[0, 2, 1], [0, 0, -1], [0, 0, 0]]) nodes_3 = np.array([[0, 1, 0], [0, 1, 1], [0, 0, 0]]) # Central grid g1 = MockGrid(2, num_faces=5, face_nodes=fn_1, cell_faces=cf_1, num_cells=2, nodes=nodes_1) # First neighboring grid g2 = MockGrid(2, num_faces=3, face_nodes=fn_2, cell_faces=cf_2, num_cells=1, nodes=nodes_2) g3 = MockGrid(2, num_faces=3, face_nodes=fn_2, cell_faces=cf_2, num_cells=1, nodes=nodes_3) # First 1d grid, as seen from g1 g_11 = TensorGrid(np.array([0, 1, 2])) g_11.global_point_ind = np.arange(3) # Second 1d grid, as seen from g1 g_13 = TensorGrid(np.array([0, 1])) g_13.nodes = np.array([[0, 1], [0, 1], [0, 0]]) # Point indices adjusted according to ordering in nodes_1 g_13.global_point_ind = np.array([0, 3]) # First 1d grid, as seen from g2 g_22 = TensorGrid(np.array([0, 2])) g_22.global_point_ind = np.arange(2) # Second 1d grid, as seen from g3 g_33 = TensorGrid(np.array([0, 1])) g_33.nodes = np.array([[0, 1], [0, 1], [0, 0]]) g_33.global_point_ind = np.arange(2) gl = [[[g1], [g_11, g_13]], [[g2], [g_22]], [[g3], [g_33]]] intersections = [np.array([1, 2]), np.array([0]), np.array([0])] list_of_grids, glob_ind = non_conforming.init_global_ind(gl) grid_list_1d = non_conforming.process_intersections(gl, intersections, glob_ind, list_of_grids, tol=1e-4) assert len(grid_list_1d) == 2 g_1d = grid_list_1d[0] ismem, maps = ismember_rows(g_1d.global_point_ind, g1.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g1.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, g2.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g2.nodes[:, maps], g_1d.nodes) g_1d = grid_list_1d[1] ismem, maps = ismember_rows(g_1d.global_point_ind, g1.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g1.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, g3.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(g3.nodes[:, maps], g_1d.nodes)
def test_merge_three_grids_internal_intersection_no_hanging_node_reverse_order( self): # Merge three grids, where a central cell share one face each with the two # other. Importantly, one node will be involved in both shared faces. data = np.ones(16) rows = np.array([0, 1, 1, 2, 0, 3, 0, 4, 1, 3, 1, 4, 2, 3, 2, 4]) cols = np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]) fn = sps.coo_matrix((data, (rows, cols))) data = np.ones(12) rows = np.array([0, 5, 3, 1, 7, 5, 0, 2, 4, 1, 4, 6]) cols = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]) cf = sps.coo_matrix((data, (rows, cols))) nodes_1 = np.array([[-1, 0, 1, 0, 0], [0, 0, 0, -1, 1], [0, 0, 0, 0, 0]]) nodes_2 = np.array([[-1, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, -1, 1]]) nodes_3 = np.array([[0, 0, 0, 0, 0], [-1, 0, 1, 0, 0], [0, 0, 0, -1, 1]]) # Central grid gxy = MockGrid(2, num_faces=8, face_nodes=fn, cell_faces=cf, num_cells=4, nodes=nodes_1) # First neighboring grid gxz = MockGrid(2, num_faces=8, face_nodes=fn, cell_faces=cf, num_cells=4, nodes=nodes_2) gyz = MockGrid(2, num_faces=8, face_nodes=fn, cell_faces=cf, num_cells=4, nodes=nodes_3) # First 1d grid, as seen from g1 g_1x = TensorGrid(np.array([0, 1, 2])) g_1x.nodes = np.array([[1, 0, -1], [0, 0, 0], [0, 0, 0]]) g_1x.global_point_ind = np.array([2, 1, 0]) #g_1x.face_nodes.indices = np.array([1, 2, 0]) # Third 1d grid, as seen from g1 g_1y = TensorGrid(np.array([0, 1, 2])) g_1y.nodes = np.array([[0, 0, 0], [-1, 0, 1], [0, 0, 0]]) # Point indices adjusted according to ordering in nodes_1 g_1y.global_point_ind = np.array([3, 1, 4]) # First 1d grid, as seen from g2 g_2x = TensorGrid(np.array([0, 1, 2])) g_2x.nodes = np.array([[-1, 0, 1], [0, 0, 0], [0, 0, 0]]) g_2x.global_point_ind = np.arange(3) # Third 1d grid, as seen from g2 g_2z = TensorGrid(np.array([0, 1, 2])) g_2z.nodes = np.array([[0, 0, 0], [0, 0, 0], [-1, 0, 1]]) # Point indices adjusted according to ordering in nodes_1 g_2z.global_point_ind = np.array([3, 1, 4]) g_3y = TensorGrid(np.array([0, 1, 2])) g_3y.nodes = np.array([[0, 0, 0], [-1, 0, 1], [0, 0, 0]]) g_3y.global_point_ind = np.arange(3) # Second 1d grid, as seen from g1 g_3z = TensorGrid(np.array([0, 1, 2])) g_3z.nodes = np.array([[0, 0, 0], [0, 0, 0], [-1, 0, 1]]) # Point indices adjusted according to ordering in nodes_1 g_3z.global_point_ind = np.array([3, 1, 4]) gl = [[[gxy], [g_1x, g_1y]], [[gxz], [g_2x, g_2z]], [[gyz], [g_3y, g_3z]]] intersections = [np.array([1, 2]), np.array([0, 2]), np.array([0, 1])] list_of_grids, glob_ind = non_conforming.init_global_ind(gl) grid_list_1d = non_conforming.process_intersections(gl, intersections, glob_ind, list_of_grids, tol=1e-4) assert len(grid_list_1d) == 3 g_1d = grid_list_1d[0] ismem, maps = ismember_rows(g_1d.global_point_ind, gxy.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gxy.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, gxz.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gxz.nodes[:, maps], g_1d.nodes) g_1d = grid_list_1d[1] ismem, maps = ismember_rows(g_1d.global_point_ind, gxy.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gxy.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, gyz.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gyz.nodes[:, maps], g_1d.nodes) g_1d = grid_list_1d[2] ismem, maps = ismember_rows(g_1d.global_point_ind, gxz.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gxz.nodes[:, maps], g_1d.nodes) ismem, maps = ismember_rows(g_1d.global_point_ind, gyz.global_point_ind) assert ismem.sum() == g_1d.num_nodes assert np.allclose(gyz.nodes[:, maps], g_1d.nodes)
def update_nodes( g, g_1d, new_grid_1d, this_in_combined, sort_ind, global_ind_offset, list_of_grids ): """Update a 2d grid to conform to a new grid along a 1d line. Intended use: A 1d mesh that is embedded in a 2d mesh (along a fracture) has been updated / refined. This function then updates the node information in the 2d grid. Parameters: g (grid, dim==2): Main grid to update. Has faces along a fracture. g_1d (grid, dim==1): Original line grid along the fracture. new_grid_1d (grid, dim==1): New line grid, formed by merging two coinciding, but non-matching grids along a fracture. this_in_combined (np.ndarray): Which nodes in the new grid are also in the old one, as returned by merge_1d_grids(). sort_ind (np.ndarray): Sorting indices of the coordinates of the old grid, as returned by merge_1d_grids(). list_of_grids (list): Grids for all dimensions. Returns: """ nodes_per_face = 2 # Face-node relation for the grid, in terms of local and global indices fn = g.face_nodes.indices.reshape((nodes_per_face, g.num_faces), order="F") fn_glob = np.sort(g.global_point_ind[fn], axis=0) # Mappings between faces in 2d grid and cells in 1d # 2d faces along the 1d grid will be deleted. delete_faces, cell_1d = fractools.obtain_interdim_mappings( g_1d, fn_glob, nodes_per_face ) # All 1d cells should be identified with 2d faces assert ( cell_1d.size == g_1d.num_cells ), """ Failed to find mapping between 1d cells and 2d faces""" # The nodes of identified faces on the 2d grid will be deleted delete_nodes = np.unique(fn[:, delete_faces]) # Nodes to be added will have indicies towards the end num_nodes_orig = g.num_nodes num_delete_nodes = delete_nodes.size # Define indices of new nodes. new_nodes = num_nodes_orig - delete_nodes.size + np.arange(new_grid_1d.num_nodes) # Adjust node indices in the face-node relation # First, map nodes between 1d and 2d grids. Use sort_ind here to map # indices of g_1d to the same order as the new grid _, node_map_1d_2d = ismember_rows( g.global_point_ind[delete_nodes], g_1d.global_point_ind ) tmp = np.arange(g.num_nodes) adjustment = np.zeros_like(tmp) adjustment[delete_nodes] = 1 node_adjustment = tmp - np.cumsum(adjustment) # Nodes along the 1d grid are deleted and inserted again. Let the # adjutsment point to the restored nodes. # node_map_1d_2d maps from ordering in delete_nodes to ordering of 1d # points (old_grid). this_in_combined then maps further to the ordering of # the new 1d grid node_adjustment[delete_nodes] = ( g.num_nodes - num_delete_nodes + this_in_combined[node_map_1d_2d] ) g.face_nodes.indices = node_adjustment[g.face_nodes.indices] # Update node coordinates and global indices for 2d mesh g.nodes = np.hstack((g.nodes, new_grid_1d.nodes)) new_global_points = new_grid_1d.global_point_ind g.global_point_ind = np.append(g.global_point_ind, new_global_points) # Global index of deleted points old_global_pts = g.global_point_ind[delete_nodes] # Update any occurences of the old points in other grids. When sewing # together a DFN grid, this may involve more and more updates as common # nodes are found along intersections. # The new grid should also be added to the list, if it is not there before if new_grid_1d not in list_of_grids: list_of_grids.append(new_grid_1d) update_global_point_ind( list_of_grids, old_global_pts, new_global_points[this_in_combined[node_map_1d_2d]], ) # Delete old nodes g.nodes = np.delete(g.nodes, delete_nodes, axis=1) g.global_point_ind = np.delete(g.global_point_ind, delete_nodes) g.num_nodes = g.nodes.shape[1] return new_nodes, delete_faces, global_ind_offset
def merge_1d_grids(g, h, global_ind_offset=0, tol=1e-4): """Merge two 1d grids with non-matching nodes to a single grid. The grids should have common start and endpoints. They can be into 3d space in a genreal way. The function is primarily intended for merging non-conforming DFN grids. Parameters: g: 1d tensor grid. h: 1d tensor grid glob_ind_offset (int, defaults to 0): Off set for the global point index of the new grid. tol (double, defaults to 1e-4): Tolerance for when two nodes are merged into one. Returns: TensorGrid: New tensor grid, containing the combined node definition. int: New global ind offset, increased by the number of cells in the combined grid. np.array (int): Indices of common nodes (after sorting) of g and the new grid. np.array (int): Indices of common nodes (after sorting) of h and the new grid. np.array (int): Permutation indices that sort the node coordinates of g. The common indices between g and the new grid are found as new_grid.nodes[:, g_in_combined] = g.nodes[:, sorted] np.array (int): Permutation indices that sort the node coordinates of h. The common indices between h and the new grid are found as new_grid.nodes[:, h_in_combined] = h.nodes[:, sorted] """ # Nodes of the two 1d grids, combine them gp = g.nodes hp = h.nodes combined = np.hstack((gp, hp)) num_g = gp.shape[1] num_h = hp.shape[1] # Keep track of where we put the indices of the original grids g_in_full = np.arange(num_g) h_in_full = num_g + np.arange(num_h) # The tolerance should not be larger than the smallest distance between # two points on any of the grids. diff_gp = np.min(pp.distances.pointset(gp, True)) diff_hp = np.min(pp.distances.pointset(hp, True)) min_diff = np.minimum(tol, 0.5 * np.minimum(diff_gp, diff_hp)) # Uniquify points combined_unique, _, new_2_old = unique_columns_tol(combined, tol=min_diff) # Follow locations of the original grid points g_in_unique = new_2_old[g_in_full] h_in_unique = new_2_old[h_in_full] # The combined nodes must be sorted along their natural line. # Find the dimension with the largest spatial extension, and sort those # coordinates max_coord = combined_unique.max(axis=1) min_coord = combined_unique.min(axis=1) dx = max_coord - min_coord sort_dim = np.argmax(dx) sort_ind = np.argsort(combined_unique[sort_dim]) combined_sorted = combined_unique[:, sort_ind] # Follow the position of the orginial nodes through sorting _, g_sorted = ismember_rows(g_in_unique, sort_ind) _, h_sorted = ismember_rows(h_in_unique, sort_ind) num_new_grid = combined_sorted.shape[1] # Create a new 1d grid. # First use a 1d coordinate to initialize topology new_grid = pp.TensorGrid(np.arange(num_new_grid)) # Then set the right, 3d coordinates new_grid.nodes = pp.map_geometry.force_point_collinearity(combined_sorted) # Set global point indices new_grid.global_point_ind = global_ind_offset + np.arange(num_new_grid) global_ind_offset += num_new_grid return ( new_grid, global_ind_offset, g_sorted, h_sorted, np.arange(num_g), np.arange(num_h), )
def check_equivalent_buckets(buckets, decimals=12): """ Checks agreement between number of cells, faces and nodes, their coordinates and the connectivity matrices cell_faces and face_nodes. Also checks the face tags. """ dim_h = buckets[0].dim_max() dim_l = dim_h - 1 num_buckets = len(buckets) cell_maps_h, face_maps_h = [], [] cell_maps_l, face_maps_l = num_buckets * [{}], num_buckets * [{}] # Check that all buckets have the same number of grids in the lower dimension num_grids_l: int = len(buckets[0].grids_of_dimension(dim_h - 1)) for bucket in buckets: assert len(bucket.grids_of_dimension(dim_h - 1)) == num_grids_l for d in range(dim_l, dim_h + 1): for target_grid in range(len(buckets[0].grids_of_dimension(d))): n_cells, n_faces, n_nodes = np.empty(0), np.empty(0), np.empty(0) nodes, face_centers, cell_centers = [], [], [] cell_faces, face_nodes = [], [] for bucket in buckets: g = bucket.grids_of_dimension(d)[target_grid] n_cells = np.append(n_cells, g.num_cells) n_faces = np.append(n_faces, g.num_faces) n_nodes = np.append(n_nodes, g.num_nodes) cell_faces.append(g.cell_faces) face_nodes.append(g.face_nodes) cell_centers.append(g.cell_centers) face_centers.append(g.face_centers) nodes.append(g.nodes) # Check that all buckets have the same number of cells, faces and nodes assert np.unique(n_cells).size == 1 assert np.unique(n_faces).size == 1 assert np.unique(n_nodes).size == 1 # Check that the coordinates agree cell_centers = np.round(cell_centers, decimals) nodes = np.round(nodes, decimals) face_centers = np.round(face_centers, decimals) for i in range(1, num_buckets): assert np.all( sm.ismember_rows(cell_centers[0], cell_centers[i])[0]) assert np.all( sm.ismember_rows(face_centers[0], face_centers[i])[0]) assert np.all(sm.ismember_rows(nodes[0], nodes[i])[0]) # Now we know all nodes, faces and cells are in all grids, we map them # to prepare cell_faces and face_nodes comparison g_0 = buckets[0].grids_of_dimension(d)[target_grid] for i in range(1, num_buckets): bucket = buckets[i] g = bucket.grids_of_dimension(d)[target_grid] cell_map, face_map, node_map = make_maps( g_0, g, bucket.dim_max()) mapped_cf = g.cell_faces[face_map][:, cell_map] mapped_fn = g.face_nodes[node_map][:, face_map] assert np.sum(np.abs(g_0.cell_faces) != np.abs(mapped_cf)) == 0 assert np.sum(np.abs(g_0.face_nodes) != np.abs(mapped_fn)) == 0 if g.dim == dim_h: face_maps_h.append(face_map) cell_maps_h.append(cell_map) else: cell_maps_l[i][g] = cell_map face_maps_l[i][g] = face_map # Also loop on the standard face tags to check that they are # identical between the buckets. tag_keys = tags.standard_face_tags() for key in tag_keys: assert np.all( np.isclose(g_0.tags[key], g.tags[key][face_map])) # Mortar grids g_h_0 = buckets[0].grids_of_dimension(dim_h)[0] for target_grid in range(len(buckets[0].grids_of_dimension(dim_l))): g_l_0 = buckets[0].grids_of_dimension(dim_l)[target_grid] mg_0 = buckets[0].edge_props((g_h_0, g_l_0), "mortar_grid") proj_0 = mg_0.primary_to_mortar_int() for i in range(1, num_buckets): g_l_i = buckets[i].grids_of_dimension(dim_l)[target_grid] g_h_i = buckets[i].grids_of_dimension(dim_h)[0] mg_i = buckets[i].edge_props((g_h_i, g_l_i), "mortar_grid") proj_i = mg_i.primary_to_mortar_int() cm = cell_maps_l[i][g_l_i] cm_extended = np.append(cm, cm + cm.size) fm = face_maps_h[i - 1] mapped_fc = proj_i[cm_extended, :][:, fm] assert np.sum(np.absolute(proj_0) - np.absolute(mapped_fc)) == 0 return cell_maps_h, cell_maps_l, face_maps_h, face_maps_l
def split_fractures(bucket, **kwargs): """ Wrapper function to split all fractures. For each grid in the bucket, we locate the corresponding lower-dimensional grids. The faces and nodes corresponding to these grids are then split, creating internal boundaries. Parameters ---------- bucket - A grid bucket **kwargs: offset - FLOAT, defaults to 0. Will perturb the nodes around the faces that are split. NOTE: this is only for visualization. E.g., the face centers are not perturbed. Returns ------- bucket - A valid bucket where the faces are split at internal boundaries. Examples >>> import numpy as np >>> from gridding.fractured import meshing, split_grid >>> from viz.exporter import export_vtk >>> >>> f_1 = np.array([[-1, 1, 1, -1 ], [0, 0, 0, 0], [-1, -1, 1, 1]]) >>> f_2 = np.array([[0, 0, 0, 0], [-1, 1, 1, -1 ], [-.7, -.7, .8, .8]]) >>> f_set = [f_1, f_2] >>> domain = {'xmin': -2, 'xmax': 2, 'ymin': -2, 'ymax': 2, 'zmin': -2, 'zmax': 2} >>> bucket = meshing.create_grid(f_set, domain) >>> [g.compute_geometry() for g,_ in bucket] >>> >>> split_grid.split_fractures(bucket, offset=0.1) >>> export_vtk(bucket, "grid") """ offset = kwargs.get("offset", 0) # For each vertex in the bucket we find the corresponding lower- # dimensional grids. for gh, _ in bucket: # add new field to grid gh.frac_pairs = np.zeros((2, 0), dtype=np.int32) if gh.dim < 1: # Nothing to do. We can not split 0D grids. continue # Find connected vertices and corresponding edges. neigh = np.array(bucket.node_neighbors(gh)) # Find the neighbours that are lower dimensional is_low_dim_grid = np.where([w.dim < gh.dim for w in neigh]) edges = [(gh, w) for w in neigh[is_low_dim_grid]] if len(edges) == 0: # No lower dim grid. Nothing to do. continue face_cells = [bucket.edge_props(e, "face_cells") for e in edges] # We split all the faces that are connected to a lower-dim grid. # The new faces will share the same nodes and properties (normals, # etc.) face_cells = split_faces(gh, face_cells) for e, f in zip(edges, face_cells): bucket.add_edge_props("face_cells", [e]) bucket.edge_props(e)["face_cells"] = f # We now find which lower-dim nodes correspond to which higher- # dim nodes. We split these nodes according to the topology of # the connected higher-dim cells. At a X-intersection we split # the node into four, while at the fracture boundary it is not split. gl = [e[1] for e in edges] gl_2_gh_nodes = [] for g in gl: source = np.atleast_2d(g.global_point_ind).astype(np.int32) target = np.atleast_2d(gh.global_point_ind).astype(np.int32) _, mapping = setmembership.ismember_rows(source, target) gl_2_gh_nodes.append(mapping) split_nodes(gh, gl, gl_2_gh_nodes, offset) # Remove zeros from cell_faces [g.cell_faces.eliminate_zeros() for g, _ in bucket] [g.update_boundary_node_tag() for g, _ in bucket] return bucket
def triangle_grid(fracs, domain, do_snap_to_grid=False, **kwargs): """ Generate a gmsh grid in a 2D domain with fractures. The function uses modified versions of pygmsh and mesh_io, both downloaded from github. To be added: Functionality for tuning gmsh, including grid size, refinements, etc. Parameters ---------- fracs: (dictionary) Two fields: points (2 x num_points) np.ndarray, edges (2 x num_lines) connections between points, defines fractures. box: (dictionary) keys xmin, xmax, ymin, ymax, [together bounding box for the domain] do_snap_to_grid (boolean, optional): If true, points are snapped to an underlying Cartesian grid with resolution tol before geometry computations are carried out. This used to be the standard, but indications are it is better not to do this. This keyword construct is a stop-gap measure to invoke the old functionality if desired. This option will most likely dissapear in the future. **kwargs: To be explored. Returns ------- list (length 3): For each dimension (2 -> 0), a list of all grids in that dimension. Examples p = np.array([[-1, 1, 0, 0], [0, 0, -1, 1]]) lines = np.array([[0, 2], [1, 3]]) char_h = 0.5 * np.ones(p.shape[1]) tags = np.array([1, 3]) fracs = {'points': p, 'edges': lines} box = {'xmin': -2, 'xmax': 2, 'ymin': -2, 'ymax': 2} g = triangle_grid(fracs, box) """ logger.info("Create 2d mesh") # Verbosity level verbose = kwargs.get("verbose", 1) # File name for communication with gmsh file_name = kwargs.get("file_name", "gmsh_frac_file") kwargs.pop("file_name", str()) tol = kwargs.get("tol", 1e-4) in_file = file_name + ".geo" out_file = file_name + ".msh" # Pick out fracture points, and their connections frac_pts = fracs["points"] frac_con = fracs["edges"] # Unified description of points and lines for domain, and fractures pts_all, lines, domain_pts = __merge_domain_fracs_2d( domain, frac_pts, frac_con) # Snap to underlying grid before comparing points if do_snap_to_grid: pts_all = cg.snap_to_grid(pts_all, tol) assert np.all(np.diff(lines[:2], axis=0) != 0) # Ensure unique description of points pts_all, _, old_2_new = unique_columns_tol(pts_all, tol=tol) lines[:2] = old_2_new[lines[:2]] to_remove = np.where(lines[0, :] == lines[1, :])[0] lines = np.delete(lines, to_remove, axis=1) # In some cases the fractures and boundaries impose the same constraint # twice, although it is not clear why. Avoid this by uniquifying the lines. # This may disturb the line tags in lines[2], but we should not be # dependent on those. li = np.sort(lines[:2], axis=0) _, new_2_old, old_2_new = unique_columns_tol(li, tol=tol) lines = lines[:, new_2_old] assert np.all(np.diff(lines[:2], axis=0) != 0) # We split all fracture intersections so that the new lines do not # intersect, except possible at the end points logger.info("Remove edge crossings") tm = time.time() pts_split, lines_split = cg.remove_edge_crossings(pts_all, lines, tol=tol, snap=do_snap_to_grid) logger.info("Done. Elapsed time " + str(time.time() - tm)) # Ensure unique description of points if do_snap_to_grid: pts_split = cg.snap_to_grid(pts_split, tol) pts_split, _, old_2_new = unique_columns_tol(pts_split, tol=tol) lines_split[:2] = old_2_new[lines_split[:2]] to_remove = np.where(lines[0, :] == lines[1, :])[0] lines = np.delete(lines, to_remove, axis=1) # Remove lines with the same start and end-point. # This can be caused by L-intersections, or possibly also if the two # endpoints are considered equal under tolerance tol. remove_line_ind = np.where(np.diff(lines_split[:2], axis=0)[0] == 0)[0] lines_split = np.delete(lines_split, remove_line_ind, axis=1) # TODO: This operation may leave points that are not referenced by any # lines. We should probably delete these. # We find the end points that are shared by more than one intersection intersections = __find_intersection_points(lines_split) # Gridding size if "mesh_size_frac" in kwargs.keys(): # Tag points at the domain corners logger.info("Determine mesh size") tm = time.time() boundary_pt_ind = ismember_rows(pts_split, domain_pts, sort=False)[0] mesh_size, pts_split, lines_split = tools.determine_mesh_size( pts_split, boundary_pt_ind, lines_split, **kwargs) logger.info("Done. Elapsed time " + str(time.time() - tm)) else: mesh_size = None # gmsh options meshing_algorithm = kwargs.get("meshing_algorithm") # Create a writer of gmsh .geo-files gw = gmsh_interface.GmshWriter( pts_split, lines_split, domain=domain, mesh_size=mesh_size, intersection_points=intersections, meshing_algorithm=meshing_algorithm, ) gw.write_geo(in_file) triangle_grid_run_gmsh(file_name, **kwargs) return triangle_grid_from_gmsh(file_name, **kwargs)