def test_merge_two_grids(self): """ Test coupling from one grid of three faces to grid of two faces. An example setting: 0 1 2 |--|--| ( left grid) | (left coupling) * (mortar_grid \ (right coupling) |--| (right grid) 0 1 """ face_faces = np.array([[0, 0, 0], [0, 1, 0]]) face_faces = sps.csc_matrix(face_faces) left_side = pp.PointGrid(np.array([2, 0, 0]).T) left_side.compute_geometry() mg = pp.grids.mortar_grid.BoundaryMortar(0, left_side, face_faces) self.assertTrue(mg.num_cells == 1) self.assertTrue(mg.num_sides() == 1) self.assertTrue(np.all(mg.master_to_mortar_avg().A == [0, 1, 0])) self.assertTrue(np.all(mg.master_to_mortar_int().A == [0, 1, 0])) self.assertTrue(np.all(mg.slave_to_mortar_avg().A == [0, 1])) self.assertTrue(np.all(mg.slave_to_mortar_int().A == [0, 1]))
def test_merge_single_grid(self): """ Test coupling from one grid to itself. An example setting: |--|--|--| ( grid ) 0 1 2 3 (left_coupling) \ / (right coupling) \ / * (mortar grid) with a coupling from the left face (0) to the right face (1). The mortar grid will just be a point """ face_faces = np.array([[0, 0, 0], [0, 0, 0], [1, 0, 0]]) face_faces = sps.csc_matrix(face_faces) left_side = pp.PointGrid(np.array([0, 0, 0]).T) left_side.compute_geometry() mg = pp.grids.mortar_grid.BoundaryMortar(0, left_side, face_faces) self.assertTrue(mg.num_cells == 1) self.assertTrue(mg.num_sides() == 1) self.assertTrue(np.all(mg.master_to_mortar_avg().A == [1, 0, 0])) self.assertTrue(np.all(mg.master_to_mortar_int().A == [1, 0, 0])) self.assertTrue(np.all(mg.slave_to_mortar_avg().A == [0, 0, 1])) self.assertTrue(np.all(mg.slave_to_mortar_int().A == [0, 0, 1]))
def import_grid_0d(folder, fname, **kwargs): # load the node fnodes_root = kwargs.get("nodes", "InterCellsCoord") fnodes = folder + "/" + fnodes_root + fname + ".txt" nodes = np.loadtxt(fnodes, dtype=np.float) nodes = np.atleast_2d(nodes).T nodes = np.vstack([nodes, np.zeros(nodes.shape[1])]) name = "PointGrid" g = pp.PointGrid(nodes, name) g.compute_geometry() # load the point to local to global map fglobal_root = kwargs.get("global", "InterCellsGlobalID") fglobal = folder + "/" + fglobal_root + fname + ".txt" g.global_point_ind = np.loadtxt(fglobal, dtype=np.int).T return g
def test_0d(self): g = pp.PointGrid(np.zeros((3, 1))) z = np.arange(2) gn, *rest = pp.grid_extrusion.extrude_grid(g, z) self.assertTrue(gn.dim == 1)
def setUp(self, x=0, y=0): c = np.array([x, y, 0]).reshape((-1, 1)) self.g = pp.PointGrid(c)
def __extract_cells_from_faces_1d(g, f): assert np.size(f) == 1 node = np.argwhere(g.face_nodes[:, f])[:, 0] h = pp.PointGrid(g.nodes[:, node]) h.compute_geometry() return h, f, node
def create_0d_grids( pts: np.ndarray, cells: Dict[str, np.ndarray], phys_names: Dict[int, str], cell_info: Dict[str, np.ndarray], target_tag_stem: str = None, ) -> List[pp.PointGrid]: """Create 0d grids for points of a specified type from a gmsh tessalation. Only points that were defined as 'physical' in the gmsh sense may have a grid created, but then only if the physical name matches specified target_tag_stem. It is assumed that the mesh is read by meshio. See porepy.fracs.simplex for how to do this. Parameters: pts (np.ndarray, npt x 3): Global point set from gmsh cells (dict): Should have a key vertex, which maps to a np.ndarray if indices of the points that form point grids. phys_names (dict): mapping from the gmsh tags assigned to physical entities to the physical name of that tag. cell_info (dictionary): Should have a key 'vertex', that contains the physical names (in the gmsh sense) of the points. target_tag_stem (str, optional): The target physical name, all points that have this tag will be assigned a grid. The string is assumed to be on the from BASE_NAME_OF_TAG_{INDEX}, where _INDEX is a number. The comparison is made between the physical names and the target_tag_stem, up to the last underscore. If not provided, the physical names of fracture points will be used as target. Returns: list of grids: List of 0d grids for all physical points that matched with the specified target tag. """ if target_tag_stem is None: target_tag_stem = PhysicalNames.FRACTURE_INTERSECTION_POINT.value g_0d = [] if "vertex" in cells: # Index (in the array pts) of the points that are specified as physical in the # .geo-file point_cells = cells["vertex"].ravel() # Keys to the physical names table of the points that have been decleared as # physical physical_name_indices = cell_info["vertex"] # Loop over all physical points for pi, phys_names_ind in enumerate(physical_name_indices): pn = phys_names[phys_names_ind] offset_index = pn.rfind("_") phys_name_vertex = pn[:offset_index] # Check if this is the target. The -1 is needed to avoid the extra _ in # the defined constantnt if phys_name_vertex == target_tag_stem[:-1]: # This should be a new grid g = pp.PointGrid(pts[point_cells[pi]]) g.global_point_ind = np.atleast_1d(np.asarray(point_cells[pi])) # Store the index of this physical name tag. g._physical_name_index = int(pn[offset_index + 1:]) g_0d.append(g) else: continue return g_0d
def _cart_grid_2d(fracs, nx, physdims=None): """ Create grids for a domain with possibly intersecting fractures in 2d. Based on lines describing the individual fractures, the method constructs grids in 2d (whole domain), 1d (individual fracture), and 0d (fracture intersections). Parameters ---------- fracs (list of np.ndarray, each 2x2): Vertexes of the line for each fracture. The fracture lines must align to the coordinat axis. The fractures will snap to the closest grid nodes. nx (np.ndarray): Number of cells in each direction. Should be 2D. physdims (np.ndarray): Physical dimensions in each direction. Defaults to same as nx, that is, cells of unit size. Returns ------- list (length 3): For each dimension (2 -> 0), a list of all grids in that dimension. Examples -------- frac1 = np.array([[1, 4], [2, 2]]) frac2 = np.array([[2, 2], [1, 4]]) fracs = [frac1, frac2] gb = cart_grid_2d(fracs, [5, 5]) """ nx = np.asarray(nx) if physdims is None: physdims = nx elif np.asarray(physdims).size != nx.size: raise ValueError("Physical dimension must equal grid dimension") else: physdims = np.asarray(physdims) g_2d = pp.CartGrid(nx, physdims=physdims) g_2d.global_point_ind = np.arange(g_2d.num_nodes) g_2d.compute_geometry() g_1d = [] g_0d = [] # 1D grids: shared_nodes = np.zeros(g_2d.num_nodes) for f in fracs: is_x_frac = f[1, 0] == f[1, 1] is_y_frac = f[0, 0] == f[0, 1] assert is_x_frac != is_y_frac, "Fracture must align to x- or y-axis" if f.shape[0] == 2: f = np.vstack((f, np.zeros(f.shape[1]))) nodes = _find_nodes_on_line(g_2d, nx, f[:, 0], f[:, 1]) # nodes = np.unique(nodes) loc_coord = g_2d.nodes[:, nodes] g = mesh_2_grid.create_embedded_line_grid(loc_coord, nodes) g_1d.append(g) shared_nodes[nodes] += 1 # Create 0-D grids if np.any(shared_nodes > 1): for global_node in np.argwhere(shared_nodes > 1).ravel(): g = pp.PointGrid(g_2d.nodes[:, global_node]) g.global_point_ind = np.asarray(global_node) g_0d.append(g) grids = [[g_2d], g_1d, g_0d] return grids
def _cart_grid_3d(fracs, nx, physdims=None): """ Create grids for a domain with possibly intersecting fractures in 3d. Based on rectangles describing the individual fractures, the method constructs grids in 3d (the whole domain), 2d (one for each individual fracture), 1d (along fracture intersections), and 0d (meeting between intersections). Parameters ---------- fracs (list of np.ndarray, each 3x4): Vertexes in the rectangle for each fracture. The vertices must be sorted and aligned to the axis. The fractures will snap to the closest grid faces. nx (np.ndarray): Number of cells in each direction. Should be 3D. physdims (np.ndarray): Physical dimensions in each direction. Defaults to same as nx, that is, cells of unit size. Returns ------- list (length 4): For each dimension (3 -> 0), a list of all grids in that dimension. Examples -------- frac1 = np.array([[1, 1, 4, 4], [1, 4, 4, 1], [2, 2, 2, 2]]) frac2 = np.array([[2, 2, 2, 2], [1, 1, 4, 4], [1, 4, 4, 1]]) fracs = [frac1, frac2] gb = cart_grid_3d(fracs, [5, 5, 5]) """ nx = np.asarray(nx) if physdims is None: physdims = nx elif np.asarray(physdims).size != nx.size: raise ValueError("Physical dimension must equal grid dimension") else: physdims = np.asarray(physdims) # We create a 3D cartesian grid. The global node mapping is trivial. g_3d = pp.CartGrid(nx, physdims=physdims) g_3d.global_point_ind = np.arange(g_3d.num_nodes) g_3d.compute_geometry() g_2d = [] g_1d = [] g_0d = [] # We set the tolerance for finding points in a plane. This can be any # small number, that is smaller than .25 of the cell sizes. tol = 0.1 * physdims / nx # Create 2D grids for fi, f in enumerate(fracs): assert np.all(f.shape == (3, 4)), "fractures must have shape [3,4]" is_xy_frac = np.allclose(f[2, 0], f[2]) is_xz_frac = np.allclose(f[1, 0], f[1]) is_yz_frac = np.allclose(f[0, 0], f[0]) assert (is_xy_frac + is_xz_frac + is_yz_frac == 1), "Fracture must align to x-, y- or z-axis" # snap to grid f_s = (np.round(f * nx[:, np.newaxis] / physdims[:, np.newaxis]) * physdims[:, np.newaxis] / nx[:, np.newaxis]) if is_xy_frac: flat_dim = [2] active_dim = [0, 1] elif is_xz_frac: flat_dim = [1] active_dim = [0, 2] else: flat_dim = [0] active_dim = [1, 2] # construct normal vectors. If the rectangle is ordered # clockwise we need to flip the normals so they point # outwards. sign = 2 * pp.geometry_property_checks.is_ccw_polygon( f_s[active_dim]) - 1 tangent = f_s.take(np.arange(f_s.shape[1]) + 1, axis=1, mode="wrap") - f_s normal = tangent normal[active_dim] = tangent[active_dim[1::-1]] normal[active_dim[1]] = -normal[active_dim[1]] normal = sign * normal # We find all the faces inside the convex hull defined by the # rectangle. To find the faces on the fracture plane, we remove any # faces that are further than tol from the snapped fracture plane. in_hull = pp.utils.half_space.half_space_int(normal, f_s, g_3d.face_centers) f_tag = np.logical_and( in_hull, np.logical_and( f_s[flat_dim, 0] - tol[flat_dim] <= g_3d.face_centers[flat_dim], g_3d.face_centers[flat_dim] < f_s[flat_dim, 0] + tol[flat_dim], ), ) f_tag = f_tag.ravel() nodes = sps.find(g_3d.face_nodes[:, f_tag])[0] nodes = np.unique(nodes) loc_coord = g_3d.nodes[:, nodes] g = _create_embedded_2d_grid(loc_coord, nodes) g.frac_num = fi g_2d.append(g) # Create 1D grids: # Here we make use of the network class to find the intersection of # fracture planes. We could maybe avoid this by doing something similar # as for the 2D-case, and count the number of faces belonging to each edge, # but we use the FractureNetwork class for now. frac_list = [] for f in fracs: frac_list.append(pp.Fracture(f)) # Combine the fractures into a network network = pp.FractureNetwork3d(frac_list) # Impose domain boundary. For the moment, the network should be immersed in # the domain, or else gmsh will complain. box = { "xmin": 0, "ymin": 0, "zmin": 0, "xmax": physdims[0], "ymax": physdims[1], "zmax": physdims[2], } network.impose_external_boundary(box) # Find intersections and split them. network.find_intersections() network.split_intersections() # Extract geometrical network information. pts = network.decomposition["points"] edges = network.decomposition["edges"] poly = network._poly_2_segment() # And tags identifying points and edges corresponding to normal # fractures, domain boundaries and subdomain boundaries. Only the # entities corresponding to normal fractures should actually be gridded. # TODO: Constraints have not been implemented for structured DFM grids. # Simply pass nothing for now, not sure how do deal with this, or if it at all is # meaningful. edge_tags, _, _ = network._classify_edges(poly, []) const = constants.GmshConstants() auxiliary_points, edge_tags = network._on_domain_boundary(edges, edge_tags) bound_and_aux = np.array([const.DOMAIN_BOUNDARY_TAG, const.AUXILIARY_TAG]) # From information of which lines are internal, we can find intersection points. # This part will become more elaborate if we introduce constraints, see the # FractureNetwork3d class. # Find all points on fracture intersection lines isect_p = edges[:, edge_tags == const.FRACTURE_INTERSECTION_LINE_TAG].ravel() # Count the number of occurences num_occ_pt = np.bincount(isect_p) # Intersection poitns if intersection_points = np.where(num_occ_pt > 1)[0] edges = np.vstack((edges, edge_tags)) # Loop through the edges to make 1D grids. Ommit the auxiliary edges. for e in np.ravel( np.where(edges[2] == const.FRACTURE_INTERSECTION_LINE_TAG)): # We find the start and end point of each fracture intersection (1D # grid) and then the corresponding global node index. if np.isin(edge_tags[e], bound_and_aux): continue s_pt = pts[:, edges[0, e]] e_pt = pts[:, edges[1, e]] nodes = _find_nodes_on_line(g_3d, nx, s_pt, e_pt) loc_coord = g_3d.nodes[:, nodes] assert (loc_coord.shape[1] > 1), "1d grid in intersection should span\ more than one node" g = mesh_2_grid.create_embedded_line_grid(loc_coord, nodes) g_1d.append(g) # Create 0D grids # Here we also use the intersection information from the FractureNetwork # class. No grids for auxiliary points. for p in intersection_points: if auxiliary_points[p] == const.DOMAIN_BOUNDARY_TAG: continue node = np.argmin(pp.distances.point_pointset(pts[:, p], g_3d.nodes)) assert np.allclose(g_3d.nodes[:, node], pts[:, p]) g = pp.PointGrid(g_3d.nodes[:, node]) g.global_point_ind = np.asarray(node) g_0d.append(g) grids = [[g_3d], g_2d, g_1d, g_0d] return grids
left_side.compute_geometry() mg = pp.grids.mortar_grid.MortarGrid(0, {"0": left_side}, face_faces) self.assertTrue(mg.num_cells == 1) self.assertTrue(mg.num_sides() == 1) self.assertTrue(np.all(mg.primary_to_mortar_avg().A == [0, 1, 0])) self.assertTrue(np.all(mg.primary_to_mortar_int().A == [0, 1, 0])) self.assertTrue(np.all(mg.secondary_to_mortar_avg().A == [0, 1])) self.assertTrue(np.all(mg.secondary_to_mortar_int().A == [0, 1])) @pytest.mark.parametrize( "g", [ pp.PointGrid([0, 0, 0]), pp.CartGrid([2]), pp.CartGrid([2, 2]), pp.CartGrid([2, 2, 2]), pp.StructuredTriangleGrid([2, 2]), pp.StructuredTetrahedralGrid([1, 1, 1]), ], ) def test_pickle_grid(g): """Test that grids can be pickled. Write, read and compare.""" fn = "tmp.grid" pickle.dump(g, open(fn, "wb")) g_read = pickle.load(open(fn, "rb")) test_utils.compare_grids(g, g_read)