def test_upwind_2d_simplex_surf_discharge_negative(self): g = simplex.StructuredTriangleGrid([2, 1], [1, 1]) R = cg.rot(-np.pi / 5., [1, 1, -1]) g.nodes = np.dot(R, g.nodes) g.compute_geometry(is_embedded=True) solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, np.dot(R, [-1, 0, 0])) bf = g.tags['domain_boundary_faces'].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ['neu']) param.set_bc(solver, bc) data = {'param': param, 'discharge': dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = np.array([[1, 0, 0, -1], [-1, 0, 0, 0], [0, 0, 1, 0], [0, 0, -1, 1]]) deltaT_known = 1 / 6 rtol = 1e-15 atol = rtol assert np.allclose(M, M_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
def test_upwind_1d_discharge_negative_bc_neu(self): g = structured.CartGrid(3, 1) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [-2, 0, 0]) bf = g.tags['domain_boundary_faces'].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ['neu']) bc_val = np.array([2, 0, 0, -2]).ravel('F') param.set_bc(solver, bc) param.set_bc_val(solver, bc_val) data = {'param': param, 'discharge': dis} M, rhs = solver.matrix_rhs(g, data) deltaT = solver.cfl(g, data) M_known = np.array([[0, -2, 0], [0, 2, -2], [0, 0, 2]]) rhs_known = np.array([-2, 0, 2]) deltaT_known = 1 / 12 rtol = 1e-15 atol = rtol assert np.allclose(M.todense(), M_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
def test_upwind_2d_simplex_surf_discharge_positive(self): g = simplex.StructuredTriangleGrid([2, 1], [1, 1]) R = cg.rot(np.pi / 2., [1, 1, 0]) g.nodes = np.dot(R, g.nodes) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, np.dot(R, [1, 0, 0])) bf = g.tags["domain_boundary_faces"].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ["neu"]) param.set_bc(solver, bc) data = {"param": param, "discharge": dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = np.array([[1, -1, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1], [-1, 0, 0, 1]]) deltaT_known = 1 / 6 rtol = 1e-15 atol = rtol self.assertTrue(np.allclose(M, M_known, rtol, atol)) self.assertTrue(np.allclose(deltaT, deltaT_known, rtol, atol))
def test_upwind_2d_cart_surf_discharge_negative(self): g = structured.CartGrid([3, 2], [1, 1]) R = cg.rot(np.pi/6., [1,1,0]) g.nodes = np.dot(R, g.nodes) g.compute_geometry(is_embedded=True) solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, np.dot(R, [-1, 0, 0])) bf = g.get_boundary_faces() bc = BoundaryCondition(g, bf, bf.size * ['neu']) param.set_bc(solver, bc) data = {'param': param, 'discharge': dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = 0.5 * np.array([[ 0,-1, 0, 0, 0, 0], [ 0, 1,-1, 0, 0, 0], [ 0, 0, 1, 0, 0, 0], [ 0, 0, 0, 0,-1, 0], [ 0, 0, 0, 0, 1,-1], [ 0, 0, 0, 0, 0, 1]]) deltaT_known = 1/6 rtol = 1e-15 atol = rtol assert np.allclose(M, M_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
def test_upwind_1d_discharge_negative_bc_dir(self): g = structured.CartGrid(3, 1) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [-2, 0, 0]) bf = g.tags["domain_boundary_faces"].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ["dir"]) bc_val = 3 * np.ones(g.num_faces).ravel("F") param.set_bc(solver, bc) param.set_bc_val(solver, bc_val) data = {"param": param, "discharge": dis} M, rhs = solver.matrix_rhs(g, data) deltaT = solver.cfl(g, data) M_known = np.array([[2, -2, 0], [0, 2, -2], [0, 0, 2]]) rhs_known = np.array([0, 0, 6]) deltaT_known = 1 / 12 rtol = 1e-15 atol = rtol self.assertTrue(np.allclose(M.todense(), M_known, rtol, atol)) self.assertTrue(np.allclose(rhs, rhs_known, rtol, atol)) self.assertTrue(np.allclose(deltaT, deltaT_known, rtol, atol))
def test_upwind_3d_cart_discharge_positive(self): g = structured.CartGrid([2, 2, 2], [1, 1, 1]) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [1, 0, 0]) bf = g.tags["domain_boundary_faces"].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ["neu"]) param.set_bc(solver, bc) data = {"param": param, "discharge": dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = 0.25 * np.array([ [1, 0, 0, 0, 0, 0, 0, 0], [-1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, -1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, -1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, -1, 0], ]) deltaT_known = 1 / 4 rtol = 1e-15 atol = rtol self.assertTrue(np.allclose(M, M_known, rtol, atol)) self.assertTrue(np.allclose(deltaT, deltaT_known, rtol, atol))
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 test_upwind_1d_discharge_negative(self): g = structured.CartGrid(3, 1) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [-2, 0, 0]) bf = g.get_boundary_faces() bc = BoundaryCondition(g, bf, bf.size * ['neu']) param.set_bc(solver, bc) data = {'param': param, 'discharge': dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = np.array([[0,-2, 0], [0, 2,-2], [0, 0, 2]]) deltaT_known = 1/12 rtol = 1e-15 atol = rtol assert np.allclose(M, M_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
def test_upwind_2d_cart_discharge_positive(self): g = structured.CartGrid([3, 2], [1, 1]) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [2, 0, 0]) bf = g.tags['domain_boundary_faces'].nonzero()[0] bc = BoundaryCondition(g, bf, bf.size * ['neu']) param.set_bc(solver, bc) data = {'param': param, 'discharge': dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = np.array([[1, 0, 0, 0, 0, 0], [-1, 1, 0, 0, 0, 0], [0, -1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, -1, 1, 0], [0, 0, 0, 0, -1, 0]]) deltaT_known = 1 / 12 rtol = 1e-15 atol = rtol assert np.allclose(M, M_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
def test_upwind_2d_simplex_discharge_negative(self): g = simplex.StructuredTriangleGrid([2, 1], [1, 1]) g.compute_geometry() solver = upwind.Upwind() param = Parameters(g) dis = solver.discharge(g, [-1, 0, 0]) bf = g.get_boundary_faces() bc = BoundaryCondition(g, bf, bf.size * ['neu']) param.set_bc(solver, bc) data = {'param': param, 'discharge': dis} M = solver.matrix_rhs(g, data)[0].todense() deltaT = solver.cfl(g, data) M_known = np.array([[ 1, 0, 0,-1], [-1, 0, 0, 0], [ 0, 0, 1, 0], [ 0, 0,-1, 1]]) deltaT_known = 1/6 rtol = 1e-15 atol = rtol assert np.allclose(M, M_known, rtol, atol) assert np.allclose(deltaT, deltaT_known, rtol, atol)
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 add_data_transport(gb): gb.add_node_props(['bc', 'bc_val', 'discharge', 'apertures']) for g, d in gb: b_faces = g.tags['domain_boundary_faces'].nonzero()[0] index = np.nonzero( abs(g.face_centers[:, b_faces]) == np.ones([3, b_faces.size]))[1] b_faces = b_faces[index] d['bc'] = bc.BoundaryCondition(g, b_faces, ['dir'] * b_faces.size) d['bc_val'] = {'dir': np.ones(b_faces.size)} d['apertures'] = np.ones(g.num_cells) * np.power(.01, float(g.dim < 3)) d['discharge'] = upwind.Upwind().discharge( g, [1, 1, 2 * np.power(1000, g.dim < 2)], d['apertures']) gb.add_edge_prop('discharge') for e, d in gb.edges_props(): g_h = gb.sorted_nodes_of_edge(e)[1] d['discharge'] = gb.node_prop(g_h, 'discharge')
def add_data_transport(gb): gb.add_node_props(["bc", "bc_val", "discharge", "apertures"]) for g, d in gb: b_faces = g.tags["domain_boundary_faces"].nonzero()[0] index = np.nonzero( abs(g.face_centers[:, b_faces]) == np.ones([3, b_faces.size]))[1] b_faces = b_faces[index] d["bc"] = bc.BoundaryCondition(g, b_faces, ["dir"] * b_faces.size) d["bc_val"] = {"dir": np.ones(b_faces.size)} d["apertures"] = np.ones(g.num_cells) * np.power(.01, float(g.dim < 3)) d["discharge"] = upwind.Upwind().discharge( g, [1, 1, 2 * np.power(1000, g.dim < 2)], d["apertures"]) gb.add_edge_prop("discharge") for e, d in gb.edges_props(): g_h = gb.sorted_nodes_of_edge(e)[1] d["discharge"] = gb.node_prop(g_h, "discharge")
def atest_upwind_2d_1d_cross_with_elimination(self): """ Simplest possible elimination scenario, one 0d-grid removed. Check on upwind matrix, rhs, solution and time step estimate. Full solution included (as comments) for comparison purposes if test breaks. """ f1 = np.array([[0, 1], [.5, .5]]) f2 = np.array([[.5, .5], [0, 1]]) domain = {"xmin": 0, "ymin": 0, "xmax": 1, "ymax": 1} mesh_size = 0.4 mesh_kwargs = {} mesh_kwargs["mesh_size"] = { "mode": "constant", "value": mesh_size, "bound_value": mesh_size, } gb = pp.meshing.cart_grid([f1, f2], [2, 2], **{"physdims": [1, 1]}) # gb = pp.meshing.simplex_grid( [f1, f2],domain,**mesh_kwargs) gb.compute_geometry() gb.assign_node_ordering() # Enforce node orderning because of Python 3.5 and 2.7. # Don't do it in general. cell_centers_1 = np.array([ [7.50000000e-01, 2.500000000e-01], [5.00000000e-01, 5.00000000e-01], [-5.55111512e-17, 5.55111512e-17], ]) cell_centers_2 = np.array([ [5.00000000e-01, 5.00000000e-01], [7.50000000e-01, 2.500000000e-01], [-5.55111512e-17, 5.55111512e-17], ]) for g, d in gb: if g.dim == 1: if np.allclose(g.cell_centers, cell_centers_1): d["node_number"] = 1 elif np.allclose(g.cell_centers, cell_centers_2): d["node_number"] = 2 else: raise ValueError("Grid not found") tol = 1e-3 solver = pp.TpfaMixedDim() gb.add_node_props(["param"]) a = 1e-2 for g, d in gb: param = pp.Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) kxx = np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max()) p = pp.SecondOrderTensor(3, kxx, kyy=kxx, kzz=kxx) param.set_tensor("flow", p) bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] if bound_faces.size != 0: bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(["neu"] * bound_faces.size) labels[right] = ["dir"] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0, bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu] * a_dim param.set_bc("flow", pp.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val("flow", bc_val) # Transport bottom = bound_face_centers[1, :] < tol top = bound_face_centers[1, :] > 1 - tol labels = np.array(["neu"] * bound_faces.size) labels[np.logical_or(np.logical_or(left, right), np.logical_or(top, bottom))] = ["dir"] bc_val = np.zeros(g.num_faces) param.set_bc("transport", pp.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val("transport", bc_val) else: param.set_bc("transport", pp.BoundaryCondition(g, np.empty(0), np.empty(0))) param.set_bc("flow", pp.BoundaryCondition(g, np.empty(0), np.empty(0))) # Transport: source = g.cell_volumes * a_dim param.set_source("transport", source) d["param"] = param gb.add_edge_props("param") for e, d in gb.edges(): g_h = gb.nodes_of_edge(e)[1] d["param"] = pp.Parameters(g_h) A, rhs = solver.matrix_rhs(gb) # p = sps.linalg.spsolve(A,rhs) _, p_red, _, _ = condensation.solve_static_condensation(A, rhs, gb, dim=0) dim_to_remove = 0 gb_r, elimination_data = gb.duplicate_without_dimension(dim_to_remove) condensation.compute_elimination_fluxes(gb, gb_r, elimination_data) solver.split(gb_r, "pressure", p_red) # pp.fvutils.compute_discharges(gb) pp.fvutils.compute_discharges(gb_r) # ------Transport------# advection_discr = upwind.Upwind(physics="transport") advection_coupling_conditions = upwind.UpwindCoupling(advection_discr) advection_coupler = coupler.Coupler(advection_discr, advection_coupling_conditions) U_r, rhs_u_r = advection_coupler.matrix_rhs(gb_r) _, rhs_src_r = pp.IntegralMixedDim( physics="transport").matrix_rhs(gb_r) rhs_u_r = rhs_u_r + rhs_src_r deltaT = np.amin( gb_r.apply_function(advection_discr.cfl, advection_coupling_conditions.cfl).data) theta_r = sps.linalg.spsolve(U_r, rhs_u_r) U_known, rhs_known, theta_known, deltaT_known = known_for_elimination() tol = 1e-7 self.assertTrue(np.isclose(deltaT, deltaT_known, tol, tol)) self.assertTrue((np.amax(np.absolute(U_r - U_known))) < tol) self.assertTrue((np.amax(np.absolute(rhs_u_r - rhs_known))) < tol) self.assertTrue((np.amax(np.absolute(theta_r - theta_known))) < tol)
# gb = meshing.cart_grid(f_set, [Nx, Nx, Nx]) path_to_gmsh = '~/gmsh-2.16.0-Linux/bin/gmsh' gb = meshing.simplex_grid(f_set, domain, gmsh_path=path_to_gmsh) gb.assign_node_ordering() ################## Transport solver ################## print("Compute global matrix and rhs for the advection problem") gb_r, elimination_data = gb.duplicate_without_dimension(0) condensation.compute_elimination_fluxes(gb, gb_r, elimination_data) add_data_transport(gb) add_data_transport(gb_r) upwind_solver = upwind.Upwind() upwind_cc = upwind.UpwindCoupling(upwind_solver) coupler_solver = coupler.Coupler(upwind_solver, upwind_cc) U, rhs = coupler_solver.matrix_rhs(gb) U_r, rhs_r = coupler_solver.matrix_rhs(gb_r) deltaT = np.amin([upwind_solver.cfl(g, d) for g, d in gb]) deltaT_r = np.amin([upwind_solver.cfl(g, d) for g, d in gb_r]) T = deltaT * max(Nx, Ny) * 4 gb.add_node_prop("deltaT", None, deltaT) gb_r.add_node_prop("deltaT", None, deltaT_r) mass_solver = mass_matrix.MassMatrix() coupler_solver = coupler.Coupler(mass_solver)
diffusion = second_order_tensor.SecondOrderTensorTensor(g.dim, kxx) f = np.ones(g.num_cells) * g.cell_volumes data = {'beta_n': beta_n, 'bc': bnd, 'bc_val': bnd_val, 'k': diffusion, 'f': f} return data #------------------------------------------------------------------------------# # the f is considered twice, we guess that non-homogeneous Neumann as well. Nx = Ny = 20 g = structured.CartGrid([Nx, Ny], [1, 1]) g.compute_geometry() advection = upwind.Upwind() diffusion = tpfa.Tpfa() # Assign parameters data = add_data(g, advection) U, rhs_u = advection.matrix_rhs(g, data) D, rhs_d = diffusion.matrix_rhs(g, data) theta = sps.linalg.spsolve(D + U, rhs_u + rhs_d) exporter.export_vtk(g, "advection_diffusion", {"theta": theta})
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)