def operator(self, u): sqrt_w = self.get_sqrt_weight() inv_sqrt_w_u = cse(u / sqrt_w) if self.is_unique_only_up_to_constant(): # The exterior Dirichlet operator in this representation # has a nullspace. The mean of the density must be matched # to the desired solution separately. As is, this operator # returns a mean that is not well-specified. # # See Hackbusch, https://books.google.com/books?id=Ssnf7SZB0ZMC # Theorem 8.2.18b amb_dim = self.kernel.dim ones_contribution = (sym.Ones() * sym.mean(amb_dim, amb_dim - 1, inv_sqrt_w_u)) else: ones_contribution = 0 return ( -self.loc_sign * 0.5 * u + sqrt_w * (self.alpha * sym.S(self.kernel, inv_sqrt_w_u, qbx_forced_limit=+1, kernel_arguments=self.kernel_arguments) - sym.D(self.kernel, inv_sqrt_w_u, qbx_forced_limit="avg", kernel_arguments=self.kernel_arguments) + ones_contribution) )
def get_extension_bie_symbolic_operator(loc_sign=1): """ loc_sign: -1 for interior Dirichlet +1 for exterior Dirichlet """ logger = logging.getLogger("SETUP") logger.info(locals()) dim = 2 cse = sym.cse sigma_sym = sym.make_sym_vector("sigma", dim) int_sigma = sym.Ones() * sym.integral(2, 1, sigma_sym) nvec_sym = sym.make_sym_vector("normal", dim) mu_sym = sym.var("mu") stresslet_obj = StressletWrapper(dim=dim) stokeslet_obj = StokesletWrapper(dim=dim) bdry_op_sym = ( loc_sign * 0.5 * sigma_sym - stresslet_obj.apply( sigma_sym, nvec_sym, mu_sym, qbx_forced_limit='avg') - stokeslet_obj.apply(sigma_sym, mu_sym, qbx_forced_limit='avg') + int_sigma) return bdry_op_sym
def operator(self, u): from sumpy.kernel import HelmholtzKernel, LaplaceKernel sqrt_w = self.get_sqrt_weight() inv_sqrt_w_u = cse(u / sqrt_w) knl = self.kernel lknl = self.laplace_kernel knl_kwargs = {} knl_kwargs["kernel_arguments"] = self.kernel_arguments DpS0u = sym.Dp( knl, # noqa cse(sym.S(lknl, inv_sqrt_w_u)), **knl_kwargs) if self.use_improved_operator: Dp0S0u = -0.25 * u + sym.Sp( # noqa lknl, # noqa sym.Sp(lknl, inv_sqrt_w_u, qbx_forced_limit="avg"), qbx_forced_limit="avg") if isinstance(self.kernel, HelmholtzKernel): DpS0u = ( # noqa sym.Dp( knl - lknl, # noqa cse(sym.S(lknl, inv_sqrt_w_u, qbx_forced_limit=+1)), qbx_forced_limit=+1, **knl_kwargs) + Dp0S0u) elif isinstance(knl, LaplaceKernel): DpS0u = Dp0S0u # noqa else: raise ValueError( f"no improved operator for '{self.kernel}' known") if self.is_unique_only_up_to_constant(): # The interior Neumann operator in this representation # has a nullspace. The mean of the density must be matched # to the desired solution separately. As is, this operator # returns a mean that is not well-specified. amb_dim = self.kernel.dim ones_contribution = (sym.Ones() * sym.mean(amb_dim, amb_dim - 1, inv_sqrt_w_u)) else: ones_contribution = 0 return ( -self.loc_sign * 0.5 * u + sqrt_w * (sym.Sp(knl, inv_sqrt_w_u, qbx_forced_limit="avg", **knl_kwargs) - self.alpha * DpS0u + ones_contribution))
def operator(self, u, **kwargs): """ :param u: symbolic variable for the density. :param kwargs: additional keyword arguments passed on to the layer potential constructor. """ sqrt_w = self.get_sqrt_weight() inv_sqrt_w_u = sym.cse(u/sqrt_w) if self.is_unique_only_up_to_constant(): # The exterior Dirichlet operator in this representation # has a nullspace. The mean of the density must be matched # to the desired solution separately. As is, this operator # returns a mean that is not well-specified. # # See Hackbusch, https://books.google.com/books?id=Ssnf7SZB0ZMC # Theorem 8.2.18b ones_contribution = ( sym.Ones() * sym.mean(self.dim, self.dim - 1, inv_sqrt_w_u)) else: ones_contribution = 0 def S(density): # noqa return sym.S(self.kernel, density, kernel_arguments=self.kernel_arguments, qbx_forced_limit=+1, **kwargs) def D(density): # noqa return sym.D(self.kernel, density, kernel_arguments=self.kernel_arguments, qbx_forced_limit="avg", **kwargs) return ( -0.5 * self.loc_sign * u + sqrt_w * ( self.alpha * S(inv_sqrt_w_u) - D(inv_sqrt_w_u) + ones_contribution ) )
def operator(self, u, **kwargs): """ :param u: symbolic variable for the density. :param kwargs: additional keyword arguments passed on to the layer potential constructor. """ sqrt_w = self.get_sqrt_weight() inv_sqrt_w_u = sym.cse(u/sqrt_w) laplace_s_inv_sqrt_w_u = sym.cse( sym.S(self.laplace_kernel, inv_sqrt_w_u, qbx_forced_limit=+1) ) kwargs["kernel_arguments"] = self.kernel_arguments # NOTE: the improved operator here is based on right-precondioning # by a single layer and then using some Calderon identities to simplify # the result. The integral equation we start with for Neumann is # I + S' + alpha D' = g # where D' is hypersingular if self.use_improved_operator: def Sp(density): return sym.Sp(self.laplace_kernel, density, qbx_forced_limit="avg") # NOTE: using the Calderon identity # D' S = -u/4 + S'^2 Dp0S0u = -0.25 * u + Sp(Sp(inv_sqrt_w_u)) from sumpy.kernel import HelmholtzKernel, LaplaceKernel if isinstance(self.kernel, HelmholtzKernel): DpS0u = ( sym.Dp( self.kernel - self.laplace_kernel, laplace_s_inv_sqrt_w_u, qbx_forced_limit=+1, **kwargs) + Dp0S0u) elif isinstance(self.kernel, LaplaceKernel): DpS0u = Dp0S0u else: raise ValueError(f"no improved operator for '{self.kernel}' known") else: DpS0u = sym.Dp(self.kernel, laplace_s_inv_sqrt_w_u, **kwargs) if self.is_unique_only_up_to_constant(): # The interior Neumann operator in this representation # has a nullspace. The mean of the density must be matched # to the desired solution separately. As is, this operator # returns a mean that is not well-specified. ones_contribution = ( sym.Ones() * sym.mean(self.dim, self.dim - 1, inv_sqrt_w_u)) else: ones_contribution = 0 kwargs["qbx_forced_limit"] = "avg" return ( -0.5 * self.loc_sign * u + sqrt_w * ( sym.Sp(self.kernel, inv_sqrt_w_u, **kwargs) - self.alpha * DpS0u + ones_contribution ) )
def run_exterior_stokes_2d(ctx_factory, nelements, mesh_order=4, target_order=4, qbx_order=4, fmm_order=10, mu=1, circle_rad=1.5, do_plot=False): # This program tests an exterior Stokes flow in 2D using the # compound representation given in Hsiao & Kress, # ``On an integral equation for the two-dimensional exterior Stokes problem,'' # Applied Numerical Mathematics 1 (1985). logging.basicConfig(level=logging.INFO) cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) ovsmp_target_order = 4 * target_order from meshmode.mesh.generation import ( # noqa make_curve_mesh, starfish, ellipse, drop) mesh = make_curve_mesh(lambda t: circle_rad * ellipse(1, t), np.linspace(0, 1, nelements + 1), target_order) coarse_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource target_association_tolerance = 0.05 qbx, _ = QBXLayerPotentialSource( coarse_density_discr, fine_order=ovsmp_target_order, qbx_order=qbx_order, fmm_order=fmm_order, target_association_tolerance=target_association_tolerance, _expansions_in_tree_have_extent=True, ).with_refinement() density_discr = qbx.density_discr normal = bind(density_discr, sym.normal(2).as_vector())(queue) path_length = bind(density_discr, sym.integral(2, 1, 1))(queue) # {{{ describe bvp from pytential.symbolic.stokes import StressletWrapper, StokesletWrapper dim = 2 cse = sym.cse sigma_sym = sym.make_sym_vector("sigma", dim) meanless_sigma_sym = cse(sigma_sym - sym.mean(2, 1, sigma_sym)) int_sigma = sym.Ones() * sym.integral(2, 1, sigma_sym) nvec_sym = sym.make_sym_vector("normal", dim) mu_sym = sym.var("mu") # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = 1 stresslet_obj = StressletWrapper(dim=2) stokeslet_obj = StokesletWrapper(dim=2) bdry_op_sym = (-loc_sign * 0.5 * sigma_sym - stresslet_obj.apply( sigma_sym, nvec_sym, mu_sym, qbx_forced_limit='avg') + stokeslet_obj.apply( meanless_sigma_sym, mu_sym, qbx_forced_limit='avg') - (0.5 / np.pi) * int_sigma) # }}} bound_op = bind(qbx, bdry_op_sym) # {{{ fix rhs and solve def fund_soln(x, y, loc, strength): #with direction (1,0) for point source r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) scaling = strength / (4 * np.pi * mu) xcomp = (-cl.clmath.log(r) + (x - loc[0])**2 / r**2) * scaling ycomp = ((x - loc[0]) * (y - loc[1]) / r**2) * scaling return [xcomp, ycomp] def rotlet_soln(x, y, loc): r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) xcomp = -(y - loc[1]) / r**2 ycomp = (x - loc[0]) / r**2 return [xcomp, ycomp] def fund_and_rot_soln(x, y, loc, strength): #with direction (1,0) for point source r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) scaling = strength / (4 * np.pi * mu) xcomp = ((-cl.clmath.log(r) + (x - loc[0])**2 / r**2) * scaling - (y - loc[1]) * strength * 0.125 / r**2 + 3.3) ycomp = (((x - loc[0]) * (y - loc[1]) / r**2) * scaling + (x - loc[0]) * strength * 0.125 / r**2 + 1.5) return [xcomp, ycomp] nodes = density_discr.nodes().with_queue(queue) fund_soln_loc = np.array([0.5, -0.2]) strength = 100. bc = fund_and_rot_soln(nodes[0], nodes[1], fund_soln_loc, strength) omega_sym = sym.make_sym_vector("omega", dim) u_A_sym_bdry = stokeslet_obj.apply( # noqa: N806 omega_sym, mu_sym, qbx_forced_limit=1) omega = [ cl.array.to_device(queue, (strength / path_length) * np.ones(len(nodes[0]))), cl.array.to_device(queue, np.zeros(len(nodes[0]))) ] bvp_rhs = bind(qbx, sym.make_sym_vector("bc", dim) + u_A_sym_bdry)(queue, bc=bc, mu=mu, omega=omega) gmres_result = gmres(bound_op.scipy_op(queue, "sigma", np.float64, mu=mu, normal=normal), bvp_rhs, x0=bvp_rhs, tol=1e-9, progress=True, stall_iterations=0, hard_failure=True) # }}} # {{{ postprocess/visualize sigma = gmres_result.solution sigma_int_val_sym = sym.make_sym_vector("sigma_int_val", 2) int_val = bind(qbx, sym.integral(2, 1, sigma_sym))(queue, sigma=sigma) int_val = -int_val / (2 * np.pi) print("int_val = ", int_val) u_A_sym_vol = stokeslet_obj.apply( # noqa: N806 omega_sym, mu_sym, qbx_forced_limit=2) representation_sym = ( -stresslet_obj.apply(sigma_sym, nvec_sym, mu_sym, qbx_forced_limit=2) + stokeslet_obj.apply(meanless_sigma_sym, mu_sym, qbx_forced_limit=2) - u_A_sym_vol + sigma_int_val_sym) nsamp = 30 eval_points_1d = np.linspace(-3., 3., nsamp) eval_points = np.zeros((2, len(eval_points_1d)**2)) eval_points[0, :] = np.tile(eval_points_1d, len(eval_points_1d)) eval_points[1, :] = np.repeat(eval_points_1d, len(eval_points_1d)) def circle_mask(test_points, radius): return (test_points[0, :]**2 + test_points[1, :]**2 > radius**2) def outside_circle(test_points, radius): mask = circle_mask(test_points, radius) return np.array([row[mask] for row in test_points]) eval_points = outside_circle(eval_points, radius=circle_rad) from pytential.target import PointsTarget vel = bind((qbx, PointsTarget(eval_points)), representation_sym)(queue, sigma=sigma, mu=mu, normal=normal, sigma_int_val=int_val, omega=omega) print("@@@@@@@@") fplot = FieldPlotter(np.zeros(2), extent=6, npoints=100) plot_pts = outside_circle(fplot.points, radius=circle_rad) plot_vel = bind((qbx, PointsTarget(plot_pts)), representation_sym)(queue, sigma=sigma, mu=mu, normal=normal, sigma_int_val=int_val, omega=omega) def get_obj_array(obj_array): return make_obj_array([ary.get() for ary in obj_array]) exact_soln = fund_and_rot_soln(cl.array.to_device(queue, eval_points[0]), cl.array.to_device(queue, eval_points[1]), fund_soln_loc, strength) vel = get_obj_array(vel) err = vel - get_obj_array(exact_soln) # FIXME: Pointwise relative errors don't make sense! rel_err = err / (get_obj_array(exact_soln)) if 0: print("@@@@@@@@") print("vel[0], err[0], rel_err[0] ***** vel[1], err[1], rel_err[1]: ") for i in range(len(vel[0])): print("%15.8e, %15.8e, %15.8e ***** %15.8e, %15.8e, %15.8e\n" % (vel[0][i], err[0][i], rel_err[0][i], vel[1][i], err[1][i], rel_err[1][i])) print("@@@@@@@@") l2_err = np.sqrt((6. / (nsamp - 1))**2 * np.sum(err[0] * err[0]) + (6. / (nsamp - 1))**2 * np.sum(err[1] * err[1])) l2_rel_err = np.sqrt((6. / (nsamp - 1))**2 * np.sum(rel_err[0] * rel_err[0]) + (6. / (nsamp - 1))**2 * np.sum(rel_err[1] * rel_err[1])) print("L2 error estimate: ", l2_err) print("L2 rel error estimate: ", l2_rel_err) print("max error at sampled points: ", max(abs(err[0])), max(abs(err[1]))) print("max rel error at sampled points: ", max(abs(rel_err[0])), max(abs(rel_err[1]))) if do_plot: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt full_pot = np.zeros_like(fplot.points) * float("nan") mask = circle_mask(fplot.points, radius=circle_rad) for i, vel in enumerate(plot_vel): full_pot[i, mask] = vel.get() plt.quiver(fplot.points[0], fplot.points[1], full_pot[0], full_pot[1], linewidth=0.1) plt.savefig("exterior-2d-field.pdf") # }}} h_max = bind(qbx, sym.h_max(qbx.ambient_dim))(queue) return h_max, l2_err
def Wr(operand: sym.Expression) -> sym.Expression: # noqa: N802 return sym.Ones() * sym.integral(self.ambient_dim, self.dim, operand)
def run_exterior_stokes( ctx_factory, *, ambient_dim, target_order, qbx_order, resolution, fmm_order=False, # FIXME: FMM is slower than direct evaluation source_ovsmp=None, radius=1.5, mu=1.0, visualize=False, _target_association_tolerance=0.05, _expansions_in_tree_have_extent=True): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) # {{{ geometry if source_ovsmp is None: source_ovsmp = 4 if ambient_dim == 2 else 8 places = {} if ambient_dim == 2: from meshmode.mesh.generation import make_curve_mesh, ellipse mesh = make_curve_mesh(lambda t: radius * ellipse(1.0, t), np.linspace(0.0, 1.0, resolution + 1), target_order) elif ambient_dim == 3: from meshmode.mesh.generation import generate_icosphere mesh = generate_icosphere(radius, target_order + 1, uniform_refinement_rounds=resolution) else: raise ValueError(f"unsupported dimension: {ambient_dim}") pre_density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource qbx = QBXLayerPotentialSource( pre_density_discr, fine_order=source_ovsmp * target_order, qbx_order=qbx_order, fmm_order=fmm_order, target_association_tolerance=_target_association_tolerance, _expansions_in_tree_have_extent=_expansions_in_tree_have_extent) places["source"] = qbx from extra_int_eq_data import make_source_and_target_points point_source, point_target = make_source_and_target_points( side=+1, inner_radius=0.5 * radius, outer_radius=2.0 * radius, ambient_dim=ambient_dim, ) places["point_source"] = point_source places["point_target"] = point_target if visualize: from sumpy.visualization import make_field_plotter_from_bbox from meshmode.mesh.processing import find_bounding_box fplot = make_field_plotter_from_bbox(find_bounding_box(mesh), h=0.1, extend_factor=1.0) mask = np.linalg.norm(fplot.points, ord=2, axis=0) > (radius + 0.25) from pytential.target import PointsTarget plot_target = PointsTarget(fplot.points[:, mask].copy()) places["plot_target"] = plot_target del mask places = GeometryCollection(places, auto_where="source") density_discr = places.get_discretization("source") logger.info("ndofs: %d", density_discr.ndofs) logger.info("nelements: %d", density_discr.mesh.nelements) # }}} # {{{ symbolic sym_normal = sym.make_sym_vector("normal", ambient_dim) sym_mu = sym.var("mu") if ambient_dim == 2: from pytential.symbolic.stokes import HsiaoKressExteriorStokesOperator sym_omega = sym.make_sym_vector("omega", ambient_dim) op = HsiaoKressExteriorStokesOperator(omega=sym_omega) elif ambient_dim == 3: from pytential.symbolic.stokes import HebekerExteriorStokesOperator op = HebekerExteriorStokesOperator() else: assert False sym_sigma = op.get_density_var("sigma") sym_bc = op.get_density_var("bc") sym_op = op.operator(sym_sigma, normal=sym_normal, mu=sym_mu) sym_rhs = op.prepare_rhs(sym_bc, mu=mu) sym_velocity = op.velocity(sym_sigma, normal=sym_normal, mu=sym_mu) sym_source_pot = op.stokeslet.apply(sym_sigma, sym_mu, qbx_forced_limit=None) # }}} # {{{ boundary conditions normal = bind(places, sym.normal(ambient_dim).as_vector())(actx) np.random.seed(42) charges = make_obj_array([ actx.from_numpy(np.random.randn(point_source.ndofs)) for _ in range(ambient_dim) ]) if ambient_dim == 2: total_charge = make_obj_array([actx.np.sum(c) for c in charges]) omega = bind(places, total_charge * sym.Ones())(actx) if ambient_dim == 2: bc_context = {"mu": mu, "omega": omega} op_context = {"mu": mu, "omega": omega, "normal": normal} else: bc_context = {} op_context = {"mu": mu, "normal": normal} bc = bind(places, sym_source_pot, auto_where=("point_source", "source"))(actx, sigma=charges, mu=mu) rhs = bind(places, sym_rhs)(actx, bc=bc, **bc_context) bound_op = bind(places, sym_op) # }}} # {{{ solve from pytential.solve import gmres gmres_tol = 1.0e-9 result = gmres(bound_op.scipy_op(actx, "sigma", np.float64, **op_context), rhs, x0=rhs, tol=gmres_tol, progress=visualize, stall_iterations=0, hard_failure=True) sigma = result.solution # }}} # {{{ check velocity at "point_target" def rnorm2(x, y): y_norm = actx.np.linalg.norm(y.dot(y), ord=2) if y_norm < 1.0e-14: y_norm = 1.0 d = x - y return actx.np.linalg.norm(d.dot(d), ord=2) / y_norm ps_velocity = bind(places, sym_velocity, auto_where=("source", "point_target"))(actx, sigma=sigma, **op_context) ex_velocity = bind(places, sym_source_pot, auto_where=("point_source", "point_target"))(actx, sigma=charges, mu=mu) v_error = rnorm2(ps_velocity, ex_velocity) h_max = bind(places, sym.h_max(ambient_dim))(actx) logger.info("resolution %4d h_max %.5e error %.5e", resolution, h_max, v_error) # }}}} # {{{ visualize if not visualize: return h_max, v_error from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, density_discr, target_order) filename = "stokes_solution_{}d_{}_ovsmp_{}.vtu".format( ambient_dim, resolution, source_ovsmp) vis.write_vtk_file(filename, [ ("density", sigma), ("bc", bc), ("rhs", rhs), ], overwrite=True) # }}} return h_max, v_error
def ref_result(self): return make_obj_array([1.0e-15 * sym.Ones()] * self.ambient_dim)