def _append_face_geometry_fracture_grid( g: pp.Grid, n_new_faces: int, new_centers: np.ndarray ) -> None: """ Appends and updates faces geometry information for new faces. Also updates num_faces. """ g.face_normals = np.append(g.face_normals, np.zeros((3, n_new_faces)), axis=1) g.face_areas = np.append(g.face_areas, np.ones(n_new_faces)) g.face_centers = np.append(g.face_centers, new_centers, axis=1) g.num_faces += n_new_faces
def _duplicate_specific_faces(gh: pp.Grid, frac_id: np.ndarray) -> np.ndarray: """ Duplicate faces of gh specified by frac_id. """ # Find which of the faces to split are tagged with a standard face tag, # that is, as fracture, tip or domain_boundary rem = tags.all_face_tags(gh.tags)[frac_id] # Set the faces to be split to fracture faces # Q: Why only if the face already had a tag (e.g., why [rem])? # Possible answer: We wil not split them (see redefinition of frac_id below), # but want them to be tagged as fracture_faces gh.tags["fracture_faces"][frac_id[rem]] = True # Faces to be split should not be tip gh.tags["tip_faces"][frac_id] = False # Only consider previously untagged faces for splitting frac_id = frac_id[~rem] if frac_id.size == 0: return frac_id # Expand the face-node relation to include duplicated nodes # Do this by directly manipulating the CSC-format of the matrix # Nodes of the target faces node_start = gh.face_nodes.indptr[frac_id] node_end = gh.face_nodes.indptr[frac_id + 1] nodes = gh.face_nodes.indices[mcolon(node_start, node_end)] # Start point for the new columns. They will be appended to the matrix, thus # the offset of the previous size of gh.face_nodes added_node_pos = np.cumsum(node_end - node_start) + gh.face_nodes.indptr[-1] # Sanity checks assert added_node_pos.size == frac_id.size assert added_node_pos[-1] - gh.face_nodes.indptr[-1] == nodes.size # Expand row-data by adding node indices gh.face_nodes.indices = np.hstack((gh.face_nodes.indices, nodes)) # Expand column pointers gh.face_nodes.indptr = np.hstack((gh.face_nodes.indptr, added_node_pos)) # Expand data array gh.face_nodes.data = np.hstack( (gh.face_nodes.data, np.ones(nodes.size, dtype=bool))) # Update matrix shape gh.face_nodes._shape = (gh.num_nodes, gh.face_nodes.shape[1] + frac_id.size) assert gh.face_nodes.indices.size == gh.face_nodes.indptr[-1] # We also copy the attributes of the original faces. gh.num_faces += frac_id.size gh.face_normals = np.hstack((gh.face_normals, gh.face_normals[:, frac_id])) gh.face_areas = np.append(gh.face_areas, gh.face_areas[frac_id]) gh.face_centers = np.hstack((gh.face_centers, gh.face_centers[:, frac_id])) # Not sure if this still does the correct thing. Might have to # send in a logical array instead of frac_id. gh.tags["fracture_faces"][frac_id] = True gh.tags["tip_faces"][frac_id] = False update_fields = gh.tags.keys() update_values: List[List[np.ndarray]] = [[]] * len(update_fields) for i, key in enumerate(update_fields): # faces related tags are doubled and the value is inherit from the original if key.endswith("_faces"): update_values[i] = gh.tags[key][frac_id] tags.append_tags(gh.tags, update_fields, update_values) return frac_id
def _update_geometry( g_h: pp.Grid, g_l: pp.Grid, new_cells: np.ndarray, n_old_cells_l: int, n_old_faces_l: int, ) -> None: # Update geometry on each iteration to ensure correct tags. # The geometry of the higher-dimensional grid can be computed straightforwardly. g_h.compute_geometry() if g_h.dim == 2: # 1d geometry computation is valid also for manifolds g_l.compute_geometry() else: # The implementation of 2d compute_geometry() assumes that the # grid is planar. The simplest option is to treat one cell at # a time, and then merge the arrays at the end. # Initialize arrays for geometric quantities fa = np.empty(0) # Face areas fc = np.empty((3, 0)) # Face centers fn = np.empty((3, 0)) # Face normals cv = np.empty(0) # Cell volumes cc = np.empty((3, 0)) # Cell centers # Many of the faces will have their quantities computed twice, # once from each side. Keep track of which faces we are dealing with face_ind = np.array([], dtype=np.int) for ci in new_cells: sub_g, fi, _ = pp.partition.extract_subgrid(g_l, ci) sub_g.compute_geometry() fa = np.append(fa, sub_g.face_areas) fc = np.append(fc, sub_g.face_centers, axis=1) fn = np.append(fn, sub_g.face_normals, axis=1) cv = np.append(cv, sub_g.cell_volumes) cc = np.append(cc, sub_g.cell_centers, axis=1) face_ind = np.append(face_ind, fi) # The new cell geometry is composed of values from the previous grid, and # the values computed one by one for the new cells g_l.cell_volumes = np.hstack((g_l.cell_volumes[:n_old_cells_l], cv)) g_l.cell_centers = np.hstack((g_l.cell_centers[:, :n_old_cells_l], cc)) # For the faces, more work is needed face_areas = np.zeros(g_l.num_faces) face_centers = np.zeros((3, g_l.num_faces)) face_normals = np.zeros((3, g_l.num_faces)) # For the old faces, transfer already computed values face_areas[:n_old_faces_l] = g_l.face_areas[:n_old_faces_l] face_centers[:, :n_old_faces_l] = g_l.face_centers[:, :n_old_faces_l] face_normals[:, :n_old_faces_l] = g_l.face_normals[:, :n_old_faces_l] for fi in range(n_old_faces_l, g_l.num_faces): # Geometric quantities for this face hit = np.where(face_ind == fi)[0] # There should be 1 or 2 hits assert hit.size > 0 and hit.size < 3 # For areas and centers, the computations based on the two neighboring # cells should give the same result. Check, and then use the value. mean_area = np.mean(fa[hit]) mean_center = np.mean(fc[:, hit], axis=1) assert np.allclose(fa[hit], mean_area) assert np.allclose(fc[:, hit], mean_center.reshape((3, 1))) face_areas[fi] = mean_area face_centers[:, fi] = mean_center # The normal is more difficult, since this is not unique. # The direction of the normal vectors computed from subgrids should be # consistent with the +- convention in the main grid. # Normal vectors found for this global face normals = fn[:, hit] if normals.size == 3: normals = normals.reshape((3, 1)) # For the moment, use the mean of the two values. mean_normal = np.mean(normals, axis=1) face_normals[:, fi] = mean_normal / np.linalg.norm( mean_normal) * mean_area # Sanity check # assert np.allclose(np.linalg.norm(face_normals, axis=0), face_areas) # Store computed values g_l.face_areas = face_areas g_l.face_centers = face_centers g_l.face_normals = face_normals