From 370b5f37eddd57a39ea7608bae575023ca3863d4 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sat, 16 Dec 2017 13:37:31 -0600 Subject: [PATCH 01/59] Made progress adding in model for DPIE to solve Maxwell equations. Still need to figure out what exactly to do with a boundary integral term used to enforce uniqueness. --- pytential/symbolic/pde/maxwell/__init__.py | 137 ++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 489f97fe..73528984 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -115,7 +115,7 @@ def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=N # {{{ Charge-Current MFIE class PECChargeCurrentMFIEOperator: - r"""Magnetic Field Integral Equation operator with PEC boundary + """Magnetic Field Integral Equation operator with PEC boundary conditions, under the assumption of no surface charges. See :file:`contrib/notes/mfie.tm` in the repository for a derivation. @@ -283,4 +283,139 @@ class MuellerAugmentedMFIEOperator(object): # }}} + +# {{{ Decoupled Potential Integral Equation Operator +class DPIEOperator: + """ + Decoupled Potential Integral Equation operator with PEC boundary + conditions, defaults as scaled DPIE. + + See https://arxiv.org/abs/1404.0749 for derivation. + + Uses E(x,t) = Re{E(x) exp(-i omega t)} and H(x,t) = Re{H(x) exp(-i omega t)} + and solves for the E(x), H(x) fields using vector and scalar potentials via + the Lorenz Gauage. The DPIE formulates the problem purely in terms of the + vector and scalar potentials, A and phi, and then backs out E(x) and H(x) + via relationships to the vector and scalar potentials. + """ + + def __init__(self, k=sym.var("k"), char_funcs): + from sumpy.kernel import HelmholtzKernel + self.k = k + self.kernel = HelmholtzKernel(3) + self.char_funcs = char_funcs + + + def phi_operator(self,sigma,V_array): + """ + Integral Equation operator for obtaining scalar potential, `phi` + """ + return sym.join_fields( + 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + + np.sum(V_array,self.char_funcs), + # include integral here + ) + + + def phi_rhs(self, phi_inc, Q_array): + """ + The Right-Hand-Side for the Integral Equation for `phi` + """ + return sym.join_fields(-phi_inc, + Q_array/self.k) + + def A_operator(self, a, rho, v_array): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # define Derivative instance for divergence + d = sym.Derivative() + + # define the normal vector in symbolic form + n = sym.normal(len(a), where).as_vector() + + # define system of integral equations for A + return sym.join_fields( + 0.5*a + sym.n_cross(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg")) + -self.k * sym.n_cross(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) + + 1j*( + self.k*sym.n_cross(sym.cross(sym.S(self.kernel,n,k=self.k,qbx_forced_limit="avg"),a)) + + sym.n_cross(sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg"))) + ), + 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + + 1j*( + d.dnabla(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + ) + + np.sum(v_array,self.char_funcs), + # linear equation used for uniqueness + ) + + def A_rhs(self, A_inc, q_array): + """ + The Right-Hand-Side for the Integral Equation for `A` + """ + + # define Derivative instance for divergence + d = sym.Derivative() + + # define RHS for `A` integral equation system + return sym.join_fields( + -sym.n_cross(A_inc), + -d.dnabla(A_inc)/self.k, + q_array + ) + + + def scalar_potential_rep(self, sigma, qbx_forced_limit=None): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) + - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) + + def vector_potential_rep(self, a, rho, qbx_forced_limit=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + # define the normal vector in symbolic form + n = sym.normal(len(a), where).as_vector() + + # define the vector potential representation + return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) + - self.k*sym.S(self,kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit) + + 1j*( + self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) + + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) + ) + + + def scattered_volume_field(self, sigma_soln, a_soln, rho_soln, qbx_forced_limit=None): + """ + 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`. + """ + + # obtain expressions for scalar and vector potentials + A = self.vector_potential_rep(a_soln,rho_soln,qbx_forced_limit=qbx_forced_limit) + phi = self.scalar_potential_rep(sigma_soln,qbx_forced_limit=qbx_forced_limit) + + # evaluate the potential form for the electric and magnetic fields + E_scat = 1j*self.k*A - sym.grad(3, phi) + H_scat = sym.curl(A) + + # join the fields into a vector + return sym.join_fields(E_scat, H_scat) + +# }}} + # vim: foldmethod=marker -- GitLab From 00aec5270cafe6e2df63bc700a5d72c8a2d97147 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Mon, 18 Dec 2017 07:30:39 -0600 Subject: [PATCH 02/59] Made small tweaks to code. --- pytential/symbolic/pde/maxwell/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 73528984..e8873669 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -334,7 +334,7 @@ class DPIEOperator: d = sym.Derivative() # define the normal vector in symbolic form - n = sym.normal(len(a), where).as_vector() + n = sym.normal(len(a), None).as_vector() # define system of integral equations for A return sym.join_fields( @@ -383,7 +383,7 @@ class DPIEOperator: based on the vector density `a` and scalar density `rho` """ # define the normal vector in symbolic form - n = sym.normal(len(a), where).as_vector() + n = sym.normal(len(a), None).as_vector() # define the vector potential representation return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) -- GitLab From 2ccc8f026d50762cf2c4aabc301500dac060bfa0 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Thu, 21 Dec 2017 23:23:16 -0600 Subject: [PATCH 03/59] Finished most of the DPIE model based on feedback from Andreas, though I think I may still have to sort out solving the normal integral equation across the whole domain.. maybe. Otherwise, putting together the test is the next big step. I got started on this, but will continue it on my linux computer. --- pytential/symbolic/pde/maxwell/__init__.py | 59 ++- test/test_maxwell_dpie.py | 513 +++++++++++++++++++++ 2 files changed, 557 insertions(+), 15 deletions(-) create mode 100644 test/test_maxwell_dpie.py diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index e8873669..73360c00 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -299,11 +299,23 @@ class DPIEOperator: via relationships to the vector and scalar potentials. """ - def __init__(self, k=sym.var("k"), char_funcs): + def __init__(self, k=sym.var("k"), geometry_list): from sumpy.kernel import HelmholtzKernel + + # specify the frequency variable that will be tuned self.k = k + + # specify the 3-D Helmholtz kernel self.kernel = HelmholtzKernel(3) - self.char_funcs = char_funcs + + # specify a list of strings representing geometry objects + self.geom_list = geometry_list + + # create the characteristic functions that give a value of + # 1 when we are on some surface/valume and a value of 0 otherwise + self.char_funcs = np.zeros((len(geometry_list),), dtype=sym.var) + for idx in range(0,len(geometry_list)): + self.char_funcs[idx] = sym.D(self.kernel,1,source=self.geom_list[idx]) def phi_operator(self,sigma,V_array): @@ -313,15 +325,25 @@ class DPIEOperator: return sym.join_fields( 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - + np.sum(V_array,self.char_funcs), - # include integral here + + np.dot(V_array,self.char_funcs), + sym.integral(ambient_dim=3,dim=2, + sym.Dp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg")/self.k + + 1j*sigma/2.0 - 1j*sym.Sp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + ) ) - def phi_rhs(self, phi_inc, Q_array): + def phi_rhs(self, phi_inc): """ The Right-Hand-Side for the Integral Equation for `phi` """ + + # get the Q_array + Q_array = np.zeros((len(geometry_list),),dtype=sym.var) + for i in range(0,len(geometry_list)): + Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=geometry_list[i]) + + # return the resulting field return sym.join_fields(-phi_inc, Q_array/self.k) @@ -330,9 +352,6 @@ class DPIEOperator: Integral Equation operator for obtaining vector potential, `A` """ - # define Derivative instance for divergence - d = sym.Derivative() - # define the normal vector in symbolic form n = sym.normal(len(a), None).as_vector() @@ -346,25 +365,35 @@ class DPIEOperator: ), 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + 1j*( - d.dnabla(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + sym.div(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") ) - + np.sum(v_array,self.char_funcs), - # linear equation used for uniqueness + + np.dot(v_array,self.char_funcs), + sym.integral(ambient_dim=3,dim=2, + sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) + - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) + + 1j*( + self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + - rho/2.0 + + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + ) + ) ) - def A_rhs(self, A_inc, q_array): + def A_rhs(self, A_inc): """ The Right-Hand-Side for the Integral Equation for `A` """ - # define Derivative instance for divergence - d = sym.Derivative() + # get the q_array + q_array = np.zeros((len(geometry_list),),dtype=sym.var) + for i in range(0,len(geometry_list)): + q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=geometry_list[i]) # define RHS for `A` integral equation system return sym.join_fields( -sym.n_cross(A_inc), - -d.dnabla(A_inc)/self.k, + -sym.div(A_inc)/self.k, q_array ) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py new file mode 100644 index 00000000..35874643 --- /dev/null +++ b/test/test_maxwell_dpie.py @@ -0,0 +1,513 @@ +from __future__ import division, absolute_import, print_function + +__copyright__ = "Copyright (C) 2017 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np +import numpy.linalg as la # noqa +import pyopencl as cl +import pyopencl.clmath # noqa +import pyopencl.clrandom # noqa +import pytest + +from pytential import bind, sym, norm + +from sumpy.visualization import make_field_plotter_from_bbox # noqa +from sumpy.point_calculus import CalculusPatch, frequency_domain_maxwell +from sumpy.tools import vector_from_device +from pytential.target import PointsTarget +from meshmode.mesh.processing import find_bounding_box + +import logging +logger = logging.getLogger(__name__) + +from pyopencl.tools import ( # noqa + pytest_generate_tests_for_pyopencl as pytest_generate_tests) + + +# {{{ test cases + +class MaxwellTestCase: + fmm_backend = "fmmlib" + + def __init__(self, k, is_interior, resolutions, qbx_order, + fmm_tolerance): + self.k = k + self.is_interior = is_interior + self.resolutions = resolutions + self.qbx_order = qbx_order + self.fmm_tolerance = fmm_tolerance + + +class SphereTestCase(MaxwellTestCase): + target_order = 8 + gmres_tol = 1e-10 + + def get_mesh(self, resolution, target_order): + from meshmode.mesh.generation import generate_icosphere + from meshmode.mesh.refinement import refine_uniformly + return refine_uniformly( + generate_icosphere(2, target_order), + resolution) + + def get_observation_mesh(self, target_order): + from meshmode.mesh.generation import generate_icosphere + + if self.is_interior: + return generate_icosphere(5, target_order) + else: + return generate_icosphere(0.5, target_order) + + def get_source(self, queue): + if self.is_interior: + source_ctr = np.array([[0.35, 0.1, 0.15]]).T + else: + source_ctr = np.array([[5, 0.1, 0.15]]).T + + source_rad = 0.3 + + sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) + from pytential.source import PointPotentialSource + return PointPotentialSource( + queue.context, + cl.array.to_device(queue, sources)) + + +class RoundedCubeTestCase(MaxwellTestCase): + target_order = 8 + gmres_tol = 1e-10 + + def get_mesh(self, resolution, target_order): + from meshmode.mesh.io import generate_gmsh, FileSource + mesh = generate_gmsh( + FileSource("rounded-cube.step"), 2, order=3, + other_options=[ + "-string", + "Mesh.CharacteristicLengthMax = %g;" % resolution]) + + from meshmode.mesh.processing import perform_flips, affine_map + mesh = affine_map(mesh, b=np.array([-0.5, -0.5, -0.5])) + mesh = affine_map(mesh, A=np.eye(3)*2) + + # now centered at origin and extends to -1,1 + + # Flip elements--gmsh generates inside-out geometry. + return perform_flips(mesh, np.ones(mesh.nelements)) + + def get_observation_mesh(self, target_order): + from meshmode.mesh.generation import generate_icosphere + + if self.is_interior: + return generate_icosphere(5, target_order) + else: + return generate_icosphere(0.5, target_order) + + def get_source(self, queue): + if self.is_interior: + source_ctr = np.array([[0.35, 0.1, 0.15]]).T + else: + source_ctr = np.array([[5, 0.1, 0.15]]).T + + source_rad = 0.3 + + sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) + from pytential.source import PointPotentialSource + return PointPotentialSource( + queue.context, + cl.array.to_device(queue, sources)) + + +class ElliptiPlaneTestCase(MaxwellTestCase): + target_order = 4 + gmres_tol = 1e-7 + + def get_mesh(self, resolution, target_order): + from pytools import download_from_web_if_not_present + + download_from_web_if_not_present( + "https://raw.githubusercontent.com/inducer/geometries/master/" + "surface-3d/elliptiplane.brep") + + from meshmode.mesh.io import generate_gmsh, FileSource + mesh = generate_gmsh( + FileSource("elliptiplane.brep"), 2, order=2, + other_options=[ + "-string", + "Mesh.CharacteristicLengthMax = %g;" % resolution]) + + # now centered at origin and extends to -1,1 + + # Flip elements--gmsh generates inside-out geometry. + from meshmode.mesh.processing import perform_flips + return perform_flips(mesh, np.ones(mesh.nelements)) + + def get_observation_mesh(self, target_order): + from meshmode.mesh.generation import generate_icosphere + + if self.is_interior: + return generate_icosphere(12, target_order) + else: + return generate_icosphere(0.5, target_order) + + def get_source(self, queue): + if self.is_interior: + source_ctr = np.array([[0.35, 0.1, 0.15]]).T + else: + source_ctr = np.array([[3, 1, 10]]).T + + source_rad = 0.3 + + sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) + from pytential.source import PointPotentialSource + return PointPotentialSource( + queue.context, + cl.array.to_device(queue, sources)) + +# }}} + + +tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], + qbx_order=3, fmm_tolerance=1e-4) +tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], + qbx_order=3, fmm_tolerance=1e-4) + +tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], + qbx_order=3, fmm_tolerance=1e-4) + +tc_plane_ext = ElliptiPlaneTestCase(k=2, is_interior=False, resolutions=[0.15], + qbx_order=3, fmm_tolerance=1e-4) + + +class EHField(object): + def __init__(self, eh_field): + assert len(eh_field) == 6 + self.field = eh_field + + @property + def e(self): + return self.field[:3] + + @property + def h(self): + return self.field[3:] + + +# {{{ driver + +@pytest.mark.parametrize("case", [ + #tc_int, + tc_ext, + ]) +def test_pec_dpie_extinction(ctx_getter, case, visualize=False): + """For (say) is_interior=False (the 'exterior' MFIE), this test verifies + extinction of the combined (incoming + scattered) field on the interior + of the scatterer. + """ + logging.basicConfig(level=logging.INFO) + + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + pytest.importorskip("pyfmmlib") + + np.random.seed(12) + + knl_kwargs = {"k": case.k} + + # {{{ come up with a solution to Maxwell's equations + + j_sym = sym.make_sym_vector("j", 3) + jt_sym = sym.make_sym_vector("jt", 2) + rho_sym = sym.var("rho") + + from pytential.symbolic.pde.maxwell import ( + DPIEOperator, + get_sym_maxwell_point_source, + get_sym_maxwell_plane_wave) + + geom_list = [None] + dpie = DPIEOperator(geometry_list = geom_list) + + test_source = case.get_source(queue) + + calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) + calc_patch_tgt = PointsTarget(cl.array.to_device(queue, calc_patch.points)) + + rng = cl.clrandom.PhiloxGenerator(cl_ctx, seed=12) + src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) + + def eval_inc_field_at(tgt): + if 0: + # plane wave + return bind( + tgt, + get_sym_maxwell_plane_wave( + amplitude_vec=np.array([1, 1, 1]), + v=np.array([1, 0, 0]), + omega=case.k) + )(queue) + else: + # point source + return bind( + (test_source, tgt), + get_sym_maxwell_point_source(mfie.kernel, j_sym, mfie.k) + )(queue, j=src_j, k=case.k) + + pde_test_inc = EHField( + vector_from_device(queue, eval_inc_field_at(calc_patch_tgt))) + + source_maxwell_resids = [ + calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_inc.e, np.inf) + for x in frequency_domain_maxwell( + calc_patch, pde_test_inc.e, pde_test_inc.h, case.k)] + print("Source Maxwell residuals:", source_maxwell_resids) + assert max(source_maxwell_resids) < 1e-6 + + # }}} + + loc_sign = -1 if case.is_interior else +1 + + from pytools.convergence import EOCRecorder + + eoc_rec_repr_maxwell = EOCRecorder() + eoc_pec_bc = EOCRecorder() + eoc_rec_e = EOCRecorder() + eoc_rec_h = EOCRecorder() + + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + for resolution in case.resolutions: + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + qbx, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + h_max = qbx.h_max + + scat_discr = qbx.density_discr + obs_discr = Discretization( + cl_ctx, observation_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + inc_field_scat = EHField(eval_inc_field_at(scat_discr)) + inc_field_obs = EHField(eval_inc_field_at(obs_discr)) + + # {{{ system solve + + inc_xyz_sym = EHField(sym.make_sym_vector("inc_fld", 6)) + + bound_j_op = bind(qbx, mfie.j_operator(loc_sign, jt_sym)) + j_rhs = bind(qbx, mfie.j_rhs(inc_xyz_sym.h))( + queue, inc_fld=inc_field_scat.field, **knl_kwargs) + + gmres_settings = dict( + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + from pytential.solve import gmres + gmres_result = gmres( + bound_j_op.scipy_op(queue, "jt", np.complex128, **knl_kwargs), + j_rhs, **gmres_settings) + + jt = gmres_result.solution + + bound_rho_op = bind(qbx, mfie.rho_operator(loc_sign, rho_sym)) + rho_rhs = bind(qbx, mfie.rho_rhs(jt_sym, inc_xyz_sym.e))( + queue, jt=jt, inc_fld=inc_field_scat.field, **knl_kwargs) + + gmres_result = gmres( + bound_rho_op.scipy_op(queue, "rho", np.complex128, **knl_kwargs), + rho_rhs, **gmres_settings) + + rho = gmres_result.solution + + # }}} + + jxyz = bind(qbx, sym.tangential_to_xyz(jt_sym))(queue, jt=jt) + + # {{{ volume eval + + sym_repr = mfie.scattered_volume_field(jt_sym, rho_sym) + + def eval_repr_at(tgt, source=None): + if source is None: + source = qbx + + return bind((source, tgt), sym_repr)(queue, jt=jt, rho=rho, **knl_kwargs) + + pde_test_repr = EHField( + vector_from_device(queue, eval_repr_at(calc_patch_tgt))) + + maxwell_residuals = [ + calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) + for x in frequency_domain_maxwell( + calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] + print("Maxwell residuals:", maxwell_residuals) + + eoc_rec_repr_maxwell.add_data_point(h_max, max(maxwell_residuals)) + + # }}} + + # {{{ check PEC BC on total field + + bc_repr = EHField(mfie.scattered_volume_field( + jt_sym, rho_sym, qbx_forced_limit=loc_sign)) + pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) + pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) + + eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( + queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, + **knl_kwargs) + + def scat_norm(f): + return norm(qbx, queue, f, p=np.inf) + + e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) + h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) + + print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) + + eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) + + # }}} + + # {{{ visualization + + if visualize: + from meshmode.discretization.visualization import make_visualizer + bdry_vis = make_visualizer(queue, scat_discr, case.target_order+3) + + bdry_normals = bind(scat_discr, sym.normal(3))(queue)\ + .as_vector(dtype=object) + + bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ + ("j", jxyz), + ("rho", rho), + ("Einc", inc_field_scat.e), + ("Hinc", inc_field_scat.h), + ("bdry_normals", bdry_normals), + ("e_bc_residual", eh_bc_values[:3]), + ("h_bc_residual", eh_bc_values[3]), + ]) + + fplot = make_field_plotter_from_bbox( + find_bounding_box(scat_discr.mesh), h=(0.05, 0.05, 0.3), + extend_factor=0.3) + + from pytential.qbx import QBXTargetAssociationFailedException + + qbx_tgt_tol = qbx.copy(target_association_tolerance=0.2) + + fplot_tgt = PointsTarget(cl.array.to_device(queue, fplot.points)) + try: + fplot_repr = eval_repr_at(fplot_tgt, source=qbx_tgt_tol) + except QBXTargetAssociationFailedException as e: + fplot.write_vtk_file( + "failed-targets.vts", + [ + ("failed_targets", e.failed_target_flags.get(queue)) + ]) + raise + + fplot_repr = EHField(vector_from_device(queue, fplot_repr)) + + fplot_inc = EHField( + vector_from_device(queue, eval_inc_field_at(fplot_tgt))) + + fplot.write_vtk_file( + "potential-%s.vts" % resolution, + [ + ("E", fplot_repr.e), + ("H", fplot_repr.h), + ("Einc", fplot_inc.e), + ("Hinc", fplot_inc.h), + ] + ) + + # }}} + + # {{{ error in E, H + + obs_repr = EHField(eval_repr_at(obs_discr)) + + def obs_norm(f): + return norm(obs_discr, queue, f, p=np.inf) + + rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) + / obs_norm(inc_field_obs.e)) + rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) + / obs_norm(inc_field_obs.h)) + + # }}} + + print("ERR", h_max, rel_err_h, rel_err_e) + + eoc_rec_h.add_data_point(h_max, rel_err_h) + eoc_rec_e.add_data_point(h_max, rel_err_e) + + print("--------------------------------------------------------") + print("is_interior=%s" % case.is_interior) + print("--------------------------------------------------------") + + good = True + for which_eoc, eoc_rec, order_tol in [ + ("maxwell", eoc_rec_repr_maxwell, 1.5), + ("PEC BC", eoc_pec_bc, 1.5), + ("H", eoc_rec_h, 1.5), + ("E", eoc_rec_e, 1.5)]: + print(which_eoc) + print(eoc_rec.pretty_print()) + + if len(eoc_rec.history) > 1: + if eoc_rec.order_estimate() < case.qbx_order - order_tol: + good = False + + assert good + +# }}} + + +# You can test individual routines by typing +# $ python test_maxwell.py 'test_routine()' + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + from py.test.cmdline import main + main([__file__]) + +# vim: fdm=marker -- GitLab From 774a2483c1a293fd498bfecf5fc273029f1cc4c6 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Fri, 22 Dec 2017 12:05:18 -0600 Subject: [PATCH 04/59] Cleaned up most of the DPIE model code and started putting together the test code for the DPIE tackling a baseline Maxwell problem. Note that the test code is still in development and incomplete. --- pytential/symbolic/pde/maxwell/__init__.py | 50 +++++----- test/test_maxwell_dpie.py | 103 ++++++++++++++++----- 2 files changed, 103 insertions(+), 50 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 73360c00..f11d4cfa 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -299,7 +299,7 @@ class DPIEOperator: via relationships to the vector and scalar potentials. """ - def __init__(self, k=sym.var("k"), geometry_list): + def __init__(self, geometry_list, k=sym.var("k")): from sumpy.kernel import HelmholtzKernel # specify the frequency variable that will be tuned @@ -309,7 +309,7 @@ class DPIEOperator: self.kernel = HelmholtzKernel(3) # specify a list of strings representing geometry objects - self.geom_list = geometry_list + self.geometry_list = geometry_list # create the characteristic functions that give a value of # 1 when we are on some surface/valume and a value of 0 otherwise @@ -326,7 +326,7 @@ class DPIEOperator: 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + np.dot(V_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2, + sym.integral(ambient_dim=3,dim=2,operand= sym.Dp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg")/self.k + 1j*sigma/2.0 - 1j*sym.Sp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") ) @@ -339,9 +339,9 @@ class DPIEOperator: """ # get the Q_array - Q_array = np.zeros((len(geometry_list),),dtype=sym.var) - for i in range(0,len(geometry_list)): - Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=geometry_list[i]) + Q_array = np.zeros((len(self.geometry_list),),dtype=sym.var) + for i in range(0,len(self.geometry_list)): + Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) # return the resulting field return sym.join_fields(-phi_inc, @@ -369,15 +369,13 @@ class DPIEOperator: - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") ) + np.dot(v_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2, - sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) - - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) - + 1j*( - self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) - - rho/2.0 - + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - ) - ) + sym.integral(ambient_dim=3,dim=2,operand=sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) + - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) + + 1j*( + self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + - rho/2.0 + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + ) + ) ) def A_rhs(self, A_inc): @@ -386,9 +384,9 @@ class DPIEOperator: """ # get the q_array - q_array = np.zeros((len(geometry_list),),dtype=sym.var) - for i in range(0,len(geometry_list)): - q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=geometry_list[i]) + q_array = np.zeros((len(self.geometry_list),),dtype=sym.var) + for i in range(0,len(self.geometry_list)): + q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) # define RHS for `A` integral equation system return sym.join_fields( @@ -403,8 +401,8 @@ class DPIEOperator: This method is a representation of the scalar potential, phi, based on the density `sigma`. """ - return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) - - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) + return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ + - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) def vector_potential_rep(self, a, rho, qbx_forced_limit=None): """ @@ -415,12 +413,12 @@ class DPIEOperator: n = sym.normal(len(a), None).as_vector() # define the vector potential representation - return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) - - self.k*sym.S(self,kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit) - + 1j*( - self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) - + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) - ) + return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ + - self.k*sym.S(self.kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit)\ + + 1j*( + self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) + + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) + ) def scattered_volume_field(self, sigma_soln, a_soln, rho_soln, qbx_forced_limit=None): diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 35874643..a36fb2ae 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -222,39 +222,67 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): extinction of the combined (incoming + scattered) field on the interior of the scatterer. """ + + # setup the basic config for logging logging.basicConfig(level=logging.INFO) + # setup the OpenCL context and queue cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) + # import or skip pyfmmlib pytest.importorskip("pyfmmlib") + # initialize the random seed np.random.seed(12) + # specify a dictionary with some useful arguments knl_kwargs = {"k": case.k} + # specify the list of geometry objects being used + geom_list = ["g0"] + # {{{ come up with a solution to Maxwell's equations j_sym = sym.make_sym_vector("j", 3) jt_sym = sym.make_sym_vector("jt", 2) rho_sym = sym.var("rho") + # specify some symbolic variables that will be used + # in the process to solve integral equations for the DPIE + rho_sym = sym.var("rho") + sigma_sym = sym.var("sigma") + a_sym = sym.make_sym_vector("a", 3) + V_sym = sym.make_sym_vector("V", len(geom_list)) + v_sym = sym.make_sym_vector("v", len(geom_list)) + + + # import some functionality from maxwell into this + # local scope environment from pytential.symbolic.pde.maxwell import ( DPIEOperator, get_sym_maxwell_point_source, get_sym_maxwell_plane_wave) - geom_list = [None] - dpie = DPIEOperator(geometry_list = geom_list) + # initialize the DPIE operator based on the geometry list + dpie = DPIEOperator(geometry_list=geom_list) + # get test source locations from the passed in case's queue test_source = case.get_source(queue) + # create the calculus patch and get calculus patch for targets calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) calc_patch_tgt = PointsTarget(cl.array.to_device(queue, calc_patch.points)) + # define a random number generator based on OpenCL rng = cl.clrandom.PhiloxGenerator(cl_ctx, seed=12) + + # use OpenCL random number generator to create a set of random + # source locations for various variables being solved for src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) + # define a local method that will evaluate some incident field + # at a set of target locations def eval_inc_field_at(tgt): if 0: # plane wave @@ -269,43 +297,58 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # point source return bind( (test_source, tgt), - get_sym_maxwell_point_source(mfie.kernel, j_sym, mfie.k) + get_sym_maxwell_point_source(dpie.kernel, j_sym, dpie.k) )(queue, j=src_j, k=case.k) + # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField( vector_from_device(queue, eval_inc_field_at(calc_patch_tgt))) + # compute residuals of incident field at source points source_maxwell_resids = [ calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_inc.e, np.inf) for x in frequency_domain_maxwell( calc_patch, pde_test_inc.e, pde_test_inc.h, case.k)] + + # make sure Maxwell residuals are small so we know the incident field + # properly satisfies the maxwell equations print("Source Maxwell residuals:", source_maxwell_resids) assert max(source_maxwell_resids) < 1e-6 # }}} - loc_sign = -1 if case.is_interior else +1 - from pytools.convergence import EOCRecorder + # {{{ Solve for the scattered field - eoc_rec_repr_maxwell = EOCRecorder() - eoc_pec_bc = EOCRecorder() - eoc_rec_e = EOCRecorder() - eoc_rec_h = EOCRecorder() + loc_sign = -1 if case.is_interior else +1 + # import a bunch of stuff that will be useful + from pytools.convergence import EOCRecorder from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory + InterpolatoryQuadratureSimplexGroupFactory from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + # setup an EOC Recorder + eoc_rec_repr_maxwell = EOCRecorder() + eoc_pec_bc = EOCRecorder() + eoc_rec_e = EOCRecorder() + eoc_rec_h = EOCRecorder() + + # loop through the case's resolutions and compute the scattered field solution for resolution in case.resolutions: - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization pre_scat_discr = Discretization( cl_ctx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # obtain the QBX layer potential source object qbx, _ = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, qbx_order=case.qbx_order, @@ -313,36 +356,48 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): case.fmm_tolerance), fmm_backend=case.fmm_backend ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + geom_map = {"g0": qbx} + + # get the maximum mesh element edge length h_max = qbx.h_max - scat_discr = qbx.density_discr - obs_discr = Discretization( - cl_ctx, observation_mesh, - InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + # define the scattered and observation discretization + scat_discr = qbx.density_discr + obs_discr = Discretization(cl_ctx, observation_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - inc_field_scat = EHField(eval_inc_field_at(scat_discr)) - inc_field_obs = EHField(eval_inc_field_at(obs_discr)) + # get the incident field at the scatter and observation locations + inc_field_scat = EHField(eval_inc_field_at(scat_discr)) + inc_field_obs = EHField(eval_inc_field_at(obs_discr)) - # {{{ system solve + # {{{ solve the system of integral equations inc_xyz_sym = EHField(sym.make_sym_vector("inc_fld", 6)) - bound_j_op = bind(qbx, mfie.j_operator(loc_sign, jt_sym)) - j_rhs = bind(qbx, mfie.j_rhs(inc_xyz_sym.h))( - queue, inc_fld=inc_field_scat.field, **knl_kwargs) + # setup operators that will be solved + phi_op = bind(geom_map,dpie.phi_operator(sigma=sigma_sym,V_array=V_sym)) + phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=)) + A_op = bind(geom_map,dpie.A_operator(a=a_sym,rho=rho_sym,v_array=v_sym)) + A_rhs = bind(geom_map,dpie.A_rhs(A_inc=A_inc_field)) + # set GMRES settings for solving gmres_settings = dict( tol=case.gmres_tol, progress=True, hard_failure=True, stall_iterations=50, no_progress_factor=1.05) + + # setup GMRES to solve vector equation from pytential.solve import gmres gmres_result = gmres( - bound_j_op.scipy_op(queue, "jt", np.complex128, **knl_kwargs), - j_rhs, **gmres_settings) + A_op.scipy_op(queue, "jt", np.complex128, **knl_kwargs), + A_rhs, **gmres_settings) jt = gmres_result.solution + # setup GMRES to solve scalar equation bound_rho_op = bind(qbx, mfie.rho_operator(loc_sign, rho_sym)) rho_rhs = bind(qbx, mfie.rho_rhs(jt_sym, inc_xyz_sym.e))( queue, jt=jt, inc_fld=inc_field_scat.field, **knl_kwargs) -- GitLab From 188f56ee2e0dda46299eba863fad5dcf3bb41752 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Fri, 22 Dec 2017 13:09:37 -0600 Subject: [PATCH 05/59] Add documentation for DPIE model. Includes summary of math formulation and where to find the model and test code. --- doc/dpie_doc.pdf | Bin 0 -> 169003 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/dpie_doc.pdf diff --git a/doc/dpie_doc.pdf b/doc/dpie_doc.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b52ac2b4db252f69a897ce3355d7126dad6bcb5c GIT binary patch literal 169003 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 z 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 literal 0 HcmV?d00001 -- GitLab From 83baa542ec43cc0705fb4ef4aa7def4d5449163c Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 08:06:55 -0600 Subject: [PATCH 06/59] Modified DPIE model to stuff all densities into one vector and modified the test code to incorporate this change. Still need to finish writing the actual volume evaluation code and code to generate the incident fields for the scalar and vector potential. --- pytential/symbolic/pde/maxwell/__init__.py | 40 ++++++++++++++---- test/test_maxwell_dpie.py | 49 ++++++++++------------ 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index f11d4cfa..59d0490b 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -317,11 +317,22 @@ class DPIEOperator: for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel,1,source=self.geom_list[idx]) + def numVectorPotentialDensities(): + return 3 + len(geometry_list) - def phi_operator(self,sigma,V_array): + def numScalarPotentialDensities(): + return 1 + len(geometry_list) + + def phi_operator(self,phi_densities): """ Integral Equation operator for obtaining scalar potential, `phi` """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[0] + V_array = phi_densities[1:] + + # produce integral equation system return sym.join_fields( 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") @@ -347,11 +358,16 @@ class DPIEOperator: return sym.join_fields(-phi_inc, Q_array/self.k) - def A_operator(self, a, rho, v_array): + def A_operator(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` """ + # extract the densities needed to solve the system of equations + rho = A_densities[0] + a = sym.tangential_to_xyz(A_densities[1:3]) + v_array = A_densities[3:] + # define the normal vector in symbolic form n = sym.normal(len(a), None).as_vector() @@ -396,19 +412,29 @@ class DPIEOperator: ) - def scalar_potential_rep(self, sigma, qbx_forced_limit=None): + def scalar_potential_rep(self, phi_densities, qbx_forced_limit=None): """ This method is a representation of the scalar potential, phi, based on the density `sigma`. """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[0] + + # evaluate scalar potential representation return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) - def vector_potential_rep(self, a, rho, qbx_forced_limit=None): + def vector_potential_rep(self, A_densities, qbx_forced_limit=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` """ + + # extract the densities needed to solve the system of equations + rho = A_densities[0] + a = sym.tangential_to_xyz(A_densities[1:3]) + # define the normal vector in symbolic form n = sym.normal(len(a), None).as_vector() @@ -421,7 +447,7 @@ class DPIEOperator: ) - def scattered_volume_field(self, sigma_soln, a_soln, rho_soln, qbx_forced_limit=None): + def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the @@ -433,8 +459,8 @@ class DPIEOperator: """ # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(a_soln,rho_soln,qbx_forced_limit=qbx_forced_limit) - phi = self.scalar_potential_rep(sigma_soln,qbx_forced_limit=qbx_forced_limit) + A = self.vector_potential_rep(A_densities,qbx_forced_limit=qbx_forced_limit) + phi = self.scalar_potential_rep(phi_densities,qbx_forced_limit=qbx_forced_limit) # evaluate the potential form for the electric and magnetic fields E_scat = 1j*self.k*A - sym.grad(3, phi) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index a36fb2ae..2c35c639 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -248,14 +248,6 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): jt_sym = sym.make_sym_vector("jt", 2) rho_sym = sym.var("rho") - # specify some symbolic variables that will be used - # in the process to solve integral equations for the DPIE - rho_sym = sym.var("rho") - sigma_sym = sym.var("sigma") - a_sym = sym.make_sym_vector("a", 3) - V_sym = sym.make_sym_vector("V", len(geom_list)) - v_sym = sym.make_sym_vector("v", len(geom_list)) - # import some functionality from maxwell into this # local scope environment @@ -267,6 +259,13 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # initialize the DPIE operator based on the geometry list dpie = DPIEOperator(geometry_list=geom_list) + + # specify some symbolic variables that will be used + # in the process to solve integral equations for the DPIE + phi_densities = sym.make_sym_vector("phi_densities", dpie.numScalarPotentialDensities()) + A_densities = sym.make_sym_vector("A_densities", dpie.numVectorPotentialDensities()) + + # get test source locations from the passed in case's queue test_source = case.get_source(queue) @@ -377,10 +376,10 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): inc_xyz_sym = EHField(sym.make_sym_vector("inc_fld", 6)) # setup operators that will be solved - phi_op = bind(geom_map,dpie.phi_operator(sigma=sigma_sym,V_array=V_sym)) + phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=)) - A_op = bind(geom_map,dpie.A_operator(a=a_sym,rho=rho_sym,v_array=v_sym)) - A_rhs = bind(geom_map,dpie.A_rhs(A_inc=A_inc_field)) + A_op = bind(geom_map,dpie.A_operator(A_densities=A_densities)) + A_rhs = bind(geom_map,dpie.A_rhs(A_inc=)) # set GMRES settings for solving gmres_settings = dict( @@ -389,32 +388,26 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): hard_failure=True, stall_iterations=50, no_progress_factor=1.05) - # setup GMRES to solve vector equation + # get the GMRES functionality from pytential.solve import gmres - gmres_result = gmres( - A_op.scipy_op(queue, "jt", np.complex128, **knl_kwargs), - A_rhs, **gmres_settings) - - jt = gmres_result.solution - - # setup GMRES to solve scalar equation - bound_rho_op = bind(qbx, mfie.rho_operator(loc_sign, rho_sym)) - rho_rhs = bind(qbx, mfie.rho_rhs(jt_sym, inc_xyz_sym.e))( - queue, jt=jt, inc_fld=inc_field_scat.field, **knl_kwargs) + # solve for the scalar potential densities gmres_result = gmres( - bound_rho_op.scipy_op(queue, "rho", np.complex128, **knl_kwargs), - rho_rhs, **gmres_settings) + phi_op.scipy_op(queue, "phi_densities", np.complex128, **knl_kwargs), + phi_rhs, **gmres_settings) + phi_dens = gmres_result.solution - rho = gmres_result.solution + # solve for the vector potential densities + gmres_result = gmres( + A_op.scipy_op(queue, "A_densities", np.complex128, **knl_kwargs), + A_rhs, **gmres_settings) + A_dens = gmres_result.solution # }}} - jxyz = bind(qbx, sym.tangential_to_xyz(jt_sym))(queue, jt=jt) - # {{{ volume eval - sym_repr = mfie.scattered_volume_field(jt_sym, rho_sym) + sym_repr = dpie.scattered_volume_field(phi_dens,A_dens) def eval_repr_at(tgt, source=None): if source is None: -- GitLab From 3590453a6fa4b3a5e99f29b58b9b0755c2509c13 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 12:05:10 -0600 Subject: [PATCH 07/59] Push minor comments modification to the primitive method --- pytential/symbolic/primitives.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index a0e229dd..2a5865cd 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -507,7 +507,15 @@ class NodeMax(NodalOperation): def integral(ambient_dim, dim, operand, where=None): - """A volume integral of *operand*.""" + ''' + Performs boundary integral on *operand*. + `ambient_dim` is the number of dimensions used to represent space while `dim` + is the dimensionality of the surface being integrated over. + + Example| + We wish to integrate over the 2-D surface of a sphere that resides in + in 3-dimensions, so `ambient_dim` = 3 and `dim` = 2. + ''' return NodeSum( area_element(ambient_dim, dim, where) -- GitLab From 4feec0740b70ffe0c7d22ef91884dcb847ee1047 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 13:47:01 -0600 Subject: [PATCH 08/59] I believe I completed the testing script for the DPIE. Need to run it on a linux computer to validate and refine it. --- pytential/symbolic/pde/maxwell/__init__.py | 29 ++++++++++-- test/test_maxwell_dpie.py | 52 +++++++++++++--------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 59d0490b..cf4528b4 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -67,6 +67,27 @@ def get_sym_maxwell_point_source(kernel, jxyz, k): # }}} +# {{{ point source for vector potential + +def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): + """Return a symbolic expression that, when bound to a + :class:`pytential.source.PointPotentialSource` will yield + a potential fields satisfying Maxwell's equations. + + Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. + + This will return an object of four entries, the first being the + scalar potential and the last three being the components of the + vector potential. + """ + field = get_sym_maxwell_point_source(kernel, jxyz, k) + return sym.join_fields( + 0*1j, # scalar potential + field[:3]/(1j*k) # vector potential + ) + +# }}} + # {{{ plane wave @@ -285,7 +306,7 @@ class MuellerAugmentedMFIEOperator(object): # {{{ Decoupled Potential Integral Equation Operator -class DPIEOperator: +class DPIEOperator(object): """ Decoupled Potential Integral Equation operator with PEC boundary conditions, defaults as scaled DPIE. @@ -313,7 +334,7 @@ class DPIEOperator: # create the characteristic functions that give a value of # 1 when we are on some surface/valume and a value of 0 otherwise - self.char_funcs = np.zeros((len(geometry_list),), dtype=sym.var) + self.char_funcs = sym.make_sym_vector("Q_array",len(self.geometry_list)) for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel,1,source=self.geom_list[idx]) @@ -350,7 +371,7 @@ class DPIEOperator: """ # get the Q_array - Q_array = np.zeros((len(self.geometry_list),),dtype=sym.var) + Q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) for i in range(0,len(self.geometry_list)): Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) @@ -400,7 +421,7 @@ class DPIEOperator: """ # get the q_array - q_array = np.zeros((len(self.geometry_list),),dtype=sym.var) + q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) for i in range(0,len(self.geometry_list)): q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 2c35c639..754f22f6 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -244,10 +244,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ come up with a solution to Maxwell's equations + # define symbolic variable for current density + # for use in defining incident field j_sym = sym.make_sym_vector("j", 3) - jt_sym = sym.make_sym_vector("jt", 2) - rho_sym = sym.var("rho") - # import some functionality from maxwell into this # local scope environment @@ -299,6 +298,12 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): get_sym_maxwell_point_source(dpie.kernel, j_sym, dpie.k) )(queue, j=src_j, k=case.k) + # method to get vector potential and scalar potential for incident + # E-M fields + def get_inc_potentials(tgt): + return bind((test_source, tgt), + get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) + # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField( vector_from_device(queue, eval_inc_field_at(calc_patch_tgt))) @@ -368,18 +373,20 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # get the incident field at the scatter and observation locations - inc_field_scat = EHField(eval_inc_field_at(scat_discr)) - inc_field_obs = EHField(eval_inc_field_at(obs_discr)) + inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) + inc_EM_field_obs = EHField(eval_inc_field_at(obs_discr)) + inc_vec_field_scat = get_inc_potentials(scat_discr) + inv_vec_field_obs = get_inc_potentials(obs_discr) # {{{ solve the system of integral equations - inc_xyz_sym = EHField(sym.make_sym_vector("inc_fld", 6)) + inc_xyz_vec_sym = sym.make_sym_vector("inc_vec_fld", 4) # setup operators that will be solved phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) - phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=)) + phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_xyz_vec_sym[0]))(queue,inc_vec_fld=inc_vec_field_scat,**knl_kwargs) A_op = bind(geom_map,dpie.A_operator(A_densities=A_densities)) - A_rhs = bind(geom_map,dpie.A_rhs(A_inc=)) + A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_xyz_vec_sym[1:]))(queue,inc_vec_fld=inc_vec_field_scat,**knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -403,17 +410,20 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): A_rhs, **gmres_settings) A_dens = gmres_result.solution + # extract useful solutions + phi = bind(qbx, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) + Axyz = bind(qbx, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + # }}} # {{{ volume eval - sym_repr = dpie.scattered_volume_field(phi_dens,A_dens) + sym_repr = dpie.scattered_volume_field(phi_densities,A_densities) def eval_repr_at(tgt, source=None): if source is None: source = qbx - - return bind((source, tgt), sym_repr)(queue, jt=jt, rho=rho, **knl_kwargs) + return bind((source, tgt), sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, **knl_kwargs) pde_test_repr = EHField( vector_from_device(queue, eval_repr_at(calc_patch_tgt))) @@ -430,20 +440,20 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ check PEC BC on total field - bc_repr = EHField(mfie.scattered_volume_field( - jt_sym, rho_sym, qbx_forced_limit=loc_sign)) + bc_repr = EHField(dpie.scattered_volume_field( + phi_densities, A_densities, qbx_forced_limit=loc_sign)) pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) - pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) + pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + sym.curl(inc_xyz_vec_sym[1:])) eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( - queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, + queue, phi_densities=phi_dens, A_densities=A_dens, inc_vec_fld=inc_field_vec_scat, **knl_kwargs) def scat_norm(f): return norm(qbx, queue, f, p=np.inf) - e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) - h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) + e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_EM_field_scat.e) + h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_EM_field_scat.h) print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) @@ -461,10 +471,10 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): .as_vector(dtype=object) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ - ("j", jxyz), - ("rho", rho), - ("Einc", inc_field_scat.e), - ("Hinc", inc_field_scat.h), + ("phi", phi), + ("Axyz", Axyz), + ("Einc", inc_EM_field_scat.e), + ("Hinc", inc_EM_field_scat.h), ("bdry_normals", bdry_normals), ("e_bc_residual", eh_bc_values[:3]), ("h_bc_residual", eh_bc_values[3]), -- GitLab From 72b5e50a215d419b99f0f4163303f58f6cbf4b82 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 15:19:19 -0600 Subject: [PATCH 09/59] Issues importing DPIE Operator for some reason.. trying something to see if it works. --- test/test_maxwell_dpie.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 754f22f6..3cb70ebc 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -250,10 +250,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # import some functionality from maxwell into this # local scope environment - from pytential.symbolic.pde.maxwell import ( - DPIEOperator, - get_sym_maxwell_point_source, - get_sym_maxwell_plane_wave) + from pytential.symbolic.pde.maxwell import (DPIEOperator,get_sym_maxwell_point_source,get_sym_maxwell_plane_wave) # initialize the DPIE operator based on the geometry list dpie = DPIEOperator(geometry_list=geom_list) -- GitLab From f007b8524f6b240b3573e63020c9e8fdd4e0d0cb Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 15:20:50 -0600 Subject: [PATCH 10/59] Issues importing DPIE Operator for some reason.. trying something to see if it works. #2 --- pytential/symbolic/pde/maxwell/__init__.py | 2 +- test/test_maxwell_dpie.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index cf4528b4..41cd431b 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -306,7 +306,7 @@ class MuellerAugmentedMFIEOperator(object): # {{{ Decoupled Potential Integral Equation Operator -class DPIEOperator(object): +class DPIEOperator: """ Decoupled Potential Integral Equation operator with PEC boundary conditions, defaults as scaled DPIE. diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 3cb70ebc..5d3de257 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -250,7 +250,10 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # import some functionality from maxwell into this # local scope environment - from pytential.symbolic.pde.maxwell import (DPIEOperator,get_sym_maxwell_point_source,get_sym_maxwell_plane_wave) + from pytential.symbolic.pde.maxwell import ( + DPIEOperator, + get_sym_maxwell_point_source, + get_sym_maxwell_plane_wave) # initialize the DPIE operator based on the geometry list dpie = DPIEOperator(geometry_list=geom_list) -- GitLab From d1718cee1d8ec16061c063061084e30e5b6f5925 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 16:07:27 -0600 Subject: [PATCH 11/59] Found minor errors that needed to be fixed while debugging with test code. --- pytential/symbolic/pde/maxwell/__init__.py | 10 +++++----- test/test_maxwell_dpie.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 41cd431b..88c6f658 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -336,13 +336,13 @@ class DPIEOperator: # 1 when we are on some surface/valume and a value of 0 otherwise self.char_funcs = sym.make_sym_vector("Q_array",len(self.geometry_list)) for idx in range(0,len(geometry_list)): - self.char_funcs[idx] = sym.D(self.kernel,1,source=self.geom_list[idx]) + self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) - def numVectorPotentialDensities(): - return 3 + len(geometry_list) + def numVectorPotentialDensities(self): + return 3 + len(self.geometry_list) - def numScalarPotentialDensities(): - return 1 + len(geometry_list) + def numScalarPotentialDensities(self): + return 1 + len(self.geometry_list) def phi_operator(self,phi_densities): """ diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 5d3de257..861a205b 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -253,6 +253,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): from pytential.symbolic.pde.maxwell import ( DPIEOperator, get_sym_maxwell_point_source, + get_sym_maxwell_point_source_potentials, get_sym_maxwell_plane_wave) # initialize the DPIE operator based on the geometry list -- GitLab From e41b249a28238b0fd34b152f1967904fbd7969e7 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sun, 24 Dec 2017 17:21:57 -0600 Subject: [PATCH 12/59] Modified geometry stuff in test code because I seem to misunderstand how to use a map to connect the QBX geometry with the symbolic names using the dictionary. --- test/test_maxwell_dpie.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 861a205b..e1c1b3c9 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -240,7 +240,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): knl_kwargs = {"k": case.k} # specify the list of geometry objects being used - geom_list = ["g0"] + geom_list = [None] # {{{ come up with a solution to Maxwell's equations @@ -363,7 +363,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): ).with_refinement(_expansion_disturbance_tolerance=0.05) # define the geometry dictionary - geom_map = {"g0": qbx} + #geom_map = {"g0": qbx} + geom_map = qbx # get the maximum mesh element edge length h_max = qbx.h_max -- GitLab From 9d5cc57ae0a431725acf9086a8aed7fece54c7a4 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 24 Jan 2018 11:53:38 -0600 Subject: [PATCH 13/59] Separated out the DPIE code since was getting some weird import issues. Still have the issues, but will commit this separation anyway since it should be independent of the code inside this new file. --- pytential/symbolic/pde/maxwell/__init__.py | 210 -------------- pytential/symbolic/pde/maxwell/dpie.py | 323 +++++++++++++++++++++ test/test_maxwell_dpie.py | 13 +- 3 files changed, 328 insertions(+), 218 deletions(-) create mode 100644 pytential/symbolic/pde/maxwell/dpie.py diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 88c6f658..d734ad30 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -67,27 +67,6 @@ def get_sym_maxwell_point_source(kernel, jxyz, k): # }}} -# {{{ point source for vector potential - -def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): - """Return a symbolic expression that, when bound to a - :class:`pytential.source.PointPotentialSource` will yield - a potential fields satisfying Maxwell's equations. - - Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. - - This will return an object of four entries, the first being the - scalar potential and the last three being the components of the - vector potential. - """ - field = get_sym_maxwell_point_source(kernel, jxyz, k) - return sym.join_fields( - 0*1j, # scalar potential - field[:3]/(1j*k) # vector potential - ) - -# }}} - # {{{ plane wave @@ -303,193 +282,4 @@ class MuellerAugmentedMFIEOperator(object): # }}} - - -# {{{ Decoupled Potential Integral Equation Operator -class DPIEOperator: - """ - Decoupled Potential Integral Equation operator with PEC boundary - conditions, defaults as scaled DPIE. - - See https://arxiv.org/abs/1404.0749 for derivation. - - Uses E(x,t) = Re{E(x) exp(-i omega t)} and H(x,t) = Re{H(x) exp(-i omega t)} - and solves for the E(x), H(x) fields using vector and scalar potentials via - the Lorenz Gauage. The DPIE formulates the problem purely in terms of the - vector and scalar potentials, A and phi, and then backs out E(x) and H(x) - via relationships to the vector and scalar potentials. - """ - - def __init__(self, geometry_list, k=sym.var("k")): - from sumpy.kernel import HelmholtzKernel - - # specify the frequency variable that will be tuned - self.k = k - - # specify the 3-D Helmholtz kernel - self.kernel = HelmholtzKernel(3) - - # specify a list of strings representing geometry objects - self.geometry_list = geometry_list - - # create the characteristic functions that give a value of - # 1 when we are on some surface/valume and a value of 0 otherwise - self.char_funcs = sym.make_sym_vector("Q_array",len(self.geometry_list)) - for idx in range(0,len(geometry_list)): - self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) - - def numVectorPotentialDensities(self): - return 3 + len(self.geometry_list) - - def numScalarPotentialDensities(self): - return 1 + len(self.geometry_list) - - def phi_operator(self,phi_densities): - """ - Integral Equation operator for obtaining scalar potential, `phi` - """ - - # extract the densities needed to solve the system of equations - sigma = phi_densities[0] - V_array = phi_densities[1:] - - # produce integral equation system - return sym.join_fields( - 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - + np.dot(V_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2,operand= - sym.Dp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg")/self.k - + 1j*sigma/2.0 - 1j*sym.Sp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - ) - ) - - - def phi_rhs(self, phi_inc): - """ - The Right-Hand-Side for the Integral Equation for `phi` - """ - - # get the Q_array - Q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) - for i in range(0,len(self.geometry_list)): - Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) - - # return the resulting field - return sym.join_fields(-phi_inc, - Q_array/self.k) - - def A_operator(self, A_densities): - """ - Integral Equation operator for obtaining vector potential, `A` - """ - - # extract the densities needed to solve the system of equations - rho = A_densities[0] - a = sym.tangential_to_xyz(A_densities[1:3]) - v_array = A_densities[3:] - - # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() - - # define system of integral equations for A - return sym.join_fields( - 0.5*a + sym.n_cross(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg")) - -self.k * sym.n_cross(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) - + 1j*( - self.k*sym.n_cross(sym.cross(sym.S(self.kernel,n,k=self.k,qbx_forced_limit="avg"),a)) - + sym.n_cross(sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg"))) - ), - 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - + 1j*( - sym.div(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) - - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - ) - + np.dot(v_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2,operand=sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) - - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) - + 1j*( - self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) - - rho/2.0 + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - ) - ) - ) - - def A_rhs(self, A_inc): - """ - The Right-Hand-Side for the Integral Equation for `A` - """ - - # get the q_array - q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) - for i in range(0,len(self.geometry_list)): - q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) - - # define RHS for `A` integral equation system - return sym.join_fields( - -sym.n_cross(A_inc), - -sym.div(A_inc)/self.k, - q_array - ) - - - def scalar_potential_rep(self, phi_densities, qbx_forced_limit=None): - """ - This method is a representation of the scalar potential, phi, - based on the density `sigma`. - """ - - # extract the densities needed to solve the system of equations - sigma = phi_densities[0] - - # evaluate scalar potential representation - return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) - - def vector_potential_rep(self, A_densities, qbx_forced_limit=None): - """ - This method is a representation of the vector potential, phi, - based on the vector density `a` and scalar density `rho` - """ - - # extract the densities needed to solve the system of equations - rho = A_densities[0] - a = sym.tangential_to_xyz(A_densities[1:3]) - - # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() - - # define the vector potential representation - return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ - - self.k*sym.S(self.kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - + 1j*( - self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) - + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) - ) - - - def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): - """ - 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`. - """ - - # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(A_densities,qbx_forced_limit=qbx_forced_limit) - phi = self.scalar_potential_rep(phi_densities,qbx_forced_limit=qbx_forced_limit) - - # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, phi) - H_scat = sym.curl(A) - - # join the fields into a vector - return sym.join_fields(E_scat, H_scat) - -# }}} - # vim: foldmethod=marker diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py new file mode 100644 index 00000000..2c2cc03b --- /dev/null +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -0,0 +1,323 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2010-2013 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np # noqa +from pytential import sym +from collections import namedtuple +from functools import partial + +tangential_to_xyz = sym.tangential_to_xyz +xyz_to_tangential = sym.xyz_to_tangential +cse = sym.cse + +__doc__ = """ + +.. autofunction:: get_sym_maxwell_point_source +.. autofunction:: get_sym_maxwell_point_source_potentials +.. autofunction:: get_sym_maxwell_plane_wave +.. autoclass:: DPIEOperator +""" + + +# {{{ point source + +def get_sym_maxwell_point_source(kernel, jxyz, k): + """Return a symbolic expression that, when bound to a + :class:`pytential.source.PointPotentialSource` will yield + a field satisfying Maxwell's equations. + + 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`. + """ + # This ensures div A = 0, which is simply a consequence of div curl S=0. + # This means we use the Coulomb gauge to generate this field. + + A = sym.curl(sym.S(kernel, jxyz, k=k, qbx_forced_limit=None)) + + # https://en.wikipedia.org/w/index.php?title=Maxwell%27s_equations&oldid=798940325#Alternative_formulations + # (Vector calculus/Potentials/Any Gauge) + # assumed time dependence exp(-1j*omega*t) + return sym.join_fields( + 1j*k*A, + sym.curl(A)) + +# }}} + +# {{{ point source for vector potential + +def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): + """Return a symbolic expression that, when bound to a + :class:`pytential.source.PointPotentialSource` will yield + a potential fields satisfying Maxwell's equations. + + Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. + + This will return an object of four entries, the first being the + scalar potential and the last three being the components of the + vector potential. + """ + field = get_sym_maxwell_point_source(kernel, jxyz, k) + return sym.join_fields( + 0*1j, # scalar potential + field[:3]/(1j*k) # vector potential + ) + +# }}} + + +# {{{ plane wave + +def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=None): + """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)) + +# }}} + + + +# {{{ Decoupled Potential Integral Equation Operator +class DPIEOperator: + """ + Decoupled Potential Integral Equation operator with PEC boundary + conditions, defaults as scaled DPIE. + + See https://arxiv.org/abs/1404.0749 for derivation. + + Uses E(x,t) = Re{E(x) exp(-i omega t)} and H(x,t) = Re{H(x) exp(-i omega t)} + and solves for the E(x), H(x) fields using vector and scalar potentials via + the Lorenz Gauage. The DPIE formulates the problem purely in terms of the + vector and scalar potentials, A and phi, and then backs out E(x) and H(x) + via relationships to the vector and scalar potentials. + """ + + def __init__(self, geometry_list, k=sym.var("k")): + from sumpy.kernel import HelmholtzKernel + + # specify the frequency variable that will be tuned + self.k = k + + # specify the 3-D Helmholtz kernel + self.kernel = HelmholtzKernel(3) + + # specify a list of strings representing geometry objects + self.geometry_list = geometry_list + + # create the characteristic functions that give a value of + # 1 when we are on some surface/valume and a value of 0 otherwise + self.char_funcs = sym.make_sym_vector("Q_array",len(self.geometry_list)) + for idx in range(0,len(geometry_list)): + self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) + + def numVectorPotentialDensities(self): + return 3 + len(self.geometry_list) + + def numScalarPotentialDensities(self): + return 1 + len(self.geometry_list) + + def phi_operator(self,phi_densities): + """ + Integral Equation operator for obtaining scalar potential, `phi` + """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[0] + V_array = phi_densities[1:] + + # produce integral equation system + return sym.join_fields( + 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + + np.dot(V_array,self.char_funcs), + sym.integral(ambient_dim=3,dim=2,operand= + sym.Dp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg")/self.k + + 1j*sigma/2.0 - 1j*sym.Sp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") + ) + ) + + + def phi_rhs(self, phi_inc): + """ + The Right-Hand-Side for the Integral Equation for `phi` + """ + + # get the Q_array + Q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) + for i in range(0,len(self.geometry_list)): + Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) + + # return the resulting field + return sym.join_fields(-phi_inc, + Q_array/self.k) + + def A_operator(self, A_densities): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # extract the densities needed to solve the system of equations + rho = A_densities[0] + a = sym.tangential_to_xyz(A_densities[1:3]) + v_array = A_densities[3:] + + # define the normal vector in symbolic form + n = sym.normal(len(a), None).as_vector() + + # define system of integral equations for A + return sym.join_fields( + 0.5*a + sym.n_cross(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg")) + -self.k * sym.n_cross(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) + + 1j*( + self.k*sym.n_cross(sym.cross(sym.S(self.kernel,n,k=self.k,qbx_forced_limit="avg"),a)) + + sym.n_cross(sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg"))) + ), + 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + + 1j*( + sym.div(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + ) + + np.dot(v_array,self.char_funcs), + sym.integral(ambient_dim=3,dim=2,operand=sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) + - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) + + 1j*( + self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + - rho/2.0 + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + ) + ) + ) + + def A_rhs(self, A_inc): + """ + The Right-Hand-Side for the Integral Equation for `A` + """ + + # get the q_array + q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) + for i in range(0,len(self.geometry_list)): + q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) + + # define RHS for `A` integral equation system + return sym.join_fields( + -sym.n_cross(A_inc), + -sym.div(A_inc)/self.k, + q_array + ) + + + def scalar_potential_rep(self, phi_densities, qbx_forced_limit=None): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[0] + + # evaluate scalar potential representation + return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ + - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) + + def vector_potential_rep(self, A_densities, qbx_forced_limit=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # extract the densities needed to solve the system of equations + rho = A_densities[0] + a = sym.tangential_to_xyz(A_densities[1:3]) + + # define the normal vector in symbolic form + n = sym.normal(len(a), None).as_vector() + + # define the vector potential representation + return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ + - self.k*sym.S(self.kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit)\ + + 1j*( + self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) + + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) + ) + + + def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): + """ + 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`. + """ + + # obtain expressions for scalar and vector potentials + A = self.vector_potential_rep(A_densities,qbx_forced_limit=qbx_forced_limit) + phi = self.scalar_potential_rep(phi_densities,qbx_forced_limit=qbx_forced_limit) + + # evaluate the potential form for the electric and magnetic fields + E_scat = 1j*self.k*A - sym.grad(3, phi) + H_scat = sym.curl(A) + + # join the fields into a vector + return sym.join_fields(E_scat, H_scat) + +# }}} diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index e1c1b3c9..4b85109a 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -250,12 +250,10 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # import some functionality from maxwell into this # local scope environment - from pytential.symbolic.pde.maxwell import ( - DPIEOperator, - get_sym_maxwell_point_source, - get_sym_maxwell_point_source_potentials, - get_sym_maxwell_plane_wave) - + #from pytential.symbolic.pde.maxwell import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) + #from pytential.symbolic.pde.maxwell.dpie import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) + import pytential.symbolic.pde.maxwell.dpie as mw + # initialize the DPIE operator based on the geometry list dpie = DPIEOperator(geometry_list=geom_list) @@ -302,8 +300,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # method to get vector potential and scalar potential for incident # E-M fields def get_inc_potentials(tgt): - return bind((test_source, tgt), - get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) + return bind((test_source, tgt),get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField( -- GitLab From ff4d39c80d8088a421351c4bf95ef934c92e65b0 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 24 Jan 2018 12:58:13 -0600 Subject: [PATCH 14/59] Working on sorting out issues with the symbolic differential operators. Hardset the grad(phi) term to 0 and currently looking into issues with the A_operator --- pytential/symbolic/pde/maxwell/dpie.py | 5 +++-- test/test_maxwell_dpie.py | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 2c2cc03b..acfc4fc4 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -164,7 +164,7 @@ class DPIEOperator: # create the characteristic functions that give a value of # 1 when we are on some surface/valume and a value of 0 otherwise - self.char_funcs = sym.make_sym_vector("Q_array",len(self.geometry_list)) + self.char_funcs = sym.make_sym_vector("chi",len(self.geometry_list)) for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) @@ -203,7 +203,8 @@ class DPIEOperator: # get the Q_array Q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) for i in range(0,len(self.geometry_list)): - Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) + #Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) + Q_array[i] = 0 # return the resulting field return sym.join_fields(-phi_inc, diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 4b85109a..6c71c333 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -255,7 +255,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): import pytential.symbolic.pde.maxwell.dpie as mw # initialize the DPIE operator based on the geometry list - dpie = DPIEOperator(geometry_list=geom_list) + dpie = mw.DPIEOperator(geometry_list=geom_list) # specify some symbolic variables that will be used @@ -285,7 +285,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # plane wave return bind( tgt, - get_sym_maxwell_plane_wave( + mw.get_sym_maxwell_plane_wave( amplitude_vec=np.array([1, 1, 1]), v=np.array([1, 0, 0]), omega=case.k) @@ -294,13 +294,13 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # point source return bind( (test_source, tgt), - get_sym_maxwell_point_source(dpie.kernel, j_sym, dpie.k) + mw.get_sym_maxwell_point_source(dpie.kernel, j_sym, dpie.k) )(queue, j=src_j, k=case.k) # method to get vector potential and scalar potential for incident # E-M fields def get_inc_potentials(tgt): - return bind((test_source, tgt),get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) + return bind((test_source, tgt),mw.get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField( @@ -378,7 +378,6 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): inv_vec_field_obs = get_inc_potentials(obs_discr) # {{{ solve the system of integral equations - inc_xyz_vec_sym = sym.make_sym_vector("inc_vec_fld", 4) # setup operators that will be solved -- GitLab From 6ab61a78f4f67746843a48bceaf59d5002df8ab7 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 7 Feb 2018 15:18:24 -0600 Subject: [PATCH 15/59] Made modifications to rho and A operators to allow for incorporating the various disjoint objects. Still finishing up the A operator and need to think about how to properly evaluate incident waves on the various disjoint surfaces using existing codebase for both the A_rhs and rho_rhs. Also modified inputs to A_rhs and rho_rhs so that gradient/divergence operations on incident potential fields are specified independently from the potential field itself. --- pytential/symbolic/pde/maxwell/dpie.py | 182 ++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 22 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index acfc4fc4..534f0ff0 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -155,12 +155,14 @@ class DPIEOperator: # specify the frequency variable that will be tuned self.k = k + self.stype = type(self.k) # specify the 3-D Helmholtz kernel self.kernel = HelmholtzKernel(3) # specify a list of strings representing geometry objects self.geometry_list = geometry_list + self.nobjs = len(geometry_list) # create the characteristic functions that give a value of # 1 when we are on some surface/valume and a value of 0 otherwise @@ -169,12 +171,98 @@ class DPIEOperator: self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) def numVectorPotentialDensities(self): - return 3 + len(self.geometry_list) + return 4*len(self.geometry_list) def numScalarPotentialDensities(self): - return 1 + len(self.geometry_list) + return 2*len(self.geometry_list) - def phi_operator(self,phi_densities): + def D(self, density_vec, target): + """ + Double layer potential operator across multiple disjoint objects + """ + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # init output symbolic quantity with zeros + output = np.zeros((ndim,), dtype=type(density)) + + # compute individual double layer potential evaluations at the given + # density across all the disjoint objects + for i in range(0,nobj): + output = output + sym.D(self.kernel, density_vec[:,i], + k=self.k,qbx_forced_limit="avg", + source=self.geometry_list[i],target=target) + + # return the output summation + return output + + def S(self, density_vec, target): + """ + Double layer potential operator across multiple disjoint objects + """ + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # init output symbolic quantity with zeros + output = np.zeros((ndim,), dtype=self.stype) + + # compute individual double layer potential evaluations at the given + # density across all the disjoint objects + for i in range(0,nobj): + output = output + sym.S(self.kernel, density_vec[:,i], + k=self.k, qbx_forced_limit="avg", + source=self.geometry_list[i], target=target) + + # return the output summation + return output + + + def Dp(self, density_vec, target): + """ + D' layer potential operator across multiple disjoint objects + """ + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # init output symbolic quantity with zeros + output = np.zeros((ndim,), dtype=self.stype) + + # compute individual double layer potential evaluations at the given + # density across all the disjoint objects + for i in range(0,nobj): + output = output + sym.Dp(self.kernel, density_vec[:,i], + k=self.k,qbx_forced_limit="avg", + source=self.geometry_list[i],target=target) + + # return the output summation + return output + + def Sp(self, density_vec, target): + """ + S' layer potential operator across multiple disjoint objects + """ + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # init output symbolic quantity with zeros + output = np.zeros((ndim,), dtype=self.stype) + + # compute individual double layer potential evaluations at the given + # density across all the disjoint objects + for i in range(0,nobj): + output = output + sym.Sp(self.kernel, density_vec[:,i], + k=self.k, qbx_forced_limit="avg", + source=self.geometry_list[i], target=target) + + # return the output summation + return output + + + def phi_operator0(self, phi_densities): """ Integral Equation operator for obtaining scalar potential, `phi` """ @@ -194,23 +282,50 @@ class DPIEOperator: ) ) + def phi_operator(self, phi_densities): + """ + Integral Equation operator for obtaining scalar potential, `phi` + """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[:self.nobjs] + sigma_m = sigma.reshape((1,self.nobjs)) + V = phi_densities[self.nobjs:] + + # init output matvec vector for the phi density IE + output = np.zeros((2*self.nobjs,), dtype=self.stype) - def phi_rhs(self, phi_inc): + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + # get nth disjoint object + obj_n = self.geometry_list[n] + + # setup IE for evaluation over the nth disjoint object's surface + output[n] = 0.5*sigma[n] + self.D(sigma_m,obj_n) - 1j*self.k*self.S(sigma_m,obj_n) - V[n] + + # setup equation that integrates some integral operators over the nth surface + output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, + operand=(self.Dp(sigma_m,target=None)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=None))) + + # return the resulting system of IE + return output + + + def phi_rhs(self, phi_inc, gradphi_inc): """ The Right-Hand-Side for the Integral Equation for `phi` """ - # get the Q_array - Q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) - for i in range(0,len(self.geometry_list)): - #Q_array[i] = -sym.integral(3,2,sym.n_dot(sym.grad(3,phi_inc)),where=self.geometry_list[i]) - Q_array[i] = 0 + # get the Q_{j} terms inside RHS expression + Q = np.zeros((self.nobjs,), dtype=self.stype) + for i in range(0,self.nobjs): + Q[i] = -sym.integral(3,2,sym.n_dot(gradphi_inc),where=self.geometry_list[i]) # return the resulting field - return sym.join_fields(-phi_inc, - Q_array/self.k) + return sym.join_fields(-phi_inc, Q/self.k) - def A_operator(self, A_densities): + def A_operator0(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` """ @@ -222,6 +337,7 @@ class DPIEOperator: # define the normal vector in symbolic form n = sym.normal(len(a), None).as_vector() + r = sym.n_cross(a) # define system of integral equations for A return sym.join_fields( @@ -233,7 +349,9 @@ class DPIEOperator: ), 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") + 1j*( - sym.div(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) + sym.d_dx(3,sym.S(self.kernel,r[0],k=self.k,qbx_forced_limit="avg")) + + sym.d_dy(3,sym.S(self.kernel,r[1],k=self.k,qbx_forced_limit="avg")) + + sym.d_dz(3,sym.S(self.kernel,r[2],k=self.k,qbx_forced_limit="avg")) - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") ) + np.dot(v_array,self.char_funcs), @@ -246,22 +364,42 @@ class DPIEOperator: ) ) - def A_rhs(self, A_inc): + def A_operator(self, A_densities): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # extract the densities needed to solve the system of equations + v = A_densities[:self.nobjs] + rho = A_densities[self.nobjs:(2*self.nobjs)] + rho_m = rho.resize((1,self.nobjs)) + a_loc = A_densities[2*self.nobjs:] + a = np.zeros((3,self.nobjs),dtype=self.stype) + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # define the normal vector in symbolic form + n = sym.normal(len(a), None).as_vector() + r = sym.n_cross(a) + + # init output matvec vector for the phi density IE + output = np.zeros((5*self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + def A_rhs(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` """ # get the q_array - q_array = sym.make_sym_vector("Q_array",len(self.geometry_list)) - for i in range(0,len(self.geometry_list)): - q_array[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) + q = np.zeros((self.nobjs,), dtype=self.stype) + for i in range(0,self.nobjs): + q[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) # define RHS for `A` integral equation system - return sym.join_fields( - -sym.n_cross(A_inc), - -sym.div(A_inc)/self.k, - q_array - ) + return sym.join_fields( -sym.n_cross(A_inc), -divA_inc/self.k, q) def scalar_potential_rep(self, phi_densities, qbx_forced_limit=None): -- GitLab From 142be7edfd570fca4ea49f65c93355f3cf7157a9 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 14 Feb 2018 11:19:26 -0600 Subject: [PATCH 16/59] Finished main first iteration for the integral operators for the various vector and scalar potential stuff in the DPIE. Still need to check that the normals being thrown into the integral operators correspond to the correct problem. Additionally, may need to implement custom differential operators that can handled multiple densities at once. --- pytential/symbolic/pde/maxwell/dpie.py | 95 ++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 534f0ff0..cdda91a1 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -176,7 +176,7 @@ class DPIEOperator: def numScalarPotentialDensities(self): return 2*len(self.geometry_list) - def D(self, density_vec, target): + def D(self, density_vec, target=None): """ Double layer potential operator across multiple disjoint objects """ @@ -197,7 +197,7 @@ class DPIEOperator: # return the output summation return output - def S(self, density_vec, target): + def S(self, density_vec, target=None): """ Double layer potential operator across multiple disjoint objects """ @@ -219,7 +219,7 @@ class DPIEOperator: return output - def Dp(self, density_vec, target): + def Dp(self, density_vec, target=None): """ D' layer potential operator across multiple disjoint objects """ @@ -240,7 +240,7 @@ class DPIEOperator: # return the output summation return output - def Sp(self, density_vec, target): + def Sp(self, density_vec, target=None): """ S' layer potential operator across multiple disjoint objects """ @@ -265,6 +265,7 @@ class DPIEOperator: def phi_operator0(self, phi_densities): """ Integral Equation operator for obtaining scalar potential, `phi` + Old Operator without looking into multiple bodies """ # extract the densities needed to solve the system of equations @@ -282,7 +283,7 @@ class DPIEOperator: ) ) - def phi_operator(self, phi_densities): + def phi_operator(self, phi_densities, V): """ Integral Equation operator for obtaining scalar potential, `phi` """ @@ -290,7 +291,6 @@ class DPIEOperator: # extract the densities needed to solve the system of equations sigma = phi_densities[:self.nobjs] sigma_m = sigma.reshape((1,self.nobjs)) - V = phi_densities[self.nobjs:] # init output matvec vector for the phi density IE output = np.zeros((2*self.nobjs,), dtype=self.stype) @@ -306,7 +306,8 @@ class DPIEOperator: # setup equation that integrates some integral operators over the nth surface output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, - operand=(self.Dp(sigma_m,target=None)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=None))) + operand=(self.Dp(sigma_m,target=None)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=None)),\ + where=obj_n) # return the resulting system of IE return output @@ -328,6 +329,7 @@ class DPIEOperator: def A_operator0(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` + Old Operator without looking into multiple bodies """ # extract the densities needed to solve the system of equations @@ -364,16 +366,15 @@ class DPIEOperator: ) ) - def A_operator(self, A_densities): + def A_operator(self, A_densities, v): """ Integral Equation operator for obtaining vector potential, `A` """ # extract the densities needed to solve the system of equations - v = A_densities[:self.nobjs] - rho = A_densities[self.nobjs:(2*self.nobjs)] + rho = A_densities[:self.nobjs] rho_m = rho.resize((1,self.nobjs)) - a_loc = A_densities[2*self.nobjs:] + a_loc = A_densities[self.nobjs:] a = np.zeros((3,self.nobjs),dtype=self.stype) for n in range(0,self.nobjs): a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) @@ -388,6 +389,35 @@ class DPIEOperator: # produce integral equation system over each disjoint object for n in range(0,self.nobjs): + # get the nth target geometry to have IE solved across + target_loc = self.geometry_list[n] + + # generate the set of equations for the vector densities, a, coupled + # across the various geometries involved + output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,target_loc),where=target_loc) \ + + -self.k * sym.n_cross(self.S(n*rho_m,target_loc),where=target_loc) \ + + 1j*( self.k* sym.n_cross(self.S(sym.n_cross(a),target_loc),where=target_loc) \ + sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,target_loc)),where=target_loc) + ) + + # generate the set of equations for the scalar densities, rho, coupled + # across the various geometries involved + output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,target_loc) \ + + 1j*( sym.div(self.S(sym.n_cross(a,where=target_loc))) \ + -self.k*self.S(rho_m) + )\ + + v[n] + + # add the equation that integrates everything out into some constant + output[(4*self.nobjs + n)] = sym.integral(ambient_dim=3,dim=2,\ + operand=(sym.n_dot(sym.curl(self.S(a))) - self.k*sym.n_dot(self.S(n*rho_m)) + \ + 1j*(self.k*sym.n_dot(sym.n_cross(a)) - 0.5*rho[n] + self.Sp(rho_m))),\ + where=target_loc) + + # return output equations + return output + + def A_rhs(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` @@ -402,10 +432,11 @@ class DPIEOperator: return sym.join_fields( -sym.n_cross(A_inc), -divA_inc/self.k, q) - def scalar_potential_rep(self, phi_densities, qbx_forced_limit=None): + def scalar_potential_rep0(self, phi_densities, qbx_forced_limit=None): """ This method is a representation of the scalar potential, phi, based on the density `sigma`. + Old representation """ # extract the densities needed to solve the system of equations @@ -415,7 +446,20 @@ class DPIEOperator: return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) - def vector_potential_rep(self, A_densities, qbx_forced_limit=None): + def scalar_potential_rep(self, phi_densities, target=None): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[:self.nobjs] + sigma_m = sigma.reshape((1,self.nobjs)) + + # evaluate scalar potential representation + return self.D(sigma_m,target) - 1j*self.k*self.S(sigma_m,target) + + def vector_potential_rep0(self, A_densities, qbx_forced_limit=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` @@ -436,6 +480,27 @@ class DPIEOperator: + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) ) + def vector_potential_rep(self, A_densities, target=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # extract the densities needed to solve the system of equations + rho = A_densities[:self.nobjs] + rho_m = rho.resize((1,self.nobjs)) + a_loc = A_densities[self.nobjs:] + a = np.zeros((3,self.nobjs),dtype=self.stype) + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # define the normal vector in symbolic form + n = sym.normal(len(a), None).as_vector() + + # define the vector potential representation + return sym.curl(self.S(a,target)) - self.k*self.S(n*rho_m,target) \ + + 1j*(self.k*self.S(sym.n_cross(a),target) + sym.grad(self.S(rho_m,target))) + def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): """ @@ -449,8 +514,8 @@ class DPIEOperator: """ # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(A_densities,qbx_forced_limit=qbx_forced_limit) - phi = self.scalar_potential_rep(phi_densities,qbx_forced_limit=qbx_forced_limit) + A = self.vector_potential_rep(A_densities) + phi = self.scalar_potential_rep(phi_densities) # evaluate the potential form for the electric and magnetic fields E_scat = 1j*self.k*A - sym.grad(3, phi) -- GitLab From 41c0003cc5d1ec6a1fbd39f054e793446f4a739e Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 14 Feb 2018 15:34:09 -0600 Subject: [PATCH 17/59] Updated the documentation for the DPIE model. --- doc/dpie_doc.pdf | Bin 169003 -> 178189 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/dpie_doc.pdf b/doc/dpie_doc.pdf index b52ac2b4db252f69a897ce3355d7126dad6bcb5c..e2f037d5d3cb117a25978aff14bc2f6dec9cb2c7 100644 GIT binary patch delta 162029 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

0(EZ7@AG}ETQws3 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 IttvzCe_Md7y#N3J delta 153255 zcmZVlLy#^^)3pt^ZQHhOYq#y*ZQD58wryLxZQHhOyT9vxBHn-Uk1BIjj&e{DEAyzB zJD93+n0RG?PSP<4vfpbR(`%FXUmNM3a2kTd=~5823K)~hhrS0S3TR#-kac+XH~KHN zp48<$lgTDtX9;r=2KHRz_T`JovK#f!snu)7%{raU!(KbAD~k=}-&wb_ll2xxL3=^V zUMs-E)AF2tuT|LHZPn%G2q)IB_@;a4ukqFDo2eV1XcS-@2iTSG{k!3b(_xx1^71kH zvQ0~jH!_C5`MlT|UE@BzoOZ2HTWj)B#kgV6TJ|acKq=+Y6GZlE;s2Hv=$q{w!Ue?k z{xZWcU0eDVa{FQkU~Q<-lHNU%;unzhKodrPNO^zI#2&@qz8l)e=iN{$WeTR@rZ;;i zzyARAd>)Qv3b_*$$nWLWKX`Q8n%Qz(M`I`Q6SM$}`rn1VZs^HbN8b|nuX?+$wcRi{ z>f)NLE;G@xyj&_42clB&B42Q@gNW4!J*=En2ZK}UW&~SSAa9l*E9vdq*1z~v*h#*R zBww1?9uXN?O)GGp{e`33OOJEwj#!jNL=1q9xi3E$@@{y9+*-kow@XMvae<%BkHf{Z z`hTp$Mxkm@Z`2~`sR(d4+5Vwb9KXsje*cb6o@lhOlbsC&3I**_j6RhyG#q0w)4>XZ zb0XOSpM!Fix%>A%pqtUU*mf+3Y#kw4K&vjrrB7geGlNp#%s)<~o8<2fLW4b2`al5g z{p*n!=`qP;*q-0UL?O&RR^o6TtkiyFlVju6wQYq{JZg77i`gEh6m9eb+Wh#KWSDax zS2eSJ^{)LU`|bhMeOa|%ZhvP72C-naprTGtp0QyrL;ucYj4wPcdUr@2#|GyK?$U^6*kIE0QQ1?c9yqa;+NttQB8Xw-e1TaD79 zXR0*56CbU@678JvlZ-{p`W2*3Bjs@~#V{o8o2sk1g%`2u2hkJw8biEba2J41waEi| zmI8S(!u(T5y78ef9G<6Zr;|!*$bYnc^^tTVq)3%cg@Ww_denJjqKwHFUu39QTHgI7 zPNryln12?E-!y=s`*7R$khFpA6A1P1RwlehFiT`wT)It8LQQ0XI8b#1wTmlF%v}*DTPESAZH>Tz?#9` z=WrJtOV}Bnq$n0(a8^Fr<4R&=l0ynGe*IZnyD+Jni(%e2Cy{D?+5EL#+}nz**$2KM zb+`y+OqM=)HN53Gc%~C!;~_ldGRdmaS6b;C9Ce$!rmcZSX&eePvq=E59gQYMjzhTX z^Y^6Mm6m1;v^==qobbnFOh(lTB;Wmw5JRd0K)KcK#;xi&5rPOJJIjn1M>)Pc@^G5g z&o@!S5U(9&2Lr->u}RQG(RaK#i+Ufh#v_gwp7S7nJTRKM#^e#?1=CUJ*Sx_YM}iyo z!s_wDKdDqzskEvu=X$^r`?^6uSkwWQm-2&LV-w|eHWh>X2y-x2qTh8X<|85kML(Db zjj*!(N}ItqGEU`KIep_qJCk>c@5Hky+Bm<+d;Z0NIO)GMbPt!3;ki}n5Y#zJwkZ`B zD@RUMUPc!vQk3&su@_%KpiVpe2n5fDxmPD3x+^JV+v3tbLl`6TTo0bK?9?ofsKgaaT?T+bKe$`&}YR5FF(AqTAhqqW!R>P&L=2k zy-ag_Y80_{K%MSXzi^xxweYtK)$JV#Q}LCX$pkO}#M7;XMGl9s2!-|3f)AT8CcLlX zja6LGpD^6oAkPq2RRHl0-G2H=0|F1#_^lOEEw2ot9HS_C^F_m|F;n(v@4gR6JS?2Cj-R}4dR7V?777W&^)Juw8GFe4`SUYdJXL1o-1u}mu<6{J zMhoDMUgM!8zr=l<_2Rx$w4J04s_*ZRY!rRj+5gv5bjZ{@l!lKI%2 zk=OK5(d70Ziu*<-VhK~6p0i-AnJ=ooIXe|6_9mx|etW*Co67^GaLkpwS10fx(9&Al!S<2xYS0ma00_j2`)VwV5R|DFz+sI z8^q5rPQjrBS^tcr;<+J~<2vm0%P6XEDbJFh6PqNN+$_r-?g95k^ z%N=G5RnDv9$%I=ky<5t3?Pa<{ROoSg>OS+OB)Jer9(;Tzu+>Wk$WXusddS$nWSOk^ z#)Eo20%=We##Cx;rq+T1BP~);mZD#iB{qmnM%kE;!(T_;gPtqV!?nIim&RfNch8qL zh4`F5B|N>7N82bJC56g$xBw(}WFn0H#!z9A*+3GHL^7z9_93BnT5=OEC8p_6 z?Bq?$6|yKXX=E6}w&HtT@cMI2L#j?4#0^Jd;^@@Gt4ATPes?MfJ`1&eT0o)jp3w^JWp zM$gm#Lh?o|Y_b*pRDQ(GFR~FO+sirrVL0Iy33u) z9X_s@kDMxvZQ4G`Oe}tRIL~`%w}>kb!m$#I z`9eYqQ_I<~eB(8nTcS56OfWz%-hY)KL0cA0To*b7gt*PHLou#9atyjGb6B_e=;n0H zV+H{WskyBUr=NQ{hyXY?`@FyI4@(F|!@+hc?`^RqTZ`_>%f`0#l3AUSlKky#YfO;b zXjpw50{<`%77CL*^+W$$A8pihYW9~je+Q*S%Z@uz`rn7NyW}J0(0hi2>H4|T$_DKj zW;-exD`DV#f?sVw<6nTP{=Ij;VI-F&4MD-u;Ux)-c*b$cumpVBMFfrDj2Aj+FA+Nf0)78SJ;4|8Nenu z-$FjdwHjiu+M`#02?7!+kn%YCu)9`R_Dj&0I0BtPqjUuaI&E5Y$MRx0bHuaxpGA!V zGm5fpiTlCuWdH%#zB2il&?K&7l;w7TydNF@+7xS`zD_??S^4Yn8ZWT0Vh_y&L_1p` za4#)EWM#Fj{-dz#dE#v=I_nAuOc&fnCdw}Kq zxTU4(wQ-jBa$nL-Zzi#VO;hZz)Sd?e)402v7jV@zQh=_#%!jT}Xv3*?yy%(TaXpoa z|H7|=vJliHTxe~XWM~7c?#QDdqIWYHk!{v7K@h=gJU{+5` z4xzUk1Ay>Y5LT4sn_0|5HG1Zi$~7PpD`&A6Oycw@<@bM~M)c2~?&suyA$Rx%rgvAw zDpX;7I2NyM5tyD3dbFF!Zz}jwpM@B*orJS)E%34f@B)FBQrs3cy^0?T1XT?E(A)b1 zRScUwOTZ*=!4s*6XQ)&7(;U*i4jHi=cx54=c`)I+zNC&y1H9xEVL6NbyOQc!@n-Pd zhk@`I5VimXld$?KYmL#FDm3zsBw%9n*ooWcwSTT?A)QdODOZtrB`3!xYB09POA8!t zxE*RB7~ePLPLMFR2+9??;`WU8mp7S7N*5R!Y9C{@U?|Q7N>a4o8e>^U@Ng%Dg=7hU zb^ixF5Tzn2Y+jclgnf^Vm{-=#Orke%1N?gGnX7v?eKDs{gy+D292G`}+jChl#%72Z zW7d-PlAVJ)h)f^}W_{+RHNS~k9Ww&nEF67I_5#k$DBi+eb7@&4TnX=Dz^_r~Tp0}g zFIrfW?jd7&k2s#6WFG9=Ig*|*KA$Z>Y#zb8WN9DXf#4qxwT*EUVJU1tkT?x^bXW;7 z#ZqEJCG%r%JWc)+vqF>4DU=ly%9&Ki<{sGLgDwnRK{+cTHL(fhKPtus z5i59tp_VG!w@vZq**98B?T@#ymmGUZ{*uhQUHmlC`D{zQ60u1$ka#hO6GXh9hAIj*3frK{RqR(i8C>PitM7h{3YNiL-tE%tjVp5^0K+_{5f#dC8FB-< zB8M=pTmyKc{c&`Ol~F$cgB`>X2s$DVJ|7nt7mnpQ3baPt0~+sBumu>vEw1TFXOj=H z5*ci6XlrSdkvcP~ZK6h6-%_j(YJD7R1hJC3^r{&Jha`zyuAOhX86rWV3%4NX(tjo2 z0T7{#SXx!TU7pYj5%uXx3A7{anpIK$-D&^SRNt1E*hQqOC%b@TKKs5RDPs7;k1&>0 zzPbgN)3zwdqgGn=T3!e2TaI)#(-$%2C;=S!S&JCX_dz=4x@ezaI7s{J&N&m6YRtTM z*0sf%`xMTirMs%st)EQjET|+a$dK#K5&Pkj#}9PD!#^fOc} zf8$79heex@^E5KY<}h5R@LeNIL-p?|X32aPBRt7}Z(*h;C7Mh07p?Yc`!csEQeM08 zA9dM)ETOUE#x$U!CD`ssV7LnPh3|~xKws#eBLfK?>+1nUwSjg0yj`vji-{S<2S=}j zc8R1LI#HA4^1Q$F~SZ_)eGIINsJWpoxJvqcD)1Oil*9v97*&%e zs^dZge?y2{WcZrVg1{9;T9N30t$VM>)E*u!ijYq&t&QgURV^#wnnO~Qle=y1ilq&t#+{C*ac8DyZ$(@M*z(Ari4CeQZH`P-%vAstq3wGux@b+q2Rd6 zbz-(F2}>HWMzE97eR!1itc{6P2zq@%1bpE3b?P)Nhd!1 zzUhMm17j(7IJkBvYTDbQxWLhlN0Jc2)t%eS!ZY*h$n3c?W4PAQ%Nxi95br#s;>1qt z4WzV4XSq#(1^WwApY_v(7PoFY*^^9zV?4ktmgVCu)f&qZHSMK;PG85a8T|k;3e;a5 zp;7h%9RvxM|KRB`+$#iXD6)G@*lqli0j#0C>CfM<@d1bOK#ehf=CprIoDn{|$xe}& zOdx=#x0QV=RLIXjFPwdvlN`M&=@(jfKiHWK)(adwI9@Sv%{AFalwXK!@w2m%{s@4< zga@%y%3n>SfLE^FWv1S`w~40y*v7Hl*MR_5^Es-ktEMZo5b0#jnZeYaO6JX(TMP!g zV77&O@>^O0St?pxa4CR|8ijX~{_xye5N9WqMUB$3kepADuEjTZp#dl>F^@k^566N3 zFHU(x+Mwv?Eb=$LU8cQKj739}M+gAr1>@HYa`)>~DKO?!%h`;~ziWPvw-CjZJ>eC_ zwZUZEZh)Fkczn+_^tqUzhI#?biCAs`w!kBb(BW*prIP{~CL0~5{o#A>EDar+%2hn7 zNf;1Ip42{UVCRntgGHYPq=Kh*z-x%Co%k9lMPq=>eW(^FrVH-O5p5QjAPUFb|NOC|2x^) znf{-~@}HK(7DShXkC8;pM#TKT8f6$pRbyr*A{OTVB}D%v+(ax)|1ahsV&+WR5+h2w z!hnHRWrpGUzl)?gF{~ysOfFc!a*j?CnWXFJ?xzYG#AcBYo8GIHW2p_cZPgtH9uFD5Apm`x@bnkCN}}bXMr}?pNi9^z+@Jf@WhC zf!})s{RpGlTS*1MPN>1f#lEw5aGt@2f;D`J!L$0*JPq%M-4Z+vxAt58D zz+2-vt+qZ9H`zy!ec3nQ7fr~jX}RaY_^V8`s@DS4_dDvnd}FUQ4^Id->^B9VL&*Pe zGHLq5E|MUR3oOX@H4-pSv+?mA9VhrE;ipOe`=;~zl9q=DLhgb>Ps#LLO{yDb!C$t4 z(}*#Id4#a?S5L60=or%v=~_3Q&zQMCIba%jAJg~U-H7|AQ1S^tzCYKxLK%mU+=n?K z!0z*E6qKiC|I_es^(TIZOmX%266yHTg)c|`&+~~bgXLfackZZP`f*K1d~4XnBO{bi zz2(yrC+Yco7gmp@icB{dQ{uY=Z4mdN^U@YDEOTJWf9(&rp!$LIedi>ng+f*!x@27y z-hD5-)0xo$Ik->T4^CIUCcZ}%2TN~3zdDu-3q!YTQ^PHEU@`-N^RSCQML#Xf9YH_+ z+cL5|SQPtS%cuX(t?M=`K$IJn8up{Tp3y|t<~Z;fFI0z)5$>Jl5f}ln9X<9!T+H{C zs_`tDq?-$1Z-%D+zC#1)snITr9ngYDE`Xi?;7P<4LJFy_TB49U;F37 z()m_bBqCUT8-otl$Na`-TxVZUF4DuPDJngt?hO}U(S3LhL>Qu{)peCqqY z+{LqN!QYVk5CCP<50SJ}TXQZXGsu$BoCLW7A;DAikkq8~pL0fSbMU>q&boZ`lK6k| zgGGXXvjnH8Dcl7~?X@)Ik7FionWu7(OX|7dh@WT4esU>N>=lsq+1xw$q4^Dwd@m}> zF5UwC@Y5S1uGHN3IJ{?VttS-lVSx>}6Ly>ZvU6*|hOC01bi2h5cpBj;$HY4IJ!L`mqT*De zp-aH2&Ijs2;d=xf#GDoEbN=!c!h5A3lKA{UC|np51zzKZtlmUP?ajuy$Hk0_8ZWG# zSq)g@k;Trh^dE`^SWNBH`392j%6v)6MXhK0>zg-#u8HFbBROq_T63-6d`IBy>M|wZ zAQpX@wse*IU)oTgG>$ah)0%9>KZXl@&p1;zB_O)H13n7PG!E%7jKE;b`#dTK*PM&} zMeg)DFP0e)1;X)1r#BcFvfXj!bHT}Avy;m=?LfyJJN6(N#bS`OvX%Si`MkiDzI`;* z@FQ(Si)86}1F-ezgu$T6pq`oO(?d z+H=Ylw0b<%5Q z?PY;r%V}owWTPKI#=B|oGu4iQ>>6)xnDU>M)0 znGy|vG?%9v48U^eV$mggBOx2`!BY=1bl#fZog04utV$*JFqHRvdjXvz4EZ9N#8_<} zES+q{vuh)I`%H&ddHlq~~j;Ra<$op*f3H`)$? z^}(_C^Fgq(X~2oXQ?01WX?!>i(LR?~(sVh(Vob>^k*HD_Y`~V_TLn57se{%)aUuSL zBz^Q;pRN(UENNX4%e|&mTWwzvc{0?Mc_Eb$y0_Tz$e00+?$cB2ri%>jO97pDSBHe`gwfMh_+$QVWMU*lA>T3oSSkCR5|CsH((1bWJGT&`9X{o-K`N@ z9EfKt{9z%ac|_FZKeypYM5u^^d702A#CsIkNbCj+N?im%Jf>1t&XH={hd|ZdgQcWP zvlKURQCjSmR29`U>mLo%m67YwkQf|^)A`tL+;~(%W|Ey-SbZ8*Z`kqWmZeTH!h=tH z+SDMj+-9-8KLHHZkUfb6kvclw*7p#b*{LHQwaEKh*>_5w#Ra~e_DdY=oON3U+3+YQ zL$YVPo3;TuOFnBrCf#(k`dYto;FH2#d?^y4Sb{aK-L*pX3WhZZz%%?}TCSQ>W6QD5uZ^)pvS!Q(Ri@NS#yv!ff- zXeDosBb4Y@Ls-Ct(D%8jd41=`7SYpL{G{X5{7rZ}_stj*t8#5ErH-$*VJXuedSn3q zPr5k%>0t&|krhsQ1L?)aI_g!&3s;PUz$yHwJ+jl54o_n0nW7oS-avm7B->@fs7^bl zfj3VJCEwKW0cVp&%NvlL2Ij0>4tM(Xl0;?H7yJBIMOOZBPk_+qmoh8UNy^!6W*HQy ze!`E6I~-o-0wO~xZhE+#$bq&BlO^CC5eF&`G%KR|KHH;;r9czTt7FUj~&d9=u(ITDr z%7hBnt=`9Y(8X=>pO5dZ;s}p+mKRAKH08|4+Pp3pi?s2Uq%O{cE~9Rak*>(ZlpTWZ#e?5Bj?g@?S|-7OJ%Aq`{3X*PN6|x2Vaj&)&yb<#8J? zq2cyOh8BP4K1*lBIT5SfI;ly2TJK+;4kMwCaW*)U&iiX31weB&DC;WMDH@bpBi=W* zl0VZ{CKK#{kNLo0YvQYkfD|C?`*K7@C#EdZhnrjK;itfA)|)Ss zx2}euE_pxn-7^*b{e2mp<@xDHeA{syn#e?IuFkGr941WvD89g8J0BqTA09!08rts{ z;KZcsEp|w9`ssDD63nL3=uCQerH-|60!_4OY<|!n*!fS?$fA`Bg4|4pu9v=wK6N*B z9%C}iCiKyr17K%Z&$<=4_!m>YKdYd@1f?ykPE zAHypmH&!KXMswQ`c3MoLgs_7a7pj_lwB|;gOyz;mIh&Q0{l%pw zOO8?f*Q_8*niCMV=G^C!=*@vDg7ZZXawoq7A#%7MQPjrJPv(UvpOB(Z1GlQ}WGwGg z5Pq?}P!pgRqezyBC$q|nT_NHO#U1KDSYXWryMhOjELXse&W`_N_DW2JdxO{5(>PHp zdP(y!Yr)+lr(Z7dcXbdxvOwM+j}0Rjj-KDSKrVO%cNQR=bi0`yy6PC&Vz32X72@=u zHVGiSpGITOh~kI@Qg+LD*%W~<@(Hte#hkP+2)4ze(*7QP&?z?@*imVIk2;nGmqSi0 z)YN+>dk+L_fg^bQ?Ef~Hf{0G3?t0F3RI}E+04j}bJn-xuHjWDB$ z0ON`1{qOzGPvogqZ<_P;3mkB@o-RGWH>nPc5&?ybJ?aSwj%|O?S}RGw8)HH1WEG^0 z5$lCF4cKN=4pZW}E=w3&+Q{kjwk(e2f&DqVcqox5{KM{2`U<~=enD4CEn?2Yl$+T- zp$33&x^JjnJNm}TnrVtB#KG-AVmce|bU*ZR{^qcZ4lT8{V`AjElVU_Y%TiMgqLNSb z*OpYCr|VM#mDE=Gs0{0sI(Sms)Sh7DNV{GS-NB8YT9d-jtPy zZKbUzT8$Y#F_fG4OYOPU1~y@+-hpu|9%Y&JvqbdW#y)$;2JJ`)}8s&24IFHn? zL)@UH4sxp7jqa&_5u$t0d|wNRWJXMEhuM2QC=?8rhy&pvX1Ud@(~tbA7q_kXj0Uvg z^;t?(F7;y^<8sk+){8&kXvJa}x3=^{+%OMI|83lySbN|*vTOAT>`cs<+|DtwYus+vsLQ_4AW4P%jsdAlWNPzWJpXm zIl|=_>OEbhg4g_=Bc^L?cHT|AST^@Er)BAB<6<2c(&Ttv>zG(Dyl=Jo`OmrIGMhaOLxUTL*7x&;)p;n-xY9?+o1^ zy(?4g9VMS-eXr!~t78HIQ5MK?{n}CUtc}TFNR30zsyev?6UN1dSinivwP_2VS$IYy z7BQyP>B$?{ex4@Oqmi@mj@f9w9R!USib~BPpjb21t z(n~iYr@0sWE*VGMMhC5!6JU@gS*oEah&BvmMo|H#(G`(3IU$=YkCG-y@n-eYWEd z*zS}n@V_vN1r)+t8j`BR+v=s^R?qdZL1K<5W^zskWDV71!QrtmZ{4GE?ra3UMw;TV zsZ4pVhxa~yz~JD?Sj!&15Hggr{;*J0v!;Cut79%b}v#PdS z+&7931av{AZTUH{?0eFBO}oug7?MTNw*Q=4VN) z*GvY?v@pF{RZy(DBbb~7@*zSqq+*xYgqt)LRQiBkt&KjfnBk^N6|}5!YQil}*%spG z9QU)ASaP%^l9-k`UZ(cI$qjL1&~&b_w-miY0@D7Pl0PXPI*ubkUwSfFDzvsAGpwzF zwV-_uSo-m!PjL0==IB(;MH~Dn*T&7}lnrZvs%1@EN-RXgahVMjWMInlCPE?D4ta}` z)$S!Bm^K{_W4dm(QKDkP)*l;r^BrdII)imW$0sEcwTKXpr^8!U6*i4!lE0uW!hMhkzmPbBcFPItPWk50B~9px0~=~SKg za7)SGK>s*95W-Mi>}Xf6d;$%Krx-Cc&wh04DmQK!3sI_vkA+(xyNno)+fg^ZIi{U) z1dRLqTm?UYA(ymjNJtlc=(+r$C5)rB409TxjAcJhVpTB7x=riUOQZ^t@(MXc0}MdI zHs0O3CbL!L+7zovjZuVMO7uOl*?&sfQ;={gH~Cy-?LaZ@e>*$Oki0YB7#cLiGDo`h zH*!+JIQi+tq@qW{tle`nNbAc5IV#uTb5?SqFGv~-`Qz_yxy>~@$<=ugi<7($Oou6-8oBRS7fQAa}>I%LeQf0W4{a3n0pg9{JIO1B7*0SorC=qlQP zfhxl2GIQn}do4{=6p*R?0-R3vjfKSj zloV+0Lp|_+;#r3Gr0(R%Z4{M4*p6eqXTqxR6Y~spYg($;=(-Jf;pGChP}&R)h_5mm z*M4M#^~0qZfAVhm*yj!a(IY%H5gu4v zCD?;B;J5}!=~s2#0Y(_sre}JqwoE3wT7Ssk*2}7+MrFwnLl!3iq)}z94RhUXlsUQu zTNC4;-iQCd@oCdq8uYPA7iLPwE_l0Q)(1>8WFZ?JtRiHv=Q+|WT2jk7=wzj;uQT%H zh$yVj$r!Q`K<9mAuw_B#yJpPwB~3V}LNUhGXBe&zBnm+f9sk4K_^I|eM9iXa7xNiZ zcr#>9luk?2*j2OuR;8Wwz6+9W1=@7u&&|cD`UjN~Ruu;c_6KhKxB;!4Ggn+@d1%UY zlm6L|9eSU%+$E?7KmpT@&$o4X03B6vkoE>A# zjU%B@xz2ll797nZbs?J5cWUcg2wB3Sy%e z!dwu`I4eczu)mWJvzXQ0nw6Pag(;4ug0tT!n>7k(xzZZEPAh1L{88IcWxhu5a#?A^ zWblW=Y-&eoR}RWx-@zME(kKnV%U>o7o5u>EFIl zN^VJ4>HO~-1AvV}R@na5;#R22duQdfxddp^oCng=nQqlci@d787g42#_cWqSC_LNo z3}Othk&^qO@j6O8EqO2gmI03Hk{p1u~3mLE1sJ&h%PCx=c0x5 zA78T^A$LjNE7kLPJAyFstvQSpqDt6Iky!ynqZKDGh@U4M8JN$kK&_7)4l8ZwE9h80lqajqNBvyoYb2<2KPHD_k|k&EETN=b3za{XHW&q zz>13mYuL;-ocMYWOQ&G(VBsbyGDf?eaP~y2zdszYLUwjKw)>a?+Zp6$YH-#v{Jz&@ zrwYgMhNCuh_;@-ZOQ-J176F|x6_y7p{WJT_Fj>gnM)Osu#GK#Wvym;tZSb;NX z8BBeUb9HSGh*gw%QWLwSogJEKT7(DSmw6SuG3eYUks41iY0Tb_dXL za$Ukf*8b-;>9R@)X1d^wz(3$k)^Do!<Sj;5rnZRc!!!lyB=0 zl=;#j$VvYh&I7^_5h9X~EQ#Y8`s%+80f$tCO}!Z~sdo6HOUc;a3dvj9$bU=_Q~<f}!@DNIE(+YcHYeGvr4<3USx1Q6SYl=%)QNk!T zWJJ4t3iScy>+s6k4n6;kND2Wfj&%zj1X1`LF8V+KQ_xW*1}kE`eKq&}89iK8%EvxM zT2JQ_lK5=j&FJfG66V+cK)K!gF)WV%WJErnoyxDY8T|*}8N^e%_CTtFNzQ05i^uj2iP!>)R-IM@)W^p011xvp< z`R4-%6>;008Pj&VsOl`!1W8(ThBU@TyDcyroy)#-c7l73BF*b5hDxyaIeiL>^2}23i;p|2bol#;B7;<3N@8cT`1P!d zsVeRZ2j2<5_WoC zTOn^-emkCyR9XR(x#B-F4Xr<)YnxB6mpZ>Mh339{?J}OK`y5H#EFjkuyP^PT58<}O zJ1p6Z#p*-q?qOp`gH2#DqWG)LV2~*T6!B zf%AQMsZ8U@zXlNE__G?<%xk$YU6cN*69=q z?{w)_K_^<;yjQLA7nV&)iR2o)WexIAdB0_(xfE`jjU6DqqT!N;@@kNs%vK1pDm<1H zVZzVFV%(KU6T3yqLfc5=F+g8|bIk)qRLAV3K1SI{ex_RG&Ki`me;#DUp5p#@qc39q z3jjn3ubFY~)PecdO#3EM1!JVztf$^zssZjdVm=DI^D;~#ed!+EKGLa`A>-D%kn=QP z>djAmarMVIvkvJKBNr>2;>?T zT0bZSN3i-Nf>dW!N*8R;m%ZQzZ-%G5nW>t>Y%`7bh(EcjhIuZZ=S{1Z#<%x#trg;z zyTPjMt}6d8?xvSutnKsk9W!8+OG%T1^*YT z)9;{nIpFup_lt6qaVqOGo?n-p`6Er-ABh<*pjzPszto2(RMS;ZrJaFS#r}LRgR2J$ey_b%7X=ixnotM;akdL3krYje`vu)fx=Cqa<=`O=)X=PZ#@U{TVb zv-fWK^ke)w?SWRkq3&wGzJG?pb~1w0wW#&nN9Gkj7I^C(eyjFK|h9Kc~*3IX?&Bv*#vsnbbf& z52$kh?%6Ro#At#2OTTCR>Sp8H(PCp6U)hXl0D)^4TZis#SU{?Ligjl7M!l`{Dr-At zJ1wDnr#J$KoZ@qDrPd&?1`oT>Y=^Cg0K`R-SpqrzPYul8Wc-b&C5lZbUDCXJ#o zDN9Q~v)|C+B5sEYb<<#gv=cXptmWVKljk_D_~$Kt3lgeZoBbfPjb6}%qRH%rhSBYI z%(d;^I_t}0N>oFc?J8s5HC_iC_kFGXHSy;Hhtd?aXmCi9PV+9HOZn)+9&Y?2fTiM_ z%%+Kz+B$yCAeFK~0ZwXgNF&nsQ>L}~SzbEYikO*1wZ#v?!`0YeoLtwUb*h+>ZUbMP zEzh=F$Qj58)2LyPLB4lrd!s`U2F>T;1Fqex4%?CJWvW%g^-LpZET3}-`<}MGrAtrE z!{j=+;~%|yl@K=I&28X6_xA##hlJnqulBDY4je=sLseWTB}zI8uGy?ACq*}0>ao20 zxR!X#_k?288^u+96qSYroVXX-ry;e-^;aIFLkaH&X^Whgzpwczyhk)erCyv!lcq+m z;+ylv_BNeZ)plyXiAI;^j-rLgpr47wO3iha+J(#-fmJVHNfZa-s?Xx?rYp<^0XhTh zK|P^h_m_t`^hyAZF_eWj6Mtnc-cZ0{W)_rooh4HLa~j0;mt0WT0M|iK&8Wtljp02= z+qEV0d#vw!prwa>LtDVk{J)lye;&P7wqEV0k^GDIFZH>Wd>P}n(2EHa6I+{wUjW}h z(S@v=&-DcqK{YAB;kMM*mPtw9)mXf6CTa4y@z@toZ!O1c2kDpE+I%*|#RtDebqQl! zQ+RKsZ_Aqpw<#>0+z2CwmcAhW2Z2ty*=+XlgK>hqera3G;2O{DzvSNsgc0f)-V_eT za-jg#xLo*sHW_kpALUQP!C~o6A3C}<~{6zuU9ciSrE%np(rquraXxoWjBnjj` zZu+Ugb79WZJg3TV#5aMW)SB27Ni--=_sYkZS5PK-z%&2{ly$dP|L{q#<~^!@%-e=r zmnzJC8(O%>LxTx-pgY!g)Fr<{pFE`_2JJ-&A^)3@ocYx6v9`bR_Ynu2sjX^PqY~W| zgmM5nYD_RJ6x=tXz=4)F_`0&*Nl82YCvf%5VyB@39ML!%I5R0wr&8NKY z+&dk;?sf7wIBybHrqTPcI0RHdoMTj7xd;%pZ)U%T%8L=-KYb3(vu^g4wYv(_1+jT* zdaZdEhh#v(%Sj3#aMh*?%lPx7c8UKE%*6m8ynPW>s}?;78xc%rlyxYYgXY!f3dOU- zDyv4%N2rLR&y^p42^P0Jr#J%FA*BgMZIQSfM>rgC?-AOr*P@9>*1hyQ-#-`$^#Sku zFUMJ@x4#cBuLj-izRzEmv-*O7&xhIFZokKq_p-blKhMwoMTJ*`Lt*>(Z_&{_ub%|p@wY`f*V=)0)C}kF z;vO|bc;!Li8VULCrva4VyP5~#bD!%jqsH&*ILYqdzxJ5sN!j%eb~Md$73GczsK^a1 zA5y{uH88kkC`kLTUEV0K@NeM=G>%qMEp>=C8>GKHKzc)ge^EpYWx5c}kh z>w1rparlsj*QlnpBmA~Lr*iJYFxX<1vTKYEvRus?fyJQl9bSzoZ@!6rQ^5D8@xUKh z8+K8=l-e3GYRuLTv~O>gVt_J#S>0+pLK7idj(T*U{wU12W9s#&6l%!AJsa}?{$7E0 z(SYY7W4=GKEO;|^+2Cg;bJSon6Wh7D7>bNH<$ko7cHW%oaXHR^5rT03xX8A|pNKkR zsVULDQoPE0(7+O?GPIHw8m+fS?bH_f2!Zd!fSG9Jhf#* z4qh+V}VKVvP#jjr+B`s*j!E&55=!P1uV)qY@=_{XVF z)w0s4#qIP?r*-oswj$tfl*wRXTYqM*nRzkEj5Gso0aT~GjiyDLjm}0doBta$G!9LD zg#C9~j55LcHC5;bcN9(2uS;#gO8bKLdSpP*wcQ9!E<(3>0W@rx@CJD+FfYd_dwm?^ z9M_`>42SLo7TAr);HG!$p%R!*Ixz2{eLyN3vgA3~O%-ac(b4a|m)S-oL!z ze@-q_3D_ZeZh>GQ9cmYn)+;KOK8^I(d)y(?e4CfW1?aSvtoD7p_6L{f6M~xpTvUju zD&+?WVF6$2gba7t-|gPO3Q1U%b~pHsPO+(E>Q=9(J6(=YK4lyS_$i#;bFsaBXneas zoC1syTAt@u()MrMc-&#TQhKA_;mwS_zbt0Dsrir zn38lc`4d11LRX!OCd0j30l94W*BBIBHcjJYU>5P}J8WbOEHh-{I0LIRY#aU(H?9|n z<7pQjLuW;W^t#1w*$VJzGBOFXCkI%$cAMYQr#5!JFro9vMOnXPK%(iE{-`Mdbly5r zZODu_!bIl|7c01c=aL7SOT9<%(l6In*C(dTyyBCa=Od+{#fSXECIBqAbjhL>;?e8kC4`ImD9`%K_Yg1dVIp0I27Qf2j|L#mP=8!29pBZP3dNmFe8^P)Ip-kz0F`_ zdgM296gV_{t-c)eRSSjbP?<qHV*ZGqn$*WI0M?n`{1pxBsYRv_7WbqF`1xwLej7@ zk6l=uC(gcKsV-4uBZ(00wdx5V>>*$0hC z?l7EOI037t5Y*4so=Rew{zc@`q^pI3Ka={x1#GQnnDpWwx@FcUJ zwg6%X?(T`iSQYIAsoP%ZVk_wT1Ix@d4SQe{X!9ttoXI2cg9!o;VbZOCkfi8v#Mj2= z1A{XYYlh6>)rlKE$Dkz9{T&rG_ct^2X1mGlYUB z#+sz~ava1zqbD9dc@=&zMP94|t}BJI9?u%uEoeSxR0qipoJS;m03@~9Qt&7f2r)pe z#6%AXC4Fu$;`mz~M*D^A$uXMY3zf-!6mP`+uZl%vk#fi6I+D)igRv$FVn;bKOkhcd znySFGO{AspccQCxBjWPt)&Y!KwXt=FFwg~JhxBfQ=?B=REfzH#RYu;SHZ&S z!YS;Bok-Dvu{#W$qH=VSuGxZ{8xYEnlp;x#Cw{RaXE%6LB+8{bHG1=_KA+DsSe?Q? z5#7%B9*yzGVU+K>Net+aFDXMR?)VCjfxS64mjvlYAC!K6&AU|HO@@7MH=nnR%Z9m{ zG!o=Yq-d&>@p3E$luh>zAS>x-kP6wQUP%Xc`#O_TqI5IDWcBIIrrv#LIq+`K^4wXi z2ffUM+ZYGBglZeA!ypRT^MxP6Y?Eyw8baEb%L$gw6oJa`62sA}1LAp`U3n3I3 zYA^M1I-NW7A#TSv3?rZz)M=|j$|j02CDAl#VTCUcJZd>FEjsOgZ74)XC9~a5%lEMn zd4*>ut3Y1;Ag$J-$DJv75A1Y95|@{yMl~q&+NN~fSrnP~WMa8>W8GL? z&14Q?4In#6RH-5&W&LE@0a3LA7R&|&1SdR^6fvCBl=jOfBCdumK&8(Yg3!*PIJc3- zan)Z(UCD^-zP&}c18=5Tzx-5 z%C@y7zo0cNefT>U(_YS3B`db)Lw0;l@dKwL*oB6si9XtN| z6j?irl*S=VPBgE&ILIA4hzkk;b*a(ubP2W?Kr~bmiTSS)V7LuJlQWzAvB$GJYv$$- zMUw7hYgm&jGF5{~&W_-MD!scrI6DI}6RE6Bl^kjMFIBsZIdxjFz`=I1Ji%DK#z3w; zrI!*$XYe~%+20=g%DLBQ`kbO;f8*#XYmi!#T#ZbTU-px|%iH9lV^C57xqPuFHc|M| zuRDQYRS13!Kzvw$=Dh4s_U!4cd3`L4we52D`qL-~qVt*z0gZW}sCoS_d0%1D9ffyB z?^!DW%hC^MLa)wV-cpDvNSPAVf)&{4my#+Bt7K;YGtYs z-+|dlV6!8Ap(N@La!5lHcsB11Xh#+s9aboAPE)k3RA;6_eA?qjbrNcIk$5^{MA_h+ zTApa-uo0Woo)nDzC6MGdaC67hpKaBDMN_B)WkStabTP+=Re^LF0vRkqkQ5GeQ|C)i z63Mfe^ZUCza!{Twu4yUtNFMaDqO1SXD)HGg5QRPX8~aiuM+sG6B#akja4WI^-2A1Z zU^=WLC<;b2*^pTBE%rD-%|c7=-0q&z>{OH+2|wYL1Z!SU7b3AFnIUPHIaY8D#krID zyjz~h4B5l0naQw`>Z&)tvP}w|0`4%Yd8*O$gjHL%i#8F19oM;%Wa2!Oh*-Majr2+p z2r9#z?-oK44yLEOD_v2lN#%4#OD^K0G`xjL99YtbBMd{_U@V6pWI{ei(N@WxN+pQ095-$0Mqp#7bMhw&Ynt zyzMM)4;z4=LS&+fFaxh-7U~$N=FQ`8Uu!Xxa_OJ@3CLa?B@q*ShQ=O~c~uMpzI;y) zaF~0!yA+(HpunbyaGD}^^H$`fX=4(8zB4T*gk8wl+mM+O)G{ySCT+KydHW%xFnxB+ zWQ}~)#Yn?%J|@H84M2N`s6zdb(H9F(@J22flV3BU!k!j)SJ^P4_7bO)plXQlB(Afu z^QJ7b$DA>FrJ!+UeGu=$hf?bY!o?gdHG-T}p~JXp1dqU*w+7=2M*A`HdWC$^D6mn2 z4mIf<^QHc*XMp4(zhEguzO`xL(my|&Sy+Fq`}eBKx>7F|8zo_ zFp1ny;c}XWKn-_lcd`_Po}8v$nOZpmVLaVpuzf>RKdw-g=9nI8=OX0*LeaB4MwGR? zfwx#HT1Vs!m&KH1A2RA`{gmFN2(Mjh`E{wlr7Uj9;BVR@jU0pwO>D^hbE~nfjGA;E zxweXyH%PWMri0H%R2jC^y!M`cLs%s>`XvI@G?-i~sk>~sYp)if(voMhprDL;P5s5s;fnMfi8xEfU~wq33xjVHxpg^!v=on%=1usAC-`|HZnzqIIiU zx<-5Rea^ALo;kksk0bEd(OoxRX5m$8)Mf%UKqL^XxTY2JRMAgadf=)sr(zbhmn+Ho zRaIH~ZBV$b!Rek}Qkw}AXmDj`_imW6Pz}i3NI)mncOblq$IAWLQ9mqwTI-9l->7f$n6Qn0Rxv5X0rE;8D%@?-bp;o! z3W)i{JM|#{y}1luqv~U`lKy2lbU{Q9<83OuTg7Hn*(fstLg}YFQ^m==FR)S!)P{)T z>sr^K@B%VQy!esG)J82i__TMRG%a#A`bP|!QL`>2)>+?X`d%v{R}%Gj(MU45N*dCt z)V6wJ!z;R+RNw%#^Kx8rc6hiCY~eU|GN6kG*KhsCzVrQsfB=giTy>MS$wbLOj<1q= zkzqMf(^VWG#xxF{Io^SVph%L)S{u(u0by`jW|)Hb$O=w#i&gWccjh!Ul)GyzU*;ug zX~JMZ_uv;CLUgx>sh3Wb*onxpD#E79tY0G6$^|>kAG+A8SyrnEBn9uL!_>7Z9GK5GXb)BbLWVeH6E znz^sVp5dUAT=^Zd?a#<6>uS1pjXCmtC(6MDtb^#Qgo&HV$1KQrwTi2jo6GzmxG$CU zp=PQ1(M3O{6k6PA<)cb{bmCs&t`N{_mm9KCBc}}fy@r|p+O51M3ENK1EzLNnN;v|*CFr+tG zoJ?(dhf|Kr7YYWUtgSFQH_yc=$hwPMXDEEsgrunpm>ke+d*$!Cr;nNc`%D;CvGd## zcBf(N)ttJa&(r+iCxsm$`L>o)$bs?V`lq`1}eM{%^DA~T2-=T`^p7fuqWCPh? zW`ip)DU~O8LpLTHs8x21G)pGiZJrA?q?|V zEoyIO+in_67M1_^T>_?^$Iw$he6tF_CYzq(M!WPfyxdNu4<%z(cg-i4w$^dOt4^1A zXC4V>kpe3ZwfBB5Cq2I&>La`%TOm)aTVePu4qcl%p^omaM2iOvsa1uI?i^skMmY=9qO8I3Eh5;?1n zW_8qs z4&#efCt#%2qQ7&MXn7UH6CL&z@F2}ujDMp7CD_f1>1Y5&FZob|gN!Z4j6~q#Kq%;~ z5#MONhQ2zu_^bQ9|B>0i*o+~VbbUH%sFEWArF|;lj zQ3GeKg?zsDU`z27mHe$A!F3u~W5O;Ygo-8*S4fXrl4VGVhp_Y*BZ+6T!19t-#yx08O`~)W0F-(i#nX5*AL|7l ztLTI*6g8ucHmc}IrPbPB5UFfJOrw;>8U4f*5l!nQ@WL$}nFxX;9Pq4wbk(q?D>16P4`4swq?wKYAsx2eW`#lw^^M znG~ydj}p;&^u`kR*4LYL^gDbcN<7*)wJec~3omukW_!zwEOcF%gBVBoj1@LefcX_F zgw0KqYFfQNCys!LIF*gVhy5YXf*ZVpnnD@8<5~nnFtOqz{%{+qSsbSvQ1bo@cBJ`G zAm`~iVclxB^jShDo(tKvGQQsbb`$wM;oH7=w9=?pv+9XGN`-p6OdL6v7g%>-9g-*? z@yjz28?yYu5hvx0qI=Edq6v{pKzObm5-QIU-_8pxb9=zI5=SGcNLndzB*LCfMaoZ0 zoUs_VGNeYf-IWY(#D83hUkELxe#R1$rI&(^ZY@&Xe4}^lPlOu?Tg;O#e!eTAcH2ns zeLO=$hvP<_q7HX-Gh7L;)eK?7gSNCMz{@`%n|KTah|JuYXYzXALumhl*%b@!)Gb<#)fVUc^6><8W^)d&Bo6Lm{@EJXU5_p7gvAPrXr`na`xkN%!}9Ps!+#FFFJ+ARvq z*FwjhB9(+Xn!>+#B@I>mZZ<9L)!-ngLeJFzjNDm}=6i>7Qy zoB)<4?y2Kg4 z%4HU70gS%bJcC2d%+b_a!uX2HKEslg>w<-Q&nwW+x`XG?WWss%`<>nb2ipnt30Wg(ac zcCNi4nYNgg*lv$=vK~k#q`|f$EI5W(H1aFfF|n<4PCU$^UL;m>7f;Drn<@rFqA!l@ z)fkR4E1eC_lj4D2D2VNC#4W?2Kk?59luz()dnYis>-oqHSho;H4ACigjx8A*A^;nJ zz`g*5;=E%;t#nS^e zx)_nCP;7tYS|QA~ty@AT)~sIy5Z{De(=LNu9EsZ2VjCc%8KF_0QPU? zpT@=E3YJ)WF`{+VOMm52qA8b4g(` z*rYU<@{DuW02Bs;NGMqgg=BH$ZY!}|YRW@cZ7!*Cq#RJ9tGjoKo4gdiVtrHiU1l;! zzGgTrMOS&CE9fajk^D`!32_;0V^Gb4L-5aXBjN*uu2y3#m##yBXMLE@NH`w9C~Xt} zJR+A$-E|l!Agjq^3SX-+7h%k^Md%F5BUHS{U=o(l`p6;`tEh#LD^ZGYCV}&t`?oNx zJ9zoR%q`F+*D|j@>%a=!pv*64f-uCZbOK5jFYA6%3}eU-TyOv@ha`9tnmHN$6e`;Z ztQ21={A4DfV9^9#k)*oe-_5NDr5^VU^_t4zAIvh4YrSn{&}KRjUXZ>p?4kUTJv~Hc z7cHZo3RFOQs5s|FfHPcgT5EAdi~0G+^n0De&C{w)Vk!|9L0iIf2wxOOM{hkRjVI3b z*>{j96?O$hN>{{t8{Xdas)XN3>J%gMr-Vq?{kA{~d0_&;Sv zQm$+y9Be5`;+QE-;y9fD2g;?!PQw0Q1lNBUFCG%s|4noKpY|hG9uf|glq746lt1DW zkpCP0!ux-~U;o5O|Is6*oLCd5z{g{xT*aY+aj+%F(_?Y`haUS6Y$hN;!Yps@VCiN> z!oit>Y(oY7rM~5`B#h$!yDmdYDtN}A+aQ~p2{V|QPWzxxRNWcfE;hkUMyfFy?!wI4O=K2#u`ID_oWAq-yk%$cs#X!^6$Hj>KBD*L+0{6c{{q3QkD2D!JbOshB|F&r2~X5ZuhGVR9o`7;I9k5 zii)<>mF%zOx{5DF?8n5>(07hF9BVhCfxoETjf)aqibDN{tOLL|IYl0(R1 z+Uy_mWbc^_&kMZzsIJ&ZJcp?QpkQ(jn7HLb;y%X+@I|e#CsHnosZPAVAcj~@g6}=^ zgNe$3FDSJ+OCumd)?|Lff@DGC4(OE0=V;u)$uRB0-u&>$oyYEWs_uayMGRI_=l0xY z_|ydp>vYd&6|q81Je-C*Dit@jW!sh zm#oh-r$qNoldHQ#(ADO`L593UV?<1412j8_k5g}yZHnJ!8e`1QJK))(^4T){f-U`Q zyQ5;d;@RbrFHqld!!F(Sjrvf`Z5j?)$UiD~GOh@NL1>TcH9kZoB7y`Qr zJYS7a+4w}nmjZhUD>MqYn#sy#yn&QhepqBTzk zVz4=>B%}gx2hHfD+F{%qCCU@zEIhvCh2vm4YhGvm27|C}rTxFrN{;{K9ph$U`(I=w z2m3#=k_GKQ(B(hDn3$u3o4JFVD+$Lx;8L6#jDsuXx0Ue!GiuROeB-bn{u8PxtP&V0 z4Dnq5P)ueyYcp38U6TLE%m5^8|IKEy|F_DFL!7`02(KV0BQO|MjFvNr=aR`Jb`PnO^Vn?=0s^Fk)be?7*WVh4eU` zjOrSfvu5fH<+SS1CuYk&aRrr3s(50H0ofqjS!q#(!BtO0fam*8TtF{WF8GPlj}t%S z9C8v^6x?lE{877hG{Q zqK&YW;}m9m@D@r%nuh_k(*MVo@-@xojZ#X8LHs+fH#IOg;WMZge0!o!5c z$geb);0`U{sgd7N-aE28b{UfH%!kE^$PE=gMz`fhJfZ-dqU69$rSVM?-8r&NzCWQc zL&C%VR>7|8S+G(dOXo)8VodqM?@*Q(tL|Ho71Gq1EJZD=mhwDrWo-mae`Ldu2UjIw z_lT>lQxjlsvVF)Q$uMyMqzaRGsi0jRo0-=7&&9nzjDt?V>c*g5x~V5W885YvtAm^R z1!OcD`i~9kk4AuE{aHaBe2#3m%c-2L4rVF7qAIQ#T|=0Sylp_ z+y0=9HLyFIP%Gvfq)wDfo27K6bvRDUPMaOiZl`;m^gO@5M+S4fU-mq0e!s2tt+I0C za}7^#Z|iwE^BFbG?flk1XE11F7ryimK4y6G;EdA};90SA+Dsf+i$H<{ zMua60H2NPZ<2<@Dm@e_`04v(9Ejl&JGFM`I*z7B&c$$olkI5~Wv^^*F@eT?1g#1Sq zF~9u|3_qVVmDt(Yv#q(-?1DDvnsP1Prx|AKW?W_@_xNVQGke1OcI`B5J(jjt^-LTH z05*q``4J{QHTF*G4X=CUDaUc`@+s87S>m9+U-%!jvmfQSLccQF-G;3?;iPshOIAjX zgn|v;#&*!hY`cWx5Q8aHce3olw!t% z@x~SCX9bm*=0iAd$=?(Cz;N1A&L%H!4H|!1xa@33*W7ns%qZwPCHS>eM$DiCiL}_A zFCrIEM+%?M^0&s~Jn^=r#+0_XO}cHqBkvULU}ti@jE1dNCv$9}iCa`4%$JB@9Uhkl2dP10LdymR4?hteObsepO;lIg z+b~>IeRkhF&%RDIC;EYHJ{=_jff4lI{zFGlLVfeF$x!$OO@#M&OILFl2W~0kMNsMBkgxvbBIJaG^iM* zX2DqJBtqi0QdJuVObP-&$h*a>(N}9Ub6tx%$%YJGr_$C-2 zo;l1fvSR45FOHt=oPU#qDOG1_Ct}}2pXHC}8!$#OTKw5ETbVGzqbn4+7~VfZDDnYiRqcyLQgs_adGrA4WDJoyYD%S%3bT`V_nZcp?Ku?K;6+-S5`gTXUb>G zm=UaU1_Ufkh94;Hu(A^vXhI)p`^o!9L(q~4_^ckoov5|sn^GT9`Gbys#9j`QEXbwe zFybEYaJd?RhksRo^inHv=Xj519Ne1$ZXc(knxS>=J@;%CoiNA#!k&s-eP^&=f!QhuF%z(I68 zv;G1u`|L0!MT{C-G2nSc zyAGHZ&|zDvFKoaH7QqZpsn6>zF#$`iLCp%d2U|aR?})g*uZD+ zK5;HrEu_nCH?zq3WP4~Ey{f18*K6ix&BZqTmtgbLvd+ZjdJCp)U7*7F07y9rvvoW1 zi?9~18?ayawafiP&L-xsYyl|3?w+uhFMPI`M>6;xY_$A$V}J5|jW7v&`ivzOS-?;j zI&zr8e2$^|%C5m9e>?J9$>lIv2p@ML#Ad;d@?YUNhM(T6-g_@+QYS?B>Zxr&m)v+q z>@qQ(A|^@fl&sWD<-%Fz5bk~2-_K9bx3C?jYe0|55ltIr2pJ9O+#&(AzIYHN?&q1= zP|GNkVHkx$+$}#my<9*4++17#t+1;{_bakp5}q8aT=ct82Hb8k$l|a&K}EarMDO7g zGg?7=QNwp9{|=aYHaNGi!%59$Y!{q4$}y@jF@hIJTBQ;6%0Nr0z}fOU0%pwSRm#pN zVIU!MLK502J~G*41kI%a4NyiHdBNCmX-)h#UhcU_Hksk2_qf=m;27q}-!3k3uB%3& zI;2|p3h{m4Y+bt#1i6mAo;&`w4ZYe_Zg2{NH((F66}9}MXZV_Epu8UuVrYe`Fiy)-1 zn6bb(C@ziZaKa6%9^blW8>fgwJDP}`WbGnM1Kbi7N;eGgfrG=MTR zx#}gNnT;flzlnk+xd-kL57U)bzXSJV56Z3DMs!XlxwNuN#++cRsZ}^8-pjs-iHrP) z7~}#;Tuh=P^?|mYSw+(%qDB85KTwz&lyFI6Z2PnQ@BQPGuM_Czp?U2qC~g3)l#IKd z5F!S6d2%%eDNhg;CC3u&9Q_dJaX;N%eDU8^Vhhti1+dn%_#6pOaBqm;82XhI>Timj zQS|=FxjX6MitE%Z6`$3F27o|zJLsi;NS|c-ZvB$JTv3F}JhfYsR7FIWNT95;6&MV@ zxxhpiv8LqKXaRgKHw;qg7={?pVd@&fTemcl2O+@d^ zBX3O@LcaElDMA{$Ge|wDzU3mOylVMz)$9uCn*7oH`u$Nty;BGCZFssxTa+B>xOq{` z!*Z;XCv-kLCtl54Nn=IFH{g^|UBVxm1Gl6C5OvYe%{Uf#0Yzjk!NECj(;6ypmx6i* zLK_*~z=0|D{R-l!Y@U<+S6XO#M~04Wy&I5TZ62H;ZsRmM&43) zwG2$6Wg}*jVC;HbNnqwY?Mi4qC+@` z|pl-6K&hGIdmBA2!>izsw`%*3q+B8Nd^=*)Z5YCco5}^ZgkF zDUfvvX_|hN=lY2O552PNFKfW~Y+2H5i_n`M)7@cOT`dD946tiM6n4Afn+oY$o4otW zz;vIE`1ru{hL*zH9KK$T!1>@$AqK(447}aG{kn23^^pdQow`77K#rhKk1u{d`LAz1 zK^i~asEr*NBsc9wlcPlCq5PD6$+-@$(fJ@k zeWD?N;qlQx!#T!srI-lKU5JKJsE6&h(Ex1<#MD&~S_3}j`T2d^-9aJqK*ghqu;C_6 zeW+hMVRc7SwDc-uS)M1uv7HS|xHFU&FBA1dn zgP&TLxOwcXrY~PN1Je#X!o~}~3h;nwm^juPG1d1o-Da8AEIycC{`-R>7XjIrq_5>kM``DsQz&^WauPW6&~lk7cZxR+uyN-7!Hy&rpcYI; zmsoFp=}){lG15krN=DRd zz^Ja{_T+Fs?u86)I&3t|X$lcwMc_BQ@_WU0OF{Aa1&nhNt;rOH69ewdn!eF2V4YI& zi3C`Mp47lV-Si_(&Gc=sHtXZd$1XrG2=g^C?RslqKV%KkCr7Q=^c5$uoc;kv@NqMk z4ZJYr^wLDG)+A;YG7hC8p4V73{Q_#Bw&oTvA!Wv|uIhU}?e2zoktJ^wxk@CxEER%- z!CE*F5UOAlL2$~z^b%hN23JVM{WlqQSr=Fm3|}?<;^%wMO3y#AjZzg-jGO^8aDxy` zZu~}vdiYP`TQa09oUjB|TU*`S3`wkP|A0YY>05o!@q9YZo%IEC2+nfN836qiLK~=} znmh~1^Ty*VtJtM0!3f^C6KpAV=BKDaRD~Eu0)sS4yw7V~Dl2NJKXS*Uu|Mf_2nD>u z%}~Q-tD5>velyR(#X)t6(^z~O;2nid5bAHKc-63R6ZK5c>b|}`TClm~_c}$BJ`58{ z(yc1t)Km}sc2OG;q`9^jf#|+P-|!bPRhT3clO)WR)_dEl?_73j#e<~On-mQddF(Tz zBc`y|v=s4?6^%>BLp%tCHIfak!}^=%xryrmuiYg^zEcQj}+ zwlSx+v2*q(d}v#2eN1LKX)>CKM4k?5@Tpg&8>R}u!QsFEkHApxEn(`DTwh>3JJRvc z8`BAX`+m?dyrFm(rKBj&{*IhE57?o{MQJ zZ=&S&SD8-{xc#(wh%3s3^BXlo`@ZF*P!AX+lshEfPjkgRgnPPnm{;1j=7T!;S!@rE z7EU!!W3u&|O}E4u}{PuNOFpt_O3YgS$-E z4Ql`B*-P!$)V{i-`){J4`rg*MveE(T9+fm_Sf7c#VJ?FsMsx3 zzbm(v%1>}h-5iMiR#4n|AxF06jOHuhMYHQBk&7>=;GloB+pV3v5(m;mwasL~zbRWF zHyh}NAiz8hP%iPmRcs77aw6?Q#Wdg3rsaihE2wxJK@tFJ-Ui_V;O-yaQr`>s{Lm(YD0zf_YbtD{b;_!0~`>_vQr)=TF z1ZpIaEZb<;`K57T0#I}ia&PtN^<6$4RAihzU>7GuaohjwD}!=VZwo$gL^tXl!9Qv_Iw4e-sGTf9 zV5eUb6{p&^J26_6CC_DL&qH^V_mleaGJ+l7nMCMw|3KzE>iD)Hf(~B<8woWkA6d7M z7wu|`Iq~~=SrYCNY7?FK(CyOhv&IFU>u6_hOeJk+qUy%XCW8?Jrz1RhAji|5>;u^*G z+KU&&yKETAN|h{T*yOYph2H_oiQpxtUqP;DyiQ?4O-WfSin3&Pm<)VG3bnKSKBO^iKNb^y{(%->G&?LD0Cn2~CqLuX!z zaWE*whlaCNSObjkoze5T2$T?)DgTTIPO4wzvLpXi-Cbfsy`m;HG?1C6Lxzh+#sp&Q z8BG6!Ra3QKg>+qWTn=nuXs}q|4(RixbHGANAZkFb_2S#7lYX616Pn61ZDeXXe>2#! z=)%9)y1Nj+)w48*`M8{c0KvovC@d27SM<>ECfz3tTxqPi{iJb_)9{B-pW`ds3n#yx zZofGp*SzbCAkQ`RsFXITXUd#XSf%_|;=ACvQuv6U+DIXXjfsp@)|rIt07#LjOh=El zUb!SIQ7|%}9zrAM=>XAuI7=@~4Scm@9i#UrUttS%G4F(sDU~bak%Ob|A0TzXwr-kV zJ995B2+`|KIn0XvL$BUBOeSw#CuVAT77^NayzKhYo;%bqbQRxLn=1rV7>}V0`f<=7 z-PsvEx%nm=m5|6CK+)7r2+YTh$S7S5l?X#G&Qytgt7j8nQbET~$N3pqbJ;9nl#i7M zIj*gPJkgQF{lQ83Glw#SuMKGp*xxC$`%iG{c#KZv<(_cM|~pN7C@`(Mjl>!P40a zEER|6oTmUeC}hqvt7U_rPe(J7YIV1nz7DJP?A~ejJ@T&DY4@RuC(dO*_4ZEu?XQOx z*S0r#8p5!bCgZE{7MoBY{hi&Ke*o7P=&tYwhB@KQ@ABR`384B28}Y*L9o53KA`=)M zQ~{+%RiFNodPIZE+eRXrC$1HNYPwY4Xs_vI3+HGzcNd8Iu)sDK%4l|{qHXv7giDh@ zn|oL-d(ozkQDHQ$pt+LipgUk~B`1n%s6XUDkh9Mo%C^F5&l~w5EbaZcm{@pGlSxn+ zDD6VO4eq!a5`g$@6zZKi3Ep?+AtQZtr1w;>^_j*{FTot&gEgor-vB&47p5ydnlKV=Z-M3PJWVkef9)jojlB8+U2NvAat(gifr0W3S z)Y2?sa2ihM4(<=Nafv0F4BmekfYcL`A5aYTm)o84HmnoysI-#S8Gp}E5&~JeV7y;t zm6|*|^ruVD)ZGAy>h=}&O&a5T=wK-Lw(}E;H2Xtg0?FoXrNgw;iq`j$p$z>F3n-9# zC~DcXAxd8l=PoGwKiE3UsJND`T|*NbLU4Bt?ykYz-Q9z`H0~OtaSiV7F2UX1U4uh_ z%iibgd(OSz_oIJwt<|G?tTE@Tcg<1HTX5()vYJ96UmMsM41T!eB~Z16;KQoD%kIw3 zM+j!?1vw6ZHSA~=7w>;ZG;$pkv=-$l97?L$Cl1QCbC@YQDwM!E?LD)kQQ4I^eI9ac zmOmolPOB_H<)`0hR)VqVrN8IKMG>J*MdypP8PrPaX~72~C|*MLh6FT*G1Uo)}HMY`^nT%3fIGcF)k;JV?&G?;|n1cgZxTOT68r) zc^6XA?RjCHWJB%ZB7k+Eb{z$D%eUrJ^h5$nwRiTg8k7xbgsHMQH$Ug5 znFy_kH6dY~TUo9UPe1|pNwpqsIJ33*ZQ?F6lPj3X3i$-cwDlhOroQU>g#{q=0k>nSJsyl&%sgS_;hFyF%Qrp;W^+ zCyxXKL_CWqly@X)N@^Y1(C4%EiO`hwMd%nJ;nm*o+iK}4rhLod=FK$L zE5UII&kf^$B&uMGVOGyKaEsm}bJ^n4_AnVmBEYG=sDxJgHKouxw z@j4}v=aE2KCKcd$hFfhPFtB-}6dA%<$^JR``OB0=hQiNh=_DCLba8;CU2r!lZ(VhA zLaJ6)nrs}6%B*4cYv)_HH*)Wn_ow-*f*Uz{6SMCTXUx<3BWv$BD_e9pe^_6-91dxeW3vuwY?>_x-Sp%-C4#?__2L=zv$u}b9o8&R;4*F`zPFn)hwUY zKuU^fEPCwV@7kEsuw-Qtb^_KjMqFH($H`c|^w4v=_X?;{8bT*#ZZWY?YEQg;op~Tb zq{sV1Z3>!cjh~nzKibcOBDX!DD{JFj=a*j??P$LOtk-x=-bL|lHrnHJ7ku}?aGi)4 z$;$Q}c`~1y*#wr55{lig24k?hzxb3(tHnT~3!BQRdMb;lB0I-tbxRfL@~<|*Y}?n} zp`JZ>{Hi$Jib{fCxB1j;z7UyZd9Frt^7Jc zBevzS%zQglaoSzJe}mPu-8&3Y#-eWX)SK{A$WtQ&U}}*}xQau9qPdzK8aStV+$fb0 zJW4g>^D>%OuAWo4=Y~gEM(Vz;<>}N!v~_JBf7S{4>|>573Aw0?hoL#!du>;3Ddr*O zFoKHL-7$ME{0PHe;{b6TIhZ$qJgu!P%L>ZamGknVG(<-1|Jt)0v>EgxJ2^T= z>1ZT)O38KmS^RP)x9Pdfe-C}|(J*Dz40kuSRf!Q!ycpA2cjQu<-Y+6(M}w9wdzUQY zOOlT8BclCJJE*~9hft1-wMCz^k|SOW0h+RKAZZZ{d^DoaQ(x61u=lC)Eiw;Up(Yc~ zj|{H5n(Au9t}R@%x$=(Bb8VDz3KCwuXeHpgVKl*!P3talW)f&4Q8!K0+r}1TU zFqU8AEAc~1#)gL1K)Y$n2Lj%fXv@im%MQ)hTpGU5Y_0ehN9kH3{Vjuy6)E(HjfSJDX2d&?mN2CPUW0wZt}m+#i7?03SGJ2cMIE|y4>*KV z#0864D!0f*a1-a%2~BMQWCiw_n{)5mT5a@-prDK`C20q7prU=J1{&;!!^XD1rTNUc zAs;OWzw!1uX~~a`^S~onDgpvAB7_PB6&n*9w;?T4XD@<{@9WYjslup^`p1Gy6P{5RrMP?F5LZeTOw2oIQjPE${4pm>Yc8~+(TDk zb{w{a)AIwOwxy-xX9{mHBzxT|lu4=Yu((~5qnO!fss2A7-@Ke1H$%Hd-8Ebrj@Pr^ znznSCeOK()$i!K0o{F6a5#^TkFSNqb)&tF*m(ly1Z>dZkj_?n>4)XmvT|q-UTDlBFLf1*N1Ux&xK=bLA)}>w z%X#OaW;P}_Q`2Lq=U;I@!7yAcP)pThUo2i60MBjOUbB*h*V(}`pQ2)Zj@$-ih$P;k zI1t9$JDw9S$v)2NPQ(VuZ%-sa7pM0)ga)!t!)5X+e|C95K&sTO`mz7ykGYMXv?Ecq zg`auj#L>sCf!46$@|ODHbZO{FXp;2RJyph125-6O94{t zOjmd_sTAfqA_woCl6q|1LvyuB70iXJcoZ_!J_&zE~ODA|YK8Os-cXe&sUI zK*Kg~I=&=uwnDT(Y-Z^yXWil;#~{V zJ`>bYaP2p=eCgNvMr^FH5+7KCoR9vGK=Yo72Azqr-+2o57{&LoCH>ECFu;AIQRrT7 zDw$FdMh@>kFY-X>hPbkxbW1KL1~0zEEAs+ql54ZyEPk>!J>E84HsOu8>-!h>IIg|% z6srW54Yd@7P`*g=o6}0*4s(wFHbaVbJVcd6G=%&<-XIj@cL?t`2-QF0SBAjReLaL( zxqI^kW4G=pmh{hFo(W>|mG#9{+vBZ2gZhJh_M?t@UoL23p#=O^^?2Wsq?qb>>P^6a zSiimg#hRnLnz{jTO&qT1?w0>UwS@Y&8YCzunnm!mqP)-(!N0(;z=&5CSo6&j3U4=q ztwG)~t?U$*FzGnEymw=+V*(n8RIc3?A1(S!#8;Oz?$kS?l!sBa|I~7K%sul8f#ET4 zA%&YqzeU25r<_;~-}qdvS!eft@ie+b(mQFgx-N&u=x^m{LQLOUR$bFdTn*b7R5pHZ zEhjIczNMpSy*eQMn%yosOzFIFF5lH6{v)9oF)DrZz}8sW^s}PeR-`(xPIZm`EVQuq zpwP)S+AaeqC9Zg#Zzs?psq}->HNceCFx2q#Hc~_6Cb}=!!}rd2^lOW)1v@Fh#D;uzWG!J5BCyk>+>&Wc({nC93C)Yv?A1tg1o3E>9NV~#wse+l2nd@R{RP5r zN^iY_R>r5*#4B-{1?rwakv38YO}F(sh}+@My5gch+xK86EMa(KX_OI%-iWgm6le74 zNZchq*3~AB?P}xE(gqDKq;cCOHuDUU*fk@D{3^P&ZbF`T;7 zD?LLZx~%WNACAcWh_hY;L?3z_m1VSpU1Arn4&&OOzdY`3&MLJ6qc1kD`P0c9A$gLr z(WaexJ=tASO$GNC6JNsUeHYfZ8}O`i?ro5DW7M1sq^sdPLoy5CmzB!>vIbS@Uab+G zsaMC?Yb9^l%Kg57Yve+_3%AvP@JX`8_>JUyzxj2m2=R24EBYMeDvNT1$7q?_4Q(+QP}y9xeH2wO&RMNqcDUr(UPs(cvH2AHNEU)X6STcQjMCW2qub?0HA=^f%?j^rYzBxvwv-d*V%guW zTGW2hWU<%HiI#!wg(8s63t=>p*~wccF-dlRkm<`ZMqk}c0LC7FG<}xly>!%n7*1hW zRjIf-_X(dN8KX)sV-as!JJ7tufnZ$SH7{UdrNSg47?Fv>q=>yM20Flp+07bFH?86U ziNm=|FV^iL%Defi@QEnM{p17W11J=X6uSw1d%yhLcUE>x1NHk5Pe`CAQm=V3mUW1r z>H15fbdZ0N1I;PSKjSn5;sKoz}M8W>sVy{ z9_ZFc2>9!@7^~GAj z%#o&-CHKQB;*ye6e}wPP32oKewvco9u^Rtc$p+TQWid;9s*z-3AhhBJNz4kb%PPNO z&y4WOAgLbz7*QCQHcDq8FZ9s5k#fkw$e=`rPKHn~S1z*$)udVIc=Ogt2eG$;W*hg^ z46uqtfJKHt_owcdcFY3_?`;?cw~}N+u{T5q=4aB28GKIhu$k2Ua7-j#dA^b_cPqD8 zx>&9uq86+@-+;3x1iL5A##`AA2Ly(cmMpS`pLOJ=Gq;&Tz`bAn?yx(Okvwb2@YW&@ z#$$aAH>!gy=1!vwGgg>%9QQD9K5ucgyH``t0Bg{0hatZ%A1!YBTJbt!Tg-0tm@IYs z?&a~31lpJA6&9u2>_TNIyH^SY;ZHHh&|*b#q%PS*OF*QVZSBXjxxgia6H_pS*cffH z9B=U#ifmedSK?Xa!Dq;1Hlb2k2Sy0{L}>)9jv) z9<$G?-}o1t1zLp&Q}I5fk(Cd;?+1tsO*0mjadASaJK{96*XHiP)@=UjbPU>UNNg$v|K!mgzlP15?daJ9u6*N7JqN=@uN#>uCoEi*wL0)6jo za!jLOC$#Lp@Uu&#s#67wC6*(!Q%N6paxAga%VtJh;>YD&Q4tz~5ny(N8Y8nRw3ewf z#zWR4$esUQ5gGz<)!lB6?z1li00<{hI@ zifA9HB?Hq#yWITr8|<8{O?9K=K-V94e#C^XuJ>(1HeaC=k%xlcz7oKiD?=OxvRlsva zcBy{c8XkZ-Zd4HbCN`xoq07%2g=@aZVAm+cP13O)FB~sTL>{CEi|FG_#MwH^9^sWx z?&r@7q1g^HxXt5FXiG5p?vTX?Q|uOx-yx@v19#+QHz&aFSw_2rZ)B@xQ{H?7ENdIB zs0|m7)C{5nQK9q;8aPqV@7$t8QGo^vTZXfTAY)PW6eld$RD+My5fvXJ3&jnSu0)()p) zdy3igq+kfW30FE<2C*hCuc`f zL)(9#8@7KaASPxOW|n_O|29?shcr;}ur~z}(E&)HMC6r46|_KwZU71Z8#{=}7r+IA zb_bvV*f>D`z5r^FxI5qr#s|FjKQKBr&c84^P@+5F5`&HN-`AP{Ga(!M-_Xv#Cm8hr zv{SPE&Hcdr7yj_qz(0TL-v|#oBdEp`AP7wLx|?_77ND_-ASSE%q4J!jr?N7H#4PZo zj!67S!|8I0&4wZg@*BU+_Z5UXvp$L>8$ z7t4enrK-do)Z`+7O#Qd^@D$jYEv&YQQ5$86LjT|=Jgw>)z=JD zHkGH28Tnf?;#WqsLS;9!^$skF6f(SU;t6fz3+oI1qktl_E@WB*hm11kM6_u^KGhq= zIY=Biq+CO9cSjKkb*&{y)n;k*Yj&WK$dUDfC$~-}L6vv`T%%WItHy*2i2C=Uzk3Ot zA<1W36{4udyl*DrzN`@k)hNGq_?lrNm!j=<4S6FduX)=2Kv(L2Af(|~2dG6+i?UUJ zmzSBivZ}Kup}kd*&7ryw!?H$m#HQG3b;FqPsPu@&j{EfJOXZa~ML4$GBOC#LZPKax zU6Ab2PSH6Fg92|{pzUsj7#{KX#IkVCS@!8!4eTvG066u(KDEJj9E_dv)N&em7XgMF5$iY7Ab6f`_Qe+(jgGcV{NeKka zQFKFaH+(_dDT5#05FdCVL}1gi7~SRB`pJUZT=23f-_qg7r`pQ9_D|mYO%ocX~Rj+64oi>KjOmBm5S~rti?shIU>5 zlN(}Z{CA*pFtYuVk<6ekbO2^3B{3ycVL1?_0Dv06&h!^R#6`-=M8u$OY2s}07i-7C z#`yQ=zri|oroWTx-@i2#1dLL$|BK1{PjdY|@c$um0#^?CR3gqPpV zM>=#>cX3ur3wYYOjVCX>5SIF>7dzT}8l+jM`W+KQ}H@`s8ctvEVG zBV9?JOIr@AsOjPcl-n;=`Bil5fvkx4;P+AZ_Naibs5t_jwzFN3ms6;$(jfXiLJy1l z^P?OA{{sICW#~F3+zMg_QyD{!)_Sw4EPr-!-4*b(ULe`F*}~AczRHs}L4zTU%ND~F zyP&pssF^74LL5bEp#_Ia4Tgcd-}nbnV;EsJW~e}pi)CU&%X%y(y+<(s6JgmS68B(e5GL5)j{|LKGmnMEMxB2#6rxL9X$Q2aXV@bKn;Lf=x#} z$}Q+t16nd^4;T$c734VNI0W>u>UV(;pH);^^^kBPYOsbp&2l~h4G??~o0}5~q)ZH@$CHlT9EsI}w0PkIV!xgZt&*L| z$=b#*8M*`kzteawv@0owgZ6xQ?K#kP@q?+bf#ESS7-^%*Z_Tfy$0tdJE6;|vp0rq< zkU}%3ZP-+M)1T)RY*M1202oK`cfkR=eD|k%mfd`7f*i9(s@8uj2 zjrpcCr0ep$8SbBNzPN4h$XFCbXm8#`HVBvc2AdB0PMuM!)sAhozcLJPXbiJyJS9cG zlY$kDa!LL#Y4R6B`d7%ZvoeFA=|1o}F-i4rs&b%rIsi@r%@Yzh>;J_YylpFOVh` zU(I1xXF@_;yugqt!VV)x*F;k}GTs-);JbrH5DEmmTz8!5h%NrgaU%G9=)|85k&QHp zMT3=WdY-9Exf<)n zh40wKk~>luRs(8QeY_Tjf;`deIzlun!;o1c_0inNKzRjRL6_Ww%6u6Yxz!94@~{KYfnBc=cRm%iqIZ&-l3QKsS)xY4TTS@_vl*X62&3x(7)vj&sc*H~ z!Z?9Z+lRBbr-u-{!V`i;AQcdLUJO^2hTSm$g4M_B`2tL}s{>Ll%+-s5+W_pdx$e!< z8(`oCs3!=3o(!Fmz>ulC6ri>3b$WC*W)XRPTZU->s|c*BG6$j>iuQix$B>g>4E&L% zl(^PGHpcK{iuLe3N-P~(GHMfMug_Y1XpNQQ&EmJ*w`m>->^$wSOTh=`)9nu|u%gXR z1}Kh;N$w{Y{gPzfEM|-P4^o4Q%EYLV^L-*lKoVs6e#(U1J`?)hii$&X_T=y>#FYS% zSJ>|G8H+DhR0{_TPDpS_bu85gPq}NF-<0=L-CE^olh^7semjGG2k|+#oh{Uy|7e4- zs5u)nTD}oAmu#<+RLZ{U)$eGfR{vk3`=66s-|QTJFB1QrgOC+4 zh{EyLI;8YpiNOK7=Kh$AgXyo#-~x%V0g!Y8xuf^Ms6_VBw%#K6@eM&zgRj9O%38>GaMz zj6@Da9$fNvxXn0C+zqpyap>7c^rV3BU94!yt;<>-IGu$D;*7KHZ|h&FOvHZoXZ(c*(>Y+9jLLH?$PONHP?4HWUiX%Fk}b)V_8&Tbk<1MiCj$cW3Zt?`szjJXeOA z(_`vnTo4rm|vB0I|LimT~h`=7(ahM5*j<{x<&5Y zS%<2yb|EFf)+C&uzy>2!KL~z;f$YnU`XAV#&E))&wlCzlTaNwZ`9)M5n|c#s zB(Gm0f9*`Wv!-gKk@?{*uFdk9pslo#0t0(E?U&E{Yxl{2*X#e>g#+3Q z1XQ4M{L4S!KLUn>1$6xaj`~sa|Ek(R1$_x+2eeMN)pg$sIdWmRHd{kuK8ZXb1*8B9 z39~fI4~KqamO{CnlspzI?NraXx$|ec6NDrUJ`EFjQwE4W0F$>;)RBV`-|X|U z_SDnkOs!!=Z%Y;kqr1 zVAbk;Zv#puet==zP~J4QGveVPLjDq;D;}st^XBKOjFCY?(ne{pusZumbn^gP6oil~ zmxA0GDbr4i5y7@2T$BFos1zJ7{*=Z5mAg6|?}yH({-kCGz+M9q(XjsypjUL#mcM7i zv*l$6b$?T{*k*pw5jX?C>?z=r(A{jhi~2YeODa9Ckq4PTB__cVSAlJhCa|ewpQRb zLb1>0><3(^G`LdGlKQlT&>m84o|l~az<8BK2y&P+`w1Ayhl8_EG=Rj=Kw4prv68(- zekq2_Uy4Ls3R5hTF~%W2l+9`3BrYPGU90(3!-;%|*{M`Fi}8Js<=lbCc1C|dc4g)) z4ZkU_OjQlRIv7ruc^C*L+@>n>za$1LC^Hlw3Z*HjA}cKYcRm0(*#1@FWy1hmD1RyX z5APZ~)$f+Zb|$7o9PEEpd^`xa4|9Q@FaQ;3`xzV?#HR=j4dCGT+q6bkLDssD5uxKn z{b&haYR6=q3ajskYOfk8`5cw zUxkQ+?eB%}KhOQ|3Ihp}_X8k;`Xc~os2qPSr~l77$oa2Y!}TwP*c}Og1NvOJ!}X<_ zQSj4E~bgQ5Q7Rv-R`u}HV>Cz`Azu367JA~xwajfv&lZewGZ8LpA} z+++AH%t2ZBD#70(YIqG(z{)YpapffV83onJ@%L!Q!~1*onip3c8q-8e->QAFhrY4t zX>JH<#hczHjc(_HQ|2VzEie(Zjn0vb5$0dsCF~JV`R)Eo9!~SAjcalTkL?L9E+xEe0Cz<;8xaM zns&?XX;;mOdNE6PBy+v_vy|pKNit^ogiK^6Me(rcr+V6AjL~TY@O4kVs8d__t>K&YSVQsl#rOETD|;QN+L@>_^G(?Ni_IsX5N-{f;9Bo`QK!oi81gj1j6H(~BL! z6xu8J4p_=?vEp#RnsR`To5GsX;EdZ=I`B%9y;x&ga$Djr)=rcsELcP~i=7tEI`42x zakH-1&g3LW&%b?IRhn}BU-I`K;mXOz`A_~b{q3>>%FG3)gZw5XA*2AB%mpV0aQ^KH zL`(Rgg&AZFot%X&3>}G>n3?~(SMs5LL6UjkLf9Yq&BpklOyF2QJiyo(89yA4Sc#ZF z@*Y%>2hQ;2@2`J}VMaI>PA(!{&=@@c>#z67|4fJpVweVp1PSGX7g2Ej%hT*X+T`zn z|F5~i`M3EHu)`DDIZ-^hGU#zWIL$=Rz-CQ$&;-}UCJGh`LE{rR0GveP6YTz)5~a)M zAC}f=p}CIhx=qPVT*A8ScbDWt z55mc2@GQ(fym|f+JrK!=_kqOkjiI`Dzmi_9zj91ax-D^LqfTaE{kn_2!ah3X(O~uY zZee1jC8M%zZZt3IW)5w$Rr$+&Hm+fR zYo69k04bNu^C!)+>G-vM8lTQ?OK!0vSst*DZptTFv&s3p9t=B;z4LwGy-&RvUVNE| zto$7OoCM$fcp!Z5ad$Ac&F^UlIlZ^`{1-cBLpOrVCPCw!&SMOyfI}!5ee_4A?kUXN zzMF>8MW?JvSZ_K5iZHUEP4c?tG@H zcsJzuK~_0@Ez2|gBUmLz_L&RUS!JnJUbQ1}`n|NeAgzezLsBs4{A(om4>)rM`-jLC zI`j0S@>t(IJ1`&Eqnn%e;0`cpL*JbrLKS@WnuG56k$8Y7?64`Zs1W z;1`NZ!-R!NSP!4^PSY!lvu1FC?rb@Q>48ZGZ`ZpapGTNyjviWORy>_7fo+uRfffct z4LrPNT7%;9^<6$Pm*^a0f?0EZ!l%}vBT$RK9c0uW1CQ=0`_xm++|dV6(o$B1`r|{| znO0nY3I;Nvlm^-jlt)0z8p)P`ExPFym|b4tPZQYaPKJwoPTcg zmMge?o(2|l`lmx^kYwUwp)k(z1f5sidoM{9uyxw3$WETD`ik?70uUYad%Q1Cb+~WB zS6eaXau~qjHRwbYgi4bdZxLEp`C91;)Nj$({#2${QeMus%|SNR8}}QX>X z|Doz-Hvc>3!0X{LFn*U80?3Q|@j@>)NC_1+Cf(aLEKfTn|K2bUy5H^o`e(+3e$v|g zEvV?v(HoDy*mL`J#)%n}9S}dIJFaXphD8MT9xF`BV4Nn9`RG$wyOHI95)CeVnM9hP zUJf0`a*xl^8hPl=pSum7b8}pVZ%7VBO@n4bs@Nn7iN$yka4zxS^}uSHFhq@OjOUV@ zXsgNE(azfuE`dq=$uj>Lzua5mfnApFeQC@Nad@1g^M^lgE~o@i!`*s@>dSbZNr>P> zdudL%Kha@>_TdeR)H)VLn2)hQM0pb0pDAIcSh*8)>j0U;c3{d*)u@hrQA=jw($)D` zzpD}Hkh`}dBC-HGuRwd3E#v!jZ%|I&=J6TW%w*;BjosPSrpEIFM`adTeW0)wwphpr zUYAVqEoPih9+tI*??5F=*}d5*S*wu*v+hKh=c5~sWU!xnMYnPQg=952(%p)kLJ z+`XosBI|U~$YJ8-A-}*2oHxhg_nU?%*QX6^M$Ep#flDrd2VgfQGy#@-koK6V)B8c! zAP^+*NO0oRP~gMo_p?;f$uqCQhtx>YZ{mMgnN9b`C!GaXeE%f)(d zpOT&JbH99LZaSG9gU|1Jx0F)zyQaW6HtO}%NjN+GVGUS#sFYRaeR1Lxn;pmax3FiNxJJauHmeBl)h|A6{oZz7)pYyS3VYB0O@cTS#q^UMA9q`=BKC>Xr^#b zzab8ok%fgL97OjO5f-+j#3B5yB)S68Wt_y&+RDe+vMf=}(IOiCA_v#Ee@Kg`wL(%H>4uYiv z!EA`{QYFnHo{;%Zo_ffy2<@!!{vg2Vb}YB}aopM~@&0Z& zX}bPmQU)~}*>3C4Kdm9291aoDTEbx%i>eekfm@*=ZJ3>2_IJ=ZeXzPcP(I^oVL_(n zY{9zr$gT5q$FGXArv}I}eBA)GN-e^6Ag3Yq8B{nr4vY#~%b9xkl(ovEKAYOH16IS= zi4@5t#!D!<8cFlL&}%ff${ENAC3&<<+(Ck#*mFWHckZvoGg8p}pDtjU zWz}rszv@Ff#_bo&qUCPYkyrj_xuOeOPH;bcl3(BNpaKp1BPg|7U$%Ry4doso)4q;2Yq zA&c;G$x6-?LVt|YBA!Ii3|`qvS#U&)n;E+8`|2M)@~&hSNH>~|QPF#DCJkPan{tj; zFRycx(`%PJ0!PHJtSM!!22C4OK(cSiMOOlLjiy34R0!B1Xtn7b6Rlgch`ZEs&6yy0 zWTyGtNo<4|fHUqlR9Tmgc z+awB`okrLIsamNVWT_g-uep<+WEtmq4UgZjC&28Xmf6XF2K1)%*u5HD0zKuhe-y0t z+W9ofR=0@ghF!KsoYGbVeZFrWSgz8bJ(Rv$M+p{?i{6wk;8M<(Sdy^ADq3ccmhOYJ zEPqTc$iW(s^wDy0tfi%+uV;9UIYPs$<8WcCpTAQVAQJHW!}+p5vjbnSq#M!OMW0?a zO3#riJKK1X(iBs@JobQq2;487Xj02Ms8B5XqBGuDW-qZ}z`4GCR5GHFPqE}T3z=>p z9iNA$)vcwVw<7UffewFghf_R*1({i}QstLBJB9x%C@R)=YVd8xo;0pGXb*FP-@pooin=6|~^hZ6|pFHTSsMP(o2$>tdnGyVGQ z@d(`v)rKbVI$ce1i@K6xZu16;@~5L+HpG#NJn^C=#LZ2WDhlzGrk(dF#UZ`lV!j%r zyr(4>E|LvZ^>Hlb)U-X)MVlR7es3&+GE-;Rc+r_5yncE;j|uR`pY00gtP^1l^UXOi zrsKuuy=_r${Ur%FMu8PXkL7z)h%isz8|Fs9-743VnoZ!jvtWd<(4Y`}>XN|XOtWZE zK71U)uoZLTzBtYPz!g;k)bU7@MTL#D#xIDzg3So?_xO^CN05cX5XoNo3&Jj-pTxKL zyRl*Ayn+$6Vm`2LSXSxH1h#{Q@x)ZJ>576q{}j`@B87MKH%&$8Fc;2SC2Yqr1b6g9 zCSRN;O^e8ZBAZ*?iGqjp`gHQMZ;*L*1?OZII&5Ud`~n6MQX8q-xQ7=DYJ_Rdr1Su3 z_C6SZfP%G?Qgtx!cU?O)*L{}7jc zt=PxkhKvQCkl1r@|M)i0HK52tC_epbBIOK60xXkQt0w+R$Q;Sb=hbSk#|Y*k@FAyg zr}PEaG1|<2V)edMn-{$N7iz+;f)HHsxKu)vjd6N&qGl~yHM*Hk3*7DnBB$p3?DFii zXUze>he%s9J$GyM5%n{^_D8izwN^JM|6w(&;{FKa@br1`(%Z=)D#D7$o&V0K8YFCi z77)p$rhDzGgwvl~L)gp5;MhX05W!ks)_N<6+VE}4ihi`_h&n0Q@zlm~z}f{Mdv`_^ zkg$PIpJUEPXfJy>BGoqHu=Wb~PT)JCq=1{K3D;X#EsA^0D!0qNoC7$qO?LV-W5ZcF zoNEufAQPyaSv0pF3jLr5+<$yCwh72&wvNW4ri+on&1E^RO}8OdPt&CRIFvY{EYNBC z(S^O|L@9~c`^XHY_$U$0dh3A;(@b)QX?e<>xg*&&O^oxuS zLTOH6`>i9B+_%_I+WO^HD`EtUi(-wSgbG=pdPiSE)%Wx*18}z-qEWz|pLp@T@QhIC zG?~`u$F3)>g`nk}85+zp!`Tdq37^)v1*P>fZbe6Y7+tsx*2`?7(S+C*%bI#WX?W>y zxrr#56-!@8z9Utjh>J6KQEgddQXDKCn}ncldBqe8w=aNFMs5$qNY#@-2|IE+Tz4Jl ztVyV=rH~AYy|AHLG8mdzINQ6;44+3{TGxnrzN~JMjy)G(=!C$GT`$rZ?8W2ziDGEf z(yHkE=_KQ!tGBzBd3#>B43)&ycG=&bejN62OX~%5eX+F)&-xg0MlX|q4x?9_M-M@d z5FI-n8T>k@-UzH^?eVw&rrBH?4uLdsIZ3 zFbQQQh<0$jMLE9}7k{poL1#^DIXYR%ChWbGaQoPXo8D7M%9i=^ zyj(#TNzijw3GWtGeIBVzsM8HZQ^@N_AYkCb%Kn0m|J92v5C#bvzP2Z^ib2kGP>aNn zT1FdE*+Bce=O)#nbME%jm_K#oOm1qYVj9Bga0D*&dvMI_*7G4rbNkEE*owWiBP3_e z%5kkQzb~949oK>mjoGTepSOJZ0R!mt34gCv^#%qO3S0eJgy1R(N#jl61L#uk+4rvq*vNmzz|&Tc4)}-Ys*w)X^9Du-uf@pWFt5OF7Q)cnzK{ShX^aKD=2CB^$~Q$mTO# zjrXMLazOW2Dd=aRc#K4#!Gu~Wx(WPh?9s>jmPT=HIdq1>h9&mN^JmYjxZ8S<>fIc`#eg z>=Ml$g)Hl#Q*IjB4KCbTgmMc^9ZOG&^%xuSw#+@Lrx0PLy4I@K%?hFCOY9bQtpOfb z!!^;fB&T*aY+d7ivAtY8TZHFPpyy(Rz?bdaty#OU`yStQ|EO@{(G;*HLHGT!#;R@I zWTz?zwCoGgFd$xlqa|cpOT0BD?Wu@2NOA;UQz^t?sQb0$K3mF z1#ZQ7#RP?*RTe^xh$&AmiwTXK^$V|C29U>|FRg=sqNzLZ1yp~MJ zg~=x9LyG1b4VWALoQqjm6`_4+Okbc4!zJ4caQpGbpF^k=V24a@Y{QIf7D#CFQbu%& zvS6%TfT}I7?w{1L)~ysC&!KAj z*!_7+RCm3#kW_3*ox{pAcbh-XJqDkg-gA(LGEJ9FVWxGJs})cgnH$rB;Ypz5;u{LCKDk#+Zz zIa`gjj;~Z_45)_A^g_7bXumkD(*ud?&1HtVL^^IL2@cr=y~I{3y*5Q2*gS26;Dz7x zoV*ebcl^bAiHI1-$-h^zpDS|YMw+QN8F)k!C`pRj8HY?yDnvc+hg?rpRVq~<%uST4 z88L=dlqb3d_)r_tdrau;OvLJ)`BDpTx>E{n*V1|Q_MR2X#mYsL7WLFa0Nvo+`2Oib zPGem;@g zt3Zz>Era1YBX38lOv8)$Lk;dT=x9w_l~YwO_K}~*C6AC2aDv9HU&3`LBukPxA{n~% zi?=9S&ydkGb0D?=mdSIJKSz^L=7J&{=#!Lnjr#u&TlW}TN&B}8dt%#mGVvr?v29Ik z+jdrxNiwl*+qNgRjfrh*=f3~%gWCK3*45S3tGYjQU3IPFcb?S2;+5(>l-s(sh=TZJ zY%GUd`wbal(*VR}R>s;R4lPm9ScFi`@-oJ2ps{y)8O-qWWE2^zh6;`$#P_|{6p|mc zWpEMCly^p%gHPaplZu_syUA;9%ZPpC~Cs3r6{+yKAt+3apYv2 zrJA!xRi(Wh0{zPXF0f1!vh>g&mMn0+pA$JG&R~Xoc?dC{3@MGujAnD3A#zbepO50jOtGpaahZl0Q;u8L8{^ zzq@{gP+^1KuR!+mhd9d8%Zs#1KxbLZI3 z4LcTh?;5*l_aic84QksUq7Z_s4vngK0guy>*{^W{pUxVhiHWHWT3T7PPRF#6K zKU8{PIOa7x(8zYPHe)Ki9;^|LVY=IygKf&^9>`hLv>x3JgecBD^0 zW+7oLDJR^0%lzh+4APJ5f4O;Gu4V`COK$*KW_nzbCMfEVc}^b-q2PIHVu(P;g*USC+}+UY?*_Nu*{`FD4Q~51oP%*X3+;if zJBO8$Ly4Z43eLa$;e8du*n6`$uZSXr)XD#1=6eVA^n6$==>uUVJ|*FQkXkvkMl#^O=3Hd!)V5D{`~Yl_De1{xuT= z7Y!CEIvWf$7@fmH&YdqU9|X=_PdIR04HB>mT?#9Fdu|nN75T0~gxd(BDlGH+7+3Nx zd%bm^Rz&A=tJGU2P_YDFF5}UO+`At$TY&we;qr+yf>HdZmZ9RgoM2M1M22Z(fM>Podh003ENU?Y@TvDtyZ_hgP7dKD%b2s|khU zZ)W?YmZ^sLExHPb-Ou!(XXq<5B9I@PAM~K+*&=>&IpLqDdLN3F*{fgP*0BbROke`{_$?#@ofYnq$Hg1)JeD@Wf5!b$(UQ6$D2ZaVfuIZq|_3e$y9QU7(1xMF&Ba${;E&0BO$7Pc%JazRT)Y#PDt8%~*wRp_MCM4kHIdOq?L@Ft?!GM0 zf&3VgZL)hI*K59p4;<to7EwY2Jyot`*nUGZ;^LVAYa(RydQnrS2we^pjf;X1 zO8#UOT-sB(c*q5+PlVBQT{Uszk{AU=aeKDapD=Ut!aMuDCD` zsW%7SW{@R3u-c~2K=`>z$V3;lF0t|E`8u8a74Zz#zca=cJr$ywG%2R8EG%tyG>u`2 z)fmM#k_Ev-xNow;<^|k|CykU=KCYU4mOfqRK>)vCvERmhUeZTS_w?k{B?Htj9;T*? z$-|ot&ro7gPE7FQ51v;wSEr(@n)vg8RjmM7gki$pH+2KHF>lau6M_HpuzwXjbBC zPUv+xvs+%NHBuMQ*qAab)rQ|*T|}E2acC;X7?u@ z)Z=TOexS{rN!3Z^pW=$ymT{kUvmhvg!Wd{^G^MJEvBXzhSd9LZuyG7Y;@Ntlq7r=o zjTa&yes#QTJc47tD@Q)BTKBW&9a2baeQMVf%hK7e29Fi>()}`lh-b>a3W`3_?5QvE z6vmTz$3X=rKJqva;i_w(knxAr;!UA?yex83wxWMpJ;j!$x*$(H&s<717R8^;vIY~N zH%57IwP7h7@}O22{`dTx;Cirf^?i!N+Rf{%&tIpd(#ua8+}J~EvVRm5XVRm!@7%S@ z?*aI~U(lWZQlD9a0>v$z^v0>|VP47zyTs@B*SXt~((yO}xA2vCR=z;Jt}yWAq?8nTMt=2d8WCf^)LWefpu0kWW~ zsH~B4gMikwzjxTx@ElE$#S&*&0Jy-sXp_mqt*yF59`K4FesW4nvx*xHa%`@WNBGzv zW-Dx?d&QqOVdVM?NXZ6=_x|}Azs>Q&@(2_h50bxSArw*O(sIp|eyRpuZGH|0x`PSDjqi4P&G6!xPNo8OCRlo#MYDMa*5HH( z1KFp6xrHj1+%x$P^?Yb5(QHBShtvH(M=TiV@%OFvl=XF|N zzOvT0Z9hGTW$)Y6YwgG0?$acQYmXn=F5)MU23+h1&SrBDIF?@ZnWkH_pfaZEZDv>x zO(pBdEk~+?VC;G@mz~LLk*azN;rQna$onTJ^ud~~SAw2J4+lcLZ)v&C?~0vGz%r%4 zcFZ^!N;ktHG#_}jU<%7A@ab90;d(v zrpyT;xvof?URvGyBh0-$mY8_EKijP#5Pv~82fBBUZkxrym^1GNa=v9r=8Ey?rlRBl9o4>#76IRw*rW>;&CCS_F zpdmK1Gp0*Lxk1GHu1Z=mHqd6Jk_#74HG&5A_fi5}D zcSuJx?geUN)b@*7_F<93^{ey>U>bBx=2w4ngPkme-2|1bg2c;>8X+`zWC&!Iqu}?9 zA`zDs(g3ycpG!HAUL%Mk0(B6`J-0)sUNy$?7M2A@#Wu(7i~y2BVy{&_0})vK*S?om zpn<_Mq<~f{pz(s^rG(Cj78X`LRqjwND}*5wVk{U|*lE?k|Hgub&nWC{y9PlFL3_pK z9w5CnX$Q4}3&z($w2bNR5W{CAgK-ODhkM6vkGi>~ad%@!%pn z8GB%Tuw@`5tHr4W^y?HJ2=R=`j3t&yMdlDo{#1&S=$F5MZaN4cZX&DwU8BDm0QE?` zDjYDLva99*Qr{iJG?eWE#6>^^to*=Y^!14Y;g1~ z-G5erzzBTtOv30{+8p)F+47(XS3G*G5-rq7@?#u%roMXhH{B$!?4zyzoC~9W{Ox#RigfAklQX6v`?b-HRi51uJpQZ{2*mbiocB zE!Q@z`S5HV8mceEE?Lvkf^x1quJ+~+H|tP!^C1>Xs2f=)^Q#0= zJ9!i{A{o-*gb|n!ez6ed=vH;py6} zhf6lwQ!n|X;o<^ZoK6wz_e9dAK_f|`tA377Mm7MB$r1U`6pb65= z;ELU)!N;Di30!$TBUo3zk=BxT0~jp?@JA*hJW0Rs1eL2-b^Y1|lo!MepY$L{2+asf zPt-*7sJDgl)fyX2dv1ZvB3m++(s;-@?5g_q(tEZTp|3b5<}>5lGrvb%a6R--6xn*H z4z~{7DPaydHG|<0Q~z65_fTLo#S7di#XWWC?EE~snW>U+WI2eaNH*&bR_#}Xs@h7) zz76B2fEgKFw*0m3&TJx`jgZ7cr?B(-q4Nus8BMcDSfE=;%jq&o1+?1tg4^Y31iXR1 z)jM2j-3*ze7Ur+~=#v_G31+GjqsQ7H>uK_@4AF1k8{k~XxNzxGNCXUpaK|~>SF>;k zet1^XLyD!ym4ohhfmwGqX!5A<9B{lN5jeD2wwN%VSb$-TsN`NwP&9=9s`X>~!OFpz z%ryzZ4JoW7BKOrsIh_ka4#JVFC=0@tyf_IW_jT;&3rdhIGzG$j$?^YFFU#?7y=-&C z6bK;*CdYrx;xn`V_%Gec!Sb(8|DXF~{sT=bP&xk7(fPlOX)=={2pT8{`#&a~J(+VB zga`=FhCR3L6y{q{jP2F_HB`O;sDUTr`|CK6ZaI z8dJT{b{}rLCm;AO4++?NUi$>9&0`FY{D3CHP<=|DP3bzgLy4bOIP8;`Z&wb!m;6qK z>pA;mZ-E~l!lEn(4C-}`?@2Yhxr)a)4%8#Nhn*ak{EzPg?A`E`l2KmA`}?#nM6?I* zH}22LLss29nD_juU8|q!_hs4H+5AsfPg-wq@8NIZ@6vB&Z;-oTZ;;RaTiNeRPr%3L zE&LjXo?L%}E}VY#oFco&NR+$zelx2M2WR{3hlNpmh@)O*SQ7tmpb1eF+d31os}xUZhrec}6g{)x$k>Jmrxx>l5j>VQ16Y3eqJAwe7d| z=ws=EZ-1#A+TeZYhDYjIsabC*XMjxRrR@*&ID@7Z+MCIiZ=Gaa*d-=YP$zSaEGim% z1_Ca!9>S8m#|3CDZ5f(M1jt;mT_SpgjI&=gTP5y1GDh0qUg^_ocMAgg!n$U7%PAe` z!*#+40KofM$I4A|`Xhq?<9v|tk0^k=l5ltX;pKBe8b862%?$&baW0583(za9b1)0| z&yM%8J68Z=F4%xQKRNKzMJO4v=Gj_i`)~8gkw!bJR@F3&^N)So@YRAygBrqic(d1P zFP)|L5#GcxFYvk}un&5kc_Q_#9=ilR<0!_8kzwM)f z9RU@7FLOU~z&EAO;N1$Bf5MUaVb_5=INgpRM8c|ZodpM-F0X|@01HddJ&pyEB1XYW zMHS=JN$i3Ng{aU+EA4S`Fc%RvEAKMkwtQWA`Ws7!%nk%sFr z>%Vq+zu$0W$`l0NoWC-hZykU~oWl6YDO8Sj(l*>?TB_O>*U z0%HjINYpKzVCx^wz-zCDn%GTWyX|heghVM$kdKOk8IaCZXO;c%Xi1C}vc2DE3EIe0q_Ec*t6Lx;;e%_fV? z>HGtpV3w;uTwP6DU0qpQTU!(0Y(6zz+;I>K%tUwptI>`0yvw;_=f8#7ZQO0yJ?ESK z`aYt!-8}+q@80NM`<(P^1W zk(w6D0+PKBZ}j3pZsFGHOPnB&#Xt#nAtPc=V$cK*1?eFY0PY%d(|=PU+2DCvAH516 zD}`r=`AFR)9GALfzgRCf7=-rY_jlnw++LXlGZGQ9Xft)#J}vA3y*=mWVC26(%kD4B zbJ@K0XQ@6@_+AIIA=4=Q{OJ#8?Nh$@6>fXWz*xHoZB}MOpn*u$^ri<_}nKnz?4 z_S4Vpo1RMl#U3kvAOWlMJZ=6@ag68_g8Sx~c(H0C0#;3yRi>BiqqY3cyLG22Q9dfK zD(mq%Bh~0oJTf9Bwu7sb?nnYQ*N^21qq#xV$WFG0nOmr=?i(JwLtnwWK7TAr5i${J zpa^vy(E#W*r9%exK;qx}zn4SZe7^Tj@4NiCpUE)KX!vN8a!-bRdQ^~d8B7kh1Mg@q zXy<6@lpn7Jhg7oZ0N~Hrf;UMhs!s0XivoGdci&n0c<*>H%IFLppPR{%`*?0juVNk+ zlY@kVQaf2zc|86~kC*znLwP;+4W{>DAbdB@0!!7nJIC%}s`0@s%i(xLN@<4>)?ya- zhtDau38A^aYY-VkosbPa|s_#@RkTQ#WXM8e9#35bH$n!}Bc zo1H_8!<$WC#XZ#$>1(i)iR!x95mB$r!Cw@+Q zzg3D;)yX^LSXyr;9XY`Fb|+h!i@FI$#lnM@a+i)4-3|fbePVr(3^1kvoyRJ&q$L%6 z=e`1g8X#PvRU~)~Il*VMoeN&{*H@4oscn1#2v(gj z68PbXu%m;{J}4KquXTsl^()kpFZc@}mv(u(dIdJNrX#-|I0#`(vrBMJ$dJS-=OE$x>ez6B>mmXC+ z9}e9_TiIMpD!^O(gzX+4+JD@!vydWf!6RR-3}P33V>HQs=u=!51=>RNcq;^I*LZPlt>CRG`l}?3Z?oJ+EyR%sTXmR80uUC%C6cmPDvgY%;fE(RZ7;#rY~O_Ac4 zbWN+e1UHLi3fWJb4fm3G{MvEO7L!*A#svs^W-tJkzXHWDOr~L*`oiic9pevXcj)|` zC|lx4;P#|k@>0pu1qIW;ovjDQt;Y>G1$GKX&&?_Ku`VsDuzy1_O}3{_r?`gE*trfJ zw5SPj>r*YEaZH$hHw1b81L^g70Ql=Hv(Wl>kW%l`!rAYA%CQ2OnY4v(2O}1>dV-F7C<2SlRIrfbzb3&+&RQ;g;lQ_ ztV6gnc^O)dJUGqznael#J^(bMOH>siQlGJsT2!BT9dAG-lcBoJTvRET7L);())A$y z^cE0I$K3mfzjn!5tM1N)3f%dZijTM0v>Q7c^5IK-t-l5edZxzZ@|Hcl13_ z`sa>#bSbvpz=4u$y=b*;81#yxK0*g1ikja`KT-5FlN*rZdGdblahr^z73TmMUI+_# z-|eZSI{C#H?fAIbRNI68;fr5$zl$ykk*D`D_0D)p<|PxS{$G6Y)crBmb;={6cB_h~ zzbG`2!ez3K@(EmSpx0YH85but>w7q>^#GV%;GUsx38AoJm#m&8ol1WFNaR0y(dSDq zmKJDvprudWOFP|$^{ z^c0_#gFi17DrUQ-b)^8|!d z>0pCHYbv>RMqfrVU{w<1@K?tOkiL@bdG{u4){?t0onO6B5`?JZ(yXO{_+!qXYMN%v zZM~3-Jn|O~LE>Am|MEq^H(M!By?^=QnSGr0<$2q?&Uq0vDc|&KngL__DZqiML@zya zQv8g2L~V+?*@Q;zn$LZJA%uSK0i*Q!Ywt;Qh`SGNGef#(<4gty;}ch$z#Ctbh}+89 zoEWCzMP2mA&aX+JZIk`(7hgn$KKbH{O*d(Wc#fH-4pnPWCt8K>et1^{dz?Wf$isUx~`fUfDc(8`|syD$3pn?@=}i;2EQC-+!n| zILG9WON(DBF<2>PAdpFRg7FoWok_l5Bu!lITOKSg(7> za7QW1So?r#K{C^-{#tJ2reXJcg2lp39%@XaxF|zVntWfG&>e%Cw3oP84VT9MgIUwO z9Ki1Nuz|i6FbBo>nFBMXBtVH$q&kKxTD(0F8}Am{ma*2Pn4g#*jy(p<0X;iUXqf%x z7*vFxSAPvXnKh6!5jk;(kJX#6H2;~&EjvLFMM>;(ukkg;5wz#}HaY120+)d{mKG#)E+@vM6O=|6B8NBDJz?#(2iA2hqgK zFW!Gg_Mh;ba46chI^?~3WU!D1w+^H@J*X`WNw!aSLOeoY`_2n$mMO;onW$JL zJy6fK;P%4zSLP+JbN6b2g=n*7HPlxj15WBks*?|}%1l`Wc{zfI9k?kA^T z4R zzlKYMnH~t=tuAe%v>UO<^?w|qJ_J+;vm73j<`iTHs~;s!F2WLRRtcq)YB#RLT{3 zDHa!jF$qEhm*^{?5zQ|_iX!CkiiW<|d^?KEH-W`q*f55mO~BwB2C8OEil<~E1bYj2#X_6aW>3St9KF@G8C~jr z_SeS$zJ}LeLiAfj{^g50f@v?b@X&#up`=`y2lA5B>vM`E^2Bj8ewyb~qvu6zWjM@C z`Hc-)m$Uud${orUpxt&iInf^t0z=S|%1mY0?{A|x5?cd@s-+nIR&(6H>xQVu30(C= zXYDO==ljvSrZMJL6}sNS!iSzXVa+`+^P7fH%~iOou@-LmCR{IfQ>X`06Aj%sFx&63)wIZv^Zmn($|N9AgM>xTpEGm z+4O`KvsF+F7?h&qGT8mE>E~5AE0W&McDJ=$dvSbn0e8nyhGj$tapf&!2pW);GZXGS z?Dop#;O|8=TZLqOTsAIUV1&I3b5BzwE4ek&P*Jq$ic{xhZf-r2=An#wz!1lDdnUm9 z0EYUHLNN4nI0StmBxW4Lrb;;ZX*29bO%a!G2cRsiNYcxaa>+T;1J$!SmPi@?iwCsb zRGfv{Pmnsj(6dt;lRe67;i!9tTB2{rWbn8JXGgf!AW&PhQ(a-N|u7-zmjxlPIddM z*gRM>a-vW|1xjX$(+INqzMNJ9LV=-j9&nz{`L72^BIaYXfRG6NNtP4nwNVDgq9K}> zh?Ar)`mMZPzQ!Var#Obm1aopu*#OfT+{zgYZkye3T@q172VfEyCIO^`J-~U>)rAX* zOUpi>K7GYyGR|}+>F;$-k{2Rsd1OB6%OLOG6T3uu0iPtP8jVN}jp|yWs|if@hkgEv zc@y$Kcrg_*6}ftgoCH@Xw@+idHH?)5;YW6daGZy<`vy{6pEUhI#nY%DrS@xj!ps)t5QB_ zk~v14!alkf354%Cw@k777jHizwn}@jl!v;D-dd|AochODhA+Jq)v?5DifzlW{08| z*sahz<-9^h!zBpSBRWfJAu*ajOYt?7yw~QWeex4bj)~dx4=5YRW0(spDt2u;$D>8^ zJ$yisLr>Lfh|1UsEp7O@#Qdt`OPoAK(;)z?b$Y?0MO7t}Le)4m*aHI z!v)%{7eGJhZwkfxyEvzCL~$|1K1Vg+`&W@(Re{CNyh?OvZojT)v#Pv?<*?l4${I5x z==s$9m#q-TiOTmpZFKO7x|5c&zs%B{$kEbCNt7ysp|KHI6e}S-si%tbI4E`NoGTt> zRJlwt5yedW?9tFhehc+s8J%|s5-IKkn!a7&i<^~iGvHg3-SqsCz)qRuK(O?oBp^QM zOW)MLdhrP#p$vtXQBG(ce_w)eVjv_wE~7I60F}_Z61&NrqYFfo*EFh{Q-`72)>m6- z$Hs9Zf?H|wdTff>91cH>Cg7$*6{9K^5iJI|ximC5J2#SxhsOXu%3Qz8q<-hkQDs4z z_v#LGk@?l2Xmuw3;sMHq>U#%VZ@=2uI6Y!>hR!Wt?x|k(Tu#M zH-!)?``{YFY_iu8f0MjM_!_=vA}~leJQa`(9E&<@V`xJpBK&hAZVQCKLU;VebSoDF zASte<|4wW3>@?_UR zw7g7sTRFDBU(?-%7O&P&($q7L6d~8dmj1!M`n^2<(k1)S_H>kr&`dQBd*^3T;BV=+ z4_Lieg8Yl-J3KO^(ze;Hbe{s9 zuZWYu@hPu(g`njKy74sEwR_0ag)e6pDc;D9uy7Hlo!p@rvR-uRT zDV}bKuvJu`hmu?nWS9=o?rQfZ=-f(zYgzmBA*9EkMv`EZW)E?HyYxQeoVZ()0}KAs z4)cJlXUZGA2e|k22pzLV1k&cm@LUaiuDrSCukd=q{BOJn`Y&F*mC3AxtXGT#Wzm+) zQ(s1m=07;G#CHvZSSb7X4_=h}!i#$VuD!%EN0V4|mzx#qBL=pxy>y7Ak0cQZs3G5x zHxZQK_6N$?pBQRJ^o<;f1Xi1nGVoBq8$;(WyBKsGp?jZ=<5O7JHZF`fcb!oa>A0nc z=R-(~hhb@jh|&d%g4QzjT1AI!6UYh^+b-NR@HN7?(^$2zMPk$G^ER(S+F)H4ayIC` z>{;|csF>iWp@s1i<3RZS7|pO)0J`+VIUc}j%vr3QSQ}y|1q55Yb`v(*3B6Qkrrh zin8GW7MyxmPQB6a4DWJg{D-)lhhctFXvz7lIJqF-a@^g|hu`yp35+Q#fG!xA8>hWD_D6kUVq|=HGLF*tGCYN#6_GYb#GSmAi&i>s z6kRYb|68nJl5(AsnyO7CbQyuiw9rXid3`+)HQ{y92i5C*@HYWF3iDrILiSe0e6YZc zlGvxg*P^#0pI|3`^GdEUEZg<>qmhsy)}a{7VRCD~O9}=jf64kR#y_~{0rt|W6qb{! z;guzE?o=MdOh6*3Fm5O2q-&K5&91OQ1`%vKde^*&09-k2)mj=UM*Ys+PCqF2+2lIl zb$$PT=%R+lc)%B3^h!U6$NwD+_=hfPqt@)y6f$0*wZQu;$ZZT8E(zyekx+EtyG7jSmj69O)uhbhb=uqxb zRC-?ex0cILI4;AT*)^tNq@KWy%#yLZ>g{>6#Ygud!a-@YR>{Fa+<9g2-{p5JC0(Z! zC(w0)1no#_<}_})8U)&nnb76Ut*+VvV-4(t9(M)b@&!OB!C-BvvXdK!*u}8&*uDQA z=S(>Z;icrS`Ohz@HZPcZ{{3@i@uMbF{Z(W-LKBjCTCX7jVU2PsG+_bA9=`3nyAvEd zI{HwQFTk~yP%^|@8oQ>Lj`bF8ew(tfogF&SSLFBkNzUQ_5&GIqe9%YrjDs}!r*Ffi z43chHRh!B-3EZ8IPrA zMaD^#YC?R+4 zafQQ})_9n+J>&r|dku3KuXl}L!%e5=LPPrSMlHj6uNbG_NIT$h4&dX-%dMj6L8#}H z*b{Z=OS?+qFoiMmFYS5H@jhSy<+{tBucSh6GbO_ zt_1DF6q4PNafo1cHPgtGG_n*vABzbmGWEwZxFUc~ zWCnOeVEKyLvPf2>V^~2!wSR~VrJX0{Em-%RnF@N?Fk%Vf4r5!c|2dJZBs!~jYK$V6F0`=^N9NXqV6q6wpCfnp<1u*Ytd6cLmvX8{OLB{U!bHHh zBazUc-+DX1MCIO4pcg`ETg9>f$^ebUaTRzJafi{(_6Ck8Rh!#TCl+yU^HD~pOFZqg z7?oB8#X&50W^{%eKCXrjTS0$x+u%_mi1>(O-%)|Sj88}{rlh}Xmkj@i z%VA|EOXj-UXNy`(umooE0*bMBf1u+#0S)i zCOVVeyDPm>2k3v^FnsuMa5Gdlj<7n2orPtsseOHwlZ1|b=Xt0aQ!>-f8twHfg* zcf1!2TwbNSy-`cnFGBV_H4^D>58QfHFb7^v8u)DDNG+0S z)7Z11Y_pxhCe)6)io6}{AWAwjSvuE|!TPP4P_rX$<>%cU(cRkFf%JniVSnA?*FSJs zf{fFHCdK+Sr+WKK9FgFgr)3`1r&~#IfM2wujN^>Ko=P@+qh8KaL;Su#yWPv>pS9Ee zX7eC>=}FT$)ZtrMq?5rBq(BHl#jR%KUR5K>p6Bb#@@PMA2cg2~Y+J zgCU(7%$sJCBW$~&#~y-KV{KYUV^a5taDKI8b1qtY8--k21;4?ZMN4Sis#RnWH780f z!o^?-D;ve|kY9vo{Dj-L zO)F$?#T)uGQ}(BeC-2SQ_8C=uF(_l8dW+i>d`bAk)-iwr3hBAIIqj8+$i@pXbd>i+ ze7*4^dDH5;*s-<4cc8=}F&r@UZ)YF7T3rMT$$=Ntam&)Z(8@qwwWzNo(9q<=-xse;4e{X)GDI z-2)l&egOxi8J(fmwSVzNNd=j+4z9TzN#4HjO5V*`-;tx4R=|q1iITtR!4bBJ{|gtf z{ww{K=?C+V|LOW+N=BgvWr9={)09_NN&evniknRA3yKKJ!SPS}uq^18ar_Dz=ED3+ zzWh&g_y04$FCLN^EnJNPTsKFdgq^d^qo4iI+l8zv~ZeAWw}kq!NEho zGc>lg=9yVEczqzyk~urGd9Zzezkzpx1EzGZ&1n&CV^VHr<)@n$qyt1A9~3qM4U))$ z`-Zo4?waDCxPeoLlsFVoS;opN2PXvvh(?jYxOmYq6E1h~aE}OxX&vUhd7lcq(#a85 zb}cisVq_6wxRFe_R(EN!^X84QL9y|%^|9fK0g9zF<;(o}24Yqbq>%uz!$^uj;AuqT zpw1u>JPz01bj%S6(r<58!0&gium!$!T@&w)04z6F4Cpbvsm?=hA-fW2oR1i{8X+Dpf znXt~>3UEqoVH2Ne5qTuP2Jc?BfzxKyvoQ|!x;TG)WlMjmMiv(FR;%zEC=G5{@cV*Q z#X0d2Fl_FibagE->C7%6VhS3#^6d%R!jk{et4Z#}!eQmD+F4nKG}3|gVUB7`ou#(s z(P|5&H^WPPhG_a#RXAp+AZwS%g2`C0hADz{at2O zt%{?xCI9=Rz|P!btR!rgdHS7#DcpuFsAW*7inN-i7+Jzau%cS#vgenN@m`xU>X^|C zRNJc8;GHxbuE-W4R`S^MiYR3Zh{3gh+BCvC7Vx+giKq|hl!zH|*?hT;6qQq{a>uj# z(!3omyQhcmN07;;)!`JSH4tqLf@VWxTWkj9*IlLHu9?x%>n>(T-fIHq4vfa6%xN~p zJRa~v|2yxH%+Nymt#8<@?n*s`x0&cQ%5l8s!TVd-UUr}}!p|bI{d(GQ?lYsVc99)2 zFWXV55<{N?Qv(UkmlbRL*9R}*OhA($OamJ-3P+cGUO;KA~>{lLnOpOd7Vu^|^ zE~@B|+BKBZ{2%+UZwSSdXJRPR5O?kDRZY6=95=IvOi5dw_OIf3TP?MhNK&)`VenX>IO)H-Vr$XMP+EwK6BV z0)|4=EcoOu<_2@jOD;qAix4$PJsfmPr@Fd2r<*#sua(!)$mGc6MSO52hP ze^mR+Y7DVM=NK>o6UT=AhLr|z!afP8V!HT>Tz#8Iz99h znD;S1nXV3QMa08?;AM|5Kj4e2NEnc~As46Ey19Kq@Fv_Eb-O)$+8S1

    Z2>vux{ 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 IJ^Ok3A3tRF9smFU -- GitLab From db265ab18e799d15640d5bd3b82ace3269fb3a6c Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 09:31:31 -0600 Subject: [PATCH 18/59] Update the copyright strings and clean up info about DPIE in the code such that it can be interpreted and typeset using Sphinx. --- pytential/symbolic/pde/maxwell/__init__.py | 26 ++++- pytential/symbolic/pde/maxwell/dpie.py | 122 +++------------------ 2 files changed, 38 insertions(+), 110 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index d734ad30..b9ce1fe6 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -1,6 +1,7 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2010-2013 Andreas Kloeckner" +__copyright__ = """ Copyright (C) 2010-2013 Andreas Kloeckner + Copyright (C) 2018 Christian Howard""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -111,11 +112,32 @@ def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=N # }}} +# {{{ point source for vector potential based on Lorenz gauge + +def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): + """Return a symbolic expression that, when bound to a + :class:`pytential.source.PointPotentialSource` will yield + a potential fields satisfying Maxwell's equations. + + Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. + + This will return an object of four entries, the first being the + scalar potential and the last three being the components of the + vector potential. + """ + field = get_sym_maxwell_point_source(kernel, jxyz, k) + return sym.join_fields( + 0*1j, # scalar potential + field[:3]/(1j*k) # vector potential + ) + +# }}} + # {{{ Charge-Current MFIE class PECChargeCurrentMFIEOperator: - """Magnetic Field Integral Equation operator with PEC boundary + r"""Magnetic Field Integral Equation operator with PEC boundary conditions, under the assumption of no surface charges. See :file:`contrib/notes/mfie.tm` in the repository for a derivation. diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index cdda91a1..b2b736d0 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -1,6 +1,6 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2010-2013 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2018 Christian Howard" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,132 +22,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import numpy as np # noqa -from pytential import sym -from collections import namedtuple -from functools import partial +# import useful tools/libs +import numpy as np # noqa +from pytential import sym +from collections import namedtuple +from functools import partial +# define a few functions based on existing functions tangential_to_xyz = sym.tangential_to_xyz xyz_to_tangential = sym.xyz_to_tangential cse = sym.cse __doc__ = """ - -.. autofunction:: get_sym_maxwell_point_source -.. autofunction:: get_sym_maxwell_point_source_potentials -.. autofunction:: get_sym_maxwell_plane_wave .. autoclass:: DPIEOperator """ -# {{{ point source - -def get_sym_maxwell_point_source(kernel, jxyz, k): - """Return a symbolic expression that, when bound to a - :class:`pytential.source.PointPotentialSource` will yield - a field satisfying Maxwell's equations. - - 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`. - """ - # This ensures div A = 0, which is simply a consequence of div curl S=0. - # This means we use the Coulomb gauge to generate this field. - - A = sym.curl(sym.S(kernel, jxyz, k=k, qbx_forced_limit=None)) - - # https://en.wikipedia.org/w/index.php?title=Maxwell%27s_equations&oldid=798940325#Alternative_formulations - # (Vector calculus/Potentials/Any Gauge) - # assumed time dependence exp(-1j*omega*t) - return sym.join_fields( - 1j*k*A, - sym.curl(A)) - -# }}} - -# {{{ point source for vector potential - -def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): - """Return a symbolic expression that, when bound to a - :class:`pytential.source.PointPotentialSource` will yield - a potential fields satisfying Maxwell's equations. - - Uses the sign convention :math:`\exp(-1 \omega t)` for the time dependency. - - This will return an object of four entries, the first being the - scalar potential and the last three being the components of the - vector potential. - """ - field = get_sym_maxwell_point_source(kernel, jxyz, k) - return sym.join_fields( - 0*1j, # scalar potential - field[:3]/(1j*k) # vector potential - ) - -# }}} - - -# {{{ plane wave - -def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=None): - """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)) - -# }}} - - # {{{ Decoupled Potential Integral Equation Operator class DPIEOperator: - """ + r""" Decoupled Potential Integral Equation operator with PEC boundary conditions, defaults as scaled DPIE. See https://arxiv.org/abs/1404.0749 for derivation. - Uses E(x,t) = Re{E(x) exp(-i omega t)} and H(x,t) = Re{H(x) exp(-i omega t)} - and solves for the E(x), H(x) fields using vector and scalar potentials via + Uses :math:`E(x,t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and + :math:`H(x,t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for + the :math:`E(x)`, :math:`H(x)` fields using vector and scalar potentials via the Lorenz Gauage. The DPIE formulates the problem purely in terms of the - vector and scalar potentials, A and phi, and then backs out E(x) and H(x) - via relationships to the vector and scalar potentials. + vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, + and then backs out :math:`E(x)` and :math:`H(x)` via relationships to + the vector and scalar potentials. """ def __init__(self, geometry_list, k=sym.var("k")): -- GitLab From b05ab0d418313cacc720da0d40b925a5b5f29982 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 10:29:46 -0600 Subject: [PATCH 19/59] Added useful helper functions for performing various operations between normal vectors and vector/scalar densities that exist across multiple disjoint objects. Also found a bug in the system of equations for the A operator, the integral term. This integral term did not have the single layer potential, with cross(n,a) as a density, defined properly. Things look better now. --- pytential/symbolic/pde/maxwell/dpie.py | 167 +++++++++++++++---------- pytential/symbolic/primitives.py | 4 +- 2 files changed, 102 insertions(+), 69 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index b2b736d0..2ae26d37 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -167,29 +167,96 @@ class DPIEOperator: # return the output summation return output + def n_cross_multi(self, density_vec, sources): + r""" + This method is such that an cross(n,a) can operate across vectors + a and n that are local to a set of disjoint source surfaces. Essentially, + imagine that :math:`\bar{a} = [a_1, a_2, \cdots, a_m]`, where :math:`a_k` represents a vector density + defined on the :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = [n_1, n_2, \cdots, n_m]`, + where :math:`n_k` represents a normal that exists on the :math:`k^{th}` disjoint object. The goal, then, + is to have an operator that does element-wise cross products.. ie: + + .. math:: + \bar{n} \times \bar{a}) = [ \left(n_1 \times a_1\right), ..., \left(n_m \times a_m \right)] + """ + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # assert that the ndim value is 3 + assert ndim == 3 + + # init output symbolic quantity with zeros + output = np.zeros(density_vec.shape, dtype=self.stype) - def phi_operator0(self, phi_densities): + # loop through the density and sources to construct the appropriate + # element-wise cross product operation + for k in range(0,nobj): + output[:,k] = sym.n_cross(density_vec[:,k],where=sources[k]) + + # return result from element-wise cross product + return output + + def n_times_multi(self, density_vec, sources): + r""" + This method is such that an :math:`\boldsymbol{n} \rho`, for some normal :math:`\boldsymbol{n}` and + some scalar :math:`\rho` can be done across normals and scalars that exist on multiple surfaces. Essentially, + imagine that :math:`\bar{\rho} = [\rho_1, \cdots, \rho_m]`, where :math:`\rho_k` represents a scalar density + defined on the :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = [\boldsymbol{n}_1, \cdots, \boldsymbol{n}_m]`, + where :math:`n_k` represents a normal that exists on the :math:`k^{th}` disjoint object. The goal, then, + is to have an operator that does element-wise products.. ie: + + .. math:: + \bar{n}\bar{\rho} = [ \left(\boldsymbol{n}_1 \rho_1\right), ..., \left(\boldsymbol{n}_m \rho_m \right)] """ - Integral Equation operator for obtaining scalar potential, `phi` - Old Operator without looking into multiple bodies + + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # assert that the ndim value is 1 + assert ndim == 1 + + # init output symbolic quantity with zeros + output = np.zeros(density_vec.shape, dtype=self.stype) + + # loop through the density and sources to construct the appropriate + # element-wise cross product operation + for k in range(0,nobj): + output[:,k] = sym.normal(3,where=sources[k]) * density_vec[0,k] + + # return result from element-wise cross product + return output + + def n_cross(self, density_vec, where=None): + r""" + This method is so, given a single surface identifier, we can compute the cross product of a normal + on this surface for a set of vectors represented as columns of some matrix + :math:`\bar{a} = [a_1, a_2, \cdots, a_m]`. The goal, then, is to perform the following, given + some normal :math:`\hat{n}`: + + .. math:: + \hat{n} \times \bar{a}) = [ \left(\hat{n} \times a_1\right), ..., \left(\hat{n} \times a_m \right)] """ - # extract the densities needed to solve the system of equations - sigma = phi_densities[0] - V_array = phi_densities[1:] - - # produce integral equation system - return sym.join_fields( - 0.5*sigma + sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - + np.dot(V_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2,operand= - sym.Dp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg")/self.k - + 1j*sigma/2.0 - 1j*sym.Sp(self.kernel,sigma,k=self.k,qbx_forced_limit="avg") - ) - ) - - def phi_operator(self, phi_densities, V): + # get the shape of density_vec + (ndim, nobj) = density_vec.shape + + # assert that the ndim value is 1 + assert ndim == 3 + + # init output symbolic quantity with zeros + output = np.zeros(density_vec.shape, dtype=self.stype) + + # loop through the density and sources to construct the appropriate + # element-wise cross product operation + for k in range(0,nobj): + output[:,k] = sym.n_cross(density_vec[:,k],where=where) + + # return result from element-wise cross product + return output + + + def phi_operator(self, phi_densities): """ Integral Equation operator for obtaining scalar potential, `phi` """ @@ -198,6 +265,9 @@ class DPIEOperator: sigma = phi_densities[:self.nobjs] sigma_m = sigma.reshape((1,self.nobjs)) + # extract the scalar quantities, { V_j }, that remove the nullspace + V = phi_densities[self.nobjs:] + # init output matvec vector for the phi density IE output = np.zeros((2*self.nobjs,), dtype=self.stype) @@ -232,47 +302,7 @@ class DPIEOperator: # return the resulting field return sym.join_fields(-phi_inc, Q/self.k) - def A_operator0(self, A_densities): - """ - Integral Equation operator for obtaining vector potential, `A` - Old Operator without looking into multiple bodies - """ - - # extract the densities needed to solve the system of equations - rho = A_densities[0] - a = sym.tangential_to_xyz(A_densities[1:3]) - v_array = A_densities[3:] - - # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() - r = sym.n_cross(a) - - # define system of integral equations for A - return sym.join_fields( - 0.5*a + sym.n_cross(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg")) - -self.k * sym.n_cross(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) - + 1j*( - self.k*sym.n_cross(sym.cross(sym.S(self.kernel,n,k=self.k,qbx_forced_limit="avg"),a)) - + sym.n_cross(sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg"))) - ), - 0.5*rho + sym.D(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - + 1j*( - sym.d_dx(3,sym.S(self.kernel,r[0],k=self.k,qbx_forced_limit="avg")) - + sym.d_dy(3,sym.S(self.kernel,r[1],k=self.k,qbx_forced_limit="avg")) - + sym.d_dz(3,sym.S(self.kernel,r[2],k=self.k,qbx_forced_limit="avg")) - - self.k*sym.S(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - ) - + np.dot(v_array,self.char_funcs), - sym.integral(ambient_dim=3,dim=2,operand=sym.n_dot(sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit="avg"))) - - self.k * sym.n_dot(sym.S(self.kernel,n*rho,k=self.k,qbx_forced_limit="avg")) - + 1j*( - self.k*sym.n_dot(sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit="avg")) - - rho/2.0 + sym.Sp(self.kernel,rho,k=self.k,qbx_forced_limit="avg") - ) - ) - ) - - def A_operator(self, A_densities, v): + def A_operator(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` """ @@ -280,13 +310,14 @@ class DPIEOperator: # extract the densities needed to solve the system of equations rho = A_densities[:self.nobjs] rho_m = rho.resize((1,self.nobjs)) - a_loc = A_densities[self.nobjs:] + v = A_densities[self.nobjs:(2*self.nobjs)] + a_loc = A_densities[(2*self.nobjs):] a = np.zeros((3,self.nobjs),dtype=self.stype) for n in range(0,self.nobjs): a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() + n = sym.normal(3, None).as_vector() r = sym.n_cross(a) # init output matvec vector for the phi density IE @@ -301,23 +332,23 @@ class DPIEOperator: # generate the set of equations for the vector densities, a, coupled # across the various geometries involved output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,target_loc),where=target_loc) \ - + -self.k * sym.n_cross(self.S(n*rho_m,target_loc),where=target_loc) \ - + 1j*( self.k* sym.n_cross(self.S(sym.n_cross(a),target_loc),where=target_loc) \ + + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),target_loc),where=target_loc) \ + + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),target_loc),where=target_loc) \ sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,target_loc)),where=target_loc) ) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,target_loc) \ - + 1j*( sym.div(self.S(sym.n_cross(a,where=target_loc))) \ + + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),target_loc)) \ -self.k*self.S(rho_m) )\ + v[n] # add the equation that integrates everything out into some constant output[(4*self.nobjs + n)] = sym.integral(ambient_dim=3,dim=2,\ - operand=(sym.n_dot(sym.curl(self.S(a))) - self.k*sym.n_dot(self.S(n*rho_m)) + \ - 1j*(self.k*sym.n_dot(sym.n_cross(a)) - 0.5*rho[n] + self.Sp(rho_m))),\ + operand=(sym.n_dot(sym.curl(self.S(a))) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list))) + \ + 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list))) - 0.5*rho[n] + self.Sp(rho_m))),\ where=target_loc) # return output equations @@ -376,7 +407,7 @@ class DPIEOperator: a = sym.tangential_to_xyz(A_densities[1:3]) # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() + n = sym.normal(3, None).as_vector() # define the vector potential representation return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 2a5865cd..7e5c210b 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1,6 +1,8 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2010-2013 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2010-2018 Andreas Kloeckner +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy -- GitLab From ff4037d8fff56945fde46903a09ea24ad19d1dd3 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 10:31:00 -0600 Subject: [PATCH 20/59] Removed any instances of a generic normal vector. --- pytential/symbolic/pde/maxwell/dpie.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 2ae26d37..78e4f63d 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -316,10 +316,6 @@ class DPIEOperator: for n in range(0,self.nobjs): a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) - # define the normal vector in symbolic form - n = sym.normal(3, None).as_vector() - r = sym.n_cross(a) - # init output matvec vector for the phi density IE output = np.zeros((5*self.nobjs,), dtype=self.stype) @@ -406,9 +402,6 @@ class DPIEOperator: rho = A_densities[0] a = sym.tangential_to_xyz(A_densities[1:3]) - # define the normal vector in symbolic form - n = sym.normal(3, None).as_vector() - # define the vector potential representation return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ - self.k*sym.S(self.kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit)\ -- GitLab From 4408a6df8a6fd23e71c57f272f8b4de89875921a Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 10:34:58 -0600 Subject: [PATCH 21/59] Modified vector potential representation to appropriately use new methods for evaluating normal-based cross products across multiple disjoint objects. Also tweaked density retrieval after stuffing the scalar quantities, {v_j}, into the density vector. --- pytential/symbolic/pde/maxwell/dpie.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 78e4f63d..d1e5c9e0 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -419,17 +419,14 @@ class DPIEOperator: # extract the densities needed to solve the system of equations rho = A_densities[:self.nobjs] rho_m = rho.resize((1,self.nobjs)) - a_loc = A_densities[self.nobjs:] + a_loc = A_densities[(2*self.nobjs):] a = np.zeros((3,self.nobjs),dtype=self.stype) for n in range(0,self.nobjs): a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) - # define the normal vector in symbolic form - n = sym.normal(len(a), None).as_vector() - # define the vector potential representation - return sym.curl(self.S(a,target)) - self.k*self.S(n*rho_m,target) \ - + 1j*(self.k*self.S(sym.n_cross(a),target) + sym.grad(self.S(rho_m,target))) + return sym.curl(self.S(a,target)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target) \ + + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target) + sym.grad(self.S(rho_m,target))) def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): -- GitLab From 498207d9fd453189875159da8d913c2c73fa3ff8 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 10:35:40 -0600 Subject: [PATCH 22/59] Removed some dead code. --- pytential/symbolic/pde/maxwell/dpie.py | 33 -------------------------- 1 file changed, 33 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index d1e5c9e0..40fe136c 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -364,21 +364,6 @@ class DPIEOperator: # define RHS for `A` integral equation system return sym.join_fields( -sym.n_cross(A_inc), -divA_inc/self.k, q) - - def scalar_potential_rep0(self, phi_densities, qbx_forced_limit=None): - """ - This method is a representation of the scalar potential, phi, - based on the density `sigma`. - Old representation - """ - - # extract the densities needed to solve the system of equations - sigma = phi_densities[0] - - # evaluate scalar potential representation - return sym.D(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - - 1j*self.k*sym.S(self.kernel,sigma,k=self.k,qbx_forced_limit=qbx_forced_limit) - def scalar_potential_rep(self, phi_densities, target=None): """ This method is a representation of the scalar potential, phi, @@ -392,24 +377,6 @@ class DPIEOperator: # evaluate scalar potential representation return self.D(sigma_m,target) - 1j*self.k*self.S(sigma_m,target) - def vector_potential_rep0(self, A_densities, qbx_forced_limit=None): - """ - This method is a representation of the vector potential, phi, - based on the vector density `a` and scalar density `rho` - """ - - # extract the densities needed to solve the system of equations - rho = A_densities[0] - a = sym.tangential_to_xyz(A_densities[1:3]) - - # define the vector potential representation - return sym.curl(sym.S(self.kernel,a,k=self.k,qbx_forced_limit=qbx_forced_limit)) \ - - self.k*sym.S(self.kernel,rho*n,k=self.k,qbx_forced_limit=qbx_forced_limit)\ - + 1j*( - self.k*sym.S(self.kernel,sym.n_cross(a),k=self.k,qbx_forced_limit=qbx_forced_limit) - + sym.grad(3,sym.S(self.kernel,rho,k=self.k,qbx_forced_limit=qbx_forced_limit)) - ) - def vector_potential_rep(self, A_densities, target=None): """ This method is a representation of the vector potential, phi, -- GitLab From 11faf9ae0923e60bf22c7b28222908537cb7c5fa Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 10:57:30 -0600 Subject: [PATCH 23/59] Defined methods in the DPIE Operator class that return the domain list, for each IE operator, that will be used in the scipy_op method to solve for all the unknowns. --- pytential/symbolic/pde/maxwell/dpie.py | 51 +++++++++++++++++++++++++- test/test_maxwell_dpie.py | 4 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 40fe136c..27e7eb8b 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -74,7 +74,7 @@ class DPIEOperator: # 1 when we are on some surface/valume and a value of 0 otherwise self.char_funcs = sym.make_sym_vector("chi",len(self.geometry_list)) for idx in range(0,len(geometry_list)): - self.char_funcs[idx] = sym.D(self.kernel,1,k=self.k,source=self.geometry_list[idx]) + self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) def numVectorPotentialDensities(self): return 4*len(self.geometry_list) @@ -82,6 +82,55 @@ class DPIEOperator: def numScalarPotentialDensities(self): return 2*len(self.geometry_list) + def getVectorDomainList(self): + """ + Method to return domain list that will be used within the scipy_op method to + solve the system of discretized integral equations. What is returned should just + be a list with values that are strings or None. + """ + + # initialize domain list + domain_list = [None]*self.numVectorPotentialDensities() + + # get strings for the actual densities + for n in range(0,self.nobjs): + + # grab nth location identifier + location = self.geometry_list[n] + + # assign domain for nth scalar density + domain_list[n] = location + + # assign domain for nth vector density + domain_list[2*self.nobjs + 2*n] = location + domain_list[2*self.nobjs + 2*n+1] = location + + # return the domain list + return domain_list + + def getScalarDomainList(self): + """ + Method to return domain list that will be used within the scipy_op method to + solve the system of discretized integral equations. What is returned should just + be a list with values that are strings or None. + """ + + # initialize domain list + domain_list = [None]*self.numScalarPotentialDensities() + + # get strings for the actual densities + for n in range(0,self.nobjs): + + # grab nth location identifier + location = self.geometry_list[n] + + # assign domain for nth scalar density + domain_list[n] = location + + # return the domain list + return domain_list + + def D(self, density_vec, target=None): """ Double layer potential operator across multiple disjoint objects diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 6c71c333..b5df7c23 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -240,7 +240,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): knl_kwargs = {"k": case.k} # specify the list of geometry objects being used - geom_list = [None] + geom_list = ["obj0"] # {{{ come up with a solution to Maxwell's equations @@ -361,7 +361,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define the geometry dictionary #geom_map = {"g0": qbx} - geom_map = qbx + geom_map = {"obj0":qbx} # get the maximum mesh element edge length h_max = qbx.h_max -- GitLab From 17394e72d220afdfa63b1ff5775a70cec01a8cbb Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 11:05:51 -0600 Subject: [PATCH 24/59] Modified test DPIE file to include the domain lists for the associated IE operators being solved. The next step is defining the incident fields in a way that generalizes to the multiple disjoint objects. --- test/test_maxwell_dpie.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index b5df7c23..25b2e283 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -398,19 +398,19 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # solve for the scalar potential densities gmres_result = gmres( - phi_op.scipy_op(queue, "phi_densities", np.complex128, **knl_kwargs), + phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.getScalarDomainList(),**knl_kwargs), phi_rhs, **gmres_settings) phi_dens = gmres_result.solution # solve for the vector potential densities gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, **knl_kwargs), + A_op.scipy_op(queue, "A_densities", np.complex128, , domains=dpie.getVectorDomainList(), **knl_kwargs), A_rhs, **gmres_settings) A_dens = gmres_result.solution # extract useful solutions - phi = bind(qbx, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) - Axyz = bind(qbx, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) + Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) # }}} -- GitLab From 6816c404936db852f138cc0fc9f8b4d78e222558 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 12:49:11 -0600 Subject: [PATCH 25/59] Added in functions to obtain the gradient and divergence of the incident scalar and vector fields (so grad(phi) and div(A)), after some derivations, in a way that should be useful for the DPIE. --- pytential/symbolic/pde/maxwell/__init__.py | 31 ++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index b9ce1fe6..81a91a5f 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -1,7 +1,9 @@ from __future__ import division, absolute_import -__copyright__ = """ Copyright (C) 2010-2013 Andreas Kloeckner - Copyright (C) 2018 Christian Howard""" +__copyright__ = """ +Copyright (C) 2010-2018 Andreas Kloeckner +Copyright (C) 2018 Christian Howard +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -133,6 +135,31 @@ def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): # }}} +def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): + """ + Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` + and yield the gradient of a scalar potential field satisfying Maxwell's equations. + + Should be representing the following: + .. math:: + \nabla \phi(x) = - e^{i k x^T u} E_p^T \left( 1 + i k x^T u\right) + """ + x = sym.nodes(3, where).as_vector() + grad_phi = -np.exp(1j*k*np.dot(x,u)) * (1 + 1j*k*np.dot(x,u)) * Ep.T + return grad_phi + +def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): + """ + Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` + and yield the divergence of a vector potential field satisfying Maxwell's equations. + + Should be representing the following: + .. math:: + \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} e^{i k x^T u} E_p^T \left( u + i k x\right) + """ + x = sym.nodes(3, where).as_vector() + divA = -np.sqrt(epsilon*mu) * np.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x) + return divA # {{{ Charge-Current MFIE -- GitLab From b3c4e8aa7aa1d227e9a10cc41721baf7a4ef73d8 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 12:52:58 -0600 Subject: [PATCH 26/59] Added into doc info for functions added to maxwell/__init__.py --- pytential/symbolic/pde/maxwell/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 81a91a5f..7c7e3619 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -38,6 +38,9 @@ __doc__ = """ .. autofunction:: get_sym_maxwell_point_source .. autofunction:: get_sym_maxwell_plane_wave +.. autofunction:: get_sym_maxwell_point_source_potentials +.. autofunction:: get_sym_maxwell_planewave_gradphi +.. autofunction:: get_sym_maxwell_planewave_divA .. autoclass:: PECChargeCurrentMFIEOperator """ -- GitLab From 855c71b09807e822ab6042d06b43a8adbaf1706c Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 13:37:54 -0600 Subject: [PATCH 27/59] Tweaked the grad(phi) definition, located in maxwell/__init__.py, because found a small error. As of now, the analytical div(A) and grad(phi) have been validated to be correct using a Finite Difference check. --- pytential/symbolic/pde/maxwell/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 7c7e3619..04dba3ba 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -148,7 +148,7 @@ def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): \nabla \phi(x) = - e^{i k x^T u} E_p^T \left( 1 + i k x^T u\right) """ x = sym.nodes(3, where).as_vector() - grad_phi = -np.exp(1j*k*np.dot(x,u)) * (1 + 1j*k*np.dot(x,u)) * Ep.T + grad_phi = -np.exp(1j*k*np.dot(x,u)) * (Ep.T + 1j*k*np.dot(Ep,x)*u.T) return grad_phi def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): -- GitLab From 89d2a400ace13b7a3e5a7e6add6ccc7779a5d7cf Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Feb 2018 15:02:35 -0600 Subject: [PATCH 28/59] Modified DPIE and test code based on run-time errors thrown so far in the process of debugging. --- pytential/symbolic/pde/maxwell/dpie.py | 4 ++-- test/test_maxwell_dpie.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 27e7eb8b..41e2d671 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -140,7 +140,7 @@ class DPIEOperator: (ndim, nobj) = density_vec.shape # init output symbolic quantity with zeros - output = np.zeros((ndim,), dtype=type(density)) + output = np.zeros((ndim,), dtype=self.stype) # compute individual double layer potential evaluations at the given # density across all the disjoint objects @@ -378,7 +378,7 @@ class DPIEOperator: # across the various geometries involved output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,target_loc),where=target_loc) \ + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),target_loc),where=target_loc) \ - + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),target_loc),where=target_loc) \ + + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),target_loc),where=target_loc) + \ sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,target_loc)),where=target_loc) ) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 25b2e283..7a942ca9 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -252,10 +252,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # local scope environment #from pytential.symbolic.pde.maxwell import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) #from pytential.symbolic.pde.maxwell.dpie import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) - import pytential.symbolic.pde.maxwell.dpie as mw + import pytential.symbolic.pde.maxwell as mw + import pytential.symbolic.pde.maxwell.dpie as mw_dpie # initialize the DPIE operator based on the geometry list - dpie = mw.DPIEOperator(geometry_list=geom_list) + dpie = mw_dpie.DPIEOperator(geometry_list=geom_list) # specify some symbolic variables that will be used @@ -404,7 +405,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # solve for the vector potential densities gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, , domains=dpie.getVectorDomainList(), **knl_kwargs), + A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.getVectorDomainList(), **knl_kwargs), A_rhs, **gmres_settings) A_dens = gmres_result.solution -- GitLab From fdfaa3aa4c3f6ac31fcec3b889cb514345f90eb2 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Tue, 13 Mar 2018 22:07:39 -0500 Subject: [PATCH 29/59] Making progress in debugging the DPIE pytential implementation. Commit #1 --- pytential/symbolic/pde/maxwell/__init__.py | 21 ++++++- pytential/symbolic/pde/maxwell/dpie.py | 53 ++++++++++------ pytential/symbolic/primitives.py | 37 ++++++++++-- test/test_maxwell_dpie.py | 70 ++++++++++++---------- 4 files changed, 123 insertions(+), 58 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 04dba3ba..8aeefad6 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -148,7 +148,7 @@ def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): \nabla \phi(x) = - e^{i k x^T u} E_p^T \left( 1 + i k x^T u\right) """ x = sym.nodes(3, where).as_vector() - grad_phi = -np.exp(1j*k*np.dot(x,u)) * (Ep.T + 1j*k*np.dot(Ep,x)*u.T) + grad_phi = -sym.exp(1j*k*np.dot(x,u)) * (Ep.T + 1j*k*np.dot(Ep,x)*u.T) return grad_phi def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): @@ -161,9 +161,26 @@ def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} e^{i k x^T u} E_p^T \left( u + i k x\right) """ x = sym.nodes(3, where).as_vector() - divA = -np.sqrt(epsilon*mu) * np.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x) + divA = -sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x) return divA +def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): + """ + Return a 2-tuple of symbolic expressions that can be bound to a :class:`pytential.source.PointPotentialSource` + and yield the scalar and vector potential fields satisfying Maxwell's equations that represent + a plane wave. + + Should be representing the following: + .. math:: + \boldsymbol{A} = -u \left(x \cdot E_p \right)\sqrt{\mu \epsilon} e^{i k x^T u} + .. math:: + \phi = - \left(x \cdot E_p\right) e^{i k x^T u} + """ + x = sym.nodes(3, where).as_vector() + A = -u * np.dot(x,Ep) * sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) + phi = -np.dot(x,Ep) * sym.exp(1j*k*np.dot(x,u)) + return (phi, A) + # {{{ Charge-Current MFIE class PECChargeCurrentMFIEOperator: diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 41e2d671..b0d53312 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -150,7 +150,10 @@ class DPIEOperator: source=self.geometry_list[i],target=target) # return the output summation - return output + if ndim == 1: + return output[0] + else: + return output def S(self, density_vec, target=None): """ @@ -171,7 +174,10 @@ class DPIEOperator: source=self.geometry_list[i], target=target) # return the output summation - return output + if ndim == 1: + return output[0] + else: + return output def Dp(self, density_vec, target=None): @@ -193,7 +199,10 @@ class DPIEOperator: source=self.geometry_list[i],target=target) # return the output summation - return output + if ndim == 1: + return output[0] + else: + return output def Sp(self, density_vec, target=None): """ @@ -214,7 +223,10 @@ class DPIEOperator: source=self.geometry_list[i], target=target) # return the output summation - return output + if ndim == 1: + return output[0] + else: + return output def n_cross_multi(self, density_vec, sources): r""" @@ -266,12 +278,12 @@ class DPIEOperator: assert ndim == 1 # init output symbolic quantity with zeros - output = np.zeros(density_vec.shape, dtype=self.stype) + output = np.zeros((3,nobj), dtype=self.stype) # loop through the density and sources to construct the appropriate # element-wise cross product operation for k in range(0,nobj): - output[:,k] = sym.normal(3,where=sources[k]) * density_vec[0,k] + output[:,k] = sym.normal(3,where=sources[k]).as_vector() * density_vec[0,k] # return result from element-wise cross product return output @@ -331,7 +343,7 @@ class DPIEOperator: # setup equation that integrates some integral operators over the nth surface output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, - operand=(self.Dp(sigma_m,target=None)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=None)),\ + operand=(self.Dp(sigma_m,target=obj_n)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ where=obj_n) # return the resulting system of IE @@ -357,8 +369,8 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - rho = A_densities[:self.nobjs] - rho_m = rho.resize((1,self.nobjs)) + rho = A_densities[0:self.nobjs] + rho_m = rho.reshape((1,self.nobjs)) v = A_densities[self.nobjs:(2*self.nobjs)] a_loc = A_densities[(2*self.nobjs):] a = np.zeros((3,self.nobjs),dtype=self.stype) @@ -372,29 +384,32 @@ class DPIEOperator: for n in range(0,self.nobjs): # get the nth target geometry to have IE solved across - target_loc = self.geometry_list[n] + obj_n = self.geometry_list[n] # generate the set of equations for the vector densities, a, coupled # across the various geometries involved - output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,target_loc),where=target_loc) \ - + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),target_loc),where=target_loc) \ - + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),target_loc),where=target_loc) + \ - sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,target_loc)),where=target_loc) + output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ + + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),obj_n),where=obj_n) \ + + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),obj_n),where=obj_n) + \ + sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,obj_n)),where=obj_n) ) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,target_loc) \ - + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),target_loc)) \ + output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,obj_n) \ + + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),obj_n)) \ -self.k*self.S(rho_m) )\ + v[n] # add the equation that integrates everything out into some constant output[(4*self.nobjs + n)] = sym.integral(ambient_dim=3,dim=2,\ - operand=(sym.n_dot(sym.curl(self.S(a))) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list))) + \ - 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list))) - 0.5*rho[n] + self.Sp(rho_m))),\ - where=target_loc) + operand=(sym.n_dot(sym.curl(self.S(a,target=obj_n)),where=obj_n) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list),target=obj_n),where=obj_n) + \ + 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list),target=obj_n),where=obj_n) - 0.5*rho[n] + self.Sp(rho_m,target=obj_n))),\ + where=obj_n) + + # print something to help with debugging + print(sym.pretty(output)) # return output equations return output diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 7e5c210b..34db30ed 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -380,6 +380,15 @@ def parametrization_derivative_matrix(ambient_dim, dim, where=None): reference-to-global parametrization. """ + return reference_jacobian( + [NodeCoordinateComponent(i, where) for i in range(ambient_dim)], + ambient_dim, dim, where) + +def parametrization_derivative_matrix_old(ambient_dim, dim, where=None): + """Return a :class:`np.array` representing the derivative of the + reference-to-global parametrization. + """ + return reference_jacobian( [NodeCoordinateComponent(i, where) for i in range(ambient_dim)], ambient_dim, dim) @@ -928,9 +937,22 @@ def tangential_derivative(ambient_dim, operand, dim=None, where=None): def normal_derivative(ambient_dim, operand, dim=None, where=None): - d = Derivative() - return d.resolve( - (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) + #d = Derivative() + #return d.resolve( + # (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) + # * d(operand)) + if isinstance(operand, (np.ndarray, MultiVector)): + def make_op(operand_i): + d = Derivative() + return d.resolve( + (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) + * d(operand_i)) + + return componentwise(make_op, operand) + else: + d = Derivative() + return d.resolve( + (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) * d(operand)) @@ -1071,7 +1093,7 @@ def project_to_tangential(xyz_vec, where=None): def n_dot(vec, where=None): - nrm = normal(len(vec), where).as_vector() + nrm = normal(len(vec), where=where).as_vector() return np.dot(nrm, vec) @@ -1089,13 +1111,16 @@ def cross(vec_a, vec_b): def n_cross(vec, where=None): - return cross(normal(3, where).as_vector(), vec) + return cross(normal(3, where=where).as_vector(), vec) def div(vec): ambient_dim = len(vec) - return sum(dd_axis(iaxis, ambient_dim, vec) for iaxis in range(ambient_dim)) + return sum(dd_axis(iaxis, ambient_dim, vec[iaxis]) for iaxis in range(ambient_dim)) +def div_old(vec): + ambient_dim = len(vec) + return sum(dd_axis(iaxis, ambient_dim, vec) for iaxis in range(ambient_dim)) def curl(vec): from pytools import levi_civita diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 7a942ca9..439fc153 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -258,13 +258,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # initialize the DPIE operator based on the geometry list dpie = mw_dpie.DPIEOperator(geometry_list=geom_list) - # specify some symbolic variables that will be used # in the process to solve integral equations for the DPIE phi_densities = sym.make_sym_vector("phi_densities", dpie.numScalarPotentialDensities()) A_densities = sym.make_sym_vector("A_densities", dpie.numVectorPotentialDensities()) - # get test source locations from the passed in case's queue test_source = case.get_source(queue) @@ -279,33 +277,33 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # source locations for various variables being solved for src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) - # define a local method that will evaluate some incident field - # at a set of target locations - def eval_inc_field_at(tgt): - if 0: - # plane wave - return bind( - tgt, - mw.get_sym_maxwell_plane_wave( - amplitude_vec=np.array([1, 1, 1]), - v=np.array([1, 0, 0]), - omega=case.k) - )(queue) - else: - # point source - return bind( - (test_source, tgt), - mw.get_sym_maxwell_point_source(dpie.kernel, j_sym, dpie.k) - )(queue, j=src_j, k=case.k) + # define some parameters for the incident wave + # direction for the wave + u_dir = np.array([1, 0, 0]) + + # polarization vector + Ep = np.array([1, 1, 1]) + + # define functions that can be used to generate incident fields for an input discretization + # define potentials based on incident plane wave + def get_incident_plane_wave_EHField(tgt): + return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Ep, v=u_dir, omega=dpie.k))(queue,k=case.k) + + # get the gradphi_inc field evaluated at some source locations + def get_incident_gradphi(tgt,where=None): + return bind((test_source,tgt),mw.get_sym_maxwell_planewave_gradphi(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue,k=case.k) + + # get the incident plane wave div(A) + def get_incident_divA(tgt,where=None): + return bind((test_source,tgt),mw.get_sym_maxwell_planewave_divA(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue,k=case.k) # method to get vector potential and scalar potential for incident # E-M fields - def get_inc_potentials(tgt): - return bind((test_source, tgt),mw.get_sym_maxwell_point_source_potentials(dpie.kernel, j_sym, dpie.k))(queue, j=src_j, k=case.k) + def get_incident_potentials(tgt): + return bind((test_source, tgt),mw.get_sym_maxwell_planewave_potentials(u=u_dir, Ep=Ep, k=dpie.k))(queue, k=case.k) # get the Electromagnetic field evaluated at the target calculus patch - pde_test_inc = EHField( - vector_from_device(queue, eval_inc_field_at(calc_patch_tgt))) + pde_test_inc = EHField(vector_from_device(queue, get_incident_plane_wave_EHField(calc_patch_tgt))) # compute residuals of incident field at source points source_maxwell_resids = [ @@ -373,19 +371,29 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # get the incident field at the scatter and observation locations - inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) - inc_EM_field_obs = EHField(eval_inc_field_at(obs_discr)) - inc_vec_field_scat = get_inc_potentials(scat_discr) - inv_vec_field_obs = get_inc_potentials(obs_discr) + #inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) + #inc_EM_field_obs = EHField(eval_inc_field_at(obs_discr)) + #inc_vec_field_scat = get_inc_potentials(scat_discr) + #inc_vec_field_obs = get_inc_potentials(obs_discr) + + # get the incident fields used for boundary conditions + (phi_inc, A_inc) = get_incident_potentials(scat_discr) + inc_divA_scat = get_incident_divA(scat_discr) + inc_gradPhi_scat = get_incident_gradphi(scat_discr) # {{{ solve the system of integral equations - inc_xyz_vec_sym = sym.make_sym_vector("inc_vec_fld", 4) + inc_A = sym.make_sym_vector("inc_A", 3) + inc_phi = sym.var("inc_phi") + inc_divA = sym.var("inc_divA") + inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) # setup operators that will be solved phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) - phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_xyz_vec_sym[0]))(queue,inc_vec_fld=inc_vec_field_scat,**knl_kwargs) A_op = bind(geom_map,dpie.A_operator(A_densities=A_densities)) - A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_xyz_vec_sym[1:]))(queue,inc_vec_fld=inc_vec_field_scat,**knl_kwargs) + + # setup the RHS with provided data so we can solve for density values across the domain + phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) + A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_A,divA_inc=inc_divA_scat))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) # set GMRES settings for solving gmres_settings = dict( -- GitLab From 4775077f8605a90f8a017683ef5da65b61f842c4 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 14 Mar 2018 15:28:12 -0500 Subject: [PATCH 30/59] Got stuff debugged such that the extinction test case is reaching the GMRES solving stage. Currently issue ties to dimensions not corresponding when a kernel gets run using the Python OpenCL wrapper. Investigating. --- pytential/solve.py | 5 ++++ pytential/symbolic/pde/maxwell/__init__.py | 4 ++-- pytential/symbolic/pde/maxwell/dpie.py | 27 ++++++++++++++-------- test/test_maxwell_dpie.py | 12 +++++----- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index 34081d27..f9ee223a 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -325,6 +325,11 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, amod = get_array_module(rhs) chopper = VectorChopper(rhs) + + for n in range(0,len(rhs)): + if len(rhs[n].shape) == 0: + rhs[n] = rhs[n].reshape((1,)) + stacked_rhs = chopper.stack(rhs) if inner_product is None: diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 8aeefad6..a966c61b 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -161,7 +161,7 @@ def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} e^{i k x^T u} E_p^T \left( u + i k x\right) """ x = sym.nodes(3, where).as_vector() - divA = -sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x) + divA = sym.join_fields(-sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x)) return divA def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): @@ -178,7 +178,7 @@ def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): """ x = sym.nodes(3, where).as_vector() A = -u * np.dot(x,Ep) * sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) - phi = -np.dot(x,Ep) * sym.exp(1j*k*np.dot(x,u)) + phi = sym.join_fields(-np.dot(x,Ep) * sym.exp(1j*k*np.dot(x,u))) return (phi, A) # {{{ Charge-Current MFIE diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index b0d53312..b9008b5f 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -96,7 +96,7 @@ class DPIEOperator: for n in range(0,self.nobjs): # grab nth location identifier - location = self.geometry_list[n] + location = self.geometry_list[n] + "t" # assign domain for nth scalar density domain_list[n] = location @@ -122,7 +122,7 @@ class DPIEOperator: for n in range(0,self.nobjs): # grab nth location identifier - location = self.geometry_list[n] + location = self.geometry_list[n] + "t" # assign domain for nth scalar density domain_list[n] = location @@ -343,7 +343,7 @@ class DPIEOperator: # setup equation that integrates some integral operators over the nth surface output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, - operand=(self.Dp(sigma_m,target=obj_n)/self.k+ 1j*sigma/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ + operand=(self.Dp(sigma_m,target=obj_n)/self.k+ 1j*sigma[n]/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ where=obj_n) # return the resulting system of IE @@ -355,13 +355,18 @@ class DPIEOperator: The Right-Hand-Side for the Integral Equation for `phi` """ + # get the scalar f expression for each object + f = np.zeros((self.nobjs,), dtype=self.stype) + for i in range(0,self.nobjs): + f[i] = -phi_inc[i] + # get the Q_{j} terms inside RHS expression Q = np.zeros((self.nobjs,), dtype=self.stype) for i in range(0,self.nobjs): - Q[i] = -sym.integral(3,2,sym.n_dot(gradphi_inc),where=self.geometry_list[i]) + Q[i] = -sym.integral(3,2,sym.n_dot(gradphi_inc,where=self.geometry_list[i]),where=self.geometry_list[i]) # return the resulting field - return sym.join_fields(-phi_inc, Q/self.k) + return sym.join_fields(f, Q/self.k) def A_operator(self, A_densities): """ @@ -398,7 +403,7 @@ class DPIEOperator: # across the various geometries involved output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,obj_n) \ + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),obj_n)) \ - -self.k*self.S(rho_m) + -self.k*self.S(rho_m,target=obj_n) )\ + v[n] @@ -420,13 +425,17 @@ class DPIEOperator: The Right-Hand-Side for the Integral Equation for `A` """ - # get the q_array + # get the q , h , and vec(f) associated with each object q = np.zeros((self.nobjs,), dtype=self.stype) + h = np.zeros((self.nobjs,), dtype=self.stype) + f = np.zeros((3*self.nobjs,), dtype=self.stype) for i in range(0,self.nobjs): - q[i] = -sym.integral(3,2,sym.n_dot(A_inc),where=self.geometry_list[i]) + q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) + h[i] = -divA_inc[i]/self.k + f[3*i:3*(i+1)] = -sym.n_cross(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]) # define RHS for `A` integral equation system - return sym.join_fields( -sym.n_cross(A_inc), -divA_inc/self.k, q) + return sym.join_fields( f, h, q) def scalar_potential_rep(self, phi_densities, target=None): """ diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 439fc153..71c1dc2c 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -299,8 +299,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # method to get vector potential and scalar potential for incident # E-M fields - def get_incident_potentials(tgt): - return bind((test_source, tgt),mw.get_sym_maxwell_planewave_potentials(u=u_dir, Ep=Ep, k=dpie.k))(queue, k=case.k) + def get_incident_potentials(tgt,where=None): + return bind((test_source, tgt),mw.get_sym_maxwell_planewave_potentials(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue, k=case.k) # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField(vector_from_device(queue, get_incident_plane_wave_EHField(calc_patch_tgt))) @@ -360,7 +360,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define the geometry dictionary #geom_map = {"g0": qbx} - geom_map = {"obj0":qbx} + geom_map = {"obj0":qbx, "obj0t":qbx.density_discr} # get the maximum mesh element edge length h_max = qbx.h_max @@ -383,8 +383,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ solve the system of integral equations inc_A = sym.make_sym_vector("inc_A", 3) - inc_phi = sym.var("inc_phi") - inc_divA = sym.var("inc_divA") + inc_phi = sym.make_sym_vector("inc_phi",1) + inc_divA = sym.make_sym_vector("inc_divA",1) inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) # setup operators that will be solved @@ -393,7 +393,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # setup the RHS with provided data so we can solve for density values across the domain phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) - A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_A,divA_inc=inc_divA_scat))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) + A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) # set GMRES settings for solving gmres_settings = dict( -- GitLab From 5530c29455ec920dd708b9b2de750e94a33774fc Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Sat, 17 Mar 2018 11:28:01 -0500 Subject: [PATCH 31/59] Made changes to DPIE and Maxwell DPIE Test script such that a field based on incident plane waves could be investigated. Currently have operators converging to desired tolerances with GMRES but the representation is having issues due to potentially too many derivatives being required? Need to investigate further. --- pytential/solve.py | 28 ++++++--- pytential/symbolic/execution.py | 5 ++ pytential/symbolic/pde/maxwell/dpie.py | 87 +++++++++++++++----------- test/test_maxwell_dpie.py | 49 ++++++++------- 4 files changed, 101 insertions(+), 68 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index f9ee223a..01a66002 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -52,10 +52,11 @@ def get_array_module(vec): # {{{ block system support class VectorChopper(object): - def __init__(self, structured_vec): + def __init__(self, structured_vec, queue = None): from pytools.obj_array import is_obj_array self.is_structured = is_obj_array(structured_vec) self.array_module = get_array_module(structured_vec) + self.queue = queue if self.is_structured: self.slices = [] @@ -63,16 +64,24 @@ class VectorChopper(object): for entry in structured_vec: if isinstance(entry, self.array_module.ndarray): length = len(entry) + isScalar = False else: length = 1 + isScalar = True - self.slices.append(slice(num_dofs, num_dofs+length)) + self.slices.append((isScalar,slice(num_dofs, num_dofs+length))) num_dofs += length def stack(self, vec): + import pyopencl as cl + import numpy as np if not self.is_structured: return vec + for n in range(0,len(self.slices)): + if self.slices[n][0]: + vec[n] = cl.array.to_device(self.queue, np.array([vec[n]])) + return self.array_module.hstack(vec) def chop(self, vec): @@ -80,7 +89,12 @@ class VectorChopper(object): return vec from pytools.obj_array import make_obj_array - return make_obj_array([vec[slc] for slc in self.slices]) + result = make_obj_array([vec[slc] for (isScalar,slc) in self.slices]) + if self.queue is not None: + for n in range(0,len(self.slices)): + if self.slices[n][0]: + result[n] = result[n].get(self.queue)[0] + return result # }}} @@ -323,13 +337,7 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, :return: a :class:`GMRESResult` """ amod = get_array_module(rhs) - - chopper = VectorChopper(rhs) - - for n in range(0,len(rhs)): - if len(rhs[n].shape) == 0: - rhs[n] = rhs[n].reshape((1,)) - + chopper = VectorChopper(rhs,op.queue) stacked_rhs = chopper.stack(rhs) if inner_product is None: diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 3e96b00c..118bc1c4 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -197,6 +197,11 @@ class MatVecOp: x = make_obj_array( [x[start:end] for start, end in self.starts_and_ends]) + # make any scalar terms actually scalars + for n in range(0,len(x)): + if x[n].shape == (1,): + x[n] = x[n].get(self.queue)[0] + args = self.extra_args.copy() args[self.arg_name] = x result = self.bound_expr(self.queue, **args) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index b9008b5f..508672fd 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -77,7 +77,7 @@ class DPIEOperator: self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) def numVectorPotentialDensities(self): - return 4*len(self.geometry_list) + return 5*len(self.geometry_list) def numScalarPotentialDensities(self): return 2*len(self.geometry_list) @@ -98,12 +98,13 @@ class DPIEOperator: # grab nth location identifier location = self.geometry_list[n] + "t" - # assign domain for nth scalar density - domain_list[n] = location - # assign domain for nth vector density - domain_list[2*self.nobjs + 2*n] = location - domain_list[2*self.nobjs + 2*n+1] = location + domain_list[3*n] = location + domain_list[3*n+1] = location + domain_list[3*n+2] = location + + # assign domain for nth scalar density + domain_list[3*self.nobjs + n] = location # return the domain list return domain_list @@ -131,7 +132,7 @@ class DPIEOperator: return domain_list - def D(self, density_vec, target=None): + def D(self, density_vec, target=None, qfl="avg"): """ Double layer potential operator across multiple disjoint objects """ @@ -146,7 +147,7 @@ class DPIEOperator: # density across all the disjoint objects for i in range(0,nobj): output = output + sym.D(self.kernel, density_vec[:,i], - k=self.k,qbx_forced_limit="avg", + k=self.k,qbx_forced_limit=qfl, source=self.geometry_list[i],target=target) # return the output summation @@ -155,7 +156,7 @@ class DPIEOperator: else: return output - def S(self, density_vec, target=None): + def S(self, density_vec, target=None, qfl="avg"): """ Double layer potential operator across multiple disjoint objects """ @@ -170,7 +171,7 @@ class DPIEOperator: # density across all the disjoint objects for i in range(0,nobj): output = output + sym.S(self.kernel, density_vec[:,i], - k=self.k, qbx_forced_limit="avg", + k=self.k, qbx_forced_limit=qfl, source=self.geometry_list[i], target=target) # return the output summation @@ -180,7 +181,7 @@ class DPIEOperator: return output - def Dp(self, density_vec, target=None): + def Dp(self, density_vec, target=None, qfl="avg"): """ D' layer potential operator across multiple disjoint objects """ @@ -195,7 +196,7 @@ class DPIEOperator: # density across all the disjoint objects for i in range(0,nobj): output = output + sym.Dp(self.kernel, density_vec[:,i], - k=self.k,qbx_forced_limit="avg", + k=self.k,qbx_forced_limit=qfl, source=self.geometry_list[i],target=target) # return the output summation @@ -204,7 +205,7 @@ class DPIEOperator: else: return output - def Sp(self, density_vec, target=None): + def Sp(self, density_vec, target=None, qfl="avg"): """ S' layer potential operator across multiple disjoint objects """ @@ -219,7 +220,7 @@ class DPIEOperator: # density across all the disjoint objects for i in range(0,nobj): output = output + sym.Sp(self.kernel, density_vec[:,i], - k=self.k, qbx_forced_limit="avg", + k=self.k, qbx_forced_limit=qfl, source=self.geometry_list[i], target=target) # return the output summation @@ -374,13 +375,14 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - rho = A_densities[0:self.nobjs] - rho_m = rho.reshape((1,self.nobjs)) - v = A_densities[self.nobjs:(2*self.nobjs)] - a_loc = A_densities[(2*self.nobjs):] + a_loc = A_densities[:(3*self.nobjs)] a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(3*self.nobjs):(4*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + v = A_densities[(4*self.nobjs):] for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + a[:,n] = a_loc[3*n:3*(n+1)] # init output matvec vector for the phi density IE output = np.zeros((5*self.nobjs,), dtype=self.stype) @@ -393,11 +395,11 @@ class DPIEOperator: # generate the set of equations for the vector densities, a, coupled # across the various geometries involved - output[3*n:3*(n+1)] = 0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ + output[3*n:3*(n+1)] = (0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),obj_n),where=obj_n) \ + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),obj_n),where=obj_n) + \ sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,obj_n)),where=obj_n) - ) + )) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved @@ -408,13 +410,13 @@ class DPIEOperator: + v[n] # add the equation that integrates everything out into some constant - output[(4*self.nobjs + n)] = sym.integral(ambient_dim=3,dim=2,\ + output[4*self.nobjs + n] = sym.integral(ambient_dim=3,dim=2,\ operand=(sym.n_dot(sym.curl(self.S(a,target=obj_n)),where=obj_n) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list),target=obj_n),where=obj_n) + \ 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list),target=obj_n),where=obj_n) - 0.5*rho[n] + self.Sp(rho_m,target=obj_n))),\ where=obj_n) # print something to help with debugging - print(sym.pretty(output)) + #print(sym.pretty(output)) # return output equations return output @@ -448,7 +450,7 @@ class DPIEOperator: sigma_m = sigma.reshape((1,self.nobjs)) # evaluate scalar potential representation - return self.D(sigma_m,target) - 1j*self.k*self.S(sigma_m,target) + return self.D(sigma_m,target,qfl=None) - 1j*self.k*self.S(sigma_m,target,qfl=None) def vector_potential_rep(self, A_densities, target=None): """ @@ -457,19 +459,20 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - rho = A_densities[:self.nobjs] - rho_m = rho.resize((1,self.nobjs)) - a_loc = A_densities[(2*self.nobjs):] + a_loc = A_densities[:(3*self.nobjs)] a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(3*self.nobjs):(4*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + a[:,n] = a_loc[3*n:3*(n+1)] # define the vector potential representation - return sym.curl(self.S(a,target)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target) \ - + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target) + sym.grad(self.S(rho_m,target))) + return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ + + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) - def scattered_volume_field(self, phi_densities, A_densities, qbx_forced_limit=None): + def scattered_volume_field(self, phi_densities, A_densities, target=None): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the @@ -480,13 +483,27 @@ class DPIEOperator: as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. """ + # extract the densities needed to solve the system of equations + sigma = phi_densities[:self.nobjs] + sigma_m = sigma.reshape((1,self.nobjs)) + + # extract the densities needed to solve the system of equations + a_loc = A_densities[:(3*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(3*self.nobjs):(4*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + for n in range(0,self.nobjs): + #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + a[:,n] = a_loc[3*n:3*(n+1)] + # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(A_densities) - phi = self.scalar_potential_rep(phi_densities) + A = self.vector_potential_rep(A_densities, target=target) + phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, phi) - H_scat = sym.curl(A) + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3,self.S(sigma_m,target,qfl=None)) + H_scat = sym.curl(sym.curl(self.S(a,target,qfl=None))) - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ + + 1j*(self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) ) # join the fields into a vector return sym.join_fields(E_scat, H_scat) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 71c1dc2c..3aa72d53 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -60,7 +60,7 @@ class MaxwellTestCase: class SphereTestCase(MaxwellTestCase): target_order = 8 - gmres_tol = 1e-10 + gmres_tol = 1e-3 def get_mesh(self, resolution, target_order): from meshmode.mesh.generation import generate_icosphere @@ -279,28 +279,32 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define some parameters for the incident wave # direction for the wave - u_dir = np.array([1, 0, 0]) + u_dir = np.array([1, 0, 0],dtype=np.complex128) # polarization vector - Ep = np.array([1, 1, 1]) + Ep = np.array([1, 1, 1],dtype=np.complex128) + + # define symbolic vectors for use + uvar = sym.make_sym_vector("u", 3) + Evar = sym.make_sym_vector("Ep",3) # define functions that can be used to generate incident fields for an input discretization # define potentials based on incident plane wave def get_incident_plane_wave_EHField(tgt): - return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Ep, v=u_dir, omega=dpie.k))(queue,k=case.k) + return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Ep, v=u_dir, omega=dpie.k))(queue,k=case.k,u=u_dir,Ep=Ep) # get the gradphi_inc field evaluated at some source locations - def get_incident_gradphi(tgt,where=None): - return bind((test_source,tgt),mw.get_sym_maxwell_planewave_gradphi(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue,k=case.k) + def get_incident_gradphi(objects, target=None): + return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=u_dir, Ep=Ep, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) # get the incident plane wave div(A) - def get_incident_divA(tgt,where=None): - return bind((test_source,tgt),mw.get_sym_maxwell_planewave_divA(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue,k=case.k) + def get_incident_divA(objects, target=None): + return bind(objects,mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) # method to get vector potential and scalar potential for incident # E-M fields - def get_incident_potentials(tgt,where=None): - return bind((test_source, tgt),mw.get_sym_maxwell_planewave_potentials(u=u_dir, Ep=Ep, k=dpie.k,where=where))(queue, k=case.k) + def get_incident_potentials(objects, target=None): + return bind(objects,mw.get_sym_maxwell_planewave_potentials(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue, k=case.k,u=u_dir,Ep=Ep) # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField(vector_from_device(queue, get_incident_plane_wave_EHField(calc_patch_tgt))) @@ -360,7 +364,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define the geometry dictionary #geom_map = {"g0": qbx} - geom_map = {"obj0":qbx, "obj0t":qbx.density_discr} + geom_map = {"obj0":qbx, "obj0t":qbx.density_discr, "scat":qbx.density_discr} # get the maximum mesh element edge length h_max = qbx.h_max @@ -377,9 +381,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): #inc_vec_field_obs = get_inc_potentials(obs_discr) # get the incident fields used for boundary conditions - (phi_inc, A_inc) = get_incident_potentials(scat_discr) - inc_divA_scat = get_incident_divA(scat_discr) - inc_gradPhi_scat = get_incident_gradphi(scat_discr) + (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') + inc_divA_scat = get_incident_divA(geom_map,'scat') + inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') # {{{ solve the system of integral equations inc_A = sym.make_sym_vector("inc_A", 3) @@ -418,22 +422,21 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): A_dens = gmres_result.solution # extract useful solutions - phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) - Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + #phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) + #Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) # }}} # {{{ volume eval - sym_repr = dpie.scattered_volume_field(phi_densities,A_densities) + sym_repr = dpie.scattered_volume_field(phi_densities,A_densities,target='tgt') - def eval_repr_at(tgt, source=None): - if source is None: - source = qbx - return bind((source, tgt), sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, **knl_kwargs) + def eval_repr_at(tgt): + map = geom_map + map['tgt'] = tgt + return bind(map, sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, **knl_kwargs) - pde_test_repr = EHField( - vector_from_device(queue, eval_repr_at(calc_patch_tgt))) + pde_test_repr = EHField(vector_from_device(queue, eval_repr_at(calc_patch_tgt))) maxwell_residuals = [ calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) -- GitLab From 1543a2ebfbe97c74a02a90cef2607aee6eec6974 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Mar 2018 14:09:19 -0500 Subject: [PATCH 32/59] Update various files to following PEP8 naming convention, tweak some raw strings, and make changes to the DPIE operators such that the tangential stuff is being done correctly. Stilly need to finish work on the E&M Field representation. Also need to modify chopper so it does not directly take a OpenCL Queue as an input to the constructor. --- pytential/solve.py | 15 +- pytential/symbolic/pde/maxwell/__init__.py | 12 +- pytential/symbolic/pde/maxwell/dpie.py | 172 +++++++++++++++++++-- pytential/symbolic/primitives.py | 1 + test/test_maxwell_dpie.py | 17 +- 5 files changed, 188 insertions(+), 29 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index 01a66002..ace7a322 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import, print_function -__copyright__ = "Copyright (C) 2012-2013 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2012-2018 Andreas Kloeckner +Copyright (C) 2018 Christian Howard +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -64,12 +67,12 @@ class VectorChopper(object): for entry in structured_vec: if isinstance(entry, self.array_module.ndarray): length = len(entry) - isScalar = False + is_scalar = False else: length = 1 - isScalar = True + is_scalar = True - self.slices.append((isScalar,slice(num_dofs, num_dofs+length))) + self.slices.append((is_scalar,slice(num_dofs, num_dofs+length))) num_dofs += length def stack(self, vec): @@ -89,11 +92,13 @@ class VectorChopper(object): return vec from pytools.obj_array import make_obj_array - result = make_obj_array([vec[slc] for (isScalar,slc) in self.slices]) + result = make_obj_array([vec[slc] for (is_scalar,slc) in self.slices]) + if self.queue is not None: for n in range(0,len(self.slices)): if self.slices[n][0]: result[n] = result[n].get(self.queue)[0] + return result # }}} diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index a966c61b..178d3df6 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -48,7 +48,7 @@ __doc__ = """ # {{{ point source def get_sym_maxwell_point_source(kernel, jxyz, k): - """Return a symbolic expression that, when bound to a + r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a field satisfying Maxwell's equations. @@ -77,7 +77,7 @@ def get_sym_maxwell_point_source(kernel, jxyz, k): # {{{ plane wave def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=None): - """Return a symbolic expression that, when bound to a + r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a field satisfying Maxwell's equations. @@ -120,7 +120,7 @@ def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=N # {{{ point source for vector potential based on Lorenz gauge def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): - """Return a symbolic expression that, when bound to a + r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a potential fields satisfying Maxwell's equations. @@ -139,7 +139,7 @@ def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): # }}} def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): - """ + r""" Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the gradient of a scalar potential field satisfying Maxwell's equations. @@ -152,7 +152,7 @@ def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): return grad_phi def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): - """ + r""" Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the divergence of a vector potential field satisfying Maxwell's equations. @@ -165,7 +165,7 @@ def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): return divA def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): - """ + r""" Return a 2-tuple of symbolic expressions that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the scalar and vector potential fields satisfying Maxwell's equations that represent a plane wave. diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 508672fd..9d4c1ba9 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -76,13 +76,16 @@ class DPIEOperator: for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) - def numVectorPotentialDensities(self): + def num_vector_potential_densities0(self): return 5*len(self.geometry_list) - def numScalarPotentialDensities(self): + def num_vector_potential_densities(self): + return 4*len(self.geometry_list) + + def num_scalar_potential_densities(self): return 2*len(self.geometry_list) - def getVectorDomainList(self): + def get_vector_domain_list0(self): """ Method to return domain list that will be used within the scipy_op method to solve the system of discretized integral equations. What is returned should just @@ -90,7 +93,7 @@ class DPIEOperator: """ # initialize domain list - domain_list = [None]*self.numVectorPotentialDensities() + domain_list = [None]*self.num_vector_potential_densities0() # get strings for the actual densities for n in range(0,self.nobjs): @@ -109,7 +112,7 @@ class DPIEOperator: # return the domain list return domain_list - def getScalarDomainList(self): + def get_vector_domain_list(self): """ Method to return domain list that will be used within the scipy_op method to solve the system of discretized integral equations. What is returned should just @@ -117,7 +120,33 @@ class DPIEOperator: """ # initialize domain list - domain_list = [None]*self.numScalarPotentialDensities() + domain_list = [None]*self.num_vector_potential_densities() + + # get strings for the actual densities + for n in range(0,self.nobjs): + + # grab nth location identifier + location = self.geometry_list[n] + "t" + + # assign domain for nth vector density + domain_list[2*n] = location + domain_list[2*n+1] = location + + # assign domain for nth scalar density + domain_list[2*self.nobjs + n] = location + + # return the domain list + return domain_list + + def get_scalar_domain_list(self): + """ + Method to return domain list that will be used within the scipy_op method to + solve the system of discretized integral equations. What is returned should just + be a list with values that are strings or None. + """ + + # initialize domain list + domain_list = [None]*self.num_scalar_potential_densities() # get strings for the actual densities for n in range(0,self.nobjs): @@ -369,7 +398,7 @@ class DPIEOperator: # return the resulting field return sym.join_fields(f, Q/self.k) - def A_operator(self, A_densities): + def a_operator0(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` """ @@ -422,7 +451,59 @@ class DPIEOperator: return output - def A_rhs(self, A_inc, divA_inc): + def a_operator(self, A_densities): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # extract the densities needed to solve the system of equations + a_loc = A_densities[:(2*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + v = A_densities[(3*self.nobjs):] + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # init output matvec vector for the phi density IE + output = np.zeros((4*self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + # get the nth target geometry to have IE solved across + obj_n = self.geometry_list[n] + + # generate the set of equations for the vector densities, a, coupled + # across the various geometries involved + output[2*n:2*(n+1)] = xyz_to_tangential((0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ + + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),obj_n),where=obj_n) \ + + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),obj_n),where=obj_n) + \ + sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,obj_n)),where=obj_n) + )), where=obj_n) + + # generate the set of equations for the scalar densities, rho, coupled + # across the various geometries involved + output[(2*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,obj_n) \ + + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),obj_n)) \ + -self.k*self.S(rho_m,target=obj_n) + )\ + + v[n] + + # add the equation that integrates everything out into some constant + output[3*self.nobjs + n] = sym.integral(ambient_dim=3,dim=2,\ + operand=(sym.n_dot(sym.curl(self.S(a,target=obj_n)),where=obj_n) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list),target=obj_n),where=obj_n) + \ + 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list),target=obj_n),where=obj_n) - 0.5*rho[n] + self.Sp(rho_m,target=obj_n))),\ + where=obj_n) + + # print something to help with debugging + #print(sym.pretty(output)) + + # return output equations + return output + + + def a_rhs0(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` """ @@ -439,6 +520,23 @@ class DPIEOperator: # define RHS for `A` integral equation system return sym.join_fields( f, h, q) + def a_rhs(self, A_inc, divA_inc): + """ + The Right-Hand-Side for the Integral Equation for `A` + """ + + # get the q , h , and vec(f) associated with each object + q = np.zeros((self.nobjs,), dtype=self.stype) + h = np.zeros((self.nobjs,), dtype=self.stype) + f = np.zeros((2*self.nobjs,), dtype=self.stype) + for i in range(0,self.nobjs): + q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) + h[i] = -divA_inc[i]/self.k + f[2*i:2*(i+1)] = xyz_to_tangential(-sym.n_cross(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) + + # define RHS for `A` integral equation system + return sym.join_fields( f, h, q) + def scalar_potential_rep(self, phi_densities, target=None): """ This method is a representation of the scalar potential, phi, @@ -452,7 +550,7 @@ class DPIEOperator: # evaluate scalar potential representation return self.D(sigma_m,target,qfl=None) - 1j*self.k*self.S(sigma_m,target,qfl=None) - def vector_potential_rep(self, A_densities, target=None): + def vector_potential_rep0(self, A_densities, target=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` @@ -471,8 +569,26 @@ class DPIEOperator: return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) + def vector_potential_rep(self, A_densities, target=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ - def scattered_volume_field(self, phi_densities, A_densities, target=None): + # extract the densities needed to solve the system of equations + a_loc = A_densities[:(2*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # define the vector potential representation + return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ + + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) + + + def scattered_volume_field0(self, phi_densities, A_densities, target=None): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the @@ -493,7 +609,6 @@ class DPIEOperator: rho = A_densities[(3*self.nobjs):(4*self.nobjs)] rho_m = rho.reshape((1,self.nobjs)) for n in range(0,self.nobjs): - #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) a[:,n] = a_loc[3*n:3*(n+1)] # obtain expressions for scalar and vector potentials @@ -508,4 +623,39 @@ class DPIEOperator: # join the fields into a vector return sym.join_fields(E_scat, H_scat) + def scattered_volume_field(self, phi_densities, A_densities, target=None): + """ + 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`. + """ + + # extract the densities needed to solve the system of equations + sigma = phi_densities[:self.nobjs] + sigma_m = sigma.reshape((1,self.nobjs)) + + # extract the densities needed to solve the system of equations + a_loc = A_densities[:(2*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # obtain expressions for scalar and vector potentials + A = self.vector_potential_rep(A_densities, target=target) + phi = self.scalar_potential_rep(phi_densities, target=target) + + # evaluate the potential form for the electric and magnetic fields + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3,self.S(sigma_m,target,qfl=None)) + H_scat = sym.curl(sym.curl(self.S(a,target,qfl=None))) - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ + + 1j*(self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) ) + + # join the fields into a vector + return sym.join_fields(E_scat, H_scat) + # }}} diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 34db30ed..b8a4cf36 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -2,6 +2,7 @@ from __future__ import division, absolute_import __copyright__ = """ Copyright (C) 2010-2018 Andreas Kloeckner +Copyright (C) 2018 Christian Howard """ __license__ = """ diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 3aa72d53..ac55f076 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import, print_function -__copyright__ = "Copyright (C) 2017 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2017 - 2018 Andreas Kloeckner +Copyright (C) 2017 - 2018 Christian Howard +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -260,8 +263,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # specify some symbolic variables that will be used # in the process to solve integral equations for the DPIE - phi_densities = sym.make_sym_vector("phi_densities", dpie.numScalarPotentialDensities()) - A_densities = sym.make_sym_vector("A_densities", dpie.numVectorPotentialDensities()) + phi_densities = sym.make_sym_vector("phi_densities", dpie.num_scalar_potential_densities()) + A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities()) # get test source locations from the passed in case's queue test_source = case.get_source(queue) @@ -393,11 +396,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # setup operators that will be solved phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) - A_op = bind(geom_map,dpie.A_operator(A_densities=A_densities)) + A_op = bind(geom_map,dpie.a_operator(A_densities=A_densities)) # setup the RHS with provided data so we can solve for density values across the domain phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) - A_rhs = bind(geom_map,dpie.A_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) + A_rhs = bind(geom_map,dpie.a_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -411,13 +414,13 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # solve for the scalar potential densities gmres_result = gmres( - phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.getScalarDomainList(),**knl_kwargs), + phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(),**knl_kwargs), phi_rhs, **gmres_settings) phi_dens = gmres_result.solution # solve for the vector potential densities gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.getVectorDomainList(), **knl_kwargs), + A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), A_rhs, **gmres_settings) A_dens = gmres_result.solution -- GitLab From 3fd128ffac87105ee4b12c5e5a369228212c39c0 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 21 Mar 2018 14:12:51 -0500 Subject: [PATCH 33/59] Removed any duplicated methods related to the non-tangential to tangential conversion of the DPIE operators (particularly for the vector operator). Also removed duplicated methods for fixed methods that reside in the primitives.py file. --- pytential/symbolic/pde/maxwell/dpie.py | 159 +------------------------ pytential/symbolic/primitives.py | 13 -- 2 files changed, 2 insertions(+), 170 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 9d4c1ba9..97d74337 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -76,42 +76,12 @@ class DPIEOperator: for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) - def num_vector_potential_densities0(self): - return 5*len(self.geometry_list) - def num_vector_potential_densities(self): return 4*len(self.geometry_list) def num_scalar_potential_densities(self): return 2*len(self.geometry_list) - def get_vector_domain_list0(self): - """ - Method to return domain list that will be used within the scipy_op method to - solve the system of discretized integral equations. What is returned should just - be a list with values that are strings or None. - """ - - # initialize domain list - domain_list = [None]*self.num_vector_potential_densities0() - - # get strings for the actual densities - for n in range(0,self.nobjs): - - # grab nth location identifier - location = self.geometry_list[n] + "t" - - # assign domain for nth vector density - domain_list[3*n] = location - domain_list[3*n+1] = location - domain_list[3*n+2] = location - - # assign domain for nth scalar density - domain_list[3*self.nobjs + n] = location - - # return the domain list - return domain_list - def get_vector_domain_list(self): """ Method to return domain list that will be used within the scipy_op method to @@ -398,58 +368,6 @@ class DPIEOperator: # return the resulting field return sym.join_fields(f, Q/self.k) - def a_operator0(self, A_densities): - """ - Integral Equation operator for obtaining vector potential, `A` - """ - - # extract the densities needed to solve the system of equations - a_loc = A_densities[:(3*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(3*self.nobjs):(4*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - v = A_densities[(4*self.nobjs):] - for n in range(0,self.nobjs): - #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) - a[:,n] = a_loc[3*n:3*(n+1)] - - # init output matvec vector for the phi density IE - output = np.zeros((5*self.nobjs,), dtype=self.stype) - - # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): - - # get the nth target geometry to have IE solved across - obj_n = self.geometry_list[n] - - # generate the set of equations for the vector densities, a, coupled - # across the various geometries involved - output[3*n:3*(n+1)] = (0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ - + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),obj_n),where=obj_n) \ - + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),obj_n),where=obj_n) + \ - sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,obj_n)),where=obj_n) - )) - - # generate the set of equations for the scalar densities, rho, coupled - # across the various geometries involved - output[(3*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,obj_n) \ - + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),obj_n)) \ - -self.k*self.S(rho_m,target=obj_n) - )\ - + v[n] - - # add the equation that integrates everything out into some constant - output[4*self.nobjs + n] = sym.integral(ambient_dim=3,dim=2,\ - operand=(sym.n_dot(sym.curl(self.S(a,target=obj_n)),where=obj_n) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list),target=obj_n),where=obj_n) + \ - 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list),target=obj_n),where=obj_n) - 0.5*rho[n] + self.Sp(rho_m,target=obj_n))),\ - where=obj_n) - - # print something to help with debugging - #print(sym.pretty(output)) - - # return output equations - return output - def a_operator(self, A_densities): """ @@ -502,24 +420,6 @@ class DPIEOperator: # return output equations return output - - def a_rhs0(self, A_inc, divA_inc): - """ - The Right-Hand-Side for the Integral Equation for `A` - """ - - # get the q , h , and vec(f) associated with each object - q = np.zeros((self.nobjs,), dtype=self.stype) - h = np.zeros((self.nobjs,), dtype=self.stype) - f = np.zeros((3*self.nobjs,), dtype=self.stype) - for i in range(0,self.nobjs): - q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) - h[i] = -divA_inc[i]/self.k - f[3*i:3*(i+1)] = -sym.n_cross(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]) - - # define RHS for `A` integral equation system - return sym.join_fields( f, h, q) - def a_rhs(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` @@ -550,26 +450,7 @@ class DPIEOperator: # evaluate scalar potential representation return self.D(sigma_m,target,qfl=None) - 1j*self.k*self.S(sigma_m,target,qfl=None) - def vector_potential_rep0(self, A_densities, target=None): - """ - This method is a representation of the vector potential, phi, - based on the vector density `a` and scalar density `rho` - """ - - # extract the densities needed to solve the system of equations - a_loc = A_densities[:(3*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(3*self.nobjs):(4*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - for n in range(0,self.nobjs): - #a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) - a[:,n] = a_loc[3*n:3*(n+1)] - - # define the vector potential representation - return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ - + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) - - def vector_potential_rep(self, A_densities, target=None): + def vector_potential_rep(self, A_densities, target=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` @@ -587,43 +468,7 @@ class DPIEOperator: return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) - - def scattered_volume_field0(self, phi_densities, A_densities, target=None): - """ - 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`. - """ - - # extract the densities needed to solve the system of equations - sigma = phi_densities[:self.nobjs] - sigma_m = sigma.reshape((1,self.nobjs)) - - # extract the densities needed to solve the system of equations - a_loc = A_densities[:(3*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(3*self.nobjs):(4*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - for n in range(0,self.nobjs): - a[:,n] = a_loc[3*n:3*(n+1)] - - # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(A_densities, target=target) - phi = self.scalar_potential_rep(phi_densities, target=target) - - # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3,self.S(sigma_m,target,qfl=None)) - H_scat = sym.curl(sym.curl(self.S(a,target,qfl=None))) - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ - + 1j*(self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) ) - - # join the fields into a vector - return sym.join_fields(E_scat, H_scat) - - def scattered_volume_field(self, phi_densities, A_densities, target=None): + def scattered_volume_field(self, phi_densities, A_densities, target=None): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index b8a4cf36..b0e37b22 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -385,15 +385,6 @@ def parametrization_derivative_matrix(ambient_dim, dim, where=None): [NodeCoordinateComponent(i, where) for i in range(ambient_dim)], ambient_dim, dim, where) -def parametrization_derivative_matrix_old(ambient_dim, dim, where=None): - """Return a :class:`np.array` representing the derivative of the - reference-to-global parametrization. - """ - - return reference_jacobian( - [NodeCoordinateComponent(i, where) for i in range(ambient_dim)], - ambient_dim, dim) - def parametrization_derivative(ambient_dim, dim, where=None): """Return a :class:`pymbolic.geometric_algebra.MultiVector` representing @@ -1119,10 +1110,6 @@ def div(vec): ambient_dim = len(vec) return sum(dd_axis(iaxis, ambient_dim, vec[iaxis]) for iaxis in range(ambient_dim)) -def div_old(vec): - ambient_dim = len(vec) - return sum(dd_axis(iaxis, ambient_dim, vec) for iaxis in range(ambient_dim)) - def curl(vec): from pytools import levi_civita from pytools.obj_array import make_obj_array -- GitLab From 662562b99a1f8da892136ddaa4e31fece0decf7f Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Mon, 23 Apr 2018 13:53:10 -0500 Subject: [PATCH 34/59] Made changes to the DPIE model and test code as well as some changes to the execution.py so the subproblem could be handled. As of now, the execution.py file still has work I need to do to ensure it can manage single densities created as a single symbolic variable versus single densities created as a single element symbolic array. Changes to the DPIE related software has been to add some tests to help with validating and debugging the results of the DPIE code. --- pytential/symbolic/execution.py | 2 +- pytential/symbolic/pde/maxwell/dpie.py | 110 ++++- test/test_maxwell_dpie.py | 606 ++++++++++++++++--------- 3 files changed, 505 insertions(+), 213 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 118bc1c4..0c7c2e11 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -190,7 +190,7 @@ class MatVecOp: else: out_host = False - do_split = len(self.starts_and_ends) > 1 + do_split = len(self.starts_and_ends) >= 1 from pytools.obj_array import make_obj_array if do_split: diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 97d74337..a8b74267 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -24,7 +24,7 @@ THE SOFTWARE. # import useful tools/libs import numpy as np # noqa -from pytential import sym +from pytential import bind, sym from collections import namedtuple from functools import partial @@ -76,6 +76,9 @@ class DPIEOperator: for idx in range(0,len(geometry_list)): self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) + def num_distinct_objects(self): + return self.nobjs + def num_vector_potential_densities(self): return 4*len(self.geometry_list) @@ -130,6 +133,28 @@ class DPIEOperator: # return the domain list return domain_list + def get_subproblem_domain_list(self): + """ + Method to return domain list that will be used within the scipy_op method to + solve the system of discretized integral equations. What is returned should just + be a list with values that are strings or None. + """ + + # initialize domain list + domain_list = [None]*self.nobjs + + # get strings for the actual densities + for n in range(0,self.nobjs): + + # grab nth location identifier + location = self.geometry_list[n] + "t" + + # assign domain for nth scalar density + domain_list[n] = location + + # return the domain list + return domain_list + def D(self, density_vec, target=None, qfl="avg"): """ @@ -343,7 +368,7 @@ class DPIEOperator: # setup equation that integrates some integral operators over the nth surface output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, - operand=(self.Dp(sigma_m,target=obj_n)/self.k+ 1j*sigma[n]/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ + operand=(self.Dp(sigma_m,target=obj_n)/self.k + 1j*sigma[n]/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ where=obj_n) # return the resulting system of IE @@ -437,6 +462,58 @@ class DPIEOperator: # define RHS for `A` integral equation system return sym.join_fields( f, h, q) + def subproblem_operator(self, tau_densities, alpha = 1j): + """ + Integral Equation operator for obtaining sub problem solution + """ + + # extract the densities from the sub problem solution + tau = tau_densities[:self.nobjs] + tau_m = tau.reshape((1,self.nobjs)) + + # init output matvec vector for the phi density IE + output = np.zeros((self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + # get nth disjoint object + obj_n = self.geometry_list[n] + + # setup IE for evaluation over the nth disjoint object's surface + output[n] = 0.5*tau[n] + self.D(tau_m,obj_n) - alpha*self.S(tau_m,obj_n) + + # return the resulting system of IE + return output + + def subproblem_rhs(self, A_densities): + """ + Integral Equation RHS for obtaining sub problem solution + """ + # extract the densities needed to solve the system of equations + a_loc = A_densities[:(2*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)] + rho_m = rho.reshape((1,self.nobjs)) + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + + # init output matvec vector for the phi density IE + output = np.zeros((self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + # get nth disjoint object + obj_n = self.geometry_list[n] + + # setup IE for evaluation over the nth disjoint object's surface + output[n] = sym.div(self.S(a,target=obj_n)) + + # return the resulting system of IE + return output + + def scalar_potential_rep(self, phi_densities, target=None): """ This method is a representation of the scalar potential, phi, @@ -456,7 +533,7 @@ class DPIEOperator: based on the vector density `a` and scalar density `rho` """ - # extract the densities needed to solve the system of equations + # extract the densities from the main IE solution a_loc = A_densities[:(2*self.nobjs)] a = np.zeros((3,self.nobjs),dtype=self.stype) rho = A_densities[(2*self.nobjs):(3*self.nobjs)] @@ -468,7 +545,20 @@ class DPIEOperator: return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) - def scattered_volume_field(self, phi_densities, A_densities, target=None): + def subproblem_rep(self, tau_densities, target=None, alpha = 1j): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + + # extract the densities needed to solve the system of equations + tau = tau_densities[:self.nobjs] + tau_m = tau.reshape((1,self.nobjs)) + + # evaluate scalar potential representation + return self.D(tau_m,target,qfl=None) - alpha*self.S(tau_m,target,qfl=None) + + def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the @@ -491,14 +581,20 @@ class DPIEOperator: for n in range(0,self.nobjs): a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + # extract the densities from the sub problem solution + tau = tau_densities[:self.nobjs] + tau_m = tau.reshape((1,self.nobjs)) + # obtain expressions for scalar and vector potentials A = self.vector_potential_rep(A_densities, target=target) phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3,self.S(sigma_m,target,qfl=None)) - H_scat = sym.curl(sym.curl(self.S(a,target,qfl=None))) - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ - + 1j*(self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) ) + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3, self.S(sigma_m,target,qfl=None)) + H_scat = sym.grad(ambient_dim=3,operand=(self.D(tau_m,target,qfl=None) - alpha*self.S(tau_m,target,qfl=None))) \ + + self.k*self.k*self.S(a,target,qfl=None) \ + - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ + + 1j*self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) # join the fields into a vector return sym.join_fields(E_scat, H_scat) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index ac55f076..d193d6fc 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -51,6 +51,7 @@ from pyopencl.tools import ( # noqa class MaxwellTestCase: fmm_backend = "fmmlib" + #fmm_backend = "sumpy" def __init__(self, k, is_interior, resolutions, qbx_order, fmm_tolerance): @@ -63,7 +64,7 @@ class MaxwellTestCase: class SphereTestCase(MaxwellTestCase): target_order = 8 - gmres_tol = 1e-3 + gmres_tol = 1e-10 def get_mesh(self, resolution, target_order): from meshmode.mesh.generation import generate_icosphere @@ -190,7 +191,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) -tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], +tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1, 2], qbx_order=3, fmm_tolerance=1e-4) tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], @@ -265,6 +266,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # in the process to solve integral equations for the DPIE phi_densities = sym.make_sym_vector("phi_densities", dpie.num_scalar_potential_densities()) A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities()) + tau_densities = sym.make_sym_vector("tau_densities", dpie.num_distinct_objects()) # get test source locations from the passed in case's queue test_source = case.get_source(queue) @@ -276,10 +278,6 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define a random number generator based on OpenCL rng = cl.clrandom.PhiloxGenerator(cl_ctx, seed=12) - # use OpenCL random number generator to create a set of random - # source locations for various variables being solved for - src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) - # define some parameters for the incident wave # direction for the wave u_dir = np.array([1, 0, 0],dtype=np.complex128) @@ -309,6 +307,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): def get_incident_potentials(objects, target=None): return bind(objects,mw.get_sym_maxwell_planewave_potentials(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue, k=case.k,u=u_dir,Ep=Ep) + # define a smooth function to represent the density + def dummy_density(omega = 1.0, where=None): + x = sym.nodes(3, where).as_vector() + return sym.sin(omega*sym.n_dot(x,where)) + # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField(vector_from_device(queue, get_incident_plane_wave_EHField(calc_patch_tgt))) @@ -326,246 +329,439 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # }}} - # {{{ Solve for the scattered field - - loc_sign = -1 if case.is_interior else +1 - - # import a bunch of stuff that will be useful - from pytools.convergence import EOCRecorder - from pytential.qbx import QBXLayerPotentialSource - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory - from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - - # setup an EOC Recorder - eoc_rec_repr_maxwell = EOCRecorder() - eoc_pec_bc = EOCRecorder() - eoc_rec_e = EOCRecorder() - eoc_rec_h = EOCRecorder() - - # loop through the case's resolutions and compute the scattered field solution - for resolution in case.resolutions: - - # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) - - # define the pre-scattered discretization - pre_scat_discr = Discretization( - cl_ctx, scat_mesh, - InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - - # obtain the QBX layer potential source object - qbx, _ = QBXLayerPotentialSource( - pre_scat_discr, fine_order=4*case.target_order, - qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder( - case.fmm_tolerance), - fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) - - # define the geometry dictionary - #geom_map = {"g0": qbx} - geom_map = {"obj0":qbx, "obj0t":qbx.density_discr, "scat":qbx.density_discr} - - # get the maximum mesh element edge length - h_max = qbx.h_max - - # define the scattered and observation discretization - scat_discr = qbx.density_discr - obs_discr = Discretization(cl_ctx, observation_mesh, - InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - - # get the incident field at the scatter and observation locations - #inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) - #inc_EM_field_obs = EHField(eval_inc_field_at(obs_discr)) - #inc_vec_field_scat = get_inc_potentials(scat_discr) - #inc_vec_field_obs = get_inc_potentials(obs_discr) + # # {{{ Test the representations + test_representations = True + + if test_representations: + + # import a bunch of stuff that will be useful + from pytools.convergence import EOCRecorder + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + + # # loop through the case's resolutions and compute the scattered field solution + rep_error = [] + for resolution in case.resolutions: + + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # use OpenCL random number generator to create a set of random + # source locations for various variables being solved for + dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) + qbx0, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + #fmm_order=False, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} + dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) + dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) + v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) + s = rng.normal(queue, (), dtype=np.float64) + for i in range(0,len(dummy_phi)): + dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + for i in range(0,len(dummy_A)): + dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) + test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs(A_densities=A_densities))(queue,A_densities=dummy_A,**knl_kwargs) + + # set GMRES settings for solving + gmres_settings = dict( + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + + # get the GMRES functionality + from pytential.solve import gmres + + subprob_result = gmres( + test_sigma_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), + test_sigma_rhs, **gmres_settings) + dummy_tau = subprob_result.solution + + sym_repr0 = dpie.scattered_volume_field(phi_densities,A_densities,tau_densities,target='tgt') + + def eval_test_repr_at(tgt): + map = geom_map + map['tgt'] = tgt + return bind(map, sym_repr0)(queue, phi_densities=dummy_phi, A_densities=dummy_A, tau_densities=dummy_tau, **knl_kwargs) + + pde_test_repr = EHField(vector_from_device(queue, eval_test_repr_at(calc_patch_tgt))) + + maxwell_residuals = [ + calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) + for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] + print("Maxwell residuals:", maxwell_residuals) + rep_error.append(maxwell_residuals) + + print("Representation Error Results:") + for n in range(0,len(rep_error)): + print("Case {0}: {1}".format(n+1,rep_error[n])) + + # #}}} + + + # # {{{ Test the operators + test_operators = False + if test_operators: + + # define error array + op_error = [] + + # define method to get locations to evaluate representation + def epsilon_off_boundary(where=None, epsilon=1e-4): + x = sym.nodes(3, where).as_vector() + return x + sym.normal(3,3,where).as_vector()*epsilon + + # import a bunch of stuff that will be useful + from pytools.convergence import EOCRecorder + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + + # loop through the case's resolutions and compute the scattered field solution + for resolution in case.resolutions: + + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # use OpenCL random number generator to create a set of random + # source locations for various variables being solved for + dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) + qbx0, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + #fmm_order=False, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} + + # init random dummy densities for the vector and scalar potentials + dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) + dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) + dummy_tau = np.array([None]*dpie0.num_distinct_objects(),dtype=dpie0.stype) + + v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) + s = rng.normal(queue, (1,), dtype=np.float64)[0] + for i in range(0,len(dummy_phi)-1): + dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + dummy_phi[1] = s + for i in range(0,len(dummy_A)-1): + dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + dummy_A[3] = s + for i in range(0,len(dummy_tau)): + dummy_tau[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + + # check that the scalar density operator and representation are similar + scalar_op = dpie0.phi_operator(phi_densities=phi_densities) + vector_op = dpie0.a_operator(A_densities=A_densities) + tau_op = dpie0.subproblem_operator(tau_densities=tau_densities) + + # evaluate operators at the dummy densities + scalar_op_eval = bind(geom_map, scalar_op)(queue, phi_densities=dummy_phi, **knl_kwargs) + vector_op_eval = bind(geom_map, vector_op)(queue, A_densities=dummy_A, **knl_kwargs) + tau_op_eval = bind(geom_map, tau_op)(queue, tau_densities=dummy_tau, **knl_kwargs) + + # compute off-boundary locations that the representation will need to be evaluated at + tgt_n = bind(geom_map,epsilon_off_boundary(where='obj0')) + geom_map['tgt'] = tgt_n + scalar_rep_eval = bind(geom_map, dpie0.scalar_potential_rep(phi_densities=phi_densities, target='tgt'))(queue, phi_densities=dummy_phi, **knl_kwargs) + vector_rep_eval = bind(geom_map, dpie0.vector_potential_rep(A_densities=A_densities, target='tgt'))(queue, A_densities=dummy_A, **knl_kwargs) + tau_op_eval = bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs) + + # compute the error between the operator values and the representation values + def error_diff(u,v): + return norm(tgt_n, queue, u-v, p=np.inf) + op_error.append([error_diff(scalar_op_eval,scalar_rep_eval), error_diff(vector_op_eval,vector_rep_eval), error_diff(sigma_op_eval,sigma_op_eval)]) + + # print the resulting error results + print("Operator Error Results:") + for n in range(0,len(op_error)): + print("Case {0}: {1}".format(n+1,op_error[n])) + + # #}}} - # get the incident fields used for boundary conditions - (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') - inc_divA_scat = get_incident_divA(geom_map,'scat') - inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') - # {{{ solve the system of integral equations - inc_A = sym.make_sym_vector("inc_A", 3) - inc_phi = sym.make_sym_vector("inc_phi",1) - inc_divA = sym.make_sym_vector("inc_divA",1) - inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) - - # setup operators that will be solved - phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) - A_op = bind(geom_map,dpie.a_operator(A_densities=A_densities)) - - # setup the RHS with provided data so we can solve for density values across the domain - phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) - A_rhs = bind(geom_map,dpie.a_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) - - # set GMRES settings for solving - gmres_settings = dict( - tol=case.gmres_tol, - progress=True, - hard_failure=True, - stall_iterations=50, no_progress_factor=1.05) - - # get the GMRES functionality - from pytential.solve import gmres + # {{{ Solve for the scattered field + solve_scattered_field = False + if solve_scattered_field: + loc_sign = -1 if case.is_interior else +1 + + # import a bunch of stuff that will be useful + from pytools.convergence import EOCRecorder + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + # setup an EOC Recorder + eoc_rec_repr_maxwell = EOCRecorder() + eoc_pec_bc = EOCRecorder() + eoc_rec_e = EOCRecorder() + eoc_rec_h = EOCRecorder() + + # loop through the case's resolutions and compute the scattered field solution + for resolution in case.resolutions: + + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # obtain the QBX layer potential source object + qbx, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + #fmm_order=False, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + #geom_map = {"g0": qbx} + geom_map = {"obj0":qbx, "obj0t":qbx.density_discr, "scat":qbx.density_discr} + + # get the maximum mesh element edge length + h_max = qbx.h_max + + # define the scattered and observation discretization + scat_discr = qbx.density_discr + obs_discr = Discretization(cl_ctx, observation_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # get the incident field at the scatter and observation locations + #inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) + #inc_EM_field_obs = EHField(eval_inc_field_at(obs_discr)) + #inc_vec_field_scat = get_inc_potentials(scat_discr) + #inc_vec_field_obs = get_inc_potentials(obs_discr) + + # get the incident fields used for boundary conditions + (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') + inc_divA_scat = get_incident_divA(geom_map,'scat') + inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') + + # {{{ solve the system of integral equations + inc_A = sym.make_sym_vector("inc_A", 3) + inc_phi = sym.make_sym_vector("inc_phi",1) + inc_divA = sym.make_sym_vector("inc_divA",1) + inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) + + # setup operators that will be solved + phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) + A_op = bind(geom_map,dpie.a_operator(A_densities=A_densities)) + + # setup the RHS with provided data so we can solve for density values across the domain + phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) + A_rhs = bind(geom_map,dpie.a_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) + + # set GMRES settings for solving + gmres_settings = dict( + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + + # get the GMRES functionality + from pytential.solve import gmres + + # solve for the scalar potential densities + gmres_result = gmres( + phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(),**knl_kwargs), + phi_rhs, **gmres_settings) + phi_dens = gmres_result.solution + + # solve for the vector potential densities + gmres_result = gmres( + A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), + A_rhs, **gmres_settings) + A_dens = gmres_result.solution + + # solve sub problem for sigma densities + tau_op= bind(geom_map,dpie.subproblem_operator(tau_densities=tau_densities)) + tau_rhs= bind(geom_map,dpie.subproblem_rhs(A_densities=A_densities))(queue,A_densities=A_dens,**knl_kwargs) + gmres_result = gmres( + sigma_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie.get_subproblem_domain_list(), **knl_kwargs), + sigma_rhs, **gmres_settings) + tau_dens = gmres_result.solution + + # extract useful solutions + #phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) + #Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + + # }}} - # solve for the scalar potential densities - gmres_result = gmres( - phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(),**knl_kwargs), - phi_rhs, **gmres_settings) - phi_dens = gmres_result.solution + # {{{ volume eval + + sym_repr = dpie.scattered_volume_field(phi_densities,A_densities,tau_densities,target='tgt') - # solve for the vector potential densities - gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), - A_rhs, **gmres_settings) - A_dens = gmres_result.solution + def eval_repr_at(tgt): + map = geom_map + map['tgt'] = tgt + return bind(map, sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, tau_densities=tau_dens, **knl_kwargs) - # extract useful solutions - #phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) - #Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + pde_test_repr = EHField(vector_from_device(queue, eval_repr_at(calc_patch_tgt))) + + maxwell_residuals = [ + calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) + for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] + print("Maxwell residuals:", maxwell_residuals) - # }}} - - # {{{ volume eval - - sym_repr = dpie.scattered_volume_field(phi_densities,A_densities,target='tgt') - - def eval_repr_at(tgt): - map = geom_map - map['tgt'] = tgt - return bind(map, sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, **knl_kwargs) + eoc_rec_repr_maxwell.add_data_point(h_max, max(maxwell_residuals)) - pde_test_repr = EHField(vector_from_device(queue, eval_repr_at(calc_patch_tgt))) + # }}} - maxwell_residuals = [ - calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) - for x in frequency_domain_maxwell( - calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] - print("Maxwell residuals:", maxwell_residuals) + # {{{ check PEC BC on total field - eoc_rec_repr_maxwell.add_data_point(h_max, max(maxwell_residuals)) + #bc_repr = EHField(dpie.scattered_volume_field( + # phi_densities, A_densities, qbx_forced_limit=loc_sign)) + #pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) + #pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + sym.curl(inc_xyz_vec_sym[1:])) - # }}} + #eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( + # queue, phi_densities=phi_dens, A_densities=A_dens, inc_vec_fld=inc_field_vec_scat, + # **knl_kwargs) - # {{{ check PEC BC on total field + #def scat_norm(f): + # return norm(qbx, queue, f, p=np.inf) - bc_repr = EHField(dpie.scattered_volume_field( - phi_densities, A_densities, qbx_forced_limit=loc_sign)) - pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) - pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + sym.curl(inc_xyz_vec_sym[1:])) + #e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_EM_field_scat.e) + #h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_EM_field_scat.h) - eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( - queue, phi_densities=phi_dens, A_densities=A_dens, inc_vec_fld=inc_field_vec_scat, - **knl_kwargs) + #print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) - def scat_norm(f): - return norm(qbx, queue, f, p=np.inf) + #eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) - e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_EM_field_scat.e) - h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_EM_field_scat.h) + # }}} - print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) + # {{{ visualization - eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) + if visualize: + from meshmode.discretization.visualization import make_visualizer + bdry_vis = make_visualizer(queue, scat_discr, case.target_order+3) - # }}} + bdry_normals = bind(scat_discr, sym.normal(3))(queue)\ + .as_vector(dtype=object) - # {{{ visualization + bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ + ("phi", phi), + ("Axyz", Axyz), + ("Einc", inc_EM_field_scat.e), + ("Hinc", inc_EM_field_scat.h), + ("bdry_normals", bdry_normals), + ("e_bc_residual", eh_bc_values[:3]), + ("h_bc_residual", eh_bc_values[3]), + ]) - if visualize: - from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, scat_discr, case.target_order+3) + fplot = make_field_plotter_from_bbox( + find_bounding_box(scat_discr.mesh), h=(0.05, 0.05, 0.3), + extend_factor=0.3) - bdry_normals = bind(scat_discr, sym.normal(3))(queue)\ - .as_vector(dtype=object) + from pytential.qbx import QBXTargetAssociationFailedException - bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ - ("phi", phi), - ("Axyz", Axyz), - ("Einc", inc_EM_field_scat.e), - ("Hinc", inc_EM_field_scat.h), - ("bdry_normals", bdry_normals), - ("e_bc_residual", eh_bc_values[:3]), - ("h_bc_residual", eh_bc_values[3]), - ]) + qbx_tgt_tol = qbx.copy(target_association_tolerance=0.2) - fplot = make_field_plotter_from_bbox( - find_bounding_box(scat_discr.mesh), h=(0.05, 0.05, 0.3), - extend_factor=0.3) + fplot_tgt = PointsTarget(cl.array.to_device(queue, fplot.points)) + try: + fplot_repr = eval_repr_at(fplot_tgt, source=qbx_tgt_tol) + except QBXTargetAssociationFailedException as e: + fplot.write_vtk_file( + "failed-targets.vts", + [ + ("failed_targets", e.failed_target_flags.get(queue)) + ]) + raise - from pytential.qbx import QBXTargetAssociationFailedException + fplot_repr = EHField(vector_from_device(queue, fplot_repr)) - qbx_tgt_tol = qbx.copy(target_association_tolerance=0.2) + fplot_inc = EHField( + vector_from_device(queue, eval_inc_field_at(fplot_tgt))) - fplot_tgt = PointsTarget(cl.array.to_device(queue, fplot.points)) - try: - fplot_repr = eval_repr_at(fplot_tgt, source=qbx_tgt_tol) - except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( - "failed-targets.vts", + "potential-%s.vts" % resolution, [ - ("failed_targets", e.failed_target_flags.get(queue)) - ]) - raise - - fplot_repr = EHField(vector_from_device(queue, fplot_repr)) - - fplot_inc = EHField( - vector_from_device(queue, eval_inc_field_at(fplot_tgt))) + ("E", fplot_repr.e), + ("H", fplot_repr.h), + ("Einc", fplot_inc.e), + ("Hinc", fplot_inc.h), + ] + ) - fplot.write_vtk_file( - "potential-%s.vts" % resolution, - [ - ("E", fplot_repr.e), - ("H", fplot_repr.h), - ("Einc", fplot_inc.e), - ("Hinc", fplot_inc.h), - ] - ) + # }}} - # }}} + # {{{ error in E, H - # {{{ error in E, H + obs_repr = EHField(eval_repr_at(obs_discr)) - obs_repr = EHField(eval_repr_at(obs_discr)) + def obs_norm(f): + return norm(obs_discr, queue, f, p=np.inf) - def obs_norm(f): - return norm(obs_discr, queue, f, p=np.inf) + #inc_field_scat = EHField(get_incident_plane_wave_EHField(scat_discr)) + #inc_field_obs = EHField(get_incident_plane_wave_EHField(obs_discr)) - rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) - / obs_norm(inc_field_obs.e)) - rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) - / obs_norm(inc_field_obs.h)) + #rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) + # / obs_norm(inc_field_obs.e)) + #rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) + # / obs_norm(inc_field_obs.h)) - # }}} + # }}} - print("ERR", h_max, rel_err_h, rel_err_e) + #print("ERR", h_max, rel_err_h, rel_err_e) - eoc_rec_h.add_data_point(h_max, rel_err_h) - eoc_rec_e.add_data_point(h_max, rel_err_e) + #eoc_rec_h.add_data_point(h_max, rel_err_h) + #eoc_rec_e.add_data_point(h_max, rel_err_e) - print("--------------------------------------------------------") - print("is_interior=%s" % case.is_interior) - print("--------------------------------------------------------") + print("--------------------------------------------------------") + print("is_interior=%s" % case.is_interior) + print("--------------------------------------------------------") - good = True - for which_eoc, eoc_rec, order_tol in [ - ("maxwell", eoc_rec_repr_maxwell, 1.5), - ("PEC BC", eoc_pec_bc, 1.5), - ("H", eoc_rec_h, 1.5), - ("E", eoc_rec_e, 1.5)]: - print(which_eoc) - print(eoc_rec.pretty_print()) + good = True + for which_eoc, eoc_rec, order_tol in [ + ("maxwell", eoc_rec_repr_maxwell, 1.5) + #("PEC BC", eoc_pec_bc, 1.5), + #("H", eoc_rec_h, 1.5), + #("E", eoc_rec_e, 1.5) + ]: + print(which_eoc) + print(eoc_rec.pretty_print()) - if len(eoc_rec.history) > 1: - if eoc_rec.order_estimate() < case.qbx_order - order_tol: - good = False + if len(eoc_rec.history) > 1: + if eoc_rec.order_estimate() < case.qbx_order - order_tol: + good = False - assert good + assert good # }}} -- GitLab From db97789be008cf2472eea1407e14e1d2e7018b67 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Thu, 26 Apr 2018 19:06:12 -0500 Subject: [PATCH 35/59] Modified DPIE code to be more closely aligned with the paper naming convention and identifiable variables/operators. Also made small tweaks to little comments in the gitlab discussion about the DPIE work. Specifically adding the DPIE to the symbolic.rst doc and changing the single quote comment for the integral function into a double quote comment. --- doc/symbolic.rst | 2 + pytential/symbolic/pde/maxwell/dpie.py | 212 +++++++++++-------------- pytential/symbolic/primitives.py | 4 +- test/test_maxwell_dpie.py | 6 +- 4 files changed, 99 insertions(+), 125 deletions(-) diff --git a/doc/symbolic.rst b/doc/symbolic.rst index e6de9534..128fc84c 100644 --- a/doc/symbolic.rst +++ b/doc/symbolic.rst @@ -28,6 +28,8 @@ Maxwell's equations .. automodule:: pytential.symbolic.pde.maxwell +.. automodule:: pytential.symbolic.pde.maxwell.dpie + Internal affairs ---------------- diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index a8b74267..1ae305aa 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -70,12 +70,6 @@ class DPIEOperator: self.geometry_list = geometry_list self.nobjs = len(geometry_list) - # create the characteristic functions that give a value of - # 1 when we are on some surface/valume and a value of 0 otherwise - self.char_funcs = sym.make_sym_vector("chi",len(self.geometry_list)) - for idx in range(0,len(geometry_list)): - self.char_funcs[idx] = sym.D(self.kernel, 1, k=self.k,source=self.geometry_list[idx]) - def num_distinct_objects(self): return self.nobjs @@ -99,7 +93,7 @@ class DPIEOperator: for n in range(0,self.nobjs): # grab nth location identifier - location = self.geometry_list[n] + "t" + location = self.geometry_list[n] + "t" # assign domain for nth vector density domain_list[2*n] = location @@ -125,10 +119,10 @@ class DPIEOperator: for n in range(0,self.nobjs): # grab nth location identifier - location = self.geometry_list[n] + "t" + location = self.geometry_list[n] + "t" # assign domain for nth scalar density - domain_list[n] = location + domain_list[n] = location # return the domain list return domain_list @@ -147,10 +141,10 @@ class DPIEOperator: for n in range(0,self.nobjs): # grab nth location identifier - location = self.geometry_list[n] + "t" + location = self.geometry_list[n] + "t" # assign domain for nth scalar density - domain_list[n] = location + domain_list[n] = location # return the domain list return domain_list @@ -253,7 +247,7 @@ class DPIEOperator: else: return output - def n_cross_multi(self, density_vec, sources): + def n_cross(self, density_vec): r""" This method is such that an cross(n,a) can operate across vectors a and n that are local to a set of disjoint source surfaces. Essentially, @@ -266,6 +260,9 @@ class DPIEOperator: \bar{n} \times \bar{a}) = [ \left(n_1 \times a_1\right), ..., \left(n_m \times a_m \right)] """ + # specify the sources to be evaluated at + sources = self.geometry_list + # get the shape of density_vec (ndim, nobj) = density_vec.shape @@ -283,7 +280,7 @@ class DPIEOperator: # return result from element-wise cross product return output - def n_times_multi(self, density_vec, sources): + def n_times(self, density_vec): r""" This method is such that an :math:`\boldsymbol{n} \rho`, for some normal :math:`\boldsymbol{n}` and some scalar :math:`\rho` can be done across normals and scalars that exist on multiple surfaces. Essentially, @@ -296,6 +293,9 @@ class DPIEOperator: \bar{n}\bar{\rho} = [ \left(\boldsymbol{n}_1 \rho_1\right), ..., \left(\boldsymbol{n}_m \rho_m \right)] """ + # specify the sources to be evaluated at + sources = self.geometry_list + # get the shape of density_vec (ndim, nobj) = density_vec.shape @@ -313,33 +313,49 @@ class DPIEOperator: # return result from element-wise cross product return output - def n_cross(self, density_vec, where=None): - r""" - This method is so, given a single surface identifier, we can compute the cross product of a normal - on this surface for a set of vectors represented as columns of some matrix - :math:`\bar{a} = [a_1, a_2, \cdots, a_m]`. The goal, then, is to perform the following, given - some normal :math:`\hat{n}`: - - .. math:: - \hat{n} \times \bar{a}) = [ \left(\hat{n} \times a_1\right), ..., \left(\hat{n} \times a_m \right)] - """ - - # get the shape of density_vec - (ndim, nobj) = density_vec.shape - - # assert that the ndim value is 1 - assert ndim == 3 + def _extract_phi_densities(self,phi_densities): + return (phi_densities[:self.nobjs],phi_densities[:self.nobjs].reshape((1,self.nobjs)),phi_densities[self.nobjs:]) - # init output symbolic quantity with zeros - output = np.zeros(density_vec.shape, dtype=self.stype) + def _extract_tau_densities(self,tau_densities): + return (tau_densities,tau_densities.reshape((1,self.nobjs))) - # loop through the density and sources to construct the appropriate - # element-wise cross product operation - for k in range(0,nobj): - output[:,k] = sym.n_cross(density_vec[:,k],where=where) - - # return result from element-wise cross product - return output + def _extract_a_densities(self,A_densities): + a0 = A_densities[:(2*self.nobjs)] + a = np.zeros((3,self.nobjs),dtype=self.stype) + rho0 = A_densities[(2*self.nobjs):(3*self.nobjs)] + rho = rho0.reshape((1,self.nobjs)) + v = A_densities[(3*self.nobjs):] + for n in range(0,self.nobjs): + a[:,n] = sym.tangential_to_xyz(a0[2*n:2*(n+1)],where=self.geometry_list[n]) + return (a0, a, rho0, rho, v) + + def _L(self, a, rho, where): + return sym.join_fields( + sym.n_cross(self.S(a,where),where=where) - self.k * sym.n_cross(self.S(self.n_times(rho),where),where=where), + self.D(rho,where)) + + def _R(self, a, rho): + return sym.join_fields( + self.k* sym.n_cross(self.S(self.n_cross(a),where),where=where) + sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), + sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,target=where) + ) + + def _scaledDPIEs_integral(self, sigma, sigma_n, where): + return sym.integral( + ambient_dim=3, + dim=2, + operand=(self.Dp(sigma,target=where)/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where)), + where=obj_n) + + def _scaledDPIEv_integral(self, a, rho, rho_n, where): + return sym.integral( + ambient_dim=3, + dim=2, + operand=( + sym.n_dot(sym.curl(self.S(a,target=where)),where=where) - self.k*sym.n_dot(self.S(self.n_times(rho),target=where),where=where) \ + + 1j*(self.k*sym.n_dot(self.S(self.n_cross(a),target=where),where=where) - 0.5*rho_n + self.Sp(rho,target=where)) + ), + where=where) def phi_operator(self, phi_densities): @@ -348,11 +364,7 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - sigma = phi_densities[:self.nobjs] - sigma_m = sigma.reshape((1,self.nobjs)) - - # extract the scalar quantities, { V_j }, that remove the nullspace - V = phi_densities[self.nobjs:] + (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) # init output matvec vector for the phi density IE output = np.zeros((2*self.nobjs,), dtype=self.stype) @@ -364,12 +376,10 @@ class DPIEOperator: obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = 0.5*sigma[n] + self.D(sigma_m,obj_n) - 1j*self.k*self.S(sigma_m,obj_n) - V[n] + output[n] = 0.5*sigma0[n] + self.D(sigma,obj_n) - 1j*self.k*self.S(sigma,obj_n) - V[n] # setup equation that integrates some integral operators over the nth surface - output[self.nobjs + n] = sym.integral(ambient_dim=3,dim=2, - operand=(self.Dp(sigma_m,target=obj_n)/self.k + 1j*sigma[n]/2.0 - 1j*self.Sp(sigma_m,target=obj_n)),\ - where=obj_n) + output[self.nobjs + n] = self._scaledDPIEs_integral(sigma,sigma[0,n],where=obj_n) # return the resulting system of IE return output @@ -400,13 +410,7 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - a_loc = A_densities[:(2*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - v = A_densities[(3*self.nobjs):] - for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # init output matvec vector for the phi density IE output = np.zeros((4*self.nobjs,), dtype=self.stype) @@ -417,30 +421,20 @@ class DPIEOperator: # get the nth target geometry to have IE solved across obj_n = self.geometry_list[n] + # Compute two IE Operators on a and rho densities + L = self._L(a, rho, obj_n) + R = self._R(a, rho, obj_n) + # generate the set of equations for the vector densities, a, coupled # across the various geometries involved - output[2*n:2*(n+1)] = xyz_to_tangential((0.5*a[:,n] + sym.n_cross(self.S(a,obj_n),where=obj_n) \ - + -self.k * sym.n_cross(self.S(self.n_times_multi(rho_m,self.geometry_list),obj_n),where=obj_n) \ - + 1j*( self.k* sym.n_cross(self.S(self.n_cross_multi(a,self.geometry_list),obj_n),where=obj_n) + \ - sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho_m,obj_n)),where=obj_n) - )), where=obj_n) + output[2*n:2*(n+1)] = xyz_to_tangential(0.5*a[:,n] + L[:3] + 1j*R[:3], where=obj_n) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(2*self.nobjs + n)] = 0.5*rho[n] + self.D(rho_m,obj_n) \ - + 1j*( sym.div(self.S(self.n_cross_multi(a,self.geometry_list),obj_n)) \ - -self.k*self.S(rho_m,target=obj_n) - )\ - + v[n] + output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] + v[n] # add the equation that integrates everything out into some constant - output[3*self.nobjs + n] = sym.integral(ambient_dim=3,dim=2,\ - operand=(sym.n_dot(sym.curl(self.S(a,target=obj_n)),where=obj_n) - self.k*sym.n_dot(self.S(self.n_times_multi(rho_m,self.geometry_list),target=obj_n),where=obj_n) + \ - 1j*(self.k*sym.n_dot(self.S(self.n_cross_multi(a,self.geometry_list),target=obj_n),where=obj_n) - 0.5*rho[n] + self.Sp(rho_m,target=obj_n))),\ - where=obj_n) - - # print something to help with debugging - #print(sym.pretty(output)) + output[3*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) # return output equations return output @@ -455,12 +449,13 @@ class DPIEOperator: h = np.zeros((self.nobjs,), dtype=self.stype) f = np.zeros((2*self.nobjs,), dtype=self.stype) for i in range(0,self.nobjs): - q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) + obj_n = self.geometry_list[n] + q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) h[i] = -divA_inc[i]/self.k - f[2*i:2*(i+1)] = xyz_to_tangential(-sym.n_cross(A_inc[3*i:3*(i+1)],where=self.geometry_list[i]),where=self.geometry_list[i]) + f[2*i:2*(i+1)] = xyz_to_tangential(-sym.n_cross(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) # define RHS for `A` integral equation system - return sym.join_fields( f, h, q) + return sym.join_fields( f, h, q ) def subproblem_operator(self, tau_densities, alpha = 1j): """ @@ -468,8 +463,7 @@ class DPIEOperator: """ # extract the densities from the sub problem solution - tau = tau_densities[:self.nobjs] - tau_m = tau.reshape((1,self.nobjs)) + (tau0, tau) = self._extract_tau_densities(tau_densities) # init output matvec vector for the phi density IE output = np.zeros((self.nobjs,), dtype=self.stype) @@ -481,7 +475,7 @@ class DPIEOperator: obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = 0.5*tau[n] + self.D(tau_m,obj_n) - alpha*self.S(tau_m,obj_n) + output[n] = 0.5*tau0[n] + self.D(tau,obj_n) - alpha*self.S(tau,obj_n) # return the resulting system of IE return output @@ -491,12 +485,7 @@ class DPIEOperator: Integral Equation RHS for obtaining sub problem solution """ # extract the densities needed to solve the system of equations - a_loc = A_densities[:(2*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # init output matvec vector for the phi density IE output = np.zeros((self.nobjs,), dtype=self.stype) @@ -508,7 +497,7 @@ class DPIEOperator: obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = sym.div(self.S(a,target=obj_n)) + output[n] = sym.div(self.S(a,target=obj_n,qfl=+1)) # return the resulting system of IE return output @@ -521,42 +510,35 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - sigma = phi_densities[:self.nobjs] - sigma_m = sigma.reshape((1,self.nobjs)) + (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) # evaluate scalar potential representation - return self.D(sigma_m,target,qfl=None) - 1j*self.k*self.S(sigma_m,target,qfl=None) + return self.D(sigma,target,qfl=None) - 1j*self.k*self.S(sigma,target,qfl=None) - def vector_potential_rep(self, A_densities, target=None): + def vector_potential_rep(self, A_densities, target=None, qfl=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` """ # extract the densities from the main IE solution - a_loc = A_densities[:(2*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define the vector potential representation - return sym.curl(self.S(a,target,qfl=None)) - self.k*self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None) \ - + 1j*(self.k*self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None) + sym.grad(3,self.S(rho_m,target,qfl=None))) + return sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl) \ + + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) - def subproblem_rep(self, tau_densities, target=None, alpha = 1j): + def subproblem_rep(self, tau_densities, target=None, alpha = 1j, qfl=None): """ This method is a representation of the scalar potential, phi, based on the density `sigma`. """ # extract the densities needed to solve the system of equations - tau = tau_densities[:self.nobjs] - tau_m = tau.reshape((1,self.nobjs)) + (tau0, tau) = self._extract_tau_densities(tau_densities) # evaluate scalar potential representation - return self.D(tau_m,target,qfl=None) - alpha*self.S(tau_m,target,qfl=None) + return self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl) def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j): """ @@ -569,32 +551,22 @@ class DPIEOperator: as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. """ - # extract the densities needed to solve the system of equations - sigma = phi_densities[:self.nobjs] - sigma_m = sigma.reshape((1,self.nobjs)) - - # extract the densities needed to solve the system of equations - a_loc = A_densities[:(2*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)] - rho_m = rho.reshape((1,self.nobjs)) - for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a_loc[2*n:2*(n+1)],where=self.geometry_list[n]) - - # extract the densities from the sub problem solution - tau = tau_densities[:self.nobjs] - tau_m = tau.reshape((1,self.nobjs)) + # extract the densities needed + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) + (sigma0,sigma, V) = self._extract_phi_densities(phi_densities) + (tau0, tau) = self._extract_tau_densities(tau_densities) # obtain expressions for scalar and vector potentials A = self.vector_potential_rep(A_densities, target=target) phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma_m,target,qfl=None)) + 1j*self.k*sym.grad(3, self.S(sigma_m,target,qfl=None)) - H_scat = sym.grad(ambient_dim=3,operand=(self.D(tau_m,target,qfl=None) - alpha*self.S(tau_m,target,qfl=None))) \ - + self.k*self.k*self.S(a,target,qfl=None) \ - - self.k*sym.curl(self.S(self.n_times_multi(rho_m,self.geometry_list),target,qfl=None)) \ - + 1j*self.k*sym.curl(self.S(self.n_cross_multi(a,self.geometry_list),target,qfl=None)) + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=None)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=None)) + H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=None) - alpha*self.S(tau,target,qfl=None))) \ + + (self.k**2) * self.S(a,target,qfl=None) \ + - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=None)) \ + + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=None)) + # join the fields into a vector return sym.join_fields(E_scat, H_scat) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index b0e37b22..2556fb03 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -510,7 +510,7 @@ class NodeMax(NodalOperation): def integral(ambient_dim, dim, operand, where=None): - ''' + """ Performs boundary integral on *operand*. `ambient_dim` is the number of dimensions used to represent space while `dim` is the dimensionality of the surface being integrated over. @@ -518,7 +518,7 @@ def integral(ambient_dim, dim, operand, where=None): Example| We wish to integrate over the 2-D surface of a sphere that resides in in 3-dimensions, so `ambient_dim` = 3 and `dim` = 2. - ''' + """ return NodeSum( area_element(ambient_dim, dim, where) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index d193d6fc..0be3cd19 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -191,7 +191,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) -tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1, 2], +tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], @@ -391,8 +391,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): from pytential.solve import gmres subprob_result = gmres( - test_sigma_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), - test_sigma_rhs, **gmres_settings) + test_tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), + test_tau_rhs, **gmres_settings) dummy_tau = subprob_result.solution sym_repr0 = dpie.scattered_volume_field(phi_densities,A_densities,tau_densities,target='tgt') -- GitLab From 1e7bf80d7ceb84f092f8fb3a0c3d86c3fec8a082 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Thu, 10 May 2018 12:07:56 -0500 Subject: [PATCH 36/59] Add updates to DPIE formulation and tests that cover operator and representation accuracy. --- pytential/symbolic/pde/maxwell/__init__.py | 4 +- pytential/symbolic/pde/maxwell/dpie.py | 188 +++++++++++-- test/test_maxwell.py | 61 ++-- test/test_maxwell_dpie.py | 312 ++++++++++++++++----- 4 files changed, 438 insertions(+), 127 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 178d3df6..78219b56 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -47,7 +47,7 @@ __doc__ = """ # {{{ point source -def get_sym_maxwell_point_source(kernel, jxyz, k): +def get_sym_maxwell_point_source_em(kernel, jxyz, k): r"""Return a symbolic expression that, when bound to a :class:`pytential.source.PointPotentialSource` will yield a field satisfying Maxwell's equations. @@ -130,7 +130,7 @@ def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): scalar potential and the last three being the components of the vector potential. """ - field = get_sym_maxwell_point_source(kernel, jxyz, k) + field = get_sym_maxwell_point_source_em(kernel, jxyz, k) return sym.join_fields( 0*1j, # scalar potential field[:3]/(1j*k) # vector potential diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 1ae305aa..81c0a295 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -76,6 +76,9 @@ class DPIEOperator: def num_vector_potential_densities(self): return 4*len(self.geometry_list) + def num_vector_potential_densities2(self): + return 5*len(self.geometry_list) + def num_scalar_potential_densities(self): return 2*len(self.geometry_list) @@ -105,6 +108,33 @@ class DPIEOperator: # return the domain list return domain_list + def get_vector_domain_list2(self): + """ + Method to return domain list that will be used within the scipy_op method to + solve the system of discretized integral equations. What is returned should just + be a list with values that are strings or None. + """ + + # initialize domain list + domain_list = [None]*self.num_vector_potential_densities() + + # get strings for the actual densities + for n in range(0,self.nobjs): + + # grab nth location identifier + location = self.geometry_list[n] + "t" + + # assign domain for nth vector density + domain_list[3*n] = location + domain_list[3*n+1] = location + domain_list[3*n+3] = location + + # assign domain for nth scalar density + domain_list[3*self.nobjs + n] = location + + # return the domain list + return domain_list + def get_scalar_domain_list(self): """ Method to return domain list that will be used within the scipy_op method to @@ -329,31 +359,41 @@ class DPIEOperator: a[:,n] = sym.tangential_to_xyz(a0[2*n:2*(n+1)],where=self.geometry_list[n]) return (a0, a, rho0, rho, v) + def _extract_a_densities2(self,A_densities): + a0 = A_densities[:(3*self.nobjs)] + a = a0.reshape((3,self.nobjs)) + rho0 = A_densities[(3*self.nobjs):(4*self.nobjs)] + rho = rho0.reshape((1,self.nobjs)) + v = A_densities[(4*self.nobjs):] + return (a0, a, rho0, rho, v) + def _L(self, a, rho, where): return sym.join_fields( - sym.n_cross(self.S(a,where),where=where) - self.k * sym.n_cross(self.S(self.n_times(rho),where),where=where), + sym.n_cross(self.S(a,where) - self.k * self.S(self.n_times(rho),where),where=where), self.D(rho,where)) - def _R(self, a, rho): + def _R(self, a, rho, where): return sym.join_fields( - self.k* sym.n_cross(self.S(self.n_cross(a),where),where=where) + sym.n_cross(sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), - sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,target=where) + sym.n_cross( self.k * self.S(self.n_cross(a),where) + sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), + sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,where) ) def _scaledDPIEs_integral(self, sigma, sigma_n, where): + qfl="avg" return sym.integral( ambient_dim=3, dim=2, - operand=(self.Dp(sigma,target=where)/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where)), - where=obj_n) + operand=(self.Dp(sigma,target=where,qfl=qfl)/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where,qfl=qfl)), + where=where) def _scaledDPIEv_integral(self, a, rho, rho_n, where): + qfl="avg" return sym.integral( ambient_dim=3, dim=2, operand=( - sym.n_dot(sym.curl(self.S(a,target=where)),where=where) - self.k*sym.n_dot(self.S(self.n_times(rho),target=where),where=where) \ - + 1j*(self.k*sym.n_dot(self.S(self.n_cross(a),target=where),where=where) - 0.5*rho_n + self.Sp(rho,target=where)) + sym.n_dot(sym.curl(self.S(a,target=where,qfl=qfl)),where=where) - self.k*sym.n_dot(self.S(self.n_times(rho),target=where,qfl=qfl),where=where) \ + + 1j*(self.k*sym.n_dot(self.S(self.n_cross(a),target=where,qfl=qfl),where=where) - 0.5*rho_n + self.Sp(rho,target=where,qfl=qfl)) ), where=where) @@ -431,7 +471,7 @@ class DPIEOperator: # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] + v[n] + output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] # add the equation that integrates everything out into some constant output[3*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) @@ -439,6 +479,41 @@ class DPIEOperator: # return output equations return output + def a_operator2(self, A_densities): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # extract the densities needed to solve the system of equations + (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) + + # init output matvec vector for the phi density IE + output = np.zeros((5*self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): + + # get the nth target geometry to have IE solved across + obj_n = self.geometry_list[n] + + # Compute two IE Operators on a and rho densities + L = self._L(a, rho, obj_n) + R = self._R(a, rho, obj_n) + + # generate the set of equations for the vector densities, a, coupled + # across the various geometries involved + output[3*n:3*(n+1)] = 0.5*a[:,n] + L[:3] + 1j*R[:3] + + # generate the set of equations for the scalar densities, rho, coupled + # across the various geometries involved + output[(3*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] + + # add the equation that integrates everything out into some constant + output[4*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) + + # return output equations + return output + def a_rhs(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` @@ -449,13 +524,13 @@ class DPIEOperator: h = np.zeros((self.nobjs,), dtype=self.stype) f = np.zeros((2*self.nobjs,), dtype=self.stype) for i in range(0,self.nobjs): - obj_n = self.geometry_list[n] + obj_n = self.geometry_list[i] q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) - h[i] = -divA_inc[i]/self.k + h[i] = -divA_inc[i] f[2*i:2*(i+1)] = xyz_to_tangential(-sym.n_cross(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) # define RHS for `A` integral equation system - return sym.join_fields( f, h, q ) + return sym.join_fields( f, h/self.k, q ) def subproblem_operator(self, tau_densities, alpha = 1j): """ @@ -497,13 +572,45 @@ class DPIEOperator: obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = sym.div(self.S(a,target=obj_n,qfl=+1)) + output[n] = sym.div(self.S(a,target=obj_n,qfl="avg")) # return the resulting system of IE return output + def subproblem_rhs2(self, function): + """ + Integral Equation RHS for obtaining sub problem solution + """ + + # init output matvec vector for the phi density IE + output = np.zeros((self.nobjs,), dtype=self.stype) + + # produce integral equation system over each disjoint object + for n in range(0,self.nobjs): - def scalar_potential_rep(self, phi_densities, target=None): + # get nth disjoint object + obj_n = self.geometry_list[n] + + # setup IE for evaluation over the nth disjoint object's surface + output[n] = function(where=obj_n) + + # return the resulting system of IE + return output + + + def scalar_potential_rep(self, phi_densities, target=None, qfl=None): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + + # extract the densities needed to solve the system of equations + (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + + # evaluate scalar potential representation + return self.D(sigma,target,qfl=qfl) - (1j*self.k)*self.S(sigma,target,qfl=qfl) + + def grad_scalar_potential_rep(self, phi_densities, target=None, qfl=None): """ This method is a representation of the scalar potential, phi, based on the density `sigma`. @@ -513,7 +620,7 @@ class DPIEOperator: (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) # evaluate scalar potential representation - return self.D(sigma,target,qfl=None) - 1j*self.k*self.S(sigma,target,qfl=None) + return sym.grad(3,self.D(sigma,target,qfl=qfl)) - (1j*self.k)*sym.grad(3,self.S(sigma,target,qfl=qfl)) def vector_potential_rep(self, A_densities, target=None, qfl=None): """ @@ -528,6 +635,45 @@ class DPIEOperator: return sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl) \ + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) + def div_vector_potential_rep(self, A_densities, target=None, qfl=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # extract the densities from the main IE solution + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) + + # define the vector potential representation + return self.k*(self.D(self.n_times(rho),target,qfl=qfl) \ + + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) + + def vector_potential_rep2(self, A_densities, target=None, qfl=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # extract the densities from the main IE solution + (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) + + # define the vector potential representation + return sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl) \ + + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) + + def div_vector_potential_rep2(self, A_densities, target=None, qfl=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # extract the densities from the main IE solution + (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) + + # define the vector potential representation + return self.k*(self.D(self.n_times(rho),target,qfl=qfl) \ + + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) + def subproblem_rep(self, tau_densities, target=None, alpha = 1j, qfl=None): """ This method is a representation of the scalar potential, phi, @@ -540,7 +686,7 @@ class DPIEOperator: # evaluate scalar potential representation return self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl) - def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j): + def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j,qfl=None): """ This will return an object of six entries, the first three of which represent the electric, and the second three of which represent the @@ -561,11 +707,11 @@ class DPIEOperator: phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=None)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=None)) - H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=None) - alpha*self.S(tau,target,qfl=None))) \ - + (self.k**2) * self.S(a,target,qfl=None) \ - - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=None)) \ - + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=None)) + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=qfl)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=qfl)) + H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl))) \ + + (self.k**2) * self.S(a,target,qfl=qfl) \ + - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=qfl)) \ + + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=qfl)) # join the fields into a vector diff --git a/test/test_maxwell.py b/test/test_maxwell.py index 715a7669..be7d33ff 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -241,7 +241,7 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): from pytential.symbolic.pde.maxwell import ( PECChargeCurrentMFIEOperator, - get_sym_maxwell_point_source, + get_sym_maxwell_point_source_em, get_sym_maxwell_plane_wave) mfie = PECChargeCurrentMFIEOperator() @@ -254,7 +254,7 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) def eval_inc_field_at(tgt): - if 0: + if 1: # plane wave return bind( tgt, @@ -380,24 +380,24 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): # {{{ check PEC BC on total field - bc_repr = EHField(mfie.scattered_volume_field( - jt_sym, rho_sym, qbx_forced_limit=loc_sign)) - pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) - pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) + # bc_repr = EHField(mfie.scattered_volume_field( + # jt_sym, rho_sym, qbx_forced_limit=loc_sign)) + # pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) + # pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) - eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( - queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, - **knl_kwargs) + # eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( + # queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, + # **knl_kwargs) - def scat_norm(f): - return norm(qbx, queue, f, p=np.inf) + # def scat_norm(f): + # return norm(qbx, queue, f, p=np.inf) - e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) - h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) + # e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) + # h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) - print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) + # print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) - eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) + # eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) # }}} @@ -458,22 +458,22 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): # {{{ error in E, H - obs_repr = EHField(eval_repr_at(obs_discr)) + # obs_repr = EHField(eval_repr_at(obs_discr)) - def obs_norm(f): - return norm(obs_discr, queue, f, p=np.inf) + # def obs_norm(f): + # return norm(obs_discr, queue, f, p=np.inf) - rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) - / obs_norm(inc_field_obs.e)) - rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) - / obs_norm(inc_field_obs.h)) + # rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) + # / obs_norm(inc_field_obs.e)) + # rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) + # / obs_norm(inc_field_obs.h)) - # }}} + # # }}} - print("ERR", h_max, rel_err_h, rel_err_e) + # print("ERR", h_max, rel_err_h, rel_err_e) - eoc_rec_h.add_data_point(h_max, rel_err_h) - eoc_rec_e.add_data_point(h_max, rel_err_e) + # eoc_rec_h.add_data_point(h_max, rel_err_h) + # eoc_rec_e.add_data_point(h_max, rel_err_e) print("--------------------------------------------------------") print("is_interior=%s" % case.is_interior) @@ -481,10 +481,11 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): good = True for which_eoc, eoc_rec, order_tol in [ - ("maxwell", eoc_rec_repr_maxwell, 1.5), - ("PEC BC", eoc_pec_bc, 1.5), - ("H", eoc_rec_h, 1.5), - ("E", eoc_rec_e, 1.5)]: + ("maxwell", eoc_rec_repr_maxwell, 1.5) + #("PEC BC", eoc_pec_bc, 1.5), + #("H", eoc_rec_h, 1.5), + #("E", eoc_rec_e, 1.5) + ]: print(which_eoc) print(eoc_rec.pretty_print()) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 0be3cd19..ee5b4142 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -191,6 +191,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) + tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) @@ -248,14 +249,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ come up with a solution to Maxwell's equations - # define symbolic variable for current density - # for use in defining incident field - j_sym = sym.make_sym_vector("j", 3) - # import some functionality from maxwell into this # local scope environment - #from pytential.symbolic.pde.maxwell import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) - #from pytential.symbolic.pde.maxwell.dpie import (get_sym_maxwell_point_source,get_sym_maxwell_point_source_potentials,get_sym_maxwell_plane_wave,DPIEOperator) import pytential.symbolic.pde.maxwell as mw import pytential.symbolic.pde.maxwell.dpie as mw_dpie @@ -283,7 +278,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): u_dir = np.array([1, 0, 0],dtype=np.complex128) # polarization vector - Ep = np.array([1, 1, 1],dtype=np.complex128) + Ep = np.array([0, 1, 1],dtype=np.complex128) # define symbolic vectors for use uvar = sym.make_sym_vector("u", 3) @@ -292,11 +287,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define functions that can be used to generate incident fields for an input discretization # define potentials based on incident plane wave def get_incident_plane_wave_EHField(tgt): - return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Ep, v=u_dir, omega=dpie.k))(queue,k=case.k,u=u_dir,Ep=Ep) + return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, omega=dpie.k))(queue,k=case.k,u=u_dir,Ep=Ep) # get the gradphi_inc field evaluated at some source locations def get_incident_gradphi(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=u_dir, Ep=Ep, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) + return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) # get the incident plane wave div(A) def get_incident_divA(objects, target=None): @@ -317,9 +312,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # compute residuals of incident field at source points source_maxwell_resids = [ - calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_inc.e, np.inf) - for x in frequency_domain_maxwell( - calc_patch, pde_test_inc.e, pde_test_inc.h, case.k)] + calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_inc.e, np.inf) + for x in frequency_domain_maxwell( + calc_patch, pde_test_inc.e, pde_test_inc.h, case.k)] # make sure Maxwell residuals are small so we know the incident field # properly satisfies the maxwell equations @@ -329,8 +324,108 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # }}} + # {{{ Test the auxiliary problem is capable of computing the desired derivatives of an appropriate input field + test_auxiliary = False + + if test_auxiliary: + # import a bunch of stuff that will be useful + from pytools.convergence import EOCRecorder + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + + # define method to get locations to evaluate representation + def epsilon_off_boundary(where=None, epsilon=1e-4): + x = sym.nodes(3, where).as_vector() + return x + sym.normal(3,2,where).as_vector()*epsilon + + # # loop through the case's resolutions and compute the scattered field solution + deriv_error = [] + for resolution in case.resolutions: + + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # use OpenCL random number generator to create a set of random + # source locations for various variables being solved for + dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) + qbx0, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + #fmm_order=False, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} + + # define points to evaluate the gradient at + tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1.0))(queue)) + geom_map['tgt'] = tgt_n + + # define the quantity that will have a derivative taken of it and its associated derivative + def getTestFunction(where=None): + z = sym.nodes(3, where).as_vector() + z2 = sym.cse(np.dot(z, z), "z_mag_squared") + g = sym.exp(1j*dpie0.k*sym.sqrt(z2))/(4.0*np.pi*sym.sqrt(z2)) + return g + + def getTestGradient(where=None): + z = sym.nodes(3, where).as_vector() + z2 = sym.cse(np.dot(z, z), "z_mag_squared") + grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - 1.0/sym.sqrt(z2))/(4*np.pi*z2) + return grad_g + + # compute output gradient evaluated at the desired object + tgrad = bind(geom_map,getTestGradient(where="tgt"))(queue,**knl_kwargs) + test_func_d = vector_from_device(queue,tgrad) + + # define the problem that will be solved + test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) + test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs2(function=getTestFunction))(queue,**knl_kwargs) + + # set GMRES settings for solving + gmres_settings = dict( + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + + # get the GMRES functionality + from pytential.solve import gmres + + subprob_result = gmres( + test_tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), + test_tau_rhs, **gmres_settings) + dummy_tau = subprob_result.solution + + # compute the error between the associated derivative quantities + tgrad = bind(geom_map,sym.grad(3,dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt')))(queue,tau_densities=dummy_tau,**knl_kwargs) + approx_d = vector_from_device(queue,tgrad) + err = calc_patch.norm(test_func_d - approx_d, np.inf) + + # append error to the error list + deriv_error.append(err) + + print("Auxiliary Error Results:") + for n in range(0,len(deriv_error)): + print("Case {0}: {1}".format(n+1,deriv_error[n])) + + # }}} + + # # {{{ Test the representations - test_representations = True + test_representations = False if test_representations: @@ -372,10 +467,12 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) - s = rng.normal(queue, (), dtype=np.float64) - for i in range(0,len(dummy_phi)): + s = 0*rng.normal(queue, (), dtype=np.float64) + n1 = len(dummy_phi) + n2 = len(dummy_A) + for i in range(0,n1): dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) - for i in range(0,len(dummy_A)): + for i in range(0,n2): dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs(A_densities=A_densities))(queue,A_densities=dummy_A,**knl_kwargs) @@ -418,7 +515,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # # {{{ Test the operators - test_operators = False + test_operators = True if test_operators: # define error array @@ -427,7 +524,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define method to get locations to evaluate representation def epsilon_off_boundary(where=None, epsilon=1e-4): x = sym.nodes(3, where).as_vector() - return x + sym.normal(3,3,where).as_vector()*epsilon + return x + sym.normal(3,2,where).as_vector()*epsilon # import a bunch of stuff that will be useful from pytools.convergence import EOCRecorder @@ -464,43 +561,70 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define the geometry dictionary geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} + # compute off-boundary locations that the representation will need to be evaluated at + tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1e-8))(queue)) + geom_map['tgt'] = tgt_n + + # redefine a_densities + #A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities2()) + # init random dummy densities for the vector and scalar potentials dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) dummy_tau = np.array([None]*dpie0.num_distinct_objects(),dtype=dpie0.stype) - v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) - s = rng.normal(queue, (1,), dtype=np.float64)[0] - for i in range(0,len(dummy_phi)-1): - dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) - dummy_phi[1] = s - for i in range(0,len(dummy_A)-1): - dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) - dummy_A[3] = s - for i in range(0,len(dummy_tau)): + # Compute zero scalar for use in extra constants that are usually solved for in operators + n1 = len(dummy_phi) + n2 = len(dummy_A) + n3 = len(dummy_tau) + + for i in range(0,n1): + if i < (n1-1): + dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + else: + dummy_phi[i] = 0.0 + for i in range(0,n2): + if i < (n2-1): + dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + else: + dummy_A[i] = 0.0 + for i in range(0,n3): dummy_tau[i] = bind(geom_map,dummy_density(where='obj0'))(queue) # check that the scalar density operator and representation are similar - scalar_op = dpie0.phi_operator(phi_densities=phi_densities) - vector_op = dpie0.a_operator(A_densities=A_densities) + def vector_op_transform(vec_op_out): + a = sym.tangential_to_xyz(vec_op_out[:2], where='obj0') + return sym.join_fields(a,vec_op_out[2:]) + + scalar_op = dpie0.phi_operator(phi_densities=phi_densities)[:-1] + vector_op = vector_op_transform(dpie0.a_operator(A_densities=A_densities)[:-1]) + #vector_op = dpie0.a_operator2(A_densities=A_densities)[:-1] tau_op = dpie0.subproblem_operator(tau_densities=tau_densities) # evaluate operators at the dummy densities - scalar_op_eval = bind(geom_map, scalar_op)(queue, phi_densities=dummy_phi, **knl_kwargs) - vector_op_eval = bind(geom_map, vector_op)(queue, A_densities=dummy_A, **knl_kwargs) - tau_op_eval = bind(geom_map, tau_op)(queue, tau_densities=dummy_tau, **knl_kwargs) + scalar_op_eval = vector_from_device(queue,bind(geom_map, scalar_op)(queue, phi_densities=dummy_phi, **knl_kwargs)) + vector_op_eval = vector_from_device(queue,bind(geom_map, vector_op)(queue, A_densities=dummy_A, **knl_kwargs)) + tau_op_eval = vector_from_device(queue,bind(geom_map, tau_op)(queue, tau_densities=dummy_tau, **knl_kwargs)) - # compute off-boundary locations that the representation will need to be evaluated at - tgt_n = bind(geom_map,epsilon_off_boundary(where='obj0')) - geom_map['tgt'] = tgt_n - scalar_rep_eval = bind(geom_map, dpie0.scalar_potential_rep(phi_densities=phi_densities, target='tgt'))(queue, phi_densities=dummy_phi, **knl_kwargs) - vector_rep_eval = bind(geom_map, dpie0.vector_potential_rep(A_densities=A_densities, target='tgt'))(queue, A_densities=dummy_A, **knl_kwargs) - tau_op_eval = bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs) + # define the vector operator equivalent representations + def vec_op_repr(A_densities, target): + return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep(A_densities=A_densities, target=target),where='obj0'), + dpie0.div_vector_potential_rep(A_densities=A_densities, target=target)/dpie0.k) + + scalar_rep_eval = vector_from_device(queue,bind(geom_map, dpie0.scalar_potential_rep(phi_densities=phi_densities, target='tgt'))(queue, phi_densities=dummy_phi, **knl_kwargs)) + vector_rep_eval = vector_from_device(queue,bind(geom_map, vec_op_repr(A_densities=A_densities,target='tgt'))(queue, A_densities=dummy_A, **knl_kwargs)) + tau_rep_eval = vector_from_device(queue,bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs)) # compute the error between the operator values and the representation values def error_diff(u,v): - return norm(tgt_n, queue, u-v, p=np.inf) - op_error.append([error_diff(scalar_op_eval,scalar_rep_eval), error_diff(vector_op_eval,vector_rep_eval), error_diff(sigma_op_eval,sigma_op_eval)]) + return np.linalg.norm(u-v,np.inf) + error_v = [error_diff(scalar_op_eval[0],scalar_rep_eval), + error_diff(vector_op_eval[0],vector_rep_eval[0]), + error_diff(vector_op_eval[1],vector_rep_eval[1]), + error_diff(vector_op_eval[2],vector_rep_eval[2]), + error_diff(vector_op_eval[3],vector_rep_eval[3]), + error_diff(tau_op_eval[0],tau_rep_eval)] + op_error.append(error_v) # print the resulting error results print("Operator Error Results:") @@ -529,7 +653,26 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): eoc_rec_e = EOCRecorder() eoc_rec_h = EOCRecorder() + def frequency_domain_gauge_condition(cpatch, A, phi, k): + # define constants used for the computation + mu = 1 + epsilon = 1 + c = 1/np.sqrt(mu*epsilon) + omega = k*c + + # compute the gauge condition residual + # assumed time dependence exp(-1j*omega*t) + resid_gauge_cond = cpatch.div(A) - 1j*omega*mu*epsilon*phi + + # return the residual for the gauge condition + return resid_gauge_cond + + def gauge_check(divA,phi,k): + return divA - 1j*k*phi + # loop through the case's resolutions and compute the scattered field solution + gauge_err = [] + maxwell_err = [] for resolution in case.resolutions: # get the scattered and observation mesh @@ -579,6 +722,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): inc_divA = sym.make_sym_vector("inc_divA",1) inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) + resid = bind(geom_map,gauge_check(inc_divA, inc_phi, dpie.k))(queue,inc_divA=inc_divA_scat,inc_phi=phi_inc,**knl_kwargs) + # setup operators that will be solved phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) A_op = bind(geom_map,dpie.a_operator(A_densities=A_densities)) @@ -601,25 +746,35 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): gmres_result = gmres( phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(),**knl_kwargs), phi_rhs, **gmres_settings) - phi_dens = gmres_result.solution + phi_dens = gmres_result.solution # solve for the vector potential densities gmres_result = gmres( A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), A_rhs, **gmres_settings) - A_dens = gmres_result.solution + A_dens = gmres_result.solution # solve sub problem for sigma densities tau_op= bind(geom_map,dpie.subproblem_operator(tau_densities=tau_densities)) tau_rhs= bind(geom_map,dpie.subproblem_rhs(A_densities=A_densities))(queue,A_densities=A_dens,**knl_kwargs) gmres_result = gmres( - sigma_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie.get_subproblem_domain_list(), **knl_kwargs), - sigma_rhs, **gmres_settings) - tau_dens = gmres_result.solution + tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie.get_subproblem_domain_list(), **knl_kwargs), + tau_rhs, **gmres_settings) + tau_dens = gmres_result.solution # extract useful solutions - #phi = bind(geom_map, dpie.scalar_potential_rep(phi_densities=phi_densities))(queue, phi_densities=phi_dens) - #Axyz = bind(geom_map, dpie.vector_potential_rep(A_densities=A_densities))(queue, A_densities=A_dens) + def eval_potentials(tgt): + tmap = geom_map + tmap['tgt'] = tgt + phi = vector_from_device(queue,bind(tmap, dpie.scalar_potential_rep(phi_densities=phi_densities,target='tgt'))(queue, phi_densities=phi_dens, **knl_kwargs)) + Axyz = vector_from_device(queue,bind(tmap, dpie.vector_potential_rep(A_densities=A_densities,target='tgt'))(queue, A_densities=A_dens, **knl_kwargs)) + return (phi,Axyz) + + (phi,A) = eval_potentials(calc_patch_tgt) + gauge_residual = frequency_domain_gauge_condition(calc_patch, A, phi, case.k) + err = calc_patch.norm(gauge_residual,np.inf) + gauge_err.append(err) + # }}} @@ -639,30 +794,36 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] print("Maxwell residuals:", maxwell_residuals) + maxwell_err.append(maxwell_residuals) eoc_rec_repr_maxwell.add_data_point(h_max, max(maxwell_residuals)) # }}} - # {{{ check PEC BC on total field + # {{{ check potential PEC BC on total field + + def scalar_pot_PEC_residual(phi, gradPhi, where=None): + return sym.n_cross( + dpie.grad_scalar_potential_rep(phi_densities=phi,target=where,qfl="avg") + gradPhi, + where=where) + + def vector_pot_PEC_residual(a_densities, inc_a, where=None): + return sym.n_cross( dpie.vector_potential_rep(A_densities=a_densities, target=where, qfl="avg") + inc_a, where=where) - #bc_repr = EHField(dpie.scattered_volume_field( - # phi_densities, A_densities, qbx_forced_limit=loc_sign)) - #pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) - #pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + sym.curl(inc_xyz_vec_sym[1:])) + phi_pec_bc_resid = scalar_pot_PEC_residual(phi_densities, inc_gradPhi, where="obj0") + A_pec_bc_resid = vector_pot_PEC_residual(A_densities, inc_A, where="obj0") - #eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( - # queue, phi_densities=phi_dens, A_densities=A_dens, inc_vec_fld=inc_field_vec_scat, - # **knl_kwargs) + scalar_bc_values = bind(geom_map, phi_pec_bc_resid)(queue, phi_densities=phi_dens, inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) + vector_bc_values = bind(geom_map, A_pec_bc_resid)(queue, A_densities=A_dens, inc_A=A_inc,**knl_kwargs) - #def scat_norm(f): - # return norm(qbx, queue, f, p=np.inf) + def scat_norm(f): + return norm(qbx, queue, f, p=np.inf) - #e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_EM_field_scat.e) - #h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_EM_field_scat.h) + scalar_bc_residual = scat_norm(scalar_bc_values) # / scat_norm(inc_gradPhi_scat) + vector_bc_residual = scat_norm(vector_bc_values) # / scat_norm(A_inc) - #print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) + print("Potential PEC BC residuals:", h_max, scalar_bc_residual, vector_bc_residual) - #eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) + eoc_pec_bc.add_data_point(h_max, max(scalar_bc_residual, vector_bc_residual)) # }}} @@ -723,34 +884,37 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ error in E, H - obs_repr = EHField(eval_repr_at(obs_discr)) + # obs_repr = EHField(eval_repr_at(obs_discr)) - def obs_norm(f): - return norm(obs_discr, queue, f, p=np.inf) + # def obs_norm(f): + # return norm(obs_discr, queue, f, p=np.inf) - #inc_field_scat = EHField(get_incident_plane_wave_EHField(scat_discr)) - #inc_field_obs = EHField(get_incident_plane_wave_EHField(obs_discr)) + # inc_field_scat = EHField(get_incident_plane_wave_EHField(scat_discr)) + # inc_field_obs = EHField(get_incident_plane_wave_EHField(obs_discr)) - #rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) + # rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) # / obs_norm(inc_field_obs.e)) - #rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) + # rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) # / obs_norm(inc_field_obs.h)) - # }}} + # # }}} - #print("ERR", h_max, rel_err_h, rel_err_e) + # print("ERR", h_max, rel_err_h, rel_err_e) - #eoc_rec_h.add_data_point(h_max, rel_err_h) - #eoc_rec_e.add_data_point(h_max, rel_err_e) + # eoc_rec_h.add_data_point(h_max, rel_err_h) + # eoc_rec_e.add_data_point(h_max, rel_err_e) print("--------------------------------------------------------") print("is_interior=%s" % case.is_interior) print("--------------------------------------------------------") + print("Gauge Error: {0}".format(gauge_err)) + print("Maxwell Residuals: {0}".format(maxwell_err)) + good = True for which_eoc, eoc_rec, order_tol in [ - ("maxwell", eoc_rec_repr_maxwell, 1.5) - #("PEC BC", eoc_pec_bc, 1.5), + ("maxwell", eoc_rec_repr_maxwell, 1.5), + ("PEC BC", eoc_pec_bc, 1.5) #("H", eoc_rec_h, 1.5), #("E", eoc_rec_e, 1.5) ]: -- GitLab From 9d7f912f5dc5a67f82325c30bf728fc0cb4e3f3d Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 16 May 2018 15:38:57 -0500 Subject: [PATCH 37/59] Commit changes to DPIE and associated test that is investigating why operators are not matching up with their representations. Still unsure but at least the code is complete. --- pytential/symbolic/pde/maxwell/dpie.py | 115 +++++++++++++++++++++++++ test/test_maxwell_dpie.py | 4 + 2 files changed, 119 insertions(+) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 81c0a295..17f8c469 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -514,6 +514,67 @@ class DPIEOperator: # return output equations return output + def a_operator0(self, A_densities): + """ + Integral Equation operator for obtaining vector potential, `A` + """ + + # get object this will be working on + obj = self.geometry_list[0] + + # extract the densities needed to solve the system of equations + a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) + a_n = a.reshape((3,1)) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] + #rho_n = rho.reshape((1,1)) + v = A_densities[(3*self.nobjs):] + + # init output matvec vector for the phi density IE + output = np.zeros((4,), dtype=self.stype) + + # Compute useful sub-density expressions + n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) + n_cross_a = sym.n_cross(a,where=obj) + + # generate the set of equations for the vector densities, a, coupled + # across the various geometries involved + # a_lhs = 0.5*a \ + # + sym.n_cross(self.S(a_n,obj) \ + # - self.k*self.S(n_times_rho,obj) \ + # + 1j*( + # self.k*self.S(n_cross_a,obj) + sym.grad(3,self.S(rho_n,obj)) + # ), + # where=obj) + a_lhs = 0.5*a \ + + sym.n_cross(sym.S(self.kernel, a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ + - self.k*sym.S(self.kernel, n_times_rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ + + 1j*( + self.k*sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ + + sym.grad(3,sym.S(self.kernel, rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) + ), + where=obj) + output[:2] = xyz_to_tangential(a_lhs, where=obj) + + # generate the set of equations for the scalar densities, rho, coupled + # across the various geometries involved + # output[2] = 0.5*rho[0] \ + # + self.D(rho_n,obj) \ + # + 1j*(sym.div(self.S(n_cross_a,obj)) \ + # - self.k*self.S(rho_n,obj)) \ + # - v[0] + + output[2] = 0.5*rho \ + + sym.D(self.kernel, rho,k=self.k, qbx_forced_limit="avg",source=obj,target=obj) \ + + 1j*(sym.div(sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) \ + - self.k*sym.S(self.kernel, rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) \ + - v[0] + + # add the equation that integrates everything out into some constant + output[3] = 0 + + # return output equations + return output + def a_rhs(self, A_inc, divA_inc): """ The Right-Hand-Side for the Integral Equation for `A` @@ -648,6 +709,60 @@ class DPIEOperator: return self.k*(self.D(self.n_times(rho),target,qfl=qfl) \ + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) + def vector_potential_rep0(self, A_densities, target=None, qfl=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # get object this will be working on + obj = self.geometry_list[0] + + # extract the densities needed to solve the system of equations + a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) + a_n = a.reshape((3,1)) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] + #rho_n = rho.reshape((1,1)) + v = A_densities[(3*self.nobjs):] + + # Compute useful sub-density expressions + n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) + n_cross_a = sym.n_cross(a,where=obj) + + # define the vector potential representation + # return sym.curl(self.S(a_n,target,qfl=qfl)) - self.k*self.S(n_times_rho,target,qfl=qfl) \ + # + 1j*(self.k*self.S(n_cross_a,target,qfl=qfl) + sym.grad(3,self.S(rho_n,target,qfl=qfl))) + + return sym.curl(sym.S(self.kernel, a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target)) \ + - self.k*sym.S(self.kernel, n_times_rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ + + 1j*(self.k*sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ + + sym.grad(3,sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target))) + + def div_vector_potential_rep0(self, A_densities, target=None, qfl=None): + """ + This method is a representation of the vector potential, phi, + based on the vector density `a` and scalar density `rho` + """ + + # get object this will be working on + obj = self.geometry_list[0] + + # extract the densities needed to solve the system of equations + a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) + a_n = a.reshape((3,1)) + rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] + #rho_n = rho.reshape((1,1)) + v = A_densities[(3*self.nobjs):] + + # Compute useful sub-density expressions + n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) + n_cross_a = sym.n_cross(a,where=obj) + + # define the vector potential representation + return self.k*(sym.D(self.kernel, n_times_rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ + + 1j*(sym.div(sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target)) \ + - self.k * sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target))) + def vector_potential_rep2(self, A_densities, target=None, qfl=None): """ This method is a representation of the vector potential, phi, diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index ee5b4142..e2bc53ea 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -597,6 +597,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): return sym.join_fields(a,vec_op_out[2:]) scalar_op = dpie0.phi_operator(phi_densities=phi_densities)[:-1] + #vector_op = vector_op_transform(dpie0.a_operator0(A_densities=A_densities)[:-1]) vector_op = vector_op_transform(dpie0.a_operator(A_densities=A_densities)[:-1]) #vector_op = dpie0.a_operator2(A_densities=A_densities)[:-1] tau_op = dpie0.subproblem_operator(tau_densities=tau_densities) @@ -607,6 +608,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): tau_op_eval = vector_from_device(queue,bind(geom_map, tau_op)(queue, tau_densities=dummy_tau, **knl_kwargs)) # define the vector operator equivalent representations + #def vec_op_repr(A_densities, target): + # return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep0(A_densities=A_densities, target=target),where='obj0'), + # dpie0.div_vector_potential_rep0(A_densities=A_densities, target=target)/dpie0.k) def vec_op_repr(A_densities, target): return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep(A_densities=A_densities, target=target),where='obj0'), dpie0.div_vector_potential_rep(A_densities=A_densities, target=target)/dpie0.k) -- GitLab From 135f1e45587d22cc37b3cef486d1ad7865a02489 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 16 May 2018 20:40:43 -0500 Subject: [PATCH 38/59] Fixed bug in div(A) portion of the IE system of equations for the scaled DPIEv. Residuals looking better. --- pytential/symbolic/pde/maxwell/dpie.py | 6 +++--- test/test_maxwell_dpie.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 17f8c469..e930f060 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -375,7 +375,7 @@ class DPIEOperator: def _R(self, a, rho, where): return sym.join_fields( sym.n_cross( self.k * self.S(self.n_cross(a),where) + sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), - sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,where) + (sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,where)) ) def _scaledDPIEs_integral(self, sigma, sigma_n, where): @@ -471,7 +471,7 @@ class DPIEOperator: # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] + output[(2*self.nobjs + n)] = 0*(0.5*rho[0,n] + L[-1]) + 1j*R[-1] - v[n] # add the equation that integrates everything out into some constant output[3*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) @@ -706,7 +706,7 @@ class DPIEOperator: (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define the vector potential representation - return self.k*(self.D(self.n_times(rho),target,qfl=qfl) \ + return self.k*( self.D(rho,target,qfl=qfl) \ + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) def vector_potential_rep0(self, A_densities, target=None, qfl=None): diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index e2bc53ea..9a150e00 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -562,7 +562,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} # compute off-boundary locations that the representation will need to be evaluated at - tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1e-8))(queue)) + tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1e-4))(queue)) geom_map['tgt'] = tgt_n # redefine a_densities -- GitLab From 9070f9845476949dfce19fb820996560c572223a Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 27 Jun 2018 12:03:26 -0500 Subject: [PATCH 39/59] Finished writing up the journal paper version for the DPIE and validating that it converges when run against the test scattering problem. This DPIE inherits from the baseline DPIE model and overrides the necessary methods. --- pytential/symbolic/pde/maxwell/dpie.py | 503 ++++++++++++------------- test/test_maxwell_dpie.py | 120 ++++-- 2 files changed, 324 insertions(+), 299 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index e930f060..8b0b39dd 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -35,11 +35,12 @@ cse = sym.cse __doc__ = """ .. autoclass:: DPIEOperator +.. autoclass:: DPIEOperatorEvanescent """ -# {{{ Decoupled Potential Integral Equation Operator +# {{{ Decoupled Potential Integral Equation Operator - based on Arxiv paper class DPIEOperator: r""" Decoupled Potential Integral Equation operator with PEC boundary @@ -108,33 +109,6 @@ class DPIEOperator: # return the domain list return domain_list - def get_vector_domain_list2(self): - """ - Method to return domain list that will be used within the scipy_op method to - solve the system of discretized integral equations. What is returned should just - be a list with values that are strings or None. - """ - - # initialize domain list - domain_list = [None]*self.num_vector_potential_densities() - - # get strings for the actual densities - for n in range(0,self.nobjs): - - # grab nth location identifier - location = self.geometry_list[n] + "t" - - # assign domain for nth vector density - domain_list[3*n] = location - domain_list[3*n+1] = location - domain_list[3*n+3] = location - - # assign domain for nth scalar density - domain_list[3*self.nobjs + n] = location - - # return the domain list - return domain_list - def get_scalar_domain_list(self): """ Method to return domain list that will be used within the scipy_op method to @@ -180,10 +154,23 @@ class DPIEOperator: return domain_list - def D(self, density_vec, target=None, qfl="avg"): + def _layerpot_op(self,layerpot_op,density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): """ - Double layer potential operator across multiple disjoint objects + Generic layer potential operator method that works across all objects within the DPIE model """ + if kernel is None: + kernel = self.kernel + + if k is None: + k = self.k + + kargs = dict() + if not use_laplace: + kargs['k'] = k + + # define a convenient integral operator that functions across the multiple objects + def int_op(idx): + return layerpot_op(kernel, density_vec[:,idx],qbx_forced_limit=qfl,source=self.geometry_list[idx],target=target, **kargs) # get the shape of density_vec (ndim, nobj) = density_vec.shape @@ -194,9 +181,7 @@ class DPIEOperator: # compute individual double layer potential evaluations at the given # density across all the disjoint objects for i in range(0,nobj): - output = output + sym.D(self.kernel, density_vec[:,i], - k=self.k,qbx_forced_limit=qfl, - source=self.geometry_list[i],target=target) + output = output + int_op(i) # return the output summation if ndim == 1: @@ -204,78 +189,30 @@ class DPIEOperator: else: return output - def S(self, density_vec, target=None, qfl="avg"): + def D(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): """ Double layer potential operator across multiple disjoint objects """ + return self._layerpot_op(layerpot_op=sym.D, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) - # get the shape of density_vec - (ndim, nobj) = density_vec.shape - - # init output symbolic quantity with zeros - output = np.zeros((ndim,), dtype=self.stype) - - # compute individual double layer potential evaluations at the given - # density across all the disjoint objects - for i in range(0,nobj): - output = output + sym.S(self.kernel, density_vec[:,i], - k=self.k, qbx_forced_limit=qfl, - source=self.geometry_list[i], target=target) - - # return the output summation - if ndim == 1: - return output[0] - else: - return output + def S(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): + """ + Single layer potential operator across multiple disjoint objects + """ + return self._layerpot_op(layerpot_op=sym.S, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) - def Dp(self, density_vec, target=None, qfl="avg"): + def Dp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): """ D' layer potential operator across multiple disjoint objects """ + return self._layerpot_op(layerpot_op=sym.Dp, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) - # get the shape of density_vec - (ndim, nobj) = density_vec.shape - - # init output symbolic quantity with zeros - output = np.zeros((ndim,), dtype=self.stype) - - # compute individual double layer potential evaluations at the given - # density across all the disjoint objects - for i in range(0,nobj): - output = output + sym.Dp(self.kernel, density_vec[:,i], - k=self.k,qbx_forced_limit=qfl, - source=self.geometry_list[i],target=target) - - # return the output summation - if ndim == 1: - return output[0] - else: - return output - - def Sp(self, density_vec, target=None, qfl="avg"): + def Sp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): """ S' layer potential operator across multiple disjoint objects """ - - # get the shape of density_vec - (ndim, nobj) = density_vec.shape - - # init output symbolic quantity with zeros - output = np.zeros((ndim,), dtype=self.stype) - - # compute individual double layer potential evaluations at the given - # density across all the disjoint objects - for i in range(0,nobj): - output = output + sym.Sp(self.kernel, density_vec[:,i], - k=self.k, qbx_forced_limit=qfl, - source=self.geometry_list[i], target=target) - - # return the output summation - if ndim == 1: - return output[0] - else: - return output + return self._layerpot_op(layerpot_op=sym.Sp, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) def n_cross(self, density_vec): r""" @@ -356,44 +293,66 @@ class DPIEOperator: rho = rho0.reshape((1,self.nobjs)) v = A_densities[(3*self.nobjs):] for n in range(0,self.nobjs): - a[:,n] = sym.tangential_to_xyz(a0[2*n:2*(n+1)],where=self.geometry_list[n]) - return (a0, a, rho0, rho, v) - - def _extract_a_densities2(self,A_densities): - a0 = A_densities[:(3*self.nobjs)] - a = a0.reshape((3,self.nobjs)) - rho0 = A_densities[(3*self.nobjs):(4*self.nobjs)] - rho = rho0.reshape((1,self.nobjs)) - v = A_densities[(4*self.nobjs):] + a[:,n] = cse(sym.tangential_to_xyz(a0[2*n:2*(n+1)],where=self.geometry_list[n]),"axyz_{0}".format(n)) return (a0, a, rho0, rho, v) def _L(self, a, rho, where): + + # define some useful common sub expressions + Sa = cse(self.S(a,where),"Sa_"+where) + Srho = cse(self.S(rho,where),"Srho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) + Drho = cse(self.D(rho,where),"Drho_"+where) + return sym.join_fields( - sym.n_cross(self.S(a,where) - self.k * self.S(self.n_times(rho),where),where=where), - self.D(rho,where)) + sym.n_cross(sym.curl(self.S(a,where)) - self.k * Sn_times_rho,where=where), + Drho) def _R(self, a, rho, where): + # define some useful common sub expressions + Sa = cse(self.S(a,where),"Sa_"+where) + Srho = cse(self.S(rho,where),"Srho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) + Drho = cse(self.D(rho,where),"Drho_"+where) + return sym.join_fields( - sym.n_cross( self.k * self.S(self.n_cross(a),where) + sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), - (sym.div(self.S(self.n_cross(a),where)) - self.k * self.S(rho,where)) + sym.n_cross( self.k * Sn_cross_a + sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), + sym.div(self.S(self.n_cross(a),where)) - self.k * Srho ) def _scaledDPIEs_integral(self, sigma, sigma_n, where): qfl="avg" + return sym.integral( ambient_dim=3, dim=2, operand=(self.Dp(sigma,target=where,qfl=qfl)/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where,qfl=qfl)), where=where) - def _scaledDPIEv_integral(self, a, rho, rho_n, where): + def _scaledDPIEv_integral(self, **kwargs): qfl="avg" + + # grab densities and domain to integrate over + a = kwargs['a'] + rho = kwargs['rho'] + rho_n = kwargs['rho_n'] + where = kwargs['where'] + + # define some useful common sub expressions + Sa = cse(self.S(a,where),"Sa_"+where) + Srho = cse(self.S(rho,where),"Srho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) + Drho = cse(self.D(rho,where),"Drho_"+where) + return sym.integral( ambient_dim=3, dim=2, operand=( - sym.n_dot(sym.curl(self.S(a,target=where,qfl=qfl)),where=where) - self.k*sym.n_dot(self.S(self.n_times(rho),target=where,qfl=qfl),where=where) \ - + 1j*(self.k*sym.n_dot(self.S(self.n_cross(a),target=where,qfl=qfl),where=where) - 0.5*rho_n + self.Sp(rho,target=where,qfl=qfl)) + sym.n_dot( sym.curl(self.S(a,where)),where=where) - self.k*sym.n_dot(Sn_times_rho,where=where) \ + + 1j*(self.k*sym.n_dot(Sn_cross_a,where=where) - 0.5*rho_n + self.Sp(rho,target=where,qfl=qfl)) ), where=where) @@ -471,106 +430,10 @@ class DPIEOperator: # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(2*self.nobjs + n)] = 0*(0.5*rho[0,n] + L[-1]) + 1j*R[-1] - v[n] + output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] # add the equation that integrates everything out into some constant - output[3*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) - - # return output equations - return output - - def a_operator2(self, A_densities): - """ - Integral Equation operator for obtaining vector potential, `A` - """ - - # extract the densities needed to solve the system of equations - (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) - - # init output matvec vector for the phi density IE - output = np.zeros((5*self.nobjs,), dtype=self.stype) - - # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): - - # get the nth target geometry to have IE solved across - obj_n = self.geometry_list[n] - - # Compute two IE Operators on a and rho densities - L = self._L(a, rho, obj_n) - R = self._R(a, rho, obj_n) - - # generate the set of equations for the vector densities, a, coupled - # across the various geometries involved - output[3*n:3*(n+1)] = 0.5*a[:,n] + L[:3] + 1j*R[:3] - - # generate the set of equations for the scalar densities, rho, coupled - # across the various geometries involved - output[(3*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] - - # add the equation that integrates everything out into some constant - output[4*self.nobjs + n] = self._scaledDPIEv_integral(a, rho, rho[0,n], where=obj_n) - - # return output equations - return output - - def a_operator0(self, A_densities): - """ - Integral Equation operator for obtaining vector potential, `A` - """ - - # get object this will be working on - obj = self.geometry_list[0] - - # extract the densities needed to solve the system of equations - a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) - a_n = a.reshape((3,1)) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] - #rho_n = rho.reshape((1,1)) - v = A_densities[(3*self.nobjs):] - - # init output matvec vector for the phi density IE - output = np.zeros((4,), dtype=self.stype) - - # Compute useful sub-density expressions - n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) - n_cross_a = sym.n_cross(a,where=obj) - - # generate the set of equations for the vector densities, a, coupled - # across the various geometries involved - # a_lhs = 0.5*a \ - # + sym.n_cross(self.S(a_n,obj) \ - # - self.k*self.S(n_times_rho,obj) \ - # + 1j*( - # self.k*self.S(n_cross_a,obj) + sym.grad(3,self.S(rho_n,obj)) - # ), - # where=obj) - a_lhs = 0.5*a \ - + sym.n_cross(sym.S(self.kernel, a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ - - self.k*sym.S(self.kernel, n_times_rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ - + 1j*( - self.k*sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj) \ - + sym.grad(3,sym.S(self.kernel, rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) - ), - where=obj) - output[:2] = xyz_to_tangential(a_lhs, where=obj) - - # generate the set of equations for the scalar densities, rho, coupled - # across the various geometries involved - # output[2] = 0.5*rho[0] \ - # + self.D(rho_n,obj) \ - # + 1j*(sym.div(self.S(n_cross_a,obj)) \ - # - self.k*self.S(rho_n,obj)) \ - # - v[0] - - output[2] = 0.5*rho \ - + sym.D(self.kernel, rho,k=self.k, qbx_forced_limit="avg",source=obj,target=obj) \ - + 1j*(sym.div(sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) \ - - self.k*sym.S(self.kernel, rho, k=self.k, qbx_forced_limit="avg",source=obj, target=obj)) \ - - v[0] - - # add the equation that integrates everything out into some constant - output[3] = 0 + output[3*self.nobjs + n] = self._scaledDPIEv_integral(a=a, rho=rho, rho_n=rho[0,n], where=obj_n) # return output equations return output @@ -638,7 +501,7 @@ class DPIEOperator: # return the resulting system of IE return output - def subproblem_rhs2(self, function): + def subproblem_rhs_func(self, function): """ Integral Equation RHS for obtaining sub problem solution """ @@ -671,6 +534,18 @@ class DPIEOperator: # evaluate scalar potential representation return self.D(sigma,target,qfl=qfl) - (1j*self.k)*self.S(sigma,target,qfl=qfl) + def scalar_potential_constants(self, phi_densities): + """ + This method is a representation of the scalar potential, phi, + based on the density `sigma`. + """ + + # extract the densities needed to solve the system of equations + (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + + # evaluate scalar potential representation + return V + def grad_scalar_potential_rep(self, phi_densities, target=None, qfl=None): """ This method is a representation of the scalar potential, phi, @@ -693,7 +568,7 @@ class DPIEOperator: (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define the vector potential representation - return sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl) \ + return (sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl)) \ + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) def div_vector_potential_rep(self, A_densities, target=None, qfl=None): @@ -709,97 +584,187 @@ class DPIEOperator: return self.k*( self.D(rho,target,qfl=qfl) \ + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) - def vector_potential_rep0(self, A_densities, target=None, qfl=None): + def subproblem_rep(self, tau_densities, target=None, alpha = 1j, qfl=None): """ - This method is a representation of the vector potential, phi, - based on the vector density `a` and scalar density `rho` + This method is a representation of the scalar potential, phi, + based on the density `sigma`. """ - # get object this will be working on - obj = self.geometry_list[0] - # extract the densities needed to solve the system of equations - a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) - a_n = a.reshape((3,1)) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] - #rho_n = rho.reshape((1,1)) - v = A_densities[(3*self.nobjs):] + (tau0, tau) = self._extract_tau_densities(tau_densities) + + # evaluate scalar potential representation + return self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl) + + def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j,qfl=None): + """ + 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`. + """ + + # extract the densities needed + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) + (sigma0,sigma, V) = self._extract_phi_densities(phi_densities) + (tau0, tau) = self._extract_tau_densities(tau_densities) + + # obtain expressions for scalar and vector potentials + A = self.vector_potential_rep(A_densities, target=target) + phi = self.scalar_potential_rep(phi_densities, target=target) + + # evaluate the potential form for the electric and magnetic fields + E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=qfl)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=qfl)) + H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl))) \ + + (self.k**2) * self.S(a,target,qfl=qfl) \ + - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=qfl)) \ + + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=qfl)) + + + # join the fields into a vector + return sym.join_fields(E_scat, H_scat) + +# }}} - # Compute useful sub-density expressions - n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) - n_cross_a = sym.n_cross(a,where=obj) - # define the vector potential representation - # return sym.curl(self.S(a_n,target,qfl=qfl)) - self.k*self.S(n_times_rho,target,qfl=qfl) \ - # + 1j*(self.k*self.S(n_cross_a,target,qfl=qfl) + sym.grad(3,self.S(rho_n,target,qfl=qfl))) - return sym.curl(sym.S(self.kernel, a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target)) \ - - self.k*sym.S(self.kernel, n_times_rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ - + 1j*(self.k*sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ - + sym.grad(3,sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target))) +# {{{ Decoupled Potential Integral Equation Operator - Based on Journal Paper +class DPIEOperatorEvanescent(DPIEOperator): + r""" + Decoupled Potential Integral Equation operator with PEC boundary + conditions, defaults as scaled DPIE. + + See https://onlinelibrary.wiley.com/doi/abs/10.1002/cpa.21585 for journal paper. + + Uses :math:`E(x,t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and + :math:`H(x,t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for + the :math:`E(x)`, :math:`H(x)` fields using vector and scalar potentials via + the Lorenz Gauage. The DPIE formulates the problem purely in terms of the + vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, + and then backs out :math:`E(x)` and :math:`H(x)` via relationships to + the vector and scalar potentials. + """ + + def __init__(self, geometry_list, k=sym.var("k")): + from sumpy.kernel import HelmholtzKernel + from sumpy.kernel import LaplaceKernel - def div_vector_potential_rep0(self, A_densities, target=None, qfl=None): + # specify the frequency variable that will be tuned + self.k = k + self.ik = 1j*k + self.stype = type(self.k) + + # specify the 3-D Helmholtz kernel + self.kernel = HelmholtzKernel(3) + self.kernel_ik = HelmholtzKernel(3, allow_evanescent=True) + self.kernel_laplace = LaplaceKernel(3) + + # specify a list of strings representing geometry objects + self.geometry_list = geometry_list + self.nobjs = len(geometry_list) + + def _eval_all_objects(self, density_vec, int_op, qfl="avg", k=None, kernel=None): """ - This method is a representation of the vector potential, phi, - based on the vector density `a` and scalar density `rho` + This private method is so some input integral operator and input density can be used to + evaluate the set of locations defined by the geometry list """ + output = np.zeros(density_vec.shape, dtype=self.stype) + (ndim, nobj) = density_vec.shape + for i in range(0,nobj): + output[:,i] = int_op(density_vec=density_vec, target=self.geometry_list[i], qfl=qfl, k=k, kernel=kernel) + return output - # get object this will be working on - obj = self.geometry_list[0] + def _L(self, a, rho, where): - # extract the densities needed to solve the system of equations - a = sym.tangential_to_xyz(A_densities[:(2*self.nobjs)],where=obj) - a_n = a.reshape((3,1)) - rho = A_densities[(2*self.nobjs):(3*self.nobjs)][0] - #rho_n = rho.reshape((1,1)) - v = A_densities[(3*self.nobjs):] + # define some useful common sub expressions + Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + Drho = cse(self.D(rho,where),"Drho_"+where) + + return sym.join_fields( + sym.n_cross(sym.curl(self.S(a,where)) - self.k * Sn_times_rho,where=where), + Drho) - # Compute useful sub-density expressions - n_times_rho = (sym.normal(3,where=obj).as_vector() * rho) - n_cross_a = sym.n_cross(a,where=obj) + def _R(self, a, rho, where): + # define some useful common sub expressions + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") + Srho = cse(self.S(Srho_ik_nest,where),"Srho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest),where),"Sn_cross_a_"+where) + + return self.k*sym.join_fields( + sym.n_cross( self.k * Sn_cross_a + sym.grad(ambient_dim=3,operand=self.S(Srho_ik_nest,where)),where=where), + sym.div(self.S(self.n_cross(Sa_ik_nest),where)) - self.k * Srho + ) - # define the vector potential representation - return self.k*(sym.D(self.kernel, n_times_rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target) \ - + 1j*(sym.div(sym.S(self.kernel, n_cross_a, k=self.k, qbx_forced_limit=qfl, source=obj, target=target)) \ - - self.k * sym.S(self.kernel, rho, k=self.k, qbx_forced_limit=qfl, source=obj, target=target))) + def _scaledDPIEs_integral(self, sigma, sigma_n, where): + qfl="avg" + + return sym.integral( + ambient_dim=3, + dim=2, + operand=( (self.Dp(sigma,target=where,qfl=qfl) - self.Dp(sigma,target=where,qfl=qfl,kernel=self.kernel_laplace,use_laplace=True))/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where,qfl=qfl)), + where=where) - def vector_potential_rep2(self, A_densities, target=None, qfl=None): + def _scaledDPIEv_integral(self, **kwargs): + qfl="avg" + + # grab densities and domain to integrate over + a = kwargs['a'] + rho = kwargs['rho'] + rho_n = kwargs['rho_n'] + where = kwargs['where'] + + # define some useful common sub expressions + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik = cse(self.S(rho,where,k=self.ik,kernel=self.kernel_ik),"Srho_ik"+where) + Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest),where),"Sn_cross_a_nest_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + + return sym.integral( + ambient_dim=3, + dim=2, + operand=( + -self.k*sym.n_dot(Sn_times_rho,where=where) \ + + 1j*self.k*(self.k*sym.n_dot(Sn_cross_a,where=where) - 0.5*Srho_ik + self.Sp(Srho_ik_nest,target=where,qfl=qfl)) + ), + where=where) + + def vector_potential_rep(self, A_densities, target=None, qfl=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` """ # extract the densities from the main IE solution - (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) + + # define some useful quantities + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") # define the vector potential representation - return sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl) \ - + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) + return (sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl)) \ + + 1j*self.k*(self.k*self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl) + sym.grad(3,self.S(Srho_ik_nest,target,qfl=qfl))) - def div_vector_potential_rep2(self, A_densities, target=None, qfl=None): + def div_vector_potential_rep(self, A_densities, target=None, qfl=None): """ This method is a representation of the vector potential, phi, based on the vector density `a` and scalar density `rho` """ # extract the densities from the main IE solution - (a0, a, rho0, rho, v) = self._extract_a_densities2(A_densities) - - # define the vector potential representation - return self.k*(self.D(self.n_times(rho),target,qfl=qfl) \ - + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) - - def subproblem_rep(self, tau_densities, target=None, alpha = 1j, qfl=None): - """ - This method is a representation of the scalar potential, phi, - based on the density `sigma`. - """ + (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) - # extract the densities needed to solve the system of equations - (tau0, tau) = self._extract_tau_densities(tau_densities) + # define some useful quantities + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") - # evaluate scalar potential representation - return self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl) + # define the vector potential representation + return self.k*( self.D(rho,target,qfl=qfl) \ + + 1j*self.k*(sym.div(self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl)) - self.k * self.S(Srho_ik_nest,target,qfl=qfl))) def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j,qfl=None): """ @@ -807,7 +772,6 @@ class DPIEOperator: 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`. """ @@ -818,6 +782,7 @@ class DPIEOperator: (tau0, tau) = self._extract_tau_densities(tau_densities) # obtain expressions for scalar and vector potentials + Sa_ik_nest = self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik) A = self.vector_potential_rep(A_densities, target=target) phi = self.scalar_potential_rep(phi_densities, target=target) @@ -826,7 +791,7 @@ class DPIEOperator: H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl))) \ + (self.k**2) * self.S(a,target,qfl=qfl) \ - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=qfl)) \ - + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=qfl)) + + 1j*(self.k**2)*sym.curl(self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl)) # join the fields into a vector diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 9a150e00..27d0a9d0 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -192,8 +192,8 @@ class ElliptiPlaneTestCase(MaxwellTestCase): tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) -tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], - qbx_order=3, fmm_tolerance=1e-4) +tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0], + qbx_order=7, fmm_tolerance=1e-4) tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], qbx_order=3, fmm_tolerance=1e-4) @@ -242,7 +242,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): np.random.seed(12) # specify a dictionary with some useful arguments - knl_kwargs = {"k": case.k} + knl_kwargs = {"k": case.k, "ik": 1j*case.k} # specify the list of geometry objects being used geom_list = ["obj0"] @@ -255,7 +255,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): import pytential.symbolic.pde.maxwell.dpie as mw_dpie # initialize the DPIE operator based on the geometry list - dpie = mw_dpie.DPIEOperator(geometry_list=geom_list) + dpie = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) # specify some symbolic variables that will be used # in the process to solve integral equations for the DPIE @@ -287,20 +287,20 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define functions that can be used to generate incident fields for an input discretization # define potentials based on incident plane wave def get_incident_plane_wave_EHField(tgt): - return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, omega=dpie.k))(queue,k=case.k,u=u_dir,Ep=Ep) + return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, omega=dpie.k))(queue,u=u_dir,Ep=Ep,**knl_kwargs) # get the gradphi_inc field evaluated at some source locations def get_incident_gradphi(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) + return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) # get the incident plane wave div(A) def get_incident_divA(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,k=case.k,u=u_dir,Ep=Ep) + return bind(objects,mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) # method to get vector potential and scalar potential for incident # E-M fields def get_incident_potentials(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_potentials(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue, k=case.k,u=u_dir,Ep=Ep) + return bind(objects,mw.get_sym_maxwell_planewave_potentials(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) # define a smooth function to represent the density def dummy_density(omega = 1.0, where=None): @@ -392,7 +392,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define the problem that will be solved test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) - test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs2(function=getTestFunction))(queue,**knl_kwargs) + test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs_func(function=getTestFunction))(queue,**knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -515,7 +515,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # # {{{ Test the operators - test_operators = True + test_operators = False if test_operators: # define error array @@ -549,7 +549,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # use OpenCL random number generator to create a set of random # source locations for various variables being solved for - dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) + dpie0 = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) qbx0, _ = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, @@ -565,6 +565,26 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1e-4))(queue)) geom_map['tgt'] = tgt_n + # define a dummy density, specifically to be used for the vector potential A densities + x, y, z = qbx0.density_discr.nodes().with_queue(queue) + m = cl.clmath + + density_sym = sym.make_sym_vector("density", 2) + + # The tangential coordinate system is element-local, so we can't just + # conjure up some globally smooth functions, interpret their values + # in the tangential coordinate system, and be done. Instead, generate + # an XYZ function and project it. + density = bind( + qbx0, + sym.xyz_to_tangential(sym.make_sym_vector("jxyz", 3)))( + queue, + jxyz=sym.make_obj_array([ + m.cos(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z), + m.sin(0.5*x) * m.cos(0.5*y) * m.sin(0.5*z), + m.sin(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z), + ])) + # redefine a_densities #A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities2()) @@ -584,7 +604,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): else: dummy_phi[i] = 0.0 for i in range(0,n2): - if i < (n2-1): + if i < 2: + dummy_A[i] = density[i] + elif i < (n2-1): dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) else: dummy_A[i] = 0.0 @@ -596,9 +618,9 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): a = sym.tangential_to_xyz(vec_op_out[:2], where='obj0') return sym.join_fields(a,vec_op_out[2:]) - scalar_op = dpie0.phi_operator(phi_densities=phi_densities)[:-1] + scalar_op = dpie0.phi_operator(phi_densities=phi_densities) #vector_op = vector_op_transform(dpie0.a_operator0(A_densities=A_densities)[:-1]) - vector_op = vector_op_transform(dpie0.a_operator(A_densities=A_densities)[:-1]) + vector_op = vector_op_transform(dpie0.a_operator(A_densities=A_densities)) #vector_op = dpie0.a_operator2(A_densities=A_densities)[:-1] tau_op = dpie0.subproblem_operator(tau_densities=tau_densities) @@ -619,6 +641,36 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): vector_rep_eval = vector_from_device(queue,bind(geom_map, vec_op_repr(A_densities=A_densities,target='tgt'))(queue, A_densities=dummy_A, **knl_kwargs)) tau_rep_eval = vector_from_device(queue,bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs)) + + axyz = sym.tangential_to_xyz(density_sym,where='obj0') + def nxcurlS0(qbx_forced_limit): + return sym.n_cross(sym.curl(dpie0.S(axyz.reshape(3,1),target='obj0t',qfl=qbx_forced_limit)),where='obj0') + test_op_err = vector_from_device(queue,bind(geom_map, 0.5*axyz + nxcurlS0("avg") - nxcurlS0(+1))(queue,density=density,**knl_kwargs)) + + from sumpy.kernel import LaplaceKernel + knl = LaplaceKernel(3) + + from sumpy.kernel import HelmholtzKernel + knl = HelmholtzKernel(3) + + def nxcurlS(qbx_forced_limit): + + return sym.n_cross(sym.curl(sym.S( + knl, + sym.cse(sym.tangential_to_xyz(density_sym, where='obj0'), "jxyz"), + k=dpie0.k, + qbx_forced_limit=qbx_forced_limit,source='obj0', target='obj0t')),where='obj0') + + jump_identity_sym = ( + nxcurlS(+1) + - (nxcurlS("avg") + 0.5*sym.tangential_to_xyz(density_sym,where='obj0'))) + + bound_jump_identity = bind(geom_map, jump_identity_sym) + jump_identity = bound_jump_identity(queue, density=density, **knl_kwargs) + + err = (norm(qbx0, queue, jump_identity, np.inf)) + print("ERROR", qbx0.h_max, err) + # compute the error between the operator values and the representation values def error_diff(u,v): return np.linalg.norm(u-v,np.inf) @@ -627,7 +679,10 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): error_diff(vector_op_eval[1],vector_rep_eval[1]), error_diff(vector_op_eval[2],vector_rep_eval[2]), error_diff(vector_op_eval[3],vector_rep_eval[3]), - error_diff(tau_op_eval[0],tau_rep_eval)] + error_diff(tau_op_eval[0],tau_rep_eval), + np.linalg.norm(test_op_err[0],np.inf), + np.linalg.norm(test_op_err[1],np.inf), + np.linalg.norm(test_op_err[2],np.inf)] op_error.append(error_v) # print the resulting error results @@ -639,7 +694,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ Solve for the scattered field - solve_scattered_field = False + solve_scattered_field = True if solve_scattered_field: loc_sign = -1 if case.is_interior else +1 @@ -715,17 +770,18 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): #inc_vec_field_scat = get_inc_potentials(scat_discr) #inc_vec_field_obs = get_inc_potentials(obs_discr) - # get the incident fields used for boundary conditions - (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') - inc_divA_scat = get_incident_divA(geom_map,'scat') - inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') - # {{{ solve the system of integral equations inc_A = sym.make_sym_vector("inc_A", 3) inc_phi = sym.make_sym_vector("inc_phi",1) inc_divA = sym.make_sym_vector("inc_divA",1) inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) + # get the incident fields used for boundary conditions + (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') + inc_divA_scat = get_incident_divA(geom_map,'scat') + inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') + + # check that the boundary conditions satisfy gauge condition resid = bind(geom_map,gauge_check(inc_divA, inc_phi, dpie.k))(queue,inc_divA=inc_divA_scat,inc_phi=phi_inc,**knl_kwargs) # setup operators that will be solved @@ -805,25 +861,24 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ check potential PEC BC on total field - def scalar_pot_PEC_residual(phi, gradPhi, where=None): - return sym.n_cross( - dpie.grad_scalar_potential_rep(phi_densities=phi,target=where,qfl="avg") + gradPhi, - where=where) + def scalar_pot_PEC_residual(phi, inc_phi, where=None): + V = dpie.scalar_potential_constants(phi_densities=phi) + return dpie.scalar_potential_rep(phi_densities=phi,target=where, qfl=loc_sign) + inc_phi - V[0] def vector_pot_PEC_residual(a_densities, inc_a, where=None): - return sym.n_cross( dpie.vector_potential_rep(A_densities=a_densities, target=where, qfl="avg") + inc_a, where=where) + return sym.n_cross( dpie.vector_potential_rep(A_densities=a_densities, target=where, qfl=loc_sign) + inc_a, where=where) - phi_pec_bc_resid = scalar_pot_PEC_residual(phi_densities, inc_gradPhi, where="obj0") + phi_pec_bc_resid = scalar_pot_PEC_residual(phi_densities, inc_phi, where="obj0") A_pec_bc_resid = vector_pot_PEC_residual(A_densities, inc_A, where="obj0") - scalar_bc_values = bind(geom_map, phi_pec_bc_resid)(queue, phi_densities=phi_dens, inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) + scalar_bc_values = bind(geom_map, phi_pec_bc_resid)(queue, phi_densities=phi_dens, inc_phi=phi_inc,**knl_kwargs) vector_bc_values = bind(geom_map, A_pec_bc_resid)(queue, A_densities=A_dens, inc_A=A_inc,**knl_kwargs) def scat_norm(f): return norm(qbx, queue, f, p=np.inf) - scalar_bc_residual = scat_norm(scalar_bc_values) # / scat_norm(inc_gradPhi_scat) - vector_bc_residual = scat_norm(vector_bc_values) # / scat_norm(A_inc) + scalar_bc_residual = scat_norm(scalar_bc_values) #/ scat_norm(phi_inc) + vector_bc_residual = scat_norm(vector_bc_values) #/ scat_norm(A_inc) print("Potential PEC BC residuals:", h_max, scalar_bc_residual, vector_bc_residual) @@ -831,6 +886,11 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # }}} + # {{{ check if DPIE helmholtz BCs are satisfied + + + #}}} + # {{{ visualization if visualize: -- GitLab From 9e9ba2e9d6491a833a77dcffc17316a181353016 Mon Sep 17 00:00:00 2001 From: Christian Howard Date: Wed, 27 Jun 2018 14:54:17 -0500 Subject: [PATCH 40/59] Removed some commented code in the test_maxwell.py file to make it closer to what it was before. Also fixed an issue in the MatVecOp class and matvec method that made it difficult to work with scalar IEs and scalar IEs represented by a single element symbolic array. --- pytential/symbolic/execution.py | 9 ++++-- test/test_maxwell.py | 54 ++++++++++++++++----------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 0c7c2e11..1722db15 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -170,7 +170,7 @@ class MatVecOp: def __init__(self, bound_expr, queue, arg_name, dtype, total_dofs, - starts_and_ends, extra_args): + starts_and_ends, extra_args, isScalar=False): self.bound_expr = bound_expr self.queue = queue self.arg_name = arg_name @@ -178,6 +178,7 @@ class MatVecOp: self.total_dofs = total_dofs self.starts_and_ends = starts_and_ends self.extra_args = extra_args + self.isScalar = isScalar @property def shape(self): @@ -190,7 +191,7 @@ class MatVecOp: else: out_host = False - do_split = len(self.starts_and_ends) >= 1 + do_split = not self.isScalar from pytools.obj_array import make_obj_array if do_split: @@ -277,10 +278,12 @@ class BoundExpression: """ from pytools.obj_array import is_obj_array + isScalar = False if is_obj_array(self.code.result): nresults = len(self.code.result) else: nresults = 1 + isScalar = True from pytential.symbolic.primitives import DEFAULT_TARGET domains = _domains_default(nresults, self.places, domains, @@ -303,7 +306,7 @@ class BoundExpression: # for linear system solving, in which case the assumption # has to be true. return MatVecOp(self, queue, - arg_name, dtype, total_dofs, starts_and_ends, extra_args) + arg_name, dtype, total_dofs, starts_and_ends, extra_args, isScalar=isScalar) def __call__(self, queue, **args): exec_mapper = EvaluationMapper(self, queue, args) diff --git a/test/test_maxwell.py b/test/test_maxwell.py index be7d33ff..a3ceca6e 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -380,24 +380,24 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): # {{{ check PEC BC on total field - # bc_repr = EHField(mfie.scattered_volume_field( - # jt_sym, rho_sym, qbx_forced_limit=loc_sign)) - # pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) - # pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) + bc_repr = EHField(mfie.scattered_volume_field( + jt_sym, rho_sym, qbx_forced_limit=loc_sign)) + pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) + pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) - # eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( - # queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, - # **knl_kwargs) + eh_bc_values = bind(qbx, sym.join_fields(pec_bc_e, pec_bc_h))( + queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, + **knl_kwargs) - # def scat_norm(f): - # return norm(qbx, queue, f, p=np.inf) + def scat_norm(f): + return norm(qbx, queue, f, p=np.inf) - # e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) - # h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) + e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) + h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) - # print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) + print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) - # eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) + eoc_pec_bc.add_data_point(h_max, max(e_bc_residual, h_bc_residual)) # }}} @@ -458,22 +458,22 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): # {{{ error in E, H - # obs_repr = EHField(eval_repr_at(obs_discr)) + obs_repr = EHField(eval_repr_at(obs_discr)) - # def obs_norm(f): - # return norm(obs_discr, queue, f, p=np.inf) + def obs_norm(f): + return norm(obs_discr, queue, f, p=np.inf) - # rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) - # / obs_norm(inc_field_obs.e)) - # rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) - # / obs_norm(inc_field_obs.h)) + rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) + / obs_norm(inc_field_obs.e)) + rel_err_h = (obs_norm(inc_field_obs.h + obs_repr.h) + / obs_norm(inc_field_obs.h)) # # }}} - # print("ERR", h_max, rel_err_h, rel_err_e) + print("ERR", h_max, rel_err_h, rel_err_e) - # eoc_rec_h.add_data_point(h_max, rel_err_h) - # eoc_rec_e.add_data_point(h_max, rel_err_e) + eoc_rec_h.add_data_point(h_max, rel_err_h) + eoc_rec_e.add_data_point(h_max, rel_err_e) print("--------------------------------------------------------") print("is_interior=%s" % case.is_interior) @@ -481,10 +481,10 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): good = True for which_eoc, eoc_rec, order_tol in [ - ("maxwell", eoc_rec_repr_maxwell, 1.5) - #("PEC BC", eoc_pec_bc, 1.5), - #("H", eoc_rec_h, 1.5), - #("E", eoc_rec_e, 1.5) + ("maxwell", eoc_rec_repr_maxwell, 1.5), + ("PEC BC", eoc_pec_bc, 1.5), + ("H", eoc_rec_h, 1.5), + ("E", eoc_rec_e, 1.5) ]: print(which_eoc) print(eoc_rec.pretty_print()) -- GitLab From d2b9b029b14201d1ebe3af31a943406852f891a0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 13:58:37 -0500 Subject: [PATCH 41/59] Flake8 fixes outside DPIE --- pytential/solve.py | 14 +++++++------- pytential/symbolic/execution.py | 15 ++++++++------- pytential/symbolic/primitives.py | 17 +++++++++++------ test/test_maxwell.py | 2 +- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index 4b929fdd..761bfcab 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -55,7 +55,7 @@ def get_array_module(vec): # {{{ block system support class VectorChopper(object): - def __init__(self, structured_vec, queue = None): + def __init__(self, structured_vec, queue=None): from pytools.obj_array import is_obj_array self.is_structured = is_obj_array(structured_vec) self.array_module = get_array_module(structured_vec) @@ -72,7 +72,7 @@ class VectorChopper(object): length = 1 is_scalar = True - self.slices.append((is_scalar,slice(num_dofs, num_dofs+length))) + self.slices.append((is_scalar, slice(num_dofs, num_dofs+length))) num_dofs += length def stack(self, vec): @@ -81,7 +81,7 @@ class VectorChopper(object): if not self.is_structured: return vec - for n in range(0,len(self.slices)): + for n in range(0, len(self.slices)): if self.slices[n][0]: vec[n] = cl.array.to_device(self.queue, np.array([vec[n]])) @@ -92,13 +92,13 @@ class VectorChopper(object): return vec from pytools.obj_array import make_obj_array - result = make_obj_array([vec[slc] for (is_scalar,slc) in self.slices]) + result = make_obj_array([vec[slc] for (is_scalar, slc) in self.slices]) if self.queue is not None: - for n in range(0,len(self.slices)): + for n in range(0, len(self.slices)): if self.slices[n][0]: result[n] = result[n].get(self.queue)[0] - + return result # }}} @@ -340,7 +340,7 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, :return: a :class:`GMRESResult` """ amod = get_array_module(rhs) - chopper = VectorChopper(rhs,op.queue) + chopper = VectorChopper(rhs, op.queue) stacked_rhs = chopper.stack(rhs) if inner_product is None: diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index a47ead62..6652d01c 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -231,7 +231,7 @@ class MatVecOp: def __init__(self, bound_expr, queue, arg_name, dtype, total_dofs, - starts_and_ends, extra_args, isScalar=False): + starts_and_ends, extra_args, is_scalar=False): self.bound_expr = bound_expr self.queue = queue self.arg_name = arg_name @@ -239,7 +239,7 @@ class MatVecOp: self.total_dofs = total_dofs self.starts_and_ends = starts_and_ends self.extra_args = extra_args - self.isScalar = isScalar + self.is_scalar = is_scalar @property def shape(self): @@ -252,7 +252,7 @@ class MatVecOp: else: out_host = False - do_split = not self.isScalar + do_split = not self.is_scalar from pytools.obj_array import make_obj_array if do_split: @@ -260,7 +260,7 @@ class MatVecOp: [x[start:end] for start, end in self.starts_and_ends]) # make any scalar terms actually scalars - for n in range(0,len(x)): + for n in range(0, len(x)): if x[n].shape == (1,): x[n] = x[n].get(self.queue)[0] @@ -344,12 +344,12 @@ class BoundExpression: """ from pytools.obj_array import is_obj_array - isScalar = False + is_scalar = False if is_obj_array(self.code.result): nresults = len(self.code.result) else: nresults = 1 - isScalar = True + is_scalar = True from pytential.symbolic.primitives import DEFAULT_TARGET domains = _domains_default(nresults, self.places, domains, @@ -372,7 +372,8 @@ class BoundExpression: # for linear system solving, in which case the assumption # has to be true. return MatVecOp(self, queue, - arg_name, dtype, total_dofs, starts_and_ends, extra_args, isScalar=isScalar) + arg_name, dtype, total_dofs, starts_and_ends, extra_args, + is_scalar=is_scalar) def __call__(self, queue, **args): exec_mapper = EvaluationMapper(self, queue, args) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 1d8ae01c..eeb6f996 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -791,9 +791,10 @@ def integral(ambient_dim, dim, operand, where=None): `ambient_dim` is the number of dimensions used to represent space while `dim` is the dimensionality of the surface being integrated over. - Example| - We wish to integrate over the 2-D surface of a sphere that resides in - in 3-dimensions, so `ambient_dim` = 3 and `dim` = 2. + .. note:: + + If, for example, we wish to integrate over the 2-D surface of a sphere + that resides in in 3-dimensions, then *ambient_dim* = 3 and *dim* = 2. """ return NodeSum( @@ -1259,14 +1260,15 @@ def normal_derivative(ambient_dim, operand, dim=None, where=None): def make_op(operand_i): d = Derivative() return d.resolve( - (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) + (normal(ambient_dim, dim, where) + .scalar_product(d.dnabla(ambient_dim))) * d(operand_i)) return componentwise(make_op, operand) else: d = Derivative() return d.resolve( - (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) + (normal(ambient_dim, dim, where).scalar_product(d.dnabla(ambient_dim))) * d(operand)) @@ -1433,7 +1435,10 @@ def n_cross(vec, where=None): def div(vec): ambient_dim = len(vec) - return sum(dd_axis(iaxis, ambient_dim, vec[iaxis]) for iaxis in range(ambient_dim)) + return sum( + dd_axis(iaxis, ambient_dim, vec[iaxis]) + for iaxis in range(ambient_dim)) + def curl(vec): from pytools import levi_civita diff --git a/test/test_maxwell.py b/test/test_maxwell.py index a96753ea..a4f05c76 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -268,7 +268,7 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): # point source return bind( (test_source, tgt), - get_sym_maxwell_point_source(mfie.kernel, j_sym, mfie.k) + get_sym_maxwell_point_source_em(mfie.kernel, j_sym, mfie.k) )(queue, j=src_j, k=case.k) pde_test_inc = EHField( -- GitLab From 796ff821a3f2796ee1ea87fb0ec318a4e2c06af0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 15:28:19 -0500 Subject: [PATCH 42/59] GMRES: Don't require a CL queue if not solving a system --- pytential/solve.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index 761bfcab..0f4d3c32 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -81,6 +81,10 @@ class VectorChopper(object): if not self.is_structured: return vec + if self.queue is None: + raise ValueError("a CL queue must be supplied if support of systems " + "of equations is desired") + for n in range(0, len(self.slices)): if self.slices[n][0]: vec[n] = cl.array.to_device(self.queue, np.array([vec[n]])) @@ -91,6 +95,10 @@ class VectorChopper(object): if not self.is_structured: return vec + if self.queue is None: + raise ValueError("a CL queue must be supplied if support of systems " + "of equations is desired") + from pytools.obj_array import make_obj_array result = make_obj_array([vec[slc] for (is_scalar, slc) in self.slices]) @@ -320,7 +328,7 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, inner_product=None, maxiter=None, hard_failure=None, no_progress_factor=None, stall_iterations=None, - callback=None, progress=False): + callback=None, progress=False, cl_queue=None): """Solve a linear system Ax=b by means of GMRES with restarts. @@ -336,11 +344,17 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, :arg stall_iterations: Number of iterations with residual decrease below *no_progress_factor* indicates stall. Set to 0 to disable stall detection. + :arg cl_queue: a :class:`pyopencl.CommandQueue` instance, to support + automatic vector splitting/assembly for systems of equations :return: a :class:`GMRESResult` """ amod = get_array_module(rhs) - chopper = VectorChopper(rhs, op.queue) + + if cl_queue is None: + cl_queue = getattr(op, "queue", None) + chopper = VectorChopper(rhs, cl_queue) + stacked_rhs = chopper.stack(rhs) if inner_product is None: -- GitLab From 9c77ac5c004bf9adf983502a20fe469c5924456f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 15:31:06 -0500 Subject: [PATCH 43/59] Fix indexing on MFIE residual checks --- test/test_maxwell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_maxwell.py b/test/test_maxwell.py index a4f05c76..8b2bcfc2 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -394,7 +394,7 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): return norm(qbx, queue, f, p=np.inf) e_bc_residual = scat_norm(eh_bc_values[:3]) / scat_norm(inc_field_scat.e) - h_bc_residual = scat_norm(eh_bc_values[3]) / scat_norm(inc_field_scat.h) + h_bc_residual = scat_norm(eh_bc_values[3:]) / scat_norm(inc_field_scat.h) print("E/H PEC BC residuals:", h_max, e_bc_residual, h_bc_residual) @@ -418,7 +418,7 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): ("Hinc", inc_field_scat.h), ("bdry_normals", bdry_normals), ("e_bc_residual", eh_bc_values[:3]), - ("h_bc_residual", eh_bc_values[3]), + ("h_bc_residual", eh_bc_values[3:]), ]) fplot = make_field_plotter_from_bbox( -- GitLab From 35331b61ba5e1e0643fdb49b7b1773faed993b88 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 15:33:27 -0500 Subject: [PATCH 44/59] Nodewise reductions: tolerate zero scalars --- pytential/symbolic/execution.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 6652d01c..082a1ce9 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -74,10 +74,20 @@ class EvaluationMapper(EvaluationMapperBase): expr) def map_node_sum(self, expr): - return cl.array.sum(self.rec(expr.operand)).get()[()] + expr_val = self.rec(expr.operand) + from numbers import Number + if isinstance(expr_val, Number) and expr_val == 0: + return expr_val + + return cl.array.sum(expr_val).get()[()] def map_node_max(self, expr): - return cl.array.max(self.rec(expr.operand)).get()[()] + expr_val = self.rec(expr.operand) + from numbers import Number + if isinstance(expr_val, Number) and expr_val == 0: + return expr_val + + return cl.array.max(expr_val).get()[()] def _map_elementwise_reduction(self, reduction_name, expr): @memoize_in(self.bound_expr, "elementwise_"+reduction_name) -- GitLab From 49ac891c0ce5170f38c190c6790aae6497820462 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Jul 2018 12:50:28 -0500 Subject: [PATCH 45/59] Clean up non-test Flake8 complaints --- pytential/symbolic/pde/maxwell/__init__.py | 62 ++- pytential/symbolic/pde/maxwell/dpie.py | 527 ++++++++++++--------- 2 files changed, 348 insertions(+), 241 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index fbf5b715..fc5f1077 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -117,7 +117,8 @@ def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, epsilon=1, mu=1, where=N # }}} -# {{{ point source for vector potential based on Lorenz gauge + +# {{{ Maxwell sources for scalar/vector potentials def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): r"""Return a symbolic expression that, when bound to a @@ -136,50 +137,67 @@ def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): field[:3]/(1j*k) # vector potential ) -# }}} def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): - r""" - Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` - and yield the gradient of a scalar potential field satisfying Maxwell's equations. + r""" Return symbolic expression that can be bound to a + :class:`pytential.source.PointPotentialSource` and yield the gradient of a + scalar potential field satisfying Maxwell's equations. + + Represents the following: - Should be representing the following: .. math:: + \nabla \phi(x) = - e^{i k x^T u} E_p^T \left( 1 + i k x^T u\right) """ x = sym.nodes(3, where).as_vector() - grad_phi = -sym.exp(1j*k*np.dot(x,u)) * (Ep.T + 1j*k*np.dot(Ep,x)*u.T) + grad_phi = -sym.exp(1j*k*np.dot(x, u)) * (Ep.T + 1j*k*np.dot(Ep, x)*u.T) return grad_phi + def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): - r""" - Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` - and yield the divergence of a vector potential field satisfying Maxwell's equations. + r"""Return symbolic expression that can be bound to a + :class:`pytential.source.PointPotentialSource` and yield the divergence of + a vector potential field satisfying Maxwell's equations. + + Represents the following: - Should be representing the following: .. math:: - \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} e^{i k x^T u} E_p^T \left( u + i k x\right) + + \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} + e^{i k x^T u} E_p^T \left( u + i k x\right) """ x = sym.nodes(3, where).as_vector() - divA = sym.join_fields(-sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) * np.dot(Ep,u + 1j*k*x)) + divA = sym.join_fields( + -sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x, u)) + * np.dot(Ep, u + 1j*k*x)) + return divA + def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): - r""" - Return a 2-tuple of symbolic expressions that can be bound to a :class:`pytential.source.PointPotentialSource` - and yield the scalar and vector potential fields satisfying Maxwell's equations that represent - a plane wave. + r"""Return a 2-tuple of symbolic expressions that can be bound to a + :class:`pytential.source.PointPotentialSource` and yield the scalar and + vector potential fields satisfying Maxwell's equations that represent a + plane wave. + + Represents the following: - Should be representing the following: .. math:: - \boldsymbol{A} = -u \left(x \cdot E_p \right)\sqrt{\mu \epsilon} e^{i k x^T u} + + \boldsymbol{A} = -u \left(x \cdot E_p \right) + \sqrt{\mu \epsilon} e^{i k x^T u} + .. math:: + \phi = - \left(x \cdot E_p\right) e^{i k x^T u} """ x = sym.nodes(3, where).as_vector() - A = -u * np.dot(x,Ep) * sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x,u)) - phi = sym.join_fields(-np.dot(x,Ep) * sym.exp(1j*k*np.dot(x,u))) - return (phi, A) + A = -u * np.dot(x, Ep) * sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x, u)) + phi = sym.join_fields(-np.dot(x, Ep) * sym.exp(1j*k*np.dot(x, u))) + return phi, A + +# }}} + # {{{ Charge-Current MFIE diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 8b0b39dd..3b483ba5 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -22,38 +22,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -# import useful tools/libs -import numpy as np # noqa -from pytential import bind, sym -from collections import namedtuple -from functools import partial - -# define a few functions based on existing functions -tangential_to_xyz = sym.tangential_to_xyz -xyz_to_tangential = sym.xyz_to_tangential -cse = sym.cse +import numpy as np # noqa +from pytential import sym __doc__ = """ .. autoclass:: DPIEOperator .. autoclass:: DPIEOperatorEvanescent """ +cse = sym.cse # {{{ Decoupled Potential Integral Equation Operator - based on Arxiv paper -class DPIEOperator: + +class DPIEOperator(object): r""" Decoupled Potential Integral Equation operator with PEC boundary conditions, defaults as scaled DPIE. - See https://arxiv.org/abs/1404.0749 for derivation. + See `the arxiv paper `_ for derivation. - Uses :math:`E(x,t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and - :math:`H(x,t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for + Uses :math:`E(x, t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and + :math:`H(x, t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for the :math:`E(x)`, :math:`H(x)` fields using vector and scalar potentials via - the Lorenz Gauage. The DPIE formulates the problem purely in terms of the - vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, - and then backs out :math:`E(x)` and :math:`H(x)` via relationships to + the Lorenz Gauage. The DPIE formulates the problem purely in terms of the + vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, + and then backs out :math:`E(x)` and :math:`H(x)` via relationships to the vector and scalar potentials. """ @@ -61,15 +55,15 @@ class DPIEOperator: from sumpy.kernel import HelmholtzKernel # specify the frequency variable that will be tuned - self.k = k - self.stype = type(self.k) + self.k = k + self.stype = type(self.k) - # specify the 3-D Helmholtz kernel - self.kernel = HelmholtzKernel(3) + # specify the 3-D Helmholtz kernel + self.kernel = HelmholtzKernel(3) # specify a list of strings representing geometry objects - self.geometry_list = geometry_list - self.nobjs = len(geometry_list) + self.geometry_list = geometry_list + self.nobjs = len(geometry_list) def num_distinct_objects(self): return self.nobjs @@ -84,17 +78,16 @@ class DPIEOperator: return 2*len(self.geometry_list) def get_vector_domain_list(self): - """ - Method to return domain list that will be used within the scipy_op method to - solve the system of discretized integral equations. What is returned should just - be a list with values that are strings or None. + """Method to return domain list that will be used within the scipy_op + method to solve the system of discretized integral equations. What is + returned should just be a list with values that are strings or None. """ # initialize domain list domain_list = [None]*self.num_vector_potential_densities() # get strings for the actual densities - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): # grab nth location identifier location = self.geometry_list[n] + "t" @@ -110,17 +103,16 @@ class DPIEOperator: return domain_list def get_scalar_domain_list(self): - """ - Method to return domain list that will be used within the scipy_op method to - solve the system of discretized integral equations. What is returned should just - be a list with values that are strings or None. + """Method to return domain list that will be used within the scipy_op + method to solve the system of discretized integral equations. What is + returned should just be a list with values that are strings or None. """ # initialize domain list domain_list = [None]*self.num_scalar_potential_densities() # get strings for the actual densities - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): # grab nth location identifier location = self.geometry_list[n] + "t" @@ -132,17 +124,16 @@ class DPIEOperator: return domain_list def get_subproblem_domain_list(self): - """ - Method to return domain list that will be used within the scipy_op method to - solve the system of discretized integral equations. What is returned should just - be a list with values that are strings or None. + """Method to return domain list that will be used within the scipy_op + method to solve the system of discretized integral equations. What is + returned should just be a list with values that are strings or None. """ # initialize domain list domain_list = [None]*self.nobjs # get strings for the actual densities - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): # grab nth location identifier location = self.geometry_list[n] + "t" @@ -153,10 +144,10 @@ class DPIEOperator: # return the domain list return domain_list - - def _layerpot_op(self,layerpot_op,density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): - """ - Generic layer potential operator method that works across all objects within the DPIE model + def _layerpot_op(self, layerpot_op, density_vec, target=None, qfl="avg", k=None, + kernel=None, use_laplace=False): + """Generic layer potential operator method that works across all + objects within the DPIE model """ if kernel is None: kernel = self.kernel @@ -168,9 +159,11 @@ class DPIEOperator: if not use_laplace: kargs['k'] = k - # define a convenient integral operator that functions across the multiple objects + # define a convenient integral operator that functions across the + # multiple objects def int_op(idx): - return layerpot_op(kernel, density_vec[:,idx],qbx_forced_limit=qfl,source=self.geometry_list[idx],target=target, **kargs) + return layerpot_op(kernel, density_vec[:, idx], qbx_forced_limit=qfl, + source=self.geometry_list[idx], target=target, **kargs) # get the shape of density_vec (ndim, nobj) = density_vec.shape @@ -180,7 +173,7 @@ class DPIEOperator: # compute individual double layer potential evaluations at the given # density across all the disjoint objects - for i in range(0,nobj): + for i in range(0, nobj): output = output + int_op(i) # return the output summation @@ -189,42 +182,56 @@ class DPIEOperator: else: return output - def D(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): + def D(self, density_vec, target=None, qfl="avg", k=None, kernel=None, + use_laplace=False): """ Double layer potential operator across multiple disjoint objects """ - return self._layerpot_op(layerpot_op=sym.D, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) + return self._layerpot_op(layerpot_op=sym.D, density_vec=density_vec, + target=target, qfl=qfl, k=k, kernel=kernel, + use_laplace=use_laplace) - def S(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): + def S(self, density_vec, target=None, qfl="avg", k=None, kernel=None, + use_laplace=False): """ Single layer potential operator across multiple disjoint objects """ - return self._layerpot_op(layerpot_op=sym.S, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) - + return self._layerpot_op(layerpot_op=sym.S, density_vec=density_vec, + target=target, qfl=qfl, k=k, kernel=kernel, + use_laplace=use_laplace) - def Dp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): + def Dp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, + use_laplace=False): """ D' layer potential operator across multiple disjoint objects """ - return self._layerpot_op(layerpot_op=sym.Dp, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) + return self._layerpot_op(layerpot_op=sym.Dp, density_vec=density_vec, + target=target, qfl=qfl, k=k, kernel=kernel, + use_laplace=use_laplace) - def Sp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, use_laplace=False): + def Sp(self, density_vec, target=None, qfl="avg", k=None, kernel=None, + use_laplace=False): """ S' layer potential operator across multiple disjoint objects """ - return self._layerpot_op(layerpot_op=sym.Sp, density_vec=density_vec, target=target, qfl=qfl, k=k, kernel=kernel, use_laplace=use_laplace) + return self._layerpot_op(layerpot_op=sym.Sp, density_vec=density_vec, + target=target, qfl=qfl, k=k, kernel=kernel, + use_laplace=use_laplace) def n_cross(self, density_vec): - r""" - This method is such that an cross(n,a) can operate across vectors - a and n that are local to a set of disjoint source surfaces. Essentially, - imagine that :math:`\bar{a} = [a_1, a_2, \cdots, a_m]`, where :math:`a_k` represents a vector density - defined on the :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = [n_1, n_2, \cdots, n_m]`, - where :math:`n_k` represents a normal that exists on the :math:`k^{th}` disjoint object. The goal, then, - is to have an operator that does element-wise cross products.. ie: + r"""This method is such that ``cross(n, a)`` can operate across vectors + a and n that are local to a set of disjoint source surfaces. + Essentially, imagine that :math:`\bar{a} = [a_1, a_2, \cdots, a_m]`, + where :math:`a_k` represents a vector density defined on the + :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = [n_1, + n_2, \cdots, n_m]`, where :math:`n_k` represents a normal that exists + on the :math:`k^{th}` disjoint object. The goal, then, is to have an + operator that does element-wise cross products.. ie: .. math:: - \bar{n} \times \bar{a}) = [ \left(n_1 \times a_1\right), ..., \left(n_m \times a_m \right)] + + \bar{n} \times \bar{a}) = [ \left(n_1 \times a_1\right), \dots, + \left(n_m \times a_m \right)] """ # specify the sources to be evaluated at @@ -241,23 +248,28 @@ class DPIEOperator: # loop through the density and sources to construct the appropriate # element-wise cross product operation - for k in range(0,nobj): - output[:,k] = sym.n_cross(density_vec[:,k],where=sources[k]) + for k in range(0, nobj): + output[:, k] = sym.n_cross(density_vec[:, k], where=sources[k]) # return result from element-wise cross product return output def n_times(self, density_vec): - r""" - This method is such that an :math:`\boldsymbol{n} \rho`, for some normal :math:`\boldsymbol{n}` and - some scalar :math:`\rho` can be done across normals and scalars that exist on multiple surfaces. Essentially, - imagine that :math:`\bar{\rho} = [\rho_1, \cdots, \rho_m]`, where :math:`\rho_k` represents a scalar density - defined on the :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = [\boldsymbol{n}_1, \cdots, \boldsymbol{n}_m]`, - where :math:`n_k` represents a normal that exists on the :math:`k^{th}` disjoint object. The goal, then, - is to have an operator that does element-wise products.. ie: + r"""This method is such that an :math:`\boldsymbol{n} \rho`, for some + normal :math:`\boldsymbol{n}` and some scalar :math:`\rho` can be done + across normals and scalars that exist on multiple surfaces. + Essentially, imagine that :math:`\bar{\rho} = [\rho_1, \cdots, + \rho_m]`, where :math:`\rho_k` represents a scalar density defined on + the :math:`k^{th}` disjoint object. Also imagine that :math:`bar{n} = + [\boldsymbol{n}_1, \cdots, \boldsymbol{n}_m]`, where :math:`n_k` + represents a normal that exists on the :math:`k^{th}` disjoint object. + The goal, then, is to have an operator that does element-wise + products, i.e.: .. math:: - \bar{n}\bar{\rho} = [ \left(\boldsymbol{n}_1 \rho_1\right), ..., \left(\boldsymbol{n}_m \rho_m \right)] + + \bar{n}\bar{\rho} = [ \left(\boldsymbol{n}_1 \rho_1\right), \dots, + \left(\boldsymbol{n}_m \rho_m \right)] """ # specify the sources to be evaluated at @@ -270,69 +282,77 @@ class DPIEOperator: assert ndim == 1 # init output symbolic quantity with zeros - output = np.zeros((3,nobj), dtype=self.stype) + output = np.zeros((3, nobj), dtype=self.stype) # loop through the density and sources to construct the appropriate # element-wise cross product operation - for k in range(0,nobj): - output[:,k] = sym.normal(3,where=sources[k]).as_vector() * density_vec[0,k] + for k in range(0, nobj): + output[:, k] = \ + sym.normal(3, where=sources[k]).as_vector() * density_vec[0, k] # return result from element-wise cross product return output - def _extract_phi_densities(self,phi_densities): - return (phi_densities[:self.nobjs],phi_densities[:self.nobjs].reshape((1,self.nobjs)),phi_densities[self.nobjs:]) + def _extract_phi_densities(self, phi_densities): + return (phi_densities[:self.nobjs], + phi_densities[:self.nobjs].reshape((1, self.nobjs)), + phi_densities[self.nobjs:]) - def _extract_tau_densities(self,tau_densities): - return (tau_densities,tau_densities.reshape((1,self.nobjs))) + def _extract_tau_densities(self, tau_densities): + return (tau_densities, tau_densities.reshape((1, self.nobjs))) - def _extract_a_densities(self,A_densities): + def _extract_a_densities(self, A_densities): a0 = A_densities[:(2*self.nobjs)] - a = np.zeros((3,self.nobjs),dtype=self.stype) + a = np.zeros((3, self.nobjs), dtype=self.stype) rho0 = A_densities[(2*self.nobjs):(3*self.nobjs)] - rho = rho0.reshape((1,self.nobjs)) + rho = rho0.reshape((1, self.nobjs)) v = A_densities[(3*self.nobjs):] - for n in range(0,self.nobjs): - a[:,n] = cse(sym.tangential_to_xyz(a0[2*n:2*(n+1)],where=self.geometry_list[n]),"axyz_{0}".format(n)) + for n in range(0, self.nobjs): + a[:, n] = cse(sym.tangential_to_xyz(a0[2*n:2*(n+1)], + where=self.geometry_list[n]), "axyz_{0}".format(n)) return (a0, a, rho0, rho, v) def _L(self, a, rho, where): # define some useful common sub expressions - Sa = cse(self.S(a,where),"Sa_"+where) - Srho = cse(self.S(rho,where),"Srho_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) - Drho = cse(self.D(rho,where),"Drho_"+where) + # Sa = cse(self.S(a, where), "Sa_"+where) + # Srho = cse(self.S(rho, where), "Srho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) + # Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) + Drho = cse(self.D(rho, where), "Drho_"+where) return sym.join_fields( - sym.n_cross(sym.curl(self.S(a,where)) - self.k * Sn_times_rho,where=where), + sym.n_cross( + sym.curl(self.S(a, where)) - self.k * Sn_times_rho, + where=where), Drho) def _R(self, a, rho, where): # define some useful common sub expressions - Sa = cse(self.S(a,where),"Sa_"+where) - Srho = cse(self.S(rho,where),"Srho_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) - Drho = cse(self.D(rho,where),"Drho_"+where) + # Sa = cse(self.S(a, where), "Sa_"+where) + Srho = cse(self.S(rho, where), "Srho_"+where) + # Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) + # Drho = cse(self.D(rho, where), "Drho_"+where) return sym.join_fields( - sym.n_cross( self.k * Sn_cross_a + sym.grad(ambient_dim=3,operand=self.S(rho,where)),where=where), - sym.div(self.S(self.n_cross(a),where)) - self.k * Srho + sym.n_cross(self.k * Sn_cross_a + sym.grad(ambient_dim=3, + operand=self.S(rho, where)), where=where), + sym.div(self.S(self.n_cross(a), where)) - self.k * Srho ) def _scaledDPIEs_integral(self, sigma, sigma_n, where): - qfl="avg" + qfl = "avg" return sym.integral( ambient_dim=3, dim=2, - operand=(self.Dp(sigma,target=where,qfl=qfl)/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where,qfl=qfl)), + operand=(self.Dp(sigma, target=where, qfl=qfl)/self.k + + 1j*0.5*sigma_n - 1j*self.Sp(sigma, target=where, qfl=qfl)), where=where) def _scaledDPIEv_integral(self, **kwargs): - qfl="avg" + qfl = "avg" # grab densities and domain to integrate over a = kwargs['a'] @@ -341,49 +361,55 @@ class DPIEOperator: where = kwargs['where'] # define some useful common sub expressions - Sa = cse(self.S(a,where),"Sa_"+where) - Srho = cse(self.S(rho,where),"Srho_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(a),where),"Sn_cross_a_"+where) - Drho = cse(self.D(rho,where),"Drho_"+where) + # Sa = cse(self.S(a, where), "Sa_"+where) + # Srho = cse(self.S(rho, where), "Srho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) + # Drho = cse(self.D(rho, where), "Drho_"+where) return sym.integral( ambient_dim=3, dim=2, operand=( - sym.n_dot( sym.curl(self.S(a,where)),where=where) - self.k*sym.n_dot(Sn_times_rho,where=where) \ - + 1j*(self.k*sym.n_dot(Sn_cross_a,where=where) - 0.5*rho_n + self.Sp(rho,target=where,qfl=qfl)) + sym.n_dot(sym.curl(self.S(a, where)), where=where) - + self.k*sym.n_dot(Sn_times_rho, where=where) + + 1j*(self.k*sym.n_dot(Sn_cross_a, where=where) - 0.5*rho_n + + self.Sp(rho, target=where, qfl=qfl)) ), where=where) - def phi_operator(self, phi_densities): """ Integral Equation operator for obtaining scalar potential, `phi` """ # extract the densities needed to solve the system of equations - (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) # init output matvec vector for the phi density IE output = np.zeros((2*self.nobjs,), dtype=self.stype) # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): - # get nth disjoint object + # get nth disjoint object obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = 0.5*sigma0[n] + self.D(sigma,obj_n) - 1j*self.k*self.S(sigma,obj_n) - V[n] + output[n] = ( + 0.5*sigma0[n] + + self.D(sigma, obj_n) + - 1j*self.k*self.S(sigma, obj_n) - V[n] + ) - # setup equation that integrates some integral operators over the nth surface - output[self.nobjs + n] = self._scaledDPIEs_integral(sigma,sigma[0,n],where=obj_n) + # set up equation that integrates some integral operators over the + # nth surface + output[self.nobjs + n] = self._scaledDPIEs_integral(sigma, sigma[0, + n], where=obj_n) # return the resulting system of IE return output - def phi_rhs(self, phi_inc, gradphi_inc): """ The Right-Hand-Side for the Integral Equation for `phi` @@ -391,18 +417,18 @@ class DPIEOperator: # get the scalar f expression for each object f = np.zeros((self.nobjs,), dtype=self.stype) - for i in range(0,self.nobjs): + for i in range(0, self.nobjs): f[i] = -phi_inc[i] # get the Q_{j} terms inside RHS expression Q = np.zeros((self.nobjs,), dtype=self.stype) - for i in range(0,self.nobjs): - Q[i] = -sym.integral(3,2,sym.n_dot(gradphi_inc,where=self.geometry_list[i]),where=self.geometry_list[i]) + for i in range(0, self.nobjs): + Q[i] = -sym.integral(3, 2, sym.n_dot(gradphi_inc, + where=self.geometry_list[i]), where=self.geometry_list[i]) # return the resulting field return sym.join_fields(f, Q/self.k) - def a_operator(self, A_densities): """ Integral Equation operator for obtaining vector potential, `A` @@ -415,7 +441,7 @@ class DPIEOperator: output = np.zeros((4*self.nobjs,), dtype=self.stype) # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): # get the nth target geometry to have IE solved across obj_n = self.geometry_list[n] @@ -426,14 +452,17 @@ class DPIEOperator: # generate the set of equations for the vector densities, a, coupled # across the various geometries involved - output[2*n:2*(n+1)] = xyz_to_tangential(0.5*a[:,n] + L[:3] + 1j*R[:3], where=obj_n) + output[2*n:2*(n+1)] = sym.xyz_to_tangential( + 0.5*a[:, n] + L[:3] + 1j*R[:3], + where=obj_n) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved - output[(2*self.nobjs + n)] = 0.5*rho[0,n] + L[-1] + 1j*R[-1] - v[n] + output[(2*self.nobjs + n)] = 0.5*rho[0, n] + L[-1] + 1j*R[-1] - v[n] # add the equation that integrates everything out into some constant - output[3*self.nobjs + n] = self._scaledDPIEv_integral(a=a, rho=rho, rho_n=rho[0,n], where=obj_n) + output[3*self.nobjs + n] = self._scaledDPIEv_integral( + a=a, rho=rho, rho_n=rho[0, n], where=obj_n) # return output equations return output @@ -447,16 +476,18 @@ class DPIEOperator: q = np.zeros((self.nobjs,), dtype=self.stype) h = np.zeros((self.nobjs,), dtype=self.stype) f = np.zeros((2*self.nobjs,), dtype=self.stype) - for i in range(0,self.nobjs): + for i in range(0, self.nobjs): obj_n = self.geometry_list[i] - q[i] = -sym.integral(3,2,sym.n_dot(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) + q[i] = -sym.integral(3, 2, sym.n_dot(A_inc[3*i:3*(i+1)], where=obj_n), + where=obj_n) h[i] = -divA_inc[i] - f[2*i:2*(i+1)] = xyz_to_tangential(-sym.n_cross(A_inc[3*i:3*(i+1)],where=obj_n),where=obj_n) + f[2*i:2*(i+1)] = sym.xyz_to_tangential( + -sym.n_cross(A_inc[3*i:3*(i+1)], where=obj_n), where=obj_n) # define RHS for `A` integral equation system - return sym.join_fields( f, h/self.k, q ) + return sym.join_fields(f, h/self.k, q) - def subproblem_operator(self, tau_densities, alpha = 1j): + def subproblem_operator(self, tau_densities, alpha=1j): """ Integral Equation operator for obtaining sub problem solution """ @@ -468,13 +499,13 @@ class DPIEOperator: output = np.zeros((self.nobjs,), dtype=self.stype) # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): - # get nth disjoint object + # get nth disjoint object obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = 0.5*tau0[n] + self.D(tau,obj_n) - alpha*self.S(tau,obj_n) + output[n] = 0.5*tau0[n] + self.D(tau, obj_n) - alpha*self.S(tau, obj_n) # return the resulting system of IE return output @@ -490,13 +521,13 @@ class DPIEOperator: output = np.zeros((self.nobjs,), dtype=self.stype) # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): - # get nth disjoint object + # get nth disjoint object obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = sym.div(self.S(a,target=obj_n,qfl="avg")) + output[n] = sym.div(self.S(a, target=obj_n, qfl="avg")) # return the resulting system of IE return output @@ -510,9 +541,9 @@ class DPIEOperator: output = np.zeros((self.nobjs,), dtype=self.stype) # produce integral equation system over each disjoint object - for n in range(0,self.nobjs): + for n in range(0, self.nobjs): - # get nth disjoint object + # get nth disjoint object obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface @@ -521,7 +552,6 @@ class DPIEOperator: # return the resulting system of IE return output - def scalar_potential_rep(self, phi_densities, target=None, qfl=None): """ This method is a representation of the scalar potential, phi, @@ -529,10 +559,13 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) # evaluate scalar potential representation - return self.D(sigma,target,qfl=qfl) - (1j*self.k)*self.S(sigma,target,qfl=qfl) + return ( + self.D(sigma, target, qfl=qfl) + - (1j*self.k)*self.S(sigma, target, qfl=qfl) + ) def scalar_potential_constants(self, phi_densities): """ @@ -541,7 +574,7 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) # evaluate scalar potential representation return V @@ -553,10 +586,13 @@ class DPIEOperator: """ # extract the densities needed to solve the system of equations - (sigma0,sigma,V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) # evaluate scalar potential representation - return sym.grad(3,self.D(sigma,target,qfl=qfl)) - (1j*self.k)*sym.grad(3,self.S(sigma,target,qfl=qfl)) + return ( + sym.grad(3, self.D(sigma, target, qfl=qfl)) + - (1j*self.k)*sym.grad(3, self.S(sigma, target, qfl=qfl)) + ) def vector_potential_rep(self, A_densities, target=None, qfl=None): """ @@ -568,8 +604,12 @@ class DPIEOperator: (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define the vector potential representation - return (sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl)) \ - + 1j*(self.k*self.S(self.n_cross(a),target,qfl=qfl) + sym.grad(3,self.S(rho,target,qfl=qfl))) + return ( + (sym.curl(self.S(a, target, qfl=qfl)) + - self.k*self.S(self.n_times(rho), target, qfl=qfl)) + + 1j*(self.k*self.S(self.n_cross(a), target, qfl=qfl) + + sym.grad(3, self.S(rho, target, qfl=qfl))) + ) def div_vector_potential_rep(self, A_densities, target=None, qfl=None): """ @@ -581,10 +621,11 @@ class DPIEOperator: (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define the vector potential representation - return self.k*( self.D(rho,target,qfl=qfl) \ - + 1j*(sym.div(self.S(self.n_cross(a),target,qfl=qfl)) - self.k * self.S(rho,target,qfl=qfl))) + return self.k*(self.D(rho, target, qfl=qfl) + + 1j*(sym.div(self.S(self.n_cross(a), target, qfl=qfl)) + - self.k * self.S(rho, target, qfl=qfl))) - def subproblem_rep(self, tau_densities, target=None, alpha = 1j, qfl=None): + def subproblem_rep(self, tau_densities, target=None, alpha=1j, qfl=None): """ This method is a representation of the scalar potential, phi, based on the density `sigma`. @@ -594,13 +635,14 @@ class DPIEOperator: (tau0, tau) = self._extract_tau_densities(tau_densities) # evaluate scalar potential representation - return self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl) + return self.D(tau, target, qfl=qfl) - alpha*self.S(tau, target, qfl=qfl) - def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j,qfl=None): + def scattered_volume_field(self, phi_densities, A_densities, tau_densities, + target=None, alpha=1j, qfl=None): """ 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. + magnetic field. This satisfies the time-domain Maxwell's equations as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. @@ -608,20 +650,27 @@ class DPIEOperator: # extract the densities needed (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) - (sigma0,sigma, V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) (tau0, tau) = self._extract_tau_densities(tau_densities) # obtain expressions for scalar and vector potentials - A = self.vector_potential_rep(A_densities, target=target) - phi = self.scalar_potential_rep(phi_densities, target=target) + A = self.vector_potential_rep(A_densities, target=target) + # phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=qfl)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=qfl)) - H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl))) \ - + (self.k**2) * self.S(a,target,qfl=qfl) \ - - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=qfl)) \ - + 1j*self.k*sym.curl(self.S(self.n_cross(a),target,qfl=qfl)) - + E_scat = ( + 1j*self.k*A + - sym.grad(3, self.D(sigma, target, qfl=qfl)) + + 1j*self.k*sym.grad(3, self.S(sigma, target, qfl=qfl)) + ) + H_scat = ( + sym.grad(3, operand=( + self.D(tau, target, qfl=qfl) + - alpha*self.S(tau, target, qfl=qfl))) + + (self.k**2) * self.S(a, target, qfl=qfl) + - self.k * sym.curl(self.S(self.n_times(rho), target, qfl=qfl)) + + 1j*self.k*sym.curl(self.S(self.n_cross(a), target, qfl=qfl)) + ) # join the fields into a vector return sym.join_fields(E_scat, H_scat) @@ -629,21 +678,22 @@ class DPIEOperator: # }}} - # {{{ Decoupled Potential Integral Equation Operator - Based on Journal Paper + class DPIEOperatorEvanescent(DPIEOperator): r""" Decoupled Potential Integral Equation operator with PEC boundary conditions, defaults as scaled DPIE. - See https://onlinelibrary.wiley.com/doi/abs/10.1002/cpa.21585 for journal paper. + See `the journal paper + `_ for details. - Uses :math:`E(x,t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and - :math:`H(x,t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for + Uses :math:`E(x, t) = Re \lbrace E(x) \exp(-i \omega t) \rbrace` and + :math:`H(x, t) = Re \lbrace H(x) \exp(-i \omega t) \rbrace` and solves for the :math:`E(x)`, :math:`H(x)` fields using vector and scalar potentials via - the Lorenz Gauage. The DPIE formulates the problem purely in terms of the - vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, - and then backs out :math:`E(x)` and :math:`H(x)` via relationships to + the Lorenz Gauage. The DPIE formulates the problem purely in terms of the + vector and scalar potentials, :math:`\boldsymbol{A}` and :math:`\phi`, + and then backs out :math:`E(x)` and :math:`H(x)` via relationships to the vector and scalar potentials. """ @@ -652,83 +702,102 @@ class DPIEOperatorEvanescent(DPIEOperator): from sumpy.kernel import LaplaceKernel # specify the frequency variable that will be tuned - self.k = k - self.ik = 1j*k - self.stype = type(self.k) + self.k = k + self.ik = 1j*k + self.stype = type(self.k) - # specify the 3-D Helmholtz kernel - self.kernel = HelmholtzKernel(3) - self.kernel_ik = HelmholtzKernel(3, allow_evanescent=True) + # specify the 3-D Helmholtz kernel + self.kernel = HelmholtzKernel(3) + self.kernel_ik = HelmholtzKernel(3, allow_evanescent=True) self.kernel_laplace = LaplaceKernel(3) # specify a list of strings representing geometry objects - self.geometry_list = geometry_list - self.nobjs = len(geometry_list) + self.geometry_list = geometry_list + self.nobjs = len(geometry_list) def _eval_all_objects(self, density_vec, int_op, qfl="avg", k=None, kernel=None): - """ - This private method is so some input integral operator and input density can be used to - evaluate the set of locations defined by the geometry list + """This private method is so some input integral operator and input + density can be used to evaluate the set of locations defined by the + geometry list """ output = np.zeros(density_vec.shape, dtype=self.stype) (ndim, nobj) = density_vec.shape - for i in range(0,nobj): - output[:,i] = int_op(density_vec=density_vec, target=self.geometry_list[i], qfl=qfl, k=k, kernel=kernel) + for i in range(0, nobj): + output[:, i] = int_op(density_vec=density_vec, + target=self.geometry_list[i], qfl=qfl, k=k, kernel=kernel) return output def _L(self, a, rho, where): # define some useful common sub expressions - Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) - Drho = cse(self.D(rho,where),"Drho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) + Drho = cse(self.D(rho, where), "Drho_"+where) return sym.join_fields( - sym.n_cross(sym.curl(self.S(a,where)) - self.k * Sn_times_rho,where=where), + sym.n_cross( + sym.curl(self.S(a, where)) - self.k * Sn_times_rho, + where=where), Drho) def _R(self, a, rho, where): # define some useful common sub expressions - Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") - Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") - Srho = cse(self.S(Srho_ik_nest,where),"Srho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest),where),"Sn_cross_a_"+where) + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, + kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, + kernel=self.kernel_ik), "Srho_ik_nest") + Srho = cse(self.S(Srho_ik_nest, where), "Srho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), where), + "Sn_cross_a_"+where) return self.k*sym.join_fields( - sym.n_cross( self.k * Sn_cross_a + sym.grad(ambient_dim=3,operand=self.S(Srho_ik_nest,where)),where=where), - sym.div(self.S(self.n_cross(Sa_ik_nest),where)) - self.k * Srho + sym.n_cross(self.k * Sn_cross_a + sym.grad(ambient_dim=3, + operand=self.S(Srho_ik_nest, where)), where=where), + sym.div(self.S(self.n_cross(Sa_ik_nest), where)) - self.k * Srho ) def _scaledDPIEs_integral(self, sigma, sigma_n, where): - qfl="avg" + qfl = "avg" return sym.integral( ambient_dim=3, dim=2, - operand=( (self.Dp(sigma,target=where,qfl=qfl) - self.Dp(sigma,target=where,qfl=qfl,kernel=self.kernel_laplace,use_laplace=True))/self.k + 1j*0.5*sigma_n - 1j*self.Sp(sigma,target=where,qfl=qfl)), + operand=( + (self.Dp(sigma, target=where, qfl=qfl) + - self.Dp(sigma, target=where, qfl=qfl, + kernel=self.kernel_laplace, use_laplace=True)) + / self.k + + 1j*0.5*sigma_n + - 1j*self.Sp(sigma, target=where, qfl=qfl)), where=where) def _scaledDPIEv_integral(self, **kwargs): - qfl="avg" + qfl = "avg" # grab densities and domain to integrate over a = kwargs['a'] rho = kwargs['rho'] - rho_n = kwargs['rho_n'] + # rho_n = kwargs['rho_n'] where = kwargs['where'] # define some useful common sub expressions - Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") - Srho_ik = cse(self.S(rho,where,k=self.ik,kernel=self.kernel_ik),"Srho_ik"+where) - Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") - Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest),where),"Sn_cross_a_nest_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho),where),"Sn_times_rho_"+where) + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, + kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik = cse(self.S(rho, where, k=self.ik, kernel=self.kernel_ik), + "Srho_ik"+where) + Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, + kernel=self.kernel_ik), "Srho_ik_nest") + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), where), + "Sn_cross_a_nest_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) return sym.integral( ambient_dim=3, dim=2, operand=( - -self.k*sym.n_dot(Sn_times_rho,where=where) \ - + 1j*self.k*(self.k*sym.n_dot(Sn_cross_a,where=where) - 0.5*Srho_ik + self.Sp(Srho_ik_nest,target=where,qfl=qfl)) + -self.k*sym.n_dot(Sn_times_rho, where=where) + + 1j*self.k*( + self.k*sym.n_dot(Sn_cross_a, where=where) + - 0.5*Srho_ik + self.Sp(Srho_ik_nest, target=where, qfl=qfl)) ), where=where) @@ -742,12 +811,18 @@ class DPIEOperatorEvanescent(DPIEOperator): (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define some useful quantities - Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") - Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, + kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, + kernel=self.kernel_ik), "Srho_ik_nest") # define the vector potential representation - return (sym.curl(self.S(a,target,qfl=qfl)) - self.k*self.S(self.n_times(rho),target,qfl=qfl)) \ - + 1j*self.k*(self.k*self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl) + sym.grad(3,self.S(Srho_ik_nest,target,qfl=qfl))) + return ( + (sym.curl(self.S(a, target, qfl=qfl)) + - self.k*self.S(self.n_times(rho), target, qfl=qfl)) + + 1j*self.k*(self.k*self.S(self.n_cross(Sa_ik_nest), target, qfl=qfl) + + sym.grad(3, self.S(Srho_ik_nest, target, qfl=qfl))) + ) def div_vector_potential_rep(self, A_densities, target=None, qfl=None): """ @@ -759,18 +834,22 @@ class DPIEOperatorEvanescent(DPIEOperator): (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) # define some useful quantities - Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") - Srho_ik_nest = cse(self._eval_all_objects(rho,self.S, k=self.ik, kernel=self.kernel_ik),"Srho_ik_nest") + Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, + kernel=self.kernel_ik), "Sa_ik_nest") + Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, + kernel=self.kernel_ik), "Srho_ik_nest") # define the vector potential representation - return self.k*( self.D(rho,target,qfl=qfl) \ - + 1j*self.k*(sym.div(self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl)) - self.k * self.S(Srho_ik_nest,target,qfl=qfl))) + return self.k*(self.D(rho, target, qfl=qfl) + + 1j*self.k*(sym.div(self.S(self.n_cross(Sa_ik_nest), target, + qfl=qfl)) - self.k * self.S(Srho_ik_nest, target, qfl=qfl))) - def scattered_volume_field(self, phi_densities, A_densities, tau_densities, target=None, alpha=1j,qfl=None): + def scattered_volume_field(self, phi_densities, A_densities, tau_densities, + target=None, alpha=1j, qfl=None): """ 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. + magnetic field. This satisfies the time-domain Maxwell's equations as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. @@ -778,23 +857,33 @@ class DPIEOperatorEvanescent(DPIEOperator): # extract the densities needed (a0, a, rho0, rho, v) = self._extract_a_densities(A_densities) - (sigma0,sigma, V) = self._extract_phi_densities(phi_densities) + (sigma0, sigma, V) = self._extract_phi_densities(phi_densities) (tau0, tau) = self._extract_tau_densities(tau_densities) # obtain expressions for scalar and vector potentials - Sa_ik_nest = self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik) - A = self.vector_potential_rep(A_densities, target=target) - phi = self.scalar_potential_rep(phi_densities, target=target) + Sa_ik_nest = self._eval_all_objects(a, self.S, k=self.ik, + kernel=self.kernel_ik) + A = self.vector_potential_rep(A_densities, target=target) + # phi = self.scalar_potential_rep(phi_densities, target=target) # evaluate the potential form for the electric and magnetic fields - E_scat = 1j*self.k*A - sym.grad(3, self.D(sigma,target,qfl=qfl)) + 1j*self.k*sym.grad(3, self.S(sigma,target,qfl=qfl)) - H_scat = sym.grad(3,operand=(self.D(tau,target,qfl=qfl) - alpha*self.S(tau,target,qfl=qfl))) \ - + (self.k**2) * self.S(a,target,qfl=qfl) \ - - self.k * sym.curl(self.S(self.n_times(rho),target,qfl=qfl)) \ - + 1j*(self.k**2)*sym.curl(self.S(self.n_cross(Sa_ik_nest),target,qfl=qfl)) - + E_scat = ( + 1j*self.k*A + - sym.grad(3, self.D(sigma, target, qfl=qfl)) + + 1j*self.k*sym.grad(3, self.S(sigma, target, qfl=qfl))) + H_scat = ( + sym.grad(3, operand=( + self.D(tau, target, qfl=qfl) + - alpha*self.S(tau, target, qfl=qfl))) + + (self.k**2) * self.S(a, target, qfl=qfl) + - self.k * sym.curl(self.S(self.n_times(rho), target, qfl=qfl)) + + 1j*(self.k**2)*sym.curl(self.S(self.n_cross(Sa_ik_nest), + target, qfl=qfl)) + ) # join the fields into a vector return sym.join_fields(E_scat, H_scat) # }}} + +# vim: foldmethod=marker -- GitLab From 1f9aad0da03369c793e526f40816fd1a51c0d69f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Jul 2018 15:18:25 -0500 Subject: [PATCH 46/59] Flake8-clean the DPIE tests --- test/test_maxwell_dpie.py | 560 +++++++++++++++++++++++--------------- 1 file changed, 347 insertions(+), 213 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 27d0a9d0..9e650e15 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -39,6 +39,8 @@ from sumpy.point_calculus import CalculusPatch, frequency_domain_maxwell from sumpy.tools import vector_from_device from pytential.target import PointsTarget from meshmode.mesh.processing import find_bounding_box +from pytools.convergence import EOCRecorder +from pytential.solve import gmres import logging logger = logging.getLogger(__name__) @@ -112,7 +114,7 @@ class RoundedCubeTestCase(MaxwellTestCase): mesh = affine_map(mesh, b=np.array([-0.5, -0.5, -0.5])) mesh = affine_map(mesh, A=np.eye(3)*2) - # now centered at origin and extends to -1,1 + # now centered at origin and extends to -1, 1 # Flip elements--gmsh generates inside-out geometry. return perform_flips(mesh, np.ones(mesh.nelements)) @@ -158,7 +160,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): "-string", "Mesh.CharacteristicLengthMax = %g;" % resolution]) - # now centered at origin and extends to -1,1 + # now centered at origin and extends to -1, 1 # Flip elements--gmsh generates inside-out geometry. from meshmode.mesh.processing import perform_flips @@ -216,8 +218,6 @@ class EHField(object): return self.field[3:] -# {{{ driver - @pytest.mark.parametrize("case", [ #tc_int, tc_ext, @@ -251,16 +251,18 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # import some functionality from maxwell into this # local scope environment - import pytential.symbolic.pde.maxwell as mw - import pytential.symbolic.pde.maxwell.dpie as mw_dpie - + import pytential.symbolic.pde.maxwell as mw + import pytential.symbolic.pde.maxwell.dpie as mw_dpie + # initialize the DPIE operator based on the geometry list dpie = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) # specify some symbolic variables that will be used # in the process to solve integral equations for the DPIE - phi_densities = sym.make_sym_vector("phi_densities", dpie.num_scalar_potential_densities()) - A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities()) + phi_densities = sym.make_sym_vector("phi_densities", + dpie.num_scalar_potential_densities()) + A_densities = sym.make_sym_vector("A_densities", + dpie.num_vector_potential_densities()) tau_densities = sym.make_sym_vector("tau_densities", dpie.num_distinct_objects()) # get test source locations from the passed in case's queue @@ -270,45 +272,51 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) calc_patch_tgt = PointsTarget(cl.array.to_device(queue, calc_patch.points)) - # define a random number generator based on OpenCL - rng = cl.clrandom.PhiloxGenerator(cl_ctx, seed=12) - # define some parameters for the incident wave # direction for the wave - u_dir = np.array([1, 0, 0],dtype=np.complex128) + u_dir = np.array([1, 0, 0], dtype=np.complex128) # polarization vector - Ep = np.array([0, 1, 1],dtype=np.complex128) + Ep = np.array([0, 1, 1], dtype=np.complex128) # define symbolic vectors for use uvar = sym.make_sym_vector("u", 3) - Evar = sym.make_sym_vector("Ep",3) + Evar = sym.make_sym_vector("Ep", 3) - # define functions that can be used to generate incident fields for an input discretization + # define functions that can be used to generate incident fields for an + # input discretization # define potentials based on incident plane wave def get_incident_plane_wave_EHField(tgt): - return bind((test_source,tgt),mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, omega=dpie.k))(queue,u=u_dir,Ep=Ep,**knl_kwargs) + return bind((test_source, tgt), + mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, + omega=dpie.k))(queue, u=u_dir, Ep=Ep, **knl_kwargs) # get the gradphi_inc field evaluated at some source locations def get_incident_gradphi(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_gradphi(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) + return bind(objects, mw.get_sym_maxwell_planewave_gradphi(u=uvar, + Ep=Evar, k=dpie.k, where=target))(queue, u=u_dir, + Ep=Ep, **knl_kwargs) # get the incident plane wave div(A) def get_incident_divA(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) + return bind(objects, mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, + k=dpie.k, where=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) - # method to get vector potential and scalar potential for incident + # method to get vector potential and scalar potential for incident # E-M fields def get_incident_potentials(objects, target=None): - return bind(objects,mw.get_sym_maxwell_planewave_potentials(u=uvar, Ep=Evar, k=dpie.k,where=target))(queue,u=u_dir,Ep=Ep,**knl_kwargs) + return bind(objects, mw.get_sym_maxwell_planewave_potentials(u=uvar, + Ep=Evar, k=dpie.k, where=target))(queue, u=u_dir, + Ep=Ep, **knl_kwargs) # define a smooth function to represent the density - def dummy_density(omega = 1.0, where=None): + def dummy_density(omega=1.0, where=None): x = sym.nodes(3, where).as_vector() - return sym.sin(omega*sym.n_dot(x,where)) + return sym.sin(omega*sym.n_dot(x, where)) # get the Electromagnetic field evaluated at the target calculus patch - pde_test_inc = EHField(vector_from_device(queue, get_incident_plane_wave_EHField(calc_patch_tgt))) + pde_test_inc = EHField(vector_from_device(queue, + get_incident_plane_wave_EHField(calc_patch_tgt))) # compute residuals of incident field at source points source_maxwell_resids = [ @@ -323,32 +331,35 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # }}} + # {{{ test the auxiliary problem + + # test that the aux problem is capable of computing the desired derivatives + # of an appropriate input field - # {{{ Test the auxiliary problem is capable of computing the desired derivatives of an appropriate input field - test_auxiliary = False + test_auxiliary = False if test_auxiliary: # import a bunch of stuff that will be useful - from pytools.convergence import EOCRecorder from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - # define method to get locations to evaluate representation def epsilon_off_boundary(where=None, epsilon=1e-4): x = sym.nodes(3, where).as_vector() - return x + sym.normal(3,2,where).as_vector()*epsilon + return x + sym.normal(3, 2, where).as_vector()*epsilon + + # loop through the case's resolutions and compute the scattered field + # solution - # # loop through the case's resolutions and compute the scattered field solution deriv_error = [] for resolution in case.resolutions: # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) + scat_mesh = case.get_mesh(resolution, case.target_order) + # observation_mesh = case.get_observation_mesh(case.target_order) # define the pre-scattered discretization pre_scat_discr = Discretization( @@ -362,18 +373,24 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), fmm_backend=case.fmm_backend ).with_refinement(_expansion_disturbance_tolerance=0.05) # define the geometry dictionary - geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} + geom_map = { + "obj0": qbx0, + "obj0t": qbx0.density_discr, + "scat": qbx0.density_discr} # define points to evaluate the gradient at - tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1.0))(queue)) + tgt_n = PointsTarget(bind(geom_map, + epsilon_off_boundary(where='obj0', epsilon=1.0))(queue)) geom_map['tgt'] = tgt_n - # define the quantity that will have a derivative taken of it and its associated derivative + # define the quantity that will have a derivative taken of it and + # its associated derivative def getTestFunction(where=None): z = sym.nodes(3, where).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") @@ -383,16 +400,20 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): def getTestGradient(where=None): z = sym.nodes(3, where).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") - grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - 1.0/sym.sqrt(z2))/(4*np.pi*z2) + grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - + 1.0/sym.sqrt(z2))/(4*np.pi*z2) return grad_g # compute output gradient evaluated at the desired object - tgrad = bind(geom_map,getTestGradient(where="tgt"))(queue,**knl_kwargs) - test_func_d = vector_from_device(queue,tgrad) + tgrad = bind(geom_map, getTestGradient(where="tgt"))(queue, **knl_kwargs) + test_func_d = vector_from_device(queue, tgrad) # define the problem that will be solved - test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) - test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs_func(function=getTestFunction))(queue,**knl_kwargs) + test_tau_op = bind(geom_map, + dpie0.subproblem_operator(tau_densities=tau_densities)) + test_tau_rhs = bind(geom_map, + dpie0.subproblem_rhs_func(function=getTestFunction))(queue, + **knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -401,50 +422,51 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): hard_failure=True, stall_iterations=50, no_progress_factor=1.05) - # get the GMRES functionality - from pytential.solve import gmres - subprob_result = gmres( - test_tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), + test_tau_op.scipy_op(queue, "tau_densities", np.complex128, + domains=dpie0.get_subproblem_domain_list(), + **knl_kwargs), test_tau_rhs, **gmres_settings) dummy_tau = subprob_result.solution # compute the error between the associated derivative quantities - tgrad = bind(geom_map,sym.grad(3,dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt')))(queue,tau_densities=dummy_tau,**knl_kwargs) - approx_d = vector_from_device(queue,tgrad) + tgrad = bind(geom_map, sym.grad(3, + dpie0.subproblem_rep(tau_densities=tau_densities, + target='tgt')))(queue, tau_densities=dummy_tau, + **knl_kwargs) + approx_d = vector_from_device(queue, tgrad) err = calc_patch.norm(test_func_d - approx_d, np.inf) # append error to the error list deriv_error.append(err) print("Auxiliary Error Results:") - for n in range(0,len(deriv_error)): - print("Case {0}: {1}".format(n+1,deriv_error[n])) + for n in range(0, len(deriv_error)): + print("Case {0}: {1}".format(n+1, deriv_error[n])) # }}} + # {{{ Test the representations - # # {{{ Test the representations test_representations = False if test_representations: # import a bunch of stuff that will be useful - from pytools.convergence import EOCRecorder from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - - # # loop through the case's resolutions and compute the scattered field solution + # loop through the case's resolutions and compute the scattered field + # solution rep_error = [] for resolution in case.resolutions: # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) + scat_mesh = case.get_mesh(resolution, case.target_order) + # observation_mesh = case.get_observation_mesh(case.target_order) # define the pre-scattered discretization pre_scat_discr = Discretization( @@ -458,24 +480,36 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), fmm_backend=case.fmm_backend ).with_refinement(_expansion_disturbance_tolerance=0.05) # define the geometry dictionary - geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} - dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) - dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) - v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) - s = 0*rng.normal(queue, (), dtype=np.float64) + geom_map = { + "obj0": qbx0, + "obj0t": qbx0.density_discr, + "scat": qbx0.density_discr + } + + dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(), + dtype=dpie0.stype) + dummy_A = np.array([None]*dpie0.num_vector_potential_densities(), + dtype=dpie0.stype) + + # v = rng.normal(queue, (qbx0.density_discr.nnodes,), dtype=np.float64) + # s = 0*rng.normal(queue, (), dtype=np.float64) n1 = len(dummy_phi) n2 = len(dummy_A) - for i in range(0,n1): - dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) - for i in range(0,n2): - dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) - test_tau_op= bind(geom_map,dpie0.subproblem_operator(tau_densities=tau_densities)) - test_tau_rhs= bind(geom_map,dpie0.subproblem_rhs(A_densities=A_densities))(queue,A_densities=dummy_A,**knl_kwargs) + for i in range(0, n1): + dummy_phi[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + for i in range(0, n2): + dummy_A[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + test_tau_op = bind(geom_map, + dpie0.subproblem_operator(tau_densities=tau_densities)) + test_tau_rhs = bind(geom_map, + dpie0.subproblem_rhs(A_densities=A_densities))(queue, + A_densities=dummy_A, **knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -485,36 +519,43 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): stall_iterations=50, no_progress_factor=1.05) # get the GMRES functionality - from pytential.solve import gmres subprob_result = gmres( - test_tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), + test_tau_op.scipy_op(queue, "tau_densities", np.complex128, + domains=dpie0.get_subproblem_domain_list(), + **knl_kwargs), test_tau_rhs, **gmres_settings) - dummy_tau = subprob_result.solution + dummy_tau = subprob_result.solution - sym_repr0 = dpie.scattered_volume_field(phi_densities,A_densities,tau_densities,target='tgt') + sym_repr0 = dpie.scattered_volume_field(phi_densities, A_densities, + tau_densities, target='tgt') def eval_test_repr_at(tgt): map = geom_map map['tgt'] = tgt - return bind(map, sym_repr0)(queue, phi_densities=dummy_phi, A_densities=dummy_A, tau_densities=dummy_tau, **knl_kwargs) + return bind(map, sym_repr0)(queue, phi_densities=dummy_phi, + A_densities=dummy_A, tau_densities=dummy_tau, + **knl_kwargs) - pde_test_repr = EHField(vector_from_device(queue, eval_test_repr_at(calc_patch_tgt))) + pde_test_repr = EHField(vector_from_device(queue, + eval_test_repr_at(calc_patch_tgt))) maxwell_residuals = [ - calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) - for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] + calc_patch.norm(x, np.inf) / + calc_patch.norm(pde_test_repr.e, np.inf) + for x in frequency_domain_maxwell(calc_patch, + pde_test_repr.e, pde_test_repr.h, case.k)] print("Maxwell residuals:", maxwell_residuals) rep_error.append(maxwell_residuals) print("Representation Error Results:") - for n in range(0,len(rep_error)): - print("Case {0}: {1}".format(n+1,rep_error[n])) + for n in range(0, len(rep_error)): + print("Case {0}: {1}".format(n+1, rep_error[n])) - # #}}} + # }}} + # {{{ test the operators - # # {{{ Test the operators test_operators = False if test_operators: @@ -524,23 +565,22 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # define method to get locations to evaluate representation def epsilon_off_boundary(where=None, epsilon=1e-4): x = sym.nodes(3, where).as_vector() - return x + sym.normal(3,2,where).as_vector()*epsilon + return x + sym.normal(3, 2, where).as_vector()*epsilon # import a bunch of stuff that will be useful - from pytools.convergence import EOCRecorder from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - - # loop through the case's resolutions and compute the scattered field solution + # loop through the case's resolutions and compute the scattered field + # solution for resolution in case.resolutions: # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) + scat_mesh = case.get_mesh(resolution, case.target_order) + # observation_mesh = case.get_observation_mesh(case.target_order) # define the pre-scattered discretization pre_scat_discr = Discretization( @@ -554,18 +594,27 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), fmm_backend=case.fmm_backend ).with_refinement(_expansion_disturbance_tolerance=0.05) # define the geometry dictionary - geom_map = {"obj0":qbx0, "obj0t":qbx0.density_discr, "scat":qbx0.density_discr} - - # compute off-boundary locations that the representation will need to be evaluated at - tgt_n = PointsTarget(bind(geom_map, epsilon_off_boundary(where='obj0',epsilon=1e-4))(queue)) + geom_map = { + "obj0": qbx0, + "obj0t": qbx0.density_discr, + "scat": qbx0.density_discr + } + + # compute off-boundary locations that the representation will need + # to be evaluated at + tgt_n = PointsTarget( + bind(geom_map, + epsilon_off_boundary(where='obj0', epsilon=1e-4))(queue)) geom_map['tgt'] = tgt_n - # define a dummy density, specifically to be used for the vector potential A densities + # define a dummy density, specifically to be used for the vector + # potential A densities x, y, z = qbx0.density_discr.nodes().with_queue(queue) m = cl.clmath @@ -586,66 +635,99 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): ])) # redefine a_densities - #A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities2()) + #A_densities = sym.make_sym_vector("A_densities", dpie.num_vector_potential_densities2()) # noqa # init random dummy densities for the vector and scalar potentials - dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(),dtype=dpie0.stype) - dummy_A = np.array([None]*dpie0.num_vector_potential_densities(),dtype=dpie0.stype) - dummy_tau = np.array([None]*dpie0.num_distinct_objects(),dtype=dpie0.stype) - - # Compute zero scalar for use in extra constants that are usually solved for in operators + dummy_phi = np.array([None]*dpie0.num_scalar_potential_densities(), + dtype=dpie0.stype) + dummy_A = np.array([None]*dpie0.num_vector_potential_densities(), + dtype=dpie0.stype) + dummy_tau = np.array([None]*dpie0.num_distinct_objects(), + dtype=dpie0.stype) + + # compute zero scalar for use in extra constants that are usually + # solved for in operators n1 = len(dummy_phi) n2 = len(dummy_A) n3 = len(dummy_tau) - for i in range(0,n1): + for i in range(0, n1): if i < (n1-1): - dummy_phi[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + dummy_phi[i] = bind(geom_map, dummy_density(where='obj0'))(queue) else: dummy_phi[i] = 0.0 - for i in range(0,n2): + for i in range(0, n2): if i < 2: dummy_A[i] = density[i] elif i < (n2-1): - dummy_A[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + dummy_A[i] = bind(geom_map, dummy_density(where='obj0'))(queue) else: dummy_A[i] = 0.0 - for i in range(0,n3): - dummy_tau[i] = bind(geom_map,dummy_density(where='obj0'))(queue) + for i in range(0, n3): + dummy_tau[i] = bind(geom_map, dummy_density(where='obj0'))(queue) # check that the scalar density operator and representation are similar def vector_op_transform(vec_op_out): a = sym.tangential_to_xyz(vec_op_out[:2], where='obj0') - return sym.join_fields(a,vec_op_out[2:]) + return sym.join_fields(a, vec_op_out[2:]) scalar_op = dpie0.phi_operator(phi_densities=phi_densities) - #vector_op = vector_op_transform(dpie0.a_operator0(A_densities=A_densities)[:-1]) - vector_op = vector_op_transform(dpie0.a_operator(A_densities=A_densities)) + #vector_op = vector_op_transform(dpie0.a_operator0(A_densities=A_densities)[:-1]) # noqa + vector_op = vector_op_transform( + dpie0.a_operator(A_densities=A_densities)) #vector_op = dpie0.a_operator2(A_densities=A_densities)[:-1] tau_op = dpie0.subproblem_operator(tau_densities=tau_densities) # evaluate operators at the dummy densities - scalar_op_eval = vector_from_device(queue,bind(geom_map, scalar_op)(queue, phi_densities=dummy_phi, **knl_kwargs)) - vector_op_eval = vector_from_device(queue,bind(geom_map, vector_op)(queue, A_densities=dummy_A, **knl_kwargs)) - tau_op_eval = vector_from_device(queue,bind(geom_map, tau_op)(queue, tau_densities=dummy_tau, **knl_kwargs)) + scalar_op_eval = vector_from_device(queue, + bind(geom_map, scalar_op)( + queue, phi_densities=dummy_phi, **knl_kwargs)) + vector_op_eval = vector_from_device(queue, + bind(geom_map, vector_op)( + queue, A_densities=dummy_A, **knl_kwargs)) + tau_op_eval = vector_from_device(queue, + bind(geom_map, tau_op)( + queue, tau_densities=dummy_tau, **knl_kwargs)) # define the vector operator equivalent representations #def vec_op_repr(A_densities, target): - # return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep0(A_densities=A_densities, target=target),where='obj0'), - # dpie0.div_vector_potential_rep0(A_densities=A_densities, target=target)/dpie0.k) + # return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep0(A_densities=A_densities, target=target), where='obj0'), # noqa: E501 + # dpie0.div_vector_potential_rep0(A_densities=A_densities, target=target)/dpie0.k) # noqa: E501 def vec_op_repr(A_densities, target): - return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep(A_densities=A_densities, target=target),where='obj0'), - dpie0.div_vector_potential_rep(A_densities=A_densities, target=target)/dpie0.k) - - scalar_rep_eval = vector_from_device(queue,bind(geom_map, dpie0.scalar_potential_rep(phi_densities=phi_densities, target='tgt'))(queue, phi_densities=dummy_phi, **knl_kwargs)) - vector_rep_eval = vector_from_device(queue,bind(geom_map, vec_op_repr(A_densities=A_densities,target='tgt'))(queue, A_densities=dummy_A, **knl_kwargs)) - tau_rep_eval = vector_from_device(queue,bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities,target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs)) + return sym.join_fields( + sym.n_cross( + dpie0.vector_potential_rep( + A_densities=A_densities, + target=target), + where='obj0'), + dpie0.div_vector_potential_rep( + A_densities=A_densities, + target=target)/dpie0.k) + + scalar_rep_eval = vector_from_device(queue, bind(geom_map, + dpie0.scalar_potential_rep(phi_densities=phi_densities, + target='tgt'))(queue, phi_densities=dummy_phi, + **knl_kwargs)) + vector_rep_eval = vector_from_device(queue, bind(geom_map, + vec_op_repr(A_densities=A_densities, target='tgt'))(queue, + A_densities=dummy_A, **knl_kwargs)) + tau_rep_eval = vector_from_device(queue, bind(geom_map, + dpie0.subproblem_rep(tau_densities=tau_densities, + target='tgt'))(queue, tau_densities=dummy_tau, + **knl_kwargs)) + + axyz = sym.tangential_to_xyz(density_sym, where='obj0') - - axyz = sym.tangential_to_xyz(density_sym,where='obj0') def nxcurlS0(qbx_forced_limit): - return sym.n_cross(sym.curl(dpie0.S(axyz.reshape(3,1),target='obj0t',qfl=qbx_forced_limit)),where='obj0') - test_op_err = vector_from_device(queue,bind(geom_map, 0.5*axyz + nxcurlS0("avg") - nxcurlS0(+1))(queue,density=density,**knl_kwargs)) + return sym.n_cross(sym.curl(dpie0.S(axyz.reshape(3, 1), + target='obj0t', qfl=qbx_forced_limit)), where='obj0') + + test_op_err = vector_from_device(queue, + bind( + geom_map, + 0.5*axyz + + nxcurlS0("avg") - nxcurlS0(+1) + )(queue, density=density, **knl_kwargs)) from sumpy.kernel import LaplaceKernel knl = LaplaceKernel(3) @@ -655,15 +737,21 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): def nxcurlS(qbx_forced_limit): - return sym.n_cross(sym.curl(sym.S( - knl, - sym.cse(sym.tangential_to_xyz(density_sym, where='obj0'), "jxyz"), - k=dpie0.k, - qbx_forced_limit=qbx_forced_limit,source='obj0', target='obj0t')),where='obj0') + return sym.n_cross( + sym.curl(sym.S( + knl, + sym.cse( + sym.tangential_to_xyz(density_sym, where='obj0'), + "jxyz"), + k=dpie0.k, + qbx_forced_limit=qbx_forced_limit, + source='obj0', target='obj0t')), + where='obj0') jump_identity_sym = ( nxcurlS(+1) - - (nxcurlS("avg") + 0.5*sym.tangential_to_xyz(density_sym,where='obj0'))) + - (nxcurlS("avg") + + 0.5*sym.tangential_to_xyz(density_sym, where='obj0'))) bound_jump_identity = bind(geom_map, jump_identity_sym) jump_identity = bound_jump_identity(queue, density=density, **knl_kwargs) @@ -671,35 +759,36 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): err = (norm(qbx0, queue, jump_identity, np.inf)) print("ERROR", qbx0.h_max, err) - # compute the error between the operator values and the representation values - def error_diff(u,v): - return np.linalg.norm(u-v,np.inf) - error_v = [error_diff(scalar_op_eval[0],scalar_rep_eval), - error_diff(vector_op_eval[0],vector_rep_eval[0]), - error_diff(vector_op_eval[1],vector_rep_eval[1]), - error_diff(vector_op_eval[2],vector_rep_eval[2]), - error_diff(vector_op_eval[3],vector_rep_eval[3]), - error_diff(tau_op_eval[0],tau_rep_eval), - np.linalg.norm(test_op_err[0],np.inf), - np.linalg.norm(test_op_err[1],np.inf), - np.linalg.norm(test_op_err[2],np.inf)] + # compute the error between the operator values and the + # representation values + + def error_diff(u, v): + return np.linalg.norm(u-v, np.inf) + error_v = [error_diff(scalar_op_eval[0], scalar_rep_eval), + error_diff(vector_op_eval[0], vector_rep_eval[0]), + error_diff(vector_op_eval[1], vector_rep_eval[1]), + error_diff(vector_op_eval[2], vector_rep_eval[2]), + error_diff(vector_op_eval[3], vector_rep_eval[3]), + error_diff(tau_op_eval[0], tau_rep_eval), + np.linalg.norm(test_op_err[0], np.inf), + np.linalg.norm(test_op_err[1], np.inf), + np.linalg.norm(test_op_err[2], np.inf)] op_error.append(error_v) # print the resulting error results print("Operator Error Results:") - for n in range(0,len(op_error)): - print("Case {0}: {1}".format(n+1,op_error[n])) + for n in range(0, len(op_error)): + print("Case {0}: {1}".format(n+1, op_error[n])) - # #}}} + # }}} + # {{{ solve for the scattered field - # {{{ Solve for the scattered field solve_scattered_field = True if solve_scattered_field: loc_sign = -1 if case.is_interior else +1 # import a bunch of stuff that will be useful - from pytools.convergence import EOCRecorder from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -707,10 +796,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder # setup an EOC Recorder - eoc_rec_repr_maxwell = EOCRecorder() - eoc_pec_bc = EOCRecorder() - eoc_rec_e = EOCRecorder() - eoc_rec_h = EOCRecorder() + eoc_rec_repr_maxwell = EOCRecorder() + eoc_pec_bc = EOCRecorder() def frequency_domain_gauge_condition(cpatch, A, phi, k): # define constants used for the computation @@ -726,17 +813,18 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # return the residual for the gauge condition return resid_gauge_cond - def gauge_check(divA,phi,k): + def gauge_check(divA, phi, k): return divA - 1j*k*phi - # loop through the case's resolutions and compute the scattered field solution + # loop through the case's resolutions and compute the scattered field + # solution gauge_err = [] maxwell_err = [] for resolution in case.resolutions: # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - observation_mesh = case.get_observation_mesh(case.target_order) + scat_mesh = case.get_mesh(resolution, case.target_order) + # observation_mesh = case.get_observation_mesh(case.target_order) # define the pre-scattered discretization pre_scat_discr = Discretization( @@ -748,21 +836,27 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder(case.fmm_tolerance), + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), fmm_backend=case.fmm_backend ).with_refinement(_expansion_disturbance_tolerance=0.05) # define the geometry dictionary #geom_map = {"g0": qbx} - geom_map = {"obj0":qbx, "obj0t":qbx.density_discr, "scat":qbx.density_discr} + geom_map = { + "obj0": qbx, + "obj0t": qbx.density_discr, + "scat": qbx.density_discr + } # get the maximum mesh element edge length h_max = qbx.h_max # define the scattered and observation discretization - scat_discr = qbx.density_discr - obs_discr = Discretization(cl_ctx, observation_mesh, - InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + scat_discr = qbx.density_discr + # obs_discr = Discretization( + # cl_ctx, observation_mesh, + # InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # get the incident field at the scatter and observation locations #inc_EM_field_scat = EHField(eval_inc_field_at(scat_discr)) @@ -772,25 +866,34 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ solve the system of integral equations inc_A = sym.make_sym_vector("inc_A", 3) - inc_phi = sym.make_sym_vector("inc_phi",1) - inc_divA = sym.make_sym_vector("inc_divA",1) + inc_phi = sym.make_sym_vector("inc_phi", 1) + inc_divA = sym.make_sym_vector("inc_divA", 1) inc_gradPhi = sym.make_sym_vector("inc_gradPhi", 3) # get the incident fields used for boundary conditions - (phi_inc, A_inc) = get_incident_potentials(geom_map,'scat') - inc_divA_scat = get_incident_divA(geom_map,'scat') - inc_gradPhi_scat = get_incident_gradphi(geom_map,'scat') + (phi_inc, A_inc) = get_incident_potentials(geom_map, 'scat') + inc_divA_scat = get_incident_divA(geom_map, 'scat') + inc_gradPhi_scat = get_incident_gradphi(geom_map, 'scat') # check that the boundary conditions satisfy gauge condition - resid = bind(geom_map,gauge_check(inc_divA, inc_phi, dpie.k))(queue,inc_divA=inc_divA_scat,inc_phi=phi_inc,**knl_kwargs) + # resid = bind(geom_map, gauge_check(inc_divA, inc_phi, + # dpie.k))(queue, inc_divA=inc_divA_scat, inc_phi=phi_inc, + # **knl_kwargs) # setup operators that will be solved - phi_op = bind(geom_map,dpie.phi_operator(phi_densities=phi_densities)) - A_op = bind(geom_map,dpie.a_operator(A_densities=A_densities)) + phi_op = bind(geom_map, dpie.phi_operator(phi_densities=phi_densities)) + A_op = bind(geom_map, dpie.a_operator(A_densities=A_densities)) + + # setup the RHS with provided data so we can solve for density + # values across the domain - # setup the RHS with provided data so we can solve for density values across the domain - phi_rhs = bind(geom_map,dpie.phi_rhs(phi_inc=inc_phi,gradphi_inc=inc_gradPhi))(queue,inc_phi=phi_inc,inc_gradPhi=inc_gradPhi_scat,**knl_kwargs) - A_rhs = bind(geom_map,dpie.a_rhs(A_inc=inc_A,divA_inc=inc_divA))(queue,inc_A=A_inc,inc_divA=inc_divA_scat,**knl_kwargs) + phi_rhs = bind(geom_map, dpie.phi_rhs(phi_inc=inc_phi, + gradphi_inc=inc_gradPhi))(queue, inc_phi=phi_inc, + inc_gradPhi=inc_gradPhi_scat, **knl_kwargs) + A_rhs = bind(geom_map, dpie.a_rhs(A_inc=inc_A, + divA_inc=inc_divA))( + queue, inc_A=A_inc, inc_divA=inc_divA_scat, + **knl_kwargs) # set GMRES settings for solving gmres_settings = dict( @@ -799,26 +902,32 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): hard_failure=True, stall_iterations=50, no_progress_factor=1.05) - # get the GMRES functionality - from pytential.solve import gmres - # solve for the scalar potential densities gmres_result = gmres( - phi_op.scipy_op(queue, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(),**knl_kwargs), - phi_rhs, **gmres_settings) + phi_op.scipy_op(queue, "phi_densities", + np.complex128, + domains=dpie.get_scalar_domain_list(), + **knl_kwargs), + phi_rhs, **gmres_settings) phi_dens = gmres_result.solution # solve for the vector potential densities gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), + A_op.scipy_op(queue, "A_densities", np.complex128, + domains=dpie.get_vector_domain_list(), **knl_kwargs), A_rhs, **gmres_settings) A_dens = gmres_result.solution # solve sub problem for sigma densities - tau_op= bind(geom_map,dpie.subproblem_operator(tau_densities=tau_densities)) - tau_rhs= bind(geom_map,dpie.subproblem_rhs(A_densities=A_densities))(queue,A_densities=A_dens,**knl_kwargs) + tau_op = bind(geom_map, + dpie.subproblem_operator(tau_densities=tau_densities)) + tau_rhs = bind(geom_map, + dpie.subproblem_rhs(A_densities=A_densities))(queue, + A_densities=A_dens, **knl_kwargs) gmres_result = gmres( - tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie.get_subproblem_domain_list(), **knl_kwargs), + tau_op.scipy_op(queue, "tau_densities", np.complex128, + domains=dpie.get_subproblem_domain_list(), + **knl_kwargs), tau_rhs, **gmres_settings) tau_dens = gmres_result.solution @@ -826,32 +935,46 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): def eval_potentials(tgt): tmap = geom_map tmap['tgt'] = tgt - phi = vector_from_device(queue,bind(tmap, dpie.scalar_potential_rep(phi_densities=phi_densities,target='tgt'))(queue, phi_densities=phi_dens, **knl_kwargs)) - Axyz = vector_from_device(queue,bind(tmap, dpie.vector_potential_rep(A_densities=A_densities,target='tgt'))(queue, A_densities=A_dens, **knl_kwargs)) - return (phi,Axyz) - - (phi,A) = eval_potentials(calc_patch_tgt) - gauge_residual = frequency_domain_gauge_condition(calc_patch, A, phi, case.k) - err = calc_patch.norm(gauge_residual,np.inf) + phi = vector_from_device(queue, bind(tmap, + dpie.scalar_potential_rep(phi_densities=phi_densities, + target='tgt'))(queue, phi_densities=phi_dens, + **knl_kwargs)) + Axyz = vector_from_device(queue, bind(tmap, + dpie.vector_potential_rep(A_densities=A_densities, + target='tgt'))(queue, A_densities=A_dens, + **knl_kwargs)) + return (phi, Axyz) + + (phi, A) = eval_potentials(calc_patch_tgt) + gauge_residual = frequency_domain_gauge_condition( + calc_patch, A, phi, case.k) + err = calc_patch.norm(gauge_residual, np.inf) gauge_err.append(err) - # }}} # {{{ volume eval - sym_repr = dpie.scattered_volume_field(phi_densities,A_densities,tau_densities,target='tgt') + sym_repr = dpie.scattered_volume_field( + phi_densities, A_densities, tau_densities, target='tgt') def eval_repr_at(tgt): map = geom_map map['tgt'] = tgt - return bind(map, sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, tau_densities=tau_dens, **knl_kwargs) + return bind(map, sym_repr)(queue, phi_densities=phi_dens, + A_densities=A_dens, tau_densities=tau_dens, + **knl_kwargs) - pde_test_repr = EHField(vector_from_device(queue, eval_repr_at(calc_patch_tgt))) + pde_test_repr = EHField(vector_from_device(queue, + eval_repr_at(calc_patch_tgt))) maxwell_residuals = [ - calc_patch.norm(x, np.inf) / calc_patch.norm(pde_test_repr.e, np.inf) - for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] + calc_patch.norm(x, np.inf) / + calc_patch.norm(pde_test_repr.e, np.inf) + for x in frequency_domain_maxwell( + calc_patch, pde_test_repr.e, + pde_test_repr.h, case.k)] + print("Maxwell residuals:", maxwell_residuals) maxwell_err.append(maxwell_residuals) @@ -863,33 +986,44 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): def scalar_pot_PEC_residual(phi, inc_phi, where=None): V = dpie.scalar_potential_constants(phi_densities=phi) - return dpie.scalar_potential_rep(phi_densities=phi,target=where, qfl=loc_sign) + inc_phi - V[0] + return dpie.scalar_potential_rep( + phi_densities=phi, target=where, qfl=loc_sign + ) + inc_phi - V[0] def vector_pot_PEC_residual(a_densities, inc_a, where=None): - return sym.n_cross( dpie.vector_potential_rep(A_densities=a_densities, target=where, qfl=loc_sign) + inc_a, where=where) + return sym.n_cross( + dpie.vector_potential_rep( + A_densities=a_densities, target=where, qfl=loc_sign) + + inc_a, where=where) - phi_pec_bc_resid = scalar_pot_PEC_residual(phi_densities, inc_phi, where="obj0") - A_pec_bc_resid = vector_pot_PEC_residual(A_densities, inc_A, where="obj0") + phi_pec_bc_resid = scalar_pot_PEC_residual( + phi_densities, inc_phi, where="obj0") + A_pec_bc_resid = vector_pot_PEC_residual( + A_densities, inc_A, where="obj0") - scalar_bc_values = bind(geom_map, phi_pec_bc_resid)(queue, phi_densities=phi_dens, inc_phi=phi_inc,**knl_kwargs) - vector_bc_values = bind(geom_map, A_pec_bc_resid)(queue, A_densities=A_dens, inc_A=A_inc,**knl_kwargs) + scalar_bc_values = bind(geom_map, phi_pec_bc_resid)( + queue, phi_densities=phi_dens, inc_phi=phi_inc, **knl_kwargs) + vector_bc_values = bind(geom_map, A_pec_bc_resid)( + queue, A_densities=A_dens, inc_A=A_inc, **knl_kwargs) def scat_norm(f): - return norm(qbx, queue, f, p=np.inf) + return norm(qbx, queue, f, p=np.inf) - scalar_bc_residual = scat_norm(scalar_bc_values) #/ scat_norm(phi_inc) - vector_bc_residual = scat_norm(vector_bc_values) #/ scat_norm(A_inc) + scalar_bc_residual = scat_norm(scalar_bc_values) # / scat_norm(phi_inc) + vector_bc_residual = scat_norm(vector_bc_values) # / scat_norm(A_inc) - print("Potential PEC BC residuals:", h_max, scalar_bc_residual, vector_bc_residual) + print( + "Potential PEC BC residuals:", + h_max, scalar_bc_residual, vector_bc_residual) - eoc_pec_bc.add_data_point(h_max, max(scalar_bc_residual, vector_bc_residual)) + eoc_pec_bc.add_data_point( + h_max, max(scalar_bc_residual, vector_bc_residual)) # }}} - # {{{ check if DPIE helmholtz BCs are satisfied - + # {{{ check if dpie helmholtz bcs are satisfied - #}}} + # }}} # {{{ visualization @@ -902,12 +1036,12 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("phi", phi), - ("Axyz", Axyz), - ("Einc", inc_EM_field_scat.e), - ("Hinc", inc_EM_field_scat.h), + # ("Axyz", Axyz), + # ("Einc", inc_EM_field_scat.e), + # ("Hinc", inc_EM_field_scat.h), ("bdry_normals", bdry_normals), - ("e_bc_residual", eh_bc_values[:3]), - ("h_bc_residual", eh_bc_values[3]), + # ("e_bc_residual", eh_bc_values[:3]), + # ("h_bc_residual", eh_bc_values[3]), ]) fplot = make_field_plotter_from_bbox( @@ -931,16 +1065,16 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): fplot_repr = EHField(vector_from_device(queue, fplot_repr)) - fplot_inc = EHField( - vector_from_device(queue, eval_inc_field_at(fplot_tgt))) + # fplot_inc = EHField( + # vector_from_device(queue, eval_inc_field_at(fplot_tgt))) fplot.write_vtk_file( "potential-%s.vts" % resolution, [ ("E", fplot_repr.e), ("H", fplot_repr.h), - ("Einc", fplot_inc.e), - ("Hinc", fplot_inc.h), + # ("Einc", fplot_inc.e), + # ("Hinc", fplot_inc.h), ] ) -- GitLab From 722ac7a324f551ffcda349ed8fa19ac9751957f7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Jul 2018 17:14:06 -0500 Subject: [PATCH 47/59] DPIE tests: split off aux problem test, minor refactor, actually test multiple resolutions --- test/test_maxwell_dpie.py | 295 ++++++++++++++++++++------------------ 1 file changed, 159 insertions(+), 136 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 9e650e15..3d762438 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -42,6 +42,15 @@ from meshmode.mesh.processing import find_bounding_box from pytools.convergence import EOCRecorder from pytential.solve import gmres +import pytential.symbolic.pde.maxwell as mw +import pytential.symbolic.pde.maxwell.dpie as mw_dpie +from pytential.qbx import QBXLayerPotentialSource +from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory +from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + import logging logger = logging.getLogger(__name__) @@ -194,7 +203,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) -tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0], +tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], qbx_order=7, fmm_tolerance=1e-4) tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], @@ -218,42 +227,174 @@ class EHField(object): return self.field[3:] +# {{ test_dpie_auxiliary + +@pytest.mark.parametrize("case", [ + #tc_int, + tc_ext, + ]) +def test_dpie_auxiliary(ctx_factory, case): + logging.basicConfig(level=logging.INFO) + + cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + + pytest.importorskip("pyfmmlib") + + np.random.seed(12) + + knl_kwargs = {"k": case.k, "ik": 1j*case.k} + + geom_list = ["obj0"] + + dpie = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) + tau_densities = sym.make_sym_vector("tau_densities", dpie.num_distinct_objects()) + + calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) + + # {{{ test the auxiliary problem + + # test that the aux problem is capable of computing the desired derivatives + # of an appropriate input field + + # define method to get locations to evaluate representation + def epsilon_off_boundary(where=None, epsilon=1e-4): + x = sym.nodes(3, where).as_vector() + return x + sym.normal(3, 2, where).as_vector()*epsilon + + # loop through the case's resolutions and compute the scattered field + # solution + + eoc_rec = EOCRecorder() + + for resolution in case.resolutions: + + # get the scattered and observation mesh + scat_mesh = case.get_mesh(resolution, case.target_order) + # observation_mesh = case.get_observation_mesh(case.target_order) + + # define the pre-scattered discretization + pre_scat_discr = Discretization( + cl_ctx, scat_mesh, + InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) + + # use OpenCL random number generator to create a set of random + # source locations for various variables being solved for + dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) + qbx0, _ = QBXLayerPotentialSource( + pre_scat_discr, fine_order=4*case.target_order, + #fmm_order=False, + qbx_order=case.qbx_order, + fmm_level_to_order=SimpleExpansionOrderFinder( + case.fmm_tolerance), + fmm_backend=case.fmm_backend + ).with_refinement(_expansion_disturbance_tolerance=0.05) + + # define the geometry dictionary + geom_map = { + "obj0": qbx0, + "obj0t": qbx0.density_discr, + "scat": qbx0.density_discr} + + # define points to evaluate the gradient at + tgt_n = PointsTarget(bind(geom_map, + epsilon_off_boundary(where='obj0', epsilon=1.0))(queue)) + geom_map['tgt'] = tgt_n + + # define the quantity that will have a derivative taken of it and + # its associated derivative + def getTestFunction(where=None): + z = sym.nodes(3, where).as_vector() + z2 = sym.cse(np.dot(z, z), "z_mag_squared") + g = sym.exp(1j*dpie0.k*sym.sqrt(z2))/(4.0*np.pi*sym.sqrt(z2)) + return g + + def getTestGradient(where=None): + z = sym.nodes(3, where).as_vector() + z2 = sym.cse(np.dot(z, z), "z_mag_squared") + grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - + 1.0/sym.sqrt(z2))/(4*np.pi*z2) + return grad_g + + # compute output gradient evaluated at the desired object + tgrad = bind(geom_map, getTestGradient(where="tgt"))(queue, **knl_kwargs) + test_func_d = vector_from_device(queue, tgrad) + + # define the problem that will be solved + test_tau_op = bind(geom_map, + dpie0.subproblem_operator(tau_densities=tau_densities)) + test_tau_rhs = bind(geom_map, + dpie0.subproblem_rhs_func(function=getTestFunction))(queue, + **knl_kwargs) + + # set GMRES settings for solving + gmres_settings = dict( + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + + subprob_result = gmres( + test_tau_op.scipy_op(queue, "tau_densities", np.complex128, + domains=dpie0.get_subproblem_domain_list(), + **knl_kwargs), + test_tau_rhs, **gmres_settings) + dummy_tau = subprob_result.solution + + # compute the error between the associated derivative quantities + tgrad = bind(geom_map, sym.grad(3, + dpie0.subproblem_rep(tau_densities=tau_densities, + target='tgt')))(queue, tau_densities=dummy_tau, + **knl_kwargs) + approx_d = vector_from_device(queue, tgrad) + err = ( + calc_patch.norm(test_func_d - approx_d, np.inf) + / + calc_patch.norm(approx_d, np.inf)) + + # append error to the error list + eoc_rec.add_data_point(qbx0.h_max, err) + + print(eoc_rec) + + assert eoc_rec.order_estimate() >= case.qbx_order - 0.5 + + # }}} + +# }}} + + @pytest.mark.parametrize("case", [ #tc_int, tc_ext, ]) -def test_pec_dpie_extinction(ctx_getter, case, visualize=False): - """For (say) is_interior=False (the 'exterior' MFIE), this test verifies +def test_pec_dpie_extinction( + ctx_factory, case, + visualize=False, + test_representations=False, + test_operators=False, + ): + + """For (say) is_interior=False (the 'exterior' BVP), this test verifies extinction of the combined (incoming + scattered) field on the interior of the scatterer. """ - # setup the basic config for logging logging.basicConfig(level=logging.INFO) - # setup the OpenCL context and queue - cl_ctx = ctx_getter() + cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - # import or skip pyfmmlib pytest.importorskip("pyfmmlib") - # initialize the random seed np.random.seed(12) - # specify a dictionary with some useful arguments knl_kwargs = {"k": case.k, "ik": 1j*case.k} - # specify the list of geometry objects being used geom_list = ["obj0"] # {{{ come up with a solution to Maxwell's equations - # import some functionality from maxwell into this - # local scope environment - import pytential.symbolic.pde.maxwell as mw - import pytential.symbolic.pde.maxwell.dpie as mw_dpie - # initialize the DPIE operator based on the geometry list dpie = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) @@ -331,124 +472,7 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # }}} - # {{{ test the auxiliary problem - - # test that the aux problem is capable of computing the desired derivatives - # of an appropriate input field - - test_auxiliary = False - - if test_auxiliary: - # import a bunch of stuff that will be useful - from pytential.qbx import QBXLayerPotentialSource - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory - from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - - # define method to get locations to evaluate representation - def epsilon_off_boundary(where=None, epsilon=1e-4): - x = sym.nodes(3, where).as_vector() - return x + sym.normal(3, 2, where).as_vector()*epsilon - - # loop through the case's resolutions and compute the scattered field - # solution - - deriv_error = [] - for resolution in case.resolutions: - - # get the scattered and observation mesh - scat_mesh = case.get_mesh(resolution, case.target_order) - # observation_mesh = case.get_observation_mesh(case.target_order) - - # define the pre-scattered discretization - pre_scat_discr = Discretization( - cl_ctx, scat_mesh, - InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - - # use OpenCL random number generator to create a set of random - # source locations for various variables being solved for - dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) - qbx0, _ = QBXLayerPotentialSource( - pre_scat_discr, fine_order=4*case.target_order, - #fmm_order=False, - qbx_order=case.qbx_order, - fmm_level_to_order=SimpleExpansionOrderFinder( - case.fmm_tolerance), - fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) - - # define the geometry dictionary - geom_map = { - "obj0": qbx0, - "obj0t": qbx0.density_discr, - "scat": qbx0.density_discr} - - # define points to evaluate the gradient at - tgt_n = PointsTarget(bind(geom_map, - epsilon_off_boundary(where='obj0', epsilon=1.0))(queue)) - geom_map['tgt'] = tgt_n - - # define the quantity that will have a derivative taken of it and - # its associated derivative - def getTestFunction(where=None): - z = sym.nodes(3, where).as_vector() - z2 = sym.cse(np.dot(z, z), "z_mag_squared") - g = sym.exp(1j*dpie0.k*sym.sqrt(z2))/(4.0*np.pi*sym.sqrt(z2)) - return g - - def getTestGradient(where=None): - z = sym.nodes(3, where).as_vector() - z2 = sym.cse(np.dot(z, z), "z_mag_squared") - grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - - 1.0/sym.sqrt(z2))/(4*np.pi*z2) - return grad_g - - # compute output gradient evaluated at the desired object - tgrad = bind(geom_map, getTestGradient(where="tgt"))(queue, **knl_kwargs) - test_func_d = vector_from_device(queue, tgrad) - - # define the problem that will be solved - test_tau_op = bind(geom_map, - dpie0.subproblem_operator(tau_densities=tau_densities)) - test_tau_rhs = bind(geom_map, - dpie0.subproblem_rhs_func(function=getTestFunction))(queue, - **knl_kwargs) - - # set GMRES settings for solving - gmres_settings = dict( - tol=case.gmres_tol, - progress=True, - hard_failure=True, - stall_iterations=50, no_progress_factor=1.05) - - subprob_result = gmres( - test_tau_op.scipy_op(queue, "tau_densities", np.complex128, - domains=dpie0.get_subproblem_domain_list(), - **knl_kwargs), - test_tau_rhs, **gmres_settings) - dummy_tau = subprob_result.solution - - # compute the error between the associated derivative quantities - tgrad = bind(geom_map, sym.grad(3, - dpie0.subproblem_rep(tau_densities=tau_densities, - target='tgt')))(queue, tau_densities=dummy_tau, - **knl_kwargs) - approx_d = vector_from_device(queue, tgrad) - err = calc_patch.norm(test_func_d - approx_d, np.inf) - - # append error to the error list - deriv_error.append(err) - - print("Auxiliary Error Results:") - for n in range(0, len(deriv_error)): - print("Case {0}: {1}".format(n+1, deriv_error[n])) - - # }}} - - # {{{ Test the representations - - test_representations = False + # {{{ test the representations if test_representations: @@ -518,8 +542,6 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): hard_failure=True, stall_iterations=50, no_progress_factor=1.05) - # get the GMRES functionality - subprob_result = gmres( test_tau_op.scipy_op(queue, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), @@ -556,7 +578,6 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): # {{{ test the operators - test_operators = False if test_operators: # define error array @@ -1125,6 +1146,8 @@ def test_pec_dpie_extinction(ctx_getter, case, visualize=False): assert good + # }}} + # }}} -- GitLab From 3dbd2e4d502b0620b5e6a2bce039f57a391e0f08 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 6 Nov 2018 21:26:11 -0600 Subject: [PATCH 48/59] Bump QBX order in DPIE test down to 3 --- test/test_maxwell_dpie.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 3d762438..8a5a1eaf 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -204,7 +204,7 @@ tc_int = SphereTestCase(k=1.2, is_interior=True, resolutions=[0, 1], qbx_order=3, fmm_tolerance=1e-4) tc_ext = SphereTestCase(k=1.2, is_interior=False, resolutions=[0, 1], - qbx_order=7, fmm_tolerance=1e-4) + qbx_order=3, fmm_tolerance=1e-4) tc_rc_ext = RoundedCubeTestCase(k=6.4, is_interior=False, resolutions=[0.1], qbx_order=3, fmm_tolerance=1e-4) -- GitLab From dcbc3b20009e65f0c7d30973c579089eb3c561b0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 7 Nov 2018 03:09:37 -0600 Subject: [PATCH 49/59] Placate flake8 about operator-in-front --- pytential/symbolic/pde/maxwell/dpie.py | 12 ++++++------ test/test_maxwell_dpie.py | 19 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 3b483ba5..b722d298 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -347,8 +347,8 @@ class DPIEOperator(object): return sym.integral( ambient_dim=3, dim=2, - operand=(self.Dp(sigma, target=where, qfl=qfl)/self.k + - 1j*0.5*sigma_n - 1j*self.Sp(sigma, target=where, qfl=qfl)), + operand=(self.Dp(sigma, target=where, qfl=qfl)/self.k + + 1j*0.5*sigma_n - 1j*self.Sp(sigma, target=where, qfl=qfl)), where=where) def _scaledDPIEv_integral(self, **kwargs): @@ -371,10 +371,10 @@ class DPIEOperator(object): ambient_dim=3, dim=2, operand=( - sym.n_dot(sym.curl(self.S(a, where)), where=where) - - self.k*sym.n_dot(Sn_times_rho, where=where) - + 1j*(self.k*sym.n_dot(Sn_cross_a, where=where) - 0.5*rho_n + - self.Sp(rho, target=where, qfl=qfl)) + sym.n_dot(sym.curl(self.S(a, where)), where=where) + - self.k*sym.n_dot(Sn_times_rho, where=where) + + 1j*(self.k*sym.n_dot(Sn_cross_a, where=where) - 0.5*rho_n + + self.Sp(rho, target=where, qfl=qfl)) ), where=where) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 8a5a1eaf..987f5745 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -312,8 +312,8 @@ def test_dpie_auxiliary(ctx_factory, case): def getTestGradient(where=None): z = sym.nodes(3, where).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") - grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - - 1.0/sym.sqrt(z2))/(4*np.pi*z2) + grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k + - 1.0/sym.sqrt(z2))/(4*np.pi*z2) return grad_g # compute output gradient evaluated at the desired object @@ -349,8 +349,7 @@ def test_dpie_auxiliary(ctx_factory, case): approx_d = vector_from_device(queue, tgrad) err = ( calc_patch.norm(test_func_d - approx_d, np.inf) - / - calc_patch.norm(approx_d, np.inf)) + / calc_patch.norm(approx_d, np.inf)) # append error to the error list eoc_rec.add_data_point(qbx0.h_max, err) @@ -563,8 +562,8 @@ def test_pec_dpie_extinction( eval_test_repr_at(calc_patch_tgt))) maxwell_residuals = [ - calc_patch.norm(x, np.inf) / - calc_patch.norm(pde_test_repr.e, np.inf) + calc_patch.norm(x, np.inf) + / calc_patch.norm(pde_test_repr.e, np.inf) for x in frequency_domain_maxwell(calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] print("Maxwell residuals:", maxwell_residuals) @@ -746,8 +745,8 @@ def test_pec_dpie_extinction( test_op_err = vector_from_device(queue, bind( geom_map, - 0.5*axyz + - nxcurlS0("avg") - nxcurlS0(+1) + 0.5*axyz + + nxcurlS0("avg") - nxcurlS0(+1) )(queue, density=density, **knl_kwargs)) from sumpy.kernel import LaplaceKernel @@ -990,8 +989,8 @@ def test_pec_dpie_extinction( eval_repr_at(calc_patch_tgt))) maxwell_residuals = [ - calc_patch.norm(x, np.inf) / - calc_patch.norm(pde_test_repr.e, np.inf) + calc_patch.norm(x, np.inf) + / calc_patch.norm(pde_test_repr.e, np.inf) for x in frequency_domain_maxwell( calc_patch, pde_test_repr.e, pde_test_repr.h, case.k)] -- GitLab From 5ac929f1b2eb8dd39975586dc59e0fa716d6cee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Wed, 13 Feb 2019 00:56:38 +0100 Subject: [PATCH 50/59] Placate Flake8 --- pytential/symbolic/pde/maxwell/dpie.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index b722d298..ca908833 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -162,8 +162,8 @@ class DPIEOperator(object): # define a convenient integral operator that functions across the # multiple objects def int_op(idx): - return layerpot_op(kernel, density_vec[:, idx], qbx_forced_limit=qfl, - source=self.geometry_list[idx], target=target, **kargs) + return layerpot_op(kernel, density_vec[:, idx], qbx_forced_limit=qfl, + source=self.geometry_list[idx], target=target, **kargs) # get the shape of density_vec (ndim, nobj) = density_vec.shape -- GitLab From cc390b156d258d108ab13e92017e29934ca7ad7c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 30 Apr 2019 12:57:46 -0500 Subject: [PATCH 51/59] Allow curl_S to take kwargs --- pytential/symbolic/pde/maxwell/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 54fd3113..51e73c78 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -312,8 +312,8 @@ class MuellerAugmentedMFIEOperator(object): S = partial(sym.S, self.kernel, qbx_forced_limit="avg") - def curl_S(dens): - return sym.curl(sym.S(self.kernel, dens, qbx_forced_limit="avg")) + def curl_S(dens, **kwargs): + return sym.curl(sym.S(self.kernel, dens, qbx_forced_limit="avg", **kwargs)) grad = partial(sym.grad, 3) -- GitLab From ea686ebcd621e6f9688c2b46e434e72f52212a6d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 19 Aug 2019 19:34:33 -0500 Subject: [PATCH 52/59] Fix h_max in DPIE tests --- test/test_maxwell_dpie.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 987f5745..7d3f43bf 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -352,7 +352,7 @@ def test_dpie_auxiliary(ctx_factory, case): / calc_patch.norm(approx_d, np.inf)) # append error to the error list - eoc_rec.add_data_point(qbx0.h_max, err) + eoc_rec.add_data_point(bind(qbx0, sym.h_max(qbx0.ambient_dim))(queue), err) print(eoc_rec) @@ -777,7 +777,7 @@ def test_pec_dpie_extinction( jump_identity = bound_jump_identity(queue, density=density, **knl_kwargs) err = (norm(qbx0, queue, jump_identity, np.inf)) - print("ERROR", qbx0.h_max, err) + print("ERROR", bind(qbx0, sym.h_max(qbx0.ambient_dim))(queue), err) # compute the error between the operator values and the # representation values @@ -870,7 +870,7 @@ def test_pec_dpie_extinction( } # get the maximum mesh element edge length - h_max = qbx.h_max + h_max = bind(qbx, sym.h_max(qbx.ambient_dim))(queue) # define the scattered and observation discretization scat_discr = qbx.density_discr -- GitLab From 71def213d44934f8e1efda8bf1358fc8b49a0f19 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 20 Aug 2019 10:08:15 -0500 Subject: [PATCH 53/59] Modernize DPIE for where->dofdesc --- pytential/symbolic/primitives.py | 1 + test/test_maxwell_dpie.py | 81 ++++++++++++++++---------------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 6ee6e950..fea38f80 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1847,6 +1847,7 @@ def cross(vec_a, vec_b): for i in range(3)]) +@_deprecate_kwargs('where', 'dofdesc') def n_cross(vec, dofdesc=None): return cross(normal(3, dofdesc=dofdesc).as_vector(), vec) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 7d3f43bf..3d68bd3f 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -258,9 +258,9 @@ def test_dpie_auxiliary(ctx_factory, case): # of an appropriate input field # define method to get locations to evaluate representation - def epsilon_off_boundary(where=None, epsilon=1e-4): - x = sym.nodes(3, where).as_vector() - return x + sym.normal(3, 2, where).as_vector()*epsilon + def epsilon_off_boundary(dofdesc=None, epsilon=1e-4): + x = sym.nodes(3, dofdesc).as_vector() + return x + sym.normal(3, 2, dofdesc).as_vector()*epsilon # loop through the case's resolutions and compute the scattered field # solution @@ -298,26 +298,26 @@ def test_dpie_auxiliary(ctx_factory, case): # define points to evaluate the gradient at tgt_n = PointsTarget(bind(geom_map, - epsilon_off_boundary(where='obj0', epsilon=1.0))(queue)) + epsilon_off_boundary(dofdesc='obj0', epsilon=1.0))(queue)) geom_map['tgt'] = tgt_n # define the quantity that will have a derivative taken of it and # its associated derivative - def getTestFunction(where=None): - z = sym.nodes(3, where).as_vector() + def getTestFunction(dofdesc=None): + z = sym.nodes(3, dofdesc).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") g = sym.exp(1j*dpie0.k*sym.sqrt(z2))/(4.0*np.pi*sym.sqrt(z2)) return g - def getTestGradient(where=None): - z = sym.nodes(3, where).as_vector() + def getTestGradient(dofdesc=None): + z = sym.nodes(3, dofdesc).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k - 1.0/sym.sqrt(z2))/(4*np.pi*z2) return grad_g # compute output gradient evaluated at the desired object - tgrad = bind(geom_map, getTestGradient(where="tgt"))(queue, **knl_kwargs) + tgrad = bind(geom_map, getTestGradient(dofdesc="tgt"))(queue, **knl_kwargs) test_func_d = vector_from_device(queue, tgrad) # define the problem that will be solved @@ -434,25 +434,25 @@ def test_pec_dpie_extinction( # get the gradphi_inc field evaluated at some source locations def get_incident_gradphi(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_gradphi(u=uvar, - Ep=Evar, k=dpie.k, where=target))(queue, u=u_dir, + Ep=Evar, k=dpie.k, dofdesc=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) # get the incident plane wave div(A) def get_incident_divA(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, - k=dpie.k, where=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) + k=dpie.k, dofdesc=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) # method to get vector potential and scalar potential for incident # E-M fields def get_incident_potentials(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_potentials(u=uvar, - Ep=Evar, k=dpie.k, where=target))(queue, u=u_dir, + Ep=Evar, k=dpie.k, dofdesc=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) # define a smooth function to represent the density - def dummy_density(omega=1.0, where=None): - x = sym.nodes(3, where).as_vector() - return sym.sin(omega*sym.n_dot(x, where)) + def dummy_density(omega=1.0, dofdesc=None): + x = sym.nodes(3, dofdesc).as_vector() + return sym.sin(omega*sym.n_dot(x, dofdesc)) # get the Electromagnetic field evaluated at the target calculus patch pde_test_inc = EHField(vector_from_device(queue, @@ -525,9 +525,9 @@ def test_pec_dpie_extinction( n1 = len(dummy_phi) n2 = len(dummy_A) for i in range(0, n1): - dummy_phi[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + dummy_phi[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) for i in range(0, n2): - dummy_A[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) test_tau_op = bind(geom_map, dpie0.subproblem_operator(tau_densities=tau_densities)) test_tau_rhs = bind(geom_map, @@ -583,9 +583,9 @@ def test_pec_dpie_extinction( op_error = [] # define method to get locations to evaluate representation - def epsilon_off_boundary(where=None, epsilon=1e-4): - x = sym.nodes(3, where).as_vector() - return x + sym.normal(3, 2, where).as_vector()*epsilon + def epsilon_off_boundary(dofdesc=None, epsilon=1e-4): + x = sym.nodes(3, dofdesc).as_vector() + return x + sym.normal(3, 2, dofdesc).as_vector()*epsilon # import a bunch of stuff that will be useful from pytential.qbx import QBXLayerPotentialSource @@ -630,7 +630,7 @@ def test_pec_dpie_extinction( # to be evaluated at tgt_n = PointsTarget( bind(geom_map, - epsilon_off_boundary(where='obj0', epsilon=1e-4))(queue)) + epsilon_off_boundary(dofdesc='obj0', epsilon=1e-4))(queue)) geom_map['tgt'] = tgt_n # define a dummy density, specifically to be used for the vector @@ -673,22 +673,23 @@ def test_pec_dpie_extinction( for i in range(0, n1): if i < (n1-1): - dummy_phi[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + dummy_phi[i] = bind( + geom_map, dummy_density(dofdesc='obj0'))(queue) else: dummy_phi[i] = 0.0 for i in range(0, n2): if i < 2: dummy_A[i] = density[i] elif i < (n2-1): - dummy_A[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) else: dummy_A[i] = 0.0 for i in range(0, n3): - dummy_tau[i] = bind(geom_map, dummy_density(where='obj0'))(queue) + dummy_tau[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) # check that the scalar density operator and representation are similar def vector_op_transform(vec_op_out): - a = sym.tangential_to_xyz(vec_op_out[:2], where='obj0') + a = sym.tangential_to_xyz(vec_op_out[:2], dofdesc='obj0') return sym.join_fields(a, vec_op_out[2:]) scalar_op = dpie0.phi_operator(phi_densities=phi_densities) @@ -711,7 +712,7 @@ def test_pec_dpie_extinction( # define the vector operator equivalent representations #def vec_op_repr(A_densities, target): - # return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep0(A_densities=A_densities, target=target), where='obj0'), # noqa: E501 + # return sym.join_fields(sym.n_cross(dpie0.vector_potential_rep0(A_densities=A_densities, target=target), dofdesc='obj0'), # noqa: E501 # dpie0.div_vector_potential_rep0(A_densities=A_densities, target=target)/dpie0.k) # noqa: E501 def vec_op_repr(A_densities, target): return sym.join_fields( @@ -719,7 +720,7 @@ def test_pec_dpie_extinction( dpie0.vector_potential_rep( A_densities=A_densities, target=target), - where='obj0'), + dofdesc='obj0'), dpie0.div_vector_potential_rep( A_densities=A_densities, target=target)/dpie0.k) @@ -736,11 +737,11 @@ def test_pec_dpie_extinction( target='tgt'))(queue, tau_densities=dummy_tau, **knl_kwargs)) - axyz = sym.tangential_to_xyz(density_sym, where='obj0') + axyz = sym.tangential_to_xyz(density_sym, dofdesc='obj0') def nxcurlS0(qbx_forced_limit): return sym.n_cross(sym.curl(dpie0.S(axyz.reshape(3, 1), - target='obj0t', qfl=qbx_forced_limit)), where='obj0') + target='obj0t', qfl=qbx_forced_limit)), dofdesc='obj0') test_op_err = vector_from_device(queue, bind( @@ -761,17 +762,17 @@ def test_pec_dpie_extinction( sym.curl(sym.S( knl, sym.cse( - sym.tangential_to_xyz(density_sym, where='obj0'), + sym.tangential_to_xyz(density_sym, dofdesc='obj0'), "jxyz"), k=dpie0.k, qbx_forced_limit=qbx_forced_limit, source='obj0', target='obj0t')), - where='obj0') + dofdesc='obj0') jump_identity_sym = ( nxcurlS(+1) - (nxcurlS("avg") - + 0.5*sym.tangential_to_xyz(density_sym, where='obj0'))) + + 0.5*sym.tangential_to_xyz(density_sym, dofdesc='obj0'))) bound_jump_identity = bind(geom_map, jump_identity_sym) jump_identity = bound_jump_identity(queue, density=density, **knl_kwargs) @@ -1004,22 +1005,22 @@ def test_pec_dpie_extinction( # {{{ check potential PEC BC on total field - def scalar_pot_PEC_residual(phi, inc_phi, where=None): + def scalar_pot_PEC_residual(phi, inc_phi, dofdesc=None): V = dpie.scalar_potential_constants(phi_densities=phi) return dpie.scalar_potential_rep( - phi_densities=phi, target=where, qfl=loc_sign + phi_densities=phi, target=dofdesc, qfl=loc_sign ) + inc_phi - V[0] - def vector_pot_PEC_residual(a_densities, inc_a, where=None): + def vector_pot_PEC_residual(a_densities, inc_a, dofdesc=None): return sym.n_cross( dpie.vector_potential_rep( - A_densities=a_densities, target=where, qfl=loc_sign) - + inc_a, where=where) + A_densities=a_densities, target=dofdesc, qfl=loc_sign) + + inc_a, dofdesc=dofdesc) phi_pec_bc_resid = scalar_pot_PEC_residual( - phi_densities, inc_phi, where="obj0") + phi_densities, inc_phi, dofdesc="obj0") A_pec_bc_resid = vector_pot_PEC_residual( - A_densities, inc_A, where="obj0") + A_densities, inc_A, dofdesc="obj0") scalar_bc_values = bind(geom_map, phi_pec_bc_resid)( queue, phi_densities=phi_dens, inc_phi=phi_inc, **knl_kwargs) @@ -1158,7 +1159,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from py.test.cmdline import main + from pytest import main main([__file__]) # vim: fdm=marker -- GitLab From f6a4c886319f51ee267fa549c131abb1589cf294 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 21 Aug 2019 14:36:37 -0500 Subject: [PATCH 54/59] More where->dofdesc in Maxwell --- pytential/symbolic/pde/maxwell/__init__.py | 12 +- pytential/symbolic/pde/maxwell/dpie.py | 136 ++++++++++----------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 19016060..5a32ecb7 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -139,7 +139,7 @@ def get_sym_maxwell_point_source_potentials(kernel, jxyz, k): ) -def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): +def get_sym_maxwell_planewave_gradphi(u, Ep, k, dofdesc=None): r""" Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the gradient of a scalar potential field satisfying Maxwell's equations. @@ -150,12 +150,12 @@ def get_sym_maxwell_planewave_gradphi(u, Ep, k, where=None): \nabla \phi(x) = - e^{i k x^T u} E_p^T \left( 1 + i k x^T u\right) """ - x = sym.nodes(3, where).as_vector() + x = sym.nodes(3, dofdesc).as_vector() grad_phi = -sym.exp(1j*k*np.dot(x, u)) * (Ep.T + 1j*k*np.dot(Ep, x)*u.T) return grad_phi -def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): +def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, dofdesc=None): r"""Return symbolic expression that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the divergence of a vector potential field satisfying Maxwell's equations. @@ -167,7 +167,7 @@ def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): \nabla \cdot \boldsymbol{A} = -\sqrt{\mu \epsilon} e^{i k x^T u} E_p^T \left( u + i k x\right) """ - x = sym.nodes(3, where).as_vector() + x = sym.nodes(3, dofdesc).as_vector() divA = sym.join_fields( -sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x, u)) * np.dot(Ep, u + 1j*k*x)) @@ -175,7 +175,7 @@ def get_sym_maxwell_planewave_divA(u, Ep, k, epsilon=1, mu=1, where=None): return divA -def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): +def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, dofdesc=None): r"""Return a 2-tuple of symbolic expressions that can be bound to a :class:`pytential.source.PointPotentialSource` and yield the scalar and vector potential fields satisfying Maxwell's equations that represent a @@ -192,7 +192,7 @@ def get_sym_maxwell_planewave_potentials(u, Ep, k, epsilon=1, mu=1, where=None): \phi = - \left(x \cdot E_p\right) e^{i k x^T u} """ - x = sym.nodes(3, where).as_vector() + x = sym.nodes(3, dofdesc).as_vector() A = -u * np.dot(x, Ep) * sym.sqrt(epsilon*mu) * sym.exp(1j*k*np.dot(x, u)) phi = sym.join_fields(-np.dot(x, Ep) * sym.exp(1j*k*np.dot(x, u))) return phi, A diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index ca908833..8bf9ea96 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -249,7 +249,7 @@ class DPIEOperator(object): # loop through the density and sources to construct the appropriate # element-wise cross product operation for k in range(0, nobj): - output[:, k] = sym.n_cross(density_vec[:, k], where=sources[k]) + output[:, k] = sym.n_cross(density_vec[:, k], dofdesc=sources[k]) # return result from element-wise cross product return output @@ -288,7 +288,7 @@ class DPIEOperator(object): # element-wise cross product operation for k in range(0, nobj): output[:, k] = \ - sym.normal(3, where=sources[k]).as_vector() * density_vec[0, k] + sym.normal(3, dofdesc=sources[k]).as_vector() * density_vec[0, k] # return result from element-wise cross product return output @@ -309,47 +309,47 @@ class DPIEOperator(object): v = A_densities[(3*self.nobjs):] for n in range(0, self.nobjs): a[:, n] = cse(sym.tangential_to_xyz(a0[2*n:2*(n+1)], - where=self.geometry_list[n]), "axyz_{0}".format(n)) + dofdesc=self.geometry_list[n]), "axyz_{0}".format(n)) return (a0, a, rho0, rho, v) - def _L(self, a, rho, where): + def _L(self, a, rho, dofdesc): # define some useful common sub expressions - # Sa = cse(self.S(a, where), "Sa_"+where) - # Srho = cse(self.S(rho, where), "Srho_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) - # Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) - Drho = cse(self.D(rho, where), "Drho_"+where) + # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) + # Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) + Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + # Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) + Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) return sym.join_fields( sym.n_cross( - sym.curl(self.S(a, where)) - self.k * Sn_times_rho, - where=where), + sym.curl(self.S(a, dofdesc)) - self.k * Sn_times_rho, + dofdesc=dofdesc), Drho) - def _R(self, a, rho, where): + def _R(self, a, rho, dofdesc): # define some useful common sub expressions - # Sa = cse(self.S(a, where), "Sa_"+where) - Srho = cse(self.S(rho, where), "Srho_"+where) - # Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) - # Drho = cse(self.D(rho, where), "Drho_"+where) + # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) + Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) + # Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) + # Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) return sym.join_fields( sym.n_cross(self.k * Sn_cross_a + sym.grad(ambient_dim=3, - operand=self.S(rho, where)), where=where), - sym.div(self.S(self.n_cross(a), where)) - self.k * Srho + operand=self.S(rho, dofdesc)), dofdesc=dofdesc), + sym.div(self.S(self.n_cross(a), dofdesc)) - self.k * Srho ) - def _scaledDPIEs_integral(self, sigma, sigma_n, where): + def _scaledDPIEs_integral(self, sigma, sigma_n, dofdesc): qfl = "avg" return sym.integral( ambient_dim=3, dim=2, - operand=(self.Dp(sigma, target=where, qfl=qfl)/self.k - + 1j*0.5*sigma_n - 1j*self.Sp(sigma, target=where, qfl=qfl)), - where=where) + operand=(self.Dp(sigma, target=dofdesc, qfl=qfl)/self.k + + 1j*0.5*sigma_n - 1j*self.Sp(sigma, target=dofdesc, qfl=qfl)), + dofdesc=dofdesc) def _scaledDPIEv_integral(self, **kwargs): qfl = "avg" @@ -358,25 +358,25 @@ class DPIEOperator(object): a = kwargs['a'] rho = kwargs['rho'] rho_n = kwargs['rho_n'] - where = kwargs['where'] + dofdesc = kwargs['dofdesc'] # define some useful common sub expressions - # Sa = cse(self.S(a, where), "Sa_"+where) - # Srho = cse(self.S(rho, where), "Srho_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(a), where), "Sn_cross_a_"+where) - # Drho = cse(self.D(rho, where), "Drho_"+where) + # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) + # Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) + Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) + # Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) return sym.integral( ambient_dim=3, dim=2, operand=( - sym.n_dot(sym.curl(self.S(a, where)), where=where) - - self.k*sym.n_dot(Sn_times_rho, where=where) - + 1j*(self.k*sym.n_dot(Sn_cross_a, where=where) - 0.5*rho_n - + self.Sp(rho, target=where, qfl=qfl)) + sym.n_dot(sym.curl(self.S(a, dofdesc)), dofdesc=dofdesc) + - self.k*sym.n_dot(Sn_times_rho, dofdesc=dofdesc) + + 1j*(self.k*sym.n_dot(Sn_cross_a, dofdesc=dofdesc) - 0.5*rho_n + + self.Sp(rho, target=dofdesc, qfl=qfl)) ), - where=where) + dofdesc=dofdesc) def phi_operator(self, phi_densities): """ @@ -405,7 +405,7 @@ class DPIEOperator(object): # set up equation that integrates some integral operators over the # nth surface output[self.nobjs + n] = self._scaledDPIEs_integral(sigma, sigma[0, - n], where=obj_n) + n], dofdesc=obj_n) # return the resulting system of IE return output @@ -424,7 +424,7 @@ class DPIEOperator(object): Q = np.zeros((self.nobjs,), dtype=self.stype) for i in range(0, self.nobjs): Q[i] = -sym.integral(3, 2, sym.n_dot(gradphi_inc, - where=self.geometry_list[i]), where=self.geometry_list[i]) + dofdesc=self.geometry_list[i]), dofdesc=self.geometry_list[i]) # return the resulting field return sym.join_fields(f, Q/self.k) @@ -454,7 +454,7 @@ class DPIEOperator(object): # across the various geometries involved output[2*n:2*(n+1)] = sym.xyz_to_tangential( 0.5*a[:, n] + L[:3] + 1j*R[:3], - where=obj_n) + dofdesc=obj_n) # generate the set of equations for the scalar densities, rho, coupled # across the various geometries involved @@ -462,7 +462,7 @@ class DPIEOperator(object): # add the equation that integrates everything out into some constant output[3*self.nobjs + n] = self._scaledDPIEv_integral( - a=a, rho=rho, rho_n=rho[0, n], where=obj_n) + a=a, rho=rho, rho_n=rho[0, n], dofdesc=obj_n) # return output equations return output @@ -478,11 +478,11 @@ class DPIEOperator(object): f = np.zeros((2*self.nobjs,), dtype=self.stype) for i in range(0, self.nobjs): obj_n = self.geometry_list[i] - q[i] = -sym.integral(3, 2, sym.n_dot(A_inc[3*i:3*(i+1)], where=obj_n), - where=obj_n) + q[i] = -sym.integral(3, 2, sym.n_dot(A_inc[3*i:3*(i+1)], dofdesc=obj_n), + dofdesc=obj_n) h[i] = -divA_inc[i] f[2*i:2*(i+1)] = sym.xyz_to_tangential( - -sym.n_cross(A_inc[3*i:3*(i+1)], where=obj_n), where=obj_n) + -sym.n_cross(A_inc[3*i:3*(i+1)], dofdesc=obj_n), dofdesc=obj_n) # define RHS for `A` integral equation system return sym.join_fields(f, h/self.k, q) @@ -547,7 +547,7 @@ class DPIEOperator(object): obj_n = self.geometry_list[n] # setup IE for evaluation over the nth disjoint object's surface - output[n] = function(where=obj_n) + output[n] = function(dofdesc=obj_n) # return the resulting system of IE return output @@ -727,48 +727,48 @@ class DPIEOperatorEvanescent(DPIEOperator): target=self.geometry_list[i], qfl=qfl, k=k, kernel=kernel) return output - def _L(self, a, rho, where): + def _L(self, a, rho, dofdesc): # define some useful common sub expressions - Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) - Drho = cse(self.D(rho, where), "Drho_"+where) + Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) return sym.join_fields( sym.n_cross( - sym.curl(self.S(a, where)) - self.k * Sn_times_rho, - where=where), + sym.curl(self.S(a, dofdesc)) - self.k * Sn_times_rho, + dofdesc=dofdesc), Drho) - def _R(self, a, rho, where): + def _R(self, a, rho, dofdesc): # define some useful common sub expressions Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, kernel=self.kernel_ik), "Srho_ik_nest") - Srho = cse(self.S(Srho_ik_nest, where), "Srho_"+where) - Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), where), - "Sn_cross_a_"+where) + Srho = cse(self.S(Srho_ik_nest, dofdesc), "Srho_"+dofdesc) + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), dofdesc), + "Sn_cross_a_"+dofdesc) return self.k*sym.join_fields( sym.n_cross(self.k * Sn_cross_a + sym.grad(ambient_dim=3, - operand=self.S(Srho_ik_nest, where)), where=where), - sym.div(self.S(self.n_cross(Sa_ik_nest), where)) - self.k * Srho + operand=self.S(Srho_ik_nest, dofdesc)), dofdesc=dofdesc), + sym.div(self.S(self.n_cross(Sa_ik_nest), dofdesc)) - self.k * Srho ) - def _scaledDPIEs_integral(self, sigma, sigma_n, where): + def _scaledDPIEs_integral(self, sigma, sigma_n, dofdesc): qfl = "avg" return sym.integral( ambient_dim=3, dim=2, operand=( - (self.Dp(sigma, target=where, qfl=qfl) - - self.Dp(sigma, target=where, qfl=qfl, + (self.Dp(sigma, target=dofdesc, qfl=qfl) + - self.Dp(sigma, target=dofdesc, qfl=qfl, kernel=self.kernel_laplace, use_laplace=True)) / self.k + 1j*0.5*sigma_n - - 1j*self.Sp(sigma, target=where, qfl=qfl)), - where=where) + - 1j*self.Sp(sigma, target=dofdesc, qfl=qfl)), + dofdesc=dofdesc) def _scaledDPIEv_integral(self, **kwargs): qfl = "avg" @@ -777,29 +777,29 @@ class DPIEOperatorEvanescent(DPIEOperator): a = kwargs['a'] rho = kwargs['rho'] # rho_n = kwargs['rho_n'] - where = kwargs['where'] + dofdesc = kwargs['dofdesc'] # define some useful common sub expressions Sa_ik_nest = cse(self._eval_all_objects(a, self.S, k=self.ik, kernel=self.kernel_ik), "Sa_ik_nest") - Srho_ik = cse(self.S(rho, where, k=self.ik, kernel=self.kernel_ik), - "Srho_ik"+where) + Srho_ik = cse(self.S(rho, dofdesc, k=self.ik, kernel=self.kernel_ik), + "Srho_ik"+dofdesc) Srho_ik_nest = cse(self._eval_all_objects(rho, self.S, k=self.ik, kernel=self.kernel_ik), "Srho_ik_nest") - Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), where), - "Sn_cross_a_nest_"+where) - Sn_times_rho = cse(self.S(self.n_times(rho), where), "Sn_times_rho_"+where) + Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), dofdesc), + "Sn_cross_a_nest_"+dofdesc) + Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) return sym.integral( ambient_dim=3, dim=2, operand=( - -self.k*sym.n_dot(Sn_times_rho, where=where) + -self.k*sym.n_dot(Sn_times_rho, dofdesc=dofdesc) + 1j*self.k*( - self.k*sym.n_dot(Sn_cross_a, where=where) - - 0.5*Srho_ik + self.Sp(Srho_ik_nest, target=where, qfl=qfl)) + self.k*sym.n_dot(Sn_cross_a, dofdesc=dofdesc) + - 0.5*Srho_ik + self.Sp(Srho_ik_nest, target=dofdesc, qfl=qfl)) ), - where=where) + dofdesc=dofdesc) def vector_potential_rep(self, A_densities, target=None, qfl=None): """ -- GitLab From a1d45dc4d24f94b55a31cdbe292c39c60fa4bfde Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 21 Aug 2019 14:37:46 -0500 Subject: [PATCH 55/59] DPIE Flake8 --- pytential/symbolic/pde/maxwell/dpie.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/pde/maxwell/dpie.py b/pytential/symbolic/pde/maxwell/dpie.py index 8bf9ea96..1c35b142 100644 --- a/pytential/symbolic/pde/maxwell/dpie.py +++ b/pytential/symbolic/pde/maxwell/dpie.py @@ -317,7 +317,8 @@ class DPIEOperator(object): # define some useful common sub expressions # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) # Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) - Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_times_rho = cse( + self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) # Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) @@ -331,7 +332,8 @@ class DPIEOperator(object): # define some useful common sub expressions # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) - # Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + # Sn_times_rho = cse( + # self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) # Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) @@ -363,7 +365,8 @@ class DPIEOperator(object): # define some useful common sub expressions # Sa = cse(self.S(a, dofdesc), "Sa_"+dofdesc) # Srho = cse(self.S(rho, dofdesc), "Srho_"+dofdesc) - Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_times_rho = cse( + self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) Sn_cross_a = cse(self.S(self.n_cross(a), dofdesc), "Sn_cross_a_"+dofdesc) # Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) @@ -730,7 +733,8 @@ class DPIEOperatorEvanescent(DPIEOperator): def _L(self, a, rho, dofdesc): # define some useful common sub expressions - Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_times_rho = cse( + self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) Drho = cse(self.D(rho, dofdesc), "Drho_"+dofdesc) return sym.join_fields( @@ -788,7 +792,8 @@ class DPIEOperatorEvanescent(DPIEOperator): kernel=self.kernel_ik), "Srho_ik_nest") Sn_cross_a = cse(self.S(self.n_cross(Sa_ik_nest), dofdesc), "Sn_cross_a_nest_"+dofdesc) - Sn_times_rho = cse(self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) + Sn_times_rho = cse( + self.S(self.n_times(rho), dofdesc), "Sn_times_rho_"+dofdesc) return sym.integral( ambient_dim=3, -- GitLab From 0956606bb9551d1be479306d140dc715dd91c7c2 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 21 Aug 2019 14:39:49 -0500 Subject: [PATCH 56/59] More DPIE test where/dofdesc and Flake8 fixes --- test/test_maxwell_dpie.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 3d68bd3f..215bff10 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -303,13 +303,13 @@ def test_dpie_auxiliary(ctx_factory, case): # define the quantity that will have a derivative taken of it and # its associated derivative - def getTestFunction(dofdesc=None): + def get_test_function(dofdesc=None): z = sym.nodes(3, dofdesc).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") g = sym.exp(1j*dpie0.k*sym.sqrt(z2))/(4.0*np.pi*sym.sqrt(z2)) return g - def getTestGradient(dofdesc=None): + def get_test_gradient(dofdesc=None): z = sym.nodes(3, dofdesc).as_vector() z2 = sym.cse(np.dot(z, z), "z_mag_squared") grad_g = z*sym.exp(1j*dpie0.k*sym.sqrt(z2))*(1j*dpie.k @@ -317,14 +317,14 @@ def test_dpie_auxiliary(ctx_factory, case): return grad_g # compute output gradient evaluated at the desired object - tgrad = bind(geom_map, getTestGradient(dofdesc="tgt"))(queue, **knl_kwargs) + tgrad = bind(geom_map, get_test_gradient(dofdesc="tgt"))(queue, **knl_kwargs) test_func_d = vector_from_device(queue, tgrad) # define the problem that will be solved test_tau_op = bind(geom_map, dpie0.subproblem_operator(tau_densities=tau_densities)) test_tau_rhs = bind(geom_map, - dpie0.subproblem_rhs_func(function=getTestFunction))(queue, + dpie0.subproblem_rhs_func(function=get_test_function))(queue, **knl_kwargs) # set GMRES settings for solving -- GitLab From 3ce31074197d7cd8787f2baa592d5a2c3ccb5d5d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 22 Aug 2019 15:05:55 -0500 Subject: [PATCH 57/59] Fix _prepare_domains to preserve None entries --- pytential/symbolic/execution.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 02ea90f7..eb1e2447 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -441,7 +441,7 @@ class MatVecOp: # {{{ expression prep -def _prepare_domains(nresults, places, domains, default_domain): +def _prepare_domains(nresults, places, domains, default_domain, preserve_none=False): """ :arg nresults: number of results. :arg places: a :class:`pytential.symbolic.execution.GeometryCollection`. @@ -461,7 +461,10 @@ def _prepare_domains(nresults, places, domains, default_domain): dom_name = domains return nresults * [dom_name] - domains = [sym.as_dofdesc(d) for d in domains] + domains = [ + sym.as_dofdesc(d) if d is not None else None + for d in domains] + assert len(domains) == nresults return domains -- GitLab From 0fbff2e4d70c27d29ca56b8c5ad666ddc3da642d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 23 Aug 2019 15:23:51 -0500 Subject: [PATCH 58/59] Fix eval_repr_at in test_pec_dpie_extinction --- test/test_maxwell_dpie.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index 215bff10..cd630a94 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -979,10 +979,13 @@ def test_pec_dpie_extinction( sym_repr = dpie.scattered_volume_field( phi_densities, A_densities, tau_densities, target='tgt') - def eval_repr_at(tgt): - map = geom_map - map['tgt'] = tgt - return bind(map, sym_repr)(queue, phi_densities=phi_dens, + def eval_repr_at(tgt, source=None): + places = geom_map.copy() + places['tgt'] = tgt + if source is not None: + places['obj0'] = source + + return bind(places, sym_repr)(queue, phi_densities=phi_dens, A_densities=A_dens, tau_densities=tau_dens, **knl_kwargs) -- GitLab From f85b3d7645915e9c0db3c26b2249d65bf5f87bfc Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Mon, 21 Jun 2021 16:26:00 -0500 Subject: [PATCH 59/59] WIP actx update --- test/test_maxwell_dpie.py | 135 +++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/test/test_maxwell_dpie.py b/test/test_maxwell_dpie.py index cd630a94..ee43b847 100644 --- a/test/test_maxwell_dpie.py +++ b/test/test_maxwell_dpie.py @@ -27,6 +27,7 @@ THE SOFTWARE. import numpy as np import numpy.linalg as la # noqa +from arraycontext import PyOpenCLArrayContext import pyopencl as cl import pyopencl.clmath # noqa import pyopencl.clrandom # noqa @@ -92,7 +93,7 @@ class SphereTestCase(MaxwellTestCase): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -102,9 +103,7 @@ class SphereTestCase(MaxwellTestCase): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) class RoundedCubeTestCase(MaxwellTestCase): @@ -136,7 +135,7 @@ class RoundedCubeTestCase(MaxwellTestCase): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -146,9 +145,7 @@ class RoundedCubeTestCase(MaxwellTestCase): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) class ElliptiPlaneTestCase(MaxwellTestCase): @@ -183,7 +180,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -193,9 +190,7 @@ class ElliptiPlaneTestCase(MaxwellTestCase): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) # }}} @@ -238,6 +233,7 @@ def test_dpie_auxiliary(ctx_factory, case): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) pytest.importorskip("pyfmmlib") @@ -275,20 +271,20 @@ def test_dpie_auxiliary(ctx_factory, case): # define the pre-scattered discretization pre_scat_discr = Discretization( - cl_ctx, scat_mesh, + actx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # use OpenCL random number generator to create a set of random # source locations for various variables being solved for dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) - qbx0, _ = QBXLayerPotentialSource( + qbx0 = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, fmm_level_to_order=SimpleExpansionOrderFinder( case.fmm_tolerance), fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) + ) # define the geometry dictionary geom_map = { @@ -298,7 +294,7 @@ def test_dpie_auxiliary(ctx_factory, case): # define points to evaluate the gradient at tgt_n = PointsTarget(bind(geom_map, - epsilon_off_boundary(dofdesc='obj0', epsilon=1.0))(queue)) + epsilon_off_boundary(dofdesc='obj0', epsilon=1.0))(actx)) geom_map['tgt'] = tgt_n # define the quantity that will have a derivative taken of it and @@ -317,14 +313,14 @@ def test_dpie_auxiliary(ctx_factory, case): return grad_g # compute output gradient evaluated at the desired object - tgrad = bind(geom_map, get_test_gradient(dofdesc="tgt"))(queue, **knl_kwargs) + tgrad = bind(geom_map, get_test_gradient(dofdesc="tgt"))(actx, **knl_kwargs) test_func_d = vector_from_device(queue, tgrad) # define the problem that will be solved test_tau_op = bind(geom_map, dpie0.subproblem_operator(tau_densities=tau_densities)) test_tau_rhs = bind(geom_map, - dpie0.subproblem_rhs_func(function=get_test_function))(queue, + dpie0.subproblem_rhs_func(function=get_test_function))(actx, **knl_kwargs) # set GMRES settings for solving @@ -335,7 +331,7 @@ def test_dpie_auxiliary(ctx_factory, case): stall_iterations=50, no_progress_factor=1.05) subprob_result = gmres( - test_tau_op.scipy_op(queue, "tau_densities", np.complex128, + test_tau_op.scipy_op(actx, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), test_tau_rhs, **gmres_settings) @@ -344,7 +340,7 @@ def test_dpie_auxiliary(ctx_factory, case): # compute the error between the associated derivative quantities tgrad = bind(geom_map, sym.grad(3, dpie0.subproblem_rep(tau_densities=tau_densities, - target='tgt')))(queue, tau_densities=dummy_tau, + target='tgt')))(actx, tau_densities=dummy_tau, **knl_kwargs) approx_d = vector_from_device(queue, tgrad) err = ( @@ -352,7 +348,7 @@ def test_dpie_auxiliary(ctx_factory, case): / calc_patch.norm(approx_d, np.inf)) # append error to the error list - eoc_rec.add_data_point(bind(qbx0, sym.h_max(qbx0.ambient_dim))(queue), err) + eoc_rec.add_data_point(bind(qbx0, sym.h_max(qbx0.ambient_dim))(actx), err) print(eoc_rec) @@ -383,6 +379,7 @@ def test_pec_dpie_extinction( cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) pytest.importorskip("pyfmmlib") @@ -406,7 +403,7 @@ def test_pec_dpie_extinction( tau_densities = sym.make_sym_vector("tau_densities", dpie.num_distinct_objects()) # get test source locations from the passed in case's queue - test_source = case.get_source(queue) + test_source = case.get_source(actx) # create the calculus patch and get calculus patch for targets calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) @@ -429,24 +426,24 @@ def test_pec_dpie_extinction( def get_incident_plane_wave_EHField(tgt): return bind((test_source, tgt), mw.get_sym_maxwell_plane_wave(amplitude_vec=Evar, v=uvar, - omega=dpie.k))(queue, u=u_dir, Ep=Ep, **knl_kwargs) + omega=dpie.k))(actx, u=u_dir, Ep=Ep, **knl_kwargs) # get the gradphi_inc field evaluated at some source locations def get_incident_gradphi(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_gradphi(u=uvar, - Ep=Evar, k=dpie.k, dofdesc=target))(queue, u=u_dir, + Ep=Evar, k=dpie.k, dofdesc=target))(actx, u=u_dir, Ep=Ep, **knl_kwargs) # get the incident plane wave div(A) def get_incident_divA(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_divA(u=uvar, Ep=Evar, - k=dpie.k, dofdesc=target))(queue, u=u_dir, Ep=Ep, **knl_kwargs) + k=dpie.k, dofdesc=target))(actx, u=u_dir, Ep=Ep, **knl_kwargs) # method to get vector potential and scalar potential for incident # E-M fields def get_incident_potentials(objects, target=None): return bind(objects, mw.get_sym_maxwell_planewave_potentials(u=uvar, - Ep=Evar, k=dpie.k, dofdesc=target))(queue, u=u_dir, + Ep=Evar, k=dpie.k, dofdesc=target))(actx, u=u_dir, Ep=Ep, **knl_kwargs) # define a smooth function to represent the density @@ -493,20 +490,20 @@ def test_pec_dpie_extinction( # define the pre-scattered discretization pre_scat_discr = Discretization( - cl_ctx, scat_mesh, + actx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # use OpenCL random number generator to create a set of random # source locations for various variables being solved for dpie0 = mw_dpie.DPIEOperator(geometry_list=geom_list) - qbx0, _ = QBXLayerPotentialSource( + qbx0 = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, fmm_level_to_order=SimpleExpansionOrderFinder( case.fmm_tolerance), fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) + ) # define the geometry dictionary geom_map = { @@ -525,13 +522,13 @@ def test_pec_dpie_extinction( n1 = len(dummy_phi) n2 = len(dummy_A) for i in range(0, n1): - dummy_phi[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) + dummy_phi[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(actx) for i in range(0, n2): - dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) + dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(actx) test_tau_op = bind(geom_map, dpie0.subproblem_operator(tau_densities=tau_densities)) test_tau_rhs = bind(geom_map, - dpie0.subproblem_rhs(A_densities=A_densities))(queue, + dpie0.subproblem_rhs(A_densities=A_densities))(actx, A_densities=dummy_A, **knl_kwargs) # set GMRES settings for solving @@ -542,7 +539,7 @@ def test_pec_dpie_extinction( stall_iterations=50, no_progress_factor=1.05) subprob_result = gmres( - test_tau_op.scipy_op(queue, "tau_densities", np.complex128, + test_tau_op.scipy_op(actx, "tau_densities", np.complex128, domains=dpie0.get_subproblem_domain_list(), **knl_kwargs), test_tau_rhs, **gmres_settings) @@ -554,11 +551,11 @@ def test_pec_dpie_extinction( def eval_test_repr_at(tgt): map = geom_map map['tgt'] = tgt - return bind(map, sym_repr0)(queue, phi_densities=dummy_phi, + return bind(map, sym_repr0)(actx, phi_densities=dummy_phi, A_densities=dummy_A, tau_densities=dummy_tau, **knl_kwargs) - pde_test_repr = EHField(vector_from_device(queue, + pde_test_repr = EHField(vector_from_device(actx, eval_test_repr_at(calc_patch_tgt))) maxwell_residuals = [ @@ -604,20 +601,20 @@ def test_pec_dpie_extinction( # define the pre-scattered discretization pre_scat_discr = Discretization( - cl_ctx, scat_mesh, + actx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # use OpenCL random number generator to create a set of random # source locations for various variables being solved for dpie0 = mw_dpie.DPIEOperatorEvanescent(geometry_list=geom_list) - qbx0, _ = QBXLayerPotentialSource( + qbx0 = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, fmm_level_to_order=SimpleExpansionOrderFinder( case.fmm_tolerance), fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) + ) # define the geometry dictionary geom_map = { @@ -630,7 +627,7 @@ def test_pec_dpie_extinction( # to be evaluated at tgt_n = PointsTarget( bind(geom_map, - epsilon_off_boundary(dofdesc='obj0', epsilon=1e-4))(queue)) + epsilon_off_boundary(dofdesc='obj0', epsilon=1e-4))(actx)) geom_map['tgt'] = tgt_n # define a dummy density, specifically to be used for the vector @@ -647,7 +644,7 @@ def test_pec_dpie_extinction( density = bind( qbx0, sym.xyz_to_tangential(sym.make_sym_vector("jxyz", 3)))( - queue, + actx, jxyz=sym.make_obj_array([ m.cos(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z), m.sin(0.5*x) * m.cos(0.5*y) * m.sin(0.5*z), @@ -674,18 +671,18 @@ def test_pec_dpie_extinction( for i in range(0, n1): if i < (n1-1): dummy_phi[i] = bind( - geom_map, dummy_density(dofdesc='obj0'))(queue) + geom_map, dummy_density(dofdesc='obj0'))(actx) else: dummy_phi[i] = 0.0 for i in range(0, n2): if i < 2: dummy_A[i] = density[i] elif i < (n2-1): - dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) + dummy_A[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(actx) else: dummy_A[i] = 0.0 for i in range(0, n3): - dummy_tau[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(queue) + dummy_tau[i] = bind(geom_map, dummy_density(dofdesc='obj0'))(actx) # check that the scalar density operator and representation are similar def vector_op_transform(vec_op_out): @@ -702,13 +699,13 @@ def test_pec_dpie_extinction( # evaluate operators at the dummy densities scalar_op_eval = vector_from_device(queue, bind(geom_map, scalar_op)( - queue, phi_densities=dummy_phi, **knl_kwargs)) + actx, phi_densities=dummy_phi, **knl_kwargs)) vector_op_eval = vector_from_device(queue, bind(geom_map, vector_op)( - queue, A_densities=dummy_A, **knl_kwargs)) + actx, A_densities=dummy_A, **knl_kwargs)) tau_op_eval = vector_from_device(queue, bind(geom_map, tau_op)( - queue, tau_densities=dummy_tau, **knl_kwargs)) + actx, tau_densities=dummy_tau, **knl_kwargs)) # define the vector operator equivalent representations #def vec_op_repr(A_densities, target): @@ -727,14 +724,14 @@ def test_pec_dpie_extinction( scalar_rep_eval = vector_from_device(queue, bind(geom_map, dpie0.scalar_potential_rep(phi_densities=phi_densities, - target='tgt'))(queue, phi_densities=dummy_phi, + target='tgt'))(actx, phi_densities=dummy_phi, **knl_kwargs)) vector_rep_eval = vector_from_device(queue, bind(geom_map, - vec_op_repr(A_densities=A_densities, target='tgt'))(queue, + vec_op_repr(A_densities=A_densities, target='tgt'))(actx, A_densities=dummy_A, **knl_kwargs)) tau_rep_eval = vector_from_device(queue, bind(geom_map, dpie0.subproblem_rep(tau_densities=tau_densities, - target='tgt'))(queue, tau_densities=dummy_tau, + target='tgt'))(actx, tau_densities=dummy_tau, **knl_kwargs)) axyz = sym.tangential_to_xyz(density_sym, dofdesc='obj0') @@ -748,7 +745,7 @@ def test_pec_dpie_extinction( geom_map, 0.5*axyz + nxcurlS0("avg") - nxcurlS0(+1) - )(queue, density=density, **knl_kwargs)) + )(actx, density=density, **knl_kwargs)) from sumpy.kernel import LaplaceKernel knl = LaplaceKernel(3) @@ -775,10 +772,10 @@ def test_pec_dpie_extinction( + 0.5*sym.tangential_to_xyz(density_sym, dofdesc='obj0'))) bound_jump_identity = bind(geom_map, jump_identity_sym) - jump_identity = bound_jump_identity(queue, density=density, **knl_kwargs) + jump_identity = bound_jump_identity(actx, density=density, **knl_kwargs) err = (norm(qbx0, queue, jump_identity, np.inf)) - print("ERROR", bind(qbx0, sym.h_max(qbx0.ambient_dim))(queue), err) + print("ERROR", bind(qbx0, sym.h_max(qbx0.ambient_dim))(actx), err) # compute the error between the operator values and the # representation values @@ -849,18 +846,18 @@ def test_pec_dpie_extinction( # define the pre-scattered discretization pre_scat_discr = Discretization( - cl_ctx, scat_mesh, + actx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) # obtain the QBX layer potential source object - qbx, _ = QBXLayerPotentialSource( + qbx = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, #fmm_order=False, qbx_order=case.qbx_order, fmm_level_to_order=SimpleExpansionOrderFinder( case.fmm_tolerance), fmm_backend=case.fmm_backend - ).with_refinement(_expansion_disturbance_tolerance=0.05) + ) # define the geometry dictionary #geom_map = {"g0": qbx} @@ -871,7 +868,7 @@ def test_pec_dpie_extinction( } # get the maximum mesh element edge length - h_max = bind(qbx, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(qbx, sym.h_max(qbx.ambient_dim))(actx) # define the scattered and observation discretization scat_discr = qbx.density_discr @@ -909,11 +906,11 @@ def test_pec_dpie_extinction( # values across the domain phi_rhs = bind(geom_map, dpie.phi_rhs(phi_inc=inc_phi, - gradphi_inc=inc_gradPhi))(queue, inc_phi=phi_inc, + gradphi_inc=inc_gradPhi))(actx, inc_phi=phi_inc, inc_gradPhi=inc_gradPhi_scat, **knl_kwargs) A_rhs = bind(geom_map, dpie.a_rhs(A_inc=inc_A, divA_inc=inc_divA))( - queue, inc_A=A_inc, inc_divA=inc_divA_scat, + actx, inc_A=A_inc, inc_divA=inc_divA_scat, **knl_kwargs) # set GMRES settings for solving @@ -925,7 +922,7 @@ def test_pec_dpie_extinction( # solve for the scalar potential densities gmres_result = gmres( - phi_op.scipy_op(queue, "phi_densities", + phi_op.scipy_op(actx, "phi_densities", np.complex128, domains=dpie.get_scalar_domain_list(), **knl_kwargs), @@ -934,7 +931,7 @@ def test_pec_dpie_extinction( # solve for the vector potential densities gmres_result = gmres( - A_op.scipy_op(queue, "A_densities", np.complex128, + A_op.scipy_op(actx, "A_densities", np.complex128, domains=dpie.get_vector_domain_list(), **knl_kwargs), A_rhs, **gmres_settings) A_dens = gmres_result.solution @@ -943,10 +940,10 @@ def test_pec_dpie_extinction( tau_op = bind(geom_map, dpie.subproblem_operator(tau_densities=tau_densities)) tau_rhs = bind(geom_map, - dpie.subproblem_rhs(A_densities=A_densities))(queue, + dpie.subproblem_rhs(A_densities=A_densities))(actx, A_densities=A_dens, **knl_kwargs) gmres_result = gmres( - tau_op.scipy_op(queue, "tau_densities", np.complex128, + tau_op.scipy_op(actx, "tau_densities", np.complex128, domains=dpie.get_subproblem_domain_list(), **knl_kwargs), tau_rhs, **gmres_settings) @@ -958,11 +955,11 @@ def test_pec_dpie_extinction( tmap['tgt'] = tgt phi = vector_from_device(queue, bind(tmap, dpie.scalar_potential_rep(phi_densities=phi_densities, - target='tgt'))(queue, phi_densities=phi_dens, + target='tgt'))(actx, phi_densities=phi_dens, **knl_kwargs)) Axyz = vector_from_device(queue, bind(tmap, dpie.vector_potential_rep(A_densities=A_densities, - target='tgt'))(queue, A_densities=A_dens, + target='tgt'))(actx, A_densities=A_dens, **knl_kwargs)) return (phi, Axyz) @@ -985,11 +982,11 @@ def test_pec_dpie_extinction( if source is not None: places['obj0'] = source - return bind(places, sym_repr)(queue, phi_densities=phi_dens, + return bind(places, sym_repr)(actx, phi_densities=phi_dens, A_densities=A_dens, tau_densities=tau_dens, **knl_kwargs) - pde_test_repr = EHField(vector_from_device(queue, + pde_test_repr = EHField(vector_from_device(actx, eval_repr_at(calc_patch_tgt))) maxwell_residuals = [ @@ -1026,9 +1023,9 @@ def test_pec_dpie_extinction( A_densities, inc_A, dofdesc="obj0") scalar_bc_values = bind(geom_map, phi_pec_bc_resid)( - queue, phi_densities=phi_dens, inc_phi=phi_inc, **knl_kwargs) + actx, phi_densities=phi_dens, inc_phi=phi_inc, **knl_kwargs) vector_bc_values = bind(geom_map, A_pec_bc_resid)( - queue, A_densities=A_dens, inc_A=A_inc, **knl_kwargs) + actx, A_densities=A_dens, inc_A=A_inc, **knl_kwargs) def scat_norm(f): return norm(qbx, queue, f, p=np.inf) @@ -1055,7 +1052,7 @@ def test_pec_dpie_extinction( from meshmode.discretization.visualization import make_visualizer bdry_vis = make_visualizer(queue, scat_discr, case.target_order+3) - bdry_normals = bind(scat_discr, sym.normal(3))(queue)\ + bdry_normals = bind(scat_discr, sym.normal(3))(actx)\ .as_vector(dtype=object) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ -- GitLab