def scattered_volume_field(self, Jt, rho, qbx_forced_limit=None): """ This will return an object array of six entries, the first three of which represent the electric, and the second three of which represent the magnetic field. This satisfies the time-domain Maxwell's equations as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. """ Jxyz = sym.cse(sym.tangential_to_xyz(Jt), "Jxyz") A = sym.S(self.kernel, Jxyz, k=self.k, qbx_forced_limit=qbx_forced_limit) phi = sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qbx_forced_limit) E_scat = 1j*self.k*A - sym.grad(3, phi) H_scat = sym.curl(A) return sym.flat_obj_array(E_scat, H_scat)
def scattered_volume_field(self, Jt, rho, qbx_forced_limit=None): """ This will return an object array of six entries, the first three of which represent the electric, and the second three of which represent the magnetic field. This satisfies the time-domain Maxwell's equations as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. """ Jxyz = sym.cse(sym.tangential_to_xyz(Jt), "Jxyz") A = sym.S(self.kernel, Jxyz, k=self.k, qbx_forced_limit=qbx_forced_limit) phi = sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qbx_forced_limit) E_scat = 1j*self.k*A - sym.grad(3, phi) H_scat = sym.curl(A) return sym.join_fields(E_scat, H_scat)
def nonlocal_integral_eq( mesh, scatterer_bdy_id, outer_bdy_id, wave_number, options_prefix=None, solver_parameters=None, fspace=None, vfspace=None, true_sol_grad_expr=None, actx=None, dgfspace=None, dgvfspace=None, meshmode_src_connection=None, qbx_kwargs=None, ): r""" see run_method for descriptions of unlisted args args: gamma and beta are used to precondition with the following equation: \Delta u - \kappa^2 \gamma u = 0 (\partial_n - i\kappa\beta) u |_\Sigma = 0 """ # make sure we get outer bdy id as tuple in case it consists of multiple ids if isinstance(outer_bdy_id, int): outer_bdy_id = [outer_bdy_id] outer_bdy_id = tuple(outer_bdy_id) # away from the excluded region, but firedrake and meshmode point # into pyt_inner_normal_sign = -1 ambient_dim = mesh.geometric_dimension() # {{{ Build src and tgt # build connection meshmode near src boundary -> src boundary inside meshmode from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from meshmode.discretization.connection import make_face_restriction factory = InterpolatoryQuadratureSimplexGroupFactory( dgfspace.finat_element.degree) src_bdy_connection = make_face_restriction(actx, meshmode_src_connection.discr, factory, scatterer_bdy_id) # source is a qbx layer potential from pytential.qbx import QBXLayerPotentialSource disable_refinement = (fspace.mesh().geometric_dimension() == 3) qbx = QBXLayerPotentialSource(src_bdy_connection.to_discr, **qbx_kwargs, _disable_refinement=disable_refinement) # get target indices and point-set target_indices, target = get_target_points_and_indices( fspace, outer_bdy_id) # }}} # build the operations from pytential import bind, sym r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * sym.grad( ambient_dim, sym.D(HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) """ op = pyt_inner_normal_sign * 1j * sym.var("k") * (sym.D( HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) # bind the operations pyt_grad_op = bind((qbx, target), grad_op) pyt_op = bind((qbx, target), op) # }}} class MatrixFreeB(object): def __init__(self, A, pyt_grad_op, pyt_op, actx, kappa): """ :arg kappa: The wave number """ self.actx = actx self.k = kappa self.pyt_op = pyt_op self.pyt_grad_op = pyt_grad_op self.A = A self.meshmode_src_connection = meshmode_src_connection # {{{ Create some functions needed for multing self.x_fntn = Function(fspace) # CG self.potential_int = Function(fspace) self.potential_int.dat.data[:] = 0.0 self.grad_potential_int = Function(vfspace) self.grad_potential_int.dat.data[:] = 0.0 self.pyt_result = Function(fspace) self.n = FacetNormal(mesh) self.v = TestFunction(fspace) # some meshmode ones self.x_mm_fntn = self.meshmode_src_connection.discr.empty( self.actx, dtype='c') # }}} def mult(self, mat, x, y): # Copy function data into the fivredrake function self.x_fntn.dat.data[:] = x[:] # Transfer the function to meshmode self.meshmode_src_connection.from_firedrake(project( self.x_fntn, dgfspace), out=self.x_mm_fntn) # Restrict to boundary x_mm_fntn_on_bdy = src_bdy_connection(self.x_mm_fntn) # Apply the operation potential_int_mm = self.pyt_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) grad_potential_int_mm = self.pyt_grad_op(self.actx, u=x_mm_fntn_on_bdy, k=self.k) # Store in firedrake self.potential_int.dat.data[target_indices] = potential_int_mm.get( ) for dim in range(grad_potential_int_mm.shape[0]): self.grad_potential_int.dat.data[ target_indices, dim] = grad_potential_int_mm[dim].get() # Integrate the potential r""" Compute the inner products using firedrake. Note this will be subtracted later, hence appears off by a sign. .. math:: \langle n(x) \cdot \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma - \langle i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma """ self.pyt_result = assemble( inner(inner(self.grad_potential_int, self.n), self.v) * ds(outer_bdy_id) - inner(self.potential_int, self.v) * ds(outer_bdy_id)) # y <- Ax - evaluated potential self.A.mult(x, y) with self.pyt_result.dat.vec_ro as ep: y.axpy(-1, ep) # {{{ Compute normal helmholtz operator u = TrialFunction(fspace) v = TestFunction(fspace) r""" .. math:: \langle \nabla u, \nabla v \rangle - \kappa^2 \cdot \langle u, v \rangle - i \kappa \langle u, v \rangle_\Sigma """ a = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2) * inner(u, v) * dx \ - Constant(1j * wave_number) * inner(u, v) * ds(outer_bdy_id) # get the concrete matrix from a general bilinear form A = assemble(a).M.handle # }}} # {{{ Setup Python matrix B = PETSc.Mat().create() # build matrix context Bctx = MatrixFreeB(A, pyt_grad_op, pyt_op, actx, wave_number) # set up B as same size as A B.setSizes(*A.getSizes()) B.setType(B.Type.PYTHON) B.setPythonContext(Bctx) B.setUp() # }}} # {{{ Create rhs # Remember f is \partial_n(true_sol)|_\Gamma # so we just need to compute \int_\Gamma\partial_n(true_sol) H(x-y) sigma = sym.make_sym_vector("sigma", ambient_dim) r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * \ sym.grad(ambient_dim, sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ op = 1j * sym.var("k") * pyt_inner_normal_sign * \ sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None) rhs_grad_op = bind((qbx, target), grad_op) rhs_op = bind((qbx, target), op) # Transfer to meshmode metadata = {'quadrature_degree': 2 * fspace.ufl_element().degree()} dg_true_sol_grad = project(true_sol_grad_expr, dgvfspace, form_compiler_parameters=metadata) true_sol_grad_mm = meshmode_src_connection.from_firedrake(dg_true_sol_grad, actx=actx) true_sol_grad_mm = src_bdy_connection(true_sol_grad_mm) # Apply the operations f_grad_convoluted_mm = rhs_grad_op(actx, sigma=true_sol_grad_mm, k=wave_number) f_convoluted_mm = rhs_op(actx, sigma=true_sol_grad_mm, k=wave_number) # Transfer function back to firedrake f_grad_convoluted = Function(vfspace) f_convoluted = Function(fspace) f_grad_convoluted.dat.data[:] = 0.0 f_convoluted.dat.data[:] = 0.0 for dim in range(f_grad_convoluted_mm.shape[0]): f_grad_convoluted.dat.data[target_indices, dim] = f_grad_convoluted_mm[dim].get() f_convoluted.dat.data[target_indices] = f_convoluted_mm.get() r""" \langle f, v \rangle_\Gamma + \langle i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma - \langle n(x) \cdot \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma """ rhs_form = inner(inner(true_sol_grad_expr, FacetNormal(mesh)), v) * ds(scatterer_bdy_id, metadata=metadata) \ + inner(f_convoluted, v) * ds(outer_bdy_id) \ - inner(inner(f_grad_convoluted, FacetNormal(mesh)), v) * ds(outer_bdy_id) rhs = assemble(rhs_form) # {{{ set up a solver: solution = Function(fspace, name="Computed Solution") # {{{ Used for preconditioning if 'gamma' in solver_parameters or 'beta' in solver_parameters: gamma = complex(solver_parameters.pop('gamma', 1.0)) import cmath beta = complex(solver_parameters.pop('beta', cmath.sqrt(gamma))) p = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2 * gamma) * inner(u, v) * dx \ - Constant(1j * wave_number * beta) * inner(u, v) * ds(outer_bdy_id) P = assemble(p).M.handle else: P = A # }}} # Set up options to contain solver parameters: ksp = PETSc.KSP().create() if solver_parameters['pc_type'] == 'pyamg': del solver_parameters['pc_type'] # We are using the AMG preconditioner pyamg_tol = solver_parameters.get('pyamg_tol', None) if pyamg_tol is not None: pyamg_tol = float(pyamg_tol) pyamg_maxiter = solver_parameters.get('pyamg_maxiter', None) if pyamg_maxiter is not None: pyamg_maxiter = int(pyamg_maxiter) ksp.setOperators(B) ksp.setUp() pc = ksp.pc pc.setType(pc.Type.PYTHON) pc.setPythonContext( AMGTransmissionPreconditioner(wave_number, fspace, A, tol=pyamg_tol, maxiter=pyamg_maxiter, use_plane_waves=True)) # Otherwise use regular preconditioner else: ksp.setOperators(B, P) options_manager = OptionsManager(solver_parameters, options_prefix) options_manager.set_from_options(ksp) import petsc4py.PETSc petsc4py.PETSc.Sys.popErrorHandler() with rhs.dat.vec_ro as b: with solution.dat.vec as x: ksp.solve(b, x) # }}} return ksp, solution
def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): mesh = case.get_mesh(resolution, case.target_order) print("%d elements" % mesh.nelements) from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) source_order = 4*case.target_order refiner_extra_kwargs = {} qbx_lpot_kwargs = {} if case.fmm_backend is None: qbx_lpot_kwargs["fmm_order"] = False else: if hasattr(case, "fmm_tol"): from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder qbx_lpot_kwargs["fmm_level_to_order"] = SimpleExpansionOrderFinder( case.fmm_tol) elif hasattr(case, "fmm_order"): qbx_lpot_kwargs["fmm_order"] = case.fmm_order else: qbx_lpot_kwargs["fmm_order"] = case.qbx_order + 5 qbx = QBXLayerPotentialSource( pre_density_discr, fine_order=source_order, qbx_order=case.qbx_order, _box_extent_norm=getattr(case, "box_extent_norm", None), _from_sep_smaller_crit=getattr(case, "from_sep_smaller_crit", None), _from_sep_smaller_min_nsources_cumul=30, fmm_backend=case.fmm_backend, **qbx_lpot_kwargs) if case.use_refinement: if case.k != 0 and getattr(case, "refine_on_helmholtz_k", True): refiner_extra_kwargs["kernel_length_scale"] = 5/case.k if hasattr(case, "scaled_max_curvature_threshold"): refiner_extra_kwargs["_scaled_max_curvature_threshold"] = \ case.scaled_max_curvature_threshold if hasattr(case, "expansion_disturbance_tolerance"): refiner_extra_kwargs["_expansion_disturbance_tolerance"] = \ case.expansion_disturbance_tolerance if hasattr(case, "refinement_maxiter"): refiner_extra_kwargs["maxiter"] = case.refinement_maxiter #refiner_extra_kwargs["visualize"] = True print("%d elements before refinement" % pre_density_discr.mesh.nelements) qbx, _ = qbx.with_refinement(**refiner_extra_kwargs) print("%d stage-1 elements after refinement" % qbx.density_discr.mesh.nelements) print("%d stage-2 elements after refinement" % qbx.stage2_density_discr.mesh.nelements) print("quad stage-2 elements have %d nodes" % qbx.quad_stage2_density_discr.groups[0].nunit_nodes) density_discr = qbx.density_discr if hasattr(case, "visualize_geometry") and case.visualize_geometry: bdry_normals = bind( density_discr, sym.normal(mesh.ambient_dim) )(queue).as_vector(dtype=object) bdry_vis = make_visualizer(queue, density_discr, case.target_order) bdry_vis.write_vtk_file("geometry.vtu", [ ("normals", bdry_normals) ]) # {{{ plot geometry if 0: if mesh.ambient_dim == 2: # show geometry, centers, normals nodes_h = density_discr.nodes().get(queue=queue) pt.plot(nodes_h[0], nodes_h[1], "x-") normal = bind(density_discr, sym.normal(2))(queue).as_vector(np.object) pt.quiver(nodes_h[0], nodes_h[1], normal[0].get(queue), normal[1].get(queue)) pt.gca().set_aspect("equal") pt.show() elif mesh.ambient_dim == 3: bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) bdry_normals = bind(density_discr, sym.normal(3))(queue)\ .as_vector(dtype=object) bdry_vis.write_vtk_file("pre-solve-source-%s.vtu" % resolution, [ ("bdry_normals", bdry_normals), ]) else: raise ValueError("invalid mesh dim") # }}} # {{{ set up operator from pytential.symbolic.pde.scalar import ( DirichletOperator, NeumannOperator) from sumpy.kernel import LaplaceKernel, HelmholtzKernel if case.k: knl = HelmholtzKernel(mesh.ambient_dim) knl_kwargs = {"k": sym.var("k")} concrete_knl_kwargs = {"k": case.k} else: knl = LaplaceKernel(mesh.ambient_dim) knl_kwargs = {} concrete_knl_kwargs = {} if knl.is_complex_valued: dtype = np.complex128 else: dtype = np.float64 loc_sign = +1 if case.prob_side in [+1, "scat"] else -1 if case.bc_type == "dirichlet": op = DirichletOperator(knl, loc_sign, use_l2_weighting=True, kernel_arguments=knl_kwargs) elif case.bc_type == "neumann": op = NeumannOperator(knl, loc_sign, use_l2_weighting=True, use_improved_operator=False, kernel_arguments=knl_kwargs) else: assert False op_u = op.operator(sym.var("u")) # }}} # {{{ set up test data if case.prob_side == -1: test_src_geo_radius = case.outer_radius test_tgt_geo_radius = case.inner_radius elif case.prob_side == +1: test_src_geo_radius = case.inner_radius test_tgt_geo_radius = case.outer_radius elif case.prob_side == "scat": test_src_geo_radius = case.outer_radius test_tgt_geo_radius = case.outer_radius else: raise ValueError("unknown problem_side") point_sources = make_circular_point_group( mesh.ambient_dim, 10, test_src_geo_radius, func=lambda x: x**1.5) test_targets = make_circular_point_group( mesh.ambient_dim, 20, test_tgt_geo_radius) np.random.seed(22) source_charges = np.random.randn(point_sources.shape[1]) source_charges[-1] = -np.sum(source_charges[:-1]) source_charges = source_charges.astype(dtype) assert np.sum(source_charges) < 1e-15 source_charges_dev = cl.array.to_device(queue, source_charges) # }}} # {{{ establish BCs from pytential.source import PointPotentialSource from pytential.target import PointsTarget point_source = PointPotentialSource(cl_ctx, point_sources) pot_src = sym.IntG( # FIXME: qbx_forced_limit--really? knl, sym.var("charges"), qbx_forced_limit=None, **knl_kwargs) test_direct = bind((point_source, PointsTarget(test_targets)), pot_src)( queue, charges=source_charges_dev, **concrete_knl_kwargs) if case.bc_type == "dirichlet": bc = bind((point_source, density_discr), pot_src)( queue, charges=source_charges_dev, **concrete_knl_kwargs) elif case.bc_type == "neumann": bc = bind( (point_source, density_discr), sym.normal_derivative( qbx.ambient_dim, pot_src, where=sym.DEFAULT_TARGET) )(queue, charges=source_charges_dev, **concrete_knl_kwargs) # }}} # {{{ solve bound_op = bind(qbx, op_u) rhs = bind(density_discr, op.prepare_rhs(sym.var("bc")))(queue, bc=bc) try: from pytential.solve import gmres gmres_result = gmres( bound_op.scipy_op(queue, "u", dtype, **concrete_knl_kwargs), rhs, tol=case.gmres_tol, progress=True, hard_failure=True, stall_iterations=50, no_progress_factor=1.05) except QBXTargetAssociationFailedException as e: bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) bdry_vis.write_vtk_file("failed-targets-%s.vtu" % resolution, [ ("failed_targets", e.failed_target_flags), ]) raise print("gmres state:", gmres_result.state) weighted_u = gmres_result.solution # }}} # {{{ build matrix for spectrum check if 0: from sumpy.tools import build_matrix mat = build_matrix( bound_op.scipy_op( queue, arg_name="u", dtype=dtype, k=case.k)) w, v = la.eig(mat) if 0: pt.imshow(np.log10(1e-20+np.abs(mat))) pt.colorbar() pt.show() #assert abs(s[-1]) < 1e-13, "h #assert abs(s[-2]) > 1e-7 #from pudb import set_trace; set_trace() # }}} if case.prob_side != "scat": # {{{ error check points_target = PointsTarget(test_targets) bound_tgt_op = bind((qbx, points_target), op.representation(sym.var("u"))) test_via_bdry = bound_tgt_op(queue, u=weighted_u, k=case.k) err = test_via_bdry - test_direct err = err.get() test_direct = test_direct.get() test_via_bdry = test_via_bdry.get() # {{{ remove effect of net source charge if case.k == 0 and case.bc_type == "neumann" and loc_sign == -1: # remove constant offset in interior Laplace Neumann error tgt_ones = np.ones_like(test_direct) tgt_ones = tgt_ones/la.norm(tgt_ones) err = err - np.vdot(tgt_ones, err)*tgt_ones # }}} rel_err_2 = la.norm(err)/la.norm(test_direct) rel_err_inf = la.norm(err, np.inf)/la.norm(test_direct, np.inf) # }}} print("rel_err_2: %g rel_err_inf: %g" % (rel_err_2, rel_err_inf)) else: rel_err_2 = None rel_err_inf = None # {{{ test gradient if case.check_gradient and case.prob_side != "scat": bound_grad_op = bind((qbx, points_target), op.representation( sym.var("u"), map_potentials=lambda pot: sym.grad(mesh.ambient_dim, pot), qbx_forced_limit=None)) #print(bound_t_deriv_op.code) grad_from_src = bound_grad_op( queue, u=weighted_u, **concrete_knl_kwargs) grad_ref = (bind( (point_source, points_target), sym.grad(mesh.ambient_dim, pot_src) )(queue, charges=source_charges_dev, **concrete_knl_kwargs) ) grad_err = (grad_from_src - grad_ref) rel_grad_err_inf = ( la.norm(grad_err[0].get(), np.inf) / la.norm(grad_ref[0].get(), np.inf)) print("rel_grad_err_inf: %g" % rel_grad_err_inf) # }}} # {{{ test tangential derivative if case.check_tangential_deriv and case.prob_side != "scat": bound_t_deriv_op = bind(qbx, op.representation( sym.var("u"), map_potentials=lambda pot: sym.tangential_derivative(2, pot), qbx_forced_limit=loc_sign)) #print(bound_t_deriv_op.code) tang_deriv_from_src = bound_t_deriv_op( queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get() tang_deriv_ref = (bind( (point_source, density_discr), sym.tangential_derivative(2, pot_src) )(queue, charges=source_charges_dev, **concrete_knl_kwargs) .as_scalar().get()) if 0: pt.plot(tang_deriv_ref.real) pt.plot(tang_deriv_from_src.real) pt.show() td_err = (tang_deriv_from_src - tang_deriv_ref) rel_td_err_inf = la.norm(td_err, np.inf)/la.norm(tang_deriv_ref, np.inf) print("rel_td_err_inf: %g" % rel_td_err_inf) else: rel_td_err_inf = None # }}} # {{{ any-D file plotting if visualize: bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) bdry_normals = bind(density_discr, sym.normal(qbx.ambient_dim))(queue)\ .as_vector(dtype=object) sym_sqrt_j = sym.sqrt_jac_q_weight(density_discr.ambient_dim) u = bind(density_discr, sym.var("u")/sym_sqrt_j)(queue, u=weighted_u) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("u", u), ("bc", bc), #("bdry_normals", bdry_normals), ]) from sumpy.visualization import make_field_plotter_from_bbox # noqa from meshmode.mesh.processing import find_bounding_box vis_grid_spacing = (0.1, 0.1, 0.1)[:qbx.ambient_dim] if hasattr(case, "vis_grid_spacing"): vis_grid_spacing = case.vis_grid_spacing vis_extend_factor = 0.2 if hasattr(case, "vis_extend_factor"): vis_grid_spacing = case.vis_grid_spacing fplot = make_field_plotter_from_bbox( find_bounding_box(mesh), h=vis_grid_spacing, extend_factor=vis_extend_factor) qbx_tgt_tol = qbx.copy(target_association_tolerance=0.15) from pytential.target import PointsTarget try: solved_pot = bind( (qbx_tgt_tol, PointsTarget(fplot.points)), op.representation(sym.var("u")) )(queue, u=weighted_u, k=case.k) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( "failed-targets.vts", [ ("failed_targets", e.failed_target_flags.get(queue)) ]) raise from sumpy.kernel import LaplaceKernel ones_density = density_discr.zeros(queue) ones_density.fill(1) indicator = bind( (qbx_tgt_tol, PointsTarget(fplot.points)), -sym.D(LaplaceKernel(density_discr.ambient_dim), sym.var("sigma"), qbx_forced_limit=None))( queue, sigma=ones_density).get() solved_pot = solved_pot.get() true_pot = bind((point_source, PointsTarget(fplot.points)), pot_src)( queue, charges=source_charges_dev, **concrete_knl_kwargs).get() #fplot.show_scalar_in_mayavi(solved_pot.real, max_val=5) if case.prob_side == "scat": fplot.write_vtk_file( "potential-%s.vts" % resolution, [ ("pot_scattered", solved_pot), ("pot_incoming", -true_pot), ("indicator", indicator), ] ) else: fplot.write_vtk_file( "potential-%s.vts" % resolution, [ ("solved_pot", solved_pot), ("true_pot", true_pot), ("indicator", indicator), ] ) # }}} class Result(Record): pass return Result( h_max=qbx.h_max, rel_err_2=rel_err_2, rel_err_inf=rel_err_inf, rel_td_err_inf=rel_td_err_inf, gmres_result=gmres_result)
# }}} print("rel_err_2: %g rel_err_inf: %g" % (rel_err_2, rel_err_inf)) else: rel_err_2 = None rel_err_inf = None # {{{ test gradient if case.check_gradient and case.prob_side != "scat": bound_grad_op = bind( (qbx, points_target), op.representation( sym.var("u"), map_potentials=lambda pot: sym.grad(mesh.ambient_dim, pot), qbx_forced_limit=None)) #print(bound_t_deriv_op.code) grad_from_src = bound_grad_op(queue, u=weighted_u, **concrete_knl_kwargs) grad_ref = (bind((point_source, points_target), sym.grad(mesh.ambient_dim, pot_src))(queue, charges=source_charges_dev, **concrete_knl_kwargs)) grad_err = (grad_from_src - grad_ref)
rel_err_inf = la.norm(err, np.inf) / la.norm(test_direct, np.inf) logger.info("rel_err_2: %.5e rel_err_inf: %.5e", rel_err_2, rel_err_inf) else: rel_err_2 = None rel_err_inf = None # }}} # {{{ test gradient if case.check_gradient and case.side != "scat": sym_grad_op = op.representation( sym_u, map_potentials=lambda p: sym.grad(ambient_dim, p), qbx_forced_limit=None) grad_from_src = bind(places, sym_grad_op, auto_where=(case.name, "point_target"))( actx, u=weighted_u, **case.knl_concrete_kwargs) grad_ref = bind(places, sym.grad(ambient_dim, pot_src), auto_where=("point_source", "point_target"))( actx, charges=source_charges_dev, **case.knl_concrete_kwargs)
def nonlocal_integral_eq( mesh, scatterer_bdy_id, outer_bdy_id, wave_number, options_prefix=None, solver_parameters=None, fspace=None, vfspace=None, true_sol_grad=None, queue=None, fspace_analog=None, qbx_kwargs=None, ): r""" see run_method for descriptions of unlisted args args: :arg queue: A command queue for the computing context gamma and beta are used to precondition with the following equation: \Delta u - \kappa^2 \gamma u = 0 (\partial_n - i\kappa\beta) u |_\Sigma = 0 """ with_refinement = True # away from the excluded region, but firedrake and meshmode point # into pyt_inner_normal_sign = -1 ambient_dim = mesh.geometric_dimension() # {{{ Create operator from pytential import sym r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * sym.grad( ambient_dim, sym.D(HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) """ op = pyt_inner_normal_sign * 1j * sym.var("k") * (sym.D( HelmholtzKernel(ambient_dim), sym.var("u"), k=sym.var("k"), qbx_forced_limit=None)) pyt_grad_op = fd2mm.fd_bind( queue.context, fspace_analog, grad_op, source=(fspace, scatterer_bdy_id), target=(vfspace, outer_bdy_id), with_refinement=with_refinement, qbx_kwargs=qbx_kwargs, ) pyt_op = fd2mm.fd_bind( queue.context, fspace_analog, op, source=(fspace, scatterer_bdy_id), target=(fspace, outer_bdy_id), with_refinement=with_refinement, qbx_kwargs=qbx_kwargs, ) # }}} class MatrixFreeB(object): def __init__(self, A, pyt_grad_op, pyt_op, queue, kappa): """ :arg kappa: The wave number """ self.queue = queue self.k = kappa self.pyt_op = pyt_op self.pyt_grad_op = pyt_grad_op self.A = A # {{{ Create some functions needed for multing self.x_fntn = Function(fspace) self.potential_int = Function(fspace) self.potential_int.dat.data[:] = 0.0 self.grad_potential_int = Function(vfspace) self.grad_potential_int.dat.data[:] = 0.0 self.pyt_result = Function(fspace) self.n = FacetNormal(mesh) self.v = TestFunction(fspace) # }}} def mult(self, mat, x, y): # Perform pytential operation self.x_fntn.dat.data[:] = x[:] self.pyt_op(self.queue, self.potential_int, u=self.x_fntn, k=self.k) self.pyt_grad_op(self.queue, self.grad_potential_int, u=self.x_fntn, k=self.k) # Integrate the potential r""" Compute the inner products using firedrake. Note this will be subtracted later, hence appears off by a sign. .. math:: \langle n(x) \cdot \nabla( \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma - \langle i \kappa \cdot \int_\Gamma( u(y) \partial_n H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma """ self.pyt_result = assemble( inner(inner(self.grad_potential_int, self.n), self.v) * ds(outer_bdy_id) - inner(self.potential_int, self.v) * ds(outer_bdy_id)) # y <- Ax - evaluated potential self.A.mult(x, y) with self.pyt_result.dat.vec_ro as ep: y.axpy(-1, ep) # {{{ Compute normal helmholtz operator u = TrialFunction(fspace) v = TestFunction(fspace) r""" .. math:: \langle \nabla u, \nabla v \rangle - \kappa^2 \cdot \langle u, v \rangle - i \kappa \langle u, v \rangle_\Sigma """ a = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2) * inner(u, v) * dx \ - Constant(1j * wave_number) * inner(u, v) * ds(outer_bdy_id) # get the concrete matrix from a general bilinear form A = assemble(a).M.handle # }}} # {{{ Setup Python matrix B = PETSc.Mat().create() # build matrix context Bctx = MatrixFreeB(A, pyt_grad_op, pyt_op, queue, wave_number) # set up B as same size as A B.setSizes(*A.getSizes()) B.setType(B.Type.PYTHON) B.setPythonContext(Bctx) B.setUp() # }}} # {{{ Create rhs # Remember f is \partial_n(true_sol)|_\Gamma # so we just need to compute \int_\Gamma\partial_n(true_sol) H(x-y) from pytential import sym sigma = sym.make_sym_vector("sigma", ambient_dim) r""" ..math: x \in \Sigma grad_op(x) = \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ grad_op = pyt_inner_normal_sign * \ sym.grad(ambient_dim, sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None)) r""" ..math: x \in \Sigma op(x) = i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ) """ op = 1j * sym.var("k") * pyt_inner_normal_sign * \ sym.S(HelmholtzKernel(ambient_dim), sym.n_dot(sigma), k=sym.var("k"), qbx_forced_limit=None) rhs_grad_op = fd2mm.fd_bind( queue.context, fspace_analog, grad_op, source=(vfspace, scatterer_bdy_id), target=(vfspace, outer_bdy_id), with_refinement=with_refinement, qbx_kwargs=qbx_kwargs, ) rhs_op = fd2mm.fd_bind( queue.context, fspace_analog, op, source=(vfspace, scatterer_bdy_id), target=(fspace, outer_bdy_id), with_refinement=with_refinement, qbx_kwargs=qbx_kwargs, ) f_grad_convoluted = Function(vfspace) f_convoluted = Function(fspace) rhs_grad_op(queue, f_grad_convoluted, sigma=true_sol_grad, k=wave_number) rhs_op(queue, f_convoluted, sigma=true_sol_grad, k=wave_number) r""" \langle f, v \rangle_\Gamma + \langle i \kappa \cdot \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y), v \rangle_\Sigma - \langle n(x) \cdot \nabla( \int_\Gamma( f(y) H_0^{(1)}(\kappa |x - y|) )d\gamma(y) ), v \rangle_\Sigma """ rhs_form = inner(inner(true_sol_grad, FacetNormal(mesh)), v) * ds(scatterer_bdy_id) \ + inner(f_convoluted, v) * ds(outer_bdy_id) \ - inner(inner(f_grad_convoluted, FacetNormal(mesh)), v) * ds(outer_bdy_id) rhs = assemble(rhs_form) # {{{ set up a solver: solution = Function(fspace, name="Computed Solution") # {{{ Used for preconditioning if 'gamma' in solver_parameters or 'beta' in solver_parameters: gamma = complex(solver_parameters.pop('gamma', 1.0)) import cmath beta = complex(solver_parameters.pop('beta', cmath.sqrt(gamma))) p = inner(grad(u), grad(v)) * dx \ - Constant(wave_number**2 * gamma) * inner(u, v) * dx \ - Constant(1j * wave_number * beta) * inner(u, v) * ds(outer_bdy_id) P = assemble(p).M.handle else: P = A # }}} # Set up options to contain solver parameters: ksp = PETSc.KSP().create() if solver_parameters['pc_type'] == 'pyamg': del solver_parameters['pc_type'] # We are using the AMG preconditioner pyamg_tol = solver_parameters.get('pyamg_tol', None) if pyamg_tol is not None: pyamg_tol = float(pyamg_tol) pyamg_maxiter = solver_parameters.get('pyamg_maxiter', None) if pyamg_maxiter is not None: pyamg_maxiter = int(pyamg_maxiter) ksp.setOperators(B) ksp.setUp() pc = ksp.pc pc.setType(pc.Type.PYTHON) pc.setPythonContext( AMGTransmissionPreconditioner(wave_number, fspace, A, tol=pyamg_tol, maxiter=pyamg_maxiter, use_plane_waves=True)) # Otherwise use regular preconditioner else: ksp.setOperators(B, P) options_manager = OptionsManager(solver_parameters, options_prefix) options_manager.set_from_options(ksp) with rhs.dat.vec_ro as b: with solution.dat.vec as x: ksp.solve(b, x) # }}} return ksp, solution
def compute_biharmonic_extension(queue, target_discr, qbx, density_discr, f, fx, fy, target_association_tolerance=0.05): """Biharmoc extension. Currently only support interior domains in 2D (i.e., extension is on the exterior). """ # pylint: disable=invalid-unary-operand-type dim = 2 queue = setup_command_queue(queue=queue) qbx_forced_limit = 1 normal = get_normal_vectors(queue, density_discr, loc_sign=1) bdry_op_sym = get_extension_bie_symbolic_operator(loc_sign=1) bound_op = bind(qbx, bdry_op_sym) bc = [fy, -fx] bvp_rhs = bind(qbx, sym.make_sym_vector("bc", dim))(queue, bc=bc) gmres_result = gmres(bound_op.scipy_op(queue, "sigma", np.float64, mu=1., normal=normal), bvp_rhs, tol=1e-9, progress=True, stall_iterations=0, hard_failure=True) mu = gmres_result.solution arclength_parametrization_derivatives_sym = sym.make_sym_vector( "arclength_parametrization_derivatives", dim) density_mu_sym = sym.make_sym_vector("mu", dim) dxids_sym = arclength_parametrization_derivatives_sym[0] + \ 1j * arclength_parametrization_derivatives_sym[1] dxids_conj_sym = arclength_parametrization_derivatives_sym[0] - \ 1j * arclength_parametrization_derivatives_sym[1] density_rho_sym = density_mu_sym[1] - 1j * density_mu_sym[0] density_conj_rho_sym = density_mu_sym[1] + 1j * density_mu_sym[0] # convolutions GS1 = sym.IntG( # noqa: N806 ComplexLinearLogKernel(dim), density_rho_sym, qbx_forced_limit=None) GS2 = sym.IntG( # noqa: N806 ComplexLinearKernel(dim), density_conj_rho_sym, qbx_forced_limit=None) GD1 = sym.IntG( # noqa: N806 ComplexFractionalKernel(dim), density_rho_sym * dxids_sym, qbx_forced_limit=None) GD2 = [ sym.IntG( # noqa: N806 AxisTargetDerivative(iaxis, ComplexLogKernel(dim)), density_conj_rho_sym * dxids_sym + density_rho_sym * dxids_conj_sym, qbx_forced_limit=qbx_forced_limit) for iaxis in range(dim) ] GS1_bdry = sym.IntG( # noqa: N806 ComplexLinearLogKernel(dim), density_rho_sym, qbx_forced_limit=qbx_forced_limit) GS2_bdry = sym.IntG( # noqa: N806 ComplexLinearKernel(dim), density_conj_rho_sym, qbx_forced_limit=qbx_forced_limit) GD1_bdry = sym.IntG( # noqa: N806 ComplexFractionalKernel(dim), density_rho_sym * dxids_sym, qbx_forced_limit=qbx_forced_limit) xp, yp = get_arclength_parametrization_derivative(queue, density_discr) xp = -xp yp = -yp tangent = get_tangent_vectors(queue, density_discr, loc_sign=qbx_forced_limit) # check and fix the direction of parametrization # logger.info("Fix all negative signs in:" + # str(xp * tangent[0] + yp * tangent[1])) grad_v2 = [ bind(qbx, GD2[iaxis])(queue, mu=mu, arclength_parametrization_derivatives=make_obj_array( [xp, yp])).real for iaxis in range(dim) ] v2_tangent_der = sum(tangent[iaxis] * grad_v2[iaxis] for iaxis in range(dim)) from pytential.symbolic.pde.scalar import NeumannOperator from sumpy.kernel import LaplaceKernel operator_v1 = NeumannOperator(LaplaceKernel(dim), loc_sign=qbx_forced_limit) bound_op_v1 = bind(qbx, operator_v1.operator(var("sigma"))) # FIXME: the positive sign works here rhs_v1 = operator_v1.prepare_rhs(1 * v2_tangent_der) gmres_result = gmres(bound_op_v1.scipy_op(queue, "sigma", dtype=np.float64), rhs_v1, tol=1e-9, progress=True, stall_iterations=0, hard_failure=True) sigma = gmres_result.solution qbx_stick_out = qbx.copy( target_association_tolerance=target_association_tolerance) v1 = bind((qbx_stick_out, target_discr), operator_v1.representation(var("sigma"), qbx_forced_limit=None))(queue, sigma=sigma) grad_v1 = bind( (qbx_stick_out, target_discr), operator_v1.representation( var("sigma"), qbx_forced_limit=None, map_potentials=lambda pot: sym.grad(dim, pot)))(queue, sigma=sigma) v1_bdry = bind( qbx, operator_v1.representation(var("sigma"), qbx_forced_limit=qbx_forced_limit))( queue, sigma=sigma) z_conj = target_discr.nodes()[0] - 1j * target_discr.nodes()[1] z_conj_bdry = density_discr.nodes().with_queue(queue)[0] \ - 1j * density_discr.nodes().with_queue(queue)[1] int_rho = 1 / (8 * np.pi) * bind( qbx, sym.integral(dim, dim - 1, density_rho_sym))(queue, mu=mu) omega_S1 = bind( # noqa: N806 (qbx_stick_out, target_discr), GS1)(queue, mu=mu).real omega_S2 = -1 * bind( # noqa: N806 (qbx_stick_out, target_discr), GS2)(queue, mu=mu).real omega_S3 = (z_conj * int_rho).real # noqa: N806 omega_S = -(omega_S1 + omega_S2 + omega_S3) # noqa: N806 grad_omega_S1 = bind( # noqa: N806 (qbx_stick_out, target_discr), sym.grad(dim, GS1))(queue, mu=mu).real grad_omega_S2 = -1 * bind( # noqa: N806 (qbx_stick_out, target_discr), sym.grad(dim, GS2))(queue, mu=mu).real grad_omega_S3 = (int_rho * make_obj_array([1., -1.])).real # noqa: N806 grad_omega_S = -(grad_omega_S1 + grad_omega_S2 + grad_omega_S3 ) # noqa: N806 omega_S1_bdry = bind(qbx, GS1_bdry)(queue, mu=mu).real # noqa: N806 omega_S2_bdry = -1 * bind(qbx, GS2_bdry)(queue, mu=mu).real # noqa: N806 omega_S3_bdry = (z_conj_bdry * int_rho).real # noqa: N806 omega_S_bdry = -(omega_S1_bdry + omega_S2_bdry + omega_S3_bdry ) # noqa: N806 omega_D1 = bind( # noqa: N806 (qbx_stick_out, target_discr), GD1)(queue, mu=mu, arclength_parametrization_derivatives=make_obj_array([xp, yp])).real omega_D = (omega_D1 + v1) # noqa: N806 grad_omega_D1 = bind( # noqa: N806 (qbx_stick_out, target_discr), sym.grad(dim, GD1))( queue, mu=mu, arclength_parametrization_derivatives=make_obj_array([xp, yp])).real grad_omega_D = grad_omega_D1 + grad_v1 # noqa: N806 omega_D1_bdry = bind( # noqa: N806 qbx, GD1_bdry)(queue, mu=mu, arclength_parametrization_derivatives=make_obj_array( [xp, yp])).real omega_D_bdry = (omega_D1_bdry + v1_bdry) # noqa: N806 int_bdry_mu = bind( qbx, sym.integral(dim, dim - 1, sym.make_sym_vector("mu", dim)))(queue, mu=mu) omega_W = ( # noqa: N806 int_bdry_mu[0] * target_discr.nodes()[1] - int_bdry_mu[1] * target_discr.nodes()[0]) grad_omega_W = make_obj_array( # noqa: N806 [-int_bdry_mu[1], int_bdry_mu[0]]) omega_W_bdry = ( # noqa: N806 int_bdry_mu[0] * density_discr.nodes().with_queue(queue)[1] - int_bdry_mu[1] * density_discr.nodes().with_queue(queue)[0]) int_bdry = bind(qbx, sym.integral(dim, dim - 1, var("integrand")))( queue, integrand=omega_S_bdry + omega_D_bdry + omega_W_bdry) debugging_info = {} debugging_info['omega_S'] = omega_S debugging_info['omega_D'] = omega_D debugging_info['omega_W'] = omega_W debugging_info['omega_v1'] = v1 debugging_info['omega_D1'] = omega_D1 int_interior_func_bdry = bind(qbx, sym.integral(2, 1, var("integrand")))(queue, integrand=f) path_length = get_path_length(queue, density_discr) ext_f = omega_S + omega_D + omega_W + (int_interior_func_bdry - int_bdry) / path_length grad_f = grad_omega_S + grad_omega_D + grad_omega_W return ext_f, grad_f[0], grad_f[1], debugging_info