def main(id_problem, tol=1e-5, N_pts=1000, if_export=False): folder_export = 'example_2_2_tpfa/' + str(id_problem) + "/" file_export = 'tpfa' gb = example_2_2_create_grid.create(id_problem, tol=tol) # Assign parameters example_2_2_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flux = tpfa.TpfaDFN(gb.dim_max(), 'flow') A_flux, b_flux = solver_flux.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flux + A_source, b_flux + b_source) solver_flux.split(gb, 'pressure', p) if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(['pressure']) b_box = gb.bounding_box() y_range = np.linspace(b_box[0][1] + tol, b_box[1][1] - tol, N_pts) pts = np.stack((1.5 * np.ones(N_pts), y_range, 0.5 * np.ones(N_pts))) values = example_2_2_data.plot_over_line(gb, pts, 'pressure', tol) arc_length = y_range - b_box[0][1] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate fvutils.compute_discharges(gb, 'flow') diam, flow_rate = example_2_2_data.compute_flow_rate(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate))
def test_upwind_example1(self, if_export=False): ####################### # Simple 2d upwind problem with implicit Euler scheme in time ####################### T = 1 Nx, Ny = 10, 1 g = structured.CartGrid([Nx, Ny], [1, 1]) g.compute_geometry() advect = upwind.Upwind("transport") param = Parameters(g) dis = advect.discharge(g, [1, 0, 0]) b_faces = g.get_all_boundary_faces() bc = BoundaryCondition(g, b_faces, ["dir"] * b_faces.size) bc_val = np.hstack(([1], np.zeros(g.num_faces - 1))) param.set_bc("transport", bc) param.set_bc_val("transport", bc_val) data = {"param": param, "discharge": dis} data["deltaT"] = advect.cfl(g, data) U, rhs = advect.matrix_rhs(g, data) M, _ = mass_matrix.MassMatrix().matrix_rhs(g, data) conc = np.zeros(g.num_cells) # Perform an LU factorization to speedup the solver IE_solver = sps.linalg.factorized((M + U).tocsc()) # Loop over the time Nt = int(T / data["deltaT"]) time = np.empty(Nt) folder = "example1" save = Exporter(g, "conc_IE", folder) for i in np.arange(Nt): # Update the solution # Backward and forward substitution to solve the system conc = IE_solver(M.dot(conc) + rhs) time[i] = data["deltaT"] * i if if_export: save.write_vtk({"conc": conc}, time_step=i) if if_export: save.write_pvd(time) known = np.array([ 0.99969927, 0.99769441, 0.99067741, 0.97352474, 0.94064879, 0.88804726, 0.81498958, 0.72453722, 0.62277832, 0.51725056, ]) assert np.allclose(conc, known)
def main(id_problem, is_coarse=False, tol=1e-5, if_export=False): gb = example_1_create_grid.create(0.5 / float(id_problem), tol) if is_coarse: co.coarsen(gb, 'by_tpfa') folder_export = "example_1_vem_coarse/" else: folder_export = "example_1_vem/" file_name_error = folder_export + "vem_error.txt" if if_export: save = Exporter(gb, "vem", folder_export) example_1_data.assign_frac_id(gb) # Assign parameters example_1_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMDFN(gb.dim_max(), 'flow') A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.DualSourceDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", 'pressure', "P0u", "err"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", 'pressure') solver_flow.project_u(gb, "discharge", "P0u") only_max_dim = lambda g: g.dim == gb.dim_max() diam = gb.diameter(only_max_dim) error_pressure = example_1_data.error_pressure(gb, "p") error_discharge = example_1_data.error_discharge(gb, "P0u") print("h=", diam, "- err(p)=", error_pressure, "- err(P0u)=", error_discharge) error_pressure = example_1_data.error_pressure(gb, 'pressure') with open(file_name_error, 'a') as f: info = str(gb.num_cells(only_max_dim)) + " " +\ str(gb.num_cells(only_max_dim)) + " " +\ str(error_pressure) + " " +\ str(error_discharge) + " " +\ str(gb.num_faces(only_max_dim)) + "\n" f.write(info) if if_export: save.write_vtk(['pressure', "err", "P0u"])
def main(id_problem, tol=1e-5, N_pts=1000, if_export=False): mesh_size = 0.025 # 0.01 0.05 folder_export = ("example_2_3_vem_coarse_" + str(mesh_size) + "/" + str(id_problem) + "/") file_export = "vem" gb = example_2_3_create_grid.create(id_problem, is_coarse=True, mesh_size=mesh_size, tol=tol) internal_flag = FaceTag.FRACTURE [g.remove_face_tag_if_tag(FaceTag.BOUNDARY, internal_flag) for g, _ in gb] # Assign parameters example_2_3_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMDFN(gb.dim_max(), "flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.IntegralDFN(gb.dim_max(), "flow") A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", "p", "P0u"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", "p") solver_flow.project_u(gb, "discharge", "P0u") if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(["p", "P0u"]) b_box = gb.bounding_box() y_range = np.linspace(b_box[0][1] + tol, b_box[1][1] - tol, N_pts) pts = np.stack((0.35 * np.ones(N_pts), y_range, np.zeros(N_pts))) values = example_2_3_data.plot_over_line(gb, pts, "p", tol) arc_length = y_range - b_box[0][1] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate diam, flow_rate = example_2_3_data.compute_flow_rate_vem(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate)) # compute the number of cells num_cells = gb.num_cells(lambda g: g.dim == 2) with open(folder_export + "cells.txt", "w") as f: f.write(str(num_cells))
def main(grid_name, direction): file_export = 'solution' tol = 1e-4 folder_grids = '/home/elle/Dropbox/Work/tipetut/' gb = pickle.load(open(folder_grids + grid_name, 'rb')) co.coarsen(gb, 'by_volume') folder_export = './example_4_vem_coarse_' + grid_name + '_' + direction + '/' domain = { 'xmin': -800, 'xmax': 600, 'ymin': 100, 'ymax': 1500, 'zmin': -100, 'zmax': 1000 } internal_flag = FaceTag.FRACTURE [g.remove_face_tag_if_tag(FaceTag.BOUNDARY, internal_flag) for g, _ in gb] example_4_data.add_data(gb, domain, direction, tol) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMDFN(gb.dim_max(), 'flow') A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.IntegralDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", "p", "P0u"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", "p") solver_flow.project_u(gb, "discharge", "P0u") save = Exporter(gb, file_export, folder_export) save.write_vtk(["p", "P0u"]) # compute the flow rate diam, flow_rate = example_4_data.compute_flow_rate_vem( gb, direction, domain, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate)) # compute the number of cells num_cells = gb.num_cells(lambda g: g.dim == 2) with open(folder_export + "cells.txt", "w") as f: f.write(str(num_cells))
def test_upwind_example0(self, if_export=False): ####################### # Simple 2d upwind problem with explicit Euler scheme in time ####################### T = 1 Nx, Ny = 4, 1 g = structured.CartGrid([Nx, Ny], [1, 1]) g.compute_geometry() advect = upwind.Upwind("transport") param = Parameters(g) dis = advect.discharge(g, [1, 0, 0]) b_faces = g.get_all_boundary_faces() bc = BoundaryCondition(g, b_faces, ['dir'] * b_faces.size) bc_val = np.hstack(([1], np.zeros(g.num_faces - 1))) param.set_bc("transport", bc) param.set_bc_val("transport", bc_val) data = {'param': param, 'discharge': dis} data['deltaT'] = advect.cfl(g, data) U, rhs = advect.matrix_rhs(g, data) OF = advect.outflow(g, data) M, _ = mass_matrix.MassMatrix().matrix_rhs(g, data) conc = np.zeros(g.num_cells) M_minus_U = M - U invM, _ = mass_matrix.InvMassMatrix().matrix_rhs(g, data) # Loop over the time Nt = int(T / data['deltaT']) time = np.empty(Nt) folder = 'example0' production = np.zeros(Nt) save = Exporter(g, "conc_EE", folder) for i in np.arange(Nt): # Update the solution production[i] = np.sum(OF.dot(conc)) conc = invM.dot((M_minus_U).dot(conc) + rhs) time[i] = data['deltaT'] * i if if_export: save.write_vtk({"conc": conc}, time_step=i) if if_export: save.write_pvd(time) known = 1.09375 assert np.sum(production) == known
def solve_tpfa(gb, folder): # Choose and define the solvers and coupler solver_flow = tpfa.TpfaMixedDim("flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = source.IntegralMixedDim("flow") A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "pressure", p) save = Exporter(gb, "sol", folder=folder) save.write_vtk(["pressure"])
def solve_p1(gb, folder, return_only_matrix=False): # Choose and define the solvers and coupler solver_flow = pp.P1MixedDim("flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) A = A_flow if return_only_matrix: return A, mortar_dof_size(A, gb, solver_flow) p = sps.linalg.spsolve(A, b_flow) solver_flow.split(gb, "pressure", p) save = Exporter(gb, "sol", folder=folder, simplicial=True) save.write_vtk("pressure", point_data=True)
def main(id_problem, tol=1e-5, N_pts=1000, if_export=False): mesh_size = 0.15 folder_export = 'example_2_1_vem_coarse_' + str(mesh_size) + '/' + str( id_problem) + "/" file_export = 'vem' gb = example_2_1_create_grid.create(id_problem, is_coarse=True, mesh_size=mesh_size, tol=tol) internal_flag = FaceTag.FRACTURE [g.remove_face_tag_if_tag(FaceTag.BOUNDARY, internal_flag) for g, _ in gb] # Assign parameters example_2_1_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMDFN(gb.dim_max(), 'flow') A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.DualSourceDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", 'pressure', "P0u"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", 'pressure') solver_flow.project_u(gb, "discharge", "P0u") if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(['pressure', "P0u"]) b_box = gb.bounding_box() z_range = np.linspace(b_box[0][2] + tol, b_box[1][2] - tol, N_pts) pts = np.stack((0.5 * np.ones(N_pts), 0.5 * np.ones(N_pts), z_range)) values = example_2_1_data.plot_over_line(gb, pts, 'pressure', tol) arc_length = z_range - b_box[0][2] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate diam, flow_rate = example_2_1_data.compute_flow_rate_vem(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate))
def solve_rt0(gb, folder): # Choose and define the solvers and coupler solver_flow = rt0.RT0MixedDim("flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.IntegralMixedDim("flow") A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) solver_flow.extract_p(gb, "up", "pressure") save = Exporter(gb, "sol", folder=folder) save.write_vtk(["pressure"])
def solve_rt0(gb, folder, return_only_matrix=False): # Choose and define the solvers and coupler solver_flow = pp.RT0MixedDim("flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) A = A_flow if return_only_matrix: return A, mortar_dof_size(A, gb, solver_flow) up = sps.linalg.spsolve(A, b_flow) solver_flow.split(gb, "up", up) solver_flow.extract_p(gb, "up", "pressure") save = Exporter(gb, "sol", folder=folder) save.write_vtk(["pressure"])
def main(id_problem, tol=1e-5, N_pts=1000, if_export=False): mesh_size = 0.025 # 0.01 0.05 folder_export = 'example_2_3_tpfa_' + str(mesh_size) + '/' + str( id_problem) + "/" file_export = 'tpfa' gb = example_2_3_create_grid.create(id_problem, mesh_size=mesh_size, tol=tol) # Assign parameters example_2_3_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flux = tpfa.TpfaDFN(gb.dim_max(), 'flow') A_flux, b_flux = solver_flux.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flux + A_source, b_flux + b_source) solver_flux.split(gb, "p", p) if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(["p"]) b_box = gb.bounding_box() y_range = np.linspace(b_box[0][1] + tol, b_box[1][1] - tol, N_pts) pts = np.stack((0.35 * np.ones(N_pts), y_range, np.zeros(N_pts))) values = example_2_3_data.plot_over_line(gb, pts, 'p', tol) arc_length = y_range - b_box[0][1] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate fvutils.compute_discharges(gb, 'flow') diam, flow_rate = example_2_3_data.compute_flow_rate(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate)) # compute the number of cells num_cells = gb.num_cells(lambda g: g.dim == 2) with open(folder_export + "cells.txt", "w") as f: f.write(str(num_cells))
def main(grid_name, direction): file_export = 'solution' tol = 1e-4 folder_grids = '/home/elle/Dropbox/Work/tipetut/' gb = pickle.load(open(folder_grids + grid_name, 'rb')) folder_export = './example_4_mpfa_' + grid_name + '_' + direction + '/' domain = { 'xmin': -800, 'xmax': 600, 'ymin': 100, 'ymax': 1500, 'zmin': -100, 'zmax': 1000 } example_4_data.add_data(gb, domain, direction, tol) # Choose and define the solvers and coupler solver_flux = mpfa.MpfaDFN(gb.dim_max(), 'flow') A_flux, b_flux = solver_flux.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), 'flow') A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flux + A_source, b_flux + b_source) solver_flux.split(gb, "p", p) save = Exporter(gb, file_export, folder_export) save.write_vtk(["p"]) # compute the flow rate fvutils.compute_discharges(gb, 'flow') diam, flow_rate = example_4_data.compute_flow_rate(gb, direction, domain, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate)) # compute the number of cells num_cells = gb.num_cells(lambda g: g.dim == 2) with open(folder_export + "cells.txt", "w") as f: f.write(str(num_cells))
def main(grid_name, direction): file_export = "solution" tol = 1e-4 folder_grids = "/home/elle/Dropbox/Work/tipetut/" gb = pickle.load(open(folder_grids + grid_name, "rb")) folder_export = "./example_4_tpfa_" + grid_name + "_" + direction + "/" domain = { "xmin": -800, "xmax": 600, "ymin": 100, "ymax": 1500, "zmin": -100, "zmax": 1000, } example_4_data.add_data(gb, domain, direction, tol) # Choose and define the solvers and coupler solver_flux = tpfa.TpfaDFN(gb.dim_max(), "flow") A_flux, b_flux = solver_flux.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), "flow") A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flux + A_source, b_flux + b_source) solver_flux.split(gb, "p", p) save = Exporter(gb, file_export, folder_export) save.write_vtk(["p"]) # compute the flow rate fvutils.compute_discharges(gb, "flow") diam, flow_rate = example_4_data.compute_flow_rate(gb, direction, domain, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate)) # compute the number of cells num_cells = gb.num_cells(lambda g: g.dim == 2) with open(folder_export + "cells.txt", "w") as f: f.write(str(num_cells))
def main(id_problem, is_coarse=False, tol=1e-5, N_pts=1000, if_export=False): folder_export = "example_2_2_vem/" + str(id_problem) + "/" file_export = "vem" gb = example_2_2_create_grid.create(id_problem, is_coarse=is_coarse, tol=tol) # Assign parameters example_2_2_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMDFN(gb.dim_max(), "flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.DualSourceDFN(gb.dim_max(), "flow") A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", "pressure", "P0u"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", "pressure") solver_flow.project_u(gb, "discharge", "P0u") if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(["pressure", "P0u"]) b_box = gb.bounding_box() y_range = np.linspace(b_box[0][1] + tol, b_box[1][1] - tol, N_pts) pts = np.stack((1.5 * np.ones(N_pts), y_range, 0.5 * np.ones(N_pts))) values = example_2_2_data.plot_over_line(gb, pts, "pressure", tol) arc_length = y_range - b_box[0][1] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate diam, flow_rate = example_2_2_data.compute_flow_rate_vem(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate))
def main(kf, description, is_coarse=False, if_export=False): mesh_kwargs = {} mesh_kwargs['mesh_size'] = { 'mode': 'constant', 'value': 0.045, 'bound_value': 0.045 } domain = {'xmin': 0, 'xmax': 1, 'ymin': 0, 'ymax': 1} file_name = 'network_geiger.csv' write_network(file_name) gb = importer.dfm_2d_from_csv(file_name, mesh_kwargs, domain) gb.compute_geometry() if is_coarse: co.coarsen(gb, 'by_volume') gb.assign_node_ordering() internal_flag = FaceTag.FRACTURE [g.remove_face_tag_if_tag(FaceTag.BOUNDARY, internal_flag) for g, _ in gb] # Assign parameters add_data(gb, domain, kf) # Choose and define the solvers and coupler solver_flow = vem_dual.DualVEMMixedDim('flow') A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = vem_source.IntegralMixedDim('flow') A_source, b_source = solver_source.matrix_rhs(gb) up = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "up", up) gb.add_node_props(["discharge", 'pressure', "P0u"]) solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", 'pressure') solver_flow.project_u(gb, "discharge", "P0u") if if_export: save = Exporter(gb, "vem", folder="vem_" + description) save.write_vtk(['pressure', "P0u"])
def main(kf, description, multi_point, if_export=False): # Define the geometry and produce the meshes mesh_kwargs = {} mesh_size = 0.045 mesh_kwargs['mesh_size'] = { 'mode': 'constant', 'value': mesh_size, 'bound_value': mesh_size } domain = {'xmin': 0, 'xmax': 1, 'ymin': 0, 'ymax': 1} file_name = 'network_geiger.csv' write_network(file_name) gb = importer.dfm_2d_from_csv(file_name, mesh_kwargs, domain) gb.compute_geometry() gb.assign_node_ordering() # Assign parameters add_data(gb, domain, kf, mesh_size) # Choose discretization and define the solver if multi_point: solver = mpfa.MpfaMixedDim('flow') else: solver = tpfa.TpfaMixedDim('flow') # Discretize A, b = solver.matrix_rhs(gb) # Solve the linear system p = sps.linalg.spsolve(A, b) # Store the solution gb.add_node_props(['pressure']) solver.split(gb, 'pressure', p) if if_export: save = Exporter(gb, "fv", folder="fv_" + description) save.write_vtk(['pressure'])
def main(id_problem, tol=1e-5, N_pts=1000, if_export=False): mesh_size = 0.425 folder_export = "example_2_1_mpfa_" + str(mesh_size) + "/" + str( id_problem) + "/" file_export = "mpfa" gb = example_2_1_create_grid.create(id_problem, mesh_size=mesh_size, tol=tol) # Assign parameters example_2_1_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flux = mpfa.MpfaDFN(gb.dim_max(), "flow") A_flux, b_flux = solver_flux.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), "flow") A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flux + A_source, b_flux + b_source) solver_flux.split(gb, "pressure", p) if if_export: save = Exporter(gb, file_export, folder_export) save.write_vtk(["pressure"]) b_box = gb.bounding_box() z_range = np.linspace(b_box[0][2], b_box[1][2], N_pts) pts = np.stack((0.5 * np.ones(N_pts), 0.5 * np.ones(N_pts), z_range)) values = example_2_1_data.plot_over_line(gb, pts, "pressure", tol) arc_length = z_range - b_box[0][2] np.savetxt(folder_export + "plot_over_line.txt", (arc_length, values)) # compute the flow rate fvutils.compute_discharges(gb, "flow") diam, flow_rate = example_2_1_data.compute_flow_rate(gb, tol) np.savetxt(folder_export + "flow_rate.txt", (diam, flow_rate))
def main(id_problem, tol=1e-5, if_export=False): folder_export = "example_1_tpfa/" file_name_error = folder_export + "tpfa_error.txt" gb = example_1_create_grid.create(0.5 / float(id_problem), tol) if if_export: save = Exporter(gb, "tpfa", folder_export) example_1_data.assign_frac_id(gb) # Assign parameters example_1_data.add_data(gb, tol) # Choose and define the solvers and coupler solver_flow = tpfa.TpfaDFN(gb.dim_max(), "flow") A_flow, b_flow = solver_flow.matrix_rhs(gb) solver_source = source.IntegralDFN(gb.dim_max(), "flow") A_source, b_source = solver_source.matrix_rhs(gb) p = sps.linalg.spsolve(A_flow + A_source, b_flow + b_source) solver_flow.split(gb, "pressure", p) def only_max_dim(g): return g.dim == gb.dim_max() diam = gb.diameter(only_max_dim) error_pressure = example_1_data.error_pressure(gb, "pressure") print("h=", diam, "- err(p)=", error_pressure) with open(file_name_error, "a") as f: info = (str(gb.num_cells(only_max_dim)) + " " + str(gb.num_cells(only_max_dim)) + " " + str(error_pressure) + "\n") f.write(info) if if_export: save.write_vtk(["pressure", "err"])
def test_upwind_example2(self, if_export=False): ####################### # Simple 2d upwind problem with explicit Euler scheme in time coupled with # a Darcy problem ####################### T = 2 Nx, Ny = 10, 10 folder = 'example2' def funp_ex(pt): return -np.sin(pt[0]) * np.sin(pt[1]) - pt[0] g = structured.CartGrid([Nx, Ny], [1, 1]) g.compute_geometry() param = Parameters(g) # Permeability perm = tensor.SecondOrderTensor(g.dim, kxx=np.ones(g.num_cells)) param.set_tensor("flow", perm) # Source term param.set_source("flow", np.zeros(g.num_cells)) # Boundaries b_faces = g.get_all_boundary_faces() bc = BoundaryCondition(g, b_faces, ['dir'] * b_faces.size) bc_val = np.zeros(g.num_faces) bc_val[b_faces] = funp_ex(g.face_centers[:, b_faces]) param.set_bc("flow", bc) param.set_bc_val("flow", bc_val) # Darcy solver data = {'param': param} solver = vem_dual.DualVEM("flow") D_flow, b_flow = solver.matrix_rhs(g, data) solver_source = vem_source.DualSource('flow') D_source, b_source = solver_source.matrix_rhs(g, data) up = sps.linalg.spsolve(D_flow + D_source, b_flow + b_source) p, u = solver.extract_p(g, up), solver.extract_u(g, up) P0u = solver.project_u(g, u, data) save = Exporter(g, "darcy", folder) if if_export: save.write_vtk({'pressure': p, "P0u": P0u}) # Discharge dis = u # Boundaries bc = BoundaryCondition(g, b_faces, ['dir'] * b_faces.size) bc_val = np.hstack(([1], np.zeros(g.num_faces - 1))) param.set_bc("transport", bc) param.set_bc_val("transport", bc_val) data = {'param': param, 'discharge': dis} # Advect solver advect = upwind.Upwind("transport") U, rhs = advect.matrix_rhs(g, data) data['deltaT'] = advect.cfl(g, data) M, _ = mass_matrix.MassMatrix().matrix_rhs(g, data) conc = np.zeros(g.num_cells) M_minus_U = M - U invM, _ = mass_matrix.InvMassMatrix().matrix_rhs(g, data) # Loop over the time Nt = int(T / data['deltaT']) time = np.empty(Nt) save.change_name("conc_darcy") for i in np.arange(Nt): # Update the solution conc = invM.dot((M_minus_U).dot(conc) + rhs) time[i] = data['deltaT'] * i if if_export: save.write_vtk({"conc": conc}, time_step=i) if if_export: save.write_pvd(time) known = \ np.array([9.63168200e-01, 8.64054875e-01, 7.25390695e-01, 5.72228235e-01, 4.25640080e-01, 2.99387331e-01, 1.99574336e-01, 1.26276876e-01, 7.59011550e-02, 4.33431230e-02, 3.30416807e-02, 1.13058617e-01, 2.05372538e-01, 2.78382057e-01, 3.14035373e-01, 3.09920132e-01, 2.75024694e-01, 2.23163145e-01, 1.67386939e-01, 1.16897527e-01, 1.06111312e-03, 1.11951850e-02, 3.87907727e-02, 8.38516119e-02, 1.36617802e-01, 1.82773271e-01, 2.10446545e-01, 2.14651936e-01, 1.97681518e-01, 1.66549151e-01, 3.20751341e-05, 9.85780113e-04, 6.07062715e-03, 1.99393042e-02, 4.53237556e-02, 8.00799828e-02, 1.17199623e-01, 1.47761481e-01, 1.64729339e-01, 1.65390555e-01, 9.18585872e-07, 8.08267622e-05, 8.47227168e-04, 4.08879583e-03, 1.26336029e-02, 2.88705048e-02, 5.27841497e-02, 8.10459333e-02, 1.07956484e-01, 1.27665318e-01, 2.51295298e-08, 6.29844122e-06, 1.09361990e-04, 7.56743783e-04, 3.11384414e-03, 9.04446601e-03, 2.03443897e-02, 3.75208816e-02, 5.89595194e-02, 8.11457277e-02, 6.63498510e-10, 4.73075468e-07, 1.33728945e-05, 1.30243418e-04, 7.01905707e-04, 2.55272292e-03, 6.96686157e-03, 1.52290448e-02, 2.78607282e-02, 4.40402650e-02, 1.71197497e-11, 3.47118057e-08, 1.57974045e-06, 2.13489614e-05, 1.48634295e-04, 6.68104990e-04, 2.18444135e-03, 5.58646819e-03, 1.17334966e-02, 2.09744728e-02, 4.37822313e-13, 2.52373622e-09, 1.83589660e-07, 3.40553325e-06, 3.02948532e-05, 1.66504215e-04, 6.45119867e-04, 1.90731440e-03, 4.53436628e-03, 8.99977737e-03, 1.12627412e-14, 1.84486857e-10, 2.13562387e-08, 5.39492977e-07, 6.08223906e-06, 4.05535296e-05, 1.84731221e-04, 6.25871542e-04, 1.66459389e-03, 3.59980231e-03]) assert np.allclose(conc, known)
class StaticModel(): ''' Class for solving an static elasticity problem flow problem: \nabla \sigma = 0, where nabla is the stress tensor. Parameters in Init: gb: (Grid) a grid object. data (dictionary): dictionary of data. Should contain a Parameter class with the keyword 'Param' physics: (string): defaults to 'mechanics' Functions: solve(): Calls reassemble and solves the linear system. Returns: the displacement d. Sets attributes: self.x step(): Same as solve, but without reassemble of the matrices reassemble(): Assembles the lhs matrix and rhs array. Returns: lhs, rhs. Sets attributes: self.lhs, self.rhs stress_disc(): Defines the discretization of the stress term. Returns stress discretization object (E.g., Mpsa) grid(): Returns: the Grid or GridBucket data(): Returns: Data dictionary traction(name='traction'): Calculate the traction over each face in the grid and assigne it to the data dictionary as keyword name. save(): calls split('d'). Then export the pressure to a vtk file to the folder kwargs['folder_name'] with file name kwargs['file_name'], default values are 'results' for the folder and physics for the file name. ''' def __init__(self, gb, data, physics='mechanics', **kwargs): self.physics = physics self._gb = gb if not isinstance(self._gb, Grid): raise ValueError('StaticModel only defined for Grid class') self._data = data self.lhs = [] self.rhs = [] self.x = [] file_name = kwargs.get('file_name', physics) folder_name = kwargs.get('folder_name', 'results') tic = time.time() logger.info('Create exporter') self.exporter = Exporter(self._gb, file_name, folder_name) logger.info('Elapsed time: ' + str(time.time() - tic)) self._stress_disc = self.stress_disc() self.displacement_name = 'displacement' self.frac_displacement_name = 'frac_displacement' def solve(self, max_direct=40000, callback=False, **kwargs): """ Reassemble and solve linear system. After the funtion has been called, the attributes lhs and rhs are updated according to the parameter states. Also, the attribute x gives the pressure given the current state. The function attempts to set up the best linear solver based on the system size. The setup and parameter choices here are still experimental. Parameters: max_direct (int): Maximum number of unknowns where a direct solver is applied. If a direct solver can be applied this is usually the most efficient option. However, if the system size is too large compared to available memory, a direct solver becomes extremely slow. callback (boolean, optional): If True iteration information will be output when an iterative solver is applied (system size larger than max_direct) Returns: np.array: Pressure state. """ # Discretize tic = time.time() logger.info('Discretize') self.lhs, self.rhs = self.reassemble(**kwargs) logger.info('Done. Elapsed time ' + str(time.time() - tic)) # Solve tic = time.time() ls = LSFactory() if self.rhs.size < max_direct: logger.info('Solve linear system using direct solver') self.x = ls.direct(self.lhs, self.rhs) else: logger.info('Solve linear system using GMRES') precond = self._setup_preconditioner() # precond = ls.ilu(self.lhs) slv = ls.gmres(self.lhs) self.x, info = slv(self.rhs, M=precond, callback=callback, maxiter=10000, restart=1500, tol=1e-8) if info == 0: logger.info('GMRES succeeded.') else: logger.error('GMRES failed with status ' + str(info)) logger.info('Done. Elapsed time ' + str(time.time() - tic)) return self.x def step(self, **kwargs): """ Calls self.solve(**kwargs) """ return self.solve(**kwargs) def reassemble(self, discretize=True): """ reassemble matrices. This must be called between every time step to update the rhs of the system. """ self.lhs, self.rhs = self._stress_disc.matrix_rhs( self.grid(), self.data(), discretize) return self.lhs, self.rhs def stress_disc(self): """ Define the stress discretization. Returns: FracturedMpsa (Solver object) """ return mpsa.FracturedMpsa(physics=self.physics) def grid(self): """ get the model grid Returns: gb (Grid object) """ return self._gb def data(self): """ get data Returns: data (Dictionary) """ return self._data def displacement(self, displacement_name='displacement'): """ Save the cell displacement to the data dictionary. The displacement will be saved as a (3, self.grid().num_cells) array Parameters: ----------- displacement_name: (string) Defaults to 'displacement'. Defines the keyword for the saved displacement in the data dictionary Returns: -------- d: (ndarray) the displacement as a (3, self.grid().num_cells) array """ self.displacement_name = displacement_name d = self._stress_disc.extract_u(self.grid(), self.x) d = d.reshape((3, -1), order='F') self._data[self.displacement_name] = d return d def frac_displacement(self, frac_displacement_name='frac_displacement'): """ Save the fracture displacement to the data dictionary. This is the displacement on the fracture facers. The displacement will be saved as a (3, self.grid().num_cells) array Parameters: ----------- frac_displacement_name: (string) Defaults to 'frac_displacement'. Defines the keyword for the saved displacement in the data dictionary Returns: -------- d: (ndarray) the displacement of size (3, #number of fracture faces) """ self.frac_displacement_name = frac_displacement_name d = self._stress_disc.extract_frac_u(self.grid(), self.x) d = d.reshape((3, -1), order='F') self._data[self.frac_displacement_name] = d return d def traction(self, traction_name='traction'): """ Save the traction on faces to the data dictionary. This is the area scaled traction on the fracture facers. The displacement will be saved as a (3, self.grid().num_cells) array Parameters: ----------- traction_name (string) Defaults to 'traction'. Defines the keyword for the saved traction in the data dictionary Returns: -------- d: (ndarray) the traction as a (3, self.grid().num_faces) array """ T = self._stress_disc.traction(self.grid(), self._data, self.x) T = T.reshape((self.grid().dim, -1), order='F') T_b = np.zeros(T.shape) sigma = self._data['param'].get_background_stress(self.physics) if np.any(sigma): normals = self.grid().face_normals for i in range(normals.shape[1]): T_b[:, i] = np.dot(normals[:, i], sigma) else: T_b = 0 self._data[traction_name] = T + T_b def save(self, variables=None, time_step=None): """ Save the result as vtk. Parameters: ---------- variables: (list) Optional, defaults to None. If None, only the grid will be exported. A list of strings where each element defines a keyword in the data dictionary to be saved. time_step: (float) optinal, defaults to None. The time step of the variable(s) that is saved """ if variables is None: self.exporter.write_vtk() else: variables = {k: self._data[k] for k in variables \ if k in self._data} self.exporter.write_vtk(variables, time_step=time_step) ### Helper functions for linear solve below def _setup_preconditioner(self): solvers, ind, not_ind = self._assign_solvers() def precond(r): x = np.zeros_like(r) for s, i, ni in zip(solvers, ind, not_ind): x[i] += s(r[i]) return x def precond_mult(r): x = np.zeros_like(r) A = self.lhs for s, i, ni in zip(solvers, ind, not_ind): r_i = r[i] - A[i, :][:, ni] * x[ni] x[i] += s(r_i) return x M = lambda r: precond(r) return spl.LinearOperator(self.lhs.shape, M) def _assign_solvers(self): mat, ind = self._obtain_submatrix() all_ind = np.arange(self.rhs.size) not_ind = [np.setdiff1d(all_ind, i) for i in ind] factory = LSFactory() num_mat = len(mat) solvers = np.empty(num_mat, dtype=np.object) for i, A in enumerate(mat): sz = A.shape[0] if sz < 5000: solvers[i] = factory.direct(A) else: # amg solver is pyamg is installed, if not ilu try: solvers[i] = factory.amg(A, as_precond=True) except ImportError: solvers[i] = factory.ilu(A) return solvers, ind, not_ind def _obtain_submatrix(self): return [self.lhs], [np.arange(self.grid().num_cells)]
class FrictionSlipModel: """ Class for solving a frictional slip problem: T_s <= mu * (T_n -p) Parameters in Init: gb: (Grid) a Grid Object. data: (dictionary) Should contain a Parameter class with the keyword 'Param' physics: (string): defaults to 'slip' Functions: solve(): Calls reassemble and solves the linear system. Returns: new slip if T_s > mu * (T_n - p) Sets attributes: self.x, self.is_slipping, self.d_n step(): Same as solve normal_shear_traction(): project the traction into the corresponding normal and shear components grid(): Returns: the Grid or GridBucket data(): Returns: Data dictionary fracture_dilation(slip_distance): Returns: the amount of dilation for given slip distance slip_distance(): saves the slip distance to the data dictionary and returns it aperture_change(): saves the aperture change to the data dictionary and returns it mu(faces): returns: the coefficient of friction gamma(): returns: the numerical step length parameter save(): calls split('pressure'). Then export the pressure to a vtk file to the folder kwargs['folder_name'] with file name kwargs['file_name'], default values are 'results' for the folder and physics for the file name. """ def __init__(self, gb, data, physics="slip", **kwargs): self.physics = physics if isinstance(gb, GridBucket): raise ValueError("FrictionSlip excpected a Grid, not a GridBucket") self._gb = gb self._data = data file_name = kwargs.get("file_name", physics) folder_name = kwargs.get("folder_name", "results") tic = time.time() logger.info("Create exporter") self.exporter = Exporter(self._gb, file_name, folder_name) logger.info("Elapsed time: " + str(time.time() - tic)) self.x = np.zeros((3, gb.num_faces)) self.d_n = np.zeros(gb.num_faces) self.is_slipping = np.zeros(gb.num_faces, dtype=np.bool) self.slip_name = "slip_distance" self.aperture_name = "aperture_change" def solve(self): """ Linearize and solve corresponding system First, the function calculate if the slip-criterion is satisfied for each face: T_s <= mu * (T_n - p). If this is violated, the fracture is slipping. It estimates the slip in direction of shear traction as: d += T_s - mu(T_n - p) * sqrt(face_area) / G. Stores this result in self.x which is a ndarray of dimension (3, number of faces). Also updates the ndarray self.is_slipping to True for any face that violated the slip-criterion. Requires the following keywords in the data dictionary: 'face_pressure': (ndarray) size equal number of faces in the grid. Only the pressure on the fracture faces are used, and should be equivalent to the pressure in the pressure in the corresponding lower dimensional cells. 'traction': (ndarray) size (3, number_of_faces). This should be the area scaled traction on each face. 'rock': a Rock Object with shear stiffness Rock.MU defined. Returns: new_slip (bool) returns True if the slip vector was violated for any faces """ assert self._gb.dim == 3, "only support for 3D (yet)" frac_faces = self._gb.frac_pairs fi = frac_faces[1] fi_left = frac_faces[0] T_n, T_s, n, t = self.normal_shear_traction(fi) assert np.all(T_s > -1e-10) assert np.all(T_n < 0), "Must have a normal force on the fracture" # we find the effective normal stress on the fracture face. # Here we need to multiply T_n with -1 as we want the absolute value, # and all the normal tractions should be negative. sigma_n = -T_n - self._data["face_pressure"][fi] # assert np.all(sigma_n > 0 ) # new slip are fracture faces slipping in this iteration new_slip = (T_s - self.mu(fi, self.is_slipping[fi]) * sigma_n > 1e-5 * self._data["rock"].MU) self.is_slipping[fi] = self.is_slipping[fi] | new_slip # calculate the shear stiffness shear_stiffness = np.sqrt( self._gb.face_areas[fi]) / (self._data["rock"].MU) # calculate aproximated slip distance excess_shear = np.abs(T_s) - self.mu(fi, self.is_slipping[fi]) * sigma_n slip_d = excess_shear * shear_stiffness * self.gamma() * new_slip # We also add the values to the left cells so that when we average the # face values to obtain a cell value, it will equal the face value slip_vec = -t * slip_d - n * self.fracture_dilation(slip_d, fi) self.d_n[fi] += self.fracture_dilation(slip_d, fi) self.d_n[fi_left] += self.fracture_dilation(slip_d, fi_left) assert np.all(self.d_n[fi] > -1e-6) self.x[:, fi] += slip_vec self.x[:, fi_left] -= slip_vec return new_slip def normal_shear_traction(self, faces=None): """ Project the traction vector into the normal and tangential components as seen from the fractures. Requires that the data dictionary has keyword: traction: (ndarray) size (3, number of faces). giving the area weighted traction on each face. Returns: -------- T_n: (ndarray) size (number of fracture_cells) the normal traction on each fracture. T_s: (ndarray) size (number of fracture_cells) the shear traction on each fracture. normals: (ndarray) size (3, number of fracture_cells) normal vector, i.e., the direction of normal traction tangents: (ndarray) size (3, number of fracture_cells) tangential vector, i.e., the direction of shear traction """ if faces is None: frac_faces = self._gb.frac_pairs fi = frac_faces[1] else: fi = faces assert self._gb.dim == 3 T = self._data["traction"].copy() T = T / self._gb.face_areas sgn = sign_of_faces(self._gb, fi) # sgn_test = g.cell_faces[fi, ci] T = sgn * T[:, fi] normals = sgn * self._gb.face_normals[:, fi] / self._gb.face_areas[fi] assert np.allclose(np.sqrt(np.sum(normals**2, axis=0)), 1) T_n = np.sum(T * normals, axis=0) tangents = T - T_n * normals T_s = np.sqrt(np.sum(tangents**2, axis=0)) tangents = tangents / np.sqrt(np.sum(tangents**2, axis=0)) assert np.allclose(np.sqrt(np.sum(tangents**2, axis=0)), 1) assert np.allclose(T, T_n * normals + T_s * tangents) # Sanity check: frac_faces = self._gb.frac_pairs trac = self._data["traction"].copy() fi_left = frac_faces[0] sgn_left = sign_of_faces(self._gb, fi_left) sgn_right = sign_of_faces(self._gb, fi) T_left = sgn_left * trac.reshape((3, -1), order="F")[:, fi_left] T_right = sgn_right * trac.reshape((3, -1), order="F")[:, fi] assert np.allclose(T_left, -T_right) # TESTING DONE return T_n, T_s, normals, tangents def fracture_dilation(self, distance, _): """ defines the fracture dilation as a function of slip distance Parameters: ---------- distance: (ndarray) the slip distances Returns: --------- dilation: (ndarray) the corresponding normal displacement of fractures. """ phi = 1 * np.pi / 180 return distance * np.tan(phi) def mu(self, faces, slip_faces=[]): """ Coefficient of friction. Parameters: ---------- faces: (ndarray) indexes of fracture faces slip_faces: (ndarray) optional, defaults to []. Indexes of faces that are slipping ( will be given a dynamic friciton). returns: mu: (ndarray) the coefficient of each fracture face. """ mu_d = 0.55 mu_ = 0.6 * np.ones(faces.size) mu_[slip_faces] = mu_d return mu_ def gamma(self): """ Numerical step length parameter. Defines of far a fracture violating the slip-condition should slip. """ return 2 def step(self): """ calls self.solve() """ return self.solve() def grid(self): """ returns model grid """ return self._gb def data(self): """ returns data """ return self._data def slip_distance(self, slip_name="slip_distance"): """ Save the slip distance to the data dictionary. The slip distance will be saved as a (3, self.grid().num_faces) array Parameters: ----------- slip_name: (string) Defaults to 'slip_distance'. Defines the keyword for the saved slip distance in the data dictionary Returns: -------- d: (ndarray) the slip distance as a (3, self.grid().num_faces) array """ self.slip_name = slip_name self._data[self.slip_name] = self.x return self.x def aperture_change(self, aperture_name="aperture_change"): """ Save the aperture change to the data dictionary. The aperture change will be saved as a (self.grid().num_faces) array Parameters: ----------- slip_name: (string) Defaults to 'aperture_name'. Defines the keyword for the saved aperture change in the data dictionary Returns: -------- d: (ndarray) the change in aperture as a (self.grid().num_faces) array """ self.aperture_name = aperture_name self._data[self.aperture_name] = self.d_n return self.d_n def save(self, variables=None, save_every=None): """ Save the result as vtk. Parameters: ---------- variables: (list) Optional, defaults to None. If None, only the grid will be exported. A list of strings where each element defines a keyword in the data dictionary to be saved. time_step: (float) optinal, defaults to None. The time step of the variable(s) that is saved """ if variables is None: self.exporter.write_vtk() else: variables = { k: self._data[k] for k in variables if k in self._data } self.exporter.write_vtk(variables)
solver_flow.extract_u(gb, "up", "discharge") solver_flow.extract_p(gb, "up", "p") solver_flow.project_u(gb, "discharge", "P0u") # compute the flow rate total_flow_rate = 0 for g, d in gb: bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] if bound_faces.size != 0: bound_face_centers = g.face_centers[:, bound_faces] left = bound_face_centers[0, :] < domain["xmin"] + tol flow_rate = d["discharge"][bound_faces[left]] total_flow_rate += np.sum(flow_rate) save = Exporter(gb, "darcy", export_folder, binary=False) save.write_vtk(["p", "P0u"]) ################################################################# physics = "transport" advection = upwind.UpwindMixedDim(physics) mass = mass_matrix.MassMatrixMixedDim(physics) invMass = mass_matrix.InvMassMatrixMixDim(physics) # Assign parameters add_data_advection(gb, domain, tol) gb.add_node_prop("deltaT", prop=deltaT) U, rhs_u = advection.matrix_rhs(gb) M, _ = mass.matrix_rhs(gb)
class EllipticModel: """ Class for solving an incompressible flow problem: \nabla K \nabla p = q, where K is the second order permeability tenser, p the fluid pressure and q sinks and sources. Parameters in Init: gb: (Grid /GridBucket) a grid or grid bucket object. If gb = GridBucket a Parameter class should be added to each grid bucket data node with keyword 'param'. data: (dictionary) Defaults to None. Only used if gb is a Grid. Should contain a Parameter class with the keyword 'Param' physics: (string): defaults to 'flow' Functions: solve(): Calls reassemble and solves the linear system. Returns: the pressure p. Sets attributes: self.x step(): Same as solve, but without reassemble of the matrices reassemble(): Assembles the lhs matrix and rhs array. Returns: lhs, rhs. Sets attributes: self.lhs, self.rhs source_disc(): Defines the discretization of the source term. Returns Source discretization object flux_disc(): Defines the discretization of the flux term. Returns Flux discretization object (E.g., Tpfa) grid(): Returns: the Grid or GridBucket data(): Returns: Data dictionary split(name): Assignes the solution self.x to the data dictionary at each node in the GridBucket. Parameters: name: (string) The keyword assigned to the pressure discharge(): Calls split('pressure'). Then calculate the discharges over each face in the grids and between edges in the GridBucket save(): calls split('pressure'). Then export the pressure to a vtk file to the folder kwargs['folder_name'] with file name kwargs['file_name'], default values are 'results' for the folder and physics for the file name. """ def __init__(self, gb, data=None, physics="flow", **kwargs): self.physics = physics self._gb = gb self.is_GridBucket = isinstance(self._gb, GridBucket) self._data = data self.lhs = [] self.rhs = [] self.x = [] file_name = kwargs.get("file_name", physics) folder_name = kwargs.get("folder_name", "results") mesh_kw = kwargs.get("mesh_kw", {}) tic = time.time() logger.info("Create exporter") self.exporter = Exporter(self._gb, file_name, folder_name, **mesh_kw) logger.info("Elapsed time: " + str(time.time() - tic)) self._flux_disc = self.flux_disc() self._source_disc = self.source_disc() def solve(self, max_direct=40000, callback=False, **kwargs): """ Reassemble and solve linear system. After the funtion has been called, the attributes lhs and rhs are updated according to the parameter states. Also, the attribute x gives the pressure given the current state. TODO: Provide an option to save solver information if multiple systems are to be solved with the same left hand side. The function attempts to set up the best linear solver based on the system size. The setup and parameter choices here are still experimental. Parameters: max_direct (int): Maximum number of unknowns where a direct solver is applied. If a direct solver can be applied this is usually the most efficient option. However, if the system size is too large compared to available memory, a direct solver becomes extremely slow. callback (boolean, optional): If True iteration information will be output when an iterative solver is applied (system size larger than max_direct) Returns: np.array: Pressure state. """ logger.error("Solve elliptic model") # Discretize tic = time.time() logger.warning("Discretize") self.lhs, self.rhs = self.reassemble() logger.warning("Done. Elapsed time " + str(time.time() - tic)) # Solve tic = time.time() ls = LSFactory() if self.rhs.size < max_direct: logger.warning("Solve linear system using direct solver") self.x = ls.direct(self.lhs, self.rhs) else: logger.warning("Solve linear system using GMRES") precond = self._setup_preconditioner() # precond = ls.ilu(self.lhs) slv = ls.gmres(self.lhs) self.x, info = slv( self.rhs, M=precond, callback=callback, maxiter=10000, restart=1500, tol=1e-8, ) if info == 0: logger.warning("GMRES succeeded.") else: logger.warning("GMRES failed with status " + str(info)) logger.warning("Done. Elapsed time " + str(time.time() - tic)) return self.x def step(self): return self.solve() def reassemble(self): """ reassemble matrices. This must be called between every time step to update the rhs of the system. """ lhs_flux, rhs_flux = self._discretize(self._flux_disc) lhs_source, rhs_source = self._discretize(self._source_disc) assert lhs_source.nnz == 0, "Source lhs different from zero!" self.lhs = lhs_flux self.rhs = rhs_flux + rhs_source return self.lhs, self.rhs def source_disc(self): if self.is_GridBucket: return source.IntegralMixedDim(physics=self.physics, coupling=[None]) else: return source.Integral(physics=self.physics) def flux_disc(self): if self.is_GridBucket: return tpfa.TpfaMixedDim(physics=self.physics) else: return tpfa.Tpfa(physics=self.physics) def _discretize(self, discr): if self.is_GridBucket: return discr.matrix_rhs(self.grid()) else: return discr.matrix_rhs(self.grid(), self.data()) def grid(self): return self._gb def data(self): return self._data def split(self, x_name="solution"): self.x_name = x_name self._flux_disc.split(self.grid(), self.x_name, self.x) def pressure(self, pressure_name="pressure"): self.pressure_name = pressure_name if self.is_GridBucket: self.split(self.pressure_name) else: self._data[self.pressure_name] = self.x def discharge(self, discharge_name="discharge"): if self.is_GridBucket: fvutils.compute_discharges( self.grid(), self.physics, p_name=self.pressure_name ) else: fvutils.compute_discharges( self.grid(), self.physics, discharge_name, self.pressure_name, self._data, ) def permeability(self, perm_names=["kxx", "kyy", "kzz"]): """ Assign permeability to self._data, ready for export to vtk. For the moment, we only dump the main diagonals of the permeabliity. Extensions should be trivial if needed. Parameters: perm_names (list): Which components to export. Defaults to kxx, kyy and xzz. """ def get_ind(n): if n == "kxx": return 0 elif n == "kyy": return 1 elif n == "kzz": return 2 else: raise ValueError("Unknown perm keyword " + n) for n in perm_names: ind = get_ind(n) if self.is_GridBucket: for _, d in self.grid(): d[n] = d["param"].get_permeability().perm[ind, ind, :] else: self._data[n] = self._data["param"].get_permeability().perm[ind, ind, :] def porosity(self, poro_name="porosity"): if self.is_GridBucket: for _, d in self.grid(): d[poro_name] = d["param"].get_porosity() else: self._data[poro_name] = self._data["param"].get_porosity() def save(self, variables=None, save_every=None): if variables is None: self.exporter.write_vtk() else: if not self.is_GridBucket: variables = {k: self._data[k] for k in variables if k in self._data} self.exporter.write_vtk(variables) # Helper functions for linear solve below def _setup_preconditioner(self): solvers, ind, not_ind = self._assign_solvers() def precond(r): x = np.zeros_like(r) for s, i, ni in zip(solvers, ind, not_ind): x[i] += s(r[i]) return x def precond_mult(r): x = np.zeros_like(r) A = self.lhs for s, i, ni in zip(solvers, ind, not_ind): r_i = r[i] - A[i, :][:, ni] * x[ni] x[i] += s(r_i) return x def M(r): return precond(r) return spl.LinearOperator(self.lhs.shape, M) def _assign_solvers(self): mat, ind = self._obtain_submatrix() all_ind = np.arange(self.rhs.size) not_ind = [np.setdiff1d(all_ind, i) for i in ind] factory = LSFactory() num_mat = len(mat) solvers = np.empty(num_mat, dtype=np.object) for i, A in enumerate(mat): sz = A.shape[0] if sz < 5000: solvers[i] = factory.direct(A) else: # amg solver is pyamg is installed, if not ilu try: solvers[i] = factory.amg(A, as_precond=True) except ImportError: solvers[i] = factory.ilu(A) return solvers, ind, not_ind def _obtain_submatrix(self): if isinstance(self.grid(), GridBucket): gb = self.grid() fd = self.flux_disc() mat = [] sub_ind = [] for g, _ in self.grid(): ind = fd.solver.dof_of_grid(gb, g) A = self.lhs[ind, :][:, ind] mat.append(A) sub_ind.append(ind) return mat, sub_ind else: return [self.lhs], [np.arange(self.grid().num_cells)]
class ParabolicModel(): ''' Base class for solving general pde problems. This class solves equations of the type: dT/dt + v*\nabla T - \nabla K \nabla T = q Init: - gb (Grid/GridBucket) Grid or grid bucket for the problem - physics (string) Physics key word. See Parameters class for valid physics Functions: data(): returns data dictionary. Is only used for single grids (I.e. not GridBucket) solve(): solve problem step(): take one time step update(t): update parameters to time t reassemble(): reassemble matrices and right hand side solver(): initiate solver (see numerics.pde_solver) advective_disc(): discretization of the advective term diffusive_disc(): discretization of the diffusive term soruce_disc(): discretization of the source term, q space_disc(): returns one or more of the above discretizations. If advective_disc(), source_disc() are returned we solve the problem without diffusion time_disc(): returns the time discretization initial_condition(): returns the initial condition for global variable grid(): returns the grid bucket for the problem time_step(): returns time step length end_time(): returns end time save(save_every=1): save solution. Parameter: save_every, save only every save_every time steps Example: # We create a problem with default data, neglecting the advective term class ExampleProblem(ParabolicProblem): def __init__(self, gb): self._g = gb ParabolicProblem.__init__(self) def space_disc(self): return self.source_disc(), self.diffusive_discr() gb = meshing.cart_grid([], [10,10], physdims=[1,1]) for g, d in gb: d['problem'] = ParabolicData(g, d) problem = ExampleProblem(gb) problem.solve() ''' def __init__(self, gb, physics='transport', time_step=1.0, end_time=1.0, **kwargs): self._gb = gb self.is_GridBucket = isinstance(self._gb, GridBucket) self.physics = physics self._data = kwargs.get('data', dict()) self._time_step = time_step self._end_time = end_time self._set_data() self._solver = self.solver() self._solver.parameters['store_results'] = False file_name = kwargs.get('file_name', str(physics)) folder_name = kwargs.get('folder_name', 'results') logger.info('Create exporter') tic = time.time() self.exporter = Exporter(self._gb, file_name, folder_name) logger.info('Done. Elapsed time: ' + str(time.time() - tic)) self.x_name = 'solution' self._time_disc = self.time_disc() def data(self): 'Get data dictionary' return self._data def _set_data(self): if self.is_GridBucket: for _, d in self.grid(): d['deltaT'] = self.time_step() else: self.data()['deltaT'] = self.time_step() def solve(self): 'Solve problem' tic = time.time() logger.info('Solve problem') s = self._solver.solve() logger.info('Done. Elapsed time: ' + str(time.time() - tic)) return s def step(self): 'Take one time step' return self._solver.step() def update(self, t): 'Update parameters to time t' if self.is_GridBucket: for g, d in self.grid(): d[self.physics + '_data'].update(t) else: self.data()[self.physics + '_data'].update(t) def split(self, x_name='solution'): self.x_name = x_name self._time_disc.split(self.grid(), self.x_name, self._solver.p) def reassemble(self): 'Reassemble matrices and rhs' return self._solver.reassemble() def solver(self): 'Initiate solver' return time_stepper.Implicit(self) def advective_disc(self): 'Discretization of fluid_density*fluid_specific_heat * v * \nabla T' class WeightedUpwindDisc(upwind.Upwind): def __init__(self): self.physics = 'transport' def matrix_rhs(self, g, data): lhs, rhs = upwind.Upwind.matrix_rhs(self, g, data) factor = data['param'].fluid_specific_heat\ * data['param'].fluid_density lhs *= factor rhs *= factor return lhs, rhs class WeightedUpwindCoupler(upwind.UpwindCoupling): def __init__(self, discr): self.physics = 'transport' upwind.UpwindCoupling.__init__(self, discr) def matrix_rhs(self, g_h, g_l, data_h, data_l, data_edge): cc = upwind.UpwindCoupling.matrix_rhs(self, g_h, g_l, data_h, data_l, data_edge) factor = data_h['param'].fluid_specific_heat \ * data_h['param'].fluid_density return cc * factor class WeightedUpwindMixedDim(upwind.UpwindMixedDim): def __init__(self): self.physics = 'transport' self.discr = WeightedUpwindDisc() self.discr_ndof = self.discr.ndof self.coupling_conditions = WeightedUpwindCoupler(self.discr) self.solver = coupler.Coupler(self.discr, self.coupling_conditions) if self.is_GridBucket: upwind_discr = WeightedUpwindMixedDim() else: upwind_discr = WeightedUpwindDisc() return upwind_discr def diffusive_disc(self): 'Discretization of term \nabla K \nabla T' if self.is_GridBucket: diffusive_discr = tpfa.TpfaMixedDim(physics=self.physics) else: diffusive_discr = tpfa.Tpfa(physics=self.physics) return diffusive_discr def source_disc(self): 'Discretization of source term, q' if self.is_GridBucket: return source.IntegralMixedDim(physics=self.physics) else: return source.Integral(physics=self.physics) def space_disc(self): '''Space discretization. Returns the discretization terms that should be used in the model''' return self.advective_disc(), self.diffusive_disc(), self.source_disc() def time_disc(self): """ Returns the time discretization. """ class TimeDisc(mass_matrix.MassMatrix): def __init__(self, deltaT): self.deltaT = deltaT def matrix_rhs(self, g, data): ndof = g.num_cells aperture = data['param'].get_aperture() coeff = g.cell_volumes * aperture / self.deltaT factor_fluid = data['param'].fluid_specific_heat\ * data['param'].fluid_density\ * data['param'].porosity factor_rock = data['param'].rock_specific_heat\ * data['param'].rock_density\ * (1 - data['param'].porosity) factor = sps.dia_matrix((factor_fluid + factor_rock, 0), shape=(ndof, ndof)) lhs = sps.dia_matrix((coeff, 0), shape=(ndof, ndof)) rhs = np.zeros(ndof) return factor * lhs, factor * rhs single_dim_discr = TimeDisc(self.time_step()) if self.is_GridBucket: time_discretization = coupler.Coupler(single_dim_discr) else: time_discretization = TimeDisc(self.time_step()) return time_discretization def initial_condition(self): 'Returns initial condition for global variable' if self.is_GridBucket: for _, d in self.grid(): d[self.physics] = d[self.physics + '_data'].initial_condition() global_variable = self.time_disc().merge(self.grid(), self.physics) else: global_variable = self._data[self.physics + '_data'].initial_condition() return global_variable def grid(self): 'Returns grid/grid_bucket' return self._gb def time_step(self): 'Returns the time step' return self._time_step def end_time(self): 'Returns the end time' return self._end_time def save(self, variables=None, time=None, save_every=None): if variables is None: self.exporter.write_vtk() else: if not self.is_GridBucket: variables = {k: self._data[k] for k in variables \ if k in self._data} self.exporter.write_vtk(variables, time_step=time)