def test_biot(self): self.setup() g, g_larger = self.g, self.g_larger specified_data = {"inverter": "python", "biot_alpha": 1} mechanics_keyword = "mechanics" flow_keyword = "flow" data_small = pp.initialize_default_data( g, {}, mechanics_keyword, specified_parameters=specified_data) def add_flow_data(g, d): d[pp.DISCRETIZATION_MATRICES][flow_keyword] = {} d[pp.PARAMETERS][flow_keyword] = { "bc": pp.BoundaryCondition(g), "second_order_tensor": pp.SecondOrderTensor(np.ones(g.num_cells)), "bc_values": np.zeros(g.num_faces), "inverter": "python", "mass_weight": np.ones(g.num_cells), "biot_alpha": 1, } discr = pp.Biot(mechanics_keyword=mechanics_keyword, flow_keyword=flow_keyword) add_flow_data(g, data_small) discr.discretize(g, data_small) # Discretization on a small problem # Perturb one node g_larger.nodes[0, 2] += 0.2 # Faces that have their geometry changed update_faces = np.array([2, 21, 22]) # Perturb the permeability in some cells on the larger grid mu, lmbda = np.ones(g_larger.num_cells), np.ones(g_larger.num_cells) high_coeff_cells = np.array([7, 12]) stiff_larger = pp.FourthOrderTensor(mu, lmbda) specified_data_larger = { "fourth_order_tensor": stiff_larger, "biot_alpha": 1 } # Do a full discretization on the larger grid data_full = pp.initialize_default_data( g_larger, {}, mechanics_keyword, specified_parameters=specified_data_larger) add_flow_data(g_larger, data_full) discr.discretize(g_larger, data_full) # Cells that will be marked as updated, either due to changed parameters or # the newly defined topology update_cells = np.union1d(self.new_cells, high_coeff_cells) updates = { "modified_cells": update_cells, # "modified_faces": update_faces, "map_cells": self.cell_map, "map_faces": self.face_map, } # Data dictionary for the two-step discretization data_partial = pp.initialize_default_data( g_larger, {}, mechanics_keyword, specified_parameters=specified_data_larger) add_flow_data(g_larger, data_partial) data_partial["update_discretization"] = updates self._update_and_compare( data_small, data_partial, data_full, g_larger, keywords=[flow_keyword, mechanics_keyword], discr=discr, )
def test_one_cell_a_time_node_keyword(self): # Update one and one cell, and verify that the result is the same as # with a single computation. The test is similar to what will happen # with a memory-constrained splitting. g = pp.CartGrid([3, 3]) g.compute_geometry() # Assign random permeabilities, for good measure np.random.seed(42) mu = np.random.random(g.num_cells) lmbda = np.random.random(g.num_cells) stiffness = pp.FourthOrderTensor(mu=mu, lmbda=lmbda) stress = sps.csr_matrix((g.num_faces * g.dim, g.num_cells * g.dim)) bound_stress = sps.csr_matrix( (g.num_faces * g.dim, g.num_faces * g.dim)) faces_covered = np.zeros(g.num_faces, np.bool) bnd = pp.BoundaryConditionVectorial(g) specified_data = { "fourth_order_tensor": stiffness, "bc": bnd, "inverter": "python", } keyword = "mechanics" data = pp.initialize_default_data(g, {}, keyword, specified_parameters=specified_data) discr = pp.Mpsa(keyword) discr.discretize(g, data) stress_full = data[pp.DISCRETIZATION_MATRICES][keyword][ discr.stress_matrix_key] bound_stress_full = data[pp.DISCRETIZATION_MATRICES][keyword][ discr.bound_stress_matrix_key] cn = g.cell_nodes() for ci in range(g.num_cells): ind = np.zeros(g.num_cells) ind[ci] = 1 nodes = np.squeeze(np.where(cn * ind > 0)) data[pp.PARAMETERS][keyword]["specified_nodes"] = nodes discr.discretize(g, data) partial_stress = data[pp.DISCRETIZATION_MATRICES][keyword][ discr.stress_matrix_key] partial_bound = data[pp.DISCRETIZATION_MATRICES][keyword][ discr.bound_stress_matrix_key] active_faces = data[pp.PARAMETERS][keyword]["active_faces"] if np.any(faces_covered): del_faces = self.expand_indices_nd( np.where(faces_covered)[0], g.dim) # del_faces is already expanded, set dimension to 1 pp.fvutils.remove_nonlocal_contribution( del_faces, 1, partial_stress, partial_bound) faces_covered[active_faces] = True stress += partial_stress bound_stress += partial_bound self.assertTrue((stress_full - stress).max() < 1e-8) self.assertTrue((stress_full - stress).min() > -1e-8) self.assertTrue((bound_stress - bound_stress_full).max() < 1e-8) self.assertTrue((bound_stress - bound_stress_full).min() > -1e-8)
def _test_one_cell_a_time_node_keyword(self): # EK: this test makes less sense after the notion of partial updates were # changed (git commit 41f754940). Decactive the test for now, pending an update # of the notion of discretization updates for FV methods. # Update one and one cell, and verify that the result is the same as # with a single computation. The test is similar to what will happen # with a memory-constrained splitting. g = pp.CartGrid([3, 3]) g.compute_geometry() # Assign random permeabilities, for good measure np.random.seed(42) mu = np.random.random(g.num_cells) lmbda = np.random.random(g.num_cells) stiffness = pp.FourthOrderTensor(mu=mu, lmbda=lmbda) nd = g.dim nf = g.num_faces nc = g.num_cells grad_p = sps.csr_matrix((nf * nd, nc)) div_u = sps.csr_matrix((nc, nc * nd)) bound_div_u = sps.csr_matrix((nc, nf * nd)) stab = sps.csr_matrix((nc, nc)) bound_displacement_pressure = sps.csr_matrix((nf * nd, nc)) faces_covered = np.zeros(g.num_faces, np.bool) cells_covered = np.zeros(g.num_cells, np.bool) bnd = pp.BoundaryConditionVectorial(g) specified_data = { "fourth_order_tensor": stiffness, "bc": bnd, # "inverter": "python", "biot_alpha": 1, } keyword_mech = "mechanics" keyword_flow = "flow" data = pp.initialize_default_data(g, {}, keyword_mech, specified_parameters=specified_data) data = pp.initialize_default_data(g, data, keyword_flow) discr = pp.Biot() discr.discretize(g, data) div_u_full = data[pp.DISCRETIZATION_MATRICES][keyword_flow][ discr.div_u_matrix_key] bound_div_u_full = data[pp.DISCRETIZATION_MATRICES][keyword_flow][ discr.bound_div_u_matrix_key] stab_full = data[pp.DISCRETIZATION_MATRICES][keyword_flow][ discr.stabilization_matrix_key] grad_p_full = data[pp.DISCRETIZATION_MATRICES][keyword_mech][ discr.grad_p_matrix_key] bound_pressure_full = data[pp.DISCRETIZATION_MATRICES][keyword_mech][ discr.bound_pressure_matrix_key] cn = g.cell_nodes() for ci in range(g.num_cells): ind = np.zeros(g.num_cells) ind[ci] = 1 nodes = np.squeeze(np.where(cn * ind > 0)) data[pp.PARAMETERS][keyword_mech]["specified_nodes"] = nodes discr.discretize(g, data) partial_div_u = data[pp.DISCRETIZATION_MATRICES][keyword_flow][ discr.div_u_matrix_key] partial_bound_div_u = data[pp.DISCRETIZATION_MATRICES][ keyword_flow][discr.bound_div_u_matrix_key] partial_grad_p = data[pp.DISCRETIZATION_MATRICES][keyword_mech][ discr.grad_p_matrix_key] partial_stab = data[pp.DISCRETIZATION_MATRICES][keyword_flow][ discr.stabilization_matrix_key] partial_bound_pressure = data[pp.DISCRETIZATION_MATRICES][ keyword_mech][discr.bound_pressure_matrix_key] active_faces = data[pp.PARAMETERS][keyword_mech]["active_faces"] if np.any(faces_covered): del_faces = self.expand_indices_nd( np.where(faces_covered)[0], g.dim) del_cells = np.where(cells_covered)[0] pp.fvutils.remove_nonlocal_contribution( del_cells, 1, partial_div_u, partial_bound_div_u, partial_stab) # del_faces is already expanded, set dimension to 1 pp.fvutils.remove_nonlocal_contribution( del_faces, 1, partial_grad_p, partial_bound_pressure) faces_covered[active_faces] = True cells_covered[ci] = True div_u += partial_div_u bound_div_u += partial_bound_div_u grad_p += partial_grad_p stab += partial_stab bound_displacement_pressure += partial_bound_pressure self.assertTrue((div_u_full - div_u).max() < 1e-8) self.assertTrue((bound_div_u_full - bound_div_u).min() > -1e-8) self.assertTrue((grad_p_full - grad_p).max() < 1e-8) self.assertTrue((stab_full - stab).min() > -1e-8) self.assertTrue( (bound_displacement_pressure - bound_pressure_full).min() > -1e-8)
def conv_test(n): # Analytical solution def mandel_solution(g, Nx, Ny, times, F, B, nu_u, nu, c_f, mu_s): # Some needed parameters a = np.max(g.face_centers[0]) # a = Lx x_cntr = g.cell_centers[0][:Nx] # [m] vector of x-centers y_cntr = g.cell_centers[1][::Nx] # [m] vector of y-centers # Solutions to tan(x) - ((1-nu)/(nu_u-nu)) x = 0 """ This is somehow tricky, we have to solve the equation numerically in order to find all the positive solutions to the equation. Later we will use them to compute the infinite sums. Experience has shown that 200 roots are more than enough to achieve accurate results. Note that we find the roots using the bisection method. """ f = lambda x: np.tan(x) - ( (1 - nu) / (nu_u - nu)) * x # define the algebraic eq. as a lambda function n_series = 200 # number of roots a_n = np.zeros(n_series) # initializing roots array x0 = 0 # initial point for i in range(0, len(a_n)): a_n[i] = opt.bisect( f, # function x0 + np.pi / 4, # left point x0 + np.pi / 2 - 10000 * 2.2204e-16, # right point (a tiny bit less than pi/2) xtol=1e-30, # absolute tolerance rtol=1e-15 # relative tolerance ) x0 += np.pi # apply a phase change of pi to get the next root # Creating dictionary to store analytical solutions mandel_sol = dict() mandel_sol['p'] = np.zeros((len(times), len(x_cntr))) mandel_sol['u_x'] = np.zeros((len(times), len(x_cntr))) mandel_sol['u_y'] = np.zeros((len(times), len(y_cntr))) mandel_sol['sigma_yy'] = np.zeros((len(times), len(x_cntr))) # Terms needed to compute the solutions (these are constants) p0 = (2 * F * B * (1 + nu_u)) / (3 * a) ux0_1 = ((F * nu) / (2 * mu_s * a)) ux0_2 = -((F * nu_u) / (mu_s * a)) ux0_3 = F / mu_s uy0_1 = (-F * (1 - nu)) / (2 * mu_s * a) uy0_2 = (F * (1 - nu_u) / (mu_s * a)) sigma0_1 = -F / a sigma0_2 = (-2 * F * B * (nu_u - nu)) / (a * (1 - nu)) sigma0_3 = (2 * F) / a # Saving solutions for the initial conditions mandel_sol['p'][0] = ((F * B * (1 + nu_u)) / (3 * a)) * np.ones(Nx) mandel_sol['u_x'][0] = (F * nu_u * x_cntr) / (2 * mu_s * a) mandel_sol['u_y'][0] = ((-F * (1 - nu_u)) / (2 * mu_s * a)) * y_cntr # Storing solutions for the subsequent time steps for ii in range(1, len(times)): # Analytical Pressures p_sum = 0 for n in range(len(a_n)): p_sum += (((np.sin(a_n[n])) / (a_n[n] - (np.sin(a_n[n]) * np.cos(a_n[n])))) * (np.cos((a_n[n] * x_cntr) / a) - np.cos(a_n[n])) * np.exp((-(a_n[n]**2) * c_f * times[ii]) / (a**2))) mandel_sol['p'][ii] = p0 * p_sum # Analytical horizontal displacements ux_sum1 = 0 ux_sum2 = 0 for n in range(len(a_n)): ux_sum1 += ((np.sin(a_n[n]) * np.cos(a_n[n])) / (a_n[n] - np.sin(a_n[n]) * np.cos(a_n[n])) * np.exp((-(a_n[n]**2) * c_f * times[ii]) / (a**2))) ux_sum2 += ((np.cos(a_n[n]) / (a_n[n] - (np.sin(a_n[n]) * np.cos(a_n[n])))) * np.sin(a_n[n] * (x_cntr / a)) * np.exp( (-(a_n[n]**2) * c_f * times[ii]) / (a**2))) mandel_sol['u_x'][ii] = ( ux0_1 + ux0_2 * ux_sum1) * x_cntr + ux0_3 * ux_sum2 # Analytical vertical displacements uy_sum = 0 for n in range(len(a_n)): uy_sum += (((np.sin(a_n[n]) * np.cos(a_n[n])) / (a_n[n] - np.sin(a_n[n]) * np.cos(a_n[n]))) * np.exp((-(a_n[n]**2) * c_f * times[ii]) / (a**2))) mandel_sol['u_y'][ii] = (uy0_1 + uy0_2 * uy_sum) * y_cntr # Analitical vertical stress sigma_sum1 = 0 sigma_sum2 = 0 for n in range(len(a_n)): sigma_sum1 += (((np.sin(a_n[n])) / (a_n[n] - (np.sin(a_n[n]) * np.cos(a_n[n])))) * np.cos(a_n[n] * (x_cntr / a)) * np.exp( (-(a_n[n]**2) * c_f * times[ii]) / (a**2))) sigma_sum2 += (((np.sin(a_n[n]) * np.cos(a_n[n])) / (a_n[n] - np.sin(a_n[n]) * np.cos(a_n[n]))) * np.exp( (-(a_n[n]**2) * c_f * times[ii]) / (a**2))) mandel_sol['sigma_yy'][ii] = (sigma0_1 + sigma0_2 * sigma_sum1) + ( sigma0_3 * sigma_sum2) return mandel_sol # Computing the initial condition def get_mandel_init_cond(g, F, B, nu_u, mu_s): # Initialing pressure and displacement arrays p0 = np.zeros(g.num_cells) u0 = np.zeros(g.num_cells * 2) # Some needed parameters a = np.max(g.face_centers[0]) # a = Lx p0 = ((F * B * (1 + nu_u)) / (3 * a)) * np.ones(g.num_cells) u0[::2] = (F * nu_u * g.cell_centers[0]) / (2 * mu_s * a) u0[1::2] = ((-F * (1 - nu_u)) / (2 * mu_s * a)) * g.cell_centers[1] return p0, u0 # Getting the time-dependent boundary condition def get_mandel_bc(g, y_max, times, F, B, nu_u, nu, c_f, mu_s): # Initializing top boundary array u_top = np.zeros((len(times), len(y_max))) # Some needed parameters a = np.max(g.face_centers[0]) # a = Lx b = np.max(g.face_centers[1]) # b = Ly y_top = g.face_centers[1][ y_max] # [m] y-coordinates at the top boundary # Solutions to tan(x) - ((1-nu)/(nu_u-nu)) x = 0 """ This is somehow tricky, we have to solve the equation numerically in order to find all the positive solutions to the equation. Later we will use them to compute the infinite sums. Experience has shown that 200 roots are more than enough to achieve accurate results. Note that we find the roots using the bisection method. """ f = lambda x: np.tan(x) - ( (1 - nu) / (nu_u - nu)) * x # define the algebraic eq. as a lambda function n_series = 200 # number of roots a_n = np.zeros(n_series) # initializing roots array x0 = 0 # initial point for i in range(0, len(a_n)): a_n[i] = opt.bisect( f, # function x0 + np.pi / 4, # left point x0 + np.pi / 2 - 10000 * 2.2204e-16, # right point (a tiny bit less than pi/2) xtol=1e-30, # absolute tolerance rtol=1e-15 # relative tolerance ) x0 += np.pi # apply a phase change of pi to get the next root # Terms needed to compute the solutions (these are constants) uy0_1 = (-F * (1 - nu)) / (2 * mu_s * a) uy0_2 = (F * (1 - nu_u) / (mu_s * a)) # For the initial condition: u_top[0] = ((-F * (1 - nu_u)) / (2 * mu_s * a)) * b for i in range(1, len(times)): # Analytical vertical displacements at the top boundary uy_sum = 0 for n in range(len(a_n)): uy_sum += (((np.sin(a_n[n]) * np.cos(a_n[n])) / (a_n[n] - np.sin(a_n[n]) * np.cos(a_n[n]))) * np.exp((-(a_n[n]**2) * c_f * times[i]) / (a**2))) u_top[i] = (uy0_1 + uy0_2 * uy_sum) * y_top # Returning array of u_y at the top boundary return u_top # Getting mechanics boundary conditions def get_bc_mechanics(g, u_top, times, b_faces, x_min, x_max, west, east, y_min, y_max, south, north): # Setting the tags at each boundary side for the mechanics problem labels_mech = np.array([None] * b_faces.size) labels_mech[west] = 'dir_x' # roller labels_mech[east] = 'neu' # traction free labels_mech[south] = 'dir_y' # roller labels_mech[ north] = 'dir_y' # roller (with non-zero displacement in the vertical direction) # Constructing the bc object for the mechanics problem bc_mech = pp.BoundaryConditionVectorial(g, b_faces, labels_mech) # Constructing the boundary values array for the mechanics problem bc_val_mech = np.zeros(( len(times), g.num_faces * g.dim, )) for i in range(len(times)): # West side boundary conditions (mech) bc_val_mech[i][2 * x_min] = 0 # [m] bc_val_mech[i][2 * x_min + 1] = 0 # [Pa] # East side boundary conditions (mech) bc_val_mech[i][2 * x_max] = 0 # [Pa] bc_val_mech[i][2 * x_max + 1] = 0 # [Pa] # South Side boundary conditions (mech) bc_val_mech[i][2 * y_min] = 0 # [Pa] bc_val_mech[i][2 * y_min + 1] = 0 # [m] # North Side boundary conditions (mech) bc_val_mech[i][2 * y_max] = 0 # [Pa] bc_val_mech[i][2 * y_max + 1] = u_top[i] # [m] return bc_mech, bc_val_mech # Getting flow boundary conditions def get_bc_flow(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north): # Setting the tags at each boundary side for the mechanics problem labels_flow = np.array([None] * b_faces.size) labels_flow[west] = 'neu' # no flow labels_flow[east] = 'dir' # constant pressure labels_flow[south] = 'neu' # no flow labels_flow[north] = 'neu' # no flow # Constructing the bc object for the flow problem bc_flow = pp.BoundaryCondition(g, b_faces, labels_flow) # Constructing the boundary values array for the flow problem bc_val_flow = np.zeros(g.num_faces) # West side boundary condition (flow) bc_val_flow[x_min] = 0 # [Pa] # East side boundary condition (flow) bc_val_flow[x_max] = 0 # [m^3/s] # South side boundary condition (flow) bc_val_flow[y_min] = 0 # [m^3/s] # North side boundary condition (flow) bc_val_flow[y_max] = 0 # [m^3/s] return bc_flow, bc_val_flow # ## Setting up the grid # In[7]: Nx = 40 Ny = 40 Lx = 100 Ly = 10 g = pp.CartGrid([Nx, Ny], [Lx, Ly]) #g.nodes = g.nodes + 1E-7*np.random.randn(3, g.num_nodes) g.compute_geometry() V = g.cell_volumes # Physical parameters # Skeleton parameters mu_s = 2.475E+09 # [Pa] Shear modulus lambda_s = 1.65E+09 # [Pa] Lame parameter K_s = (2 / 3) * mu_s + lambda_s # [Pa] Bulk modulus E_s = mu_s * ((9 * K_s) / (3 * K_s + mu_s)) # [Pa] Young's modulus nu_s = (3 * K_s - 2 * mu_s) / (2 * (3 * K_s + mu_s) ) # [-] Poisson's coefficient k_s = 100 * 9.869233E-13 # [m^2] Permeabiliy # Fluid parameters mu_f = 10.0E-3 # [Pa s] Dynamic viscosity # Porous medium parameters alpha_biot = 1. # [m^2] Intrinsic permeability S_m = 6.0606E-11 # [1/Pa] Specific Storage K_u = K_s + (alpha_biot**2) / S_m # [Pa] Undrained bulk modulus B = alpha_biot / (S_m * K_u) # [-] Skempton's coefficient nu_u = (3 * nu_s + B * (1 - 2 * nu_s)) / (3 - B * (1 - 2 * nu_s)) # [-] Undrained Poisson's ratio c_f = (2 * k_s * (B**2) * mu_s * (1 - nu_s) * (1 + nu_u)**2) / (9 * mu_f * (1 - nu_u) * (nu_u - nu_s)) # [m^2/s] Fluid diffusivity # Creating second and fourth order tensors # Permeability tensor perm = pp.SecondOrderTensor(g.dim, k_s * np.ones(g.num_cells)) # Stiffness matrix constit = pp.FourthOrderTensor(g.dim, mu_s * np.ones(g.num_cells), lambda_s * np.ones(g.num_cells)) # Time parameters t0 = 0 # [s] Initial time tf = 100 # [s] Final simulation time tLevels = 100 # [-] Time levels times = np.linspace(t0, tf, tLevels + 1) # [s] Vector of time evaluations dt = np.diff(times) # [s] Vector of time steps # Boundary conditions pre-processing b_faces = g.tags['domain_boundary_faces'].nonzero()[0] # Extracting indices of boundary faces w.r.t g x_min = b_faces[g.face_centers[0, b_faces] < 0.0001] x_max = b_faces[g.face_centers[0, b_faces] > 0.9999 * Lx] y_min = b_faces[g.face_centers[1, b_faces] < 0.0001] y_max = b_faces[g.face_centers[1, b_faces] > 0.9999 * Ly] # Extracting indices of boundary faces w.r.t b_faces west = np.in1d(b_faces, x_min).nonzero() east = np.in1d(b_faces, x_max).nonzero() south = np.in1d(b_faces, y_min).nonzero() north = np.in1d(b_faces, y_max).nonzero() # Applied load and top boundary condition F_load = 6.8E+8 # [N/m] Applied load u_top = get_mandel_bc(g, y_max, times, F_load, B, nu_u, nu_s, c_f, mu_s) # [m] Vector of imposed vertical displacements # MECHANICS BOUNDARY CONDITIONS bc_mech, bc_val_mech = get_bc_mechanics(g, u_top, times, b_faces, x_min, x_max, west, east, y_min, y_max, south, north) # FLOW BOUNDARY CONDITIONS bc_flow, bc_val_flow = get_bc_flow(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north) # Initialiazing solution and solver dicitionaries # Solution dictionary sol = dict() sol['time'] = np.zeros(tLevels + 1, dtype=float) sol['displacement'] = np.zeros((tLevels + 1, g.num_cells * g.dim), dtype=float) sol['displacement_faces'] = np.zeros( (tLevels + 1, g.num_faces * g.dim * 2), dtype=float) sol['pressure'] = np.zeros((tLevels + 1, g.num_cells), dtype=float) sol['traction'] = np.zeros((tLevels + 1, g.num_faces * g.dim), dtype=float) sol['flux'] = np.zeros((tLevels + 1, g.num_faces), dtype=float) sol['iter'] = np.array([], dtype=int) sol['time_step'] = np.array([], dtype=float) sol['residual'] = np.array([], dtype=float) # Solver dictionary newton_param = dict() newton_param['tol'] = 1E-6 # maximum tolerance newton_param['max_iter'] = 20 # maximum number of iterations newton_param['res_norm'] = 1000 # initializing residual newton_param['iter'] = 1 # iteration # Discrete operators and discrete equations # Flow operators F = lambda x: biot_F * x # Flux operator boundF = lambda x: biot_boundF * x # Bound Flux operator compat = lambda x: biot_compat * x # Compatibility operator (Stabilization term) divF = lambda x: biot_divF * x # Scalar divergence operator # Mechanics operators S = lambda x: biot_S * x # Stress operator boundS = lambda x: biot_boundS * x # Bound Stress operator divU = lambda x: biot_divU * x # Divergence of displacement field divS = lambda x: biot_divS * x # Vector divergence operator gradP = lambda x: biot_divS * biot_gradP * x # Pressure gradient operator boundDivU = lambda x: biot_boundDivU * x # Bound Divergence of displacement operator boundUCell = lambda x: biot_boundUCell * x # Contribution of displacement at cells -> Face displacement boundUFace = lambda x: biot_boundUFace * x # Contribution of bc_mech at the boundaries -> Face displacement boundUPressure = lambda x: biot_boundUPressure * x # Contribution of pressure at cells -> Face displacement # Discrete equations # Generalized Hooke's law T = lambda u, bc_val_mech: S(u) + boundS(bc_val_mech) # Momentum conservation equation (I) u_eq1 = lambda u, bc_val_mech: divS(T(u, bc_val_mech)) # Momentum conservation equation (II) u_eq2 = lambda p: -gradP(p) # Darcy's law Q = lambda p: (1. / mu_f) * (F(p) + boundF(bc_val_flow)) # Mass conservation equation (I) p_eq1 = lambda u, u_n, bc_val_mech, bc_val_mech_n: alpha_biot * (divU( u - u_n) + boundDivU(bc_val_mech - bc_val_mech_n)) # Mass conservation equation (II) p_eq2 = lambda p, p_n, dt: (p - p_n) * S_m * V + divF(Q( p)) * dt + alpha_biot * compat(p - p_n) # Creating AD variables # Retrieve initial conditions p_init, u_init = get_mandel_init_cond(g, F_load, B, nu_u, mu_s) # Create displacement AD-variable u_ad = Ad_array(u_init.copy(), sps.diags(np.ones(g.num_cells * g.dim))) # Create pressure AD-variable p_ad = Ad_array(p_init.copy(), sps.diags(np.ones(g.num_cells))) # The time loop tt = 0 # time counter while times[tt] < times[-1]: ################################ # Initializing data dictionary # ################################ d = dict() # initialize dictionary to store data ################################ # Creating the data objects # ################################ # Mechanics data object specified_parameters_mech = { "fourth_order_tensor": constit, "bc": bc_mech, "biot_alpha": 1., "bc_values": bc_val_mech[tt], "mass_weight": S_m } pp.initialize_default_data(g, d, "mechanics", specified_parameters_mech) # Flow data object specified_parameters_flow = { "second_order_tensor": perm, "bc": bc_flow, "biot_alpha": 1., "bc_values": bc_val_flow, "mass_weight": S_m, "time_step": dt[tt - 1] } pp.initialize_default_data(g, d, "flow", specified_parameters_flow) ################################ # CALLING MPFA/MPSA ROUTINES # ################################ # Biot discretization solver_biot = pp.Biot("mechanics", "flow") solver_biot.discretize(g, d) # Mechanics discretization matrices biot_S = d['discretization_matrices']['mechanics']['stress'] biot_boundS = d['discretization_matrices']['mechanics']['bound_stress'] biot_divU = d['discretization_matrices']['mechanics']['div_d'] biot_gradP = d['discretization_matrices']['mechanics']['grad_p'] biot_boundDivU = d['discretization_matrices']['mechanics'][ 'bound_div_d'] biot_boundUCell = d['discretization_matrices']['mechanics'][ 'bound_displacement_cell'] biot_boundUFace = d['discretization_matrices']['mechanics'][ 'bound_displacement_face'] biot_boundUPressure = d['discretization_matrices']['mechanics'][ 'bound_displacement_pressure'] biot_divS = pp.fvutils.vector_divergence(g) # Flow discretization matrices biot_F = d['discretization_matrices']['flow']['flux'] biot_boundF = d['discretization_matrices']['flow']['bound_flux'] biot_compat = d['discretization_matrices']['flow'][ 'biot_stabilization'] biot_divF = pp.fvutils.scalar_divergence(g) ################################ # Saving Initial Condition # ################################ if times[tt] == 0: sol['pressure'][tt] = p_ad.val sol['displacement'][tt] = u_ad.val sol['displacement_faces'][tt] = ( boundUCell(sol['displacement'][tt]) + boundUFace(bc_val_mech[tt]) + boundUPressure(sol['pressure'][tt])) sol['time'][tt] = times[tt] sol['traction'][tt] = T(u_ad.val, bc_val_mech[tt]) sol['flux'][tt] = Q(p_ad.val) tt += 1 # increasing time counter ################################ # Solving the set of PDE's # ################################ # Displacement and pressure at the previous time step u_n = u_ad.val.copy() p_n = p_ad.val.copy() # Updating residual and iteration at each time step newton_param.update({'res_norm': 1000, 'iter': 1}) # Newton loop while newton_param['res_norm'] > newton_param['tol'] and newton_param[ 'iter'] <= newton_param['max_iter']: # Calling equations eq1 = u_eq1(u_ad, bc_val_mech[tt]) eq2 = u_eq2(p_ad) eq3 = p_eq1(u_ad, u_n, bc_val_mech[tt], bc_val_mech[tt - 1]) eq4 = p_eq2(p_ad, p_n, dt[tt - 1]) # Assembling Jacobian of the coupled system J_mech = np.hstack( (eq1.jac, eq2.jac)) # Jacobian blocks (mechanics) J_flow = np.hstack((eq3.jac, eq4.jac)) # Jacobian blocks (flow) J = sps.bmat(np.vstack((J_mech, J_flow)), format='csc') # Jacobian (coupled) # Determining residual of the coupled system R_mech = eq1.val + eq2.val # Residual (mechanics) R_flow = eq3.val + eq4.val # Residual (flow) R = np.hstack((R_mech, R_flow)) # Residual (coupled) y = sps.linalg.spsolve(J, -R) # u_ad.val = u_ad.val + y[:g.dim * g.num_cells] # Newton update p_ad.val = p_ad.val + y[g.dim * g.num_cells:] # newton_param['res_norm'] = np.linalg.norm(R) # Updating residual if newton_param['res_norm'] <= newton_param[ 'tol'] and newton_param['iter'] <= newton_param['max_iter']: print('Iter: {} \t Error: {:.8f} [m]'.format( newton_param['iter'], newton_param['res_norm'])) elif newton_param['iter'] > newton_param['max_iter']: print('Error: Newton method did not converge!') else: newton_param['iter'] += 1 ################################ # Saving the variables # ################################ sol['iter'] = np.concatenate( (sol['iter'], np.array([newton_param['iter']]))) sol['residual'] = np.concatenate( (sol['residual'], np.array([newton_param['res_norm']]))) sol['time_step'] = np.concatenate((sol['time_step'], dt)) sol['pressure'][tt] = p_ad.val sol['displacement'][tt] = u_ad.val sol['displacement_faces'][tt] = (boundUCell(sol['displacement'][tt]) + boundUFace(bc_val_mech[tt]) + boundUPressure(sol['pressure'][tt])) sol['time'][tt] = times[tt] sol['traction'][tt] = T(u_ad.val, bc_val_mech[tt]) sol['flux'][tt] = Q(p_ad.val) # Calling analytical solution sol_mandel = mandel_solution(g, Nx, Ny, times, F_load, B, nu_u, nu_s, c_f, mu_s) # Creating analytical and numerical results arrays p_num = (Lx * sol['pressure'][-1][:Nx]) / F p_ana = (Lx * sol_mandel['p'][-1]) / F # Returning values return p_num, p_ana
def set_mechanics_parameters(self): """ Mechanical parameters. Note that we divide the momentum balance equation by self.scalar_scale. A homogeneous initial temperature is assumed. """ 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) # In the momentum balance, the coefficient hits the scalar, and should # not be scaled. Same goes for the energy balance, where we divide all # terms by T_0, hence the term originally beta K T d(div u) / dt becomes # beta K d(div u) / dt = coupling_coefficient d(div u) / dt. coupling_coefficient = self.biot_alpha(g) pp.initialize_data( g, d, self.mechanics_parameter_key, { "bc": bc, "bc_values": bc_values, "source": sources, "fourth_order_tensor": C, "biot_alpha": coupling_coefficient, "time_step": self.time_step, }, ) pp.initialize_data( g, d, self.mechanics_temperature_parameter_key, { "biot_alpha": self.biot_beta(g), "bc_values": bc_values }, ) elif g.dim == self.Nd - 1: pp.initialize_data( g, d, self.mechanics_parameter_key, { "friction_coefficient": self._friction_coefficient(g), "contact_mechanics_numerical_parameter": 10000, }, ) for e, d in gb.edges(): mg = d["mortar_grid"] # Parameters for the surface diffusion. Not used as of now. pp.initialize_data( mg, d, self.mechanics_parameter_key, { "mu": self.rock.MU, "lambda": self.rock.LAMBDA }, )
def biot_convergence_in_space(N): # coding: utf-8 # ### Source terms and analytical solutions # In[330]: def source_flow(g, tau): x1 = g.cell_centers[0] x2 = g.cell_centers[1] f_flow = tau*(2*np.sin(2*np.pi*x2) - \ 4*x1*np.pi**2*np.sin(2*np.pi*x2)*(x1 - 1)) - \ x1*np.sin(2*np.pi*x2) - \ np.sin(2*np.pi*x2)*(x1 - 1) + \ 2*np.pi*np.cos(2*np.pi*x2)*np.sin(2*np.pi*x1) return f_flow def source_mechanics(g): x1 = g.cell_centers[0] x2 = g.cell_centers[1] f_mech = np.zeros(g.num_cells * g.dim) f_mech[::2] = 6*np.sin(2*np.pi*x2) - \ x1*np.sin(2*np.pi*x2) - \ np.sin(2*np.pi*x2)*(x1 - 1) - \ 8*np.pi**2*np.cos(2*np.pi*x1)*np.cos(2*np.pi*x2) - \ 4*x1*np.pi**2*np.sin(2*np.pi*x2)*(x1 - 1) f_mech[1::2] = 4*np.pi*np.cos(2*np.pi*x2)*(x1 - 1) + \ 16*np.pi**2*np.sin(2*np.pi*x1)*np.sin(2*np.pi*x2) + \ 4*x1*np.pi*np.cos(2*np.pi*x2) - \ 2*x1*np.pi*np.cos(2*np.pi*x2)*(x1 - 1) return f_mech def analytical(g): sol = dict() x1 = g.cell_centers[0] x2 = g.cell_centers[1] sol['u'] = np.zeros(g.num_cells * g.dim) sol['u'][::2] = x1 * (1 - x1) * np.sin(2 * np.pi * x2) sol['u'][1::2] = np.sin(2 * np.pi * x1) * np.sin(2 * np.pi * x2) sol['p'] = sol['u'][::2] return sol # ### Getting mechanics boundary conditions # In[331]: def get_bc_mechanics(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north): # Setting the tags at each boundary side for the mechanics problem labels_mech = np.array([None] * b_faces.size) labels_mech[west] = 'dir' labels_mech[east] = 'dir' labels_mech[south] = 'dir' labels_mech[north] = 'dir' # Constructing the bc object for the mechanics problem bc_mech = pp.BoundaryConditionVectorial(g, b_faces, labels_mech) # Constructing the boundary values array for the mechanics problem bc_val_mech = np.zeros(g.num_faces * g.dim) return bc_mech, bc_val_mech # ### Getting flow boundary conditions # In[332]: def get_bc_flow(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north): # Setting the tags at each boundary side for the mechanics problem labels_flow = np.array([None] * b_faces.size) labels_flow[west] = 'dir' labels_flow[east] = 'dir' labels_flow[south] = 'dir' labels_flow[north] = 'dir' # Constructing the bc object for the flow problem bc_flow = pp.BoundaryCondition(g, b_faces, labels_flow) # Constructing the boundary values array for the flow problem bc_val_flow = np.zeros(g.num_faces) return bc_flow, bc_val_flow # ### Setting up the grid # In[333]: Nx = Ny = N Lx = 1 Ly = 1 g = pp.CartGrid([Nx, Ny], [Lx, Ly]) g.compute_geometry() V = g.cell_volumes # ### Physical parameters # In[334]: # Skeleton parameters mu_s = 1 # [Pa] Shear modulus lambda_s = 1 # [Pa] Lame parameter K_s = (2 / 3) * mu_s + lambda_s # [Pa] Bulk modulus E_s = mu_s * ((9 * K_s) / (3 * K_s + mu_s)) # [Pa] Young's modulus nu_s = (3 * K_s - 2 * mu_s) / (2 * (3 * K_s + mu_s) ) # [-] Poisson's coefficient k_s = 1 # [m^2] Permeabiliy # Fluid parameters mu_f = 1 # [Pa s] Dynamic viscosity # Porous medium parameters alpha_biot = 1. # [m^2] Intrinsic permeability S_m = 0 # [1/Pa] Specific Storage # ### Creating second and fourth order tensors # In[335]: # Permeability tensor perm = pp.SecondOrderTensor(g.dim, k_s * np.ones(g.num_cells)) # Stiffness matrix constit = pp.FourthOrderTensor(g.dim, mu_s * np.ones(g.num_cells), lambda_s * np.ones(g.num_cells)) # ### Time parameters # In[336]: t0 = 0 # [s] Initial time tf = 1 # [s] Final simulation time tLevels = 1 # [-] Time levels times = np.linspace(t0, tf, tLevels + 1) # [s] Vector of time evaluations dt = np.diff(times) # [s] Vector of time steps # ### Boundary conditions pre-processing # In[337]: b_faces = g.tags['domain_boundary_faces'].nonzero()[0] # Extracting indices of boundary faces w.r.t g x_min = b_faces[g.face_centers[0, b_faces] < 0.0001] x_max = b_faces[g.face_centers[0, b_faces] > 0.9999 * Lx] y_min = b_faces[g.face_centers[1, b_faces] < 0.0001] y_max = b_faces[g.face_centers[1, b_faces] > 0.9999 * Ly] # Extracting indices of boundary faces w.r.t b_faces west = np.in1d(b_faces, x_min).nonzero() east = np.in1d(b_faces, x_max).nonzero() south = np.in1d(b_faces, y_min).nonzero() north = np.in1d(b_faces, y_max).nonzero() # Mechanics boundary conditions bc_mech, bc_val_mech = get_bc_mechanics(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north) # FLOW BOUNDARY CONDITIONS bc_flow, bc_val_flow = get_bc_flow(g, b_faces, x_min, x_max, west, east, y_min, y_max, south, north) # ### Initialiazing solution and solver dicitionaries # In[338]: # Solution dictionary sol = dict() sol['time'] = np.zeros(tLevels + 1, dtype=float) sol['displacement'] = np.zeros((tLevels + 1, g.num_cells * g.dim), dtype=float) sol['displacement_faces'] = np.zeros( (tLevels + 1, g.num_faces * g.dim * 2), dtype=float) sol['pressure'] = np.zeros((tLevels + 1, g.num_cells), dtype=float) sol['traction'] = np.zeros((tLevels + 1, g.num_faces * g.dim), dtype=float) sol['flux'] = np.zeros((tLevels + 1, g.num_faces), dtype=float) sol['iter'] = np.array([], dtype=int) sol['time_step'] = np.array([], dtype=float) sol['residual'] = np.array([], dtype=float) # Solver dictionary newton_param = dict() newton_param['tol'] = 1E-8 # maximum tolerance newton_param['max_iter'] = 20 # maximum number of iterations newton_param['res_norm'] = 1000 # initializing residual newton_param['iter'] = 1 # iteration # ### Discrete operators and discrete equations # ### Flow operators # In[339]: F = lambda x: biot_F * x # Flux operator boundF = lambda x: biot_boundF * x # Bound Flux operator compat = lambda x: biot_compat * x # Compatibility operator (Stabilization term) divF = lambda x: biot_divF * x # Scalar divergence operator # ### Mechanics operators # In[340]: S = lambda x: biot_S * x # Stress operator boundS = lambda x: biot_boundS * x # Bound Stress operator divU = lambda x: biot_divU * x # Divergence of displacement field divS = lambda x: biot_divS * x # Vector divergence operator gradP = lambda x: biot_divS * biot_gradP * x # Pressure gradient operator boundDivU = lambda x: biot_boundDivU * x # Bound Divergence of displacement operator boundUCell = lambda x: biot_boundUCell * x # Contribution of displacement at cells -> Face displacement boundUFace = lambda x: biot_boundUFace * x # Contribution of bc_mech at the boundaries -> Face displacement boundUPressure = lambda x: biot_boundUPressure * x # Contribution of pressure at cells -> Face displacement # ### Discrete equations # In[341]: # Source terms f_mech = source_mechanics(g) f_flow = source_flow(g, dt[0]) # Generalized Hooke's law T = lambda u: S(u) + boundS(bc_val_mech) # Momentum conservation equation (I) u_eq1 = lambda u: divS(T(u)) # Momentum conservation equation (II) u_eq2 = lambda p: -gradP(p) + f_mech * V[0] # Darcy's law Q = lambda p: (1. / mu_f) * (F(p) + boundF(bc_val_flow)) # Mass conservation equation (I) p_eq1 = lambda u, u_n: alpha_biot * divU(u - u_n) # Mass conservation equation (II) p_eq2 = lambda p, p_n, dt: (p - p_n) * S_m * V + divF(Q( p)) * dt + alpha_biot * compat(p - p_n) * V[0] - (f_flow / dt) * V[0] # ## Creating AD variables # In[343]: # Create displacement AD-variable u_ad = Ad_array(np.zeros(g.num_cells * 2), sps.diags(np.ones(g.num_cells * g.dim))) # Create pressure AD-variable p_ad = Ad_array(np.zeros(g.num_cells), sps.diags(np.ones(g.num_cells))) # ## Performing discretization # In[344]: d = dict() # initialize dictionary to store data # Mechanics data object specified_parameters_mech = { "fourth_order_tensor": constit, "bc": bc_mech, "biot_alpha": 1., "bc_values": bc_val_mech } pp.initialize_default_data(g, d, "mechanics", specified_parameters_mech) # Flow data object specified_parameters_flow = { "second_order_tensor": perm, "bc": bc_flow, "biot_alpha": 1., "bc_values": bc_val_flow } pp.initialize_default_data(g, d, "flow", specified_parameters_flow) # Biot discretization solver_biot = pp.Biot("mechanics", "flow") solver_biot.discretize(g, d) # Mechanics discretization matrices biot_S = d['discretization_matrices']['mechanics']['stress'] biot_boundS = d['discretization_matrices']['mechanics']['bound_stress'] biot_divU = d['discretization_matrices']['mechanics']['div_d'] biot_gradP = d['discretization_matrices']['mechanics']['grad_p'] biot_boundDivU = d['discretization_matrices']['mechanics']['bound_div_d'] biot_boundUCell = d['discretization_matrices']['mechanics'][ 'bound_displacement_cell'] biot_boundUFace = d['discretization_matrices']['mechanics'][ 'bound_displacement_face'] biot_boundUPressure = d['discretization_matrices']['mechanics'][ 'bound_displacement_pressure'] biot_divS = pp.fvutils.vector_divergence(g) # Flow discretization matrices biot_F = d['discretization_matrices']['flow']['flux'] biot_boundF = d['discretization_matrices']['flow']['bound_flux'] biot_compat = d['discretization_matrices']['flow']['biot_stabilization'] biot_divF = pp.fvutils.scalar_divergence(g) # Saving initial condition sol['pressure'][0] = p_ad.val sol['displacement'][0] = u_ad.val sol['displacement_faces'][0] = (boundUCell(sol['displacement'][0]) + boundUFace(bc_val_mech) + boundUPressure(sol['pressure'][0])) sol['time'][0] = times[0] sol['traction'][0] = T(u_ad.val) sol['flux'][0] = Q(p_ad.val) # ## The time loop # In[345]: tt = 0 # time counter while times[tt] < times[-1]: tt += 1 # increasing time counter # Displacement and pressure at the previous time step u_n = u_ad.val.copy() p_n = p_ad.val.copy() # Updating residual and iteration at each time step newton_param.update({'res_norm': 1000, 'iter': 1}) # Newton loop while newton_param['res_norm'] > newton_param['tol'] and newton_param[ 'iter'] <= newton_param['max_iter']: # Calling equations eq1 = u_eq1(u_ad) eq2 = u_eq2(p_ad) eq3 = p_eq1(u_ad, u_n) eq4 = p_eq2(p_ad, p_n, dt[tt - 1]) # Assembling Jacobian of the coupled system J_mech = np.hstack( (eq1.jac, eq2.jac)) # Jacobian blocks (mechanics) J_flow = np.hstack((eq3.jac, eq4.jac)) # Jacobian blocks (flow) J = sps.bmat(np.vstack((J_mech, J_flow)), format='csc') # Jacobian (coupled) # Determining residual of the coupled system R_mech = eq1.val + eq2.val # Residual (mechanics) R_flow = eq3.val + eq4.val # Residual (flow) R = np.hstack((R_mech, R_flow)) # Residual (coupled) y = sps.linalg.spsolve(J, -R) # u_ad.val = u_ad.val + y[:g.dim * g.num_cells] # Newton update p_ad.val = p_ad.val + y[g.dim * g.num_cells:] # newton_param['res_norm'] = np.linalg.norm(R) # Updating residual if newton_param['res_norm'] <= newton_param[ 'tol'] and newton_param['iter'] <= newton_param['max_iter']: print('Iter: {} \t Error: {:.8f} [m]'.format( newton_param['iter'], newton_param['res_norm'])) elif newton_param['iter'] > newton_param['max_iter']: print('Error: Newton method did not converge!') else: newton_param['iter'] += 1 # Saving variables sol['iter'] = np.concatenate( (sol['iter'], np.array([newton_param['iter']]))) sol['residual'] = np.concatenate( (sol['residual'], np.array([newton_param['res_norm']]))) sol['time_step'] = np.concatenate((sol['time_step'], dt)) sol['pressure'][tt] = p_ad.val sol['displacement'][tt] = u_ad.val sol['displacement_faces'][tt] = (boundUCell(sol['displacement'][tt]) + boundUFace(bc_val_mech) + boundUPressure(sol['pressure'][tt])) sol['time'][tt] = times[tt] sol['traction'][tt] = T(u_ad.val) sol['flux'][tt] = Q(p_ad.val) # Determining analytical solution sol_anal = analytical(g) # Determining norms p_norm = np.linalg.norm(sol_anal['p'] - sol['pressure'][-1]) / ( np.linalg.norm(sol['pressure'][-1])) u_mag_num = np.sqrt(sol['displacement'][-1][::2]**2 + sol['displacement'][-1][1::2]**2) u_mag_ana = np.sqrt(sol_anal['u'][::2]**2 + sol_anal['u'][1::2]**2) u_norm = np.linalg.norm(u_mag_ana - u_mag_num) / np.linalg.norm(u_mag_num) return p_norm, u_norm
def assign_parameters( grid_object, data_dictionary, parameter_keyword_flow, parameter_keyword_mechanics, boundary_conditions_dictionary, ): """ Assign data to the model, which will later be used to discretize the coupled problem. Parameters: grid_object (PorePy object): PorePy grid object 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 boundary_conditions_dictionary (Dict): Dictionary containing boundary conditions """ # Renaming input data g = grid_object d = data_dictionary kw_f = parameter_keyword_flow kw_m = parameter_keyword_mechanics bc_dict = boundary_conditions_dictionary # Assing flow data # Retrieve data for the flow problem k = d[pp.PARAMETERS][kw_f]["permeability"] alpha_biot = d[pp.PARAMETERS][kw_f]["alpha_biot"] S_m = d[pp.PARAMETERS][kw_f]["specific_storage"] dt = d[pp.PARAMETERS][kw_f]["time_step"] bc_flow = bc_dict[kw_f]["bc"] bc_flow_values = bc_dict[kw_f]["bc_values"] # Create second order tensor object kxx = k * np.ones(g.num_cells) perm = pp.SecondOrderTensor(kxx) # Create specified parameters dicitionary specified_parameters_flow = { "second_order_tensor": perm, "biot_alpha": alpha_biot, "bc": bc_flow, "bc_values": bc_flow_values, "time_step": dt, "mass_weight": S_m * np.ones(g.num_cells), } # Initialize the flow data d = pp.initialize_default_data(g, d, kw_f, specified_parameters_flow) # Assign mechanics data # Retrieve data for the mechanics problem mu = d[pp.PARAMETERS][kw_m]["lame_mu"] lmbda = d[pp.PARAMETERS][kw_m]["lame_lambda"] alpha_biot = d[pp.PARAMETERS][kw_m]["alpha_biot"] bc_mech = bc_dict[kw_m]["bc"] bc_mech_values = bc_dict[kw_m]["bc_values"][1] # Create fourth order tensor mu_lame = mu * np.ones(g.num_cells) lambda_lame = lmbda * np.ones(g.num_cells) constit = pp.FourthOrderTensor(mu_lame, lambda_lame) # Create specified parameters dicitionary specified_parameters_mechanics = { "fourth_order_tensor": constit, "biot_alpha": alpha_biot, "bc": bc_mech, "bc_values": bc_mech_values, } # Initialize the mechanics d = pp.initialize_default_data(g, d, kw_m, specified_parameters_mechanics) # Save boundary conditions in d[pp.STATE] pp.set_state(d, {kw_m: {"bc_values": bc_dict[kw_m]["bc_values"][0]}})
def set_parameters(self, g, data_node, mg, data_edge): """ Set the parameters for the simulation. The stress is given in GPa. """ # Define the finite volume sub grid s_t = pp.fvutils.SubcellTopology(g) # Rock parameters rock = pp.Granite() lam = rock.LAMBDA * np.ones(g.num_cells) / self.pressure_scale mu = rock.MU * np.ones(g.num_cells) / self.pressure_scale F = self._friction_coefficient(g, mg, data_edge, s_t) k = pp.FourthOrderTensor(g.dim, mu, lam) # Define boundary regions top = g.face_centers[g.dim - 1] > np.max(g.nodes[1]) - 1e-9 bot = g.face_centers[g.dim - 1] < np.min(g.nodes[1]) + 1e-9 top_hf = top[s_t.fno_unique] bot_hf = bot[s_t.fno_unique] # Define boundary condition on sub_faces bc = pp.BoundaryConditionVectorial(g, top + bot, 'dir') bc = pp.fvutils.boundary_to_sub_boundary(bc, s_t) # Set the boundary values u_bc = np.zeros((g.dim, s_t.num_subfno_unique)) u_bc[1, top_hf] = -0.002 u_bc[0, top_hf] = 0.005 # Find the continuity points eta = 1 / 3 eta_vec = eta * np.ones(s_t.num_subfno_unique) cont_pnt = g.face_centers[:g.dim, s_t.fno_unique] + eta_vec * ( g.nodes[:g.dim, s_t.nno_unique] - g.face_centers[:g.dim, s_t.fno_unique]) # collect parameters in dictionary key = 'mech' key_m = 'mech' data_node = pp.initialize_data( g, data_node, key, { 'bc': bc, 'bc_values': u_bc.ravel('F'), 'source': 0, 'fourth_order_tensor': k, 'mpsa_eta': eta_vec, 'cont_pnt': cont_pnt, 'rock': rock, }) pp.initialize_data(mg, data_edge, key_m, { 'friction_coeff': F, 'c': 100 }) # Define discretization # For the 2D domain we solve linear elasticity with mpsa. mpsa = pp.Mpsa(key) data_node[pp.PRIMARY_VARIABLES] = {'u': {'cells': g.dim}} data_node[pp.DISCRETIZATION] = {'u': {'mpsa': mpsa}} # And define a Robin condition on the mortar grid contact = pp.numerics.interface_laws.elliptic_interface_laws.RobinContact( key_m, mpsa) data_edge[pp.PRIMARY_VARIABLES] = {'lam': {'cells': g.dim}} data_edge[pp.COUPLING_DISCRETIZATION] = { 'robin_disc': { g: ('u', 'mpsa'), g: ('u', 'mpsa'), (g, g): ('lam', contact), } } return key, key_m