def generate_2d_grid(self, n, xmax, ymax): g1 = pp.CartGrid([xmax * n, ymax * n], physdims=[xmax, ymax]) g1.compute_geometry() gb = pp.GridBucket() gb.add_nodes(g1) tol = 1e-6 left_faces = np.argwhere(g1.face_centers[1] > ymax - tol).ravel() right_faces = np.argwhere(g1.face_centers[1] < 0 + tol).ravel() val = np.ones(left_faces.size, dtype=np.bool) shape = [g1.num_faces, g1.num_faces] face_faces = sps.coo_matrix((val, (right_faces, left_faces)), shape=shape) gb.add_edge((g1, g1), face_faces) mg = pp.TensorGrid(np.linspace(0, xmax, n + 1)) mg.nodes[1] = ymax mg.compute_geometry() d_e = gb.edge_props((g1, g1)) d_e["mortar_grid"] = pp.MortarGrid(g1.dim - 1, {"0": mg}, face_faces) gb.assign_node_ordering() return gb
def generate_grid_with_fractures(self, n, xmax, ymax): x = [xmax / 3] * 2 y = [0, ymax / 2] fracs = [np.array([x, y])] y = [ymax / 2, ymax] fracs.append(np.array([x, y])) gb = pp.meshing.cart_grid(fracs, [xmax * n, ymax * n], physdims=[xmax, ymax]) tol = 1e-6 for g, d in gb: # map right faces to left left = np.argwhere(g.face_centers[1] < tol).ravel() right = np.argwhere(g.face_centers[1] > ymax - tol).ravel() g.face_centers[1, right] = 0 g.left = left g.right = right # now create mappings between two grids of equal dimension for dim in [2, 1]: grids = gb.grids_of_dimension(dim) for i, gi in enumerate(grids): if gi.left.size < 1: continue for j, gj in enumerate(grids): if gj.right.size < 1: continue face_faces = self.match_grids(gi, gj) if np.sum(face_faces) == 0: continue gb.add_edge((gi, gj), face_faces) d_e = gb.edge_props((gi, gj)) di = gb.node_props(gi) dj = gb.node_props(gj) if di["node_number"] < dj["node_number"]: # gj is left g_m, _, _ = pp.partition.extract_subgrid( gj, gj.right, faces=True ) face_faces = face_faces.T else: # gi is left g_m, _, _ = pp.partition.extract_subgrid( gi, gi.left, faces=True ) d_e["mortar_grid"] = pp.MortarGrid( gi.dim - 1, {"0": g_m}, face_faces ) gb.compute_geometry() # basically reset g.face_centers[,right] gb.assign_node_ordering() return gb
def test_pickle_mortar_grid(g): fn = 'tmp.grid' g.compute_geometry() mg = pp.MortarGrid(g.dim, {0: g, 1: g}) pickle.dump(mg, open(fn, 'wb')) mg_read = pickle.load(open(fn, 'rb')) test_utils.compare_mortar_grids(mg, mg_read) mg_one_sided = pp.MortarGrid(g.dim, {0: g}) pickle.dump(mg, open(fn, 'wb')) mg_read = pickle.load(open(fn, 'rb')) test_utils.compare_mortar_grids(mg_one_sided, mg_read) test_utils.delete_file(fn)
def generate_grids(self, n, xmax, ymax, split): g1 = pp.CartGrid([split * n, ymax * n], physdims=[split, ymax]) g2 = pp.CartGrid([(xmax - split) * n, ymax * n], physdims=[xmax - split, ymax]) g2.nodes[0] += split g1.compute_geometry() g2.compute_geometry() grids = [g2, g1] gb = pp.GridBucket() [gb.add_nodes(g) for g in grids] [g2, g1] = gb.grids_of_dimension(2) tol = 1e-6 if np.any(g2.cell_centers[0] > split): right_grid = g2 left_grid = g1 else: right_grid = g1 left_grid = g2 gb.set_node_prop(left_grid, "node_number", 1) gb.set_node_prop(right_grid, "node_number", 0) left_faces = np.argwhere(left_grid.face_centers[0] > split - tol).ravel() right_faces = np.argwhere(right_grid.face_centers[0] < split + tol).ravel() val = np.ones(left_faces.size, dtype=np.bool) shape = [right_grid.num_faces, left_grid.num_faces] face_faces = sps.coo_matrix((val, (right_faces, left_faces)), shape=shape) gb.add_edge((right_grid, left_grid), face_faces) mg = pp.TensorGrid(np.array([split] * ((n + 1) * ymax))) mg.nodes[1] = np.linspace(0, ymax, (n + 1) * ymax) mg.compute_geometry() d_e = gb.edge_props((g1, g2)) d_e["mortar_grid"] = pp.MortarGrid(g1.dim - 1, {"0": mg}, face_faces) d_e["edge_number"] = 0 return gb
def _update_mortar_grid(g_h: pp.Grid, g_l: pp.Grid, d_e: Dict[str, Any], new_cells, new_faces_h): mg_old = d_e["mortar_grid"] # Face-cell map. This has been updated during splitting, thus it has # the shapes of the new grids face_cells = d_e["face_cells"] cells, faces, _ = sps.find(face_cells) # If this is ever broken, we have a problem other_side_old = mg_old._ind_face_on_other_side other_side_new = np.copy(other_side_old) # Make sure that the + and - side of the new mortar cells is # coherent with those already in place. This may not be strictly # necessary, as the normal vectors of the grid will be adjusted # locally to the +- convention, however, it will ease the interpretation # of results, including debugging. # for ci in new_cells: # Find the occurences of this new cell in the face-cell map. # There should be exactly two of these. hit = np.where(ci == cells)[0] assert hit.size == 2 # Find the faces in the higher-dimensional grid that correspond # to this new cell loc_faces = faces[hit] # The new faces will be on each side of the fracture, and # there will be at least one node not shared by the faces. # We need to pick one of the faces, and find its neighboring # faces along the fracture, on the same side of the fracture. # The sign of the new face (in the mortar grid) will be the # same as the old one # We need to focus on split nodes, or else we risk finding neighboring # faces on both sides of the fracture. # Nodes of both local faces local_nodes_0 = g_h.face_nodes[:, loc_faces[0]].indices local_nodes_1 = g_h.face_nodes[:, loc_faces[1]].indices # Nodes that belong only to the first local face local_nodes_0_only = np.setdiff1d(local_nodes_0, local_nodes_1) # Get the other faces of these nodes. These will include both faces # on the fracture, and faces internal to g_h _, other_faces, _ = sps.find(g_h.face_nodes[local_nodes_0_only]) # Pick those of the other faces that were not added during splitting old_other_faces = np.setdiff1d(other_faces, new_faces_h) if np.any(np.in1d(old_other_faces, other_side_old)): other_side_new = np.append(other_side_new, loc_faces[0]) else: other_side_new = np.append(other_side_new, loc_faces[1]) # The new mortar grid is constructed to be matching with g_l. # If splitting is undertaken for a non-matching grid, all bets are off. side_grids = {1: g_l, 2: g_l} mg_new = pp.MortarGrid(g_l.dim, side_grids, d_e["face_cells"], face_duplicate_ind=other_side_new) d_e["mortar_grid"] = mg_new
def create_grid(self): """ Domain is unit square. One fracture, centered on (0.5, 0.5), tilted according to self.angle. """ angle = self.angle corners = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]]) # The fracture points always have x coordinates 0.2 and 0.8 # The y-coordinates are set so that the fracture forms the prescribed angle with # the x-axis frac_pt = np.array( [[0.2, 0.8], [0.5 - 0.3 * np.tan(angle), 0.5 + 0.3 * np.tan(angle)], [0, 0]]) nodes = np.hstack((corners, frac_pt)) rows = np.array([ [0, 1], [1, 5], [5, 0], [1, 2], [2, 5], [2, 4], [2, 3], [3, 0], [3, 4], [4, 0], [4, 5], [4, 5], ]).ravel() cols = np.vstack((np.arange(12), np.arange(12))).ravel("F") data = np.ones_like(rows) fn_2d = sps.coo_matrix((data, (rows, cols)), shape=(6, 12)).tocsc() rows = np.array([[0, 1, 2], [1, 3, 4], [4, 5, 10], [5, 6, 8], [7, 8, 9], [2, 11, 9]]).ravel() cols = np.tile(np.arange(6), (3, 1)).ravel("F") data = np.array( [-1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1]) cf_2d = sps.coo_matrix((data, (rows, cols)), shape=(12, 6)).tocsc() g_2d = pp.Grid(2, nodes, fn_2d, cf_2d, "mock_2d_grid") g_2d.compute_geometry() fn_1d = sps.csc_matrix(np.array([[1, 0], [0, 1]])) cf_1d = sps.csc_matrix(np.array([[-1], [1]])) g_1d = pp.Grid(1, frac_pt, fn_1d, cf_1d, "mock_1d_grid") g_1d.compute_geometry() gb = pp.GridBucket() gb.add_nodes([g_2d, g_1d]) # Construct mortar grid side_grids = { mortar_grid.LEFT_SIDE: g_1d.copy(), mortar_grid.RIGHT_SIDE: g_1d.copy(), } data = np.array([1, 1]) row = np.array([0, 0]) col = np.array([10, 11]) face_cells_mortar = sps.coo_matrix( (data, (row, col)), shape=(g_1d.num_cells, g_2d.num_faces)).tocsc() mg = pp.MortarGrid(1, side_grids, face_cells_mortar) edge = (g_2d, g_1d) gb.add_edge(edge, face_cells_mortar) d = gb.edge_props(edge) d["mortar_grid"] = mg self.gb = gb self._Nd = 2 self.g1 = g_1d self.g2 = g_2d self.edge = edge self.mg = mg
def extrude_grid_bucket(gb: pp.GridBucket, z: np.ndarray) -> Tuple[pp.GridBucket, Dict]: """ Extrude a GridBucket by extending all fixed-dimensional grids in the z-direction. In practice, the original grid bucket will be 2d, and the result is 3d. The returned GridBucket is fully functional, including mortar grids on the gb edges. The data dictionaries on nodes and edges are mainly empty. Data can be transferred from the original GridBucket via the returned map between old and new grids. Parameters: gb (pp.GridBukcet): Mixed-dimensional grid to be extruded. Should be 2d. z (np.ndarray): z-coordinates of the nodes in the extruded grid. Should be either non-negative or non-positive, and be sorted in increasing or decreasing order, respectively. Returns: gb (pp.GridBucket): Mixed-dimensional grid, 3d. The data dictionaries on nodes and edges are mostly empty. dict: Mapping from individual grids in the old bucket to the corresponding extruded grids in the new one. The dictionary values are a namedtuple with elements grid (new grid), cell_map and face_map, where the two latter describe mapping between the new and old grid, see extrude_grid for details. """ # New GridBucket. to be filled in gb_new = pp.GridBucket() # Data structure for mapping between old and new grids g_map = {} # Container for grid information Mapping = namedtuple("mapping", ["grid", "cell_map", "face_map"]) # Loop over all grids in the old bucket, extrude the grid, save mapping information for g, _ in gb: g_new, cell_map, face_map = extrude_grid(g, z) if hasattr(g, "frac_num"): g_new.frac_num = g.frac_num gb_new.add_nodes([g_new]) g_map[g] = Mapping(g_new, cell_map, face_map) # Loop over all edges in the old grid, create corresponding edges in the new gb. # Also define mortar_grids for e, d in gb.edges(): # grids of the old edge, extruded version of each grid gl, gh = gb.nodes_of_edge(e) gl_new = g_map[gl].grid gh_new = g_map[gh].grid # Next, we need the cell-face mapping for the new grid. # The idea is to first find the old map, then replace each cell-face relation # with the set of cells and faces (exploiting first that the new grids are # matching due to the extrusion algorithm, and second that the cell-map and # face-map stores indices in increasing layer index, so that the first cell # and first face both are in the first layer, thus they match, etc.). face_cells_old = d["face_cells"] # cells (in low-dim grid) and faces in high-dim grid that define the same # geometric quantity cells, faces, _ = sps.find(face_cells_old) # Cell-map for the low-dimensional grid, face-map for the high-dim cell_map = g_map[gl].cell_map face_map = g_map[gh].face_map # Data structure for the new face-cell map rows = np.empty(0, dtype=np.int) cols = np.empty(0, dtype=np.int) # The standard MortarGrid __init__ assumes that when faces are split because of # a fracture, the faces are ordered with one side first, then the other. This # will not be True for this layered construction. Instead, keep track of all # faces that should be moved to the other side. face_on_other_side = np.empty(0, dtype=np.int) # Loop over cells in gl would not have been as clean, as each cell is associated # with faces on both sides # Faces are found from the high-dim grid, cells in the low-dim grid for idx in range(faces.size): rows = np.hstack((rows, cell_map[cells[idx]])) cols = np.hstack((cols, face_map[faces[idx]])) # Here, we tacitly assume that the original grid had its faces split in the # standard way, that is, all faces on one side have index lower than any # face on the other side. if faces[idx] > np.median(faces): face_on_other_side = np.hstack( (face_on_other_side, face_map[faces[idx]]) ) data = np.ones(rows.size, dtype=np.bool) # Create new face-cell map face_cells_new = sps.coo_matrix( (data, (rows, cols)), shape=(gl_new.num_cells, gh_new.num_faces) ).tocsc() # Define the new edge e = (gh_new, gl_new) # Add to new gb, together with the new face-cell map gb_new.add_edge(e, face_cells_new) # Create a mortar grid, add to data of new edge side_g = { mortar_grid.LEFT_SIDE: gl_new.copy(), mortar_grid.RIGHT_SIDE: gl_new.copy(), } # Construct mortar grid, with instructions on which faces belong to which side mg = pp.MortarGrid( gl_new.dim, side_g, face_cells_new, face_duplicate_ind=face_on_other_side ) d_new = gb_new.edge_props(e) d_new["mortar_grid"] = mg return gb_new, g_map