def operator(self, unk): u = self.split_unknown(unk) Jxyz = cse(tangential_to_xyz(u.jt), "Jxyz") Mxyz = cse(tangential_to_xyz(u.mt), "Mxyz") omega = self.omega mu0, mu1 = self.mus eps0, eps1 = self.epss k0, k1 = self.ks S = partial(sym.S, self.kernel, qbx_forced_limit="avg") def curl_S(dens, k): return sym.curl(sym.S(self.kernel, dens, qbx_forced_limit="avg", k=k)) grad = partial(sym.grad, 3) E0 = sym.cse(1j*omega*mu0*eps0*S(Jxyz, k=k0) + mu0*curl_S(Mxyz, k=k0) - grad(S(u.rho_e, k=k0)), "E0") H0 = sym.cse(-1j*omega*mu0*eps0*S(Mxyz, k=k0) + eps0*curl_S(Jxyz, k=k0) + grad(S(u.rho_m, k=k0)), "H0") E1 = sym.cse(1j*omega*mu1*eps1*S(Jxyz, k=k1) + mu1*curl_S(Mxyz, k=k1) - grad(S(u.rho_e, k=k1)), "E1") H1 = sym.cse(-1j*omega*mu1*eps1*S(Mxyz, k=k1) + eps1*curl_S(Jxyz, k=k1) + grad(S(u.rho_m, k=k1)), "H1") F1 = (xyz_to_tangential(sym.n_cross(H1-H0) + 0.5*(eps0+eps1)*Jxyz)) F2 = (sym.n_dot(eps1*E1-eps0*E0) + 0.5*(eps1+eps0)*u.rho_e) F3 = (xyz_to_tangential(sym.n_cross(E1-E0) + 0.5*(mu0+mu1)*Mxyz)) # sign flip included F4 = -sym.n_dot(mu1*H1-mu0*H0) + 0.5*(mu1+mu0)*u.rho_m # noqa pylint:disable=invalid-unary-operand-type return sym.join_fields(F1, F2, F3, F4)
def __init__(self, domain_n_exprs, ne, interfaces, use_l2_weighting=None): """ :attr interfaces: a tuple of tuples ``(outer_domain, inner_domain, interface_id)``, where *outer_domain* and *inner_domain* are indices into *domain_k_names*, and *interface_id* is a symbolic name for the discretization of the interface. 'outer' designates the side of the interface to which the normal points. :attr domain_n_exprs: a tuple of variable names of the Helmholtz parameter *k*, to be used inside each part of the source geometry. May also be a tuple of strings, which will be transformed into variable references of the corresponding names. :attr beta: A symbolic expression for the wave number in the :math:`z` direction. May be a string, which will be interpreted as a variable name. """ self.interfaces = interfaces ne = sym.var(ne) self.ne = sym.cse(ne, "ne") self.domain_n_exprs = [ sym.var(n_expr) for idom, n_expr in enumerate(domain_n_exprs)] del domain_n_exprs import pymbolic.primitives as p def upper_half_square_root(x): return p.If( p.Comparison( (x**0.5).a.imag, "<", 0), 1j*(-x)**0.5, x**0.5) self.domain_K_exprs = [ sym.cse( upper_half_square_root(n_expr**2-ne**2), "K%d" % i) for i, n_expr in enumerate(self.domain_n_exprs)] from sumpy.kernel import HelmholtzKernel self.kernel = HelmholtzKernel(2, allow_evanescent=True)
def __init__(self, omega, mus, epss): from sumpy.kernel import HelmholtzKernel self.kernel = HelmholtzKernel(3) self.omega = omega self.mus = mus self.epss = epss self.ks = [ sym.cse(omega*(eps*mu)**0.5, "k%d" % i) for i, (eps, mu) in enumerate(zip(epss, mus))]
def __init__(self, domain_n_exprs, ne, interfaces, use_l2_weighting=None): """ :attr interfaces: a tuple of tuples ``(outer_domain, inner_domain, interface_id)``, where *outer_domain* and *inner_domain* are indices into *domain_k_names*, and *interface_id* is a symbolic name for the discretization of the interface. 'outer' designates the side of the interface to which the normal points. :attr domain_n_exprs: a tuple of variable names of the Helmholtz parameter *k*, to be used inside each part of the source geometry. May also be a tuple of strings, which will be transformed into variable references of the corresponding names. :attr beta: A symbolic expression for the wave number in the :math:`z` direction. May be a string, which will be interpreted as a variable name. """ self.interfaces = interfaces ne = sym.var(ne) self.ne = sym.cse(ne, "ne") self.domain_n_exprs = [ sym.var(n_expr) for idom, n_expr in enumerate(domain_n_exprs) ] del domain_n_exprs import pymbolic.primitives as p def upper_half_square_root(x): return p.If(p.Comparison((x**0.5).a.imag, "<", 0), 1j * (-x)**0.5, x**0.5) self.domain_K_exprs = [ sym.cse(upper_half_square_root(n_expr**2 - ne**2), "K%d" % i) for i, n_expr in enumerate(self.domain_n_exprs) ] from sumpy.kernel import HelmholtzKernel self.kernel = HelmholtzKernel(2, allow_evanescent=True)
def representation(self, i, sol): u = self.split_unknown(sol) Jxyz = sym.cse(sym.tangential_to_xyz(u.jt), "Jxyz") Mxyz = sym.cse(sym.tangential_to_xyz(u.mt), "Mxyz") # omega = self.omega mu = self.mus[i] eps = self.epss[i] k = self.ks[i] S = partial(sym.S, self.kernel, qbx_forced_limit=None, k=k) def curl_S(dens): return sym.curl(sym.S(self.kernel, dens, qbx_forced_limit=None, k=k)) grad = partial(sym.grad, 3) E0 = 1j*k*eps*S(Jxyz) + mu*curl_S(Mxyz) - grad(S(u.rho_e)) H0 = -1j*k*mu*S(Mxyz) + eps*curl_S(Jxyz) + grad(S(u.rho_m)) return sym.flat_obj_array(E0, H0)
def get_weight(self, dofdesc=None): """ :returns: a symbolic expression for the weights (quadrature weights and area elements) on *dofdesc* if :attr:`use_l2_weighting` is *True* and ``1`` otherwise. """ if self.use_l2_weighting: return sym.cse( sym.area_element(self.dim, dofdesc=dofdesc) * sym.QWeight(dofdesc=dofdesc)) else: return 1
def representation(self, i, sol): u = self.split_unknown(sol) Jxyz = sym.cse(sym.tangential_to_xyz(u.jt), "Jxyz") Mxyz = sym.cse(sym.tangential_to_xyz(u.mt), "Mxyz") # omega = self.omega mu = self.mus[i] eps = self.epss[i] k = self.ks[i] S = partial(sym.S, self.kernel, qbx_forced_limit=None, k=k) def curl_S(dens): return sym.curl(sym.S(self.kernel, dens, qbx_forced_limit=None, k=k)) grad = partial(sym.grad, 3) E0 = 1j*k*eps*S(Jxyz) + mu*curl_S(Mxyz) - grad(S(u.rho_e)) H0 = -1j*k*mu*S(Mxyz) + eps*curl_S(Jxyz) + grad(S(u.rho_m)) return sym.join_fields(E0, H0)
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 _structured_unknown(self, unknown, with_l2_weights): """ :arg with_l2_weights: If True, return the 'bare' unknowns that do not have the :math:`L^2` weights divided out. Note: Those unknowns should *not* be interpreted as point values of a density. :returns: an array of unknowns, with the following index axes: ``[pot_kind, field_kind, i_interface]``, where ``pot_kind`` is 0 for the single-layer part and 1 for the double-layer part, ``field_kind`` is 0 for the E-field and 1 for the H-field part, ``i_interface`` is the number of the enclosed domain, starting from 0. """ result = np.zeros((2, 2, len(self.interfaces)), dtype=np.object) i_unknown = 0 for pot_kind in self.pot_kinds: for field_kind in self.field_kinds: for i_interface in range(len(self.interfaces)): if self.is_field_present(field_kind): dens = unknown[i_unknown] i_unknown += 1 else: dens = 0 _, _, interface_id = self.interfaces[i_interface] if not with_l2_weights: dens = sym.cse( dens / self.get_sqrt_weight(interface_id), "dens_{pot}_{field}_{intf}".format( pot={ 0: "S", 1: "D" }[pot_kind], field={ self.field_kind_e: "E", self.field_kind_h: "H" }[field_kind], intf=i_interface)) result[pot_kind, field_kind, i_interface] = dens assert i_unknown == len(unknown) return result
def _structured_unknown(self, unknown, with_l2_weights): """ :arg with_l2_weights: If True, return the 'bare' unknowns that do not have the :math:`L^2` weights divided out. Note: Those unknowns should *not* be interpreted as point values of a density. :returns: an array of unknowns, with the following index axes: ``[side, field_kind, i_interface]``, where ``side`` is o for the outside part and i for the interior part, ``field_kind`` is 0 for the E-field and 1 for the H-field part, ``i_interface`` is the number of the enclosed domain, starting from 0. """ result = np.zeros((2, 2, len(self.interfaces)), dtype=np.object) i_unknown = 0 for side in self.sides: for field_kind in self.field_kinds: for i_interface in range(len(self.interfaces)): if self.is_field_present(field_kind): dens = unknown[i_unknown] i_unknown += 1 else: dens = 0 _, _, interface_id = self.interfaces[i_interface] if not with_l2_weights: dens = sym.cse( dens/self.get_sqrt_weight(interface_id), "dens_{side}_{field}_{dom}".format( side={ self.side_out: "o", self.side_in: "i"} [side], field={ self.field_kind_e: "E", self.field_kind_h: "H" } [field_kind], dom=i_interface)) result[side, field_kind, i_interface] = dens assert i_unknown == len(unknown) return result
def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, dofdesc=None): r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a field satisfying Maxwell's equations. :arg amplitude_vec: should be orthogonal to *v*. If it is not, it will be orthogonalized. :arg v: a three-vector representing the phase velocity of the wave (may be an object array of variables or a vector of concrete numbers) While *v* may mathematically be complex-valued, this function is for now only tested for real values. :arg omega: Accepts the "Helmholtz k" to be compatible with other parts of this module. Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. This will return an object 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`. """ # See section 7.1 of Jackson, third ed. for derivation. # NOTE: for complex, need to ensure real(n).dot(imag(n)) = 0 (7.15) x = sym.nodes(3, dofdesc=dofdesc).as_vector() v_mag_squared = sym.cse(np.dot(v, v), "v_mag_squared") n = v / sym.sqrt(v_mag_squared) amplitude_vec = amplitude_vec - np.dot(amplitude_vec, n) * n c_inv = np.sqrt(mu * epsilon) e = amplitude_vec * sym.exp(1j * np.dot(n * omega, x)) return sym.flat_obj_array(e, c_inv * sym.cross(n, e))
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, sigma, normal, mu, qbx_forced_limit): slp_qbx_forced_limit = qbx_forced_limit if slp_qbx_forced_limit == "avg": slp_qbx_forced_limit = +1 # NOTE: we set a dofdesc here to force the evaluation of this integral # on the source instead of the target when using automatic tagging # see :meth:`pytential.symbolic.mappers.LocationTagger._default_dofdesc` dd = sym.DOFDescriptor(None, discr_stage=sym.QBX_SOURCE_STAGE1) int_sigma = sym.integral(self.ambient_dim, self.dim, sigma, dofdesc=dd) meanless_sigma = sym.cse(sigma - sym.mean(self.ambient_dim, self.dim, sigma)) op_k = self.stresslet.apply(sigma, normal, mu, qbx_forced_limit=qbx_forced_limit) op_s = ( self.alpha / (2.0 * np.pi) * int_sigma - self.stokeslet.apply(meanless_sigma, mu, qbx_forced_limit=slp_qbx_forced_limit) ) return op_k + self.eta * op_s
def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=None): r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a field satisfying Maxwell's equations. :arg amplitude_vec: should be orthogonal to *v*. If it is not, it will be orthogonalized. :arg v: a three-vector representing the phase velocity of the wave (may be an object array of variables or a vector of concrete numbers) While *v* may mathematically be complex-valued, this function is for now only tested for real values. :arg omega: Accepts the "Helmholtz k" to be compatible with other parts of this module. Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. This will return an object 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`. """ # See section 7.1 of Jackson, third ed. for derivation. # NOTE: for complex, need to ensure real(n).dot(imag(n)) = 0 (7.15) x = sym.nodes(3, where).as_vector() v_mag_squared = sym.cse(np.dot(v, v), "v_mag_squared") n = v/sym.sqrt(v_mag_squared) amplitude_vec = amplitude_vec - np.dot(amplitude_vec, n)*n c_inv = np.sqrt(mu*epsilon) e = amplitude_vec * sym.exp(1j*np.dot(n*omega, x)) return sym.join_fields(e, c_inv * sym.cross(n, e))
def representation(self, domain_idx, unknown): """"Return a tuple of vectors [Ex, Ey, Ez] and [Hx, Hy, Hz] representing the solution to Maxwell's equation on domain *domain_idx*. """ result = np.zeros(4, dtype=object) unk_idx = self.unknown_index k_v = self.k_vacuum for i in range(len(self.interfaces)): dom0i, dom1i, where = self.interfaces[i] if domain_idx == dom0i: domi = dom0i elif domain_idx == dom1i: domi = dom1i else: # Interface does not border the requested domain continue beta = self.ne * k_v k = sym.cse(k_v * self.domain_n_exprs[domi], "k%d" % domi) jt = unknown[unk_idx["jz", i]] jz = unknown[unk_idx["jt", i]] mt = unknown[unk_idx["mz", i]] mz = unknown[unk_idx["mt", i]] def diff_axis(iaxis, operand): v = np.array(2, dtype=np.float64) v[iaxis] = 1 d = sym.Derivative() return d.resolve( (sym.MultiVector(v).scalar_product(d.dnabla(2))) * d(operand)) from functools import partial dx = partial(diff_axis, 1) dy = partial(diff_axis, 1) tangent = self.tangent(where) tau1 = tangent[0] tau2 = tangent[1] S = self.S # noqa D = self.D # noqa T = self.T # noqa # Ex result[0] += (-1 / (1j * k_v) * dx(T(where, domi, jt)) + beta / k_v * dx(S(domi, jz)) + k**2 / (1j * k_v) * S(domi, jt * tau1) - dy(S(domi, mz)) + 1j * beta * S(domi, mt * tau2)) # Ey result[1] += (-1 / (1j * k_v) * dy(T(where, domi, jt)) + beta / k_v * dy(S(domi, jz)) + k**2 / (1j * k_v) * S(domi, jt * tau2) + dx(S(domi, mz)) - 1j * beta * S(domi, mt * tau1)) # Ez result[2] += (-beta / k_v * T(where, domi, jt) + (k**2 - beta**2) / (1j * k_v) * S(domi, jz) + D(domi, mt)) # Hx result[3] += (1 / (1j * k_v) * dx(T(where, domi, mt)) - beta / k_v * dx(S(domi, mz)) - k**2 / (1j * k_v) * S(domi, mt * tau1) - k**2 / k_v**2 * dy(S(domi, jz)) + 1j * beta * k**2 / k_v**2 * S(domi, jt * tau2)) # Hy result[4] += (1 / (1j * k_v) * dy(T(where, domi, mt)) - beta / k_v * dy(S(domi, mz)) - k**2 / (1j * k_v) * S(domi, mt * tau2) + k**2 / k_v**2 * dx(S(domi, jz)) - 1j * beta * k**2 / k_v**2 * S(domi, jt * tau1)) # Hz result[5] += (beta / k_v * T(where, domi, mt) - (k**2 - beta**2) / (1j * k_v) * S(domi, mz) + k**2 / k_v**2 * D(domi, jt)) return result
def operator(self, unknown): result = np.zeros(4 * len(self.interfaces), dtype=object) unk_idx = self.unknown_index for i in range(len(self.interfaces)): idx_jt = unk_idx["jz", i] idx_jz = unk_idx["jt", i] idx_mt = unk_idx["mz", i] idx_mz = unk_idx["mt", i] phi1 = unknown[idx_jt] phi2 = unknown[idx_jz] phi3 = unknown[idx_mt] phi4 = unknown[idx_mz] ne = self.ne dom0i, dom1i, where = self.interfaces[i] tangent = self.tangent(where) normal = sym.cse(sym.normal(2, 1, where), "normal") S = self.S # noqa D = self.D # noqa T = self.T # noqa def Tt(where, dom, density): # noqa return sym.tangential_derivative(2, T(where, dom, density)).xproject(0) def Sn(dom, density): # noqa return sym.normal_derivative( 2, S(dom, density, qbx_forced_limit="avg")) def St(dom, density): # noqa return sym.tangential_derivative(2, S(dom, density)).xproject(0) n0 = self.domain_n_exprs[dom0i] n1 = self.domain_n_exprs[dom1i] a11 = sym.cse(n0**2 * D(dom0i, phi1) - n1**2 * D(dom1i, phi1), "a11") a22 = sym.cse(-n0**2 * Sn(dom0i, phi2) + n1**2 * Sn(dom1i, phi2), "a22") a33 = sym.cse(D(dom0i, phi3) - D(dom1i, phi3), "a33") a44 = sym.cse(-Sn(dom0i, phi4) + Sn(dom1i, phi4), "a44") a21 = sym.cse( -1j * ne * (n0**2 * tangent.scalar_product(S(dom0i, normal * phi1)) - n1**2 * tangent.scalar_product(S(dom1i, normal * phi1))), "a21") a43 = sym.cse( -1j * ne * (tangent.scalar_product(S(dom0i, normal * phi3)) - tangent.scalar_product(S(dom1i, normal * phi3))), "a43") a13 = +1 * sym.cse( ne * (T(where, dom0i, phi3) - T(where, dom1i, phi3)), "a13") a31 = -1 * sym.cse( ne * (T(where, dom0i, phi1) - T(where, dom1i, phi1)), "a31") a24 = +1 * sym.cse(ne * (St(dom0i, phi4) - St(dom1i, phi4)), "a24") a42 = -1 * sym.cse(ne * (St(dom0i, phi2) - St(dom1i, phi2)), "a42") a14 = sym.cse( 1j * ((n0**2 - ne**2) * S(dom0i, phi4) - (n1**2 - ne**2) * S(dom1i, phi4)), "a14") a32 = -sym.cse( 1j * ((n0**2 - ne**2) * S(dom0i, phi2) - (n1**2 - ne**2) * S(dom1i, phi2)), "a32") def a23_expr(phi): return ( 1j * (Tt(where, dom0i, phi) - Tt(where, dom1i, phi)) - 1j * (n0**2 * tangent.scalar_product(S(dom0i, tangent * phi)) - n1**2 * tangent.scalar_product(S(dom1i, tangent * phi)))) a23 = +1 * sym.cse(a23_expr(phi3), "a23") a41 = -1 * sym.cse(a23_expr(phi1), "a41") d1 = (n0**2 + n1**2) / 2 * phi1 d2 = (n0**2 + n1**2) / 2 * phi2 d3 = phi3 d4 = phi4 result[idx_jt] += d1 + a11 + 000 + a13 + a14 result[idx_jz] += d2 + a21 + a22 + a23 + a24 result[idx_mt] += d3 + a31 + a32 + a33 + 0 result[idx_mz] += d4 + a41 + a42 + a43 + a44 # TODO: L2 weighting # TODO: Add representation contributions to other boundaries # abutting the domain return result
def nxcurlS(qbx_forced_limit): return sym.n_cross(sym.curl(sym.S( knl, sym.cse(sym.tangential_to_xyz(density_sym), "jxyz"), qbx_forced_limit=qbx_forced_limit)))
def tangent(self, where): return sym.cse( (sym.pseudoscalar(2, 1, where) / sym.area_element(2, 1, where)), "tangent")
def main(mesh_name="torus", visualize=False): import logging logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) if mesh_name == "torus": rout = 10 rin = 1 from meshmode.mesh.generation import generate_torus base_mesh = generate_torus( rout, rin, 40, 4, mesh_order) from meshmode.mesh.processing import affine_map, merge_disjoint_meshes # nx = 1 # ny = 1 nz = 1 dz = 0 meshes = [ affine_map( base_mesh, A=np.diag([1, 1, 1]), b=np.array([0, 0, iz*dz])) for iz in range(nz)] mesh = merge_disjoint_meshes(meshes, single_group=True) if visualize: from meshmode.mesh.visualization import draw_curve draw_curve(mesh) import matplotlib.pyplot as plt plt.show() else: raise ValueError(f"unknown mesh name: {mesh_name}") pre_density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import ( QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource( pre_density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order, ) from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(3), extent=20, npoints=50) targets = actx.from_numpy(fplot.points) from pytential import GeometryCollection places = GeometryCollection({ "qbx": qbx, "qbx_target_assoc": qbx.copy(target_association_tolerance=0.2), "targets": PointsTarget(targets) }, auto_where="qbx") density_discr = places.get_discretization("qbx") # {{{ describe bvp from sumpy.kernel import LaplaceKernel kernel = LaplaceKernel(3) sigma_sym = sym.var("sigma") #sqrt_w = sym.sqrt_jac_q_weight(3) sqrt_w = 1 inv_sqrt_w_sigma = sym.cse(sigma_sym/sqrt_w) # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 bdry_op_sym = (loc_sign*0.5*sigma_sym + sqrt_w*( sym.S(kernel, inv_sqrt_w_sigma, qbx_forced_limit=+1) + sym.D(kernel, inv_sqrt_w_sigma, qbx_forced_limit="avg") )) # }}} bound_op = bind(places, bdry_op_sym) # {{{ fix rhs and solve from meshmode.dof_array import thaw, flatten, unflatten nodes = thaw(actx, density_discr.nodes()) source = np.array([rout, 0, 0]) def u_incoming_func(x): from pytools.obj_array import obj_array_vectorize x = obj_array_vectorize(actx.to_numpy, flatten(x)) x = np.array(list(x)) # return 1/cl.clmath.sqrt( (x[0] - source[0])**2 # +(x[1] - source[1])**2 # +(x[2] - source[2])**2 ) return 1.0/la.norm(x - source[:, None], axis=0) bc = unflatten(actx, density_discr, actx.from_numpy(u_incoming_func(nodes))) bvp_rhs = bind(places, sqrt_w*sym.var("bc"))(actx, bc=bc) from pytential.solve import gmres gmres_result = gmres( bound_op.scipy_op(actx, "sigma", dtype=np.float64), bvp_rhs, tol=1e-14, progress=True, stall_iterations=0, hard_failure=True) sigma = bind(places, sym.var("sigma")/sqrt_w)( actx, sigma=gmres_result.solution) # }}} from meshmode.discretization.visualization import make_visualizer bdry_vis = make_visualizer(actx, density_discr, 20) bdry_vis.write_vtk_file("laplace.vtu", [ ("sigma", sigma), ]) # {{{ postprocess/visualize repr_kwargs = dict( source="qbx_target_assoc", target="targets", qbx_forced_limit=None) representation_sym = ( sym.S(kernel, inv_sqrt_w_sigma, **repr_kwargs) + sym.D(kernel, inv_sqrt_w_sigma, **repr_kwargs)) try: fld_in_vol = actx.to_numpy( bind(places, representation_sym)(actx, sigma=sigma)) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("laplace-dirichlet-3d-failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)), ]) raise #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file("laplace-dirichlet-3d-potential.vts", [ ("potential", fld_in_vol), ])
def operator(self, unknown): result = np.zeros(4*len(self.interfaces), dtype=object) unk_idx = self.unknown_index for i in range(len(self.interfaces)): idx_jt = unk_idx["jz", i] idx_jz = unk_idx["jt", i] idx_mt = unk_idx["mz", i] idx_mz = unk_idx["mt", i] phi1 = unknown[idx_jt] phi2 = unknown[idx_jz] phi3 = unknown[idx_mt] phi4 = unknown[idx_mz] ne = self.ne dom0i, dom1i, where = self.interfaces[i] tangent = self.tangent(where) normal = sym.cse( sym.normal(2, 1, where), "normal") S = self.S # noqa D = self.D # noqa T = self.T # noqa def Tt(where, dom, density): # noqa return sym.tangential_derivative( 2, T(where, dom, density)).xproject(0) def Sn(dom, density): # noqa return sym.normal_derivative( 2, S(dom, density, qbx_forced_limit="avg")) def St(dom, density): # noqa return sym.tangential_derivative(2, S(dom, density)).xproject(0) n0 = self.domain_n_exprs[dom0i] n1 = self.domain_n_exprs[dom1i] a11 = sym.cse(n0**2 * D(dom0i, phi1) - n1**2 * D(dom1i, phi1), "a11") a22 = sym.cse(-n0**2 * Sn(dom0i, phi2) + n1**2 * Sn(dom1i, phi2), "a22") a33 = sym.cse(D(dom0i, phi3)-D(dom1i, phi3), "a33") a44 = sym.cse(-Sn(dom0i, phi4) + Sn(dom1i, phi4), "a44") a21 = sym.cse(-1j * ne * ( n0**2 * tangent.scalar_product( S(dom0i, normal * phi1)) - n1**2 * tangent.scalar_product( S(dom1i, normal * phi1))), "a21") a43 = sym.cse(-1j * ne * ( tangent.scalar_product( S(dom0i, normal * phi3)) - tangent.scalar_product( S(dom1i, normal * phi3))), "a43") a13 = +1*sym.cse( ne*(T(where, dom0i, phi3) - T(where, dom1i, phi3)), "a13") a31 = -1*sym.cse( ne*(T(where, dom0i, phi1) - T(where, dom1i, phi1)), "a31") a24 = +1*sym.cse(ne*(St(dom0i, phi4) - St(dom1i, phi4)), "a24") a42 = -1*sym.cse(ne*(St(dom0i, phi2) - St(dom1i, phi2)), "a42") a14 = sym.cse(1j*( (n0**2 - ne**2) * S(dom0i, phi4) - (n1**2 - ne**2) * S(dom1i, phi4) ), "a14") a32 = -sym.cse(1j*( (n0**2 - ne**2) * S(dom0i, phi2) - (n1**2 - ne**2) * S(dom1i, phi2) ), "a32") def a23_expr(phi): return ( 1j * (Tt(where, dom0i, phi) - Tt(where, dom1i, phi)) - 1j * ( n0**2 * tangent.scalar_product( S(dom0i, tangent * phi)) - n1**2 * tangent.scalar_product( S(dom1i, tangent * phi)))) a23 = +1*sym.cse(a23_expr(phi3), "a23") a41 = -1*sym.cse(a23_expr(phi1), "a41") d1 = (n0**2 + n1**2)/2 * phi1 d2 = (n0**2 + n1**2)/2 * phi2 d3 = phi3 d4 = phi4 result[idx_jt] += d1 + a11 + 000 + a13 + a14 result[idx_jz] += d2 + a21 + a22 + a23 + a24 result[idx_mt] += d3 + a31 + a32 + a33 + 0 result[idx_mz] += d4 + a41 + a42 + a43 + a44 # TODO: L2 weighting # TODO: Add representation contributions to other boundaries # abutting the domain return result
def timing_run(nx, ny, visualize=False): import logging logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) mesh = make_mesh(nx=nx, ny=ny, visualize=visualize) density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import (QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource(density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order) places = {"qbx": qbx} if visualize: from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) targets = PointsTarget(actx.from_numpy(fplot.points)) places.update({ "plot-targets": targets, "qbx-indicator": qbx.copy(target_association_tolerance=0.05, fmm_level_to_order=lambda lev: 7, qbx_order=2), "qbx-target-assoc": qbx.copy(target_association_tolerance=0.1) }) from pytential import GeometryCollection places = GeometryCollection(places, auto_where="qbx") density_discr = places.get_discretization("qbx") # {{{ describe bvp from sumpy.kernel import HelmholtzKernel kernel = HelmholtzKernel(2) sigma_sym = sym.var("sigma") sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = sym.cse(sigma_sym / sqrt_w) # Brakhage-Werner parameter alpha = 1j # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 k_sym = sym.var("k") S_sym = sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit=+1) D_sym = sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit="avg") bdry_op_sym = -loc_sign * 0.5 * sigma_sym + sqrt_w * (alpha * S_sym + D_sym) # }}} bound_op = bind(places, bdry_op_sym) # {{{ fix rhs and solve mode_nr = 3 from meshmode.dof_array import thaw nodes = thaw(actx, density_discr.nodes()) angle = actx.np.arctan2(nodes[1], nodes[0]) sigma = actx.np.cos(mode_nr * angle) # }}} # {{{ postprocess/visualize repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=+1) sym_op = sym.S(kernel, sym.var("sigma"), **repr_kwargs) bound_op = bind(places, sym_op) print("FMM WARM-UP RUN 1: %5d elements" % mesh.nelements) bound_op(actx, sigma=sigma, k=k) queue.finish() print("FMM WARM-UP RUN 2: %5d elements" % mesh.nelements) bound_op(actx, sigma=sigma, k=k) queue.finish() from time import time t_start = time() bound_op(actx, sigma=sigma, k=k) actx.queue.finish() elapsed = time() - t_start print("FMM TIMING RUN: %5d elements -> %g s" % (mesh.nelements, elapsed)) if visualize: ones_density = density_discr.zeros(queue) ones_density.fill(1) indicator = bind(places, sym_op, auto_where=("qbx-indicator", "plot-targets"))( queue, sigma=ones_density).get() try: fld_in_vol = bind(places, sym_op, auto_where=("qbx-target-assoc", "plot-targets"))(queue, sigma=sigma, k=k).get() except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("scaling-study-failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)), ]) raise fplot.write_vtk_file("scaling-study-potential.vts", [ ("potential", fld_in_vol), ("indicator", indicator), ]) return (mesh.nelements, elapsed)
def __init__(self, mode, k_vacuum, domain_k_exprs, beta, interfaces, use_l2_weighting=None): """ :attr mode: one of 'te', 'tm', 'tem' :attr k_vacuum: A symbolic expression for the wave number in vacuum. May be a string, which will be interpreted as a variable name. :attr interfaces: a tuple of tuples ``(outer_domain, inner_domain, interface_id)``, where *outer_domain* and *inner_domain* are indices into *domain_k_names*, and *interface_id* is a symbolic name for the discretization of the interface. 'outer' designates the side of the interface to which the normal points. :attr domain_k_exprs: a tuple of variable names of the Helmholtz parameter *k*, to be used inside each part of the source geometry. May also be a tuple of strings, which will be transformed into variable references of the corresponding names. :attr beta: A symbolic expression for the wave number in the :math:`z` direction. May be a string, which will be interpreted as a variable name. """ if use_l2_weighting is None: use_l2_weighting = False super(Dielectric2DBoundaryOperatorBase, self).__init__(use_l2_weighting=use_l2_weighting) if mode == "te": self.ez_enabled = False self.hz_enabled = True elif mode == "tm": self.ez_enabled = True self.hz_enabled = False elif mode == "tem": self.ez_enabled = True self.hz_enabled = True else: raise ValueError("invalid mode '%s'" % mode) self.interfaces = interfaces fk_e = self.field_kind_e fk_h = self.field_kind_h dir_none = self.dir_none dir_normal = self.dir_normal dir_tangential = self.dir_tangential if isinstance(beta, str): beta = sym.var(beta) beta = sym.cse(beta, "beta") if isinstance(k_vacuum, str): k_vacuum = sym.var(k_vacuum) k_vacuum = sym.cse(k_vacuum, "k_vac") self.domain_k_exprs = [ sym.var(k_expr) if isinstance(k_expr, str) else sym.cse( k_expr, "k%d" % idom) for idom, k_expr in enumerate(domain_k_exprs) ] del domain_k_exprs # Note the case of k/K! # "K" is the 2D Helmholtz parameter. # "k" is the 3D Helmholtz parameter. self.domain_K_exprs = [ sym.cse((k_expr**2 - beta**2)**0.5, "K%d" % i) for i, k_expr in enumerate(self.domain_k_exprs) ] from sumpy.kernel import HelmholtzKernel self.kernel = HelmholtzKernel(2, allow_evanescent=True) # {{{ build bc list # list of tuples, where each tuple consists of BCTermDescriptor instances all_bcs = [] for i_interface, (outer_domain, inner_domain, _) in (enumerate(self.interfaces)): k_outer = self.domain_k_exprs[outer_domain] k_inner = self.domain_k_exprs[inner_domain] all_bcs += [ ( # [E] = 0 self.BCTermDescriptor(i_interface=i_interface, direction=dir_none, field_kind=fk_e, coeff_outer=1, coeff_inner=-1), ), ( # [H] = 0 self.BCTermDescriptor(i_interface=i_interface, direction=dir_none, field_kind=fk_h, coeff_outer=1, coeff_inner=-1), ), ( self.BCTermDescriptor( i_interface=i_interface, direction=dir_tangential, field_kind=fk_e, coeff_outer=beta / (k_outer**2 - beta**2), coeff_inner=-beta / (k_inner**2 - beta**2)), self.BCTermDescriptor( i_interface=i_interface, direction=dir_normal, field_kind=fk_h, coeff_outer=sym.cse(-k_vacuum / (k_outer**2 - beta**2)), coeff_inner=sym.cse(k_vacuum / (k_inner**2 - beta**2))), ), (self.BCTermDescriptor( i_interface=i_interface, direction=dir_tangential, field_kind=fk_h, coeff_outer=beta / (k_outer**2 - beta**2), coeff_inner=-beta / (k_inner**2 - beta**2)), self.BCTermDescriptor( i_interface=i_interface, direction=dir_normal, field_kind=fk_e, coeff_outer=sym.cse( (k_outer**2 / k_vacuum) / (k_outer**2 - beta**2)), coeff_inner=sym.cse(-(k_inner**2 / k_vacuum) / (k_inner**2 - beta**2)))), ] del k_outer del k_inner self.bcs = [] for bc in all_bcs: any_significant_e = any( term.field_kind == fk_e and term.direction in [dir_normal, dir_none] for term in bc) any_significant_h = any( term.field_kind == fk_h and term.direction in [dir_normal, dir_none] for term in bc) is_necessary = ((self.ez_enabled and any_significant_e) or (self.hz_enabled and any_significant_h)) # Only keep tangential modes for TEM. Otherwise, # no jump in H already implies jump condition on # tangential derivative. is_tem = self.ez_enabled and self.hz_enabled terms = tuple(term for term in bc if term.direction != dir_tangential or is_tem) if is_necessary: self.bcs.append(terms) assert (len(all_bcs) * (int(self.ez_enabled) + int(self.hz_enabled)) // 2 == len(self.bcs))
def representation(self, domain_idx, unknown): """"Return a tuple of vectors [Ex, Ey, Ez] and [Hx, Hy, Hz] representing the solution to Maxwell's equation on domain *domain_idx*. """ result = np.zeros(4, dtype=object) unk_idx = self.unknown_index k_v = self.k_vacuum for i in range(len(self.interfaces)): dom0i, dom1i, where = self.interfaces[i] if domain_idx == dom0i: domi = dom0i elif domain_idx == dom1i: domi = dom1i else: # Interface does not border the requested domain continue beta = self.ne*k_v k = sym.cse(k_v * self.domain_n_exprs[domi], "k%d" % domi) jt = unknown[unk_idx["jz", i]] jz = unknown[unk_idx["jt", i]] mt = unknown[unk_idx["mz", i]] mz = unknown[unk_idx["mt", i]] def diff_axis(iaxis, operand): v = np.array(2, dtype=np.float64) v[iaxis] = 1 d = sym.Derivative() return d.resolve( (sym.MultiVector(v).scalar_product(d.dnabla(2))) * d(operand)) from functools import partial dx = partial(diff_axis, 1) dy = partial(diff_axis, 1) tangent = self.tangent(where) tau1 = tangent[0] tau2 = tangent[1] S = self.S # noqa D = self.D # noqa T = self.T # noqa # Ex result[0] += ( -1/(1j*k_v) * dx(T(where, domi, jt)) + beta/k_v * dx(S(domi, jz)) + k**2/(1j*k_v) * S(domi, jt * tau1) - dy(S(domi, mz)) + 1j*beta * S(domi, mt*tau2) ) # Ey result[1] += ( - 1/(1j*k_v) * dy(T(where, domi, jt)) + beta/k_v * dy(S(domi, jz)) + k**2/(1j*k_v) * S(domi, jt * tau2) + dx(S(domi, mz)) - 1j*beta*S(domi, mt * tau1) ) # Ez result[2] += ( - beta/k_v * T(where, domi, jt) + (k**2 - beta**2)/(1j*k_v) * S(domi, jz) + D(domi, mt) ) # Hx result[3] += ( 1/(1j*k_v) * dx(T(where, domi, mt)) - beta/k_v * dx(S(domi, mz)) - k**2/(1j*k_v) * S(domi, mt*tau1) - k**2/k_v**2 * dy(S(domi, jz)) + 1j * beta * k**2/k_v**2 * S(domi, jt*tau2) ) # Hy result[4] += ( 1/(1j*k_v) * dy(T(where, domi, mt)) - beta/k_v * dy(S(domi, mz)) - k**2/(1j*k_v) * S(domi, mt * tau2) + k**2/k_v**2 * dx(S(domi, jz)) - 1j*beta * k**2/k_v**2 * S(domi, jt*tau1) ) # Hz result[5] += ( beta/k_v * T(where, domi, mt) - (k**2 - beta**2)/(1j*k_v) * S(domi, mz) + k**2/k_v**2 * D(domi, jt) ) return result
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 center_info(self): """Return a :class:`CenterInfo`. |cached| """ self_discr = self.lpot_source.density_discr ncenters = 0 for el_group in self_discr.groups: kept_indices = self.kept_center_indices(el_group) # two: one for positive side, one for negative side ncenters += 2 * len(kept_indices) * el_group.nelements from pytential import sym, bind from pytools.obj_array import make_obj_array with cl.CommandQueue(self.cl_context) as queue: radii_sym = sym.cse(2*sym.area_element(), "radii") all_radii, all_pos_centers, all_neg_centers = bind(self_discr, make_obj_array([ radii_sym, sym.Nodes() + radii_sym*sym.normal(), sym.Nodes() - radii_sym*sym.normal() ]))(queue) # The centers are returned from the above as multivectors. all_pos_centers = all_pos_centers.as_vector(np.object) all_neg_centers = all_neg_centers.as_vector(np.object) # -1 for inside, +1 for outside sides = cl.array.empty( self.cl_context, ncenters, np.int8) radii = cl.array.empty( self.cl_context, ncenters, self.coord_dtype) centers = make_obj_array([ cl.array.empty(self.cl_context, ncenters, self.coord_dtype) for i in range(self_discr.ambient_dim)]) ibase = 0 for el_group in self_discr.groups: kept_center_indices = self.kept_center_indices(el_group) group_len = len(kept_indices) * el_group.nelements for side, all_centers in [ (+1, all_pos_centers), (-1, all_neg_centers), ]: sides[ibase:ibase + group_len].fill(side, queue=queue) radii_view = radii[ibase:ibase + group_len] \ .reshape(el_group.nelements, len(kept_indices)) centers_view = make_obj_array([ centers_i[ibase:ibase + group_len] .reshape((el_group.nelements, len(kept_indices))) for centers_i in centers ]) all_centers_view = make_obj_array([ el_group.view(pos_centers_i) for pos_centers_i in all_centers ]) self.code_getter.pick_expansion_centers(queue, centers=centers_view, all_centers=all_centers_view, radii=radii_view, all_radii=el_group.view(all_radii), kept_center_indices=kept_center_indices) ibase += group_len assert ibase == ncenters return CenterInfo( sides=sides, radii=radii, centers=centers).with_queue(None)
def main(mesh_name="ellipse", visualize=False): import logging logging.basicConfig(level=logging.INFO) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) from meshmode.mesh.generation import ellipse, make_curve_mesh from functools import partial if mesh_name == "ellipse": mesh = make_curve_mesh(partial(ellipse, 1), np.linspace(0, 1, nelements + 1), mesh_order) elif mesh_name == "ellipse_array": base_mesh = make_curve_mesh(partial(ellipse, 1), np.linspace(0, 1, nelements + 1), mesh_order) from meshmode.mesh.processing import affine_map, merge_disjoint_meshes nx = 2 ny = 2 dx = 2 / nx meshes = [ affine_map(base_mesh, A=np.diag([dx * 0.25, dx * 0.25]), b=np.array([dx * (ix - nx / 2), dx * (iy - ny / 2)])) for ix in range(nx) for iy in range(ny) ] mesh = merge_disjoint_meshes(meshes, single_group=True) if visualize: from meshmode.mesh.visualization import draw_curve draw_curve(mesh) import matplotlib.pyplot as plt plt.show() else: raise ValueError(f"unknown mesh name: {mesh_name}") pre_density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import (QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource(pre_density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order) from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500) targets = actx.from_numpy(fplot.points) from pytential import GeometryCollection places = GeometryCollection( { "qbx": qbx, "qbx_high_target_assoc_tol": qbx.copy(target_association_tolerance=0.05), "targets": PointsTarget(targets) }, auto_where="qbx") density_discr = places.get_discretization("qbx") # {{{ describe bvp from sumpy.kernel import LaplaceKernel, HelmholtzKernel kernel = HelmholtzKernel(2) sigma_sym = sym.var("sigma") sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = sym.cse(sigma_sym / sqrt_w) # Brakhage-Werner parameter alpha = 1j # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 k_sym = sym.var("k") bdry_op_sym = ( -loc_sign * 0.5 * sigma_sym + sqrt_w * (alpha * sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit=+1) - sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit="avg"))) # }}} bound_op = bind(places, bdry_op_sym) # {{{ fix rhs and solve from meshmode.dof_array import thaw nodes = thaw(actx, density_discr.nodes()) k_vec = np.array([2, 1]) k_vec = k * k_vec / la.norm(k_vec, 2) def u_incoming_func(x): return actx.np.exp(1j * (x[0] * k_vec[0] + x[1] * k_vec[1])) bc = -u_incoming_func(nodes) bvp_rhs = bind(places, sqrt_w * sym.var("bc"))(actx, bc=bc) from pytential.solve import gmres gmres_result = gmres(bound_op.scipy_op(actx, sigma_sym.name, dtype=np.complex128, k=k), bvp_rhs, tol=1e-8, progress=True, stall_iterations=0, hard_failure=True) # }}} # {{{ postprocess/visualize repr_kwargs = dict(source="qbx_high_target_assoc_tol", target="targets", qbx_forced_limit=None) representation_sym = ( alpha * sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, **repr_kwargs) - sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, **repr_kwargs)) u_incoming = u_incoming_func(targets) ones_density = density_discr.zeros(actx) for elem in ones_density: elem.fill(1) indicator = actx.to_numpy( bind(places, sym.D(LaplaceKernel(2), sigma_sym, **repr_kwargs))(actx, sigma=ones_density)) try: fld_in_vol = actx.to_numpy( bind(places, representation_sym)(actx, sigma=gmres_result.solution, k=k)) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("helmholtz-dirichlet-failed-targets.vts", [("failed", e.failed_target_flags.get(queue))]) raise #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file("helmholtz-dirichlet-potential.vts", [ ("potential", fld_in_vol), ("indicator", indicator), ("u_incoming", actx.to_numpy(u_incoming)), ])
def __init__(self, mode, k_vacuum, domain_k_exprs, beta, interfaces, use_l2_weighting=None): """ :attr mode: one of 'te', 'tm', 'tem' :attr k_vacuum: A symbolic expression for the wave number in vacuum. May be a string, which will be interpreted as a variable name. :attr interfaces: a tuple of tuples ``(outer_domain, inner_domain, interface_id)``, where *outer_domain* and *inner_domain* are indices into *domain_k_names*, and *interface_id* is a symbolic name for the discretization of the interface. 'outer' designates the side of the interface to which the normal points. :attr domain_k_exprs: a tuple of variable names of the Helmholtz parameter *k*, to be used inside each part of the source geometry. May also be a tuple of strings, which will be transformed into variable references of the corresponding names. :attr beta: A symbolic expression for the wave number in the :math:`z` direction. May be a string, which will be interpreted as a variable name. """ if use_l2_weighting is None: use_l2_weighting = False super(Dielectric2DBoundaryOperatorBase, self).__init__( use_l2_weighting=use_l2_weighting) if mode == "te": self.ez_enabled = False self.hz_enabled = True elif mode == "tm": self.ez_enabled = True self.hz_enabled = False elif mode == "tem": self.ez_enabled = True self.hz_enabled = True else: raise ValueError("invalid mode '%s'" % mode) self.interfaces = interfaces fk_e = self.field_kind_e fk_h = self.field_kind_h dir_none = self.dir_none dir_normal = self.dir_normal dir_tangential = self.dir_tangential if isinstance(beta, str): beta = sym.var(beta) beta = sym.cse(beta, "beta") if isinstance(k_vacuum, str): k_vacuum = sym.var(k_vacuum) k_vacuum = sym.cse(k_vacuum, "k_vac") self.domain_k_exprs = [ sym.var(k_expr) if isinstance(k_expr, str) else sym.cse(k_expr, "k%d" % idom) for idom, k_expr in enumerate(domain_k_exprs)] del domain_k_exprs # Note the case of k/K! # "K" is the 2D Helmholtz parameter. # "k" is the 3D Helmholtz parameter. self.domain_K_exprs = [ sym.cse((k_expr**2-beta**2)**0.5, "K%d" % i) for i, k_expr in enumerate(self.domain_k_exprs)] from sumpy.kernel import HelmholtzKernel self.kernel = HelmholtzKernel(2, allow_evanescent=True) # {{{ build bc list # list of tuples, where each tuple consists of BCTermDescriptor instances all_bcs = [] for i_interface, (outer_domain, inner_domain, _) in ( enumerate(self.interfaces)): k_outer = self.domain_k_exprs[outer_domain] k_inner = self.domain_k_exprs[inner_domain] all_bcs += [ ( # [E] = 0 self.BCTermDescriptor( i_interface=i_interface, direction=dir_none, field_kind=fk_e, coeff_outer=1, coeff_inner=-1), ), ( # [H] = 0 self.BCTermDescriptor( i_interface=i_interface, direction=dir_none, field_kind=fk_h, coeff_outer=1, coeff_inner=-1), ), ( self.BCTermDescriptor( i_interface=i_interface, direction=dir_tangential, field_kind=fk_e, coeff_outer=beta/(k_outer**2-beta**2), coeff_inner=-beta/(k_inner**2-beta**2)), self.BCTermDescriptor( i_interface=i_interface, direction=dir_normal, field_kind=fk_h, coeff_outer=sym.cse(-k_vacuum/(k_outer**2-beta**2)), coeff_inner=sym.cse(k_vacuum/(k_inner**2-beta**2))), ), ( self.BCTermDescriptor( i_interface=i_interface, direction=dir_tangential, field_kind=fk_h, coeff_outer=beta/(k_outer**2-beta**2), coeff_inner=-beta/(k_inner**2-beta**2)), self.BCTermDescriptor( i_interface=i_interface, direction=dir_normal, field_kind=fk_e, coeff_outer=sym.cse( (k_outer**2/k_vacuum)/(k_outer**2-beta**2)), coeff_inner=sym.cse( -(k_inner**2/k_vacuum) / (k_inner**2-beta**2))) ), ] del k_outer del k_inner self.bcs = [] for bc in all_bcs: any_significant_e = any( term.field_kind == fk_e and term.direction in [dir_normal, dir_none] for term in bc) any_significant_h = any( term.field_kind == fk_h and term.direction in [dir_normal, dir_none] for term in bc) is_necessary = ( (self.ez_enabled and any_significant_e) or (self.hz_enabled and any_significant_h)) # Only keep tangential modes for TEM. Otherwise, # no jump in H already implies jump condition on # tangential derivative. is_tem = self.ez_enabled and self.hz_enabled terms = tuple( term for term in bc if term.direction != dir_tangential or is_tem) if is_necessary: self.bcs.append(terms) assert (len(all_bcs) * (int(self.ez_enabled) + int(self.hz_enabled)) // 2 == len(self.bcs))