def _tag_injection_cell( gb: pp.GridBucket, g: pp.Grid, pts: np.ndarray, length_scale ) -> None: """ Helper method to tag find closest point on g to pts The tag is set locally to g and to node props on gb. length_scale is used to log the unscaled distance to the injection cell from pts. Parameters ---------- gb : pp.GridBucket g : pp.Grid pts : np.ndarray, shape: (3,1) length_scale : float """ assert pts.shape == (3, 1), "We only consider one point; array needs shape 3x1" tags = np.zeros(g.num_cells) ids, dsts = g.closest_cell(pts, return_distance=True) tags[ids] = 1 g.tags["well_cells"] = tags d = gb.node_props(g) pp.set_state(d, {"well": tags}) # Log information on the injection point logger.info( f"Closest cell found has (unscaled) distance: {dsts[0] * length_scale:4f}\n" f"ideal (scaled) point coordinate: {pts.T}\n" f"nearest (scaled) cell center coordinate: {g.cell_centers[:, ids].T}\n" )
def _tag_well_cells(self): """ Tag well cells with unitary values, positive for injection cells and negative for production cells. """ for g, d in self.gb: tags = np.zeros(g.num_cells) if g.dim < self._Nd: s = self.box["xmax"] ny = 25 # Avoid specifying a point on a face (having non-unique nearest # cell centre neighbour) by adding eps eps = 0.01 ny = 26 # cf grid construction p1 = np.array([[(s + eps) / 2], [11.7 * s / (ny)], [s / 2]]) p2 = np.array([[s / 2], [(14.2) * s / ny], [(s + eps) / 2]]) if d["node_number"] == 1: distances = pp.distances.point_pointset(p1, g.cell_centers) indexes = np.argsort(distances) tags[indexes[0]] = 1 # injection well elif d["node_number"] == 2: distances = pp.distances.point_pointset(p2, g.cell_centers) indexes = np.argsort(distances) tags[indexes[0]] = -1 g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()})
def initial_condition(self) -> None: """ Initial value for the Darcy fluxes. TODO: Add to THM. """ for g, d in self.gb: d[pp.PARAMETERS] = pp.Parameters() d[pp.PARAMETERS].update_dictionaries( [self.mechanics_parameter_key, self.scalar_parameter_key,] ) self.update_all_apertures(to_iterate=False) self.update_all_apertures() super().initial_condition() for g, d in self.gb: d[pp.STATE]["cell_centers"] = g.cell_centers.copy() p0 = self.initial_scalar(g) state = { self.scalar_variable: p0, "u_exp_0": np.zeros(g.num_cells), "aperture_0": self.aperture(g) * self.length_scale, } iterate = { self.scalar_variable: p0, } # For initial flux pp.set_state(d, state) pp.set_iterate(d, iterate)
def initial_condition(self) -> None: for g, d in self.gb: # Initial value for the scalar variable. initial_scalar_value = np.zeros(g.num_cells) d[pp.STATE].update({self.scalar_variable: initial_scalar_value}) for _, d in self.gb.edges(): mg = d["mortar_grid"] initial_value = np.zeros(mg.num_cells) state = {self.mortar_scalar_variable: initial_value} pp.set_state(d, state)
def _copy_biot_discretizations(self) -> None: """The Biot discretization is designed to discretize a single term of the grad_p type. It should not be difficult to generalize this, but pending such an update, the below code copies the discretization matrices from the flow related keywords to those of the temperature. """ g: pp.Grid = self.gb.grids_of_dimension(self._Nd)[0] d: Dict = self.gb.node_props(g) beta = self._biot_beta(g) alpha = self._biot_alpha(g) # For grad p term of u equation weight_grad_t = beta / alpha # Account for scaling weight_grad_t *= self.temperature_scale / self.scalar_scale # Stabilization is derived from the grad p discretization weight_stabilization = beta / alpha weight_stabilization *= self.temperature_scale / self.scalar_scale # The stabilization terms appear in the T/p equations, whereof only the first # is divided by T_0_Kelvin. weight_stabilization *= 1 / self.T_0_Kelvin # Matrix dictionaries for the different subproblems matrices_s = d[pp.DISCRETIZATION_MATRICES][self.scalar_parameter_key] matrices_ms = d[pp.DISCRETIZATION_MATRICES][self.mechanics_parameter_key] matrices_t = d[pp.DISCRETIZATION_MATRICES][self.temperature_parameter_key] matrices_mt = {} matrices_t["div_u"] = matrices_s["div_u"].copy() matrices_t["bound_div_u"] = matrices_s["bound_div_u"].copy() matrices_mt["grad_p"] = weight_grad_t * matrices_ms["grad_p"] matrices_t["biot_stabilization"] = ( weight_stabilization * matrices_s["biot_stabilization"] ) matrices_mt["bound_displacement_pressure"] = ( weight_grad_t * matrices_ms["bound_displacement_pressure"] ) # For div u term of t equation weight_div_u = beta key_m_from_t = self.mechanics_temperature_parameter_key d[pp.DISCRETIZATION_MATRICES][key_m_from_t] = matrices_mt pp.initialize_data( g, d, key_m_from_t, {"biot_alpha": weight_div_u}, ) bc_dict = {"bc_values": self._bc_values_mechanics(g)} state = {key_m_from_t: bc_dict} pp.set_state(d, state)
def initial_condition(self, time=0): """ Initial guess for Newton iteration. """ gb = self.gb key_m = self.mechanics_parameter_key for g, d in gb: nc_nd = g.num_cells * self.Nd # Initial value for the scalar variable. initial_scalar_value = self.s_0 * np.ones(g.num_cells) if g.dim == self.Nd: initial_displacements = np.zeros(nc_nd) # Initialize displacement variable state = { self.displacement_variable: initial_displacements, self.scalar_variable: initial_scalar_value, key_m: { "bc_values": d[pp.PARAMETERS][key_m]["bc_values"] }, } elif g.dim == self.Nd - 1: # Initialize contact variable traction = np.vstack(( np.zeros((self.Nd - 1, g.num_cells)), -100 * np.ones(g.num_cells), )).ravel(order="F") state = { self.contact_traction_variable: traction, "previous_iterate": { self.contact_traction_variable: traction }, self.scalar_variable: initial_scalar_value, } pp.set_state(d, state) for e, d in gb.edges(): mg = d["mortar_grid"] initial_displacements = np.zeros(mg.num_cells * self.Nd) if mg.dim == self.Nd - 1: state = { self.mortar_scalar_variable: np.zeros(mg.num_cells), self.mortar_displacement_variable: initial_displacements, "previous_iterate": { self.mortar_displacement_variable: initial_displacements }, } pp.set_state(d, state)
def well_cells(self) -> None: """ Tag well cells with unity values, positive for injection cells and negative for production cells. """ # Initiate all tags to zero for g, d in self.gb: tags = np.zeros(g.num_cells) g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()}) # Set injection cells self.params.well_cells(self.params, self.gb)
def tag_tunnel_cells(self): """ Tag tunnel-shearzone intersections Compute the nearest cell for each shear zone that intersects each of the tunnels. tunnel_sz is the following table: borehole x_gts y_gts z_gts shearzone 591 AU 72.625 125.321 33.436 S1_1 592 VE 9.735 88.360 35.419 S1_1 593 AU 74.565 135.311 33.858 S1_2 594 VE 10.917 95.617 34.431 S1_2 595 AU 74.839 143.317 33.611 S1_3 596 VE 18.560 107.674 34.916 S1_3 597 AU 72.094 106.617 32.813 S3_1 598 VE 22.420 113.208 33.608 S3_1 599 AU 72.185 110.025 33.639 S3_2 600 VE 25.125 118.238 33.762 S3_2 """ tunnel_cells_key = "tunnel_cells" shearzones = self.params.shearzone_names tunnels = ["AU", "VE"] # Fetch tunnel-shearzone intersections isc_data = self.params.isc_data df = isc_data.structures _mask = df["borehole"].isin(tunnels) & df["shearzone"].isin(shearzones) t_sz = df[_mask] keepcols = ["borehole", "x_gts", "y_gts", "z_gts", "shearzone"] tunnel_sz = t_sz[keepcols].reset_index(drop=True) gb = self.gb for g, d in gb: g.tags[tunnel_cells_key] = np.zeros(g.num_cells, dtype=bool) pp.set_state(d, {tunnel_cells_key: np.zeros(g.num_cells, dtype=bool)}) def _tag_intersection_cell(row): _coord = ( row[["x_gts", "y_gts", "z_gts"]].to_numpy(dtype=float).reshape((3, -1)) ) coord = _coord * (pp.METER / self.params.length_scale) shearzone = row["shearzone"] grid: pp.Grid = self.grids_by_name(shearzone)[0] data = gb.node_props(grid) tags = np.zeros(grid.num_cells, dtype=bool) ids, dsts = grid.closest_cell(coord, return_distance=True) tags[ids] = True grid.tags[tunnel_cells_key] |= tags data[pp.STATE][tunnel_cells_key] |= tags tunnel_sz.apply(_tag_intersection_cell, axis=1)
def test_add_state_twice(self, empty_dict): """Add two state dictionaries. The existing foo value should be overwritten, while bar should be kept. """ d = empty_dict d1 = {"foo": 1, "bar": 2} d2 = {"foo": 3, "spam": 4} pp.set_state(d, d1) pp.set_state(d, d2) for key, val in zip(["foo", "bar", "spam"], [3, 2, 4]): assert key in d[pp.STATE] assert d[pp.STATE][key] == val
def well_cells(self): """ Assign unitary values to injection cells (positive) and production cells (negative). The wells are marked by g.tags['well_cells'], and in the data dictionary of this well. """ for g, d in self.gb: tags = np.zeros(g.num_cells) if g.dim == self.Nd - 1: # We should not ask for frac_num in intersections if g.frac_num == 4: tags[1] = 1 g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()})
def initial_condition(self): """ Set initial guess for the variables. The displacement is set to zero in the Nd-domain, and at the fracture interfaces The displacement jump is thereby also zero. The contact pressure is set to zero in the tangential direction, and -1 (that is, in contact) in the normal direction. """ for g, d in self.gb: if g.dim == self.Nd: # Initialize displacement variable state = { self.displacement_variable: np.zeros(g.num_cells * self.Nd) } elif g.dim == self.Nd - 1: # Initialize contact variable traction = np.vstack( (np.zeros((g.dim, g.num_cells)), -1 * np.ones(g.num_cells))).ravel(order="F") state = { "previous_iterate": { self.contact_traction_variable: traction }, self.contact_traction_variable: traction, } else: state = {} pp.set_state(d, state) for _, d in self.gb.edges(): mg = d["mortar_grid"] if mg.dim == self.Nd - 1: size = mg.num_cells * self.Nd state = { self.mortar_displacement_variable: np.zeros(size), "previous_iterate": { self.mortar_displacement_variable: np.zeros(size) }, } else: state = {} pp.set_state(d, state)
def set_state(self, u_mortar, contact_force): # Replacement for the method 'initial_condition'. Change name to avoid # conflict with different parameters. for g, d in self.gb: if g.dim == self._Nd: # Initialize displacement variable state = { self.displacement_variable: np.zeros(g.num_cells * self._Nd) } elif g.dim == self._Nd - 1: # Initialize contact variable traction = contact_force state = { # Set value for previous iterate. This becomes what decides the # state used in discretization. pp.ITERATE: { self.contact_traction_variable: traction }, # Zero state in previous time step, just to avoid confusion self.contact_traction_variable: 0 * traction, } else: state = {} pp.set_state(d, state) for _, d in self.gb.edges(): mg = d["mortar_grid"] if mg.dim == self._Nd - 1: state = { # Set a zero state in previous time step self.mortar_displacement_variable: 0 * u_mortar, # Set value for previous iterate. This becomes what decides the # state used in discretization. pp.ITERATE: { self.mortar_displacement_variable: u_mortar }, } else: state = {} pp.set_state(d, state)
def test_add_iterate_twice_and_state(self, empty_dict): """Add two state dictionaries. The existing foo value should be overwritten, while bar should be kept. Setting values in pp.STATE should not affect the iterate values. """ d = empty_dict d1 = {"foo": 1, "bar": 2} d2 = {"foo": 3, "spam": 4} pp.set_iterate(d, d1) pp.set_iterate(d, d2) pp.set_state(d, {"foo": 5}) for key, val in zip(["foo", "bar", "spam"], [3, 2, 4]): assert key in d[pp.STATE][pp.ITERATE] assert d[pp.STATE][pp.ITERATE][key] == val assert d[pp.STATE]["foo"] == 5
def _initial_condition(self) -> None: """ In addition to the values set by the parent class, we set initial value for the temperature variable, and a previous iterate value for the scalar value. The latter is used for computation of Darcy fluxes, needed for the advective term of the energy equation. The Darcy flux parameter is also initialized. """ super()._initial_condition() for g, d in self.gb: # Initial value for the scalar variable. cell_zeros = np.zeros(g.num_cells) state = {self.temperature_variable: cell_zeros} iterate = {self.scalar_variable: cell_zeros} # For initial flux pp.set_state(d, state) pp.set_iterate(d, iterate) # Initial Darcy fluxes for advective flux. pp.initialize_data( g, d, self.temperature_parameter_key, { "darcy_flux": np.zeros(g.num_faces), }, ) for e, d in self.gb.edges(): mg = d["mortar_grid"] cell_zeros = np.zeros(mg.num_cells) state = { self.mortar_temperature_variable: cell_zeros, self.mortar_temperature_advection_variable: cell_zeros, } iterate = {self.mortar_scalar_variable: cell_zeros} pp.set_state(d, state) pp.set_iterate(d, iterate) # Initial Darcy fluxes for advective flux. d = pp.initialize_data( e, d, self.temperature_parameter_key, { "darcy_flux": np.zeros(mg.num_cells), }, )
def well_cells(self): """ Tag well cells with unity values, positive for injection cells and negative for production cells. """ # TODO: Use unscaled grid to find result. df = self.isc.borehole_plane_intersection() # Borehole-shearzone intersection of interest bh_sz = self.source_scalar_borehole_shearzone _mask = (df.shearzone == bh_sz["shearzone"]) & (df.borehole == bh_sz["borehole"]) result = df.loc[_mask, ("x_sz", "y_sz", "z_sz")] if result.empty: raise ValueError("No intersection found.") pts = result.to_numpy().T / self.length_scale assert pts.shape[1] == 1, "Should only be one intersection" tagged = False for g, d in self.gb: tags = np.zeros(g.num_cells) # Get name of grid grid_name = self.gb.node_props(g, "name") # We only tag cells in the desired fracture if grid_name == bh_sz['shearzone']: logger.info( f"Tagging grid of name: {grid_name}, and dimension {g.dim}" ) logger.info(f"Setting non-zero source value for pressure") ids, dsts = g.closest_cell(pts, return_distance=True) logger.info(f"Closest cell found has distance: {dsts[0]:4f}") # Tag the injection cell tags[ids] = 1 tagged = True g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()}) if not tagged: logger.warning("No injection cell was tagged.")
def _initial_condition(self) -> None: """ Initialize fracture pressure. Returns ------- None """ super()._initial_condition() for g, d in self.gb: if g.dim == self.Nd - 1: pp.set_state( d, { self.scalar_variable: self.params["boundary_traction"] * np.ones(g.num_cells) }, )
def initial_condition(data_dictionary, variable_flow, variable_mechanics, exact_data): """ Establishes initial condition. Parameters: data_dictionary (Dictionary) : Model's data dictionary variable_flow (String) : Primary variable of the flow problem variable_mechanics (String) : Primary varibale of the mechanics problem exact_data (Dictionary) : Containing the solutions and the top boundary condition. """ d_0 = exact_data[variable_mechanics][0] p_0 = exact_data[variable_flow][0] state = {variable_mechanics: d_0, variable_flow: p_0} pp.set_state(data_dictionary, state)
def nd_sides_shearzone_injection_cell( params: FlowParameters, gb: pp.GridBucket, reset_frac_tags: bool = True, ) -> None: """ Tag the Nd cells surrounding a shear zone injection point Parameters ---------- params : FlowParameters parameters that contain "source_scalar_borehole_shearzone" (with "shearzone", and "borehole") and "length_scale". gb : pp.GridBucket grid bucket reset_frac_tags : bool [Default: True] if set to False, keep injection tag in the shear zone. """ # Shorthand shearzone = params.source_scalar_borehole_shearzone.get("shearzone") # First, tag the fracture cell, and get the tag shearzone_injection_cell(params, gb) fracture = gb.get_grids(lambda g: gb.node_props(g, "name") == shearzone)[0] tags = fracture.tags["well_cells"] # Second, map the cell to the Nd grid nd_grid: pp.Grid = gb.grids_of_dimension(gb.dim_max())[0] data_edge = gb.edge_props((fracture, nd_grid)) mg: pp.MortarGrid = data_edge["mortar_grid"] slave_to_master_face = mg.mortar_to_master_int() * mg.slave_to_mortar_int() face_to_cell = nd_grid.cell_faces.T slave_to_master_cell = face_to_cell * slave_to_master_face nd_tags = np.abs(slave_to_master_cell) * tags # Set tags on the nd-grid nd_grid.tags["well_cells"] = nd_tags ndd = gb.node_props(nd_grid) pp.set_state(ndd, {"well": tags}) if reset_frac_tags: # reset tags on the fracture zeros = np.zeros(fracture.num_cells) fracture.tags["well_cells"] = zeros d = gb.node_props(fracture) pp.set_state(d, {"well": zeros})
def _tag_ivar_well_cells(_, gb: pp.GridBucket) -> None: """ Tag well cells with unitary values, positive for injection cells and negative for production cells. """ box = gb.bounding_box(as_dict=True) nd = gb.dim_max() for g, d in gb: tags = np.zeros(g.num_cells) if g.dim < nd: point = np.array([[(box["xmin"] + box["xmax"]) / 2], [box["ymax"]], [0],]) distances = pp.distances.point_pointset(point, g.cell_centers) indexes = np.argsort(distances) if d["node_number"] == 1: tags[indexes[-1]] = 1 # injection elif d["node_number"] == 3: tags[indexes[-1]] = -1 # production # write_well_cell_to_csv(g, indexes[-1], self) g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()})
def initial_condition(self): """ Initial guess for Newton iteration, scalar variable and bc_values (for time discretization). When stimulation phase is reached, we use displacements of last solution in initialize phase as initial condition for the cell displacements. """ super().initial_condition() # TODO: Set hydrostatic initial condition # TODO: Scale variables if self.current_phase > 0: # Stimulation phase for g, d in self.gb: if g.dim == self.Nd: initial_displacements = d["initial_cell_displacements"] pp.set_state( d, {self.displacement_variable: initial_displacements}) for e, d in self.gb.edges(): if e[0].dim == self.Nd: try: initial_displacements = d["initial_cell_displacements"] except KeyError: logger.warning( "We got KeyError on d['initial_cell_displacements']." ) mg = d["mortar_grid"] initial_displacements = np.zeros(mg.num_cells * self.Nd) state = { self.mortar_displacement_variable: initial_displacements, "previous_iterate": { self.mortar_displacement_variable: initial_displacements, }, } pp.set_state(d, state)
def copy_biot_discretizations(self) -> None: g: pp.Grid = self.gb.grids_of_dimension(self.Nd)[0] d: Dict = self.gb.node_props(g) beta = self.biot_beta(g) alpha = self.biot_alpha(g) # For grad p term of u equation weight_grad_t = beta / alpha # Account for scaling weight_grad_t *= self.temperature_scale / self.scalar_scale # Stabilization is derived from the grad p discretization weight_stabilization = beta / alpha weight_stabilization *= self.temperature_scale / self.scalar_scale # The stabilization terms appear in the T/p equations, whereof only the first # is divided by T_0_Kelvin. weight_stabilization *= 1 / self.T_0_Kelvin # Matrix dictionaries for the different subproblems matrices_s = d[pp.DISCRETIZATION_MATRICES][self.scalar_parameter_key] matrices_ms = d[pp.DISCRETIZATION_MATRICES][ self.mechanics_parameter_key] matrices_t = d[pp.DISCRETIZATION_MATRICES][ self.temperature_parameter_key] matrices_mt = {} matrices_t["div_u"] = matrices_s["div_u"].copy() matrices_t["bound_div_u"] = matrices_s["bound_div_u"].copy() matrices_mt["grad_p"] = weight_grad_t * matrices_ms["grad_p"] matrices_t["biot_stabilization"] = (weight_stabilization * matrices_s["biot_stabilization"]) matrices_mt["bound_displacement_pressure"] = ( weight_grad_t * matrices_ms["bound_displacement_pressure"]) # For div u term of t equation weight_div_u = beta key_m_from_t = self.mechanics_temperature_parameter_key d[pp.DISCRETIZATION_MATRICES][key_m_from_t] = matrices_mt d[pp.PARAMETERS][key_m_from_t] = {"biot_alpha": weight_div_u} bc_dict = {"bc_values": self.bc_values_mechanics(g)} state = {key_m_from_t: bc_dict} pp.set_state(d, state)
def _tag_well_cells(self): """ Tag well cells with unitary values, positive for injection cells and negative for production cells. """ for g, d in self.gb: tags = np.zeros(g.num_cells) if g.dim < self.Nd: point = np.array( [ [(self.box["xmin"] + self.box["xmax"]) / 2], [self.box["ymin"]], [0], ] ) distances = pp.distances.point_pointset(point, g.cell_centers) indexes = np.argsort(distances) if d["node_number"] == 1: tags[indexes[-1]] = 1 # injection g.tags["well_cells"] = tags pp.set_state(d, {"well": tags.copy()})
def initial_condition(self): """ Initial guess for Newton iteration. """ for g, d in self.gb: if g.dim == self.Nd: # Initialize displacement variable state = { self.displacement_variable: np.zeros(g.num_cells * self.Nd) } elif g.dim == self.Nd - 1: # Initialize contact variable traction = np.vstack( (np.zeros((g.dim, g.num_cells)), -1 * np.ones(g.num_cells))).ravel(order="F") state = { "previous_iterate": { self.contact_traction_variable: traction } } else: state = {} pp.set_state(d, state) for _, d in self.gb.edges(): mg = d["mortar_grid"] if mg.dim == self.Nd - 1: size = mg.num_cells * self.Nd state = { self.mortar_displacement_variable: np.zeros(mg.num_cells * self.Nd), "previous_iterate": { self.mortar_displacement_variable: np.zeros(size) }, } pp.set_state(d, state)
def test_assemble_biot_rhs_transient(self): """ Test the assembly of a Biot problem with a non-zero rhs using the assembler. The test checks whether the discretization matches that of the Biot class and that the solution reaches the expected steady state. """ gb = pp.meshing.cart_grid([], [3, 3], physdims=[1, 1]) g = gb.grids_of_dimension(2)[0] d = gb.node_props(g) # Parameters identified by two keywords. Non-default parameters of somewhat # arbitrary values are assigned to make the test more revealing. kw_m = "mechanics" kw_f = "flow" variable_m = "displacement" variable_f = "pressure" bound_mech, bound_flow = self.make_boundary_conditions(g) val_mech = np.ones(g.dim * g.num_faces) val_flow = np.ones(g.num_faces) initial_disp, initial_pressure, initial_state = self.make_initial_conditions( g, x0=1, y0=2, p0=0) dt = 1e0 biot_alpha = 0.6 state = { variable_f: initial_pressure, variable_m: initial_disp, kw_m: { "bc_values": val_mech }, } parameters_m = { "bc": bound_mech, "bc_values": val_mech, "time_step": dt, "biot_alpha": biot_alpha, } parameters_f = { "bc": bound_flow, "bc_values": val_flow, "time_step": dt, "biot_alpha": biot_alpha, "mass_weight": 0.1 * np.ones(g.num_cells), } pp.initialize_default_data(g, d, kw_m, parameters_m) pp.initialize_default_data(g, d, kw_f, parameters_f) pp.set_state(d, state) # Initial condition fot the Biot class # d["state"] = initial_state # Set up the structure for the assembler. First define variables and equation # term names. v_0 = variable_m v_1 = variable_f term_00 = "stress_divergence" term_01 = "pressure_gradient" term_10 = "displacement_divergence" term_11_0 = "fluid_mass" term_11_1 = "fluid_flux" term_11_2 = "stabilization" d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}} d[pp.DISCRETIZATION] = { v_0: { term_00: pp.Mpsa(kw_m) }, v_1: { term_11_0: IE_discretizations.ImplicitMassMatrix(kw_f, v_1), term_11_1: IE_discretizations.ImplicitMpfa(kw_f), term_11_2: pp.BiotStabilization(kw_f), }, v_0 + "_" + v_1: { term_01: pp.GradP(kw_m) }, v_1 + "_" + v_0: { term_10: pp.DivU(kw_m) }, } # Discretize the mechanics related terms using the Biot class biot_discretizer = pp.Biot() biot_discretizer._discretize_mech(g, d) general_assembler = pp.Assembler(gb) # Discretize terms that are not handled by the call to biot_discretizer general_assembler.discretize(term_filter=["fluid_mass", "fluid_flux"]) times = np.arange(5) for _ in times: # Assemble. Also discretizes the flow terms (fluid_mass and fluid_flux) A, b = general_assembler.assemble_matrix_rhs() # Assemble using the Biot class A_class, b_class = biot_discretizer.matrix_rhs(g, d, discretize=False) # Make sure the variable ordering of the matrix assembled by the assembler # matches that of the Biot class. grids = [g, g] variables = [v_0, v_1] A, b = permute_matrix_vector( A, b, general_assembler.block_dof, general_assembler.full_dof, grids, variables, ) # Compare the matrices and rhs vectors self.assertTrue(np.all(np.isclose(A.A, A_class.A))) self.assertTrue(np.all(np.isclose(b, b_class))) # Store the current solution for the next time step. x_i = sps.linalg.spsolve(A_class, b_class) u_i = x_i[:(g.dim * g.num_cells)] p_i = x_i[(g.dim * g.num_cells):] state = {variable_f: p_i, variable_m: u_i} pp.set_state(d, state) # Check that the solution has converged to the expected, uniform steady state # dictated by the BCs. self.assertTrue( np.all(np.isclose(x_i, np.ones((g.dim + 1) * g.num_cells))))
"source": source_term, "mass_weight": phi * np.ones(g.num_cells), } # Assing (or update) parameters if time == 0: pp.initialize_data(g, d, param_key, specified_data) else: d[pp.PARAMETERS][param_key]["bc_values"] = bc_values d[pp.PARAMETERS][param_key]["source"] = source_term #%% Set initial states for g, d in gb: cc = g.cell_centers pp.set_state(d) pp.set_iterate(d) d[pp.STATE][pressure_var] = p_ex(cc[0], cc[1], time * np.ones_like(cc[0])) d[pp.STATE][pp.ITERATE][pressure_var] = d[pp.STATE][pressure_var].copy() #%% AD variables and manager grid_list = [g for g, _ in gb] dof_manager = pp.DofManager(gb) equation_manager = pp.ad.EquationManager(gb, dof_manager) p = equation_manager.merge_variables([(g, pressure_var) for g in grid_list]) p_m = p.previous_iteration() p_n = p.previous_timestep() #%% We let the density to be a non-linear function of the pressure def rho(p): if isinstance(p, pp.ad.Ad_array):
def update_all_apertures(self, to_iterate=True): """ To better control the aperture computation, it is done for the entire gb by a single function call. This also allows us to ensure the fracture apertures are updated before the intersection apertures are inherited. """ gb = self.gb for g, d in gb: apertures = np.ones(g.num_cells) if g.dim == (self.Nd - 1): # Initial aperture apertures *= self.initial_aperture # Reconstruct the displacement solution on the fracture g_h = gb.node_neighbors(g)[0] data_edge = gb.edge_props((g, g_h)) if pp.STATE in data_edge: u_mortar_local = self.reconstruct_local_displacement_jump( data_edge, from_iterate=to_iterate ) apertures -= u_mortar_local[-1].clip(max=0) if to_iterate: pp.set_iterate( d, {"aperture": apertures.copy(), "specific_volume": apertures.copy()}, ) else: state = { "aperture": apertures.copy(), "specific_volume": apertures.copy(), } pp.set_state(d, state) for g, d in gb: parent_apertures = [] num_parent = [] if g.dim < (self.Nd - 1): for edges in gb.edges_of_node(g): e = edges[0] g_h = e[0] if g_h == g: g_h = e[1] if g_h.dim == (self.Nd - 1): d_h = gb.node_props(g_h) if to_iterate: a_h = d_h[pp.STATE][pp.ITERATE]["aperture"] else: a_h = d_h[pp.STATE]["aperture"] a_h_face = np.abs(g_h.cell_faces) * a_h mg = gb.edge_props(e)["mortar_grid"] # Assumes g_h is master a_l = ( mg.mortar_to_slave_avg() * mg.master_to_mortar_avg() * a_h_face ) parent_apertures.append(a_l) num_parent.append(np.sum(mg.mortar_to_slave_int().A, axis=1)) else: raise ValueError("Intersection points not implemented in 3d") parent_apertures = np.array(parent_apertures) num_parents = np.sum(np.array(num_parent), axis=0) apertures = np.sum(parent_apertures, axis=0) / num_parents specific_volumes = np.power(apertures, self.Nd - g.dim) if to_iterate: pp.set_iterate( d, { "aperture": apertures.copy(), "specific_volume": specific_volumes.copy(), }, ) else: state = { "aperture": apertures.copy(), "specific_volume": specific_volumes.copy(), } pp.set_state(d, state) return apertures
def update_all_apertures(self, to_iterate=True): """ To better control the aperture computation, it is done for the entire gb by a single function call. This also allows us to ensure the fracture apertures are updated before the intersection apertures are inherited. The aperture of a fracture is the initial aperture + normal displacement jump. """ gb = self.gb for g, d in gb: apertures = np.ones(g.num_cells) if g.dim == (self.Nd - 1): # Initial aperture apertures *= self.initial_aperture # Reconstruct the displacement solution on the fracture g_h = gb.node_neighbors(g)[0] data_edge = gb.edge_props((g, g_h)) if pp.STATE in data_edge: u_mortar_local = self.reconstruct_local_displacement_jump( data_edge, from_iterate=to_iterate) # Magnitudes of normal and tangential components norm_u_n = np.absolute(u_mortar_local[-1]) norm_u_tau = np.linalg.norm(u_mortar_local[:-1], axis=0) # Add contributions slip_angle = np.pi / 2 apertures += norm_u_n + np.cos(slip_angle) * norm_u_tau if to_iterate: state = { "iterate_aperture": apertures, "iterate_specific_volume": apertures, } else: state = {"aperture": apertures, "specific_volume": apertures} pp.set_state(d, state) for g, d in gb: parent_apertures = [] if g.dim < (self.Nd - 1): for edges in gb.edges_of_node(g): e = edges[0] g_h = e[0] if g_h == g: g_h = e[1] if g_h.dim == (self.Nd - 1): d_h = gb.node_props(g_h) if to_iterate: a_h = d_h[pp.STATE]["iterate_aperture"] else: a_h = d_h[pp.STATE]["aperture"] a_h_face = np.abs(g_h.cell_faces) * a_h mg = gb.edge_props(e)["mortar_grid"] # Assumes g_h is master a_l = (mg.mortar_to_slave_int() * mg.master_to_mortar_int() * a_h_face) parent_apertures.append(a_l) else: raise ValueError( "Intersection points not implemented in 3d") parent_apertures = np.array(parent_apertures) apertures = np.mean(parent_apertures, axis=0) specific_volumes = np.product(parent_apertures, axis=0) if to_iterate: state = { "iterate_aperture": apertures, "iterate_specific_volume": specific_volumes, } else: state = { "aperture": apertures, "specific_volume": specific_volumes } pp.set_state(d, state) return apertures
def test_add_empty_state(self, empty_dict): """Add an empty state dictionary""" d = empty_dict pp.set_state(d) assert pp.STATE in d
def solve_mandel( grid_bucket, data_dictionary, parameter_keyword_flow, parameter_keyword_mechanics, variable_flow, variable_mechanics, assembler, boundary_conditions_dictionary, ): """ The problem is soved by looping through all the time levels. The ouput is a dictionary containing the solutions for pressure and displacement for all discrete times. Parameters: grid_bucket (PorePy object): Grid bucket data_dictionary (Dict): Model's data dictionary parameter_keyword_flow (String): Keyword for the flow parameter parameter_keyword_mechanics (String): Keyword for the mechanics parameter variable_flow (String): Primary variable of the flow problem variable_mechanics (String): Primary variable of the mechanics problem assembler (PorePy object): Assembler containing discretization boundary_conditions_dictionary (Dict): Dictionary containing boundary conditions Output sol (Dict): Dictionary containing numerial solutions """ # Renaming variables gb = grid_bucket g = gb.grids_of_dimension(2)[0] d = data_dictionary kw_f = parameter_keyword_flow kw_m = parameter_keyword_mechanics variable_f = variable_flow variable_m = variable_mechanics bc_dict = boundary_conditions_dictionary # Retrieving time values from the data dicitionary time_values = d[pp.PARAMETERS][kw_f]["time_values"] # Create a dictionary to store the solutions sol = { variable_f: np.zeros((len(time_values), g.num_cells)), variable_m: np.zeros((len(time_values), g.dim * g.num_cells)), } sol[variable_f][0] = d[pp.STATE][variable_f] sol[variable_m][0] = d[pp.STATE][variable_m] # For convience, create pressure and displacement variables pressure = d[pp.STATE][variable_f] displacement = d[pp.STATE][variable_m] # Assemble equations # NOTE: The structure of the linear system is time-independent assembler = pp.Assembler(gb) # Time loop for t in range(len(time_values) - 1): # Update data for current time pp.set_state(d, {variable_m: displacement, variable_f: pressure}) pp.set_state(d, {kw_m: {"bc_values": bc_dict[kw_m]["bc_values"][t]}}) d[pp.PARAMETERS][kw_m]["bc_values"] = bc_dict[kw_m]["bc_values"][t + 1] # Assemble matrix and rhs and solve A, b = assembler.assemble_matrix_rhs() x = sps.linalg.spsolve(A, b) # Distribute primary variables assembler.distribute_variable(x) displacement = d[pp.STATE][variable_m] pressure = d[pp.STATE][variable_f] # Save in solution dictionary sol["pressure"][t + 1] = pressure sol["displacement"][t + 1] = displacement # Print progress on console sys.stdout.write( "\rSimulation progress: %d%%" % (np.ceil((t / (len(time_values) - 2)) * 100)) ) sys.stdout.flush() sys.stdout.write("\nThe simulation has ended without any errors!\n") return sol
def test_assemble_biot(self): """ Test the assembly of the Biot problem using the assembler. The test checks whether the discretization matches that of the Biot class. """ gb = pp.meshing.cart_grid([], [2, 1]) g = gb.grids_of_dimension(2)[0] d = gb.node_props(g) # Parameters identified by two keywords kw_m = "mechanics" kw_f = "flow" variable_m = "displacement" variable_f = "pressure" bound_mech, bound_flow = self.make_boundary_conditions(g) initial_disp, initial_pressure, initial_state = self.make_initial_conditions( g, x0=0, y0=0, p0=0) state = { variable_f: initial_pressure, variable_m: initial_disp, kw_m: { "bc_values": np.zeros(g.num_faces * g.dim) }, } parameters_m = {"bc": bound_mech, "biot_alpha": 1} parameters_f = {"bc": bound_flow, "biot_alpha": 1} pp.initialize_default_data(g, d, kw_m, parameters_m) pp.initialize_default_data(g, d, kw_f, parameters_f) pp.set_state(d, state) # Discretize the mechanics related terms using the Biot class biot_discretizer = pp.Biot() biot_discretizer._discretize_mech(g, d) # Set up the structure for the assembler. First define variables and equation # term names. v_0 = variable_m v_1 = variable_f term_00 = "stress_divergence" term_01 = "pressure_gradient" term_10 = "displacement_divergence" term_11_0 = "fluid_mass" term_11_1 = "fluid_flux" term_11_2 = "stabilization" d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}} d[pp.DISCRETIZATION] = { v_0: { term_00: pp.Mpsa(kw_m) }, v_1: { term_11_0: pp.MassMatrix(kw_f), term_11_1: pp.Mpfa(kw_f), term_11_2: pp.BiotStabilization(kw_f), }, v_0 + "_" + v_1: { term_01: pp.GradP(kw_m) }, v_1 + "_" + v_0: { term_10: pp.DivU(kw_m) }, } # Assemble. Also discretizes the flow terms (fluid_mass and fluid_flux) general_assembler = pp.Assembler(gb) general_assembler.discretize(term_filter=["fluid_mass", "fluid_flux"]) A, b = general_assembler.assemble_matrix_rhs() # Re-discretize and assemble using the Biot class A_class, b_class = biot_discretizer.matrix_rhs(g, d, discretize=False) # Make sure the variable ordering of the matrix assembled by the assembler # matches that of the Biot class. grids = [g, g] variables = [v_0, v_1] A, b = permute_matrix_vector( A, b, general_assembler.block_dof, general_assembler.full_dof, grids, variables, ) # Compare the matrices and rhs vectors self.assertTrue(np.all(np.isclose(A.A, A_class.A))) self.assertTrue(np.all(np.isclose(b, b_class)))