def source_temperature(self, g) -> np.ndarray: """ Sources are handled by ScalarSource discretizations. The implicit scheme yields multiplication of the rhs by dt, but this is not incorporated in ScalarSource, hence we do it here. """ injection, production = self.source_flow_rates() # Injection well t_in = -70 weight = (self.density(g, dT=t_in * self.temperature_scale) * self.fluid.specific_heat_capacity(self.background_temp_C) * self.time_step / self.T_0_Kelvin) rhs = t_in * weight * injection * g.tags["well_cells"].clip(min=0) # Production well during phase III weight = (self.density(g) * self.fluid.specific_heat_capacity(self.background_temp_C) * self.time_step / self.T_0_Kelvin) dp, p1, p0 = self._variable_increment(g, "p", self.scalar_scale) lhs = (weight * production.clip(max=0) * g.tags["well_cells"].clip(max=0) / g.cell_volumes) # Set this directly into d to avoid additional return d = self.gb.node_props(g) pp.initialize_data(g, d, self.production_well_key, {"mass_weight": lhs}) return rhs
def _set_scalar_parameters(self) -> None: super()._set_scalar_parameters() tensor_scale = self.scalar_scale / self.length_scale**2 kappa = self.params.get("kappa", 1.) * tensor_scale mass_weight = self.params.get("s0", 1.) * self.scalar_scale for g, d in self.gb: bc = self._bc_type_scalar(g) bc_values = self._bc_values_scalar(g) source_values = self._source_scalar(g) specific_volume = self._specific_volume(g) diffusivity = pp.SecondOrderTensor(kappa * specific_volume * np.ones(g.num_cells)) alpha = self.params.get("alpha", 1.) * self._biot_alpha(g) pp.initialize_data( g, d, self.scalar_parameter_key, { "bc": bc, "bc_values": bc_values, "mass_weight": mass_weight * specific_volume, "biot_alpha": alpha, "source": source_values, "second_order_tensor": diffusivity, "time_step": self.time_step, "vector_source": np.zeros(g.num_cells * self.gb.dim_max()), "ambient_dimension": self.gb.dim_max(), }, )
def _assign_data(self, u_bot, u_top): """ u_bot and u_top are the u_h in the neighbouring cells on the two sides of the fracture. Ordering is hard-coded according to the grids defined in _make_grid """ u_h = np.zeros(self.nd * self.g_h.num_cells) if self.nd == 3: u_h[0:6] = u_bot.ravel(order="f") u_h[12:18] = u_top.ravel(order="f") else: u_h[2:6] = u_bot.ravel(order="f") u_h[10:14] = u_top.ravel(order="f") # Project to interface e = (self.g_h, self.g_l) d_j = self.gb.edge_props(e) mg = d_j["mortar_grid"] trace = np.abs(pp.fvutils.vector_divergence(self.g_h)).T u_j = mg.primary_to_mortar_avg(nd=self.nd) * trace * u_h pp.set_iterate(d_j, {self.model.mortar_displacement_variable: u_j}) # Parameters used by the propagation class for g, d in self.gb: if g.dim == self.nd: param = {"shear_modulus": self.mu, "poisson_ratio": self.poisson} else: param = {"SIFs_critical": np.ones((self.nd, g.num_faces))} pp.initialize_data(g, d, self.model.mechanics_parameter_key, param)
def set_data(self, data, data_time): self.data = data self.data_time = data_time for g, d in self.gb: param = {} unity = np.ones(g.num_cells) zeros = np.zeros(g.num_cells) empty = np.empty(0) alpha = self.data.get("alpha", 2) d["deviation_from_plane_tol"] = 1e-4 d["is_tangential"] = True d["tol"] = data["tol"] # assign permeability if g.dim < self.gb.dim_max(): aperture = d[pp.STATE]["aperture"] aperture_initial = d[pp.STATE]["aperture_initial"] k = np.power(aperture / aperture_initial, alpha + 1) * self.data["k_t"] perm = pp.SecondOrderTensor(kxx=k, kyy=1, kzz=1) else: porosity = d[pp.STATE]["porosity"] porosity_initial = d[pp.STATE]["porosity_initial"] k = np.power(porosity / porosity_initial, alpha) * self.data["k"] perm = pp.SecondOrderTensor(kxx=k, kyy=k, kzz=1) # no source term is assumed by the user param["second_order_tensor"] = perm param["source"] = zeros # Boundaries b_faces = g.tags["domain_boundary_faces"].nonzero()[0] if b_faces.size: labels, param["bc_values"] = data["bc"](g, data, data["tol"]) param["bc"] = pp.BoundaryCondition(g, b_faces, labels) else: param["bc_values"] = np.zeros(g.num_faces) param["bc"] = pp.BoundaryCondition(g, empty, empty) pp.initialize_data(g, d, self.model, param) for e, d in self.gb.edges(): mg = d["mortar_grid"] g_l = self.gb.nodes_of_edge(e)[0] check_P = mg.slave_to_mortar_avg() aperture = self.gb.node_props(g_l, pp.STATE)["aperture"] aperture_initial = self.gb.node_props(g_l, pp.STATE)["aperture_initial"] k = 2 * check_P * (np.power(aperture / aperture_initial, alpha - 1) * self.data["k_n"]) pp.initialize_data(mg, d, self.model, {"normal_diffusivity": k})
def set_data(self, data, data_time): self.data = data self.data_time = data_time for g, d in self.gb: param_diff = {} param_adv = {} param_mass = {} param_source = {} unity = np.ones(g.num_cells) zeros = np.zeros(g.num_cells) empty = np.empty(0) d["Aavatsmark_transmissibilities"] = True d["tol"] = data["tol"] # assign permeability if g.dim < self.gb.dim_max(): diff = data["d_t"] * d[pp.STATE]["aperture"] else: diff = data["d"] * d[pp.STATE]["porosity"] param_diff["second_order_tensor"] = pp.SecondOrderTensor(diff) param_mass["mass_weight"] = data["mass_weight"] / data_time["step"] param_source["source"] = zeros # Boundaries b_faces = g.tags["domain_boundary_faces"].nonzero()[0] if b_faces.size: labels_diff, labels_adv, bc_val = data["bc"](g, data, data["tol"]) param_diff["bc"] = pp.BoundaryCondition(g, b_faces, labels_diff) param_adv["bc"] = pp.BoundaryCondition(g, b_faces, labels_adv) else: bc_val = np.zeros(g.num_faces) param_diff["bc"] = pp.BoundaryCondition(g, empty, empty) param_adv["bc"] = pp.BoundaryCondition(g, empty, empty) param_diff["bc_values"] = bc_val param_adv["bc_values"] = bc_val models = [self.diff_name, self.adv_name, self.mass_name, self.source_name] params = [param_diff, param_adv, param_mass, param_source] for model, param in zip(models, params): pp.initialize_data(g, d, model, param) for e, d in self.gb.edges(): mg = d["mortar_grid"] g_l = self.gb.nodes_of_edge(e)[0] check_P = mg.slave_to_mortar_avg() aperture = self.gb.node_props(g_l, pp.STATE)["aperture"] diff = 2 * check_P * (self.data["d_n"] / aperture) models = [self.diff_name, self.adv_name] params = [{"normal_diffusivity": diff}, {}] for model, param in zip(models, params): pp.initialize_data(mg, d, model, param)
def _source_temperature(self, g) -> np.ndarray: """ Sources are handled by ScalarSource discretizations. The implicit scheme yields multiplication of the rhs by dt, but this is not incorporated in ScalarSource, hence we do it here. The sink (production well) is discretized using the MassMatrix discretization to ensure the extracted energy matches the current production well temperature. """ injection, production = self._source_flow_rates() # Injection well dT = -30 T_in = self.T_0_Kelvin + dT weight = (self._fluid_density(g, dT=dT * self.temperature_scale) * self.fluid.specific_heat_capacity(self.background_temp_C) * self.time_step / self.T_0_Kelvin) rhs = T_in * weight * injection * g.tags["well_cells"].clip(min=0) # Production well, discretized by MassMatrix on the lhs weight = (self._fluid_density(g) * self.fluid.specific_heat_capacity(self.background_temp_C) * self.time_step / self.T_0_Kelvin) lhs = (weight * production.clip(max=0) * g.tags["well_cells"].clip(max=0) / g.cell_volumes) # HACK: Set this directly into d to avoid additional return d = self.gb.node_props(g) pp.initialize_data(g, d, self.production_well_key, {"mass_weight": lhs}) return rhs
def set_data(self, data): self.data = data for g, d in self.gb: param = {} d["deviation_from_plane_tol"] = 1e-8 d["is_tangential"] = True # assign permeability k = data["k"](g, d, self) # no source term is assumed by the user param["second_order_tensor"] = pp.SecondOrderTensor(kxx=k, kyy=1, kzz=1) param["source"] = data["source"](g, d, self) param["vector_source"] = data["vector_source"](g, d, self) # Boundaries b_faces = g.tags["domain_boundary_faces"].nonzero()[0] if b_faces.size: labels, param["bc_values"] = data["bc"](g, data, data["tol"], self) param["bc"] = pp.BoundaryCondition(g, b_faces, labels) else: param["bc_values"] = np.zeros(g.num_faces) param["bc"] = pp.BoundaryCondition(g, np.empty(0), np.empty(0)) pp.initialize_data(g, d, self.model, param)
def _set_mechanics_parameters(self) -> None: super()._set_mechanics_parameters() gb = self.gb for g, d in gb: # Rock parameters lam = self.params.get("lame_lambda", 1.) * np.ones( g.num_cells) / self.scalar_scale mu = self.params.get("lame_mu", 1.) * np.ones( g.num_cells) / self.scalar_scale C = pp.FourthOrderTensor(mu, lam) # Define boundary condition bc = self._bc_type_mechanics(g) # BC and source values bc_val = self._bc_values_mechanics(g) source_val = self._source_mechanics(g) pp.initialize_data( g, d, self.mechanics_parameter_key, { "bc": bc, "bc_values": bc_val, "source": source_val, "fourth_order_tensor": C, "time_step": self.time_step, "biot_alpha": self._biot_alpha(g), "p_reference": np.zeros(g.num_cells), }, )
def set_permeability_from_aperture(self): """ Cubic law in fractures, rock permeability in the matrix. """ viscosity = self.fluid.dynamic_viscosity() / self.scalar_scale gb = self.gb key = self.scalar_parameter_key for g, d in gb: if g.dim < self.Nd: # Use cubic law in fractures apertures = self.compute_aperture(g) apertures_unscaled = apertures * self.length_scale k = np.power(apertures_unscaled, 2) / 12 kxx = k / viscosity / self.length_scale**2 else: # Use the rock permeability in the matrix kxx = (self.rock.PERMEABILITY / viscosity * np.ones(g.num_cells) / self.length_scale**2) K = pp.SecondOrderTensor(kxx) d[pp.PARAMETERS][key]["second_order_tensor"] = K # Normal permeability inherited from fracture for e, d in gb.edges(): mg = d["mortar_grid"] g_s, g_m = gb.nodes_of_edge(e) data_s = gb.node_props(g_s) a = self.compute_aperture(g_s) # We assume isotropic permeability in the fracture k_s = data_s[pp.PARAMETERS][ self.scalar_parameter_key]["second_order_tensor"].values[0, 0] kn = 2 * mg.slave_to_mortar_int() * np.divide(k_s, a) pp.initialize_data(mg, d, self.scalar_parameter_key, {"normal_diffusivity": kn})
def add_transport_data(self): """ Add the transport data to the grid bucket """ keyword = self.transport_keyword self.gb.add_node_props(["param", "is_tangential"]) for g, d in self.gb: param = {} d["is_tangential"] = True unity = np.ones(g.num_cells) zeros = np.zeros(g.num_cells) empty = np.empty(0) # Specific volume. specific_volume = np.power( self.param["aperture"], self.gb.dim_max() - g.dim ) param["specific_volume"] = specific_volume # Tangential diffusivity if g.dim == self.gb.dim_max(): kxx = self.param["Dm"] * unity else: kxx = self.param["Df"] * specific_volume * unity perm = pp.SecondOrderTensor(kxx) param["second_order_tensor"] = perm # Source term param["source"] = zeros # Mass weight param["mass_weight"] = specific_volume * self.param["porosity"] * unity # Boundaries bound_faces = g.get_boundary_faces() bc_val = np.zeros(g.num_faces) if bound_faces.size == 0: param["bc"] = pp.BoundaryCondition(g, empty, empty) else: bc_val = np.zeros(g.num_faces) bc_val[bound_faces] = 0 param["bc"] = pp.BoundaryCondition(g, bound_faces, "dir") param["bc_values"] = bc_val pp.initialize_data(g, d, keyword, param) # Normal diffusivity for e, d in self.gb.edges(): # Get higher dimensional grid g_h = self.gb.nodes_of_edge(e)[1] param_h = self.gb.node_props(g_h, pp.PARAMETERS) mg = d["mortar_grid"] specific_volume_h = ( np.ones(mg.num_cells) * param_h[keyword]["specific_volume"] ) dn = self.param["Dn"] * specific_volume_h / (self.param["aperture"] / 2) param = {"normal_diffusivity": dn} pp.initialize_data(e, d, keyword, param)
def _setup_simulation_tracer(gb, data, direction): min_coord = gb.bounding_box()[0][direction] max_coord = gb.bounding_box()[1][direction] parameter_keyword = "transport" for g, d in gb: param = d[pp.PARAMETERS] transport_parameter_dictionary = {} param.update_dictionaries(parameter_keyword, transport_parameter_dictionary) param.set_from_other(parameter_keyword, "flow", ["aperture"]) d[pp.DISCRETIZATION_MATRICES][parameter_keyword] = {} unity = np.ones(g.num_cells) if g.dim == gb.dim_max(): porosity = 0.2 * unity else: porosity = 0.8 * unity specified_parameters = {"mass_weight": porosity} bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] if bound_faces.size > 0: hit_out = np.where( np.abs(g.face_centers[direction, bound_faces] - max_coord) < 1e-8)[0] hit_in = np.where( np.abs(g.face_centers[direction, bound_faces] - min_coord) < 1e-8)[0] bound_type = np.array(["neu"] * bound_faces.size) bound_type[hit_out] = "dir" bound_type[hit_in] = "dir" bound = pp.BoundaryCondition(g, bound_faces.ravel("F"), bound_type) bc_val = np.zeros(g.num_faces) bc_val[bound_faces[hit_out]] = 0 bc_val[bound_faces[hit_in]] = 1 specified_parameters.update({"bc": bound, "bc_values": bc_val}) d["inlet_faces"] = bound_faces[hit_in] else: bc = pp.BoundaryCondition(g) specified_parameters.update({ "bc": bc, "bc_values": np.zeros(g.num_faces) }) pp.initialize_data(g, d, parameter_keyword, specified_parameters) for e, d in gb.edges(): d[pp.PARAMETERS].update_dictionaries(parameter_keyword, {}) d[pp.DISCRETIZATION_MATRICES][parameter_keyword] = {} return gb
def set_permeability(self): """ Cubic law in fractures, rock permeability in the matrix. If "blocking_perm" is present in self.params, this value is used for Fracture 2. """ # Viscosity has units of Pa s, and is consequently divided by the scalar scale. viscosity = self.fluid.dynamic_viscosity() / self.scalar_scale gb = self.gb key = self.scalar_parameter_key from_iterate = True blocking_perm = self.params.get("blocking_perm", None) for g, d in gb: if g.dim < self.Nd: # Set fracture permeability specific_volumes = self.specific_volumes(g, from_iterate) if d["node_number"] == 1 or blocking_perm is None: # Use cubic law in fractures. First compute the unscaled # permeability apertures = self.aperture(g, from_iterate=from_iterate) apertures_unscaled = apertures * self.length_scale k = np.power(apertures_unscaled, 2) / 12 / viscosity else: # Blocking and intersection k = blocking_perm d[pp.PARAMETERS][key]["perm_nu"] = k # Multiply with the cross-sectional area k = k * specific_volumes # Divide by fluid viscosity and scale back kxx = k / self.length_scale ** 2 else: # Use the rock permeability in the matrix kxx = ( self.rock.PERMEABILITY / viscosity * np.ones(g.num_cells) / self.length_scale ** 2 ) K = pp.SecondOrderTensor(kxx) d[pp.PARAMETERS][key]["second_order_tensor"] = K # Normal permeability inherited from the neighboring fracture g_l for e, d in gb.edges(): mg = d["mortar_grid"] g_l, _ = gb.nodes_of_edge(e) data_l = gb.node_props(g_l) a = self.aperture(g_l, from_iterate) V = self.specific_volumes(g_l, from_iterate) # We assume isotropic permeability in the fracture, i.e. the normal # permeability equals the tangential one k_s = data_l[pp.PARAMETERS][key]["second_order_tensor"].values[0, 0] # Division through half the aperture represents taking the (normal) gradient kn = mg.slave_to_mortar_int() * np.divide(k_s, a * V / 2) pp.initialize_data(mg, d, key, {"normal_diffusivity": kn})
def set_parameters_cell_basis(self, gb: pp.GridBucket, data: Dict): """ Assign parameters for the micro gb. Very simple for now, this must be improved. Args: gb (TYPE): the micro gb. Returns: None. """ # First initialize data for g, d in gb: d["Aavatsmark_transmissibilities"] = True domain_boundary = np.logical_and( g.tags["domain_boundary_faces"], np.logical_not(g.tags["fracture_faces"]), ) boundary_faces = np.where(domain_boundary)[0] if domain_boundary.size > 0: bc_type = boundary_faces.size * ["dir"] else: bc_type = np.empty(0) bc = pp.BoundaryCondition(g, boundary_faces, bc_type) if hasattr(g, "face_on_macro_bound"): micro_ind = g.face_on_macro_bound macro_ind = g.macro_face_ind bc.is_neu[micro_ind] = data["bc_macro"]["bc"].is_neu[macro_ind] bc.is_dir[micro_ind] = data["bc_macro"]["bc"].is_dir[macro_ind] param = {"bc": bc} perm = data["g_data"](g)["second_order_tensor"] param["second_order_tensor"] = perm param["specific_volume"] = data["g_data"](g)["specific_volume"] # Use python inverter for mpfa for small problems, where it does not pay off # to fire up numba. The set threshold value is somewhat randomly picked. if g.num_cells < 100: param["mpfa_inverter"] = "python" pp.initialize_default_data(g, d, self.keyword, param) for e, d in gb.edges(): mg = d["mortar_grid"] g1, g2 = gb.nodes_of_edge(e) param = {} if not hasattr(g1, "is_auxiliary") or not g1.is_auxiliary: check_P = mg.secondary_to_mortar_avg() param.update(data["e_data"](mg, g1, g2, check_P)) pp.initialize_data(mg, d, self.keyword, param)
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 _set_scalar_parameters(self) -> None: tensor_scale = self.scalar_scale / self.length_scale ** 2 kappa = 1 * tensor_scale mass_weight = 1 * self.scalar_scale for g, d in self.gb: bc = self._bc_type_scalar(g) bc_values = self._bc_values_scalar(g) source_values = self._source_scalar(g) specific_volume = self._specific_volume(g) diffusivity = pp.SecondOrderTensor( kappa * specific_volume * np.ones(g.num_cells) ) alpha = self._biot_alpha(g) pp.initialize_data( g, d, self.scalar_parameter_key, { "bc": bc, "bc_values": bc_values, "mass_weight": mass_weight * specific_volume, "biot_alpha": alpha, "source": source_values, "second_order_tensor": diffusivity, "time_step": self.time_step, }, ) # Assign diffusivity in the normal direction of the fractures. for e, data_edge in self.gb.edges(): g_l, g_h = self.gb.nodes_of_edge(e) mg = data_edge["mortar_grid"] a_l = self._aperture(g_l) # Take trace of and then project specific volumes from g_h v_h = ( mg.primary_to_mortar_avg() * np.abs(g_h.cell_faces) * self._specific_volume(g_h) ) # Division by a/2 may be thought of as taking the gradient in the normal # direction of the fracture. normal_diffusivity = kappa * 2 / (mg.secondary_to_mortar_avg() * a_l) # The interface flux is to match fluxes across faces of g_h, # and therefore need to be weighted by the corresponding # specific volumes normal_diffusivity *= v_h data_edge = pp.initialize_data( e, data_edge, self.scalar_parameter_key, {"normal_diffusivity": normal_diffusivity}, )
def assign_parameters(time): nf = g.num_faces nc = g.num_cells fn = g.face_normals fc = g.face_centers cc = g.cell_centers V = g.cell_volumes # Permeability tensor perm = pp.SecondOrderTensor(K * np.ones(nc)) # Boundary condtions top = np.where(np.abs(fc[1] - 1) < 1e-5)[0] bottom = np.where(np.abs(fc[1]) < 1e-5)[0] left = np.where(np.abs(fc[0]) < 1e-5)[0] right = np.where(np.abs(fc[0] - 1) < 1e-5)[0] bc_faces = g.get_boundary_faces() bc_type = np.array(bc_faces.size * ["neu"]) bc_type[np.in1d(bc_faces, left)] = "dir" bc_type[np.in1d(bc_faces, right)] = "dir" bc = pp.BoundaryCondition(g, faces=bc_faces, cond=bc_type) bc_values = np.zeros(g.num_faces) pf = p_ex(fc[0], fc[1], time * np.ones(nf)) # exact face pressures adv_f = advec_ex(fc[0], fc[1], time * np.ones(nf)) # exact advective velocities Adv_f = adv_f[0] * fn[0] + adv_f[1] * fn[1] # exact advective fluxes bc_values[top] = np.abs(Adv_f[top]) bc_values[bottom] = np.abs(Adv_f[bottom]) bc_values[left] = pf[left] bc_values[right] = pf[right] source_term = f_ex(cc[0], cc[1], time * np.ones(nc)) * V # Initialize data dictionary specified_data = { "second_order_tensor": perm, "bc": bc, "bc_values": bc_values, "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
def add_flow_data(self): """ Add the flow data to the grid bucket """ keyword = self.flow_keyword # Iterate over nodes and assign data for g, d in self.gb: param = {} # Shorthand notation unity = np.ones(g.num_cells) zeros = np.zeros(g.num_cells) # Specific volume. specific_volume = np.power( self.param["aperture"], self.gb.dim_max() - g.dim ) param["specific_volume"] = specific_volume # Tangential permeability if g.dim == self.gb.dim_max(): kxx = self.param["km"] else: kxx = self.param["kf"] * specific_volume perm = pp.SecondOrderTensor(kxx * unity) param["second_order_tensor"] = perm # Source term param["source"] = zeros # Boundaries bound_faces = g.get_boundary_faces() bc_val = np.zeros(g.num_faces) param["bc"] = pp.BoundaryCondition(g, bound_faces, "dir") param["bc_values"] = bc_val pp.initialize_data(g, d, keyword, param) # Loop over edges and set coupling parameters for e, d in self.gb.edges(): # Get higher dimensional grid g_h = self.gb.nodes_of_edge(e)[1] param_h = self.gb.node_props(g_h, pp.PARAMETERS) mg = d["mortar_grid"] specific_volume_h = ( np.ones(mg.num_cells) * param_h[keyword]["specific_volume"] ) kn = self.param["kn"] * specific_volume_h / (self.param["aperture"] / 2) param = {"normal_diffusivity": kn} pp.initialize_data(e, d, keyword, param)
def set_scalar_parameters(self): gb = self.gb self.Nd = gb.dim_max() tensor_scale = self.scalar_scale / self.length_scale**2 kappa = 1 * tensor_scale mass_weight = 1 alpha = self.biot_alpha() for g, d in gb: bc = self.bc_type_scalar(g) bc_values = self.bc_values_scalar(g) source_values = self.source_scalar(g) a = self.compute_aperture(g) specific_volume = np.power(a, self.gb.dim_max() - g.dim) * np.ones( g.num_cells) diffusivity = pp.SecondOrderTensor(kappa * specific_volume * np.ones(g.num_cells)) pp.initialize_data( g, d, self.scalar_parameter_key, { "bc": bc, "bc_values": bc_values, "mass_weight": mass_weight * specific_volume, "biot_alpha": alpha, "source": source_values, "second_order_tensor": diffusivity, "time_step": self.time_step, }, ) # Assign diffusivity in the normal direction of the fractures. for e, data_edge in self.gb.edges(): g1, _ = self.gb.nodes_of_edge(e) a = self.compute_aperture(g1) mg = data_edge["mortar_grid"] normal_diffusivity = 2 / kappa * mg.slave_to_mortar_int() * a data_edge = pp.initialize_data( e, data_edge, self.scalar_parameter_key, {"normal_diffusivity": normal_diffusivity}, )
def _set_data_nodes(self): """ Method to set the data for the nodes (grids) of the grid bucket """ for g, d in self.gb: param = {} param_mass = {} unity = np.ones(g.num_cells) zeros = np.zeros(g.num_cells) empty = np.empty(0) d["tol"] = self.data["tol"] # Boundaries b_faces = g.tags["domain_boundary_faces"].nonzero()[0] if b_faces.size: labels, bc_val = self.bc_flag(self.gb, g, self.data, self.data["tol"]) param["bc"] = pp.BoundaryCondition(g, b_faces, labels) else: bc_val = np.zeros(g.num_faces) param["bc"] = pp.BoundaryCondition(g, empty, empty) param["bc_values"] = bc_val pp.initialize_data(g, d, self.discr_name, param) d[pp.PARAMETERS][self.discr_name][self.flux] = d[pp.STATE][ self.flow.flux] param_mass["mass_weight"] = 1. / self.time_step pp.initialize_data(g, d, self.mass_name, param_mass) # set the primary variable d[pp.PRIMARY_VARIABLES] = {self.variable: {"cells": 1}} # set the discretization node_discr = self.discr(self.discr_name) node_mass = self.mass(self.mass_name) d[pp.DISCRETIZATION] = { self.variable: { self.discr_name: node_discr, self.mass_name: node_mass } } # set the discretization matrix d[pp.DISCRETIZATION_MATRICES] = { self.discr_name: {}, self.mass_name: {} }
def set_mechanics_parameters(self) -> None: """ Set the parameters for the simulation. """ super().set_mechanics_parameters() for g, d in self.gb: if g.dim == self.Nd: pp.initialize_data( g, d, self.mechanics_temperature_parameter_key, { "biot_alpha": self.biot_beta(g), "bc_values": self.bc_values_mechanics(g), }, )
def set_params_disrcetize(g, ambient_dim, method, periodic=False): g.compute_geometry() keyword = "flow" if periodic: south = g.face_centers[1] < np.min(g.nodes[1]) + 1e-8 north = g.face_centers[1] > np.max(g.nodes[1]) - 1e-8 bc = pp.BoundaryCondition(g, north + south, "per") south_idx = np.argwhere(south).ravel() north_idx = np.argwhere(north).ravel() bc.set_periodic_map(np.vstack((south_idx, north_idx))) else: bc = pp.BoundaryCondition(g) k = pp.SecondOrderTensor(np.ones(g.num_cells)) params = { "bc": bc, "second_order_tensor": k, "mpfa_inverter": "python", "ambient_dimension": ambient_dim, } data = pp.initialize_data(g, {}, keyword, params) if method == "mpfa": discr = pp.Mpfa(keyword) elif method == "tpfa": discr = pp.Tpfa(keyword) discr.discretize(g, data) flux = data[pp.DISCRETIZATION_MATRICES][keyword][discr.flux_matrix_key] vector_source = data[pp.DISCRETIZATION_MATRICES][keyword][ discr.vector_source_matrix_key] div = pp.fvutils.scalar_divergence(g) return flux, vector_source, div
def _set_scalar_parameters(self) -> None: """Set parameters for the pressure / mass conservation equation.""" # Most values are handled as if this was a poro-elastic problem super()._set_scalar_parameters() for g, d in self.gb: t2s_coupling = ( self._scalar_temperature_coupling_coefficient(g) * self._specific_volume(g) * self.temperature_scale ) pp.initialize_data( g, d, self.t2s_parameter_key, {"mass_weight": t2s_coupling, "time_step": self.time_step}, )
def set_scalar_parameters(self): for g, d in self.gb: a = self.compute_aperture(g) specific_volumes = self.specific_volumes(g) # Define boundary conditions for flow bc = self.bc_type_scalar(g) # Set boundary condition values bc_values = self.bc_values_scalar(g) biot_coefficient = self.biot_alpha(g) compressibility = self.fluid.COMPRESSIBILITY mass_weight = compressibility * self.porosity(g) if g.dim == self.Nd: mass_weight += (biot_coefficient - self.porosity(g)) / self.rock.BULK_MODULUS mass_weight *= self.scalar_scale * specific_volumes pp.initialize_data( g, d, self.scalar_parameter_key, { "bc": bc, "bc_values": bc_values, "mass_weight": mass_weight, "biot_alpha": biot_coefficient, "time_step": self.time_step, "source": self.aperture_update(g, d), }, ) t2s_coupling = (self.scalar_temperature_coupling_coefficient(g) * specific_volumes * self.temperature_scale) pp.initialize_data( g, d, self.t2s_parameter_key, { "mass_weight": t2s_coupling, "time_step": self.time_step }, ) self.set_permeability_from_aperture()
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 _set_mechanics_parameters(self) -> None: """ Set the parameters for the simulation. """ super()._set_mechanics_parameters() for g, d in self.gb: if g.dim == self._Nd: pp.initialize_data( g, d, self.mechanics_temperature_parameter_key, { "biot_alpha": self._biot_beta(g), "bc_values": self._bc_values_mechanics(g), "p_reference": np.zeros(g.num_cells), }, )
def test_assembly(self): # Test the assemble_matrix_rhs method, with vector sources included. # The rest of the setup is identical to that in # self.test_2d_horizontal_ambient_dim_2() # Random size of the domain dx = np.random.rand(1)[0] # 2x2 grid of the random size g = pp.CartGrid([2, 2], [2 * dx, 2 * dx]) # Hhe vector source is a 2-vector per cell ambient_dim = 2 g.compute_geometry() bc = pp.BoundaryCondition(g) k = pp.SecondOrderTensor(np.ones(g.num_cells)) # Make source strength another random number grav_strength = np.random.rand(1) # introduce a source term in x-direction g_x = np.zeros(g.num_cells * ambient_dim) g_x[::ambient_dim] = -1 * grav_strength params = { "bc": bc, "bc_values": np.zeros(g.num_faces), "second_order_tensor": k, "mpfa_inverter": "python", "ambient_dimension": ambient_dim, "vector_source": g_x, } data = pp.initialize_data(g, {}, self.keyword, params) discr = pp.Mpfa(self.keyword) discr.discretize(g, data) A, b = discr.assemble_matrix_rhs(g, data) p_x = np.linalg.pinv(A.toarray()).dot(b) # The solution should be higher in the first x-row of cells, with magnitude # controlled by grid size and source stregth self.assertTrue(np.allclose(p_x[0] - p_x[1], dx * grav_strength)) self.assertTrue(np.allclose(p_x[2] - p_x[3], dx * grav_strength)) # The solution should be equal for equal x-coordinate self.assertTrue(np.allclose(p_x[0], p_x[2])) self.assertTrue(np.allclose(p_x[1], p_x[3])) data[pp.STATE] = {"pressure": p_x} pp.fvutils.compute_darcy_flux(g, data=data)
def set_scalar_parameters(self) -> None: """ Set parameters for the pressure / mass conservation equation. """ # Most values are handled as if this was a poro-elastic problem super().set_scalar_parameters() for g, d in self.gb: a = self.compute_aperture(g) specific_volume = np.power(a, self.gb.dim_max() - g.dim) * np.ones( g.num_cells) t2s_coupling = (self.scalar_temperature_coupling_coefficient(g) * specific_volume * self.temperature_scale) pp.initialize_data( g, d, self.t2s_parameter_key, { "mass_weight": t2s_coupling, "time_step": self.time_step }, )
def set_mechanics_parameters(self): gb = self.gb for g, d in gb: if g.dim == self.Nd: # Rock parameters rock = self.rock lam = rock.LAMBDA * np.ones(g.num_cells) / self.scalar_scale mu = rock.MU * np.ones(g.num_cells) / self.scalar_scale C = pp.FourthOrderTensor(mu, lam) bc = self.bc_type_mechanics(g) bc_values = self.bc_values_mechanics(g) sources = self.source_mechanics(g) pp.initialize_data( g, d, self.mechanics_parameter_key, { "bc": bc, "bc_values": bc_values, "source": sources, "fourth_order_tensor": C, "biot_alpha": self.biot_alpha() }, ) d[pp.PARAMETERS].set_from_other( self.mechanics_parameter_key, self.scalar_parameter_key, ["aperture", "time_step"], ) elif g.dim == self.Nd - 1: friction = self._set_friction_coefficient(g) pp.initialize_data( g, d, self.mechanics_parameter_key, {"friction_coefficient": friction}, ) pp.initialize_data(g, d, self.mechanics_parameter_key, {}) for e, d in gb.edges(): mg = d["mortar_grid"] # Parameters for the surface diffusion. No clue about values mu = self.rock.MU lmbda = self.rock.LAMBDA pp.initialize_data(mg, d, self.mechanics_parameter_key, { "mu": mu, "lambda": lmbda })
def _set_data_nodes(self): """ Method to set the data for the nodes (grids) of the grid bucket """ for g, d in self.gb: param = {} d["Aavatsmark_transmissibilities"] = True d["tol"] = self.data["tol"] # assign permeability if g.dim == 2: perm = pp.SecondOrderTensor(kxx=self.data["kxx"], kyy=self.data["kyy"], kxy=self.data["kxy"]) param["second_order_tensor"] = perm elif g.dim == 1: k = self.data["k"][g.frac_num] perm = pp.SecondOrderTensor(kxx=k*np.ones(g.num_cells)) param["second_order_tensor"] = perm # Boundaries b_faces = g.tags["domain_boundary_faces"].nonzero()[0] if b_faces.size: labels, bc_val = self.bc_flag(self.gb, g, self.data, self.data["tol"]) param["bc"] = pp.BoundaryCondition(g, b_faces, labels) else: bc_val = np.zeros(g.num_faces) param["bc"] = pp.BoundaryCondition(g, np.empty(0), np.empty(0)) param["bc_values"] = bc_val pp.initialize_data(g, d, self.model, param) # set the primary variable d[pp.PRIMARY_VARIABLES] = {self.variable: {"cells": 1}} # set the discretization node_discr = self.discr(self.model) d[pp.DISCRETIZATION] = {self.variable: {self.discr_name: node_discr}} # set the discretization matrix d[pp.DISCRETIZATION_MATRICES] = {self.model: {}}
def vector_source(self): """ Set gravity as a vector source term in the fluid flow equations""" if not self.params.gravity: return gb = self.gb scalar_key = self.scalar_parameter_key ls, ss = self.params.length_scale, self.params.scalar_scale for g, d in gb: # minus sign to convert from positive z downward (depth) to positive upward. gravity = -pp.GRAVITY_ACCELERATION * self.density(g) * (ls / ss) vector_source = np.zeros((self.Nd, g.num_cells)) vector_source[-1, :] = gravity vector_params = { "vector_source": vector_source.ravel("F"), "ambient_dimension": self.Nd, } pp.initialize_data(g, d, scalar_key, vector_params) for e, de in gb.edges(): mg: pp.MortarGrid = de["mortar_grid"] g_l, _ = gb.nodes_of_edge(e) a_l = self.aperture(g_l, scaled=True) # Compute gravity on the slave grid rho_g = -pp.GRAVITY_ACCELERATION * self.density(g_l) * (ls / ss) # Multiply by (a/2) to "cancel out" the normal gradient of the diffusivity # (see also self.set_permeability_from_aperture) gravity_l = rho_g * (a_l / 2) # Take the gravity from the slave grid and project to the interface gravity_mg = mg.slave_to_mortar_avg() * gravity_l vector_source = np.zeros((self.Nd, mg.num_cells)) vector_source[-1, :] = gravity_mg gravity = vector_source.ravel("F") pp.initialize_data(mg, de, scalar_key, {"vector_source": gravity})