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 _face_vector_to_scalar(self, nf, nd): """ Create a mapping from vector quantities on faces (stresses) to scalar quantities. The mapping is intended for the boundary discretization of the term div(u) (coupling term in the flow equation). Parameters: nf (int): Number of faces in the grid """ rows = np.tile(np.arange(nf), ((nd, 1))).reshape((1, nd * nf), order="F")[0] cols = fvutils.expand_indices_nd(np.arange(nf), nd) vals = np.ones(nf * nd) return sps.coo_matrix((vals, (rows, cols))).tocsr()
def compare_bc(g_1, data_0, data_1, face_map, phys="flow"): """ Compare type and value of BCs. """ BC_0 = data_0["param"].get_bc(phys) BC_1 = data_1["param"].get_bc(phys) vals_0 = data_0["param"].get_bc_val(phys) vals_1 = data_1["param"].get_bc_val(phys) assert np.all(BC_0.is_dir == BC_1.is_dir[face_map]) assert np.all(BC_0.is_neu == BC_1.is_neu[face_map]) if phys == "mechanics": boundary_face_map = fvutils.expand_indices_nd(face_map, g_1.dim) else: boundary_face_map = face_map assert np.all(vals_0 == vals_1[boundary_face_map])
def _discretize_mech(self, g, data): """ Discretization of poro-elasticity by the MPSA-W method. Implementation needs (in addition to those mentioned in mpsa function): 1) Fields for non-zero boundary conditions. Should be simple. 2) Split return value grad_p into forces and a divergence operator, so that we can compute Biot forces on a face. Parameters: g (core.grids.grid): grid to be discretized k (core.constit.second_order_tensor) permeability tensor bound_mech: Boundary condition object for mechancis bound_flow: Boundary condition object for flow. constit (porepy.bc.bc.BoundaryCondition) class for boundary values faces (np.ndarray) faces to be considered. Intended for partial discretization, may change in the future eta Location of pressure continuity point. Should be 1/3 for simplex grids, 0 otherwise. On boundary faces with Dirichlet conditions, eta=0 will be enforced. inverter (string) Block inverter to be used, either numba (default), cython or python. See fvutils.invert_diagonal_blocks for details. Returns: scipy.sparse.csr_matrix (shape num_faces * dim, num_cells * dim): stres discretization, in the form of mapping from cell displacement to face stresses. scipy.sparse.csr_matrix (shape num_faces * dim, num_faces * dim): discretization of boundary conditions. Interpreted as istresses induced by the boundary condition (both Dirichlet and Neumann). For Neumann, this will be the prescribed stress over the boundary face, and possibly stress on faces having nodes on the boundary. For Dirichlet, the values will be stresses induced by the prescribed displacement. Incorporation as a right hand side in linear system by multiplication with divergence operator. scipy.sparse.csr_matrix (shape num_cells * dim, num_cells): Forces from the pressure gradient (I*p-term), represented as body forces. TODO: Should rather be represented as forces on faces. scipy.sparse.csr_matrix (shape num_cells, num_cells * dim): Trace of strain matrix, cell-wise. scipy.sparse.csr_matrix (shape num_cells x num_cells): Stabilization term. Example: # Set up a Cartesian grid g = structured.CartGrid([5, 5]) c = tensor.FourthOrderTensor(g.dim, np.ones(g.num_cells)) k = tensor.SecondOrderTensor(g.dim, np.ones(g.num_cells)) # Dirirchlet boundary conditions for mechanics bound_faces = g.get_all_boundary_faces().ravel() bnd = bc.BoundaryCondition(g, bound_faces, ['dir'] * bound_faces.size) # Use no boundary conditions for flow, will default to homogeneous # Neumann. # Discretization stress, bound_stress, grad_p, div_d, stabilization = biot(g, c, bnd) flux, bound_flux = mpfa(g, k, None) # Source in the middle of the domain q_mech = np.zeros(g.num_cells * g.dim) # Divergence operator for the grid div_mech = fvutils.vector_divergence(g) div_flow = fvutils.scalar_divergence(g) a_mech = div_mech * stress a_flow = div_flow * flux a_biot = sps.bmat([[a_mech, grad_p], [div_d, a_flow + stabilization]]) # Zero boundary conditions by default. # Injection in the middle of the domain rhs = np.zeros(g.num_cells * (g.dim + 1)) rhs[g.num_cells * g.dim + np.ceil(g.num_cells / 2)] = 1 x = sps.linalg.spsolve(A, rhs) u_x = x[0:g.num_cells * g.dim: g.dim] u_y = x[1:g.num_cells * g.dim: g.dim] p = x[g.num_cells * gdim:] """ param = data["param"] bound_mech = param.get_bc("mechanics") bound_flow = param.get_bc("flow") constit = param.get_tensor("mechanics") eta = data.get("eta", 0) inverter = data.get("inverter", None) # The grid coordinates are always three-dimensional, even if the grid # is really 2D. This means that there is not a 1-1 relation between the # number of coordinates of a point / vector and the real dimension. # This again violates some assumptions tacitly made in the # discretization (in particular that the number of faces of a cell that # meets in a vertex equals the grid dimension, and that this can be # used to construct an index of local variables in the discretization). # These issues should be possible to overcome, but for the moment, we # simply force 2D grids to be proper 2D. if g.dim == 2: g = g.copy() g.cell_centers = np.delete(g.cell_centers, (2), axis=0) g.face_centers = np.delete(g.face_centers, (2), axis=0) g.face_normals = np.delete(g.face_normals, (2), axis=0) g.nodes = np.delete(g.nodes, (2), axis=0) constit.c = np.delete(constit.c, (2, 5, 6, 7, 8), axis=0) constit.c = np.delete(constit.c, (2, 5, 6, 7, 8), axis=1) nd = g.dim # Define subcell topology subcell_topology = fvutils.SubcellTopology(g) # Obtain mappings to exclude boundary faces for mechanics bound_exclusion_mech = fvutils.ExcludeBoundaries( subcell_topology, bound_mech, nd) # ... and flow bound_exclusion_flow = fvutils.ExcludeBoundaries( subcell_topology, bound_flow, nd) num_subhfno = subcell_topology.subhfno.size num_nodes = np.diff(g.face_nodes.indptr) sgn = g.cell_faces[subcell_topology.fno, subcell_topology.cno].A # The pressure gradient term in the mechanics equation is discretized # as a force on the faces. The right hand side is thus formed of the # normal vectors. def build_rhs_normals_single_dimension(dim): val = (g.face_normals[dim, subcell_topology.fno] * sgn / num_nodes[subcell_topology.fno]) mat = sps.coo_matrix( (val.squeeze(), (subcell_topology.subfno, subcell_topology.cno)), shape=(subcell_topology.num_subfno, subcell_topology.num_cno), ) return mat rhs_normals = build_rhs_normals_single_dimension(0) for iter1 in range(1, nd): this_dim = build_rhs_normals_single_dimension(iter1) rhs_normals = sps.vstack([rhs_normals, this_dim]) rhs_normals = bound_exclusion_mech.exclude_dirichlet_nd(rhs_normals) num_dir_subface = (bound_exclusion_mech.exclude_neu.shape[1] - bound_exclusion_mech.exclude_neu.shape[0]) * nd # No right hand side for cell displacement equations. rhs_normals_displ_var = sps.coo_matrix(( nd * subcell_topology.num_subfno - num_dir_subface, subcell_topology.num_cno, )) # Why minus? rhs_normals = -sps.vstack([rhs_normals, rhs_normals_displ_var]) del rhs_normals_displ_var # Call core part of MPSA hook, igrad, rhs_cells, cell_node_blocks, hook_normal = mpsa.mpsa_elasticity( g, constit, subcell_topology, bound_exclusion_mech, eta, inverter) # Output should be on face-level (not sub-face) hf2f = fvutils.map_hf_2_f(subcell_topology.fno_unique, subcell_topology.subfno_unique, nd) # Stress discretization stress = hf2f * hook * igrad * rhs_cells # Right hand side for boundary discretization rhs_bound = mpsa.create_bound_rhs(bound_mech, bound_exclusion_mech, subcell_topology, g) # Discretization of boundary values bound_stress = hf2f * hook * igrad * rhs_bound # Face-wise gradient operator. Used for the term grad_p in Biot's # equations. rows = fvutils.expand_indices_nd(subcell_topology.cno, nd) cols = np.arange(num_subhfno * nd) vals = np.tile(sgn, (nd, 1)).ravel("F") div_gradp = sps.coo_matrix( (vals, (rows, cols)), shape=(subcell_topology.num_cno * nd, num_subhfno * nd), ).tocsr() # del hook, rhs_bound del rows, cols, vals grad_p = div_gradp * hook_normal * igrad * rhs_normals # assert np.allclose(grad_p.sum(axis=0), np.zeros(g.num_cells)) del hook_normal, div_gradp div = self._subcell_gradient_to_cell_scalar(g, cell_node_blocks) div_d = div * igrad * rhs_cells # The boundary discretization of the div_d term is represented directly # on the cells, instead of going via the faces. bound_div_d = div * igrad * rhs_bound del rhs_cells stabilization = div * igrad * rhs_normals data["stress"] = stress data["bound_stress"] = bound_stress data["grad_p"] = grad_p data["div_d"] = div_d data["stabilization"] = stabilization data["bound_div_d"] = bound_div_d