From 8cead4a437b89be2b86ba10c7b19185d46e015c1 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 10 Dec 2017 13:27:15 -0600 Subject: [PATCH 001/268] Remove filter_target_lists_in_*_order in favor of using boxtree.tree.ParticleListFilter. This fixes a few deprecation warnings. --- pytential/qbx/geometry.py | 5 +++-- pytential/qbx/utils.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 71160948..1295251f 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -781,8 +781,9 @@ class QBXFMMGeometryData(object): nqbx_centers = self.ncenters flags[:nqbx_centers] = 0 - from boxtree.tree import filter_target_lists_in_tree_order - result = filter_target_lists_in_tree_order(queue, self.tree(), flags) + result = ( + self.particle_list_filter() + .filter_target_lists_in_tree_order(queue, self.tree(), flags)) logger.info("find non-qbx box target lists: done") diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index d03e8365..ecb939de 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -154,6 +154,11 @@ class TreeCodeContainer(object): from boxtree.area_query import PeerListFinder return PeerListFinder(self.cl_context) + @memoize_method + def particle_list_filter(self): + from boxtree.tree import ParticleListFilter + return ParticleListFilter(self.cl_context) + # }}} @@ -170,6 +175,9 @@ class TreeCodeContainerMixin(object): def peer_list_finder(self): return self.tree_code_container.peer_list_finder() + def particle_list_filter(self): + return self.tree_code_container.particle_list_filter() + # }}} @@ -180,9 +188,10 @@ class TreeWranglerBase(object): def build_tree(self, lpot_source, targets_list=(), use_stage2_discr=False): tb = self.code_container.build_tree() + plfilt = self.code_container.particle_list_filter() from pytential.qbx.utils import build_tree_with_qbx_metadata return build_tree_with_qbx_metadata( - self.queue, tb, lpot_source, targets_list=targets_list, + self.queue, tb, plfilt, lpot_source, targets_list=targets_list, use_stage2_discr=use_stage2_discr) def find_peer_lists(self, tree): @@ -448,7 +457,7 @@ MAX_REFINE_WEIGHT = 64 def build_tree_with_qbx_metadata( - queue, tree_builder, lpot_source, targets_list=(), + queue, tree_builder, particle_list_filter, lpot_source, targets_list=(), use_stage2_discr=False): """Return a :class:`TreeWithQBXMetadata` built from the given layer potential source. This contains particles of four different types: @@ -542,9 +551,9 @@ def build_tree_with_qbx_metadata( flags[particle_slice].fill(1) flags.finish() - from boxtree.tree import filter_target_lists_in_user_order box_to_class = ( - filter_target_lists_in_user_order(queue, tree, flags) + particle_list_filter + .filter_target_lists_in_user_order(queue, tree, flags) .with_queue(queue)) if fixup: -- GitLab From a405a0f3ae3ba1a39e769c7a2d3acad2f5ab72f7 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 10 Dec 2017 15:57:43 -0600 Subject: [PATCH 002/268] Fix a missing level of indirection. --- pytential/qbx/geometry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 1295251f..e9e7fd92 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -781,9 +781,9 @@ class QBXFMMGeometryData(object): nqbx_centers = self.ncenters flags[:nqbx_centers] = 0 - result = ( - self.particle_list_filter() - .filter_target_lists_in_tree_order(queue, self.tree(), flags)) + tree = self.tree() + plfilt = self.code_getter.particle_list_filter() + result = plfilt.filter_target_lists_in_tree_order(queue, tree, flags) logger.info("find non-qbx box target lists: done") -- GitLab From 5050a51db49ed4135170fd2b351db61040f7dcfb Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 22 Dec 2017 00:19:45 -0600 Subject: [PATCH 003/268] Upgrade conda test and install instructions to pocl 1.0 --- .test-conda-env-py3.yml | 2 +- doc/misc.rst | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index b4f204c7..37c60864 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -6,7 +6,7 @@ dependencies: - git - conda-forge::numpy - conda-forge::sympy -- pocl=0.13 +- pocl=1.0 - islpy - pyopencl - python=3.5 diff --git a/doc/misc.rst b/doc/misc.rst index 14f3f764..874935f8 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -30,7 +30,7 @@ MacOS support is in the works. #. ``conda config --add channels conda-forge`` -#. ``conda install git pip pocl=0.13 islpy pyopencl sympy pyfmmlib pytest`` +#. ``conda install git pip pocl islpy pyopencl sympy pyfmmlib pytest`` #. Type the following command:: @@ -45,14 +45,6 @@ You may also like to add this to a startup file (like :file:`$HOME/.bashrc`) or After this, you should be able to run the `tests `_ or `examples `_. -.. note:: - - You may have noticed that we prescribed pocl version 0.13 above. That's - because newer versions have a `bug - `_ that we haven't - tracked down just yet. Until this bug is found, we discourage the use of - pocl 0.14 as results may be silently inaccurate. - Troubleshooting the Installation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- GitLab From 2ae9ae25fa1e7b9dff4477663ee87bd9fac8deec Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 22 Dec 2017 19:12:16 -0500 Subject: [PATCH 004/268] Use separate disk cache directory for conda bulid --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 334a41d0..b19f42b7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,10 @@ Python 3.6 POCL: Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine + + - export XDG_CACHE_HOME=$HOME/.cache-conda + - mkdir -p $XDG_CACHE_HOME + - CONDA_ENVIRONMENT=.test-conda-env-py3.yml - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh -- GitLab From 7e885e41f5458e33c72c72d440fd414639e6af91 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 23 Dec 2017 11:05:05 -0500 Subject: [PATCH 005/268] Revert "Use separate disk cache directory for conda bulid" This reverts commit 2ae9ae25fa1e7b9dff4477663ee87bd9fac8deec. --- .gitlab-ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b19f42b7..334a41d0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,10 +29,6 @@ Python 3.6 POCL: Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine - - - export XDG_CACHE_HOME=$HOME/.cache-conda - - mkdir -p $XDG_CACHE_HOME - - CONDA_ENVIRONMENT=.test-conda-env-py3.yml - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh -- GitLab From faaab9362896a2bd3ca4f1ee8e26c52d500ff6da Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 12 Jan 2018 15:14:25 -0600 Subject: [PATCH 006/268] Assign a refine weight of zero to QBX needing targets. This improves FMM balancing (closes #72) --- pytential/qbx/geometry.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index e9e7fd92..7116edd2 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -494,11 +494,17 @@ class QBXFMMGeometryData(object): self.coord_dtype) target_radii[:self.ncenters] = self.expansion_radii() - # FIXME: https://gitlab.tiker.net/inducer/pytential/issues/72 - # refine_weights = cl.array.zeros(queue, nparticles, dtype=np.int32) - # refine_weights[:nsources] = 1 refine_weights = cl.array.empty(queue, nparticles, dtype=np.int32) - refine_weights.fill(1) + + # Assign a weight of 1 to all sources, QBX centers, and conventional + # (non-QBX) targets. Assign a weight of 0 to all targets that need + # QBX centers. The potential at the latter targets is mediated + # entirely by the QBX center, so as a matter of evaluation cost, + # their location in the tree is irrelevant. + refine_weights[:-target_info.ntargets] = 1 + user_ttc = self.user_target_to_center().with_queue(queue) + refine_weights[-target_info.ntargets:] = ( + user_ttc == target_state.NO_QBX_NEEDED).astype(np.int32) refine_weights.finish() @@ -679,8 +685,7 @@ class QBXFMMGeometryData(object): if a center needs to be used, but none was found. See :meth:`center_to_tree_targets` for the reverse look-up table. - Shape: ``[ntargets]`` of :attr:`boxtree.Tree.particle_id_dtype`, with extra - values from :class:`target_state` allowed. Targets occur in user order. + Shape: ``[ntargets]`` of :class:`numpy.int32`. Targets occur in user order. """ from pytential.qbx.target_assoc import associate_targets_to_qbx_centers tgt_info = self.target_info() @@ -689,7 +694,7 @@ class QBXFMMGeometryData(object): with cl.CommandQueue(self.cl_context) as queue: target_side_prefs = (self - .target_side_preferences()[self.ncenters:].get(queue=queue)) + .target_side_preferences()[self.ncenters:].get(queue=queue)) target_discrs_and_qbx_sides = [( PointsTarget(tgt_info.targets[:, self.ncenters:]), @@ -706,9 +711,7 @@ class QBXFMMGeometryData(object): target_association_tolerance=( self.target_association_tolerance)) - tree = self.tree() - - result = cl.array.empty(queue, tgt_info.ntargets, tree.particle_id_dtype) + result = cl.array.empty(queue, tgt_info.ntargets, np.int32) result[:self.ncenters].fill(target_state.NO_QBX_NEEDED) result[self.ncenters:] = tgt_assoc_result.target_to_center @@ -727,7 +730,11 @@ class QBXFMMGeometryData(object): with cl.CommandQueue(self.cl_context) as queue: logger.info("build center -> targets lookup table: start") - tree_ttc = cl.array.empty_like(user_ttc).with_queue(queue) + user_ttc = (user_ttc + .with_queue(queue) + .astype(self.tree().particle_id_dtype)) + + tree_ttc = cl.array.empty_like(user_ttc) tree_ttc[self.tree().sorted_target_ids] = user_ttc filtered_tree_ttc = cl.array.empty(queue, tree_ttc.shape, tree_ttc.dtype) -- GitLab From 2fa7e6b61727ef6619be19a0a8d84cfeba8ea455 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 12 Jan 2018 16:24:07 -0600 Subject: [PATCH 007/268] Lpot source: Disallow passing 'npanels' to _expansion_radii(). --- pytential/qbx/__init__.py | 9 +++------ test/test_global_qbx.py | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index c3ca7d95..82ef3e19 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -409,12 +409,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def _expansion_radii(self, last_dim_length): if last_dim_length == "npanels": - # FIXME: Make this an error - - from warnings import warn - warn("Passing 'npanels' as last_dim_length to _expansion_radii is " - "deprecated. Expansion radii should be allowed to vary " - "within a panel.", stacklevel=3) + raise ValueError( + "Passing 'npanels' as last_dim_length to _expansion_radii is " + "not allowed. Allowed values are 'nsources' and 'ncenters'.") with cl.CommandQueue(self.cl_context) as queue: return (self._panel_sizes(last_dim_length).with_queue(queue) * 0.5 diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index cc0f26f0..c7609387 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -121,7 +121,7 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): int_centers = np.array([axis.get(queue) for axis in int_centers]) ext_centers = get_centers_on_side(lpot_source, +1) ext_centers = np.array([axis.get(queue) for axis in ext_centers]) - expansion_radii = lpot_source._expansion_radii("npanels").get(queue) + expansion_radii = lpot_source._expansion_radii("nsources").get(queue) panel_sizes = lpot_source._panel_sizes("npanels").get(queue) fine_panel_sizes = lpot_source._fine_panel_sizes("npanels").get(queue) @@ -150,8 +150,8 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): # A center cannot be closer to another panel than to its originating # panel. - rad = expansion_radii[centers_panel.element_nr] - assert dist >= rad * (1-expansion_disturbance_tolerance), \ + rad = expansion_radii[centers_panel.discr_slice] + assert (dist >= rad * (1-expansion_disturbance_tolerance)).all(), \ (dist, rad, centers_panel.element_nr, sources_panel.element_nr) def check_sufficient_quadrature_resolution(centers_panel, sources_panel): -- GitLab From 6187aba2170a0e6e306df27a49f95b18ed00c8e7 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 13 Jan 2018 00:01:12 -0600 Subject: [PATCH 008/268] user_target_to_center(): Use the same dtype as target_to_center --- pytential/qbx/geometry.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 7116edd2..ca53df5a 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -685,7 +685,8 @@ class QBXFMMGeometryData(object): if a center needs to be used, but none was found. See :meth:`center_to_tree_targets` for the reverse look-up table. - Shape: ``[ntargets]`` of :class:`numpy.int32`. Targets occur in user order. + Shape: ``[ntargets]`` of :attr:`boxtree.Tree.particle_id_dtype`, with extra + values from :class:`target_state` allowed. Targets occur in user order. """ from pytential.qbx.target_assoc import associate_targets_to_qbx_centers tgt_info = self.target_info() @@ -711,7 +712,8 @@ class QBXFMMGeometryData(object): target_association_tolerance=( self.target_association_tolerance)) - result = cl.array.empty(queue, tgt_info.ntargets, np.int32) + result = cl.array.empty(queue, tgt_info.ntargets, + tgt_assoc_result.target_to_center.dtype) result[:self.ncenters].fill(target_state.NO_QBX_NEEDED) result[self.ncenters:] = tgt_assoc_result.target_to_center @@ -730,11 +732,7 @@ class QBXFMMGeometryData(object): with cl.CommandQueue(self.cl_context) as queue: logger.info("build center -> targets lookup table: start") - user_ttc = (user_ttc - .with_queue(queue) - .astype(self.tree().particle_id_dtype)) - - tree_ttc = cl.array.empty_like(user_ttc) + tree_ttc = cl.array.empty_like(user_ttc).with_queue(queue) tree_ttc[self.tree().sorted_target_ids] = user_ttc filtered_tree_ttc = cl.array.empty(queue, tree_ttc.shape, tree_ttc.dtype) -- GitLab From dd22feb5ddfda9897f31ce1d74335ad3bbf63e49 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 16 Jan 2018 19:26:44 -0600 Subject: [PATCH 009/268] Clean up and test examples dir (Closes #86) --- .gitlab-ci.yml | 16 +- examples/fmm-error.py | 129 ++++++------- examples/{ => geometries}/blob-2d.step | 0 examples/{ => geometries}/circle.step | 0 examples/{ => geometries}/circles.step | 0 examples/{ => geometries}/ellipsoid.step | 0 examples/{ => geometries}/molecule.step | 0 examples/{ => geometries}/two-balls.step | 0 .../two-cylinders-smooth.step | 0 examples/helmholtz-dirichlet.py | 2 +- examples/laplace-dirichlet-3d.py | 170 +++++++++++++++++ examples/layerpot-3d.py | 125 +++++++------ examples/layerpot.py | 173 +++++++++--------- examples/perf-model.py | 18 -- examples/scaling-study.py | 4 +- experiments/README.md | 7 + {examples => experiments}/cahn-hilliard.py | 0 .../find-photonic-mode-sk.py | 0 .../find-photonic-mode.py | 0 .../helmholtz-expression-tree.py | 0 {examples => experiments}/maxwell.py | 0 {examples => experiments}/maxwell_sphere.py | 0 {examples => experiments}/poisson.py | 0 .../qbx-tangential-deriv-jump.py | 0 .../stokes-2d-interior.py | 0 .../two-domain-helmholtz.py | 0 26 files changed, 416 insertions(+), 228 deletions(-) rename examples/{ => geometries}/blob-2d.step (100%) rename examples/{ => geometries}/circle.step (100%) rename examples/{ => geometries}/circles.step (100%) rename examples/{ => geometries}/ellipsoid.step (100%) rename examples/{ => geometries}/molecule.step (100%) rename examples/{ => geometries}/two-balls.step (100%) rename examples/{ => geometries}/two-cylinders-smooth.step (100%) create mode 100644 examples/laplace-dirichlet-3d.py delete mode 100644 examples/perf-model.py create mode 100644 experiments/README.md rename {examples => experiments}/cahn-hilliard.py (100%) rename {examples => experiments}/find-photonic-mode-sk.py (100%) rename {examples => experiments}/find-photonic-mode.py (100%) rename {examples => experiments}/helmholtz-expression-tree.py (100%) rename {examples => experiments}/maxwell.py (100%) rename {examples => experiments}/maxwell_sphere.py (100%) rename {examples => experiments}/poisson.py (100%) rename {examples => experiments}/qbx-tangential-deriv-jump.py (100%) rename {examples => experiments}/stokes-2d-interior.py (100%) rename {examples => experiments}/two-domain-helmholtz.py (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 334a41d0..a05b560d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,6 +26,20 @@ Python 3.6 POCL: except: - tags +Python 3.6 POCL Examples: + script: + - export PY_EXE=python3.6 + - export PYOPENCL_TEST=portable + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-py-project-and-run-examples.sh + - ". ./build-py-project-and-run-examples.sh" + tags: + - python3.6 + - pocl + - large-node + except: + - tags + Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine @@ -66,7 +80,7 @@ Documentation: Flake8: script: - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh - - ". ./prepare-and-run-flake8.sh pytential test" + - ". ./prepare-and-run-flake8.sh pytential test examples" tags: - python3.5 except: diff --git a/examples/fmm-error.py b/examples/fmm-error.py index fea97c99..4a15f18f 100644 --- a/examples/fmm-error.py +++ b/examples/fmm-error.py @@ -6,89 +6,90 @@ from meshmode.mesh.generation import ( # noqa from sumpy.visualization import FieldPlotter from sumpy.kernel import LaplaceKernel, HelmholtzKernel -import faulthandler -faulthandler.enable() -import logging -logging.basicConfig(level=logging.INFO) +def main(): + import logging + logging.basicConfig(level=logging.WARNING) # INFO for more progress info -cl_ctx = cl.create_some_context() -queue = cl.CommandQueue(cl_ctx) + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) -target_order = 16 -qbx_order = 3 -nelements = 60 -mode_nr = 0 + target_order = 16 + qbx_order = 3 + nelements = 60 + mode_nr = 0 -k = 0 -if k: - kernel = HelmholtzKernel("k") -else: - kernel = LaplaceKernel() -#kernel = OneKernel() + k = 0 + if k: + kernel = HelmholtzKernel(2) + else: + kernel = LaplaceKernel(2) + #kernel = OneKernel() -mesh = make_curve_mesh( - #lambda t: ellipse(1, t), - starfish, - np.linspace(0, 1, nelements+1), - target_order) + mesh = make_curve_mesh( + #lambda t: ellipse(1, t), + starfish, + np.linspace(0, 1, nelements+1), + target_order) -from pytential.qbx import QBXLayerPotentialSource -from meshmode.discretization import Discretization -from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory -density_discr = Discretization( - cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(target_order)) + pre_density_discr = Discretization( + cl_ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) -qbx = QBXLayerPotentialSource( - density_discr, fine_order=2*target_order, - qbx_order=qbx_order, fmm_order=qbx_order) -slow_qbx = QBXLayerPotentialSource( - density_discr, fine_order=2*target_order, - qbx_order=qbx_order, fmm_order=False) + slow_qbx, _ = QBXLayerPotentialSource( + pre_density_discr, fine_order=2*target_order, + qbx_order=qbx_order, fmm_order=False, + target_association_tolerance=.05 + ).with_refinement() + qbx = slow_qbx.copy(fmm_order=10) + density_discr = slow_qbx.density_discr -nodes = density_discr.nodes().with_queue(queue) + nodes = density_discr.nodes().with_queue(queue) -angle = cl.clmath.atan2(nodes[1], nodes[0]) + angle = cl.clmath.atan2(nodes[1], nodes[0]) -from pytential import bind, sym -d = sym.Derivative() -#op = d.nabla[0] * d(sym.S(kernel, sym.var("sigma"))) -#op = sym.D(kernel, sym.var("sigma")) -op = sym.S(kernel, sym.var("sigma")) + from pytential import bind, sym + #op = sym.d_dx(sym.S(kernel, sym.var("sigma")), qbx_forced_limit=None) + #op = sym.D(kernel, sym.var("sigma"), qbx_forced_limit=None) + op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None) -sigma = cl.clmath.cos(mode_nr*angle) + sigma = cl.clmath.cos(mode_nr*angle) -if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + if isinstance(kernel, HelmholtzKernel): + sigma = sigma.astype(np.complex128) -bound_bdry_op = bind(qbx, op) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=600) + from pytential.target import PointsTarget -fplot = FieldPlotter(np.zeros(2), extent=5, npoints=600) -from pytential.target import PointsTarget + fld_in_vol = bind( + (slow_qbx, PointsTarget(fplot.points)), + op)(queue, sigma=sigma, k=k).get() -fld_in_vol = bind( - (slow_qbx, PointsTarget(fplot.points)), - op)(queue, sigma=sigma, k=k).get() + fmm_fld_in_vol = bind( + (qbx, PointsTarget(fplot.points)), + op)(queue, sigma=sigma, k=k).get() -fmm_fld_in_vol = bind( - (qbx, PointsTarget(fplot.points)), - op)(queue, sigma=sigma, k=k).get() + err = fmm_fld_in_vol-fld_in_vol + im = fplot.show_scalar_in_matplotlib(np.log10(np.abs(err))) -err = fmm_fld_in_vol-fld_in_vol -im = fplot.show_scalar_in_matplotlib(np.log10(np.abs(err))) + from matplotlib.colors import Normalize + im.set_norm(Normalize(vmin=-12, vmax=0)) -from matplotlib.colors import Normalize -im.set_norm(Normalize(vmin=-6, vmax=0)) + import matplotlib.pyplot as pt + from matplotlib.ticker import NullFormatter + pt.gca().xaxis.set_major_formatter(NullFormatter()) + pt.gca().yaxis.set_major_formatter(NullFormatter()) -import matplotlib.pyplot as pt -from matplotlib.ticker import NullFormatter -pt.gca().xaxis.set_major_formatter(NullFormatter()) -pt.gca().yaxis.set_major_formatter(NullFormatter()) + cb = pt.colorbar(shrink=0.9) + cb.set_label(r"$\log_{10}(\mathdefault{Error})$") -cb = pt.colorbar(shrink=0.9) -cb.set_label(r"$\log_{10}(\mathdefault{Error})$") + pt.savefig("fmm-error-order-%d.pdf" % qbx_order) -pt.savefig("fmm-error-order-%d.pdf" % qbx_order) + +if __name__ == "__main__": + main() diff --git a/examples/blob-2d.step b/examples/geometries/blob-2d.step similarity index 100% rename from examples/blob-2d.step rename to examples/geometries/blob-2d.step diff --git a/examples/circle.step b/examples/geometries/circle.step similarity index 100% rename from examples/circle.step rename to examples/geometries/circle.step diff --git a/examples/circles.step b/examples/geometries/circles.step similarity index 100% rename from examples/circles.step rename to examples/geometries/circles.step diff --git a/examples/ellipsoid.step b/examples/geometries/ellipsoid.step similarity index 100% rename from examples/ellipsoid.step rename to examples/geometries/ellipsoid.step diff --git a/examples/molecule.step b/examples/geometries/molecule.step similarity index 100% rename from examples/molecule.step rename to examples/geometries/molecule.step diff --git a/examples/two-balls.step b/examples/geometries/two-balls.step similarity index 100% rename from examples/two-balls.step rename to examples/geometries/two-balls.step diff --git a/examples/two-cylinders-smooth.step b/examples/geometries/two-cylinders-smooth.step similarity index 100% rename from examples/two-cylinders-smooth.step rename to examples/geometries/two-cylinders-smooth.step diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index d6d9baa6..d5498fe3 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -25,7 +25,7 @@ k = 3 def main(): import logging - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) diff --git a/examples/laplace-dirichlet-3d.py b/examples/laplace-dirichlet-3d.py new file mode 100644 index 00000000..b2e895ca --- /dev/null +++ b/examples/laplace-dirichlet-3d.py @@ -0,0 +1,170 @@ +import numpy as np +import numpy.linalg as la +import pyopencl as cl +import pyopencl.clmath # noqa + +from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + +from pytential import bind, sym, norm # noqa +from pytential.target import PointsTarget + +# {{{ set some constants for use below + +nelements = 20 +bdry_quad_order = 4 +mesh_order = bdry_quad_order +qbx_order = bdry_quad_order +bdry_ovsmp_quad_order = 4*bdry_quad_order +fmm_order = 3 + +# }}} + + +def main(): + import logging + logging.basicConfig(level=logging.WARNING) # INFO for more progress info + + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + + from meshmode.mesh.generation import generate_torus + + rout = 10 + rin = 1 + if 1: + base_mesh = generate_torus( + rout, rin, 40, 4, + mesh_order) + + from meshmode.mesh.processing import affine_map, merge_disjoint_meshes + # nx = 1 + # ny = 1 + nz = 1 + dz = 0 + meshes = [ + affine_map( + base_mesh, + A=np.diag([1, 1, 1]), + b=np.array([0, 0, iz*dz])) + for iz in range(nz)] + + mesh = merge_disjoint_meshes(meshes, single_group=True) + + if 0: + from meshmode.mesh.visualization import draw_curve + draw_curve(mesh) + import matplotlib.pyplot as plt + plt.show() + + pre_density_discr = Discretization( + cl_ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) + + from pytential.qbx import ( + QBXLayerPotentialSource, QBXTargetAssociationFailedException) + qbx, _ = QBXLayerPotentialSource( + pre_density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, + fmm_order=fmm_order + ).with_refinement() + density_discr = qbx.density_discr + + # {{{ describe bvp + + from sumpy.kernel import LaplaceKernel + kernel = LaplaceKernel(3) + + cse = sym.cse + + sigma_sym = sym.var("sigma") + #sqrt_w = sym.sqrt_jac_q_weight(3) + sqrt_w = 1 + inv_sqrt_w_sigma = cse(sigma_sym/sqrt_w) + + # -1 for interior Dirichlet + # +1 for exterior Dirichlet + loc_sign = +1 + + bdry_op_sym = (loc_sign*0.5*sigma_sym + + sqrt_w*( + sym.S(kernel, inv_sqrt_w_sigma) + + sym.D(kernel, inv_sqrt_w_sigma) + )) + + # }}} + + bound_op = bind(qbx, bdry_op_sym) + + # {{{ fix rhs and solve + + nodes = density_discr.nodes().with_queue(queue) + source = np.array([rout, 0, 0]) + + def u_incoming_func(x): + # return 1/cl.clmath.sqrt( (x[0] - source[0])**2 + # +(x[1] - source[1])**2 + # +(x[2] - source[2])**2 ) + return 1.0/la.norm(x.get()-source[:, None], axis=0) + + bc = cl.array.to_device(queue, u_incoming_func(nodes)) + + bvp_rhs = bind(qbx, sqrt_w*sym.var("bc"))(queue, bc=bc) + + from pytential.solve import gmres + gmres_result = gmres( + bound_op.scipy_op(queue, "sigma", dtype=np.float64), + bvp_rhs, tol=1e-14, progress=True, + stall_iterations=0, + hard_failure=True) + + sigma = bind(qbx, sym.var("sigma")/sqrt_w)(queue, sigma=gmres_result.solution) + + # }}} + + from meshmode.discretization.visualization import make_visualizer + bdry_vis = make_visualizer(queue, density_discr, 20) + bdry_vis.write_vtk_file("laplace.vtu", [ + ("sigma", sigma), + ]) + + # {{{ postprocess/visualize + + repr_kwargs = dict(qbx_forced_limit=None) + representation_sym = ( + sym.S(kernel, inv_sqrt_w_sigma, **repr_kwargs) + + sym.D(kernel, inv_sqrt_w_sigma, **repr_kwargs)) + + from sumpy.visualization import FieldPlotter + fplot = FieldPlotter(np.zeros(3), extent=20, npoints=50) + + targets = cl.array.to_device(queue, fplot.points) + + qbx_stick_out = qbx.copy(target_stick_out_factor=0.2) + + try: + fld_in_vol = bind( + (qbx_stick_out, PointsTarget(targets)), + representation_sym)(queue, sigma=sigma).get() + except QBXTargetAssociationFailedException as e: + fplot.write_vtk_file( + "failed-targets.vts", + [ + ("failed", e.failed_target_flags.get(queue)) + ] + ) + raise + + #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) + fplot.write_vtk_file( + "potential.vts", + [ + ("potential", fld_in_vol), + ] + ) + + # }}} + + +if __name__ == "__main__": + main() diff --git a/examples/layerpot-3d.py b/examples/layerpot-3d.py index 0a35ebd7..cc620997 100644 --- a/examples/layerpot-3d.py +++ b/examples/layerpot-3d.py @@ -21,10 +21,10 @@ qbx_order = 3 mode_nr = 4 if 1: - cad_file_name = "ellipsoid.step" + cad_file_name = "geometries/ellipsoid.step" h = 0.6 else: - cad_file_name = "two-cylinders-smooth.step" + cad_file_name = "geometries/two-cylinders-smooth.step" h = 0.4 k = 0 @@ -34,76 +34,85 @@ else: kernel = LaplaceKernel(3) #kernel = OneKernel() -from meshmode.mesh.io import generate_gmsh, FileSource -mesh = generate_gmsh( - FileSource(cad_file_name), 2, order=2, - other_options=["-string", "Mesh.CharacteristicLengthMax = %g;" % h]) -from meshmode.mesh.processing import perform_flips -# Flip elements--gmsh generates inside-out geometry. -mesh = perform_flips(mesh, np.ones(mesh.nelements)) +def main(): + import logging + logging.basicConfig(level=logging.WARNING) # INFO for more progress info -from meshmode.mesh.processing import find_bounding_box -bbox_min, bbox_max = find_bounding_box(mesh) -bbox_center = 0.5*(bbox_min+bbox_max) -bbox_size = max(bbox_max-bbox_min) / 2 + from meshmode.mesh.io import generate_gmsh, FileSource + mesh = generate_gmsh( + FileSource(cad_file_name), 2, order=2, + other_options=["-string", "Mesh.CharacteristicLengthMax = %g;" % h]) -logger.info("%d elements" % mesh.nelements) + from meshmode.mesh.processing import perform_flips + # Flip elements--gmsh generates inside-out geometry. + mesh = perform_flips(mesh, np.ones(mesh.nelements)) -from pytential.qbx import QBXLayerPotentialSource -from meshmode.discretization import Discretization -from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory + from meshmode.mesh.processing import find_bounding_box + bbox_min, bbox_max = find_bounding_box(mesh) + bbox_center = 0.5*(bbox_min+bbox_max) + bbox_size = max(bbox_max-bbox_min) / 2 -density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + logger.info("%d elements" % mesh.nelements) -qbx, _ = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, - fmm_order=qbx_order + 3, - target_association_tolerance=0.15).with_refinement() + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory -nodes = density_discr.nodes().with_queue(queue) + density_discr = Discretization( + cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) -angle = cl.clmath.atan2(nodes[1], nodes[0]) + qbx, _ = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, + fmm_order=qbx_order + 3, + target_association_tolerance=0.15).with_refinement() -from pytential import bind, sym -#op = sym.d_dx(sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None)) -op = sym.D(kernel, sym.var("sigma"), qbx_forced_limit=None) -#op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None) + nodes = density_discr.nodes().with_queue(queue) -sigma = cl.clmath.cos(mode_nr*angle) -if 0: - sigma = 0*angle - from random import randrange - for i in range(5): - sigma[randrange(len(sigma))] = 1 + angle = cl.clmath.atan2(nodes[1], nodes[0]) -if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + from pytential import bind, sym + #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None)) + op = sym.D(kernel, sym.var("sigma"), qbx_forced_limit=None) + #op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None) -fplot = FieldPlotter(bbox_center, extent=3.5*bbox_size, npoints=150) + sigma = cl.clmath.cos(mode_nr*angle) + if 0: + sigma = 0*angle + from random import randrange + for i in range(5): + sigma[randrange(len(sigma))] = 1 -from pytential.target import PointsTarget -fld_in_vol = bind( - (qbx, PointsTarget(fplot.points)), - op)(queue, sigma=sigma, k=k).get() + if isinstance(kernel, HelmholtzKernel): + sigma = sigma.astype(np.complex128) -#fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) -fplot.write_vtk_file( - "potential.vts", - [ - ("potential", fld_in_vol) - ] - ) + fplot = FieldPlotter(bbox_center, extent=3.5*bbox_size, npoints=150) -bdry_normals = bind( - density_discr, - sym.normal(density_discr.ambient_dim))(queue).as_vector(dtype=object) + from pytential.target import PointsTarget + fld_in_vol = bind( + (qbx, PointsTarget(fplot.points)), + op)(queue, sigma=sigma, k=k).get() -from meshmode.discretization.visualization import make_visualizer -bdry_vis = make_visualizer(queue, density_discr, target_order) + #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) + fplot.write_vtk_file( + "potential.vts", + [ + ("potential", fld_in_vol) + ] + ) -bdry_vis.write_vtk_file("source.vtu", [ - ("sigma", sigma), - ("bdry_normals", bdry_normals), - ]) + bdry_normals = bind( + density_discr, + sym.normal(density_discr.ambient_dim))(queue).as_vector(dtype=object) + + from meshmode.discretization.visualization import make_visualizer + bdry_vis = make_visualizer(queue, density_discr, target_order) + + bdry_vis.write_vtk_file("source.vtu", [ + ("sigma", sigma), + ("bdry_normals", bdry_normals), + ]) + + +if __name__ == "__main__": + main() diff --git a/examples/layerpot.py b/examples/layerpot.py index 0371b1ff..f5e56de3 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -36,96 +36,101 @@ else: kernel_kwargs = {} #kernel = OneKernel() -from meshmode.mesh.generation import ( # noqa - make_curve_mesh, starfish, ellipse, drop) -mesh = make_curve_mesh( - #lambda t: ellipse(1, t), - starfish, - np.linspace(0, 1, nelements+1), - target_order) -from pytential.qbx import QBXLayerPotentialSource -from meshmode.discretization import Discretization -from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory +def main(): + from meshmode.mesh.generation import ( # noqa + make_curve_mesh, starfish, ellipse, drop) + mesh = make_curve_mesh( + #lambda t: ellipse(1, t), + starfish, + np.linspace(0, 1, nelements+1), + target_order) + + from pytential.qbx import QBXLayerPotentialSource + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + + pre_density_discr = Discretization( + cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, + fmm_order=qbx_order+3, + target_association_tolerance=0.005).with_refinement() + + density_discr = qbx.density_discr + + nodes = density_discr.nodes().with_queue(queue) + + angle = cl.clmath.atan2(nodes[1], nodes[0]) + + def op(**kwargs): + kwargs.update(kernel_kwargs) + + #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), **kwargs)) + return sym.D(kernel, sym.var("sigma"), **kwargs) + #op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None, **kwargs) + + sigma = cl.clmath.cos(mode_nr*angle) + if 0: + sigma = 0*angle + from random import randrange + for i in range(5): + sigma[randrange(len(sigma))] = 1 + + if isinstance(kernel, HelmholtzKernel): + sigma = sigma.astype(np.complex128) + + bound_bdry_op = bind(qbx, op()) + #mlab.figure(bgcolor=(1, 1, 1)) + if 1: + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) + from pytential.target import PointsTarget + + targets_dev = cl.array.to_device(queue, fplot.points) + fld_in_vol = bind( + (qbx, PointsTarget(targets_dev)), + op(qbx_forced_limit=None))(queue, sigma=sigma, k=k).get() + + if enable_mayavi: + fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) + else: + fplot.write_vtk_file( + "potential.vts", + [ + ("potential", fld_in_vol) + ] + ) + + if 0: + def apply_op(density): + return bound_bdry_op( + queue, sigma=cl.array.to_device(queue, density), k=k).get() + + from sumpy.tools import build_matrix + n = len(sigma) + mat = build_matrix(apply_op, dtype=np.float64, shape=(n, n)) + + import matplotlib.pyplot as pt + pt.imshow(mat) + pt.colorbar() + pt.show() -pre_density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - -qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, - fmm_order=qbx_order+3, - target_association_tolerance=0.005).with_refinement() - -density_discr = qbx.density_discr - -nodes = density_discr.nodes().with_queue(queue) - -angle = cl.clmath.atan2(nodes[1], nodes[0]) - - -def op(**kwargs): - kwargs.update(kernel_kwargs) - - #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), **kwargs)) - return sym.D(kernel, sym.var("sigma"), **kwargs) - #op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None, **kwargs) - - -sigma = cl.clmath.cos(mode_nr*angle) -if 0: - sigma = 0*angle - from random import randrange - for i in range(5): - sigma[randrange(len(sigma))] = 1 + if enable_mayavi: + # {{{ plot boundary field -if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + fld_on_bdry = bound_bdry_op(queue, sigma=sigma, k=k).get() -bound_bdry_op = bind(qbx, op()) -#mlab.figure(bgcolor=(1, 1, 1)) -if 1: - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) - from pytential.target import PointsTarget + nodes_host = density_discr.nodes().get(queue=queue) + mlab.points3d(nodes_host[0], nodes_host[1], + fld_on_bdry.real, scale_factor=0.03) - targets_dev = cl.array.to_device(queue, fplot.points) - fld_in_vol = bind( - (qbx, PointsTarget(targets_dev)), - op(qbx_forced_limit=None))(queue, sigma=sigma, k=k).get() + # }}} if enable_mayavi: - fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) - else: - fplot.write_vtk_file( - "potential.vts", - [ - ("potential", fld_in_vol) - ] - ) - -if 0: - def apply_op(density): - return bound_bdry_op( - queue, sigma=cl.array.to_device(queue, density), k=k).get() - - from sumpy.tools import build_matrix - n = len(sigma) - mat = build_matrix(apply_op, dtype=np.float64, shape=(n, n)) - - import matplotlib.pyplot as pt - pt.imshow(mat) - pt.colorbar() - pt.show() - -if enable_mayavi: - # {{{ plot boundary field + mlab.colorbar() + mlab.show() - fld_on_bdry = bound_bdry_op(queue, sigma=sigma, k=k).get() - nodes_host = density_discr.nodes().get(queue=queue) - mlab.points3d(nodes_host[0], nodes_host[1], fld_on_bdry.real, scale_factor=0.03) - - # }}} - -if enable_mayavi: - mlab.colorbar() - mlab.show() +if __name__ == "__main__": + main() diff --git a/examples/perf-model.py b/examples/perf-model.py deleted file mode 100644 index 3a87d631..00000000 --- a/examples/perf-model.py +++ /dev/null @@ -1,18 +0,0 @@ -# ------------------------------ -nlevels = 6 -nboxes = 1365 -nsources = 60 -ntargets = 12040 -form_mp = 60*p_fmm -prop_upward = 1365*p_fmm**2 -part_direct = 196560 -m2l = 31920*p_fmm**2 -mp_eval = 0 -form_local = 65000*p_fmm -prop_downward = 1365*p_fmm**2 -eval_part = 12040*p_fmm -ncenters = 2040 -qbxl_direct = 2370940*p_qbx -qbx_m2l = 35339*p_fmm*p_qbx -qbx_l2l = 2040*p_fmm*p_qbx -qbx_eval = 1902*p_qbx diff --git a/examples/scaling-study.py b/examples/scaling-study.py index 183fc915..4d20bcc1 100644 --- a/examples/scaling-study.py +++ b/examples/scaling-study.py @@ -16,8 +16,8 @@ bdry_quad_order = 4 mesh_order = bdry_quad_order qbx_order = bdry_quad_order bdry_ovsmp_quad_order = 4*bdry_quad_order -fmm_order = 25 -k = 25 +fmm_order = 10 +k = 0 # }}} diff --git a/experiments/README.md b/experiments/README.md new file mode 100644 index 00000000..d0f56efd --- /dev/null +++ b/experiments/README.md @@ -0,0 +1,7 @@ +# Experiments + +What you find in this directory are experiments that *may* have done something +useful at some point (or not). Unlike `examples`, they are not being tested on +an ongoing basis. + +So if what you find here breaks for you, you get to keep both pieces. diff --git a/examples/cahn-hilliard.py b/experiments/cahn-hilliard.py similarity index 100% rename from examples/cahn-hilliard.py rename to experiments/cahn-hilliard.py diff --git a/examples/find-photonic-mode-sk.py b/experiments/find-photonic-mode-sk.py similarity index 100% rename from examples/find-photonic-mode-sk.py rename to experiments/find-photonic-mode-sk.py diff --git a/examples/find-photonic-mode.py b/experiments/find-photonic-mode.py similarity index 100% rename from examples/find-photonic-mode.py rename to experiments/find-photonic-mode.py diff --git a/examples/helmholtz-expression-tree.py b/experiments/helmholtz-expression-tree.py similarity index 100% rename from examples/helmholtz-expression-tree.py rename to experiments/helmholtz-expression-tree.py diff --git a/examples/maxwell.py b/experiments/maxwell.py similarity index 100% rename from examples/maxwell.py rename to experiments/maxwell.py diff --git a/examples/maxwell_sphere.py b/experiments/maxwell_sphere.py similarity index 100% rename from examples/maxwell_sphere.py rename to experiments/maxwell_sphere.py diff --git a/examples/poisson.py b/experiments/poisson.py similarity index 100% rename from examples/poisson.py rename to experiments/poisson.py diff --git a/examples/qbx-tangential-deriv-jump.py b/experiments/qbx-tangential-deriv-jump.py similarity index 100% rename from examples/qbx-tangential-deriv-jump.py rename to experiments/qbx-tangential-deriv-jump.py diff --git a/examples/stokes-2d-interior.py b/experiments/stokes-2d-interior.py similarity index 100% rename from examples/stokes-2d-interior.py rename to experiments/stokes-2d-interior.py diff --git a/examples/two-domain-helmholtz.py b/experiments/two-domain-helmholtz.py similarity index 100% rename from examples/two-domain-helmholtz.py rename to experiments/two-domain-helmholtz.py -- GitLab From f5ea064982f7756e96ce49b7646f53d2bec3f2b3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 16 Jan 2018 19:30:10 -0600 Subject: [PATCH 010/268] Minor primitives doc improvement --- pytential/symbolic/primitives.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index a0e229dd..b1667314 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -135,10 +135,14 @@ Elementary numerics .. autofunction:: mean .. autoclass:: IterativeInverse -Calculus (based on Geometric Algebra) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Geometric Calculus (based on Geometric/Clifford Algebra) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: Derivative + +Conventional Calculus +^^^^^^^^^^^^^^^^^^^^^ + .. autofunction:: dd_axis .. autofunction:: d_dx .. autofunction:: d_dy -- GitLab From 4067d0c4402592cdf00f34340a7e45a909990a7b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 16 Jan 2018 19:30:22 -0600 Subject: [PATCH 011/268] Allow overriding fmm_order on copy --- pytential/qbx/__init__.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 82ef3e19..e05a8063 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -189,6 +189,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): density_discr=None, fine_order=None, qbx_order=None, + fmm_order=_not_provided, fmm_level_to_order=_not_provided, to_refined_connection=None, target_association_tolerance=_not_provided, @@ -224,6 +225,18 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): # }}} + kwargs = {} + + if (fmm_order is not _not_provided + and fmm_level_to_order is not _not_provided): + raise TypeError("may not specify both fmm_order and fmm_level_to_order") + elif fmm_order is not _not_provided: + kwargs["fmm_order"] = fmm_order + elif fmm_level_to_order is not _not_provided: + kwargs["fmm_level_to_order"] = fmm_level_to_order + else: + kwargs["fmm_level_to_order"] = self.fmm_level_to_order + # FIXME Could/should share wrangler and geometry kernels # if no relevant changes have been made. return QBXLayerPotentialSource( @@ -231,11 +244,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): fine_order=( fine_order if fine_order is not None else self.fine_order), qbx_order=qbx_order if qbx_order is not None else self.qbx_order, - fmm_level_to_order=( - # False is a valid value here - fmm_level_to_order - if fmm_level_to_order is not _not_provided - else self.fmm_level_to_order), target_association_tolerance=target_association_tolerance, to_refined_connection=( @@ -268,7 +276,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): geometry_data_inspector=( geometry_data_inspector or self.geometry_data_inspector), fmm_backend=self.fmm_backend, - ) + **kwargs) # }}} -- GitLab From fc1afe493f5346d6676562e7643b12bb88c69cbf Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Jan 2018 00:45:45 -0600 Subject: [PATCH 012/268] More examples cleanup --- .gitlab-ci.yml | 2 +- examples/layerpot.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a05b560d..8d25d57a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ Python 3.6 POCL Examples: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako" + - export EXTRA_INSTALL="numpy mako pyvisfile" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-py-project-and-run-examples.sh - ". ./build-py-project-and-run-examples.sh" tags: diff --git a/examples/layerpot.py b/examples/layerpot.py index f5e56de3..222606d2 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -15,13 +15,6 @@ import faulthandler from six.moves import range faulthandler.enable() -import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -cl_ctx = cl.create_some_context() -queue = cl.CommandQueue(cl_ctx) - target_order = 16 qbx_order = 3 nelements = 60 @@ -38,6 +31,12 @@ else: def main(): + import logging + logging.basicConfig(level=logging.WARNING) # INFO for more progress info + + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + from meshmode.mesh.generation import ( # noqa make_curve_mesh, starfish, ellipse, drop) mesh = make_curve_mesh( -- GitLab From 05276326baecf79bcf15e48d6d278bb7cea090f7 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Jan 2018 11:33:34 -0600 Subject: [PATCH 013/268] Tweak examples --- examples/helmholtz-dirichlet.py | 2 +- examples/laplace-dirichlet-3d.py | 2 +- examples/layerpot-3d.py | 9 +++------ examples/layerpot.py | 2 +- examples/scaling-study.py | 3 ++- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index d5498fe3..22f8fa8a 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -167,7 +167,7 @@ def main(): #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( - "potential.vts", + "potential-helm.vts", [ ("potential", fld_in_vol), ("indicator", indicator), diff --git a/examples/laplace-dirichlet-3d.py b/examples/laplace-dirichlet-3d.py index b2e895ca..4166dddf 100644 --- a/examples/laplace-dirichlet-3d.py +++ b/examples/laplace-dirichlet-3d.py @@ -157,7 +157,7 @@ def main(): #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( - "potential.vts", + "potential-laplace-3d.vts", [ ("potential", fld_in_vol), ] diff --git a/examples/layerpot-3d.py b/examples/layerpot-3d.py index cc620997..28f0967e 100644 --- a/examples/layerpot-3d.py +++ b/examples/layerpot-3d.py @@ -9,10 +9,6 @@ import faulthandler from six.moves import range faulthandler.enable() -import logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) @@ -37,6 +33,7 @@ else: def main(): import logging + logger = logging.getLogger(__name__) logging.basicConfig(level=logging.WARNING) # INFO for more progress info from meshmode.mesh.io import generate_gmsh, FileSource @@ -95,7 +92,7 @@ def main(): #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( - "potential.vts", + "potential-3d.vts", [ ("potential", fld_in_vol) ] @@ -108,7 +105,7 @@ def main(): from meshmode.discretization.visualization import make_visualizer bdry_vis = make_visualizer(queue, density_discr, target_order) - bdry_vis.write_vtk_file("source.vtu", [ + bdry_vis.write_vtk_file("source-3d.vtu", [ ("sigma", sigma), ("bdry_normals", bdry_normals), ]) diff --git a/examples/layerpot.py b/examples/layerpot.py index 222606d2..7b4737da 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -95,7 +95,7 @@ def main(): fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) else: fplot.write_vtk_file( - "potential.vts", + "potential-2d.vts", [ ("potential", fld_in_vol) ] diff --git a/examples/scaling-study.py b/examples/scaling-study.py index 4d20bcc1..ce6fc88f 100644 --- a/examples/scaling-study.py +++ b/examples/scaling-study.py @@ -154,6 +154,7 @@ def timing_run(nx, ny): sym_op)( queue, sigma=ones_density).get() + qbx_stick_out = qbx.copy(target_stick_out_factor=0.1) try: fld_in_vol = bind( (qbx_stick_out, PointsTarget(targets)), @@ -169,7 +170,7 @@ def timing_run(nx, ny): #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( - "potential.vts", + "potential-scaling.vts", [ ("potential", fld_in_vol), ("indicator", indicator) -- GitLab From 94291e4c2d75b25f5f58cbf1dbf1264aaf5452d3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 17 Jan 2018 12:09:30 -0600 Subject: [PATCH 014/268] Install matplotlib for example execution --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8d25d57a..ad62d954 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ Python 3.6 POCL Examples: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako pyvisfile" + - export EXTRA_INSTALL="numpy mako pyvisfile matplotlib" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-py-project-and-run-examples.sh - ". ./build-py-project-and-run-examples.sh" tags: -- GitLab From f0ddfea87f36c5f2a83e13b8b40099e014caca41 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 18 Jan 2018 17:31:57 -0600 Subject: [PATCH 015/268] Make matplotlib go without a display --- examples/fmm-error.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/fmm-error.py b/examples/fmm-error.py index 4a15f18f..110ec66b 100644 --- a/examples/fmm-error.py +++ b/examples/fmm-error.py @@ -75,6 +75,9 @@ def main(): op)(queue, sigma=sigma, k=k).get() err = fmm_fld_in_vol-fld_in_vol + + import matplotlib + matplotlib.use('Agg') im = fplot.show_scalar_in_matplotlib(np.log10(np.abs(err))) from matplotlib.colors import Normalize -- GitLab From 344ad5e1603222163a04672ebe1601da1d5077fd Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 18 Jan 2018 17:59:53 -0600 Subject: [PATCH 016/268] Switch scaling study to use less verbose logging --- examples/scaling-study.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scaling-study.py b/examples/scaling-study.py index ce6fc88f..3327e3c8 100644 --- a/examples/scaling-study.py +++ b/examples/scaling-study.py @@ -54,7 +54,7 @@ def make_mesh(nx, ny): def timing_run(nx, ny): import logging - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) -- GitLab From f1b9378766cbba593fa2348019528bde16330109 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 19 Jan 2018 16:45:03 -0600 Subject: [PATCH 017/268] Add macOS install instructions. --- doc/misc.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/misc.rst b/doc/misc.rst index 874935f8..7a3d9aba 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -4,14 +4,15 @@ Installation and Usage Installing :mod:`pytential` --------------------------- -This set of instructions is intended for 64-bit Linux computers. -MacOS support is in the works. +This set of instructions is intended for 64-bit Linux and macOS computers. #. Make sure your system has the basics to build software. On Debian derivatives (Ubuntu and many more), installing ``build-essential`` should do the trick. + On macOS, run ``xcode-select --install`` to install build tools. + Everywhere else, just making sure you have the ``g++`` package should be enough. @@ -30,6 +31,8 @@ MacOS support is in the works. #. ``conda config --add channels conda-forge`` +#. (*macOS only*) ``conda install osx-pocl-opencl`` + #. ``conda install git pip pocl islpy pyopencl sympy pyfmmlib pytest`` #. Type the following command:: -- GitLab From 247a16e77c4c03483e55189b78cfe659326cdb3c Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 19 Jan 2018 17:09:45 -0600 Subject: [PATCH 018/268] CI support for macOS --- .gitlab-ci.yml | 10 ++++++++++ .test-conda-env-py3-macos.yml | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .test-conda-env-py3-macos.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ad62d954..4210213f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,16 @@ Python 2.7 POCL: except: - tags +Python 3.5 Conda Apple: + script: + - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh + - ". ./build-and-test-py-project-within-miniconda.sh" + tags: + - apple + except: + - tags + Documentation: script: - EXTRA_INSTALL="numpy mako" diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml new file mode 100644 index 00000000..807cd696 --- /dev/null +++ b/.test-conda-env-py3-macos.yml @@ -0,0 +1,17 @@ +name: test-conda-env-py3-macos +channels: +- conda-forge +- defaults +dependencies: +- git +- conda-forge::numpy +- conda-forge::sympy +- pocl=1.0 +- islpy +- pyopencl +- python=3.5 +- symengine=0.3.0 +- python-symengine=0.3.0 +- pyfmmlib +- osx-pocl-opencl +# things not in here: loopy boxtree pymbolic meshmode sumpy -- GitLab From c8de77d120c9da4d4d1afe7123158b747a69cf9a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 23 Jan 2018 12:40:33 -0600 Subject: [PATCH 019/268] [ci skip] Fix Sphinx warnings. --- pytential/source.py | 3 +-- pytential/symbolic/primitives.py | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pytential/source.py b/pytential/source.py index 16eac21c..6420494e 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -173,7 +173,7 @@ class LayerPotentialSourceBase(PotentialSource): .. rubric:: Execution - .. automethod:: weights_and_area_elements + .. method:: weights_and_area_elements .. method:: exec_compute_potential_insn """ @@ -267,5 +267,4 @@ class LayerPotentialSourceBase(PotentialSource): # }}} - # }}} diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index b1667314..d981fdd2 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -75,10 +75,10 @@ visible only once evaluated.) Placeholders ^^^^^^^^^^^^ -.. autoclass:: Variable -.. autoclass:: make_sym_vector -.. autoclass:: make_sym_mv -.. autoclass:: make_sym_surface_mv +.. autoclass:: var +.. autofunction:: make_sym_vector +.. autofunction:: make_sym_mv +.. autofunction:: make_sym_surface_mv Functions ^^^^^^^^^ @@ -144,9 +144,9 @@ Conventional Calculus ^^^^^^^^^^^^^^^^^^^^^ .. autofunction:: dd_axis -.. autofunction:: d_dx -.. autofunction:: d_dy -.. autofunction:: d_dz +.. function:: d_dx +.. function:: d_dy +.. function:: d_dz .. autofunction:: grad_mv .. autofunction:: grad .. autofunction:: laplace -- GitLab From 2d33e5afb08a762d15aee802f2fa8176a0ebe31d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 25 Jan 2018 22:38:20 -0600 Subject: [PATCH 020/268] Let build_matrix tolerate single expressions --- pytential/symbolic/execution.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 3e96b00c..213eb428 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -406,8 +406,10 @@ def build_matrix(queue, places, expr, input_exprs, domains=None, :arg places: a mapping of symbolic names to :class:`pytential.discretization.Discretization` objects or a subclass of :class:`pytential.discretization.target.TargetBase`. - :arg input_exprs: A sequence of expressions corresponding to the + :arg input_exprs: An object array of expressions corresponding to the input block columns of the matrix. + + May also be a single expression. :arg domains: a list of discretization identifiers (see 'places') or *None* values indicating the domains on which each component of the solution vector lives. *None* values indicate that the component @@ -432,6 +434,12 @@ def build_matrix(queue, places, expr, input_exprs, domains=None, domains = _domains_default(len(input_exprs), places, domains, DEFAULT_SOURCE) + try: + iter(input_exprs) + except TypeError: + # not iterable, wrap in a list + input_exprs = [input_exprs] + input_exprs = list(input_exprs) nblock_rows = len(expr) -- GitLab From 2eb9936263f1bac52605886f5e55989db04cbcc2 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 27 Jan 2018 19:14:05 -0600 Subject: [PATCH 021/268] Add requirements.txt to macOS CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4210213f..efe60c4d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -70,6 +70,7 @@ Python 2.7 POCL: Python 3.5 Conda Apple: script: - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml + - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" tags: -- GitLab From 82c1e90791e3843b409f907f9e561ad4970e559d Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 27 Jan 2018 19:44:32 -0600 Subject: [PATCH 022/268] Try setting the locale to en_US.UTF-8 --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index efe60c4d..ecf7b037 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,6 +69,8 @@ Python 2.7 POCL: Python 3.5 Conda Apple: script: + - export LC_ALL=en_US.UTF-8 + - export LANG=en_US.UTF-8 - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh -- GitLab From 3a87e34cf2d791827a4d7382637d9b023d39cd50 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 29 Jan 2018 13:12:49 -0600 Subject: [PATCH 023/268] Mark maxwell and stokes tests as slow; skip on Apple. --- .gitlab-ci.yml | 1 + setup.cfg | 4 ++++ test/test_maxwell.py | 1 + test/test_stokes.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecf7b037..48d7cd3f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,6 +71,7 @@ Python 3.5 Conda Apple: script: - export LC_ALL=en_US.UTF-8 - export LANG=en_US.UTF-8 + - export PYTEST_ADDOPTS=-k-slowtest - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh diff --git a/setup.cfg b/setup.cfg index 42291e82..a353f3f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ + [flake8] ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503,N803,N806,N802,D102,D103 max-line-length=85 @@ -5,3 +6,6 @@ exclude= pytential/symbolic/old_diffop_primitives.py, pytential/symbolic/pde/maxwell/generalized_debye.py, +[tool:pytest] +markers= + slowtest: mark a test as slow diff --git a/test/test_maxwell.py b/test/test_maxwell.py index 715a7669..87420a10 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -213,6 +213,7 @@ class EHField(object): # {{{ driver +@pytest.mark.slowtest @pytest.mark.parametrize("case", [ #tc_int, tc_ext, diff --git a/test/test_stokes.py b/test/test_stokes.py index 1b85080f..3f456fc1 100644 --- a/test/test_stokes.py +++ b/test/test_stokes.py @@ -26,6 +26,7 @@ THE SOFTWARE. import numpy as np import pyopencl as cl import pyopencl.clmath # noqa +import pytest from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -270,6 +271,7 @@ def run_exterior_stokes_2d(ctx_factory, nelements, return qbx.h_max, l2_err +@pytest.mark.slowtest def test_exterior_stokes_2d(ctx_factory, qbx_order=3): from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() -- GitLab From fac367328ac6972af1c75d600483367214bd5d65 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 5 Feb 2018 15:36:01 -0600 Subject: [PATCH 024/268] Fix a redundant use of lambda. --- pytential/symbolic/compiler.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pytential/symbolic/compiler.py b/pytential/symbolic/compiler.py index 68b39d2d..17d23ebf 100644 --- a/pytential/symbolic/compiler.py +++ b/pytential/symbolic/compiler.py @@ -316,6 +316,7 @@ class Code(object): return "\n".join(lines) # {{{ dynamic scheduler (generates static schedules by self-observation) + class NoInstructionAvailable(Exception): pass @@ -579,9 +580,7 @@ class OperatorCompiler(IdentityMapper): group = self.group_to_operators[self.op_group_features(expr)] names = [self.get_var_name() for op in group] - kernels = sorted( - set(op.kernel for op in group), - key=lambda kernel: repr(kernel)) + kernels = sorted(set(op.kernel for op in group), key=repr) kernel_to_index = dict( (kernel, i) for i, kernel in enumerate(kernels)) -- GitLab From f8cd38b860094b2669decef74e74cf23e51db208 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 19 Feb 2018 15:40:55 -0600 Subject: [PATCH 025/268] Silence loopy language version warnings --- pytential/qbx/geometry.py | 10 +++++++--- pytential/qbx/interactions.py | 13 +++++++++---- pytential/qbx/refinement.py | 4 +++- pytential/qbx/utils.py | 13 +++++++++---- pytential/unregularized.py | 4 +++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index ca53df5a..7cc8e8ea 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -30,6 +30,7 @@ import pyopencl.array # noqa from pytools import memoize_method from boxtree.tools import DeviceDataRecord import loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION from cgen import Enum @@ -125,7 +126,8 @@ class QBXFMMGeometryCodeGetter(TreeCodeContainerMixin): """ targets[dim, i] = points[dim, i] """, - default_offset=lp.auto, name="copy_targets") + default_offset=lp.auto, name="copy_targets", + lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.fix_parameters(knl, ndims=self.ambient_dim) @@ -182,7 +184,8 @@ class QBXFMMGeometryCodeGetter(TreeCodeContainerMixin): "..." ], name="qbx_center_to_target_box_lookup", - silenced_warnings="write_race(tgt_write)") + silenced_warnings="write_race(tgt_write)", + lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.split_iname(knl, "ibox", 128, inner_tag="l.0", outer_tag="g.0") @@ -244,7 +247,8 @@ class QBXFMMGeometryCodeGetter(TreeCodeContainerMixin): lp.ValueArg("ntargets", np.int32), ], name="pick_used_centers", - silenced_warnings="write_race(center_is_used_write)") + silenced_warnings="write_race(center_is_used_write)", + lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.split_iname(knl, "i", 128, inner_tag="l.0", outer_tag="g.0") return knl diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 6105472d..d759763b 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -24,6 +24,7 @@ THE SOFTWARE. import numpy as np import loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION from pytools import memoize_method from six.moves import range @@ -113,7 +114,8 @@ class P2QBXLFromCSR(P2EBase): arguments, name=self.name, assumptions="ntgt_centers>=1", silenced_warnings="write_race(write_expn*)", - fixed_parameters=dict(dim=self.dim)) + fixed_parameters=dict(dim=self.dim), + lang_version=MOST_RECENT_LANGUAGE_VERSION) loopy_knl = self.expansion.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") @@ -209,7 +211,8 @@ class M2QBXL(E2EBase): ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), name=self.name, assumptions="ncenters>=1", silenced_warnings="write_race(write_expn*)", - fixed_parameters=dict(dim=self.dim)) + fixed_parameters=dict(dim=self.dim), + lang_version=MOST_RECENT_LANGUAGE_VERSION) for expn in [self.src_expansion, self.tgt_expansion]: loopy_knl = expn.prepare_loopy_kernel(loopy_knl) @@ -309,7 +312,8 @@ class L2QBXL(E2EBase): name=self.name, assumptions="ncenters>=1", silenced_warnings="write_race(write_expn*)", - fixed_parameters=dict(dim=self.dim, nchildren=2**self.dim)) + fixed_parameters=dict(dim=self.dim, nchildren=2**self.dim), + lang_version=MOST_RECENT_LANGUAGE_VERSION) for expn in [self.src_expansion, self.tgt_expansion]: loopy_knl = expn.prepare_loopy_kernel(loopy_knl) @@ -405,7 +409,8 @@ class QBXL2P(E2PBase): name=self.name, assumptions="nglobal_qbx_centers>=1", silenced_warnings="write_race(write_result*)", - fixed_parameters=dict(dim=self.dim, nresults=len(result_names))) + fixed_parameters=dict(dim=self.dim, nresults=len(result_names)), + lang_version=MOST_RECENT_LANGUAGE_VERSION) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") loopy_knl = self.expansion.prepare_loopy_kernel(loopy_knl) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 5d90a320..08539c68 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -28,6 +28,7 @@ THE SOFTWARE. import loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION import numpy as np import pyopencl as cl @@ -255,7 +256,8 @@ class RefinerCodeContainer(TreeCodeContainerMixin): """, options="return_dict", silenced_warnings="write_race(write_refine_flags_updated)", - name="refine_kernel_length_scale_to_panel_size_ratio") + name="refine_kernel_length_scale_to_panel_size_ratio", + lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index ecb939de..6673b450 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -32,6 +32,7 @@ from boxtree.tree import Tree import pyopencl as cl import pyopencl.array # noqa from pytools import memoize, memoize_method +from loopy.version import MOST_RECENT_LANGUAGE_VERSION import logging logger = logging.getLogger(__name__) @@ -84,7 +85,8 @@ def get_interleaver_kernel(dtype): lp.GlobalArg("dst", shape=(var("dstlen"),), dtype=dtype), "..." ], - assumptions="2*srclen = dstlen") + assumptions="2*srclen = dstlen", + lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.split_iname(knl, "i", 128, inner_tag="l.0", outer_tag="g.0") return knl @@ -217,7 +219,8 @@ def panel_sizes(discr, last_dim_length): knl = lp.make_kernel( "{[i,j,k]: 0<=i Date: Mon, 19 Feb 2018 16:17:09 -0600 Subject: [PATCH 026/268] Add nosync where appropriate --- pytential/qbx/interactions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index d759763b..98c4ba94 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -106,7 +106,7 @@ class P2QBXLFromCSR(P2EBase): """] + [""" qbx_expansions[tgt_icenter, {i}] = \ simul_reduce(sum, (isrc_box, isrc), strength*coeff{i}) \ - {{id_prefix=write_expn}} + {{id_prefix=write_expn,nosync=write_expn*}} """.format(i=i) for i in range(ncoeffs)] + [""" end @@ -188,7 +188,7 @@ class M2QBXL(E2EBase): """] + [""" qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ simul_reduce(sum, isrc_box, coeff{i}) \ - {{id_prefix=write_expn}} + {{id_prefix=write_expn,nosync=write_expn*}} """.format(i=i) for i in range(ncoeff_tgt)] + [""" end @@ -291,7 +291,7 @@ class L2QBXL(E2EBase): ] + self.get_translation_loopy_insns() + [""" qbx_expansions[icenter, {i}] = \ qbx_expansions[icenter, {i}] + coeff{i} \ - {{id_prefix=write_expn}} + {{id_prefix=write_expn,nosync=write_expn*}} """.format(i=i) for i in range(ncoeff_tgt)] + [""" end end -- GitLab From f28fae57b4eb91eb1c748e817c916adfe91d5877 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 20 Feb 2018 17:08:20 -0600 Subject: [PATCH 027/268] Add another nosync --- pytential/qbx/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 98c4ba94..061cec36 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -387,7 +387,7 @@ class QBXL2P(E2PBase): ] + loopy_insns + [""" result[{i},center_itgt] = kernel_scaling * result_{i}_p \ - {{id_prefix=write_result}} + {{id_prefix=write_result,nosync=write_result*}} """.format(i=i) for i in range(len(result_names))] + [""" end end -- GitLab From a6e8a32fb9338a51699a74788b8e170da320c1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Wed, 21 Feb 2018 01:40:21 -0500 Subject: [PATCH 028/268] Bump PYTENTIAL_KERNEL_VERSION --- pytential/qbx/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 061cec36..8426bbf5 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -33,7 +33,7 @@ from sumpy.e2e import E2EBase from sumpy.e2p import E2PBase -PYTENTIAL_KERNEL_VERSION = 5 +PYTENTIAL_KERNEL_VERSION = 6 # {{{ form qbx expansions from points -- GitLab From c86ab25015e0cc3176757491dc86808d81cde8e1 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 21 Feb 2018 03:47:43 -0600 Subject: [PATCH 029/268] Use nosync only when there is >1 write statement --- pytential/qbx/interactions.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 061cec36..9430dbca 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -106,8 +106,11 @@ class P2QBXLFromCSR(P2EBase): """] + [""" qbx_expansions[tgt_icenter, {i}] = \ simul_reduce(sum, (isrc_box, isrc), strength*coeff{i}) \ - {{id_prefix=write_expn,nosync=write_expn*}} - """.format(i=i) for i in range(ncoeffs)] + [""" + {{id_prefix=write_expn{nosync}}} + """.format(i=i, + nosync=",nosync=write_expn*" + if ncoeffs > 1 else "") + for i in range(ncoeffs)] + [""" end """], @@ -188,8 +191,11 @@ class M2QBXL(E2EBase): """] + [""" qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ simul_reduce(sum, isrc_box, coeff{i}) \ - {{id_prefix=write_expn,nosync=write_expn*}} - """.format(i=i) for i in range(ncoeff_tgt)] + [""" + {{id_prefix=write_expn{nosync}}} + """.format(i=i, + nosync=",nosync=write_expn*" + if ncoeff_tgt > 1 else "") + for i in range(ncoeff_tgt)] + [""" end """], @@ -291,8 +297,11 @@ class L2QBXL(E2EBase): ] + self.get_translation_loopy_insns() + [""" qbx_expansions[icenter, {i}] = \ qbx_expansions[icenter, {i}] + coeff{i} \ - {{id_prefix=write_expn,nosync=write_expn*}} - """.format(i=i) for i in range(ncoeff_tgt)] + [""" + {{id_prefix=write_expn{nosync}}} + """.format(i=i, + nosync=",nosync=write_expn*" + if ncoeff_tgt > 1 else "") + for i in range(ncoeff_tgt)] + [""" end end """], @@ -387,8 +396,11 @@ class QBXL2P(E2PBase): ] + loopy_insns + [""" result[{i},center_itgt] = kernel_scaling * result_{i}_p \ - {{id_prefix=write_result,nosync=write_result*}} - """.format(i=i) for i in range(len(result_names))] + [""" + {{id_prefix=write_result{nosync}}} + """.format(i=i, + nosync=",nosync=write_result*" + if len(result_names) > 1 else "") + for i in range(len(result_names))] + [""" end end """], -- GitLab From 8b1f7898ee573a9405759b698cc23f56df58bfeb Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 21 Feb 2018 15:29:20 -0600 Subject: [PATCH 030/268] Bump kernel version again --- pytential/qbx/interactions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index e952592e..470151f3 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -33,7 +33,7 @@ from sumpy.e2e import E2EBase from sumpy.e2p import E2PBase -PYTENTIAL_KERNEL_VERSION = 6 +PYTENTIAL_KERNEL_VERSION = 7 # {{{ form qbx expansions from points -- GitLab From 7e6186f0693904ee27534e2d790c6149226464da Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 22 Feb 2018 19:21:49 -0600 Subject: [PATCH 031/268] Minor doc fix --- pytential/symbolic/execution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 3e96b00c..6b1707c3 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -266,7 +266,7 @@ class BoundExpression: :arg domains: a list of discretization identifiers or *None* values indicating the domains on which each component of the solution vector lives. *None* values indicate that the component - is a scalar. If *None*, + is a scalar. If *domains* is *None*, :class:`pytential.symbolic.primitives.DEFAULT_TARGET`, is required to be a key in :attr:`places`. """ -- GitLab From 43c723bf1613facdffeb9f8105df34984e5fb102 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 22 Feb 2018 19:22:17 -0600 Subject: [PATCH 032/268] Do not attempt to oversample scalars in QBX --- pytential/qbx/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index e05a8063..d5d52ca6 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -491,8 +491,13 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): def exec_compute_potential_insn(self, queue, insn, bound_expr, evaluate): from pytools.obj_array import with_object_array_or_scalar - from functools import partial - oversample = partial(self.resampler, queue) + + def oversample_nonscalars(vec): + from numbers import Number + if isinstance(vec, Number): + return vec + else: + return self.resampler(queue, vec) if not self._refined_for_global_qbx: from warnings import warn @@ -502,7 +507,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): def evaluate_wrapper(expr): value = evaluate(expr) - return with_object_array_or_scalar(oversample, value) + return with_object_array_or_scalar(oversample_nonscalars, value) if self.fmm_level_to_order is False: func = self.exec_compute_potential_insn_direct -- GitLab From d79a9aad3014757de1b40e35af529cd010c2f0c1 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 24 Feb 2018 01:04:25 -0600 Subject: [PATCH 033/268] Remove now-extraneous 'nosync' specs now that loopy got smarter --- pytential/qbx/interactions.py | 27 +++++++++------------------ pytential/version.py | 6 ++++++ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 470151f3..0cca9f17 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -32,8 +32,7 @@ from sumpy.p2e import P2EBase from sumpy.e2e import E2EBase from sumpy.e2p import E2PBase - -PYTENTIAL_KERNEL_VERSION = 7 +from pytential.version import PYTENTIAL_KERNEL_VERSION # {{{ form qbx expansions from points @@ -106,10 +105,8 @@ class P2QBXLFromCSR(P2EBase): """] + [""" qbx_expansions[tgt_icenter, {i}] = \ simul_reduce(sum, (isrc_box, isrc), strength*coeff{i}) \ - {{id_prefix=write_expn{nosync}}} - """.format(i=i, - nosync=",nosync=write_expn*" - if ncoeffs > 1 else "") + {{id_prefix=write_expn}} + """.format(i=i) for i in range(ncoeffs)] + [""" end @@ -191,10 +188,8 @@ class M2QBXL(E2EBase): """] + [""" qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ simul_reduce(sum, isrc_box, coeff{i}) \ - {{id_prefix=write_expn{nosync}}} - """.format(i=i, - nosync=",nosync=write_expn*" - if ncoeff_tgt > 1 else "") + {{id_prefix=write_expn}} + """.format(i=i) for i in range(ncoeff_tgt)] + [""" end @@ -297,10 +292,8 @@ class L2QBXL(E2EBase): ] + self.get_translation_loopy_insns() + [""" qbx_expansions[icenter, {i}] = \ qbx_expansions[icenter, {i}] + coeff{i} \ - {{id_prefix=write_expn{nosync}}} - """.format(i=i, - nosync=",nosync=write_expn*" - if ncoeff_tgt > 1 else "") + {{id_prefix=write_expn}} + """.format(i=i) for i in range(ncoeff_tgt)] + [""" end end @@ -396,10 +389,8 @@ class QBXL2P(E2PBase): ] + loopy_insns + [""" result[{i},center_itgt] = kernel_scaling * result_{i}_p \ - {{id_prefix=write_result{nosync}}} - """.format(i=i, - nosync=",nosync=write_result*" - if len(result_names) > 1 else "") + {{id_prefix=write_result}} + """.format(i=i) for i in range(len(result_names))] + [""" end end diff --git a/pytential/version.py b/pytential/version.py index d26fbc2f..fd8dc42f 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -1,2 +1,8 @@ VERSION = (2016, 1) VERSION_TEXT = ".".join(str(i) for i in VERSION) + +# When developing on a branch, set the first element of this tuple to your +# branch name, so as to avoid conflicts with the master branch. Make sure +# to reset this to the next number up with "master" before merging into +# master. +PYTENTIAL_KERNEL_VERSION = ("remove-nosync", 8) -- GitLab From 2fcce814b808266c0e57ae0884993bbafb12415b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Sat, 24 Feb 2018 10:34:55 -0500 Subject: [PATCH 034/268] Bump kernel version, back to master --- pytential/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/version.py b/pytential/version.py index fd8dc42f..0118173d 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -5,4 +5,4 @@ VERSION_TEXT = ".".join(str(i) for i in VERSION) # branch name, so as to avoid conflicts with the master branch. Make sure # to reset this to the next number up with "master" before merging into # master. -PYTENTIAL_KERNEL_VERSION = ("remove-nosync", 8) +PYTENTIAL_KERNEL_VERSION = ("master", 9) -- GitLab From 3f43a9e3bd87884dc6addbf3fc86da4e86bb0f8c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 27 Feb 2018 00:35:19 -0600 Subject: [PATCH 035/268] Fix pytest script-based test invocation --- test/test_global_qbx.py | 2 +- test/test_layer_pot.py | 2 +- test/test_layer_pot_eigenvalues.py | 2 +- test/test_layer_pot_identity.py | 2 +- test/test_matrix.py | 2 +- test/test_maxwell.py | 2 +- test/test_muller.py | 2 +- test/test_scalar_int_eq.py | 2 +- test/test_stokes.py | 2 +- test/test_symbolic.py | 2 +- test/test_tools.py | 2 +- test/too_slow_test_helmholtz.py | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index c7609387..6fbd78d1 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -431,7 +431,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 diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index cb8669e4..b0766375 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -540,7 +540,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 diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index 2da95fcb..e60b72bf 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -382,7 +382,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 diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index fe001342..c019f930 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -410,7 +410,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 diff --git a/test/test_matrix.py b/test/test_matrix.py index d1558bcc..5485c502 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -178,7 +178,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 diff --git a/test/test_maxwell.py b/test/test_maxwell.py index 87420a10..914312e9 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -506,7 +506,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 diff --git a/test/test_muller.py b/test/test_muller.py index fb23e911..1516bb3c 100644 --- a/test/test_muller.py +++ b/test/test_muller.py @@ -82,5 +82,5 @@ 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__]) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 2fd80990..f10f471e 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -817,7 +817,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 diff --git a/test/test_stokes.py b/test/test_stokes.py index 3f456fc1..d8d5c821 100644 --- a/test/test_stokes.py +++ b/test/test_stokes.py @@ -292,7 +292,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 diff --git a/test/test_symbolic.py b/test/test_symbolic.py index 6894e15e..8b2e7cb4 100644 --- a/test/test_symbolic.py +++ b/test/test_symbolic.py @@ -177,7 +177,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 diff --git a/test/test_tools.py b/test/test_tools.py index a0f2ea8e..af774095 100644 --- a/test/test_tools.py +++ b/test/test_tools.py @@ -101,7 +101,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 diff --git a/test/too_slow_test_helmholtz.py b/test/too_slow_test_helmholtz.py index 9add5d05..25eeb075 100644 --- a/test/too_slow_test_helmholtz.py +++ b/test/too_slow_test_helmholtz.py @@ -421,7 +421,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 b664fd300bf70805d906116f90ce0db4b455db44 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Sun, 4 Mar 2018 16:27:15 -0600 Subject: [PATCH 036/268] Use sumpy and boxtree with compressed list 3 --- pytential/qbx/fmm.py | 52 +++++++++++++++++++++++++++++------ pytential/qbx/interactions.py | 50 +++++++++++++++++---------------- requirements.txt | 4 +-- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index a5292fde..ca03407c 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -247,8 +247,26 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), source_level_start_ibox, source_mpoles_view = \ self.multipole_expansions_view(multipole_exps, isrc_level) + qbx_center_to_target_box = geo_data.qbx_center_to_target_box() + target_box_to_target_box_source_level = cl.array.empty( + self.queue, len(traversal.target_boxes), + dtype=traversal.tree.box_id_dtype + ) + target_box_to_target_box_source_level.fill(-1) + target_box_to_target_box_source_level[ssn.nonempty_indices] = ( + cl.array.arange(self.queue, ssn.num_nonempty_lists, + dtype=traversal.tree.box_id_dtype) + ) + qbx_center_to_target_box_source_level = ( + target_box_to_target_box_source_level[ + qbx_center_to_target_box + ] + ) + evt, (qbx_expansions_res,) = m2qbxl(self.queue, - qbx_center_to_target_box=geo_data.qbx_center_to_target_box(), + qbx_center_to_target_box_source_level=( + qbx_center_to_target_box_source_level + ), centers=self.tree.box_centers, qbx_centers=geo_data.centers(), @@ -438,7 +456,7 @@ def drive_fmm(expansion_wrangler, src_weights): non_qbx_potentials = non_qbx_potentials + wrangler.eval_multipoles( traversal.level_start_target_box_nrs, - traversal.target_boxes, + traversal.target_boxes_sep_smaller_by_source_level, traversal.from_sep_smaller_by_level, mpole_exps) @@ -717,13 +735,15 @@ def assemble_performance_data(geo_data, uses_pde_expansions, assert tree.nlevels == len(traversal.from_sep_smaller_by_level) - for itgt_box, tgt_ibox in enumerate(traversal.target_boxes): - ntargets = box_target_counts_nonchild[tgt_ibox] - - for ilevel, sep_smaller_list in enumerate( - traversal.from_sep_smaller_by_level): + for ilevel, sep_smaller_list in enumerate( + traversal.from_sep_smaller_by_level): + for itgt_box, tgt_ibox in enumerate( + traversal.target_boxes_by_source_level[ilevel]): + ntargets = box_target_counts_nonchild[tgt_ibox] start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] - nmp_eval[ilevel, itgt_box] += ntargets * (end-start) + nmp_eval[ilevel, + traversal.from_sep_smaller_by_level.nonempty_indices[ + itgt_box]] = ntargets * (end-start) result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) @@ -856,8 +876,22 @@ def assemble_performance_data(geo_data, uses_pde_expansions, assert tree.nlevels == len(traversal.from_sep_smaller_by_level) for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): + target_box_to_target_box_source_level = np.empty( + (len(traversal.target_boxes),), + dtype=traversal.tree.box_id_dtype + ) + target_box_to_target_box_source_level[:] = -1 + target_box_to_target_box_source_level[ssn.nonempty_indices] = ( + np.arange(ssn.num_nonempty_lists, dtype=traversal.tree.box_id_dtype) + ) + for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - icontaining_tgt_box = qbx_center_to_target_box[tgt_icenter] + icontaining_tgt_box = target_box_to_target_box_source_level[ + qbx_center_to_target_box[tgt_icenter] + ] + + if icontaining_tgt_box == -1: + continue start, stop = ( ssn.starts[icontaining_tgt_box], diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 0cca9f17..3ac242ce 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -160,38 +160,40 @@ class M2QBXL(E2EBase): ], [""" for icenter - <> icontaining_tgt_box = qbx_center_to_target_box[icenter] + <> icontaining_tgt_box = \ + qbx_center_to_target_box_source_level[icenter] - <> tgt_center[idim] = qbx_centers[idim, icenter] \ - {id=fetch_tgt_center} - <> tgt_rscale = qbx_expansion_radii[icenter] + if icontaining_tgt_box != -1 + <> tgt_center[idim] = qbx_centers[idim, icenter] \ + {id=fetch_tgt_center} + <> tgt_rscale = qbx_expansion_radii[icenter] - <> isrc_start = src_box_starts[icontaining_tgt_box] - <> isrc_stop = src_box_starts[icontaining_tgt_box+1] + <> isrc_start = src_box_starts[icontaining_tgt_box] + <> isrc_stop = src_box_starts[icontaining_tgt_box+1] - for isrc_box - <> src_ibox = src_box_lists[isrc_box] \ - {id=read_src_ibox} - <> src_center[idim] = centers[idim, src_ibox] {dup=idim} - <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} - """] + [""" + for isrc_box + <> src_ibox = src_box_lists[isrc_box] \ + {id=read_src_ibox} + <> src_center[idim] = centers[idim, src_ibox] {dup=idim} + <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} + """] + [""" - <> src_coeff{i} = \ - src_expansions[src_ibox - src_base_ibox, {i}] \ - {{dep=read_src_ibox}} + <> src_coeff{i} = \ + src_expansions[src_ibox - src_base_ibox, {i}] \ + {{dep=read_src_ibox}} - """.format(i=i) for i in range(ncoeff_src)] + [ + """.format(i=i) for i in range(ncoeff_src)] + [ - ] + self.get_translation_loopy_insns() + [""" + ] + self.get_translation_loopy_insns() + [""" + end + """] + [""" + qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ + simul_reduce(sum, isrc_box, coeff{i}) \ + {{id_prefix=write_expn}} + """.format(i=i) + for i in range(ncoeff_tgt)] + [""" end - """] + [""" - qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ - simul_reduce(sum, isrc_box, coeff{i}) \ - {{id_prefix=write_expn}} - """.format(i=i) - for i in range(ncoeff_tgt)] + [""" - end """], [ diff --git a/requirements.txt b/requirements.txt index 8925c34e..130b783e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree +git+https://gitlab.tiker.net/inducer/boxtree@compress-list-3-new git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/inducer/sumpy@compress-list-3 git+https://github.com/inducer/pyfmmlib -- GitLab From f64b670326b6063412baa35e0aa6b69aadf71c6b Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Sun, 4 Mar 2018 16:31:00 -0600 Subject: [PATCH 037/268] Fix flake8 --- pytential/qbx/fmm.py | 4 ++-- pytential/qbx/interactions.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index ca03407c..8af1fa88 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -249,8 +249,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_center_to_target_box = geo_data.qbx_center_to_target_box() target_box_to_target_box_source_level = cl.array.empty( - self.queue, len(traversal.target_boxes), - dtype=traversal.tree.box_id_dtype + self.queue, len(traversal.target_boxes), + dtype=traversal.tree.box_id_dtype ) target_box_to_target_box_source_level.fill(-1) target_box_to_target_box_source_level[ssn.nonempty_indices] = ( diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 3ac242ce..dad4db1f 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -175,7 +175,8 @@ class M2QBXL(E2EBase): <> src_ibox = src_box_lists[isrc_box] \ {id=read_src_ibox} <> src_center[idim] = centers[idim, src_ibox] {dup=idim} - <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} + <> d[idim] = tgt_center[idim] - src_center[idim] \ + {dup=idim} """] + [""" <> src_coeff{i} = \ @@ -188,7 +189,8 @@ class M2QBXL(E2EBase): end """] + [""" - qbx_expansions[icenter, {i}] = qbx_expansions[icenter, {i}] + \ + qbx_expansions[icenter, {i}] = \ + qbx_expansions[icenter, {i}] + \ simul_reduce(sum, isrc_box, coeff{i}) \ {{id_prefix=write_expn}} """.format(i=i) -- GitLab From 946913b44d0595c7a4bd7cad171eff076dcfdc4e Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Sun, 4 Mar 2018 22:11:13 -0600 Subject: [PATCH 038/268] Adapt more code to compressed list 3 --- pytential/qbx/fmm.py | 8 ++++---- pytential/qbx/fmmlib.py | 31 ++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 8af1fa88..2b43917f 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -738,12 +738,12 @@ def assemble_performance_data(geo_data, uses_pde_expansions, for ilevel, sep_smaller_list in enumerate( traversal.from_sep_smaller_by_level): for itgt_box, tgt_ibox in enumerate( - traversal.target_boxes_by_source_level[ilevel]): + traversal.target_boxes_sep_smaller_by_source_level[ilevel]): ntargets = box_target_counts_nonchild[tgt_ibox] start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] - nmp_eval[ilevel, - traversal.from_sep_smaller_by_level.nonempty_indices[ - itgt_box]] = ntargets * (end-start) + nmp_eval[ilevel, sep_smaller_list.nonempty_indices[itgt_box]] = ( + ntargets * (end-start) + ) result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 887b3049..7ae75abb 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -351,19 +351,30 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): qbx_centers = geo_data.centers() centers = self.tree.box_centers ngqbx_centers = len(geo_data.global_qbx_centers()) + traversal = geo_data.traversal() if ngqbx_centers == 0: return qbx_exps mploc = self.get_translation_routine("%ddmploc", vec_suffix="_imany") - for isrc_level, ssn in enumerate( - geo_data.traversal().from_sep_smaller_by_level): + for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): source_level_start_ibox, source_mpoles_view = \ self.multipole_expansions_view(multipole_exps, isrc_level) + target_box_to_target_box_source_level = np.empty( + (len(traversal.target_boxes),), + dtype=traversal.tree.box_id_dtype + ) + target_box_to_target_box_source_level[:] = -1 + target_box_to_target_box_source_level[ssn.nonempty_indices] = ( + np.arange(ssn.num_nonempty_lists, dtype=traversal.tree.box_id_dtype) + ) + tgt_icenter_vec = geo_data.global_qbx_centers() - icontaining_tgt_box_vec = qbx_center_to_target_box[tgt_icenter_vec] + icontaining_tgt_box_vec = target_box_to_target_box_source_level[ + qbx_center_to_target_box[tgt_icenter_vec] + ] rscale2 = geo_data.expansion_radii()[geo_data.global_qbx_centers()] @@ -372,9 +383,13 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): kwargs["radius"] = (0.5 * geo_data.expansion_radii()[geo_data.global_qbx_centers()]) - nsrc_boxes_per_gqbx_center = ( - ssn.starts[icontaining_tgt_box_vec+1] - - ssn.starts[icontaining_tgt_box_vec]) + nsrc_boxes_per_gqbx_center = np.zeros(icontaining_tgt_box_vec.shape, + dtype=traversal.tree.box_id_dtype) + mask = (icontaining_tgt_box_vec != -1) + nsrc_boxes_per_gqbx_center[mask] = ( + ssn.starts[icontaining_tgt_box_vec[mask] + 1] - + ssn.starts[icontaining_tgt_box_vec[mask]] + ) nsrc_boxes = np.sum(nsrc_boxes_per_gqbx_center) src_boxes_starts = np.empty(ngqbx_centers+1, dtype=np.int32) @@ -387,7 +402,9 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): src_ibox = np.empty(nsrc_boxes, dtype=np.int32) for itgt_center, tgt_icenter in enumerate( geo_data.global_qbx_centers()): - icontaining_tgt_box = qbx_center_to_target_box[tgt_icenter] + icontaining_tgt_box = target_box_to_target_box_source_level[ + qbx_center_to_target_box[tgt_icenter] + ] src_ibox[ src_boxes_starts[itgt_center]: src_boxes_starts[itgt_center+1]] = ( -- GitLab From 4b42eb9bc588cee0274c523da6275e9406f7cc14 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 5 Mar 2018 08:27:08 -0600 Subject: [PATCH 039/268] Remove the first arg in eval_multipoles --- pytential/qbx/fmm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 2b43917f..13684459 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -455,7 +455,6 @@ def drive_fmm(expansion_wrangler, src_weights): # contribution *out* of the downward-propagating local expansions) non_qbx_potentials = non_qbx_potentials + wrangler.eval_multipoles( - traversal.level_start_target_box_nrs, traversal.target_boxes_sep_smaller_by_source_level, traversal.from_sep_smaller_by_level, mpole_exps) -- GitLab From ab6de721bbd8d1ed40fd0e7f009c2ce24ae44a2f Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 5 Mar 2018 08:53:08 -0600 Subject: [PATCH 040/268] Use updated boxtree and sumpy for conda --- .test-conda-env-py3-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index fa6c0426..b5cf0413 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ -git+https://gitlab.tiker.net/inducer/boxtree +git+https://gitlab.tiker.net/inducer/boxtree@compress-list-3-new git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/inducer/sumpy@compress-list-3 git+https://github.com/inducer/meshmode -- GitLab From e55940e3d45130889fad1a198ce54b21ff3833e5 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Wed, 7 Mar 2018 12:30:58 -0600 Subject: [PATCH 041/268] Cache qbx_center_to_target_box_source_level --- pytential/qbx/fmm.py | 31 +++++-------------------------- pytential/qbx/fmmlib.py | 21 +++++++-------------- pytential/qbx/geometry.py | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 13684459..18a34d57 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -247,25 +247,9 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), source_level_start_ibox, source_mpoles_view = \ self.multipole_expansions_view(multipole_exps, isrc_level) - qbx_center_to_target_box = geo_data.qbx_center_to_target_box() - target_box_to_target_box_source_level = cl.array.empty( - self.queue, len(traversal.target_boxes), - dtype=traversal.tree.box_id_dtype - ) - target_box_to_target_box_source_level.fill(-1) - target_box_to_target_box_source_level[ssn.nonempty_indices] = ( - cl.array.arange(self.queue, ssn.num_nonempty_lists, - dtype=traversal.tree.box_id_dtype) - ) - qbx_center_to_target_box_source_level = ( - target_box_to_target_box_source_level[ - qbx_center_to_target_box - ] - ) - evt, (qbx_expansions_res,) = m2qbxl(self.queue, qbx_center_to_target_box_source_level=( - qbx_center_to_target_box_source_level + geo_data.qbx_center_to_target_box_source_level(isrc_level) ), centers=self.tree.box_centers, @@ -875,18 +859,13 @@ def assemble_performance_data(geo_data, uses_pde_expansions, assert tree.nlevels == len(traversal.from_sep_smaller_by_level) for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): - target_box_to_target_box_source_level = np.empty( - (len(traversal.target_boxes),), - dtype=traversal.tree.box_id_dtype - ) - target_box_to_target_box_source_level[:] = -1 - target_box_to_target_box_source_level[ssn.nonempty_indices] = ( - np.arange(ssn.num_nonempty_lists, dtype=traversal.tree.box_id_dtype) + qbx_center_to_target_box_source_level = ( + geo_data.qbx_center_to_target_box_source_level(isrc_level).get() ) for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - icontaining_tgt_box = target_box_to_target_box_source_level[ - qbx_center_to_target_box[tgt_icenter] + icontaining_tgt_box = qbx_center_to_target_box_source_level[ + tgt_icenter ] if icontaining_tgt_box == -1: diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 7ae75abb..d06967a9 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -347,7 +347,6 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): qbx_exps = self.qbx_local_expansion_zeros() geo_data = self.geo_data - qbx_center_to_target_box = geo_data.qbx_center_to_target_box() qbx_centers = geo_data.centers() centers = self.tree.box_centers ngqbx_centers = len(geo_data.global_qbx_centers()) @@ -362,18 +361,12 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): source_level_start_ibox, source_mpoles_view = \ self.multipole_expansions_view(multipole_exps, isrc_level) - target_box_to_target_box_source_level = np.empty( - (len(traversal.target_boxes),), - dtype=traversal.tree.box_id_dtype - ) - target_box_to_target_box_source_level[:] = -1 - target_box_to_target_box_source_level[ssn.nonempty_indices] = ( - np.arange(ssn.num_nonempty_lists, dtype=traversal.tree.box_id_dtype) - ) - tgt_icenter_vec = geo_data.global_qbx_centers() - icontaining_tgt_box_vec = target_box_to_target_box_source_level[ - qbx_center_to_target_box[tgt_icenter_vec] + qbx_center_to_target_box_source_level = ( + geo_data.qbx_center_to_target_box_source_level(isrc_level).get() + ) + icontaining_tgt_box_vec = qbx_center_to_target_box_source_level[ + tgt_icenter_vec ] rscale2 = geo_data.expansion_radii()[geo_data.global_qbx_centers()] @@ -402,8 +395,8 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): src_ibox = np.empty(nsrc_boxes, dtype=np.int32) for itgt_center, tgt_icenter in enumerate( geo_data.global_qbx_centers()): - icontaining_tgt_box = target_box_to_target_box_source_level[ - qbx_center_to_target_box[tgt_icenter] + icontaining_tgt_box = qbx_center_to_target_box_source_level[ + tgt_icenter ] src_ibox[ src_boxes_starts[itgt_center]: diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 7cc8e8ea..30fbb8be 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -614,6 +614,33 @@ class QBXFMMGeometryData(object): return qbx_center_to_target_box.with_queue(None) + @memoize_method + def qbx_center_to_target_box_source_level(self, source_level): + """Return an array for mapping qbx centers to + `traversal.from_sep_smaller_by_level[source_level]`. + """ + traversal = self.traversal() + sep_smaller = traversal.from_sep_smaller_by_level[source_level] + qbx_center_to_target_box = self.qbx_center_to_target_box() + + target_box_to_target_box_source_level = cl.array.empty( + self.queue, len(traversal.target_boxes), + dtype=traversal.tree.box_id_dtype + ) + target_box_to_target_box_source_level.fill(-1) + target_box_to_target_box_source_level[sep_smaller.nonempty_indices] = ( + cl.array.arange(self.queue, sep_smaller.num_nonempty_lists, + dtype=traversal.tree.box_id_dtype) + ) + + qbx_center_to_target_box_source_level = ( + target_box_to_target_box_source_level[ + qbx_center_to_target_box + ] + ) + + return qbx_center_to_target_box_source_level.with_queue(None) + @memoize_method def global_qbx_flags(self): """Return an array of :class:`numpy.int8` of length -- GitLab From ef40ea850265c97e0980d2f0807485d42e51bcea Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Wed, 7 Mar 2018 23:15:54 -0600 Subject: [PATCH 042/268] Bug fix: add command queue for device array operations --- pytential/qbx/fmm.py | 16 +++++++++++----- pytential/qbx/fmmlib.py | 4 +++- pytential/qbx/geometry.py | 35 ++++++++++++++++++----------------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 18a34d57..39fc5385 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -775,6 +775,13 @@ def assemble_performance_data(geo_data, uses_pde_expansions, global_qbx_centers = geo_data.global_qbx_centers() qbx_center_to_target_box = geo_data.qbx_center_to_target_box() center_to_targets_starts = geo_data.center_to_tree_targets().starts + qbx_center_to_target_box_source_level = np.empty( + (len(tree.nlevels),), dtype=object + ) + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + geo_data.qbx_center_to_target_box_source_level(src_level) + ) with cl.CommandQueue(geo_data.cl_context) as queue: global_qbx_centers = global_qbx_centers.get( @@ -783,6 +790,9 @@ def assemble_performance_data(geo_data, uses_pde_expansions, queue=queue) center_to_targets_starts = center_to_targets_starts.get( queue=queue) + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level].get( + queue=queue) def process_form_qbxl(): ncenters = geo_data.ncenters @@ -859,14 +869,10 @@ def assemble_performance_data(geo_data, uses_pde_expansions, assert tree.nlevels == len(traversal.from_sep_smaller_by_level) for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): - qbx_center_to_target_box_source_level = ( - geo_data.qbx_center_to_target_box_source_level(isrc_level).get() - ) for itgt_center, tgt_icenter in enumerate(global_qbx_centers): icontaining_tgt_box = qbx_center_to_target_box_source_level[ - tgt_icenter - ] + isrc_level][tgt_icenter] if icontaining_tgt_box == -1: continue diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index d06967a9..bd4d025f 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -363,7 +363,9 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): tgt_icenter_vec = geo_data.global_qbx_centers() qbx_center_to_target_box_source_level = ( - geo_data.qbx_center_to_target_box_source_level(isrc_level).get() + geo_data.qbx_center_to_target_box_source_level(isrc_level).get( + self.queue + ) ) icontaining_tgt_box_vec = qbx_center_to_target_box_source_level[ tgt_icenter_vec diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 30fbb8be..da98eadd 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -623,23 +623,24 @@ class QBXFMMGeometryData(object): sep_smaller = traversal.from_sep_smaller_by_level[source_level] qbx_center_to_target_box = self.qbx_center_to_target_box() - target_box_to_target_box_source_level = cl.array.empty( - self.queue, len(traversal.target_boxes), - dtype=traversal.tree.box_id_dtype - ) - target_box_to_target_box_source_level.fill(-1) - target_box_to_target_box_source_level[sep_smaller.nonempty_indices] = ( - cl.array.arange(self.queue, sep_smaller.num_nonempty_lists, - dtype=traversal.tree.box_id_dtype) - ) - - qbx_center_to_target_box_source_level = ( - target_box_to_target_box_source_level[ - qbx_center_to_target_box - ] - ) - - return qbx_center_to_target_box_source_level.with_queue(None) + with cl.CommandQueue(self.cl_context) as queue: + target_box_to_target_box_source_level = cl.array.empty( + queue, len(traversal.target_boxes), + dtype=traversal.tree.box_id_dtype + ) + target_box_to_target_box_source_level.fill(-1) + target_box_to_target_box_source_level[sep_smaller.nonempty_indices] = ( + cl.array.arange(queue, sep_smaller.num_nonempty_lists, + dtype=traversal.tree.box_id_dtype) + ) + + qbx_center_to_target_box_source_level = ( + target_box_to_target_box_source_level[ + qbx_center_to_target_box + ] + ) + + return qbx_center_to_target_box_source_level.with_queue(None) @memoize_method def global_qbx_flags(self): -- GitLab From 1a15f5b66e126c7a0313353acf3d6b0f499aae1e Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Wed, 7 Mar 2018 23:57:23 -0600 Subject: [PATCH 043/268] Bug fix --- pytential/qbx/fmm.py | 7 ++++--- pytential/qbx/fmmlib.py | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 39fc5385..037f8188 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -776,7 +776,7 @@ def assemble_performance_data(geo_data, uses_pde_expansions, qbx_center_to_target_box = geo_data.qbx_center_to_target_box() center_to_targets_starts = geo_data.center_to_tree_targets().starts qbx_center_to_target_box_source_level = np.empty( - (len(tree.nlevels),), dtype=object + (tree.nlevels,), dtype=object ) for src_level in range(tree.nlevels): qbx_center_to_target_box_source_level[src_level] = ( @@ -791,8 +791,9 @@ def assemble_performance_data(geo_data, uses_pde_expansions, center_to_targets_starts = center_to_targets_starts.get( queue=queue) for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level].get( - queue=queue) + qbx_center_to_target_box_source_level[src_level] = ( + qbx_center_to_target_box_source_level[src_level].get(queue=queue) + ) def process_form_qbxl(): ncenters = geo_data.ncenters diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index bd4d025f..578dadce 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -99,6 +99,11 @@ class ToHostTransferredGeoDataWrapper(object): def qbx_center_to_target_box(self): return self.geo_data.qbx_center_to_target_box().get(queue=self.queue) + @memoize_method + def qbx_center_to_target_box_source_level(self, source_level): + return self.geo_data.qbx_center_to_target_box_source_level( + source_level).get(queue=self.queue) + @memoize_method def non_qbx_box_target_lists(self): return self.geo_data.non_qbx_box_target_lists().get(queue=self.queue) @@ -363,9 +368,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): tgt_icenter_vec = geo_data.global_qbx_centers() qbx_center_to_target_box_source_level = ( - geo_data.qbx_center_to_target_box_source_level(isrc_level).get( - self.queue - ) + geo_data.qbx_center_to_target_box_source_level(isrc_level) ) icontaining_tgt_box_vec = qbx_center_to_target_box_source_level[ tgt_icenter_vec -- GitLab From c2ddc150df0904b5990e46d9d647b0c35d454c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 9 Mar 2018 01:15:02 -0500 Subject: [PATCH 044/268] Improve docs on qbx_center_to_target_box_source_level --- pytential/qbx/geometry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index da98eadd..f9cc10e0 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -616,8 +616,10 @@ class QBXFMMGeometryData(object): @memoize_method def qbx_center_to_target_box_source_level(self, source_level): - """Return an array for mapping qbx centers to - `traversal.from_sep_smaller_by_level[source_level]`. + """Return an array for mapping qbx centers to indices into + interaction lists as found in + ``traversal.from_sep_smaller_by_level[source_level].`` + -1 if no such interaction list exist on *source_level*. """ traversal = self.traversal() sep_smaller = traversal.from_sep_smaller_by_level[source_level] -- GitLab From 81d072b10b11dfd1b2e342a62a42ab7f9d615086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 9 Mar 2018 01:58:40 -0500 Subject: [PATCH 045/268] Point .test-conda-env-py3-requirements.txt back at master for sumpy, boxtree [ci skip] --- .test-conda-env-py3-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index b5cf0413..fa6c0426 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ -git+https://gitlab.tiker.net/inducer/boxtree@compress-list-3-new +git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy@compress-list-3 +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/meshmode -- GitLab From 1740ebfd4bbb3cdfc81974eb9a51d406406deb32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 9 Mar 2018 02:00:56 -0500 Subject: [PATCH 046/268] Point requirements.txt back at sumpy, boxtree master --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 130b783e..8925c34e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree@compress-list-3-new +git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy@compress-list-3 +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib -- GitLab From ee5ef623eebdfbe93d7076418338564ce3e34d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 9 Mar 2018 13:06:28 -0500 Subject: [PATCH 047/268] Update .gitlab-ci.yml: Allow two retries of Conda Apple --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 48d7cd3f..750bf6f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,6 +80,7 @@ Python 3.5 Conda Apple: - apple except: - tags + retry: 2 Documentation: script: -- GitLab From 810708837f7f8e0d76ed467d17017e8185b9b704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 9 Mar 2018 14:01:48 -0500 Subject: [PATCH 048/268] Bump kernel version for https://gitlab.tiker.net/inducer/pytential/merge_requests/91 --- pytential/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/version.py b/pytential/version.py index 0118173d..426eafaf 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -5,4 +5,4 @@ VERSION_TEXT = ".".join(str(i) for i in VERSION) # branch name, so as to avoid conflicts with the master branch. Make sure # to reset this to the next number up with "master" before merging into # master. -PYTENTIAL_KERNEL_VERSION = ("master", 9) +PYTENTIAL_KERNEL_VERSION = ("master", 10) -- GitLab From dad1ba1ae6cbc5f5691e21c667680b4e3b843f06 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 11 Mar 2018 01:29:50 -0600 Subject: [PATCH 049/268] Try hard to find a git revision to use as cache key --- .gitignore | 2 + pytential/version.py | 51 +++++++++++-- setup.py | 165 +++++++++++++++++++++++++++---------------- 3 files changed, 154 insertions(+), 64 deletions(-) diff --git a/.gitignore b/.gitignore index 3d5b8042..c5d29d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ examples/*.pdf .cache tags + +pytential/_git_rev.py diff --git a/pytential/version.py b/pytential/version.py index 0118173d..ea408c3f 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -1,8 +1,49 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2018 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. +""" + +# {{{ find install- or run-time git revision + +import os +if os.environ.get("AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY") is not None: + # We're just being exec'd by setup.py. We can't import anything. + _git_rev = None + +else: + import loopy._git_rev as _git_rev_mod + _git_rev = _git_rev_mod.GIT_REVISION + + # If we're running from a dev tree, the last install (and hence the most + # recent update of the above git rev could have taken place very long ago. + from pytools import find_module_git_revision + _runtime_git_rev = find_module_git_revision(__file__, n_levels_up=1) + if _runtime_git_rev is not None: + _git_rev = _runtime_git_rev + +# }}} + + VERSION = (2016, 1) VERSION_TEXT = ".".join(str(i) for i in VERSION) -# When developing on a branch, set the first element of this tuple to your -# branch name, so as to avoid conflicts with the master branch. Make sure -# to reset this to the next number up with "master" before merging into -# master. -PYTENTIAL_KERNEL_VERSION = ("master", 9) +PYTENTIAL_KERNEL_VERSION = (VERSION, _git_rev, 0) diff --git a/setup.py b/setup.py index d8d49a9c..84438494 100644 --- a/setup.py +++ b/setup.py @@ -1,63 +1,110 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +from setuptools import setup, find_packages -def main(): - from setuptools import setup, find_packages - - version_dict = {} - init_filename = "pytential/version.py" - exec(compile(open(init_filename, "r").read(), init_filename, "exec"), - version_dict) - - setup(name="pytential", - version=version_dict["VERSION_TEXT"], - description="Evaluate layer and volume potentials accurately. " - "Solve integral equations.", - long_description=open("README.rst", "rt").read(), - author="Andreas Kloeckner", - author_email="inform@tiker.net", - license="MIT", - url="http://wiki.tiker.net/Pytential", - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Intended Audience :: Other Audience', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Programming Language :: Python', - - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - # 3.x has not yet been tested. - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Information Analysis', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Scientific/Engineering :: Visualization', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities', - ], - - packages=find_packages(), - - install_requires=[ - "pytest>=2.3", - # FIXME leave out for now - # https://code.google.com/p/sympy/issues/detail?id=3874 - #"sympy>=0.7.2", - - "modepy>=2013.3", - "pyopencl>=2013.1", - "boxtree>=2013.1", - "pymbolic>=2013.2", - "loo.py>=2017.2", - "sumpy>=2013.1", - "cgen>=2013.1.2", - - "six", - ]) - - -if __name__ == '__main__': - main() + +# {{{ capture git revision at install time + +# authoritative version in pytools/__init__.py +def find_git_revision(tree_root): + # Keep this routine self-contained so that it can be copy-pasted into + # setup.py. + + from os.path import join, exists, abspath + tree_root = abspath(tree_root) + + if not exists(join(tree_root, ".git")): + return None + + from subprocess import Popen, PIPE, STDOUT + p = Popen(["git", "rev-parse", "HEAD"], shell=False, + stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True, + cwd=tree_root) + (git_rev, _) = p.communicate() + + import sys + if sys.version_info >= (3,): + git_rev = git_rev.decode() + + git_rev = git_rev.rstrip() + + retcode = p.returncode + assert retcode is not None + if retcode != 0: + from warnings import warn + warn("unable to find git revision") + return None + + return git_rev + + +def write_git_revision(package_name): + from os.path import dirname, join + dn = dirname(__file__) + git_rev = find_git_revision(dn) + + with open(join(dn, package_name, "_git_rev.py"), "w") as outf: + outf.write("GIT_REVISION = %s\n" % repr(git_rev)) + + +write_git_revision("pytential") + +# }}} + + +version_dict = {} +init_filename = "pytential/version.py" +os.environ["AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY"] = "1" +exec(compile(open(init_filename, "r").read(), init_filename, "exec"), + version_dict) + +setup(name="pytential", + version=version_dict["VERSION_TEXT"], + description="Evaluate layer and volume potentials accurately. " + "Solve integral equations.", + long_description=open("README.rst", "rt").read(), + author="Andreas Kloeckner", + author_email="inform@tiker.net", + license="MIT", + url="http://wiki.tiker.net/Pytential", + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Intended Audience :: Other Audience', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Programming Language :: Python', + + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + # 3.x has not yet been tested. + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Information Analysis', + 'Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Scientific/Engineering :: Visualization', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities', + ], + + packages=find_packages(), + + install_requires=[ + "pytest>=2.3", + # FIXME leave out for now + # https://code.google.com/p/sympy/issues/detail?id=3874 + #"sympy>=0.7.2", + + "pytools>=2018.2", + "modepy>=2013.3", + "pyopencl>=2013.1", + "boxtree>=2013.1", + "pymbolic>=2013.2", + "loo.py>=2017.2", + "sumpy>=2013.1", + "cgen>=2013.1.2", + + "six", + ]) -- GitLab From 405c4e77007d43b6ac25b844ef65b4fdb5a86b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Sun, 11 Mar 2018 03:48:26 -0400 Subject: [PATCH 050/268] Add missing paren in comment --- pytential/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/version.py b/pytential/version.py index ea408c3f..2ba7c901 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -34,7 +34,7 @@ else: _git_rev = _git_rev_mod.GIT_REVISION # If we're running from a dev tree, the last install (and hence the most - # recent update of the above git rev could have taken place very long ago. + # recent update of the above git rev) could have taken place very long ago. from pytools import find_module_git_revision _runtime_git_rev = find_module_git_revision(__file__, n_levels_up=1) if _runtime_git_rev is not None: -- GitLab From c677061a652120128445bd9d32ee03a3ea4a4b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Sun, 11 Mar 2018 14:07:58 -0400 Subject: [PATCH 051/268] Fix git rev module reference: loopy->sumpy. --- pytential/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/version.py b/pytential/version.py index 2ba7c901..5cb9cc61 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -30,7 +30,7 @@ if os.environ.get("AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY") is not None: _git_rev = None else: - import loopy._git_rev as _git_rev_mod + import pytential._git_rev as _git_rev_mod _git_rev = _git_rev_mod.GIT_REVISION # If we're running from a dev tree, the last install (and hence the most -- GitLab From e0a85759d60b26a5e6b0b15ea876c69c25efd17a Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 8 Mar 2018 15:54:19 -0600 Subject: [PATCH 052/268] update LayerPotentialOnTargetAndCenterSubset --- .pytest_cache/v/cache/lastfailed | 21 +++++++ pytential/qbx/direct.py | 100 ++++++++++++++++++------------- 2 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 .pytest_cache/v/cache/lastfailed diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed new file mode 100644 index 00000000..fdfc5685 --- /dev/null +++ b/.pytest_cache/v/cache/lastfailed @@ -0,0 +1,21 @@ +{ + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, + "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-3-False]": true, + "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-3-False]": true, + "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-4-False]": true, + "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-6-3-False]": true, + "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-7-5-False]": true, + "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case11]": true, + "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case2]": true, + "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case3]": true, + "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case4]": true, + "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case9]": true, + "test/test_scalar_int_eq.py::test_integral_equation[ctx_getter=>-case6]": true, + "test/test_scalar_int_eq.py::test_integral_equation[ctx_getter=>-case7]": true +} \ No newline at end of file diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 6dc5cd9a..9eebef8f 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -31,54 +31,74 @@ from sumpy.qbx import LayerPotential as LayerPotentialBase # {{{ qbx applier on a target/center subset class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): - def get_compute_a_and_b_vecs(self): - return """ - <> icenter = qbx_center_numbers[itgt] - <> itgt_overall = qbx_tgt_numbers[itgt] - for idim - <> a[idim] = center[idim,icenter] - src[idim,isrc] {id=compute_a} - <> b[idim] = tgt[idim,itgt_overall] - center[idim,icenter] \ - {id=compute_b} - <> rscale = expansion_radii[icenter] - end - """ - - def get_src_tgt_arguments(self): - return [ + default_name = "qbx_tgt_ctr_subset" + + def get_kernel(self): + loopy_insns, result_names = self.get_loopy_insns_and_result_names() + kernel_exprs = self.get_kernel_exprs(result_names) + + from sumpy.tools import gather_loopy_source_arguments + arguments = ( + gather_loopy_source_arguments(self.kernels) + + [ lp.GlobalArg("src", None, shape=(self.dim, "nsources"), order="C"), lp.GlobalArg("tgt", None, shape=(self.dim, "ntargets_total"), order="C"), lp.GlobalArg("center", None, - shape=(self.dim, "ncenters_total"), order="C"), - lp.GlobalArg("expansion_radii", None, shape="ncenters_total"), - lp.GlobalArg("qbx_tgt_numbers", None, shape="ntargets"), - lp.GlobalArg("qbx_center_numbers", None, shape="ntargets"), + shape=(self.dim, "ncenters_total"), dim_tags="sep,C"), + lp.GlobalArg("expansion_radii", None, + shape="ncenters_total"), + lp.GlobalArg("qbx_tgt_numbers", None, + shape="ntargets"), + lp.GlobalArg("qbx_center_numbers", None, + shape="ntargets"), lp.ValueArg("nsources", np.int32), lp.ValueArg("ntargets", np.int32), lp.ValueArg("ntargets_total", np.int32), - lp.ValueArg("ncenters_total", np.int32), - ] - - def get_input_and_output_arguments(self): - return [ - lp.GlobalArg("strength_%d" % i, None, shape="nsources", order="C") - for i in range(self.strength_count) - ]+[ - lp.GlobalArg("result_%d" % i, None, shape="ntargets_total", - order="C") - for i in range(len(self.kernels)) - ] - - def get_result_store_instructions(self): - return [ - """ - result_KNLIDX[itgt_overall] = \ - knl_KNLIDX_scaling*simul_reduce(\ - sum, isrc, pair_result_KNLIDX) {inames=itgt} - """.replace("KNLIDX", str(iknl)) - for iknl in range(len(self.expansions)) - ] + lp.ValueArg("ncenters_total", np.int32)] + + [lp.GlobalArg("strength_%d" % i, None, + shape="nsources", order="C") + for i in range(self.strength_count)] + + [lp.GlobalArg("result_%d" % i, self.value_dtypes[i], + shape="ntargets_total", order="C") + for i in range(len(self.kernels))]) + + loopy_knl = lp.make_kernel([ + "{[itgt]: 0 <= itgt < ntargets}", + "{[isrc]: 0 <= isrc < nsources}", + "{[idim]: 0 <= idim < dim}" + ], + self.get_kernel_scaling_assignments() + + ["for itgt, isrc"] + + [""" + <> icenter = qbx_center_numbers[itgt] + <> itgt_overall = qbx_tgt_numbers[itgt] + + <> a[idim] = center[idim, icenter] - src[idim, isrc] \ + {dup=idim} + <> b[idim] = tgt[idim, itgt_overall] - center[idim, icenter] \ + {dup=idim} + <> rscale = expansion_radii[icenter] + """] + + loopy_insns + kernel_exprs + + [""" + result_{i}[itgt_overall] = knl_{i}_scaling * \ + simul_reduce(sum, isrc, pair_result_{i}) \ + {{inames=itgt}} + """.format(i=iknl) + for iknl in range(len(self.expansions))] + +["end"], + arguments, + name=self.name, + assumptions="ntargets>=1 and nsources>=1", + fixed_parameters=dict(dim=self.dim)) + + loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") + for expn in self.expansions: + loopy_knl = expn.prepare_loopy_kernel(loopy_knl) + + return loopy_knl # }}} -- GitLab From 203b6c134ba2c2bf19be90f08a98305d9bd3ee08 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 8 Mar 2018 16:44:08 -0600 Subject: [PATCH 053/268] update requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8925c34e..abad1181 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/fikl2/sumpy@p2p-cleanup git+https://github.com/inducer/pyfmmlib -- GitLab From 087a9d4ef805d7d76674b9b3e8089d3352c5ede6 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 8 Mar 2018 16:53:30 -0600 Subject: [PATCH 054/268] flake8 fix --- pytential/qbx/direct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 9eebef8f..5ea022f7 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -88,7 +88,7 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): {{inames=itgt}} """.format(i=iknl) for iknl in range(len(self.expansions))] - +["end"], + + ["end"], arguments, name=self.name, assumptions="ntargets>=1 and nsources>=1", -- GitLab From c7f56b462fb41ace21bbcbfb791a670deeec969b Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 8 Mar 2018 21:55:41 -0600 Subject: [PATCH 055/268] remove .pytest_cache --- .pytest_cache/v/cache/lastfailed | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .pytest_cache/v/cache/lastfailed diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed deleted file mode 100644 index fdfc5685..00000000 --- a/.pytest_cache/v/cache/lastfailed +++ /dev/null @@ -1,21 +0,0 @@ -{ - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot.py::test_off_surface_eval[ctx_getter=>-False]": true, - "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-3-False]": true, - "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-3-False]": true, - "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-5-4-False]": true, - "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-6-3-False]": true, - "test/test_layer_pot_eigenvalues.py::test_ellipse_eigenvalues[ctx_getter=>-1-7-5-False]": true, - "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case11]": true, - "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case2]": true, - "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case3]": true, - "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case4]": true, - "test/test_layer_pot_identity.py::test_identity_convergence[ctx_getter=>-case9]": true, - "test/test_scalar_int_eq.py::test_integral_equation[ctx_getter=>-case6]": true, - "test/test_scalar_int_eq.py::test_integral_equation[ctx_getter=>-case7]": true -} \ No newline at end of file -- GitLab From 7f520078e0236c0cd120a14ab93a1d57a60d711d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 11 Mar 2018 17:02:43 -0500 Subject: [PATCH 056/268] update conda test requirements --- .test-conda-env-py3-requirements.txt | 2 +- pytential/qbx/direct.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index fa6c0426..69094225 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/fikl2/sumpy@p2p-cleanup git+https://github.com/inducer/meshmode diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 5ea022f7..8f65e436 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -25,7 +25,7 @@ THE SOFTWARE. import loopy as lp import numpy as np -from sumpy.qbx import LayerPotential as LayerPotentialBase +from sumpy.qbx import LayerPotentialBase # {{{ qbx applier on a target/center subset -- GitLab From d4abe00df8bb55ff34afb49c001d58fcb9332c73 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 11 Mar 2018 18:29:31 -0500 Subject: [PATCH 057/268] LayerPotentialOnTargetAndCenterSubset: make class callable again --- pytential/qbx/direct.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 8f65e436..70fa0d1a 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -100,7 +100,16 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): return loopy_knl -# }}} + def __call__(self, queue, targets, sources, centers, strengths, expansion_radii, + **kwargs): + knl = self.get_cached_optimized_kernel() + + for i, dens in enumerate(strengths): + kwargs["strength_%d" % i] = dens + return knl(queue, src=sources, tgt=targets, center=centers, + expansion_radii=expansion_radii, **kwargs) + +# }}} # vim: foldmethod=marker -- GitLab From c53d4fb788c14116314d7e9cd39abc847306803c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Thu, 15 Mar 2018 16:16:17 -0400 Subject: [PATCH 058/268] Point .test-conda-env-py3-requirements.txt back at sumpy master --- .test-conda-env-py3-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index 69094225..fa6c0426 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/fikl2/sumpy@p2p-cleanup +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/meshmode -- GitLab From 9a8eddda2c9933d193209fd758037b2bb28cc306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Thu, 15 Mar 2018 16:33:12 -0400 Subject: [PATCH 059/268] Point requirements.txt back at sumpy master --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index abad1181..8925c34e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/fikl2/sumpy@p2p-cleanup +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib -- GitLab From 636b4523c7227efd1f3806b2f5139ca1218ca887 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 16 Mar 2018 17:20:38 -0500 Subject: [PATCH 060/268] Add build badge, prettify README --- README.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index cbd54048..bababc0a 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,10 @@ -pytential -========= +pytential: 2D/3D Layer Potential Evaluation +=========================================== + +.. image:: https://gitlab.tiker.net/inducer/pytential/badges/master/pipeline.svg + :target: https://gitlab.tiker.net/inducer/pytential/commits/master +.. image:: https://badge.fury.io/py/pytential.png + :target: http://pypi.python.org/pypi/pytential pytential helps you accurately evaluate layer potentials (and, sooner or later, volume potentials). @@ -23,9 +28,6 @@ and, indirectly, PyOpenCL is likely the only package you'll have to install by hand, all the others will be installed automatically. -.. image:: https://badge.fury.io/py/pytential.png - :target: http://pypi.python.org/pypi/pytential - Resources: * `documentation `_ -- GitLab From d57d3906987a8ccd5fe71ba5af2eef0bbce81ad9 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 01:17:28 -0500 Subject: [PATCH 061/268] Introduce a smarter quadrature resolution measure This measure is aware of element aspect-ratios in 3D and and varying 'parametrization speeds' across an element. It's also computed using the regular expression evaluator, not with custom machinery. The evaluator was made smarter as needed--such as by allowing it to operate on the 'stage 2' discretization of a layer potential source. This quadrature resolution measure replaces panel sizes where appropriate. --- pytential/qbx/__init__.py | 68 ++++++++++-- pytential/qbx/refinement.py | 7 +- pytential/qbx/utils.py | 92 +++++++--------- pytential/symbolic/execution.py | 42 ++++++++ pytential/symbolic/mappers.py | 17 +++ pytential/symbolic/primitives.py | 176 +++++++++++++++++++++++++++---- test/test_global_qbx.py | 17 +-- 7 files changed, 321 insertions(+), 98 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index d5d52ca6..848eeed1 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -399,8 +399,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def h_max(self): with cl.CommandQueue(self.cl_context) as queue: - panel_sizes = self._panel_sizes("npanels").with_queue(queue) - return np.asscalar(cl.array.max(panel_sizes).get()) + quad_res = self._coarsest_quad_resolution("npanels").with_queue(queue) + return np.asscalar(cl.array.max(quad_res).get()) # {{{ internal API @@ -410,7 +410,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): return utils.element_centers_of_mass(self.density_discr) @memoize_method - def _fine_panel_centers_of_mass(self): + def _stage2_panel_centers_of_mass(self): import pytential.qbx.utils as utils return utils.element_centers_of_mass(self.stage2_density_discr) @@ -422,29 +422,77 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): "not allowed. Allowed values are 'nsources' and 'ncenters'.") with cl.CommandQueue(self.cl_context) as queue: - return (self._panel_sizes(last_dim_length).with_queue(queue) * 0.5 - ).with_queue(None) + return (self._coarsest_quad_resolution(last_dim_length) + .with_queue(queue) + * 0.5).with_queue(None) # _expansion_radii should not be needed for the fine discretization @memoize_method def _close_target_tunnel_radius(self, last_dim_length): with cl.CommandQueue(self.cl_context) as queue: - return (self._panel_sizes(last_dim_length).with_queue(queue) * 0.5 + return ( + self._expansion_radii(last_dim_length).with_queue(queue) + * 0.5 ).with_queue(None) @memoize_method - def _panel_sizes(self, last_dim_length="npanels"): + def _coarsest_quad_resolution(self, last_dim_length="npanels"): + """This measures the quadrature resolution across the + mesh. In a 1D uniform mesh of uniform 'parametrization speed', it + should be the same as the panel length. + + It is empirically about a factor of 1.2 larger than sym._panel_size for + an ellipse, presumably because it uses the largest 'parametrization + speed'/'stretch factor' across the whole element. + """ import pytential.qbx.utils as utils - return utils.panel_sizes(self.density_discr, last_dim_length) + from pytential import sym, bind + with cl.CommandQueue(self.cl_context) as queue: + maxstretch = bind( + self, sym.mapping_max_stretch_factor(self.ambient_dim))(queue) + + maxstretch = utils.to_last_dim_length( + self.density_discr, maxstretch, last_dim_length) + maxstretch = maxstretch.with_queue(None) + + return maxstretch @memoize_method - def _fine_panel_sizes(self, last_dim_length="npanels"): + def _stage2_coarsest_quad_resolution(self, last_dim_length="npanels"): + """This measures the quadrature resolution across the + mesh. In a 1D uniform mesh of uniform 'parametrization speed', it + should be the same as the panel length. + + It is empirically about a factor of 1.2 larger than sym._panel_size for + an ellipse, presumably because it uses the largest 'parametrization + speed'/'stretch factor' across the whole element. + """ if last_dim_length != "npanels": + # Not technically required below, but no need to loosen for now. raise NotImplementedError() import pytential.qbx.utils as utils - return utils.panel_sizes(self.stage2_density_discr, last_dim_length) + from pytential import sym, bind + with cl.CommandQueue(self.cl_context) as queue: + maxstretch = bind( + self, sym.mapping_max_stretch_factor( + self.ambient_dim, + where=sym._QBXSourceStage2(sym.DEFAULT_SOURCE)) + )(queue) + maxstretch = utils.to_last_dim_length( + self.stage2_density_discr, maxstretch, last_dim_length) + maxstretch = maxstretch.with_queue(None) + + return maxstretch + + @memoize_method + def _source_danger_zone_radii(self, last_dim_length="npanels"): + quad_res = self._stage2_coarsest_quad_resolution(last_dim_length) + with cl.CommandQueue(self.cl_context) as queue: + return (quad_res + .with_queue(queue) + * 0.25).with_queue(None) @memoize_method def qbx_fmm_geometry_data(self, target_discrs_and_qbx_sides): diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 08539c68..2a1d8b7f 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -365,9 +365,8 @@ class RefinerWrangler(TreeWranglerBase): found_panel_to_refine = cl.array.zeros(self.queue, 1, np.int32) found_panel_to_refine.finish() - source_danger_zone_radii_by_panel = ( - lpot_source._fine_panel_sizes("npanels") - .with_queue(self.queue) / 4) + source_danger_zone_radii_by_panel = \ + lpot_source._source_danger_zone_radii("npanels") unwrap_args = AreaQueryElementwiseTemplate.unwrap_args evt = knl( @@ -410,7 +409,7 @@ class RefinerWrangler(TreeWranglerBase): npanels_to_refine_prev = cl.array.sum(refine_flags).get() evt, out = knl(self.queue, - panel_sizes=lpot_source._panel_sizes("npanels"), + panel_sizes=lpot_source._coarsest_quad_resolution("npanels"), refine_flags=refine_flags, refine_flags_updated=np.array(0), kernel_length_scale=np.array(kernel_length_scale), diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index 6673b450..84626479 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -205,68 +205,51 @@ class TreeWranglerBase(object): # }}} -# {{{ panel sizes +# {{{ to_last_dim_length -def panel_sizes(discr, last_dim_length): - if last_dim_length not in ("nsources", "ncenters", "npanels"): - raise ValueError( - "invalid value of last_dim_length: %s" % last_dim_length) +def to_last_dim_length(discr, vec, last_dim_length, queue=None): + """Takes a :class:`pyopencl.array.Array` with a last axis that has the same + length as the number of discretization nodes in the discretization *discr* + and converts it so that the last axis has a length as specified by + *last_dim_length*. + """ - # To get the panel size this does the equivalent of (∫ 1 ds)**(1/dim). - # FIXME: Kernel optimizations + queue = queue or vec.queue - if last_dim_length == "nsources" or last_dim_length == "ncenters": - knl = lp.make_kernel( - "{[i,j,k]: 0<=i= h / 4, \ - (dist, h, centers_panel.element_nr, sources_panel.element_nr) + assert dist >= dz_radius, \ + (dist, dz_radius, centers_panel.element_nr, sources_panel.element_nr) - def check_panel_size_to_helmholtz_k_ratio(panel): + def check_quad_res_to_helmholtz_k_ratio(panel): # Check wavenumber to panel size ratio. - assert panel_sizes[panel.element_nr] * helmholtz_k <= 5 + assert quad_res[panel.element_nr] * helmholtz_k <= 5 for i, panel_1 in enumerate(iter_elements(lpot_source.density_discr)): for panel_2 in iter_elements(lpot_source.density_discr): @@ -187,7 +188,7 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): for panel_2 in iter_elements(lpot_source.quad_stage2_density_discr): check_sufficient_quadrature_resolution(panel_1, panel_2) if helmholtz_k is not None: - check_panel_size_to_helmholtz_k_ratio(panel_1) + check_quad_res_to_helmholtz_k_ratio(panel_1) # }}} -- GitLab From d50d0e5a244d27a33ea7febee848f1c9f8765a61 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 15:21:04 -0500 Subject: [PATCH 062/268] Decrease logging chattiness --- pytential/qbx/geometry.py | 35 ++++++++++++++++++++++++++------ pytential/qbx/target_assoc.py | 38 ++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index f9cc10e0..8e219d72 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -32,6 +32,7 @@ from boxtree.tools import DeviceDataRecord import loopy as lp from loopy.version import MOST_RECENT_LANGUAGE_VERSION from cgen import Enum +from time import time from pytential.qbx.utils import TreeCodeContainerMixin @@ -677,7 +678,8 @@ class QBXFMMGeometryData(object): user_target_to_center = self.user_target_to_center() with cl.CommandQueue(self.cl_context) as queue: - logger.info("find global qbx centers: start") + logger.debug("find global qbx centers: start") + center_find_start_time = time() tgt_assoc_result = ( user_target_to_center.with_queue(queue)[self.ncenters:]) @@ -703,7 +705,14 @@ class QBXFMMGeometryData(object): ], queue=queue) - logger.info("find global qbx centers: done") + center_find_elapsed = time() - center_find_start_time + if center_find_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + + done_logger("find global qbx centers: done after %g seconds", + center_find_elapsed) if self.debug: logger.debug( @@ -764,7 +773,8 @@ class QBXFMMGeometryData(object): user_ttc = self.user_target_to_center() with cl.CommandQueue(self.cl_context) as queue: - logger.info("build center -> targets lookup table: start") + logger.debug("build center -> targets lookup table: start") + c2t_start_time = time() tree_ttc = cl.array.empty_like(user_ttc).with_queue(queue) tree_ttc[self.tree().sorted_target_ids] = user_ttc @@ -788,7 +798,13 @@ class QBXFMMGeometryData(object): filtered_tree_ttc, filtered_target_ids, self.ncenters, tree_ttc.dtype) - logger.info("build center -> targets lookup table: done") + c2t_elapsed = time() - c2t_start_time + if c2t_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + done_logger("build center -> targets lookup table: " + "done after %g seconds", c2t_elapsed) result = CenterToTargetList( starts=center_target_starts, @@ -807,7 +823,8 @@ class QBXFMMGeometryData(object): """ with cl.CommandQueue(self.cl_context) as queue: - logger.info("find non-qbx box target lists: start") + logger.debug("find non-qbx box target lists: start") + nonqbx_start_time = time() flags = (self.user_target_to_center().with_queue(queue) == target_state.NO_QBX_NEEDED) @@ -824,7 +841,13 @@ class QBXFMMGeometryData(object): plfilt = self.code_getter.particle_list_filter() result = plfilt.filter_target_lists_in_tree_order(queue, tree, flags) - logger.info("find non-qbx box target lists: done") + nonqbx_elapsed = time() - nonqbx_start_time + if nonqbx_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + done_logger("find non-qbx box target lists: done after %g seconds", + nonqbx_elapsed) return result.with_queue(None) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 7b9736ce..8d85a5dc 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -39,6 +39,7 @@ from cgen import Enum from pytential.qbx.utils import ( QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS, TreeWranglerBase, TreeCodeContainerMixin) +from time import time unwrap_args = AreaQueryElementwiseTemplate.unwrap_args @@ -497,7 +498,8 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for=wait_for) wait_for = [evt] - logger.info("target association: marking targets close to panels") + logger.debug("target association: marking targets close to panels") + mark_start_time = time() tunnel_radius_by_source = lpot_source._close_target_tunnel_radius("nsources") @@ -527,7 +529,13 @@ class TargetAssociationWrangler(TreeWranglerBase): cl.wait_for_events([evt]) - logger.info("target association: done marking targets close to panels") + mark_elapsed = time() - mark_start_time + if mark_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + done_logger("target association: done marking targets close to panels " + "after %g seconds", mark_elapsed) return (found_target_close_to_panel == 1).all().get() @@ -582,7 +590,8 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for.extend(min_dist_to_center.events) - logger.info("target association: finding centers for targets") + logger.debug("target association: finding centers for targets") + center_find_start_time = time() evt = knl( *unwrap_args( @@ -613,8 +622,15 @@ class TargetAssociationWrangler(TreeWranglerBase): .format(ntargets_associated)) cl.wait_for_events([evt]) - logger.info("target association: done finding centers for targets") - return + + center_find_elapsed = time() - center_find_start_time + if center_find_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + + done_logger("target association: done finding centers for targets " + "after %g seconds", center_find_elapsed) def mark_panels_for_refinement(self, tree, peer_lists, lpot_source, target_status, refine_flags, debug, @@ -653,7 +669,8 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for=wait_for) wait_for = [evt] - logger.info("target association: marking panels for refinement") + logger.debug("target association: marking panels for refinement") + mark_start_time = time() evt = knl( *unwrap_args( @@ -684,7 +701,14 @@ class TargetAssociationWrangler(TreeWranglerBase): cl.wait_for_events([evt]) - logger.info("target association: done marking panels for refinement") + mark_elapsed = time() - mark_start_time + if mark_elapsed > 0.1: + done_logger = logger.info + else: + done_logger = logger.debug + + done_logger("target association: done marking panels for refinement " + "after %g seconds", mark_elapsed) return (found_panel_to_refine == 1).all().get() -- GitLab From c0b6a434c19543d6d7cd81896d15f1ec46b67288 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 15:39:44 -0500 Subject: [PATCH 063/268] Bump oversampling for inteq test --- test/test_scalar_int_eq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index f10f471e..b15905d3 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -303,7 +303,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - source_order = 4*case.target_order + source_order = 5*case.target_order refiner_extra_kwargs = {} -- GitLab From a7efc51dc1d37ee4601d80aab1316ebc13b3afc3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 15:41:05 -0500 Subject: [PATCH 064/268] Don't use unary + on pymbolic expressions --- pytential/symbolic/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 23b3e85d..d949adfa 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -531,7 +531,7 @@ def _small_mat_eigenvalues(mat): (a, b), (c, d) = mat return make_obj_array([ -(sqrt(d**2-2*a*d+4*b*c+a**2)-d-a)/2, - +(sqrt(d**2-2*a*d+4*b*c+a**2)+d+a)/2 + (sqrt(d**2-2*a*d+4*b*c+a**2)+d+a)/2 ]) else: raise NotImplementedError( -- GitLab From 905a1282f120b9da198c98305ae1229afc5762b5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:05:05 -0500 Subject: [PATCH 065/268] Adequately exec-map pymbolic's Min/Max nodes --- pytential/symbolic/execution.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 88d85c4a..f07b1036 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -53,6 +53,26 @@ class EvaluationMapper(EvaluationMapperBase): # {{{ map_XXX + def _map_minmax(self, func, inherited, expr): + ev_children = [self.rec(ch) for ch in expr.children] + from functools import reduce, partial + if any(isinstance(ch, cl.array.Array) for ch in ev_children): + return reduce(partial(func, queue=self.queue), ev_children) + else: + return inherited(expr) + + def map_max(self, expr): + return self._map_minmax( + cl.array.maximum, + super(EvaluationMapper, self).map_max, + expr) + + def map_min(self, expr): + return self._map_minmax( + cl.array.minimum, + super(EvaluationMapper, self).map_min, + expr) + def map_node_sum(self, expr): return cl.array.sum(self.rec(expr.operand)).get()[()] -- GitLab From f4c0ce1749c30a566ad541aed43b1c7d75f0b1ca Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:05:40 -0500 Subject: [PATCH 066/268] Elwise reductions: check input size --- pytential/symbolic/execution.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index f07b1036..110a89b5 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -95,6 +95,8 @@ class EvaluationMapper(EvaluationMapperBase): operand = self.rec(expr.operand) + assert operand.shape == (discr.nnodes,) + result = cl.array.empty(self.queue, discr.nnodes, operand.dtype) for group in discr.groups: knl()(self.queue, -- GitLab From 58c6983a652d7c9f97fc4099e57886ae66ef39d9 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:06:16 -0500 Subject: [PATCH 067/268] BoundExpression.get_discretization: better naming in stage 2 getting --- pytential/symbolic/execution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 110a89b5..7f4194cd 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -314,8 +314,8 @@ class BoundExpression: def get_discretization(self, where): from pytential.symbolic.primitives import _QBXSourceStage2 if isinstance(where, _QBXSourceStage2): - discr = self.places[where.where] - return discr.stage2_density_discr + lpot_source = self.places[where.where] + return lpot_source.stage2_density_discr discr = self.places[where] -- GitLab From 1efabd643978b7477ee95e1993ce16b7af89fdb8 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:06:54 -0500 Subject: [PATCH 068/268] Implement stringification of stage-2 where and elwise reductions --- pytential/symbolic/mappers.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 6510f957..680c8c97 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -453,6 +453,9 @@ class QBXPreprocessor(IdentityMapper): # {{{ stringifier def stringify_where(where): + if isinstance(where, prim._QBXSourceStage2): + return "stage2(%s)" % stringify_where(where.where) + if where is None: return "?" elif where is prim.DEFAULT_SOURCE: @@ -482,8 +485,20 @@ class StringifyMapper(BaseStringifyMapper): for name_expr in six.itervalues(expr.extra_vars)), set()) - def map_node_sum(self, expr, enclosing_prec): - return "NodeSum(%s)" % self.rec(expr.operand, PREC_NONE) + def map_elementwise_sum(self, expr, enclosing_prec): + return "ElwiseSum.%s(%s)" % ( + stringify_where(expr.where), + self.rec(expr.operand, PREC_NONE)) + + def map_elementwise_min(self, expr, enclosing_prec): + return "ElwiseMin.%s(%s)" % ( + stringify_where(expr.where), + self.rec(expr.operand, PREC_NONE)) + + def map_elementwise_max(self, expr, enclosing_prec): + return "ElwiseMax.%s(%s)" % ( + stringify_where(expr.where), + self.rec(expr.operand, PREC_NONE)) def map_node_max(self, expr, enclosing_prec): return "NodeMax(%s)" % self.rec(expr.operand, PREC_NONE) -- GitLab From 2e21d1851019006caee0543077e87f2dea76d0b5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:07:31 -0500 Subject: [PATCH 069/268] parametrization_derivative_matrix: pass where on to reference_jacobian --- pytential/symbolic/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index d949adfa..54d66003 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -420,7 +420,7 @@ def parametrization_derivative_matrix(ambient_dim, dim, where=None): return cse( reference_jacobian( [NodeCoordinateComponent(i, where) for i in range(ambient_dim)], - ambient_dim, dim), + ambient_dim, dim, where=where), "pd_matrix", cse_scope.DISCRETIZATION) -- GitLab From b6f04da407b0d6437d0bda536ad1f6d15e3bbe5c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:08:00 -0500 Subject: [PATCH 070/268] mapping_max_stretch_factor: pass where on to ElementwiseMax --- pytential/symbolic/primitives.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 54d66003..1c51d270 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -558,7 +558,10 @@ def mapping_max_stretch_factor(ambient_dim, dim=None, where=None): 2 * _parametrization_jtj(ambient_dim, dim, where)))] from pymbolic.primitives import Max - return cse(ElementwiseMax(Max(tuple(stretch_factors))), + return cse( + ElementwiseMax( + Max(tuple(stretch_factors)), + where=where), "mapping_max_stretch", cse_scope.DISCRETIZATION) # }}} -- GitLab From b94a041f04688a4914197e4091a555df8afb5bed Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:21:35 -0500 Subject: [PATCH 071/268] mapping_max_stretch_factor: Add explanatory comment --- pytential/symbolic/primitives.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 1c51d270..0a82acf9 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -549,6 +549,15 @@ def mapping_max_stretch_factor(ambient_dim, dim=None, where=None): if dim is None: dim = ambient_dim - 1 + # The 'technique' here is ad-hoc, but I'm fairly confident it's better than + # what we had. The idea is that singular values of the mapping Jacobian + # yield "stretch factors" of the mapping Why? because it maps a right + # singular vector $`v_1`$ (of unit length) to $`\sigma_1 u_1`$, where + # $`u_1`$ is the corresponding left singular vector (also of unit length). + # And so the biggest one tells us about the direction with the 'biggest' + # stretching, where 'stretching' (*2 to remove bi-unit reference element) + # reflects available quadrature resolution in that direction. + stretch_factors = [ cse(sqrt(s), "mapping_singval_%d" % i, cse_scope.DISCRETIZATION) for i, s in enumerate( -- GitLab From 681ff389b4cc549d34cfe24269044ba59668166a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 26 Mar 2018 23:52:45 -0500 Subject: [PATCH 072/268] Typo fix --- pytential/symbolic/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 0a82acf9..94f4228e 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -209,7 +209,7 @@ class _QBXSourceStage2(object): .. note:: - This is not documented functioanlity and only intended for + This is not documented functionality and only intended for internal use. """ -- GitLab From 332fa7fa95eaf010269edbf956e89398e44de620 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 28 Mar 2018 02:13:21 -0500 Subject: [PATCH 073/268] Bump FMM order in eigenvalues test --- test/test_layer_pot_eigenvalues.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index e60b72bf..b8c9c9fd 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -100,7 +100,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, np.linspace(0, 1, nelements+1), target_order) - fmm_order = 10 + fmm_order = 12 if force_direct: fmm_order = False -- GitLab From 8664e79787cc9b60541a5180bea0dde8414977c2 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 28 Mar 2018 02:13:38 -0500 Subject: [PATCH 074/268] Revert oversampling bump in inteq test --- test/test_scalar_int_eq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index b15905d3..f10f471e 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -303,7 +303,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) - source_order = 5*case.target_order + source_order = 4*case.target_order refiner_extra_kwargs = {} -- GitLab From 46eb917584710d8c23ba43725fd0e8e16dc49a21 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 28 Mar 2018 02:14:00 -0500 Subject: [PATCH 075/268] Fix typo in run_source_refinement_test --- test/test_global_qbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index c20e3c87..f3a21663 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -124,7 +124,7 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): expansion_radii = lpot_source._expansion_radii("nsources").get(queue) quad_res = lpot_source._coarsest_quad_resolution("npanels").get(queue) source_danger_zone_radii = \ - lpot_source._source_danger_zone_radii("npanels").get("queue") + lpot_source._source_danger_zone_radii("npanels").get(queue) # {{{ check if satisfying criteria -- GitLab From e9a2339a5c15fe61e1817fe18b0ad32a915e7cf1 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 28 Mar 2018 02:17:23 -0500 Subject: [PATCH 076/268] Apply fudge factor to stretch-based quad resolution measure --- pytential/qbx/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 848eeed1..a0ca0469 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -449,8 +449,19 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): import pytential.qbx.utils as utils from pytential import sym, bind with cl.CommandQueue(self.cl_context) as queue: + # These fudge factors bring this measure to the same rough magnitude + # as the prior (el area)**(1/dim) measure. + if self.density_discr.dim == 1: + fudge_factor = 1.3 + elif self.density_discr.dim == 2: + fudge_factor = 0.7 + else: + fudge_factor = 1 # ?? + maxstretch = bind( - self, sym.mapping_max_stretch_factor(self.ambient_dim))(queue) + self, + fudge_factor*sym.mapping_max_stretch_factor(self.ambient_dim) + )(queue) maxstretch = utils.to_last_dim_length( self.density_discr, maxstretch, last_dim_length) -- GitLab From e800329c5ef991b62f88cac4b8f889e7b5760bba Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 00:00:19 -0500 Subject: [PATCH 077/268] Fix 3D quadrature resolution computation to use equilateral reference triangles --- pytential/qbx/__init__.py | 17 +++++------ pytential/symbolic/primitives.py | 52 ++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index a0ca0469..fca64009 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -449,18 +449,15 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): import pytential.qbx.utils as utils from pytential import sym, bind with cl.CommandQueue(self.cl_context) as queue: - # These fudge factors bring this measure to the same rough magnitude - # as the prior (el area)**(1/dim) measure. - if self.density_discr.dim == 1: - fudge_factor = 1.3 - elif self.density_discr.dim == 2: - fudge_factor = 0.7 - else: - fudge_factor = 1 # ?? + # Potential FIXME: A triangle has half the area of a square, + # so the prior (area)**(1/dim) quadrature resolution measure + # may be viewed as having an extraneous factor of 1/sqrt(2) + # for triangles. maxstretch = bind( self, - fudge_factor*sym.mapping_max_stretch_factor(self.ambient_dim) + sym._simplex_mapping_max_stretch_factor( + self.ambient_dim) )(queue) maxstretch = utils.to_last_dim_length( @@ -487,7 +484,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): from pytential import sym, bind with cl.CommandQueue(self.cl_context) as queue: maxstretch = bind( - self, sym.mapping_max_stretch_factor( + self, sym._simplex_mapping_max_stretch_factor( self.ambient_dim, where=sym._QBXSourceStage2(sym.DEFAULT_SOURCE)) )(queue) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 94f4228e..f7a61133 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -508,18 +508,6 @@ def _panel_size(ambient_dim, dim=None, where=None): * QWeight())**(1/dim) -def _parametrization_jtj(ambient_dim, dim=None, where=None): - """For the mapping Jacobian :math:`J` as returned by - :func:`parametrization_derivative`, return :math:`J^TJ`. - """ - - pder_mat = parametrization_derivative_matrix(ambient_dim, dim, where) - - return cse( - np.dot(pder_mat.T, pder_mat), - "pd_mat_jtj", cse_scope.DISCRETIZATION) - - def _small_mat_eigenvalues(mat): m, n = mat.shape if m != n: @@ -538,7 +526,8 @@ def _small_mat_eigenvalues(mat): "eigenvalue formula for %dx%d matrices" % (m, n)) -def mapping_max_stretch_factor(ambient_dim, dim=None, where=None): +def _simplex_mapping_max_stretch_factor(ambient_dim, dim=None, where=None, + with_elementwise_max=True): """Return the largest factor by which the reference-to-global mapping stretches the bi-unit (i.e. :math:`[-1,1]`) reference element along any axis. @@ -558,20 +547,39 @@ def mapping_max_stretch_factor(ambient_dim, dim=None, where=None): # stretching, where 'stretching' (*2 to remove bi-unit reference element) # reflects available quadrature resolution in that direction. + pder_mat = parametrization_derivative_matrix(ambient_dim, dim, where) + + # The above procedure works well only when the 'reference' end of the + # mapping is in equilateral coordinates. + from modepy.tools import EQUILATERAL_TO_UNIT_MAP + equi_to_unit = EQUILATERAL_TO_UNIT_MAP[dim].a + + # This is the Jacobian of the (equilateral reference element) -> (global) map. + equi_pder_mat = cse( + np.dot(pder_mat, equi_to_unit), + "equilateral_pder_mat") + + # Compute eigenvalues of J^T to compute SVD. + equi_pder_mat_jtj = cse( + np.dot(equi_pder_mat.T, equi_pder_mat), + "pd_mat_jtj") + stretch_factors = [ - cse(sqrt(s), "mapping_singval_%d" % i, cse_scope.DISCRETIZATION) + cse(sqrt(s), "mapping_singval_%d" % i) for i, s in enumerate( _small_mat_eigenvalues( - # Multiply by 2 to compensate for bi-unit (i.e. [-1,1]) - # reference elements. - 2 * _parametrization_jtj(ambient_dim, dim, where)))] + # Multiply by 4 to compensate for equilateral reference + # elements of side length 2. (J^T J contains two factors of + # two.) + 4 * equi_pder_mat_jtj))] from pymbolic.primitives import Max - return cse( - ElementwiseMax( - Max(tuple(stretch_factors)), - where=where), - "mapping_max_stretch", cse_scope.DISCRETIZATION) + result = Max(tuple(stretch_factors)) + + if with_elementwise_max: + result = ElementwiseMax(result, where=where) + + return cse(result, "mapping_max_stretch", cse_scope.DISCRETIZATION) # }}} -- GitLab From 84b36e871f1f1a65e88f8c6cf2e8171a29a36369 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 14:56:47 -0500 Subject: [PATCH 078/268] Add fudge factor to expansion radii in 3D to match prior scaling --- pytential/qbx/__init__.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index fca64009..1c449a65 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -421,10 +421,19 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): "Passing 'npanels' as last_dim_length to _expansion_radii is " "not allowed. Allowed values are 'nsources' and 'ncenters'.") + if self.density_discr.dim == 2: + # A triangle has half the area of a square, + # so the prior (area)**(1/dim) quadrature resolution measure + # may be viewed as having an extraneous factor of 1/sqrt(2) + # for triangles. + fudge_factor = 0.5 + else: + fudge_factor = 1 + with cl.CommandQueue(self.cl_context) as queue: return (self._coarsest_quad_resolution(last_dim_length) .with_queue(queue) - * 0.5).with_queue(None) + * 0.5 * fudge_factor).with_queue(None) # _expansion_radii should not be needed for the fine discretization @@ -449,11 +458,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): import pytential.qbx.utils as utils from pytential import sym, bind with cl.CommandQueue(self.cl_context) as queue: - # Potential FIXME: A triangle has half the area of a square, - # so the prior (area)**(1/dim) quadrature resolution measure - # may be viewed as having an extraneous factor of 1/sqrt(2) - # for triangles. - maxstretch = bind( self, sym._simplex_mapping_max_stretch_factor( -- GitLab From 40fcbef9a10ad9c306832c921ecc5fd67dbfe767 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 14:57:10 -0500 Subject: [PATCH 079/268] Tweak plotting of internal data structures (for debugging) --- pytential/qbx/__init__.py | 2 ++ pytential/qbx/geometry.py | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 1c449a65..77384bb1 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -649,6 +649,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): geo_data = self.qbx_fmm_geometry_data(target_discrs_and_qbx_sides) + # geo_data.plot() + # FIXME Exert more positive control over geo_data attribute lifetimes using # geo_data..clear_cache(geo_data). diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index f9cc10e0..4c946e80 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -843,12 +843,14 @@ class QBXFMMGeometryData(object): This only works for two-dimensional geometries. """ + import matplotlib.pyplot as pt + pt.clf() + dims = self.tree().targets.shape[0] if dims != 2: raise ValueError("only 2-dimensional geometry info can be plotted") with cl.CommandQueue(self.cl_context) as queue: - import matplotlib.pyplot as pt from meshmode.discretization.visualization import draw_curve draw_curve(self.lpot_source.quad_stage2_density_discr) @@ -933,8 +935,10 @@ class QBXFMMGeometryData(object): # }}} pt.gca().set_aspect("equal") - pt.legend() - pt.show() + #pt.legend() + pt.savefig( + "geodata-stage2-nelem%d.pdf" + % self.lpot_source.stage2_density_discr.mesh.nelements) # }}} -- GitLab From 3052ba06940705c77b6e80804225ed5e7df7224f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 14:58:15 -0500 Subject: [PATCH 080/268] Move two tests further down the asymptote to hit EOC targets --- test/test_layer_pot_identity.py | 2 +- test/test_scalar_int_eq.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index c019f930..942b6ed2 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -82,7 +82,7 @@ class StarfishGeometry(object): dim = 2 - resolutions = [30, 50, 70] + resolutions = [30, 50, 70, 90] def get_mesh(self, nelements, target_order): return make_curve_mesh( diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index f10f471e..0d9fca28 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -83,7 +83,7 @@ class IntEqTestCase: class CurveIntEqTestCase(IntEqTestCase): - resolutions = [30, 40, 50] + resolutions = [40, 50, 60] def get_mesh(self, resolution, target_order): return make_curve_mesh( -- GitLab From f7ce942d90680555fb0a6c9adb3e2e6209570b94 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 17:57:09 -0500 Subject: [PATCH 081/268] Back GQBX test off from 18-to-1 to 20-to-1 ellipse --- test/test_global_qbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index f3a21663..e82f9d9f 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -217,7 +217,7 @@ def test_source_refinement_3d(ctx_getter, surface_name, surface_f, order): @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ - ("20-to-1 ellipse", partial(ellipse, 20), 100), + ("18-to-1 ellipse", partial(ellipse, 18), 100), ("horseshoe", horseshoe, 64), ]) def test_target_association(ctx_getter, curve_name, curve_f, nelements): -- GitLab From f2f1adb3fa8094ef008d2351f0d3a6eca34ede74 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 18:44:51 -0500 Subject: [PATCH 082/268] Add 2D visualization to test_target_association --- test/test_global_qbx.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index e82f9d9f..02fc4717 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -220,7 +220,8 @@ def test_source_refinement_3d(ctx_getter, surface_name, surface_f, order): ("18-to-1 ellipse", partial(ellipse, 18), 100), ("horseshoe", horseshoe, 64), ]) -def test_target_association(ctx_getter, curve_name, curve_f, nelements): +def test_target_association(ctx_getter, curve_name, curve_f, nelements, + visualize=False): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) @@ -316,6 +317,35 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): int_targets = np.array([axis.get(queue) for axis in int_targets.nodes()]) ext_targets = np.array([axis.get(queue) for axis in ext_targets.nodes()]) + def visualize(): + import matplotlib.pyplot as plt + from meshmode.mesh.visualization import draw_curve + + draw_curve(lpot_source.density_discr.mesh) + + targets = int_targets + tgt_slice = surf_int_slice + + plt.plot(centers[0], centers[1], "+", color="orange") + ax = plt.gca() + + for tx, ty, tcenter in zip( + targets[0, tgt_slice], + targets[1, tgt_slice], + target_assoc.target_to_center[tgt_slice]): + if tcenter >= 0: + ax.add_artist( + plt.Line2D( + (tx, centers[0, tcenter]), + (ty, centers[1, tcenter]), + )) + + ax.set_aspect("equal") + plt.show() + + if visualize: + visualize() + # Checks that the sources match with their own centers. def check_on_surface_targets(nsources, true_side, target_to_center, target_to_side_result): -- GitLab From 1d3a5f6971f2f2fa6a701483141388f205f96768 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Apr 2018 19:15:05 -0500 Subject: [PATCH 083/268] Fix identifier clash in test_global_qbx.py --- test/test_global_qbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 02fc4717..28f525a6 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -317,7 +317,7 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, int_targets = np.array([axis.get(queue) for axis in int_targets.nodes()]) ext_targets = np.array([axis.get(queue) for axis in ext_targets.nodes()]) - def visualize(): + def visualize_curve_and_assoc(): import matplotlib.pyplot as plt from meshmode.mesh.visualization import draw_curve @@ -344,7 +344,7 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, plt.show() if visualize: - visualize() + visualize_curve_and_assoc() # Checks that the sources match with their own centers. def check_on_surface_targets(nsources, true_side, target_to_center, -- GitLab From 68ad202709491e8999b4032c2020ec4842fb43f8 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 3 Apr 2018 12:04:30 -0500 Subject: [PATCH 084/268] Allow passing max_expansion_radius to with_refinement --- pytential/qbx/__init__.py | 11 ++-- pytential/qbx/refinement.py | 102 +++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 77384bb1..dd168df7 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -369,7 +369,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def with_refinement(self, target_order=None, kernel_length_scale=None, - maxiter=None, visualize=False, _expansion_disturbance_tolerance=None): + maxiter=None, visualize=False, _expansion_disturbance_tolerance=None, + _max_expansion_radius=None): """ :returns: a tuple ``(lpot_src, cnx)``, where ``lpot_src`` is a :class:`QBXLayerPotentialSource` and ``cnx`` is a @@ -391,7 +392,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): InterpolatoryQuadratureSimplexGroupFactory(target_order), kernel_length_scale=kernel_length_scale, maxiter=maxiter, visualize=visualize, - expansion_disturbance_tolerance=_expansion_disturbance_tolerance) + expansion_disturbance_tolerance=_expansion_disturbance_tolerance, + max_expansion_radius=_max_expansion_radius) return lpot, connection @@ -416,11 +418,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def _expansion_radii(self, last_dim_length): - if last_dim_length == "npanels": - raise ValueError( - "Passing 'npanels' as last_dim_length to _expansion_radii is " - "not allowed. Allowed values are 'nsources' and 'ncenters'.") - if self.density_discr.dim == 2: # A triangle has half the area of a square, # so the prior (area)**(1/dim) quadrature resolution measure diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 2a1d8b7f..3985d952 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -242,23 +242,24 @@ class RefinerCodeContainer(TreeCodeContainerMixin): extra_type_aliases=(("particle_id_t", particle_id_dtype),)) @memoize_method - def kernel_length_scale_to_panel_size_ratio_checker(self): + def element_prop_threshold_checker(self): knl = lp.make_kernel( - "{[panel]: 0<=panel oversize = panel_sizes[panel] > kernel_length_scale - if oversize - refine_flags[panel] = 1 + for ielement + <> over_threshold = element_property[ielement] > threshold + if over_threshold + refine_flags[ielement] = 1 refine_flags_updated = 1 {id=write_refine_flags_updated} end end """, options="return_dict", silenced_warnings="write_race(write_refine_flags_updated)", - name="refine_kernel_length_scale_to_panel_size_ratio", + name="refine_kernel_length_scale_to_quad_resolution_ratio", lang_version=MOST_RECENT_LANGUAGE_VERSION) - knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") + + knl = lp.split_iname(knl, "ielement", 128, inner_tag="l.0", outer_tag="g.0") return knl def get_wrangler(self, queue): @@ -399,9 +400,9 @@ class RefinerWrangler(TreeWranglerBase): return found_panel_to_refine.get()[0] == 1 - def check_kernel_length_scale_to_panel_size_ratio(self, lpot_source, - kernel_length_scale, refine_flags, debug, wait_for=None): - knl = self.code_container.kernel_length_scale_to_panel_size_ratio_checker() + def check_element_prop_threshold(self, element_property, threshold, refine_flags, + debug, wait_for=None): + knl = self.code_container.element_prop_threshold_checker() logger.info("refiner: checking kernel length scale to panel size ratio") @@ -409,10 +410,11 @@ class RefinerWrangler(TreeWranglerBase): npanels_to_refine_prev = cl.array.sum(refine_flags).get() evt, out = knl(self.queue, - panel_sizes=lpot_source._coarsest_quad_resolution("npanels"), + element_property=element_property, + # lpot_source._coarsest_quad_resolution("npanels")), refine_flags=refine_flags, refine_flags_updated=np.array(0), - kernel_length_scale=np.array(kernel_length_scale), + threshold=np.array(threshold), wait_for=wait_for) cl.wait_for_events([evt]) @@ -475,7 +477,8 @@ def make_empty_refine_flags(queue, lpot_source, use_stage2_discr=False): def refine_for_global_qbx(lpot_source, wrangler, group_factory, kernel_length_scale=None, - refine_flags=None, debug=None, maxiter=None, + max_expansion_radius=None, + debug=None, maxiter=None, visualize=None, expansion_disturbance_tolerance=None): """ Entry point for calling the refiner. @@ -491,11 +494,6 @@ def refine_for_global_qbx(lpot_source, wrangler, :arg kernel_length_scale: The kernel length scale, or *None* if not applicable. All panels are refined to below this size. - :arg refine_flags: A :class:`pyopencl.array.Array` indicating which - panels should get refined initially, or `None` if no initial - refinement should be done. Should have size equal to the number of - panels. See also :func:`make_empty_refine_flags()`. - :arg maxiter: The maximum number of refiner iterations. :returns: A tuple ``(lpot_source, *conn*)`` where ``lpot_source`` is the @@ -524,14 +522,6 @@ def refine_for_global_qbx(lpot_source, wrangler, refiner = Refiner(lpot_source.density_discr.mesh) connections = [] - # Do initial refinement. - if refine_flags is not None: - conn = wrangler.refine( - lpot_source.density_discr, refiner, refine_flags, group_factory, - debug) - connections.append(conn) - lpot_source = lpot_source.copy(density_discr=conn.to_discr) - # {{{ first stage refinement def visualize_refinement(niter, stage, flags): @@ -597,33 +587,53 @@ def refine_for_global_qbx(lpot_source, wrangler, warn_max_iterations() break - # Build tree and auxiliary data. - # FIXME: The tree should not have to be rebuilt at each iteration. - tree = wrangler.build_tree(lpot_source) - peer_lists = wrangler.find_peer_lists(tree) refine_flags = make_empty_refine_flags(wrangler.queue, lpot_source) - # Check condition 1. - has_disturbed_expansions = \ - wrangler.check_expansion_disks_undisturbed_by_sources( - lpot_source, tree, peer_lists, - expansion_disturbance_tolerance, - refine_flags, debug) - if has_disturbed_expansions: - iter_violated_criteria.append("disturbed expansions") - visualize_refinement(niter, "disturbed-expansions", refine_flags) - - # Check condition 3. if kernel_length_scale is not None: - violates_kernel_length_scale = \ - wrangler.check_kernel_length_scale_to_panel_size_ratio( - lpot_source, kernel_length_scale, refine_flags, debug) + wrangler.check_element_prop_threshold( + element_property=lpot_source._coarsest_quad_resolution( + "npanels"), + threshold=kernel_length_scale, + refine_flags=refine_flags, debug=debug) if violates_kernel_length_scale: iter_violated_criteria.append("kernel length scale") visualize_refinement(niter, "kernel-length-scale", refine_flags) + if max_expansion_radius is not None: + violates_expansion_radii = \ + wrangler.check_element_prop_threshold( + element_property=lpot_source._expansion_radii( + "npanels"), + threshold=max_expansion_radius, + refine_flags=refine_flags, debug=debug) + + if violates_expansion_radii: + iter_violated_criteria.append("expansion radii") + visualize_refinement(niter, "expansion-radii", refine_flags) + + if not iter_violated_criteria: + # Only start building trees once the simple length-based criteria + # are happy. + + # Build tree and auxiliary data. + # FIXME: The tree should not have to be rebuilt at each iteration. + tree = wrangler.build_tree(lpot_source) + peer_lists = wrangler.find_peer_lists(tree) + + has_disturbed_expansions = \ + wrangler.check_expansion_disks_undisturbed_by_sources( + lpot_source, tree, peer_lists, + expansion_disturbance_tolerance, + refine_flags, debug) + if has_disturbed_expansions: + iter_violated_criteria.append("disturbed expansions") + visualize_refinement(niter, "disturbed-expansions", refine_flags) + + del tree + del peer_lists + if iter_violated_criteria: violated_criteria.append(" and ".join(iter_violated_criteria)) @@ -633,9 +643,7 @@ def refine_for_global_qbx(lpot_source, wrangler, connections.append(conn) lpot_source = lpot_source.copy(density_discr=conn.to_discr) - del tree del refine_flags - del peer_lists # }}} -- GitLab From 068a5a4e3c57798f8dfe89acb427cdc1ed40f7a4 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 5 Apr 2018 22:20:50 -0500 Subject: [PATCH 085/268] Allow specifying the tree kind in QBXLayerPotentialSource. --- pytential/qbx/__init__.py | 5 +++++ pytential/qbx/geometry.py | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index d5d52ca6..afb532af 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -82,6 +82,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _box_extent_norm=None, _from_sep_smaller_crit=None, _from_sep_smaller_min_nsources_cumul=None, + _tree_kind="adaptive", geometry_data_inspector=None, fmm_backend="sumpy", target_stick_out_factor=_not_provided): @@ -177,6 +178,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): self._from_sep_smaller_crit = _from_sep_smaller_crit self._from_sep_smaller_min_nsources_cumul = \ _from_sep_smaller_min_nsources_cumul + self._tree_kind = _tree_kind self.geometry_data_inspector = geometry_data_inspector # /!\ *All* parameters set here must also be set by copy() below, @@ -195,6 +197,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): target_association_tolerance=_not_provided, _expansions_in_tree_have_extent=_not_provided, _expansion_stick_out_factor=_not_provided, + _tree_kind=None, geometry_data_inspector=None, debug=_not_provided, @@ -273,6 +276,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _from_sep_smaller_crit=self._from_sep_smaller_crit, _from_sep_smaller_min_nsources_cumul=( self._from_sep_smaller_min_nsources_cumul), + _tree_kind=_tree_kind or self._tree_kind, geometry_data_inspector=( geometry_data_inspector or self.geometry_data_inspector), fmm_backend=self.fmm_backend, @@ -462,6 +466,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): return QBXFMMGeometryData(self.qbx_fmm_code_getter, self, target_discrs_and_qbx_sides, target_association_tolerance=self.target_association_tolerance, + tree_kind=self._tree_kind, debug=self.debug) # }}} diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 8e219d72..47c578b7 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -354,13 +354,16 @@ class QBXFMMGeometryData(object): def __init__(self, code_getter, lpot_source, target_discrs_and_qbx_sides, - target_association_tolerance, debug): + target_association_tolerance, + tree_kind, debug): """ .. rubric:: Constructor arguments See the attributes of the same name for the meaning of most of the constructor arguments. + :arg tree_kind: The tree kind to pass to the tree builder + :arg debug: a :class:`bool` flag for whether to enable potentially costly self-checks """ @@ -370,6 +373,7 @@ class QBXFMMGeometryData(object): self.target_discrs_and_qbx_sides = \ target_discrs_and_qbx_sides self.target_association_tolerance = target_association_tolerance + self.tree_kind = tree_kind self.debug = debug @property @@ -522,7 +526,7 @@ class QBXFMMGeometryData(object): debug=self.debug, stick_out_factor=lpot_src._expansion_stick_out_factor, extent_norm=lpot_src._box_extent_norm, - kind="adaptive") + kind=self.tree_kind) if self.debug: tgt_count_2 = cl.array.sum( -- GitLab From 0751ab23cde4825fa26723e661084b1172ac480d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 7 Apr 2018 20:32:14 -0500 Subject: [PATCH 086/268] QBXLPSource.with_refinement: Allow passing in existing refiner --- pytential/qbx/__init__.py | 13 ++++++++++--- pytential/qbx/refinement.py | 9 +++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index dd168df7..34caa9e8 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -369,9 +369,15 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def with_refinement(self, target_order=None, kernel_length_scale=None, - maxiter=None, visualize=False, _expansion_disturbance_tolerance=None, - _max_expansion_radius=None): + maxiter=None, visualize=False, refiner=None, + _expansion_disturbance_tolerance=None, _max_expansion_radius=None): """ + :arg refiner: If the mesh underlying :attr:`density_discr` + is itself the result of refinement, then its + :class:`meshmode.refinement.Refiner` instance may need to + be reused for continued refinement. This argument + provides the opportunity to pass in an existing refiner + that should be used for continued refinement. :returns: a tuple ``(lpot_src, cnx)``, where ``lpot_src`` is a :class:`QBXLayerPotentialSource` and ``cnx`` is a :class:`meshmode.discretization.connection.DiscretizationConnection` @@ -393,7 +399,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): kernel_length_scale=kernel_length_scale, maxiter=maxiter, visualize=visualize, expansion_disturbance_tolerance=_expansion_disturbance_tolerance, - max_expansion_radius=_max_expansion_radius) + max_expansion_radius=_max_expansion_radius, + refiner=refiner) return lpot, connection diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 3985d952..df2c9771 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -479,7 +479,8 @@ def refine_for_global_qbx(lpot_source, wrangler, group_factory, kernel_length_scale=None, max_expansion_radius=None, debug=None, maxiter=None, - visualize=None, expansion_disturbance_tolerance=None): + visualize=None, expansion_disturbance_tolerance=None, + refiner=None): """ Entry point for calling the refiner. @@ -519,7 +520,11 @@ def refine_for_global_qbx(lpot_source, wrangler, from meshmode.discretization.connection import ( ChainedDiscretizationConnection, make_same_mesh_connection) - refiner = Refiner(lpot_source.density_discr.mesh) + if refiner is None: + assert refiner.get_current_mesh() == lpot_source.density_discr.mesh + else: + refiner = Refiner(lpot_source.density_discr.mesh) + connections = [] # {{{ first stage refinement -- GitLab From b0c4172e79c8a202e0ec1ebaed59d674aaf947a6 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 7 Apr 2018 20:32:57 -0500 Subject: [PATCH 087/268] Use same dim-dependent fudge factor in source danger zone and expansion radius --- pytential/qbx/__init__.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 34caa9e8..fadcbc4e 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -423,21 +423,18 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): import pytential.qbx.utils as utils return utils.element_centers_of_mass(self.stage2_density_discr) - @memoize_method - def _expansion_radii(self, last_dim_length): + def _dim_fudge_factor(self): if self.density_discr.dim == 2: - # A triangle has half the area of a square, - # so the prior (area)**(1/dim) quadrature resolution measure - # may be viewed as having an extraneous factor of 1/sqrt(2) - # for triangles. - fudge_factor = 0.5 + return 0.75 else: - fudge_factor = 1 + return 1 + @memoize_method + def _expansion_radii(self, last_dim_length): with cl.CommandQueue(self.cl_context) as queue: return (self._coarsest_quad_resolution(last_dim_length) .with_queue(queue) - * 0.5 * fudge_factor).with_queue(None) + * 0.5 * self._dim_fudge_factor()).with_queue(None) # _expansion_radii should not be needed for the fine discretization @@ -505,10 +502,11 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def _source_danger_zone_radii(self, last_dim_length="npanels"): quad_res = self._stage2_coarsest_quad_resolution(last_dim_length) + with cl.CommandQueue(self.cl_context) as queue: return (quad_res .with_queue(queue) - * 0.25).with_queue(None) + * 0.25 * self._dim_fudge_factor()).with_queue(None) @memoize_method def qbx_fmm_geometry_data(self, target_discrs_and_qbx_sides): -- GitLab From cc3fdc0c2109b581619793963ea35e87693b4dff Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 10 Apr 2018 13:28:10 -0500 Subject: [PATCH 088/268] Tweak source danger zone radii for proper resolution control at a refinement boundary --- pytential/qbx/__init__.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index fadcbc4e..dd565278 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -438,6 +438,27 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): # _expansion_radii should not be needed for the fine discretization + @memoize_method + def _source_danger_zone_radii(self, last_dim_length="npanels"): + # This should be the expression of the expansion radii, but + # + # - in reference to the stage 2 discretization + # - mutliplied by 0.75 because + # + # - Setting this equal to the expansion radii ensures that *every* + # stage 2 element will be refined, which is wasteful. + # (so this needs to be smaller than that) + # + + # - Setting this equal to half the expansion radius will not provide + # a refinement 'buffer layer' at a 2x coarsening fringe. + + with cl.CommandQueue(self.cl_context) as queue: + return ( + (self._stage2_coarsest_quad_resolution(last_dim_length) + .with_queue(queue)) + * 0.5 * 0.75 * self._dim_fudge_factor()).with_queue(None) + @memoize_method def _close_target_tunnel_radius(self, last_dim_length): with cl.CommandQueue(self.cl_context) as queue: @@ -451,10 +472,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): """This measures the quadrature resolution across the mesh. In a 1D uniform mesh of uniform 'parametrization speed', it should be the same as the panel length. - - It is empirically about a factor of 1.2 larger than sym._panel_size for - an ellipse, presumably because it uses the largest 'parametrization - speed'/'stretch factor' across the whole element. """ import pytential.qbx.utils as utils from pytential import sym, bind @@ -476,10 +493,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): """This measures the quadrature resolution across the mesh. In a 1D uniform mesh of uniform 'parametrization speed', it should be the same as the panel length. - - It is empirically about a factor of 1.2 larger than sym._panel_size for - an ellipse, presumably because it uses the largest 'parametrization - speed'/'stretch factor' across the whole element. """ if last_dim_length != "npanels": # Not technically required below, but no need to loosen for now. @@ -499,15 +512,6 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): return maxstretch - @memoize_method - def _source_danger_zone_radii(self, last_dim_length="npanels"): - quad_res = self._stage2_coarsest_quad_resolution(last_dim_length) - - with cl.CommandQueue(self.cl_context) as queue: - return (quad_res - .with_queue(queue) - * 0.25 * self._dim_fudge_factor()).with_queue(None) - @memoize_method def qbx_fmm_geometry_data(self, target_discrs_and_qbx_sides): """ -- GitLab From 507db52c2d1e0b2f50576a6642992c83bd9b5ef4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 10 Apr 2018 16:42:27 -0500 Subject: [PATCH 089/268] Fix inverted logic in refinement-with-existing-refiner --- pytential/qbx/refinement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index df2c9771..31cf9dda 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -520,7 +520,7 @@ def refine_for_global_qbx(lpot_source, wrangler, from meshmode.discretization.connection import ( ChainedDiscretizationConnection, make_same_mesh_connection) - if refiner is None: + if refiner is not None: assert refiner.get_current_mesh() == lpot_source.density_discr.mesh else: refiner = Refiner(lpot_source.density_discr.mesh) -- GitLab From b5a71fecb4999ba2b4712f259679b337fe7b1469 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 11 Apr 2018 01:27:17 -0500 Subject: [PATCH 090/268] Revert 3D dimension fudge factor to 0.5, for increased accuracy --- pytential/qbx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index dd565278..0228b0db 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -425,7 +425,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): def _dim_fudge_factor(self): if self.density_discr.dim == 2: - return 0.75 + return 0.5 else: return 1 -- GitLab From 386b2593afca6e8f35af3e63bfc1ad0da0d2946d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 11 Apr 2018 10:58:02 -0500 Subject: [PATCH 091/268] Bump 3D jump rel tests further down the asymptote --- test/test_layer_pot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index b0766375..f81a5e55 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -404,7 +404,7 @@ def test_3d_jump_relations(ctx_factory, relation, visualize=False): from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() - for nel_factor in [6, 8, 12]: + for nel_factor in [6, 10, 14]: from meshmode.mesh.generation import generate_torus mesh = generate_torus( 5, 2, order=target_order, -- GitLab From 6fb8fbf6be632492f014804a1f365399a89ffb71 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 19 Apr 2018 16:56:35 -0500 Subject: [PATCH 092/268] Switch to RefinerWithoutAdjacency for QBX refinement --- pytential/qbx/refinement.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 31cf9dda..0d63c228 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -516,14 +516,16 @@ def refine_for_global_qbx(lpot_source, wrangler, # TODO: Stop doing redundant checks by avoiding panels which no longer need # refinement. - from meshmode.mesh.refinement import Refiner + from meshmode.mesh.refinement import RefinerWithoutAdjacency from meshmode.discretization.connection import ( ChainedDiscretizationConnection, make_same_mesh_connection) if refiner is not None: assert refiner.get_current_mesh() == lpot_source.density_discr.mesh else: - refiner = Refiner(lpot_source.density_discr.mesh) + # We may be handed a mesh that's already non-conforming, we don't rely + # on adjacency, and the no-adjacency refiner is faster. + refiner = RefinerWithoutAdjacency(lpot_source.density_discr.mesh) connections = [] -- GitLab From 0457546b86b70404b8bbcac24a2235c47135a4dc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 19 Apr 2018 16:57:21 -0500 Subject: [PATCH 093/268] Introduce force_stage2_uniform_refinement_rounds refinement parameter --- pytential/qbx/__init__.py | 6 +++++- pytential/qbx/refinement.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 0228b0db..b02fd1ca 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -370,7 +370,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): @memoize_method def with_refinement(self, target_order=None, kernel_length_scale=None, maxiter=None, visualize=False, refiner=None, - _expansion_disturbance_tolerance=None, _max_expansion_radius=None): + _expansion_disturbance_tolerance=None, + _max_expansion_radius=None, + _force_stage2_uniform_refinement_rounds=None): """ :arg refiner: If the mesh underlying :attr:`density_discr` is itself the result of refinement, then its @@ -400,6 +402,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): maxiter=maxiter, visualize=visualize, expansion_disturbance_tolerance=_expansion_disturbance_tolerance, max_expansion_radius=_max_expansion_radius, + force_stage2_uniform_refinement_rounds=( + _force_stage2_uniform_refinement_rounds), refiner=refiner) return lpot, connection diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 0d63c228..915056e6 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -478,6 +478,7 @@ def make_empty_refine_flags(queue, lpot_source, use_stage2_discr=False): def refine_for_global_qbx(lpot_source, wrangler, group_factory, kernel_length_scale=None, max_expansion_radius=None, + force_stage2_uniform_refinement_rounds=None, debug=None, maxiter=None, visualize=None, expansion_disturbance_tolerance=None, refiner=None): @@ -513,6 +514,9 @@ def refine_for_global_qbx(lpot_source, wrangler, if expansion_disturbance_tolerance is None: expansion_disturbance_tolerance = 0.025 + if force_stage2_uniform_refinement_rounds is None: + force_stage2_uniform_refinement_rounds = 0 + # TODO: Stop doing redundant checks by avoiding panels which no longer need # refinement. @@ -700,6 +704,18 @@ def refine_for_global_qbx(lpot_source, wrangler, del refine_flags del peer_lists + for round in range(force_stage2_uniform_refinement_rounds): + conn = wrangler.refine( + stage2_density_discr, + refiner, + np.ones(stage2_density_discr.mesh.nelements, dtype=np.bool), + group_factory, debug) + stage2_density_discr = conn.to_discr + fine_connections.append(conn) + lpot_source = lpot_source.copy( + to_refined_connection=ChainedDiscretizationConnection( + fine_connections)) + # }}} lpot_source = lpot_source.copy(debug=debug, _refined_for_global_qbx=True) -- GitLab From 7c8b888ed0e066705aa525ab1f2796e4e4ed5376 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 19 Apr 2018 16:57:55 -0500 Subject: [PATCH 094/268] QBX refinement vis: Don't go overboard on order --- pytential/qbx/refinement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 915056e6..82cb8eb8 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -541,7 +541,7 @@ def refine_for_global_qbx(lpot_source, wrangler, discr = lpot_source.density_discr from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(wrangler.queue, discr, 10) + vis = make_visualizer(wrangler.queue, discr, 3) flags = flags.get().astype(np.bool) nodes_flags = np.zeros(discr.nnodes) -- GitLab From 47cb31ee95996c0398f51e95a6215de393cc2954 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 19 Apr 2018 17:02:00 -0500 Subject: [PATCH 095/268] NumReferenceDerivative: Fix incorrect choice of data type for ref_axes parameter --- pytential/symbolic/execution.py | 4 ++- pytential/symbolic/mappers.py | 10 ++++++-- pytential/symbolic/primitives.py | 43 +++++++++++++++++++++++++------- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 7f4194cd..c0fe0153 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -132,9 +132,11 @@ class EvaluationMapper(EvaluationMapperBase): def map_num_reference_derivative(self, expr): discr = self.bound_expr.get_discretization(expr.where) + from pytools import flatten + ref_axes = flatten([axis] * mult for axis, mult in expr.ref_axes) return discr.num_reference_derivative( self.queue, - expr.ref_axes, self.rec(expr.operand)) \ + ref_axes, self.rec(expr.operand)) \ .with_queue(self.queue) def map_q_weight(self, expr): diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 680c8c97..0dfaa4f9 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -508,8 +508,14 @@ class StringifyMapper(BaseStringifyMapper): stringify_where(expr.where)) def map_num_reference_derivative(self, expr, enclosing_prec): - result = "d/dr%s.%s %s" % ( - ",".join(str(ax) for ax in expr.ref_axes), + diff_op = " ".join( + "d/dr%d" % axis + if mult == 1 else + "d/dr%d^%d" % (axis, mult) + for axis, mult in expr.ref_axes) + + result = "%s.%s %s" % ( + diff_op, stringify_where(expr.where), self.rec(expr.operand, PREC_PRODUCT), ) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index f7a61133..27a36268 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -374,18 +374,46 @@ class NumReferenceDerivative(DiscretizationProperty): reference coordinates. """ + def __new__(cls, ref_axes, operand, where=None): + # If the constructor is handed a multivector object, return an + # object array of the operator applied to each of the + # coefficients in the multivector. + + if isinstance(operand, (np.ndarray)): + def make_op(operand_i): + return cls(ref_axes, operand_i, where=where) + + return componentwise(make_op, operand) + else: + return DiscretizationProperty.__new__(cls) + def __init__(self, ref_axes, operand, where=None): """ - :arg ref_axes: a :class:`frozenset` of indices of - reference coordinates along which derivatives - will be taken. + :arg ref_axes: a :class:`tuple` of tuples indicating indices of + coordinate axes of the reference element to the number of derivatives + which will be taken. For example, the value ``((0, 2), (1, 1))`` + indicates that Each axis must occur at most once. The tuple must be + sorted by the axis index. + + May also be a singile integer *i*, which is viewed as equivalent + to ``((i, 1),)``. :arg where: |where-blurb| """ - if not isinstance(ref_axes, frozenset): - raise ValueError("ref_axes must be a frozenset") + if isinstance(ref_axes, int): + ref_axes = ((ref_axes, 1),) + + if not isinstance(ref_axes, tuple): + raise ValueError("ref_axes must be a tuple") + + if tuple(sorted(ref_axes)) != ref_axes: + raise ValueError("ref_axes must be sorted") + + if len(dict(ref_axes)) != len(ref_axes): + raise ValueError("ref_axes must not contain an axis more than once") self.ref_axes = ref_axes + self.operand = operand DiscretizationProperty.__init__(self, where) @@ -404,10 +432,7 @@ def reference_jacobian(func, output_dim, dim, where=None): for i in range(output_dim): func_component = func[i] for j in range(dim): - jac[i, j] = NumReferenceDerivative( - frozenset([j]), - func_component, - where) + jac[i, j] = NumReferenceDerivative(j, func_component, where) return jac -- GitLab From b6afe5a1cbfa4f7ef43d0eaaf26acc719cd26b55 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 10:35:05 -0500 Subject: [PATCH 096/268] Implement computation of first/second fundamental form and shape operator --- pytential/symbolic/primitives.py | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 27a36268..68848ff8 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -121,6 +121,10 @@ Discretization properties .. autofunction:: area_element .. autofunction:: sqrt_jac_q_weight .. autofunction:: normal +.. autofunction:: mean_curvature +.. autofunction:: first_fundamental_form +.. autofunction:: second_fundamental_form +.. autofunction:: shape_operator Elementary numerics ^^^^^^^^^^^^^^^^^^^ @@ -522,6 +526,81 @@ def mean_curvature(ambient_dim, dim=None, where=None): return (xp[0]*ypp[0] - yp[0]*xpp[0]) / (xp[0]**2 + yp[0]**2)**(3/2) +def first_fundamental_form(ambient_dim, dim=None, where=None): + if dim is None: + dim = ambient_dim - 1 + + if ambient_dim != 3 and dim != 2: + raise NotImplementedError("only available for surfaces in 3D") + + pd_mat = parametrization_derivative_matrix(ambient_dim, dim, where) + + return cse( + np.dot(pd_mat.T, pd_mat), + "fundform1") + + +def second_fundamental_form(ambient_dim, dim=None, where=None): + """Compute the second fundamental form of a surface. This is in reference + to the reference-to-global mapping in use for each element. + + .. note:: + + Some references assume that the second fundamental form is computed + with respect to an orthonormal basis, which this is not. + """ + if dim is None: + dim = ambient_dim - 1 + + if ambient_dim != 3 and dim != 2: + raise NotImplementedError("only available for surfaces in 3D") + + r = nodes(ambient_dim, where=where).as_vector() + + # https://en.wikipedia.org/w/index.php?title=Second_fundamental_form&oldid=821047433#Classical_notation + + from functools import partial + d = partial(NumReferenceDerivative, where=where) + ruu = d(((0, 2),), r) + ruv = d(((0, 1), (1, 1)), r) + rvv = d(((1, 2),), r) + + nrml = normal(ambient_dim, dim, where).as_vector() + + ff2_l = cse(np.dot(ruu, nrml), "fundform2_L") + ff2_m = cse(np.dot(ruv, nrml), "fundform2_M") + ff2_n = cse(np.dot(rvv, nrml), "fundform2_N") + + result = np.zeros((2, 2), dtype=object) + result[0, 0] = ff2_l + result[0, 1] = result[1, 0] = ff2_m + result[1, 1] = ff2_n + + return result + + +def shape_operator(ambient_dim, dim=None, where=None): + if dim is None: + dim = ambient_dim - 1 + + if ambient_dim != 3 and dim != 2: + raise NotImplementedError("only available for surfaces in 3D") + + # https://en.wikipedia.org/w/index.php?title=Differential_geometry_of_surfaces&oldid=833587563 + (E, F), (F, G) = first_fundamental_form(ambient_dim, dim, where) + (e, f), (f, g) = second_fundamental_form(ambient_dim, dim, where) + + result = np.zeros((2, 2), dtype=object) + result[0, 0] = e*G-f*F + result[0, 1] = f*G-g*F + result[1, 0] = f*E-e*F + result[1, 1] = g*E-f*F + + return cse( + 1/(E*G-F*F)*result, + "shape_operator") + + def _panel_size(ambient_dim, dim=None, where=None): # A broken quasi-1D approximation of 1D element size. Do not use. -- GitLab From 4497770dc7f8abfb32e77fd90987f59459960358 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 10:35:48 -0500 Subject: [PATCH 097/268] Add a docstring for tangential_onb --- pytential/symbolic/primitives.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 68848ff8..23dba044 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1278,6 +1278,10 @@ def Dp(kernel, *args, **kwargs): # noqa # {{{ conventional vector calculus def tangential_onb(ambient_dim, dim=None, where=None): + """Return a matrix of shape ``(ambient_dim, dim)`` with orthogonal columns + spanning the tangential space of the surface of *where*. + """ + if dim is None: dim = ambient_dim - 1 -- GitLab From 0fde3b783a5fb281c75cd51999914c4aa8b7b4b4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 10:36:24 -0500 Subject: [PATCH 098/268] Add _small_mat_inverse to primitives --- pytential/symbolic/primitives.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 23dba044..21b31e54 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -612,6 +612,24 @@ def _panel_size(ambient_dim, dim=None, where=None): * QWeight())**(1/dim) +def _small_mat_inverse(mat): + m, n = mat.shape + if m != n: + raise ValueError("inverses only make sense for square matrices") + + if m == 1: + return make_obj_array([1/mat[0, 0]]) + elif m == 2: + (a, b), (c, d) = mat + return 1/(a*d-b*c) * make_obj_array([ + [d, -b], + [-c, a], + ]) + else: + raise NotImplementedError( + "inverse formula for %dx%d matrices" % (m, n)) + + def _small_mat_eigenvalues(mat): m, n = mat.shape if m != n: -- GitLab From 362e5f130137feb81ce9d97fa033bb5d6bc8d9a3 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 10:52:57 -0500 Subject: [PATCH 099/268] Primitives: Add curvature-per-resolution measure --- pytential/symbolic/primitives.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 21b31e54..ecbdc899 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -703,6 +703,38 @@ def _simplex_mapping_max_stretch_factor(ambient_dim, dim=None, where=None, return cse(result, "mapping_max_stretch", cse_scope.DISCRETIZATION) + +def _max_curvature(ambient_dim, dim=None, where=None): + # An attempt at a 'max curvature' criterion. + + if dim is None: + dim = ambient_dim - 1 + + if ambient_dim == 2: + return abs(mean_curvature(ambient_dim, dim, where=where)) + elif ambient_dim == 3: + shape_op = shape_operator(ambient_dim, dim, where=where) + + abs_principal_curvatures = [ + abs(x) for x in _small_mat_eigenvalues(shape_op)] + from pymbolic.primitives import Max + return cse(Max(tuple(abs_principal_curvatures))) + else: + raise NotImplementedError("curvature criterion not implemented in %d " + "dimensions" % ambient_dim) + + +def _scaled_max_curvature(ambient_dim, dim=None, where=None): + """An attempt at a unit-less, scale-invariant quantity that characterizes + 'how much curviness there is on an element'. Values seem to hover around 1 + on typical meshes. Empirical evidence suggests that elements exceeding + a threshold of about 0.8-1 will have high QBX truncation error. + """ + + return _max_curvature(ambient_dim, dim, where=where) * \ + _simplex_mapping_max_stretch_factor(ambient_dim, dim, where=where, + with_elementwise_max=False) + # }}} -- GitLab From eb0f719f9fef67280fcfb1dc8800b4fb6602946a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 11:00:07 -0500 Subject: [PATCH 100/268] Allow refinement by scaled max curvature, remove max radius refinement criterion --- pytential/qbx/__init__.py | 7 ++++--- pytential/qbx/refinement.py | 27 ++++++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index b02fd1ca..73a65097 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -371,8 +371,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): def with_refinement(self, target_order=None, kernel_length_scale=None, maxiter=None, visualize=False, refiner=None, _expansion_disturbance_tolerance=None, - _max_expansion_radius=None, - _force_stage2_uniform_refinement_rounds=None): + _force_stage2_uniform_refinement_rounds=None, + _scaled_max_curvature_threshold=None): """ :arg refiner: If the mesh underlying :attr:`density_discr` is itself the result of refinement, then its @@ -401,9 +401,10 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): kernel_length_scale=kernel_length_scale, maxiter=maxiter, visualize=visualize, expansion_disturbance_tolerance=_expansion_disturbance_tolerance, - max_expansion_radius=_max_expansion_radius, force_stage2_uniform_refinement_rounds=( _force_stage2_uniform_refinement_rounds), + scaled_max_curvature_threshold=( + _scaled_max_curvature_threshold), refiner=refiner) return lpot, connection diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 82cb8eb8..d203670b 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -477,8 +477,8 @@ def make_empty_refine_flags(queue, lpot_source, use_stage2_discr=False): def refine_for_global_qbx(lpot_source, wrangler, group_factory, kernel_length_scale=None, - max_expansion_radius=None, force_stage2_uniform_refinement_rounds=None, + scaled_max_curvature_threshold=None, debug=None, maxiter=None, visualize=None, expansion_disturbance_tolerance=None, refiner=None): @@ -612,17 +612,26 @@ def refine_for_global_qbx(lpot_source, wrangler, iter_violated_criteria.append("kernel length scale") visualize_refinement(niter, "kernel-length-scale", refine_flags) - if max_expansion_radius is not None: - violates_expansion_radii = \ + if scaled_max_curvature_threshold is not None: + from pytential.qbx.utils import to_last_dim_length + from pytential import sym, bind + scaled_max_curv = to_last_dim_length( + lpot_source.density_discr, + bind(lpot_source, + sym.ElementwiseMax( + sym._scaled_max_curvature( + lpot_source.density_discr.ambient_dim))) + (wrangler.queue), "npanels") + + violates_scaled_max_curv = \ wrangler.check_element_prop_threshold( - element_property=lpot_source._expansion_radii( - "npanels"), - threshold=max_expansion_radius, + element_property=scaled_max_curv, + threshold=scaled_max_curvature_threshold, refine_flags=refine_flags, debug=debug) - if violates_expansion_radii: - iter_violated_criteria.append("expansion radii") - visualize_refinement(niter, "expansion-radii", refine_flags) + if violates_scaled_max_curv: + iter_violated_criteria.append("curvature") + visualize_refinement(niter, "curvature", refine_flags) if not iter_violated_criteria: # Only start building trees once the simple length-based criteria -- GitLab From 491a0313ec4a63a31a368f8d186f671126fa6d59 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 11:28:19 -0500 Subject: [PATCH 101/268] Add TOOD about too-strict test for test_target_association [ci skip] --- test/test_global_qbx.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 28f525a6..e7a4eaa6 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -217,6 +217,12 @@ def test_source_refinement_3d(ctx_getter, surface_name, surface_f, order): @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ + # TODO: This used to pass for a 20-to-1 ellipse (with different center + # placement). It still produces valid output right now, but the current + # test is not smart enough to recognize it. It might be useful to fix that. + # + # See discussion at + # https://gitlab.tiker.net/inducer/pytential/merge_requests/95#note_24003 ("18-to-1 ellipse", partial(ellipse, 18), 100), ("horseshoe", horseshoe, 64), ]) -- GitLab From be7475636c872eacfbe05313634513fde2284dac Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 15:57:14 -0500 Subject: [PATCH 102/268] Switch to new pytools logging infrastructure --- pytential/qbx/fmm.py | 28 ++++------- pytential/qbx/fmmlib.py | 6 +++ pytential/qbx/geometry.py | 16 ++----- pytential/qbx/refinement.py | 90 ++++++++++++++++------------------- pytential/qbx/target_assoc.py | 15 ++---- pytential/qbx/utils.py | 7 ++- 6 files changed, 67 insertions(+), 95 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 037f8188..ede7ede5 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -33,6 +33,8 @@ from sumpy.fmm import (SumpyExpansionWranglerCodeContainer, from pytools import memoize_method from pytential.qbx.interactions import P2QBXLFromCSR, M2QBXL, L2QBXL, QBXL2P +from pytools import log_process, ProcessLogger + import logging logger = logging.getLogger(__name__) @@ -192,6 +194,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), # {{{ qbx-related + @log_process(logger) def form_global_qbx_locals(self, src_weights): local_exps = self.qbx_local_expansion_zeros() @@ -228,6 +231,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), return result + @log_process(logger) def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_expansions = self.qbx_local_expansion_zeros() @@ -276,6 +280,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), return qbx_expansions + @log_process(logger) def translate_box_local_to_qbx_local(self, local_exps): qbx_expansions = self.qbx_local_expansion_zeros() @@ -320,6 +325,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), return qbx_expansions + @log_process(logger) def eval_qbx_expansions(self, qbx_expansions): pot = self.full_output_zeros() @@ -381,16 +387,12 @@ def drive_fmm(expansion_wrangler, src_weights): # Interface guidelines: Attributes of the tree are assumed to be known # to the expansion wrangler and should not be passed. - from time import time - start_time = time() - logger.info("start qbx fmm") + fmm_proc = ProcessLogger(logger, "qbx fmm") - logger.info("reorder source weights") src_weights = wrangler.reorder_sources(src_weights) # {{{ construct local multipoles - logger.info("construct local multipoles") mpole_exps = wrangler.form_multipoles( traversal.level_start_source_box_nrs, traversal.source_boxes, @@ -400,7 +402,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ propagate multipoles upward - logger.info("propagate multipoles upward") wrangler.coarsen_multipoles( traversal.level_start_source_parent_box_nrs, traversal.source_parent_boxes, @@ -410,7 +411,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ direct evaluation from neighbor source boxes ("list 1") - logger.info("direct evaluation from neighbor source boxes ('list 1')") non_qbx_potentials = wrangler.eval_direct( traversal.target_boxes, traversal.neighbor_source_boxes_starts, @@ -421,7 +421,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ translate separated siblings' ("list 2") mpoles to local - logger.info("translate separated siblings' ('list 2') mpoles to local") local_exps = wrangler.multipole_to_local( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, @@ -433,8 +432,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ evaluate sep. smaller mpoles ("list 3") at particles - logger.info("evaluate sep. smaller mpoles at particles ('list 3 far')") - # (the point of aiming this stage at particles is specifically to keep its # contribution *out* of the downward-propagating local expansions) @@ -450,8 +447,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ form locals for separated bigger source boxes ("list 4") - logger.info("form locals for separated bigger source boxes ('list 4 far')") - local_exps = local_exps + wrangler.form_locals( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, @@ -466,7 +461,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ propagate local_exps downward - logger.info("propagate local_exps downward") wrangler.refine_locals( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, @@ -476,7 +470,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ evaluate locals - logger.info("evaluate locals") non_qbx_potentials = non_qbx_potentials + wrangler.eval_locals( traversal.level_start_target_box_nrs, traversal.target_boxes, @@ -486,19 +479,14 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ wrangle qbx expansions - logger.info("form global qbx expansions from list 1") qbx_expansions = wrangler.form_global_qbx_locals(src_weights) - logger.info("translate from list 3 multipoles to qbx local expansions") qbx_expansions = qbx_expansions + \ wrangler.translate_box_multipoles_to_qbx_local(mpole_exps) - logger.info("translate from box local expansions to contained " - "qbx local expansions") qbx_expansions = qbx_expansions + \ wrangler.translate_box_local_to_qbx_local(local_exps) - logger.info("evaluate qbx local expansions") qbx_potentials = wrangler.eval_qbx_expansions( qbx_expansions) @@ -528,7 +516,7 @@ def drive_fmm(expansion_wrangler, src_weights): # }}} - logger.info("qbx fmm complete in %.2f s" % (time() - start_time)) + fmm_proc.done() return result diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 578dadce..2b6cbf88 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -30,6 +30,8 @@ from boxtree.pyfmmlib_integration import FMMLibExpansionWrangler from sumpy.kernel import LaplaceKernel, HelmholtzKernel +from pytools import log_process + import logging logger = logging.getLogger(__name__) @@ -298,6 +300,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ p2qbxl + @log_process(logger) def form_global_qbx_locals(self, src_weights): geo_data = self.geo_data trav = geo_data.traversal() @@ -348,6 +351,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ m2qbxl + @log_process(logger) def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_exps = self.qbx_local_expansion_zeros() @@ -458,6 +462,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # }}} + @log_process(logger) def translate_box_local_to_qbx_local(self, local_exps): qbx_expansions = self.qbx_local_expansion_zeros() @@ -518,6 +523,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): return qbx_expansions + @log_process(logger) def eval_qbx_expansions(self, qbx_expansions): output = self.full_output_zeros() diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 4c946e80..4c450881 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -36,6 +36,7 @@ from cgen import Enum from pytential.qbx.utils import TreeCodeContainerMixin +from pytools import log_process import logging logger = logging.getLogger(__name__) @@ -665,6 +666,7 @@ class QBXFMMGeometryData(object): return result.with_queue(None) @memoize_method + @log_process(logger) def global_qbx_centers(self): """Build a list of indices of QBX centers that use global QBX. This indexes into the global list of targets, (see :meth:`target_info`) of @@ -677,8 +679,6 @@ class QBXFMMGeometryData(object): user_target_to_center = self.user_target_to_center() with cl.CommandQueue(self.cl_context) as queue: - logger.info("find global qbx centers: start") - tgt_assoc_result = ( user_target_to_center.with_queue(queue)[self.ncenters:]) @@ -703,8 +703,6 @@ class QBXFMMGeometryData(object): ], queue=queue) - logger.info("find global qbx centers: done") - if self.debug: logger.debug( "find global qbx centers: using %d/%d centers" @@ -754,6 +752,7 @@ class QBXFMMGeometryData(object): return result.with_queue(None) @memoize_method + @log_process(logger) def center_to_tree_targets(self): """Return a :class:`CenterToTargetList`. See :meth:`target_to_center` for the reverse look-up table with targets in user order. @@ -764,8 +763,6 @@ class QBXFMMGeometryData(object): user_ttc = self.user_target_to_center() with cl.CommandQueue(self.cl_context) as queue: - logger.info("build center -> targets lookup table: start") - tree_ttc = cl.array.empty_like(user_ttc).with_queue(queue) tree_ttc[self.tree().sorted_target_ids] = user_ttc @@ -788,8 +785,6 @@ class QBXFMMGeometryData(object): filtered_tree_ttc, filtered_target_ids, self.ncenters, tree_ttc.dtype) - logger.info("build center -> targets lookup table: done") - result = CenterToTargetList( starts=center_target_starts, lists=targets_sorted_by_center).with_queue(None) @@ -797,6 +792,7 @@ class QBXFMMGeometryData(object): return result @memoize_method + @log_process(logger) def non_qbx_box_target_lists(self): """Build a list of targets per box that don't need to bother with QBX. Returns a :class:`boxtree.tree.FilteredTargetListsInTreeOrder`. @@ -807,8 +803,6 @@ class QBXFMMGeometryData(object): """ with cl.CommandQueue(self.cl_context) as queue: - logger.info("find non-qbx box target lists: start") - flags = (self.user_target_to_center().with_queue(queue) == target_state.NO_QBX_NEEDED) @@ -824,8 +818,6 @@ class QBXFMMGeometryData(object): plfilt = self.code_getter.particle_list_filter() result = plfilt.filter_target_lists_in_tree_order(queue, tree, flags) - logger.info("find non-qbx box target lists: done") - return result.with_queue(None) # {{{ plotting (for debugging) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index d203670b..2ab458a2 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -39,6 +39,8 @@ from pytential.qbx.utils import ( QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS, TreeWranglerBase, TreeCodeContainerMixin) +from pytools import ProcessLogger, log_process + import logging logger = logging.getLogger(__name__) @@ -281,6 +283,7 @@ class RefinerWrangler(TreeWranglerBase): # {{{ check subroutines for conditions 1-3 + @log_process(logger) def check_expansion_disks_undisturbed_by_sources(self, lpot_source, tree, peer_lists, expansion_disturbance_tolerance, @@ -299,9 +302,6 @@ class RefinerWrangler(TreeWranglerBase): tree.particle_id_dtype, max_levels) - logger.info("refiner: checking that expansion disk is " - "undisturbed by sources") - if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() @@ -339,10 +339,9 @@ class RefinerWrangler(TreeWranglerBase): logger.debug("refiner: found {} panel(s) to refine".format( npanels_to_refine - npanels_to_refine_prev)) - logger.info("refiner: done checking center is closest to orig panel") - return found_panel_to_refine.get()[0] == 1 + @log_process(logger) def check_sufficient_source_quadrature_resolution( self, lpot_source, tree, peer_lists, refine_flags, debug, wait_for=None): @@ -361,8 +360,6 @@ class RefinerWrangler(TreeWranglerBase): if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() - logger.info("refiner: checking sufficient quadrature resolution") - found_panel_to_refine = cl.array.zeros(self.queue, 1, np.int32) found_panel_to_refine.finish() @@ -396,16 +393,12 @@ class RefinerWrangler(TreeWranglerBase): logger.debug("refiner: found {} panel(s) to refine".format( npanels_to_refine - npanels_to_refine_prev)) - logger.info("refiner: done checking sufficient quadrature resolution") - return found_panel_to_refine.get()[0] == 1 def check_element_prop_threshold(self, element_property, threshold, refine_flags, debug, wait_for=None): knl = self.code_container.element_prop_threshold_checker() - logger.info("refiner: checking kernel length scale to panel size ratio") - if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() @@ -425,8 +418,6 @@ class RefinerWrangler(TreeWranglerBase): logger.debug("refiner: found {} panel(s) to refine".format( npanels_to_refine - npanels_to_refine_prev)) - logger.info("refiner: done checking kernel length scale to panel size ratio") - return (out["refine_flags_updated"].get() == 1).all() # }}} @@ -439,13 +430,10 @@ class RefinerWrangler(TreeWranglerBase): refine_flags = refine_flags.get(self.queue) refine_flags = refine_flags.astype(np.bool) - logger.info("refiner: calling meshmode") - - refiner.refine(refine_flags) - from meshmode.discretization.connection import make_refinement_connection - conn = make_refinement_connection(refiner, density_discr, factory) - - logger.info("refiner: done calling meshmode") + with ProcessLogger(logger, "refine mesh"): + refiner.refine(refine_flags) + from meshmode.discretization.connection import make_refinement_connection + conn = make_refinement_connection(refiner, density_discr, factory) return conn @@ -601,37 +589,43 @@ def refine_for_global_qbx(lpot_source, wrangler, refine_flags = make_empty_refine_flags(wrangler.queue, lpot_source) if kernel_length_scale is not None: - violates_kernel_length_scale = \ - wrangler.check_element_prop_threshold( - element_property=lpot_source._coarsest_quad_resolution( - "npanels"), - threshold=kernel_length_scale, - refine_flags=refine_flags, debug=debug) + with ProcessLogger(logger, + "checking kernel length scale to panel size ratio"): - if violates_kernel_length_scale: - iter_violated_criteria.append("kernel length scale") - visualize_refinement(niter, "kernel-length-scale", refine_flags) + violates_kernel_length_scale = \ + wrangler.check_element_prop_threshold( + element_property=( + lpot_source._coarsest_quad_resolution( + "npanels")), + threshold=kernel_length_scale, + refine_flags=refine_flags, debug=debug) + + if violates_kernel_length_scale: + iter_violated_criteria.append("kernel length scale") + visualize_refinement(niter, "kernel-length-scale", refine_flags) if scaled_max_curvature_threshold is not None: - from pytential.qbx.utils import to_last_dim_length - from pytential import sym, bind - scaled_max_curv = to_last_dim_length( - lpot_source.density_discr, - bind(lpot_source, - sym.ElementwiseMax( - sym._scaled_max_curvature( - lpot_source.density_discr.ambient_dim))) - (wrangler.queue), "npanels") - - violates_scaled_max_curv = \ - wrangler.check_element_prop_threshold( - element_property=scaled_max_curv, - threshold=scaled_max_curvature_threshold, - refine_flags=refine_flags, debug=debug) - - if violates_scaled_max_curv: - iter_violated_criteria.append("curvature") - visualize_refinement(niter, "curvature", refine_flags) + with ProcessLogger(logger, + "checking scaled max curvature threshold"): + from pytential.qbx.utils import to_last_dim_length + from pytential import sym, bind + scaled_max_curv = to_last_dim_length( + lpot_source.density_discr, + bind(lpot_source, + sym.ElementwiseMax( + sym._scaled_max_curvature( + lpot_source.density_discr.ambient_dim))) + (wrangler.queue), "npanels") + + violates_scaled_max_curv = \ + wrangler.check_element_prop_threshold( + element_property=scaled_max_curv, + threshold=scaled_max_curvature_threshold, + refine_flags=refine_flags, debug=debug) + + if violates_scaled_max_curv: + iter_violated_criteria.append("curvature") + visualize_refinement(niter, "curvature", refine_flags) if not iter_violated_criteria: # Only start building trees once the simple length-based criteria diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 7b9736ce..bf15bf6e 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -43,6 +43,7 @@ from pytential.qbx.utils import ( unwrap_args = AreaQueryElementwiseTemplate.unwrap_args +from pytools import log_process import logging logger = logging.getLogger(__name__) @@ -438,6 +439,7 @@ class TargetAssociationWrangler(TreeWranglerBase): self.code_container = code_container self.queue = queue + @log_process(logger) def mark_targets(self, tree, peer_lists, lpot_source, target_status, debug, wait_for=None): # Round up level count--this gets included in the kernel as @@ -497,8 +499,6 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for=wait_for) wait_for = [evt] - logger.info("target association: marking targets close to panels") - tunnel_radius_by_source = lpot_source._close_target_tunnel_radius("nsources") evt = knl( @@ -527,10 +527,9 @@ class TargetAssociationWrangler(TreeWranglerBase): cl.wait_for_events([evt]) - logger.info("target association: done marking targets close to panels") - return (found_target_close_to_panel == 1).all().get() + @log_process(logger) def try_find_centers(self, tree, peer_lists, lpot_source, target_status, target_flags, target_assoc, target_association_tolerance, debug, wait_for=None): @@ -582,8 +581,6 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for.extend(min_dist_to_center.events) - logger.info("target association: finding centers for targets") - evt = knl( *unwrap_args( tree, peer_lists, @@ -613,9 +610,9 @@ class TargetAssociationWrangler(TreeWranglerBase): .format(ntargets_associated)) cl.wait_for_events([evt]) - logger.info("target association: done finding centers for targets") return + @log_process(logger) def mark_panels_for_refinement(self, tree, peer_lists, lpot_source, target_status, refine_flags, debug, wait_for=None): @@ -653,8 +650,6 @@ class TargetAssociationWrangler(TreeWranglerBase): wait_for=wait_for) wait_for = [evt] - logger.info("target association: marking panels for refinement") - evt = knl( *unwrap_args( tree, peer_lists, @@ -684,8 +679,6 @@ class TargetAssociationWrangler(TreeWranglerBase): cl.wait_for_events([evt]) - logger.info("target association: done marking panels for refinement") - return (found_panel_to_refine == 1).all().get() def make_target_flags(self, target_discrs_and_qbx_sides): diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index 84626479..b0ffc066 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -37,6 +37,8 @@ from loopy.version import MOST_RECENT_LANGUAGE_VERSION import logging logger = logging.getLogger(__name__) +from pytools import log_process + # {{{ c and mako snippets @@ -443,6 +445,7 @@ class TreeWithQBXMetadata(Tree): MAX_REFINE_WEIGHT = 64 +@log_process(logger) def build_tree_with_qbx_metadata( queue, tree_builder, particle_list_filter, lpot_source, targets_list=(), use_stage2_discr=False): @@ -472,8 +475,6 @@ def build_tree_with_qbx_metadata( # - then panels (=centers of mass) # - then targets - logger.info("start building tree with qbx metadata") - sources = ( lpot_source.density_discr.nodes() if not use_stage2_discr @@ -585,8 +586,6 @@ def build_tree_with_qbx_metadata( tree_attrs.update(particle_classes) - logger.info("done building tree with qbx metadata") - return TreeWithQBXMetadata( qbx_panel_to_source_starts=qbx_panel_to_source_starts, qbx_panel_to_center_starts=qbx_panel_to_center_starts, -- GitLab From a061bc7b5415989e7ce2e41d3699908a00f6e062 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 20 Apr 2018 19:03:31 -0500 Subject: [PATCH 103/268] Remove another stray logging statement --- pytential/qbx/fmm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index ede7ede5..d3622e59 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -494,8 +494,6 @@ def drive_fmm(expansion_wrangler, src_weights): # {{{ reorder potentials - logger.info("reorder potentials") - nqbtl = geo_data.non_qbx_box_target_lists() all_potentials_in_tree_order = wrangler.full_output_zeros() -- GitLab From bbac8307b49c5d0733acd132191fe802565ae61b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 23 Apr 2018 14:41:23 -0500 Subject: [PATCH 104/268] Add missing StringifyMapper.map_node_sum --- pytential/symbolic/mappers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 0dfaa4f9..9fdbaae4 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -503,6 +503,9 @@ class StringifyMapper(BaseStringifyMapper): def map_node_max(self, expr, enclosing_prec): return "NodeMax(%s)" % self.rec(expr.operand, PREC_NONE) + def map_node_sum(self, expr, enclosing_prec): + return "NodeSum(%s)" % self.rec(expr.operand, PREC_NONE) + def map_node_coordinate_component(self, expr, enclosing_prec): return "x%d.%s" % (expr.ambient_axis, stringify_where(expr.where)) -- GitLab From 0fcf9e378ff1822f1190614ea32edeb92c923889 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 27 Apr 2018 16:50:35 -0500 Subject: [PATCH 105/268] Change GMRES stall logic to test for lack of residual decrease over stall_iterations iterations --- pytential/solve.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pytential/solve.py b/pytential/solve.py index 34081d27..e46fa5ea 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -169,7 +169,6 @@ def _gmres(A, b, restart=None, tol=None, x0=None, dot=None, # noqa norm_b = norm(b) last_resid_norm = None - stall_count = 0 residual_norms = [] for iteration in range(maxiter): @@ -209,21 +208,20 @@ def _gmres(A, b, restart=None, tol=None, x0=None, dot=None, # noqa else: print("*** WARNING: non-monotonic residuals in GMRES") - if stall_iterations and \ - norm_r > last_resid_norm/no_progress_factor: - stall_count += 1 + if (stall_iterations and + len(residual_norms) > stall_iterations and + norm_r > ( + residual_norms[-stall_iterations] + / no_progress_factor)): - if stall_count >= stall_iterations: - state = "stalled" - if hard_failure: - raise GMRESError(state) - else: - return GMRESResult(solution=x, - residual_norms=residual_norms, - iteration_count=iteration, success=False, - state=state) - else: - stall_count = 0 + state = "stalled" + if hard_failure: + raise GMRESError(state) + else: + return GMRESResult(solution=x, + residual_norms=residual_norms, + iteration_count=iteration, success=False, + state=state) last_resid_norm = norm_r -- GitLab From 39f04732778e72d865c87ab1a36840d7841ce164 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 27 Apr 2018 16:51:47 -0500 Subject: [PATCH 106/268] Enable QBXLayerPotentialSource.copy to set the FMM backend --- pytential/qbx/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 4e03ceca..3fe9071e 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -199,6 +199,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _expansion_stick_out_factor=_not_provided, _tree_kind=None, geometry_data_inspector=None, + fmm_backend=None, debug=_not_provided, _refined_for_global_qbx=_not_provided, @@ -279,7 +280,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _tree_kind=_tree_kind or self._tree_kind, geometry_data_inspector=( geometry_data_inspector or self.geometry_data_inspector), - fmm_backend=self.fmm_backend, + fmm_backend=fmm_backend or self.fmm_backend, **kwargs) # }}} -- GitLab From a1177fdab36d7db8eb82b4706658fc2f40a0c0e8 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 29 Apr 2018 18:53:13 -0500 Subject: [PATCH 107/268] Allow setting _max_leaf_refine_weight from QBXLayerPotentialSource.copy --- pytential/qbx/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 3fe9071e..6055e2bb 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -197,6 +197,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): target_association_tolerance=_not_provided, _expansions_in_tree_have_extent=_not_provided, _expansion_stick_out_factor=_not_provided, + _max_leaf_refine_weight=None, _tree_kind=None, geometry_data_inspector=None, fmm_backend=None, @@ -272,7 +273,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): if _expansion_stick_out_factor is not _not_provided else self._expansion_stick_out_factor), _well_sep_is_n_away=self._well_sep_is_n_away, - _max_leaf_refine_weight=self._max_leaf_refine_weight, + _max_leaf_refine_weight=( + _max_leaf_refine_weight or self._max_leaf_refine_weight), _box_extent_norm=self._box_extent_norm, _from_sep_smaller_crit=self._from_sep_smaller_crit, _from_sep_smaller_min_nsources_cumul=( -- GitLab From eef264d360003ed112704a0f3b2dd413e1154fb4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 29 Apr 2018 18:57:47 -0500 Subject: [PATCH 108/268] Fix 3D order powers in cost models --- pytential/qbx/fmm.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index d3622e59..4be9e5ca 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -525,6 +525,7 @@ def drive_fmm(expansion_wrangler, src_weights): def assemble_performance_data(geo_data, uses_pde_expansions, translation_source_power=None, translation_target_power=None, + translation_max_power=None, summarize_parallel=None, merge_close_lists=True): """ :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM @@ -573,10 +574,13 @@ def assemble_performance_data(geo_data, uses_pde_expansions, if d == 2: default_translation_source_power = 1 default_translation_target_power = 1 + default_translation_max_power = 0 elif d == 3: - default_translation_source_power = 2 - default_translation_target_power = 1 + # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. + default_translation_source_power = 0 + default_translation_target_power = 0 + default_translation_max_power = 3 else: raise ValueError("Don't know how to estimate expansion complexities " @@ -592,11 +596,16 @@ def assemble_performance_data(geo_data, uses_pde_expansions, translation_source_power = default_translation_source_power if translation_target_power is None: translation_target_power = default_translation_target_power + if translation_max_power is None: + translation_max_power = default_translation_max_power def xlat_cost(p_source, p_target): + from pymbolic.primitives import Max return ( p_source ** translation_source_power - * p_target ** translation_target_power) + * p_target ** translation_target_power + * Max((p_source, p_target)) ** translation_max_power + ) result.update( nlevels=tree.nlevels, @@ -759,11 +768,15 @@ def assemble_performance_data(geo_data, uses_pde_expansions, # {{{ form global qbx locals global_qbx_centers = geo_data.global_qbx_centers() + + # If merge_close_lists is False above, then this builds another traversal + # (which is OK). qbx_center_to_target_box = geo_data.qbx_center_to_target_box() center_to_targets_starts = geo_data.center_to_tree_targets().starts qbx_center_to_target_box_source_level = np.empty( (tree.nlevels,), dtype=object ) + for src_level in range(tree.nlevels): qbx_center_to_target_box_source_level[src_level] = ( geo_data.qbx_center_to_target_box_source_level(src_level) -- GitLab From 4825efd6f80e2d774dcc390725aae6e368fe3c23 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 29 Apr 2018 23:56:16 -0500 Subject: [PATCH 109/268] Tweak defaults for _max_leaf_refine_weight --- pytential/qbx/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 6055e2bb..e45835d3 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -78,7 +78,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _expansions_in_tree_have_extent=True, _expansion_stick_out_factor=0.5, _well_sep_is_n_away=2, - _max_leaf_refine_weight=32, + _max_leaf_refine_weight=None, _box_extent_norm=None, _from_sep_smaller_crit=None, _from_sep_smaller_min_nsources_cumul=None, @@ -139,6 +139,15 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): def fmm_level_to_order(kernel, kernel_args, tree, level): return fmm_order + if _max_leaf_refine_weight is None: + if density_discr.ambient_dim == 2: + _max_leaf_refine_weight = 64 + elif density_discr.ambient_dim == 3: + _max_leaf_refine_weight = 128 + else: + # Just guessing... + _max_leaf_refine_weight = 64 + if _from_sep_smaller_min_nsources_cumul is None: # See here for the comment thread that led to these defaults: # https://gitlab.tiker.net/inducer/boxtree/merge_requests/28#note_18661 -- GitLab From 8741e15f24da913f56b7e900359bba3c7495fcfc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 1 May 2018 14:35:57 -0500 Subject: [PATCH 110/268] Allow setting _box_extent_norm and _from_sep_smaller_crit from QBXLayerPotentialSource.copy --- pytential/qbx/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index e45835d3..f1f53cd6 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -207,6 +207,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _expansions_in_tree_have_extent=_not_provided, _expansion_stick_out_factor=_not_provided, _max_leaf_refine_weight=None, + _box_extent_norm=None, + _from_sep_smaller_crit=None, _tree_kind=None, geometry_data_inspector=None, fmm_backend=None, @@ -284,8 +286,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _well_sep_is_n_away=self._well_sep_is_n_away, _max_leaf_refine_weight=( _max_leaf_refine_weight or self._max_leaf_refine_weight), - _box_extent_norm=self._box_extent_norm, - _from_sep_smaller_crit=self._from_sep_smaller_crit, + _box_extent_norm=(_box_extent_norm or self._box_extent_norm), + _from_sep_smaller_crit=( + _from_sep_smaller_crit or self._from_sep_smaller_crit), _from_sep_smaller_min_nsources_cumul=( self._from_sep_smaller_min_nsources_cumul), _tree_kind=_tree_kind or self._tree_kind, -- GitLab From d5cdc90643976ea8a342ba98f0893aa8480d7e9d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 1 May 2018 15:01:05 -0500 Subject: [PATCH 111/268] Fix cache key determination for direct close eval kernel --- pytential/qbx/direct.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 70fa0d1a..7ad1782c 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -27,12 +27,18 @@ import numpy as np from sumpy.qbx import LayerPotentialBase +from pytential.version import PYTENTIAL_KERNEL_VERSION + # {{{ qbx applier on a target/center subset class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): default_name = "qbx_tgt_ctr_subset" + def get_cache_key(self): + return super(LayerPotentialOnTargetAndCenterSubset, self).get_cache_key() + ( + PYTENTIAL_KERNEL_VERSION,) + def get_kernel(self): loopy_insns, result_names = self.get_loopy_insns_and_result_names() kernel_exprs = self.get_kernel_exprs(result_names) -- GitLab From 23cc20262a11e607398ac60ae6ba2ba2fa883da5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 00:30:29 -0500 Subject: [PATCH 112/268] Fix refinement visualization during stage-2 refinement --- pytential/qbx/refinement.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 2ab458a2..1911a48d 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -523,15 +523,27 @@ def refine_for_global_qbx(lpot_source, wrangler, # {{{ first stage refinement - def visualize_refinement(niter, stage, flags): + def visualize_refinement(niter, stage_nr, stage_name, flags): if not visualize: return - discr = lpot_source.density_discr + if stage_nr == 1: + discr = lpot_source.density_discr + elif stage_nr == 2: + discr = lpot_source.stage2_density_discr + else: + raise ValueError("unexpected stage number") + + flags = flags.get() + logger.info("for stage %s: splitting %d/%d stage-%d elements", + stage_name, np.sum(flags), discr.mesh.nelements, stage_nr) + from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(wrangler.queue, discr, 3) - flags = flags.get().astype(np.bool) + assert len(flags) == discr.mesh.nelements + + flags = flags.astype(np.bool) nodes_flags = np.zeros(discr.nnodes) for grp in discr.groups: meg = grp.mesh_el_group @@ -549,7 +561,7 @@ def refine_for_global_qbx(lpot_source, wrangler, wrangler.queue).as_vector(dtype=object) vis_data.append(("bdry_normals", bdry_normals),) - vis.write_vtk_file("refinement-%03d-%s.vtu" % (niter, stage), vis_data) + vis.write_vtk_file("refinement-%s-%03d.vtu" % (stage_name, niter), vis_data) def warn_max_iterations(): from warnings import warn @@ -602,7 +614,8 @@ def refine_for_global_qbx(lpot_source, wrangler, if violates_kernel_length_scale: iter_violated_criteria.append("kernel length scale") - visualize_refinement(niter, "kernel-length-scale", refine_flags) + visualize_refinement( + niter, 1, "kernel-length-scale", refine_flags) if scaled_max_curvature_threshold is not None: with ProcessLogger(logger, @@ -625,7 +638,7 @@ def refine_for_global_qbx(lpot_source, wrangler, if violates_scaled_max_curv: iter_violated_criteria.append("curvature") - visualize_refinement(niter, "curvature", refine_flags) + visualize_refinement(niter, 1, "curvature", refine_flags) if not iter_violated_criteria: # Only start building trees once the simple length-based criteria @@ -643,7 +656,7 @@ def refine_for_global_qbx(lpot_source, wrangler, refine_flags, debug) if has_disturbed_expansions: iter_violated_criteria.append("disturbed expansions") - visualize_refinement(niter, "disturbed-expansions", refine_flags) + visualize_refinement(niter, 1, "disturbed-expansions", refine_flags) del tree del peer_lists @@ -689,7 +702,7 @@ def refine_for_global_qbx(lpot_source, wrangler, lpot_source, tree, peer_lists, refine_flags, debug) if has_insufficient_quad_res: iter_violated_criteria.append("insufficient quadrature resolution") - visualize_refinement(niter, "quad-resolution", refine_flags) + visualize_refinement(niter, 2, "quad-resolution", refine_flags) if iter_violated_criteria: violated_criteria.append(" and ".join(iter_violated_criteria)) -- GitLab From 861c332f82f8d64a7e806a3f5754d3ec7ad6fd88 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 00:30:55 -0500 Subject: [PATCH 113/268] Factor _equilateral_parametrization_derivative_matrix into its own function in primitives --- pytential/symbolic/primitives.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index ecbdc899..17a17013 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -648,6 +648,24 @@ def _small_mat_eigenvalues(mat): "eigenvalue formula for %dx%d matrices" % (m, n)) +def _equilateral_parametrization_derivative_matrix(ambient_dim, dim=None, + where=None): + if dim is None: + dim = ambient_dim - 1 + + pder_mat = parametrization_derivative_matrix(ambient_dim, dim, where) + + # The above procedure works well only when the 'reference' end of the + # mapping is in equilateral coordinates. + from modepy.tools import EQUILATERAL_TO_UNIT_MAP + equi_to_unit = EQUILATERAL_TO_UNIT_MAP[dim].a + + # This is the Jacobian of the (equilateral reference element) -> (global) map. + return cse( + np.dot(pder_mat, equi_to_unit), + "equilateral_pder_mat") + + def _simplex_mapping_max_stretch_factor(ambient_dim, dim=None, where=None, with_elementwise_max=True): """Return the largest factor by which the reference-to-global @@ -669,17 +687,8 @@ def _simplex_mapping_max_stretch_factor(ambient_dim, dim=None, where=None, # stretching, where 'stretching' (*2 to remove bi-unit reference element) # reflects available quadrature resolution in that direction. - pder_mat = parametrization_derivative_matrix(ambient_dim, dim, where) - - # The above procedure works well only when the 'reference' end of the - # mapping is in equilateral coordinates. - from modepy.tools import EQUILATERAL_TO_UNIT_MAP - equi_to_unit = EQUILATERAL_TO_UNIT_MAP[dim].a - - # This is the Jacobian of the (equilateral reference element) -> (global) map. - equi_pder_mat = cse( - np.dot(pder_mat, equi_to_unit), - "equilateral_pder_mat") + equi_pder_mat = _equilateral_parametrization_derivative_matrix( + ambient_dim, dim, where) # Compute eigenvalues of J^T to compute SVD. equi_pder_mat_jtj = cse( -- GitLab From 38fc8c2083ed9075f2f06bf694f677296dfc232e Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 00:33:38 -0500 Subject: [PATCH 114/268] Scalar int eq test: catch and visualize assoc-failed targets --- test/test_scalar_int_eq.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 0d9fca28..056c8571 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -36,9 +36,11 @@ from functools import partial from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh) +from meshmode.discretization.visualization import make_visualizer from sumpy.visualization import FieldPlotter from sumpy.symbolic import USE_SYMENGINE from pytential import bind, sym +from pytential.qbx import QBXTargetAssociationFailedException import logging logger = logging.getLogger(__name__) @@ -349,7 +351,6 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): pt.show() elif mesh.ambient_dim == 3: - from meshmode.discretization.visualization import make_visualizer bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) bdry_normals = bind(density_discr, sym.normal(3))(queue)\ @@ -456,13 +457,22 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): rhs = bind(density_discr, op.prepare_rhs(sym.var("bc")))(queue, bc=bc) - from pytential.solve import gmres - gmres_result = gmres( - bound_op.scipy_op(queue, "u", dtype, **concrete_knl_kwargs), - rhs, - tol=case.gmres_tol, - progress=True, - hard_failure=True) + try: + from pytential.solve import gmres + gmres_result = gmres( + bound_op.scipy_op(queue, "u", dtype, **concrete_knl_kwargs), + rhs, + tol=case.gmres_tol, + progress=True, + hard_failure=True, + stall_iterations=50, no_progress_factor=1.05) + except QBXTargetAssociationFailedException as e: + bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) + + bdry_vis.write_vtk_file("failed-targets-%s.vtu" % resolution, [ + ("failed_targets", e.failed_target_flags), + ]) + raise print("gmres state:", gmres_result.state) u = gmres_result.solution @@ -584,10 +594,9 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): # }}} - # {{{ 3D plotting + # {{{ any-D file plotting - if visualize and qbx.ambient_dim == 3: - from meshmode.discretization.visualization import make_visualizer + if visualize: bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) bdry_normals = bind(density_discr, sym.normal(qbx.ambient_dim))(queue)\ @@ -607,7 +616,6 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): qbx_tgt_tol = qbx.copy(target_association_tolerance=0.15) from pytential.target import PointsTarget - from pytential.qbx import QBXTargetAssociationFailedException try: solved_pot = bind( -- GitLab From 3b2cadb1bdaeffa65c658fb85b77f215357c17bb Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 00:38:10 -0500 Subject: [PATCH 115/268] Scalar int eq test: don't save potential difference to save disk space --- test/test_scalar_int_eq.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 056c8571..36953b14 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -641,7 +641,6 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): [ ("solved_pot", solved_pot), ("true_pot", true_pot), - ("pot_diff", solved_pot-true_pot), ] ) -- GitLab From b9815559be7db11caa391162cfeccaa8600a206c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 00:39:02 -0500 Subject: [PATCH 116/268] Add betterplane mesh gen for scalar int eq test --- test/test_scalar_int_eq.py | 123 ++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 36953b14..a734c074 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -286,7 +286,106 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): return perform_flips(mesh, np.ones(mesh.nelements)) inner_radius = 0.2 - outer_radius = 12 + outer_radius = 12 # was '-13' in some large-scale run (?) + + +class BetterplaneIntEqTestCase(IntEqTestCase): + name = "betterplane" + + resolutions = [0.3] + + fmm_backend = "fmmlib" + use_refinement = True + + qbx_order = 3 + fmm_tol = 1e-4 + target_order = 6 + check_gradient = False + check_tangential_deriv = False + + visualize_geometry = True + + #scaled_max_curvature_threshold = 1 + expansion_disturbance_tolerance = 0.3 + + # We're only expecting three digits based on FMM settings. Who are we + # kidding? + gmres_tol = 1e-5 + + 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/a869fc3/" + "surface-3d/betterplane.brep") + + from meshmode.mesh.io import generate_gmsh, ScriptWithFilesSource + mesh = generate_gmsh( + ScriptWithFilesSource(""" + Merge "betterplane.brep"; + + Mesh.CharacteristicLengthMax = %(lcmax)f; + Mesh.ElementOrder = 2; + Mesh.CharacteristicLengthExtendFromBoundary = 0; + + // 2D mesh optimization + // Mesh.Lloyd = 1; + + l_superfine() = Unique(Abs(Boundary{ Surface{ + 27, 25, 17, 13, 18 }; })); + l_fine() = Unique(Abs(Boundary{ Surface{ 2, 6, 7}; })); + l_coarse() = Unique(Abs(Boundary{ Surface{ 14, 16 }; })); + + // p() = Unique(Abs(Boundary{ Line{l_fine()}; })); + // Characteristic Length{p()} = 0.05; + + Field[1] = Attractor; + Field[1].NNodesByEdge = 100; + Field[1].EdgesList = {l_superfine()}; + + Field[2] = Threshold; + Field[2].IField = 1; + Field[2].LcMin = 0.075; + Field[2].LcMax = %(lcmax)f; + Field[2].DistMin = 0.1; + Field[2].DistMax = 0.4; + + Field[3] = Attractor; + Field[3].NNodesByEdge = 100; + Field[3].EdgesList = {l_fine()}; + + Field[4] = Threshold; + Field[4].IField = 3; + Field[4].LcMin = 0.1; + Field[4].LcMax = %(lcmax)f; + Field[4].DistMin = 0.15; + Field[4].DistMax = 0.4; + + Field[5] = Attractor; + Field[5].NNodesByEdge = 100; + Field[5].EdgesList = {l_coarse()}; + + Field[6] = Threshold; + Field[6].IField = 5; + Field[6].LcMin = 0.15; + Field[6].LcMax = %(lcmax)f; + Field[6].DistMin = 0.2; + Field[6].DistMax = 0.4; + + Field[7] = Min; + Field[7].FieldsList = {2, 4, 6}; + + Background Field = 7; + """ % { + "lcmax": resolution, + }, ["betterplane.brep"]), 2) + + # Flip elements--gmsh generates inside-out geometry. + from meshmode.mesh.processing import perform_flips + return perform_flips(mesh, np.ones(mesh.nelements)) + + inner_radius = 0.2 + outer_radius = 15 # }}} @@ -324,19 +423,39 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): qbx_lpot_kwargs["fmm_order"] = case.qbx_order + 5 qbx = QBXLayerPotentialSource( - pre_density_discr, fine_order=source_order, qbx_order=case.qbx_order, + pre_density_discr, + fine_order=source_order, + qbx_order=case.qbx_order, + _from_sep_smaller_min_nsources_cumul=30, fmm_backend=case.fmm_backend, **qbx_lpot_kwargs) if case.use_refinement: if case.k != 0: refiner_extra_kwargs["kernel_length_scale"] = 5/case.k + if hasattr(case, "scaled_max_curvature_threshold"): + refiner_extra_kwargs["_scaled_max_curvature_threshold"] = \ + case.scaled_max_curvature_threshold + + if hasattr(case, "expansion_disturbance_tolerance"): + refiner_extra_kwargs["_expansion_disturbance_tolerance"] = \ + case.expansion_disturbance_tolerance + + if hasattr(case, "refinement_maxiter"): + refiner_extra_kwargs["maxiter"] = case.refinement_maxiter + + #refiner_extra_kwargs["visualize"] = True + print("%d elements before refinement" % pre_density_discr.mesh.nelements) qbx, _ = qbx.with_refinement(**refiner_extra_kwargs) print("%d elements after refinement" % qbx.density_discr.mesh.nelements) density_discr = qbx.density_discr + if hasattr(case, "visualize_geometry") and case.visualize_geometry: + bdry_vis = make_visualizer(queue, density_discr, case.target_order) + bdry_vis.write_vtk_file("geometry.vtu", []) + # {{{ plot geometry if 0: -- GitLab From a14168200be38648a094ceb283c63a925e8f8db6 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 18:13:18 -0500 Subject: [PATCH 117/268] Add a scattering mode to the int eq test --- test/test_scalar_int_eq.py | 253 ++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 147 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index a734c074..d38fdf48 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -65,19 +65,23 @@ def make_circular_point_group(ambient_dim, npoints, radius, # {{{ test cases class IntEqTestCase: - def __init__(self, helmholtz_k, bc_type, loc_sign): + def __init__(self, helmholtz_k, bc_type, prob_side): + """ + :arg prob_side: may be -1, +1, or ``'scat'`` for a scattering problem + """ + self.helmholtz_k = helmholtz_k self.bc_type = bc_type - self.loc_sign = loc_sign + self.prob_side = prob_side @property def k(self): return self.helmholtz_k def __str__(self): - return ("name: %s, bc_type: %s, loc_sign: %s, " + return ("name: %s, bc_type: %s, prob_side: %s, " "helmholtz_k: %s, qbx_order: %d, target_order: %d" - % (self.name, self.bc_type, self.loc_sign, self.helmholtz_k, + % (self.name, self.bc_type, self.prob_side, self.helmholtz_k, self.qbx_order, self.target_order)) fmm_backend = "sumpy" @@ -453,8 +457,14 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): density_discr = qbx.density_discr if hasattr(case, "visualize_geometry") and case.visualize_geometry: + bdry_normals = bind( + density_discr, sym.normal(mesh.ambient_dim) + )(queue).as_vector(dtype=object) + bdry_vis = make_visualizer(queue, density_discr, case.target_order) - bdry_vis.write_vtk_file("geometry.vtu", []) + bdry_vis.write_vtk_file("geometry.vtu", [ + ("normals", bdry_normals) + ]) # {{{ plot geometry @@ -505,11 +515,13 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): else: dtype = np.float64 + loc_sign = +1 if case.prob_side in [-1, "scat"] else +1 + if case.bc_type == "dirichlet": - op = DirichletOperator(knl, case.loc_sign, use_l2_weighting=False, + op = DirichletOperator(knl, loc_sign, use_l2_weighting=True, kernel_arguments=knl_kwargs) elif case.bc_type == "neumann": - op = NeumannOperator(knl, case.loc_sign, use_l2_weighting=False, + op = NeumannOperator(knl, loc_sign, use_l2_weighting=True, use_improved_operator=False, kernel_arguments=knl_kwargs) else: assert False @@ -520,12 +532,17 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): # {{{ set up test data - if case.loc_sign < 0: + if case.prob_side == -1: test_src_geo_radius = case.outer_radius test_tgt_geo_radius = case.inner_radius - else: + elif case.prob_side == +1: test_src_geo_radius = case.inner_radius test_tgt_geo_radius = case.outer_radius + elif case.prob_side == "scat": + test_src_geo_radius = case.outer_radius + test_tgt_geo_radius = case.outer_radius + else: + raise ValueError("unknown problem_side") point_sources = make_circular_point_group( mesh.ambient_dim, 10, test_src_geo_radius, @@ -615,41 +632,46 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): # }}} - # {{{ error check + if case.prob_side != "scat": + # {{{ error check - points_target = PointsTarget(test_targets) - bound_tgt_op = bind((qbx, points_target), - op.representation(sym.var("u"))) + points_target = PointsTarget(test_targets) + bound_tgt_op = bind((qbx, points_target), + op.representation(sym.var("u"))) - test_via_bdry = bound_tgt_op(queue, u=u, k=case.k) + test_via_bdry = bound_tgt_op(queue, u=u, k=case.k) - err = test_direct-test_via_bdry + err = test_via_bdry - test_direct - err = err.get() - test_direct = test_direct.get() - test_via_bdry = test_via_bdry.get() + err = err.get() + test_direct = test_direct.get() + test_via_bdry = test_via_bdry.get() - # {{{ remove effect of net source charge + # {{{ remove effect of net source charge - if case.k == 0 and case.bc_type == "neumann" and case.loc_sign == -1: + if case.k == 0 and case.bc_type == "neumann" and loc_sign == -1: - # remove constant offset in interior Laplace Neumann error - tgt_ones = np.ones_like(test_direct) - tgt_ones = tgt_ones/la.norm(tgt_ones) - err = err - np.vdot(tgt_ones, err)*tgt_ones + # remove constant offset in interior Laplace Neumann error + tgt_ones = np.ones_like(test_direct) + tgt_ones = tgt_ones/la.norm(tgt_ones) + err = err - np.vdot(tgt_ones, err)*tgt_ones - # }}} + # }}} - rel_err_2 = la.norm(err)/la.norm(test_direct) - rel_err_inf = la.norm(err, np.inf)/la.norm(test_direct, np.inf) + rel_err_2 = la.norm(err)/la.norm(test_direct) + rel_err_inf = la.norm(err, np.inf)/la.norm(test_direct, np.inf) - # }}} + # }}} + + print("rel_err_2: %g rel_err_inf: %g" % (rel_err_2, rel_err_inf)) - print("rel_err_2: %g rel_err_inf: %g" % (rel_err_2, rel_err_inf)) + else: + rel_err_2 = None + rel_err_inf = None # {{{ test gradient - if case.check_gradient: + if case.check_gradient and case.prob_side != "scat": bound_grad_op = bind((qbx, points_target), op.representation( sym.var("u"), @@ -677,14 +699,15 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): print("rel_grad_err_inf: %g" % rel_grad_err_inf) # }}} + # {{{ test tangential derivative - if case.check_tangential_deriv: + if case.check_tangential_deriv and case.prob_side != "scat": bound_t_deriv_op = bind(qbx, op.representation( sym.var("u"), map_potentials=lambda pot: sym.tangential_derivative(2, pot), - qbx_forced_limit=case.loc_sign)) + qbx_forced_limit=loc_sign)) #print(bound_t_deriv_op.code) @@ -724,14 +747,23 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("u", u), ("bc", bc), - ("bdry_normals", bdry_normals), + #("bdry_normals", bdry_normals), ]) from sumpy.visualization import make_field_plotter_from_bbox # noqa from meshmode.mesh.processing import find_bounding_box + vis_grid_spacing = (0.025, 0.025, 0.15)[:qbx.ambient_dim] + if hasattr(case, "vis_grid_spacing"): + vis_grid_spacing = case.vis_grid_spacing + vis_extend_factor = 0.2 + if hasattr(case, "vis_extend_factor"): + vis_grid_spacing = case.vis_grid_spacing + fplot = make_field_plotter_from_bbox( - find_bounding_box(mesh), h=(0.025, 0.025, 0.15)[:qbx.ambient_dim]) + find_bounding_box(mesh), + h=vis_grid_spacing, + extend_factor=3.3) qbx_tgt_tol = qbx.copy(target_association_tolerance=0.15) from pytential.target import PointsTarget @@ -749,118 +781,41 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): ]) raise + from sumpy.kernel import LaplaceKernel + ones_density = density_discr.zeros(queue) + ones_density.fill(1) + indicator = bind( + (qbx_tgt_tol, PointsTarget(fplot.points)), + -sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None))( + queue, sigma=ones_density).get() + solved_pot = solved_pot.get() true_pot = bind((point_source, PointsTarget(fplot.points)), pot_src)( queue, charges=source_charges_dev, **concrete_knl_kwargs).get() #fplot.show_scalar_in_mayavi(solved_pot.real, max_val=5) - fplot.write_vtk_file( - "potential-%s.vts" % resolution, - [ - ("solved_pot", solved_pot), - ("true_pot", true_pot), - ] - ) + if case.prob_side == "scat": + fplot.write_vtk_file( + "potential-%s.vts" % resolution, + [ + ("pot_scattered", solved_pot), + ("pot_incoming", -true_pot), + ("indicator", indicator), + ] + ) + else: + fplot.write_vtk_file( + "potential-%s.vts" % resolution, + [ + ("solved_pot", solved_pot), + ("true_pot", true_pot), + ("indicator", indicator), + ] + ) # }}} - # {{{ 2D plotting - - if 0: - fplot = FieldPlotter(np.zeros(2), - extent=1.25*2*max(test_src_geo_radius, test_tgt_geo_radius), - npoints=200) - - #pt.plot(u) - #pt.show() - - fld_from_src = bind((point_source, PointsTarget(fplot.points)), - pot_src)(queue, charges=source_charges_dev, **concrete_knl_kwargs) - fld_from_bdry = bind( - (qbx, PointsTarget(fplot.points)), - op.representation(sym.var("u")) - )(queue, u=u, k=case.k) - fld_from_src = fld_from_src.get() - fld_from_bdry = fld_from_bdry.get() - - nodes = density_discr.nodes().get(queue=queue) - - def prep(): - pt.plot(point_sources[0], point_sources[1], "o", - label="Monopole 'Point Charges'") - pt.plot(test_targets[0], test_targets[1], "v", - label="Observation Points") - pt.plot(nodes[0], nodes[1], "k-", - label=r"$\Gamma$") - - from matplotlib.cm import get_cmap - cmap = get_cmap() - cmap._init() - if 0: - cmap._lut[(cmap.N*99)//100:, -1] = 0 # make last percent transparent? - - prep() - if 1: - pt.subplot(131) - pt.title("Field error (loc_sign=%s)" % case.loc_sign) - log_err = np.log10(1e-20+np.abs(fld_from_src-fld_from_bdry)) - log_err = np.minimum(-3, log_err) - fplot.show_scalar_in_matplotlib(log_err, cmap=cmap) - - #from matplotlib.colors import Normalize - #im.set_norm(Normalize(vmin=-6, vmax=1)) - - cb = pt.colorbar(shrink=0.9) - cb.set_label(r"$\log_{10}(\mathdefault{Error})$") - - if 1: - pt.subplot(132) - prep() - pt.title("Source Field") - fplot.show_scalar_in_matplotlib( - fld_from_src.real, max_val=3) - - pt.colorbar(shrink=0.9) - if 1: - pt.subplot(133) - prep() - pt.title("Solved Field") - fplot.show_scalar_in_matplotlib( - fld_from_bdry.real, max_val=3) - - pt.colorbar(shrink=0.9) - - # total field - #fplot.show_scalar_in_matplotlib( - #fld_from_src.real+fld_from_bdry.real, max_val=0.1) - - #pt.colorbar() - - pt.legend(loc="best", prop=dict(size=15)) - from matplotlib.ticker import NullFormatter - pt.gca().xaxis.set_major_formatter(NullFormatter()) - pt.gca().yaxis.set_major_formatter(NullFormatter()) - - pt.gca().set_aspect("equal") - - if 0: - border_factor_top = 0.9 - border_factor = 0.3 - - xl, xh = pt.xlim() - xhsize = 0.5*(xh-xl) - pt.xlim(xl-border_factor*xhsize, xh+border_factor*xhsize) - - yl, yh = pt.ylim() - yhsize = 0.5*(yh-yl) - pt.ylim(yl-border_factor_top*yhsize, yh+border_factor*yhsize) - - #pt.savefig("helmholtz.pdf", dpi=600) - pt.show() - - # }}} - class Result(Record): pass @@ -878,10 +833,10 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): @pytest.mark.parametrize("case", [ EllipseIntEqTestCase(helmholtz_k=helmholtz_k, bc_type=bc_type, - loc_sign=loc_sign) + prob_side=prob_side) for helmholtz_k in [0, 1.2] for bc_type in ["dirichlet", "neumann"] - for loc_sign in [-1, +1] + for prob_side in [-1, +1] ]) # Sample test run: # 'test_integral_equation(cl._csc, EllipseIntEqTestCase(0, "dirichlet", +1), visualize=True)' # noqa: E501 @@ -907,11 +862,14 @@ def test_integral_equation(ctx_getter, case, visualize=False): eoc_rec_target = EOCRecorder() eoc_rec_td = EOCRecorder() + have_error_data = False for resolution in case.resolutions: result = run_int_eq_test(cl_ctx, queue, case, resolution, visualize=visualize) - eoc_rec_target.add_data_point(result.h_max, result.rel_err_2) + if result.rel_err_2 is not None: + have_error_data = True + eoc_rec_target.add_data_point(result.h_max, result.rel_err_2) if result.rel_td_err_inf is not None: eoc_rec_td.add_data_point(result.h_max, result.rel_td_err_inf) @@ -923,14 +881,15 @@ def test_integral_equation(ctx_getter, case, visualize=False): else: assert False - print("TARGET ERROR:") - print(eoc_rec_target) - assert eoc_rec_target.order_estimate() > tgt_order - 1.3 + if have_error_data: + print("TARGET ERROR:") + print(eoc_rec_target) + assert eoc_rec_target.order_estimate() > tgt_order - 1.3 - if case.check_tangential_deriv: - print("TANGENTIAL DERIVATIVE ERROR:") - print(eoc_rec_td) - assert eoc_rec_td.order_estimate() > tgt_order - 2.3 + if case.check_tangential_deriv: + print("TANGENTIAL DERIVATIVE ERROR:") + print(eoc_rec_td) + assert eoc_rec_td.order_estimate() > tgt_order - 2.3 # }}} -- GitLab From 22664169e66f726dd5ce819f22bf513ef5d633e5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 4 May 2018 19:07:10 -0500 Subject: [PATCH 118/268] Fix inteq test screwups --- test/test_scalar_int_eq.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index d38fdf48..16fe354d 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -37,7 +37,6 @@ from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh) from meshmode.discretization.visualization import make_visualizer -from sumpy.visualization import FieldPlotter from sumpy.symbolic import USE_SYMENGINE from pytential import bind, sym from pytential.qbx import QBXTargetAssociationFailedException @@ -515,7 +514,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): else: dtype = np.float64 - loc_sign = +1 if case.prob_side in [-1, "scat"] else +1 + loc_sign = +1 if case.prob_side in [+1, "scat"] else -1 if case.bc_type == "dirichlet": op = DirichletOperator(knl, loc_sign, use_l2_weighting=True, @@ -763,7 +762,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): fplot = make_field_plotter_from_bbox( find_bounding_box(mesh), h=vis_grid_spacing, - extend_factor=3.3) + extend_factor=vis_extend_factor) qbx_tgt_tol = qbx.copy(target_association_tolerance=0.15) from pytential.target import PointsTarget -- GitLab From 8e1f95a691b902fc1175bb2ba0a10fd394ba9982 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 5 May 2018 13:51:14 -0500 Subject: [PATCH 119/268] Tweaks/fixes to scalar inteq test --- test/test_scalar_int_eq.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 16fe354d..6d5563dc 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -69,6 +69,9 @@ class IntEqTestCase: :arg prob_side: may be -1, +1, or ``'scat'`` for a scattering problem """ + if helmholtz_k is None: + helmholtz_k = self.default_helmholtz_k + self.helmholtz_k = helmholtz_k self.bc_type = bc_type self.prob_side = prob_side @@ -145,6 +148,7 @@ class EllipsoidIntEqTestCase(Helmholtz3DIntEqTestCase): # Flip elements--gmsh generates inside-out geometry. return perform_flips(mesh, np.ones(mesh.nelements)) + qbx_order = 5 fmm_order = 13 inner_radius = 0.4 @@ -253,7 +257,7 @@ class ManyEllipsoidIntEqTestCase(Helmholtz3DIntEqTestCase): class ElliptiplaneIntEqTestCase(IntEqTestCase): name = "elliptiplane" - resolutions = [0.2] + resolutions = [0.1] fmm_backend = "fmmlib" use_refinement = True @@ -295,6 +299,7 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): class BetterplaneIntEqTestCase(IntEqTestCase): name = "betterplane" + default_helmholtz_k = 10 resolutions = [0.3] fmm_backend = "fmmlib" @@ -315,6 +320,9 @@ class BetterplaneIntEqTestCase(IntEqTestCase): # kidding? gmres_tol = 1e-5 + vis_grid_spacing = (0.03, 0.15, 0.03) + vis_extend_factor = 0.2 + def get_mesh(self, resolution, target_order): from pytools import download_from_web_if_not_present @@ -451,7 +459,10 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): print("%d elements before refinement" % pre_density_discr.mesh.nelements) qbx, _ = qbx.with_refinement(**refiner_extra_kwargs) - print("%d elements after refinement" % qbx.density_discr.mesh.nelements) + print("%d stage-1 elements after refinement" + % qbx.density_discr.mesh.nelements) + print("%d stage-2 elements after refinement" + % qbx.stage2_density_discr.mesh.nelements) density_discr = qbx.density_discr @@ -785,7 +796,9 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): ones_density.fill(1) indicator = bind( (qbx_tgt_tol, PointsTarget(fplot.points)), - -sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None))( + -sym.D(LaplaceKernel(density_discr.ambient_dim), + sym.var("sigma"), + qbx_forced_limit=None))( queue, sigma=ones_density).get() solved_pot = solved_pot.get() -- GitLab From 3e39c614ceb7fa5eb86cbc11e5d7c029a3efdff4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 6 May 2018 17:10:23 -0500 Subject: [PATCH 120/268] Scalar int eq test: remove weighting from density before vis --- test/test_scalar_int_eq.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 6d5563dc..14f44d26 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -299,7 +299,7 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): class BetterplaneIntEqTestCase(IntEqTestCase): name = "betterplane" - default_helmholtz_k = 10 + default_helmholtz_k = 15 resolutions = [0.3] fmm_backend = "fmmlib" @@ -320,7 +320,7 @@ class BetterplaneIntEqTestCase(IntEqTestCase): # kidding? gmres_tol = 1e-5 - vis_grid_spacing = (0.03, 0.15, 0.03) + vis_grid_spacing = (0.04, 0.2, 0.04) vis_extend_factor = 0.2 def get_mesh(self, resolution, target_order): @@ -621,7 +621,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): raise print("gmres state:", gmres_result.state) - u = gmres_result.solution + weighted_u = gmres_result.solution # }}} @@ -649,7 +649,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): bound_tgt_op = bind((qbx, points_target), op.representation(sym.var("u"))) - test_via_bdry = bound_tgt_op(queue, u=u, k=case.k) + test_via_bdry = bound_tgt_op(queue, u=weighted_u, k=case.k) err = test_via_bdry - test_direct @@ -691,7 +691,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): #print(bound_t_deriv_op.code) grad_from_src = bound_grad_op( - queue, u=u, **concrete_knl_kwargs) + queue, u=weighted_u, **concrete_knl_kwargs) grad_ref = (bind( (point_source, points_target), @@ -722,7 +722,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): #print(bound_t_deriv_op.code) tang_deriv_from_src = bound_t_deriv_op( - queue, u=u, **concrete_knl_kwargs).as_scalar().get() + queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get() tang_deriv_ref = (bind( (point_source, density_discr), @@ -754,6 +754,9 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): bdry_normals = bind(density_discr, sym.normal(qbx.ambient_dim))(queue)\ .as_vector(dtype=object) + sym_sqrt_j = sym.sqrt_jac_q_weight(density_discr.ambient_dim) + u = bind(density_discr, sym.var("u")/sym_sqrt_j)(queue, u=weighted_u) + bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("u", u), ("bc", bc), @@ -763,7 +766,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): from sumpy.visualization import make_field_plotter_from_bbox # noqa from meshmode.mesh.processing import find_bounding_box - vis_grid_spacing = (0.025, 0.025, 0.15)[:qbx.ambient_dim] + vis_grid_spacing = (0.1, 0.1, 0.1)[:qbx.ambient_dim] if hasattr(case, "vis_grid_spacing"): vis_grid_spacing = case.vis_grid_spacing vis_extend_factor = 0.2 -- GitLab From ed71af376708decb9c61b5cf7f3a1f96fde6f7f4 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 6 May 2018 20:24:29 -0500 Subject: [PATCH 121/268] Set balancing, norm, and from_sep_smaller_crit defaults for 3D --- pytential/qbx/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index f1f53cd6..d8c00193 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -132,6 +132,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): if _box_extent_norm is None: _box_extent_norm = "l2" + if _from_sep_smaller_crit is None: + _from_sep_smaller_crit = "static_l2" + if fmm_level_to_order is None: if fmm_order is False: fmm_level_to_order = False @@ -141,9 +144,12 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): if _max_leaf_refine_weight is None: if density_discr.ambient_dim == 2: + # FIXME: This should be verified now that l^2 is the default. _max_leaf_refine_weight = 64 elif density_discr.ambient_dim == 3: - _max_leaf_refine_weight = 128 + # For static_linf/linf: https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/8#note_25009 # noqa + # For static_l2/l2: https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/12 # noqa + _max_leaf_refine_weight = 512 else: # Just guessing... _max_leaf_refine_weight = 64 -- GitLab From 3e00c7520d97e50711c843e0b5fe19e8a7bb5400 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 7 May 2018 00:29:20 -0500 Subject: [PATCH 122/268] Switch default from_sep_smaller_crit back to precise_linf --- pytential/qbx/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index d8c00193..ff4c1ade 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -133,7 +133,9 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): _box_extent_norm = "l2" if _from_sep_smaller_crit is None: - _from_sep_smaller_crit = "static_l2" + # This seems to win no matter what the box extent norm is + # https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/10 + _from_sep_smaller_crit = "precise_linf" if fmm_level_to_order is None: if fmm_order is False: -- GitLab From 2aaeb3e0c4040542aa7ce774176497ecc0ec9101 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 7 May 2018 00:34:10 -0500 Subject: [PATCH 123/268] Scalar inteq test: more options, tweaks --- test/test_scalar_int_eq.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 14f44d26..060c663a 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -272,6 +272,10 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): # kidding? gmres_tol = 1e-5 + # to match the scheme given in the GIGAQBX3D paper + box_extent_norm = "l2" + from_sep_smaller_crit = "static_l2" + def get_mesh(self, resolution, target_order): from pytools import download_from_web_if_not_present @@ -299,8 +303,9 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): class BetterplaneIntEqTestCase(IntEqTestCase): name = "betterplane" - default_helmholtz_k = 15 + default_helmholtz_k = 10 resolutions = [0.3] + # refine_on_helmholtz_k = False fmm_backend = "fmmlib" use_refinement = True @@ -437,11 +442,14 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): pre_density_discr, fine_order=source_order, qbx_order=case.qbx_order, + + _box_extent_norm=getattr(case, "box_extent_norm", None), + _from_sep_smaller_crit=getattr(case, "from_sep_smaller_crit", None), _from_sep_smaller_min_nsources_cumul=30, fmm_backend=case.fmm_backend, **qbx_lpot_kwargs) if case.use_refinement: - if case.k != 0: + if case.k != 0 and getattr(case, "refine_on_helmholtz_k", True): refiner_extra_kwargs["kernel_length_scale"] = 5/case.k if hasattr(case, "scaled_max_curvature_threshold"): -- GitLab From 9348bbef76ea5cea854a0d0427b2a94591daee09 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 8 May 2018 11:49:18 -0500 Subject: [PATCH 124/268] Scalar int eq test: use still-weighted u in evaluating the representation --- test/test_scalar_int_eq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 060c663a..5bd8385d 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -793,7 +793,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): solved_pot = bind( (qbx_tgt_tol, PointsTarget(fplot.points)), op.representation(sym.var("u")) - )(queue, u=u, k=case.k) + )(queue, u=weighted_u, k=case.k) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( "failed-targets.vts", -- GitLab From 5e512e06d3591792bb726c63dda6da596951a209 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 10 May 2018 20:07:44 -0500 Subject: [PATCH 125/268] tests: parametrize test_matrix_build for more cases --- test/test_matrix.py | 46 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/test/test_matrix.py b/test/test_matrix.py index 5485c502..689fd3a9 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -40,7 +40,9 @@ from pyopencl.tools import ( # noqa @pytest.mark.skipif(USE_SYMENGINE, reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") -def test_matrix_build(ctx_factory): +@pytest.mark.parametrize(("k", "layer_pot_id"), + [(0, 1), (0, 2), (0, 3)]) +def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -53,8 +55,6 @@ def test_matrix_build(ctx_factory): nelements = 30 curve_f = partial(ellipse, 3) - k = 1 - from sumpy.kernel import LaplaceKernel, HelmholtzKernel if k: knl = HelmholtzKernel(2) @@ -64,22 +64,21 @@ def test_matrix_build(ctx_factory): knl_kwargs = {} from pytools.obj_array import make_obj_array, is_obj_array - - if 1: + if layer_pot_id == 1: + u_sym = sym.var("u") + op = sym.Sp(knl, u_sym, **knl_kwargs) + elif layer_pot_id == 2: u_sym = sym.make_sym_vector("u", 2) u0_sym, u1_sym = u_sym op = make_obj_array([ - sym.Sp(knl, u0_sym, **knl_kwargs) - + sym.D(knl, u1_sym, **knl_kwargs), + sym.Sp(knl, u0_sym, **knl_kwargs) + + sym.D(knl, u1_sym, **knl_kwargs), - sym.S(knl, 0.4*u0_sym, **knl_kwargs) - + 0.3*sym.D(knl, u0_sym, **knl_kwargs) + sym.S(knl, 0.4 * u0_sym, **knl_kwargs) + + 0.3 * sym.D(knl, u0_sym, **knl_kwargs) ]) - elif 0: - u_sym = sym.var("u") - op = sym.Sp(knl, u_sym, **knl_kwargs) - else: + elif layer_pot_id == 3: k0 = 3 k1 = 2.9 beta = 2.5 @@ -97,9 +96,11 @@ def test_matrix_build(ctx_factory): u_sym = pde_op.make_unknown("u") op = pde_op.operator(u_sym) + else: + raise ValueError("Unknown layer_pot_id: {}".format(layer_pot_id)) mesh = make_curve_mesh(curve_f, - np.linspace(0, 1, nelements+1), + np.linspace(0, 1, nelements + 1), target_order) from meshmode.discretization import Discretization @@ -110,35 +111,32 @@ def test_matrix_build(ctx_factory): cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4 * target_order, qbx_order, # Don't use FMM for now fmm_order=False).with_refinement() - density_discr = qbx.density_discr - bound_op = bind(qbx, op) from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym).get() - if 0: + if plot: from sumpy.tools import build_matrix as build_matrix_via_matvec mat2 = build_matrix_via_matvec(bound_op.scipy_op(queue, "u")) + print(la.norm((mat - mat2).real, "fro") / la.norm(mat2.real, "fro"), + la.norm((mat - mat2).imag, "fro") / la.norm(mat2.imag, "fro")) - print( - la.norm((mat-mat2).real, "fro")/la.norm(mat2.real, "fro"), - la.norm((mat-mat2).imag, "fro")/la.norm(mat2.imag, "fro")) import matplotlib.pyplot as pt pt.subplot(121) - pt.imshow(np.log10(np.abs(1e-20+(mat-mat2).real))) + pt.imshow(np.log10(np.abs(1.0e-20 + (mat - mat2).real))) pt.colorbar() pt.subplot(122) - pt.imshow(np.log10(np.abs(1e-20+(mat-mat2).imag))) + pt.imshow(np.log10(np.abs(1.0e-20 + (mat - mat2).imag))) pt.colorbar() pt.show() - if 0: + if plot: import matplotlib.pyplot as pt pt.subplot(121) pt.imshow(mat.real) -- GitLab From 717125cd142924322917d505f06e714f30f11943 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 10 May 2018 20:23:24 -0500 Subject: [PATCH 126/268] tests: fix test_matrix test --- pytential/symbolic/execution.py | 11 ++++------- test/test_matrix.py | 21 ++------------------- 2 files changed, 6 insertions(+), 26 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index c0fe0153..f658a6aa 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -495,18 +495,15 @@ def build_matrix(queue, places, expr, input_exprs, domains=None, from pytools.obj_array import is_obj_array, make_obj_array if not is_obj_array(expr): expr = make_obj_array([expr]) - - from pytential.symbolic.primitives import DEFAULT_SOURCE - domains = _domains_default(len(input_exprs), places, domains, - DEFAULT_SOURCE) - try: - iter(input_exprs) + input_exprs = list(input_exprs) except TypeError: # not iterable, wrap in a list input_exprs = [input_exprs] - input_exprs = list(input_exprs) + from pytential.symbolic.primitives import DEFAULT_SOURCE + domains = _domains_default(len(input_exprs), places, domains, + DEFAULT_SOURCE) nblock_rows = len(expr) nblock_columns = len(input_exprs) diff --git a/test/test_matrix.py b/test/test_matrix.py index 689fd3a9..7c626e57 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -41,7 +41,8 @@ from pyopencl.tools import ( # noqa @pytest.mark.skipif(USE_SYMENGINE, reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") @pytest.mark.parametrize(("k", "layer_pot_id"), - [(0, 1), (0, 2), (0, 3)]) + [(0, 1), (0, 2), + (42, 1), (42, 2)]) def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -78,24 +79,6 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): sym.S(knl, 0.4 * u0_sym, **knl_kwargs) + 0.3 * sym.D(knl, u0_sym, **knl_kwargs) ]) - elif layer_pot_id == 3: - k0 = 3 - k1 = 2.9 - beta = 2.5 - - from pytential.symbolic.pde.scalar import ( # noqa - DielectricSRep2DBoundaryOperator as SRep, - DielectricSDRep2DBoundaryOperator as SDRep) - pde_op = SDRep( - mode="tem", - k_vacuum=1, - interfaces=((0, 1, sym.DEFAULT_SOURCE),), - domain_k_exprs=(k0, k1), - beta=beta, - use_l2_weighting=False) - - u_sym = pde_op.make_unknown("u") - op = pde_op.operator(u_sym) else: raise ValueError("Unknown layer_pot_id: {}".format(layer_pot_id)) -- GitLab From af96eb2609c85b2c528aa7e253fded04fb535969 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 10 May 2018 20:29:24 -0500 Subject: [PATCH 127/268] rename variable to match other tests --- test/test_matrix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_matrix.py b/test/test_matrix.py index 7c626e57..1d04789d 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -43,7 +43,7 @@ from pyopencl.tools import ( # noqa @pytest.mark.parametrize(("k", "layer_pot_id"), [(0, 1), (0, 2), (42, 1), (42, 2)]) -def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): +def test_matrix_build(ctx_factory, k, layer_pot_id, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -104,7 +104,7 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym).get() - if plot: + if visualize: from sumpy.tools import build_matrix as build_matrix_via_matvec mat2 = build_matrix_via_matvec(bound_op.scipy_op(queue, "u")) print(la.norm((mat - mat2).real, "fro") / la.norm(mat2.real, "fro"), @@ -119,7 +119,7 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, plot=False): pt.colorbar() pt.show() - if plot: + if visualize: import matplotlib.pyplot as pt pt.subplot(121) pt.imshow(mat.real) -- GitLab From c4ba49d21c007dbb3a887c0c228846fe5e41db68 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 19 May 2018 07:23:39 -0500 Subject: [PATCH 128/268] Make dd_axis work with vector inputs --- pytential/symbolic/primitives.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 17a17013..fbb277df 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -909,6 +909,13 @@ def dd_axis(axis, ambient_dim, operand): """Return the derivative along (XYZ) axis *axis* (in *ambient_dim*-dimensional space) of *operand*. """ + from pytools.obj_array import is_obj_array, with_object_array_or_scalar + if is_obj_array(operand): + def dd_axis_comp(operand_i): + return dd_axis(axis, ambient_dim, operand_i) + + return with_object_array_or_scalar(dd_axis_comp, operand) + d = Derivative() unit_vector = np.zeros(ambient_dim) -- GitLab From 30255f9f9bad83abf228a9677a0e43c1796cfa2a Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 19 May 2018 07:23:56 -0500 Subject: [PATCH 129/268] Fix sym.div() --- pytential/symbolic/primitives.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index fbb277df..3a297fd1 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1416,7 +1416,9 @@ def n_cross(vec, where=None): 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 curl(vec): -- GitLab From 12b3079421a957137ae090504045af4dc2983adc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sat, 19 May 2018 07:24:38 -0500 Subject: [PATCH 130/268] Test div S(n rho)==-D(rho) --- test/test_layer_pot.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index f81a5e55..3e50aa94 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -389,16 +389,20 @@ def test_perf_data_gathering(ctx_getter, n_arms=5): # {{{ test 3D jump relations -@pytest.mark.parametrize("relation", ["sp", "nxcurls"]) +@pytest.mark.parametrize("relation", ["sp", "nxcurls", "div_s"]) def test_3d_jump_relations(ctx_factory, relation, visualize=False): - #logging.basicConfig(level=logging.INFO) + # logging.basicConfig(level=logging.INFO) pytest.importorskip("pyfmmlib") cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) - target_order = 4 + if relation == "div_s": + target_order = 3 + else: + target_order = 4 + qbx_order = target_order from pytools.convergence import EOCRecorder @@ -469,6 +473,16 @@ def test_3d_jump_relations(ctx_factory, relation, visualize=False): - (sym.Sp(knl, density_sym, qbx_forced_limit="avg") - 0.5*density_sym)) + elif relation == "div_s": + + density = m.cos(2*x) * m.cos(2*y) * m.cos(z) + density_sym = sym.var("density") + + jump_identity_sym = ( + sym.div(sym.S(knl, sym.normal(3).as_vector()*density_sym, + qbx_forced_limit="avg")) + + sym.D(knl, density_sym, qbx_forced_limit="avg")) + else: raise ValueError("unexpected value of 'relation': %s" % relation) -- GitLab From 6c294e765ef65f2ccdbf69b313b568c388d28993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 25 May 2018 18:20:52 +0200 Subject: [PATCH 131/268] Missing word in docstr --- 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 489f97fe..86fdd993 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -165,7 +165,7 @@ class PECChargeCurrentMFIEOperator: def scattered_volume_field(self, Jt, rho, qbx_forced_limit=None): """ - This will return an object of six entries, the first three of which + This will return an object array of six entries, the first three of which represent the electric, and the second three of which represent the magnetic field. This satisfies the time-domain Maxwell's equations as verified by :func:`sumpy.point_calculus.frequency_domain_maxwell`. -- GitLab From 68fc570cda4c70534381ed5645f5d2a500b1180c Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 1 Jun 2018 14:43:57 -0500 Subject: [PATCH 132/268] Mark the sumpy integral identity test as slow. --- test/test_layer_pot_identity.py | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 942b6ed2..4b05c682 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -233,12 +233,12 @@ class DynamicTestCase(object): if (self.geometry.mesh_name == "sphere" and self.k != 0 and self.fmm_backend == "sumpy"): - pytest.skip("both direct eval and generating the FMM kernels " + raise ValueError("both direct eval and generating the FMM kernels " "are too slow") if (self.geometry.mesh_name == "sphere" and self.expr.zero_op_name == "green_grad"): - pytest.skip("does not achieve sufficient precision") + raise ValueError("does not achieve sufficient precision") if self.fmm_backend == "fmmlib": pytest.importorskip("pyfmmlib") @@ -246,22 +246,28 @@ class DynamicTestCase(object): # {{{ integral identity tester + +@pytest.mark.slowtest +@pytest.mark.parametrize("case", [ + DynamicTestCase(SphereGeometry(), GreenExpr(), 0), +]) +def test_identity_convergence_slow(case): + test_identity_convergence(case) + + @pytest.mark.parametrize("case", [ - tc - for geom in [ - StarfishGeometry(), - SphereGeometry(), - ] - for tc in [ - DynamicTestCase(geom, GreenExpr(), 0), - DynamicTestCase(geom, GreenExpr(), 1.2), - DynamicTestCase(geom, GradGreenExpr(), 0), - DynamicTestCase(geom, GradGreenExpr(), 1.2), - DynamicTestCase(geom, ZeroCalderonExpr(), 0), - - DynamicTestCase(geom, GreenExpr(), 0, fmm_backend="fmmlib"), - DynamicTestCase(geom, GreenExpr(), 1.2, fmm_backend="fmmlib"), - ]]) + # 2d + DynamicTestCase(StarfishGeometry(), GreenExpr(), 0), + DynamicTestCase(StarfishGeometry(), GreenExpr(), 1.2), + DynamicTestCase(StarfishGeometry(), GradGreenExpr(), 0), + DynamicTestCase(StarfishGeometry(), GradGreenExpr(), 1.2), + DynamicTestCase(StarfishGeometry(), ZeroCalderonExpr(), 0), + DynamicTestCase(StarfishGeometry(), GreenExpr(), 0, fmm_backend="fmmlib"), + DynamicTestCase(StarfishGeometry(), GreenExpr(), 1.2, fmm_backend="fmmlib"), + # 3d + DynamicTestCase(SphereGeometry(), GreenExpr(), 0, fmm_backend="fmmlib"), + DynamicTestCase(SphereGeometry(), GreenExpr(), 1.2, fmm_backend="fmmlib") +]) def test_identity_convergence(ctx_getter, case, visualize=False): logging.basicConfig(level=logging.INFO) -- GitLab From 8c49300749309ef16bb50b826b65db0145044778 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 1 Jun 2018 14:44:19 -0500 Subject: [PATCH 133/268] Skip slow tests in CI. --- .gitlab-ci.yml | 53 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 750bf6f4..e05717fc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,12 +1,13 @@ -Python 3.5 POCL: +Python 2.7 POCL: script: - - export PY_EXE=python3.5 + - export PY_EXE=python2.7 - export PYOPENCL_TEST=portable + - export PYTEST_ADDOPTS=-k-slowtest - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: - - python3.5 + - python2.7 - pocl - large-node except: @@ -16,6 +17,7 @@ Python 3.6 POCL: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable + - export PYTEST_ADDOPTS=-k-slowtest - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" @@ -26,6 +28,36 @@ Python 3.6 POCL: except: - tags +Python 2.7 POCL Slow: + script: + - export PY_EXE=python2.7 + - export PYOPENCL_TEST=portable + - export PYTEST_ADDOPTS=-kslowtest + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python2.7 + - pocl + - large-node + only: + - schedules + +Python 3.6 POCL Slow: + script: + - export PY_EXE=python3.6 + - export PYOPENCL_TEST=portable + - export PYTEST_ADDOPTS=-kslowtest + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python3.6 + - pocl + - large-node + only: + - schedules + Python 3.6 POCL Examples: script: - export PY_EXE=python3.6 @@ -44,6 +76,7 @@ Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine - CONDA_ENVIRONMENT=.test-conda-env-py3.yml + - export PYTEST_ADDOPTS=-k-slowtest - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" @@ -53,20 +86,6 @@ Python 3.5 Conda: except: - tags -Python 2.7 POCL: - script: - - export PY_EXE=python2.7 - - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako" - - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - - ". ./build-and-test-py-project.sh" - tags: - - python2.7 - - pocl - - large-node - except: - - tags - Python 3.5 Conda Apple: script: - export LC_ALL=en_US.UTF-8 -- GitLab From aadb66dad6b30074c9611f58e7a202594c75ee20 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 1 Jun 2018 22:11:21 -0500 Subject: [PATCH 134/268] Fix TODO relating to strictness of target association test. --- test/test_global_qbx.py | 49 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index e7a4eaa6..d0128819 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -217,13 +217,7 @@ def test_source_refinement_3d(ctx_getter, surface_name, surface_f, order): @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ - # TODO: This used to pass for a 20-to-1 ellipse (with different center - # placement). It still produces valid output right now, but the current - # test is not smart enough to recognize it. It might be useful to fix that. - # - # See discussion at - # https://gitlab.tiker.net/inducer/pytential/merge_requests/95#note_24003 - ("18-to-1 ellipse", partial(ellipse, 18), 100), + ("20-to-1 ellipse", partial(ellipse, 20), 100), ("horseshoe", horseshoe, 64), ]) def test_target_association(ctx_getter, curve_name, curve_f, nelements, @@ -320,6 +314,8 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, expansion_radii = lpot_source._expansion_radii("ncenters").get(queue) + surf_targets = np.array( + [axis.get(queue) for axis in lpot_source.density_discr.nodes()]) int_targets = np.array([axis.get(queue) for axis in int_targets.nodes()]) ext_targets = np.array([axis.get(queue) for axis in ext_targets.nodes()]) @@ -352,48 +348,45 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, if visualize: visualize_curve_and_assoc() - # Checks that the sources match with their own centers. - def check_on_surface_targets(nsources, true_side, target_to_center, - target_to_side_result): - assert (target_to_center >= 0).all() - - sources = np.arange(0, nsources) - - # Centers are on alternating sides of the geometry. Dividing by - # two yields the number of the source that spawned the center. - assert (target_to_center//2 == sources).all() - - assert (target_to_side_result == true_side).all() - # Checks that the targets match with centers on the appropriate side and # within the allowable distance. def check_close_targets(centers, targets, true_side, target_to_center, target_to_side_result, tgt_slice): - assert (target_to_center >= 0).all() + targets_have_centers = (target_to_center >= 0).all() + assert targets_have_centers + assert (target_to_side_result == true_side).all() + + TOL = 1e-3 dists = la.norm((targets.T - centers.T[target_to_center]), axis=1) - assert (dists <= expansion_radii[target_to_center]).all() + assert (dists <= (1 + TOL) * expansion_radii[target_to_center]).all() # Center side order = -1, 1, -1, 1, ... target_to_center_side = 2 * (target_assoc.target_to_center % 2) - 1 - check_on_surface_targets( - nsources, -1, + # interior surface + check_close_targets( + centers, surf_targets, -1, target_assoc.target_to_center[surf_int_slice], - target_to_center_side[surf_int_slice]) + target_to_center_side[surf_int_slice], + surf_int_slice) - check_on_surface_targets( - nsources, +1, + # exterior surface + check_close_targets( + centers, surf_targets, +1, target_assoc.target_to_center[surf_ext_slice], - target_to_center_side[surf_ext_slice]) + target_to_center_side[surf_ext_slice], + surf_ext_slice) + # interior volume check_close_targets( centers, int_targets, -1, target_assoc.target_to_center[vol_int_slice], target_to_center_side[vol_int_slice], vol_int_slice) + # exterior volume check_close_targets( centers, ext_targets, +1, target_assoc.target_to_center[vol_ext_slice], -- GitLab From 0328f366ffd462e7c3d131c2a64c0e8cec1eced6 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 1 Jun 2018 22:12:21 -0500 Subject: [PATCH 135/268] Address a FIXME regarding a sloppy tolerance - the new version of the algorithm meets a strict tolerance. --- test/test_layer_pot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 3e50aa94..b59af654 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -139,8 +139,7 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): pt.colorbar() pt.show() - # FIXME: Why does the FMM only meet this sloppy tolerance? - assert linf_err < 1e-2 + assert linf_err < 1e-14 # }}} -- GitLab From 465b322b593d2b1af3abb8df816e4c28ea3b6190 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 3 Jun 2018 18:39:01 -0500 Subject: [PATCH 136/268] Loosen tolerance, and don't use "is True" for truth testing. --- test/test_layer_pot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index b59af654..66445fee 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -92,7 +92,7 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): nelements = 30 target_order = 8 qbx_order = 3 - if use_fmm is True: + if use_fmm: fmm_order = qbx_order else: fmm_order = False @@ -139,7 +139,7 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): pt.colorbar() pt.show() - assert linf_err < 1e-14 + assert linf_err < 1e-3 # }}} -- GitLab From a9d39ccc006eed1df4d0aaaeee4a405bc34ced89 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 13:47:23 -0500 Subject: [PATCH 137/268] Use PYTEST_ADDOPTS to skip slow tests. --- .gitlab-ci.yml | 53 ++++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e05717fc..6b6cf7a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,14 @@ +# Environment variables +# +# * PYTEST_ADDOPTS is used to filter test runs. The default value is "-k-slowtest", +# which skips the slow running tests. +# * SKIP_EXAMPLES, if non-empty, can be used to skip the examples job. + Python 2.7 POCL: script: - export PY_EXE=python2.7 - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=-k-slowtest + - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" @@ -17,7 +23,7 @@ Python 3.6 POCL: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=-k-slowtest + - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" @@ -28,38 +34,9 @@ Python 3.6 POCL: except: - tags -Python 2.7 POCL Slow: - script: - - export PY_EXE=python2.7 - - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=-kslowtest - - export EXTRA_INSTALL="numpy mako" - - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - - ". ./build-and-test-py-project.sh" - tags: - - python2.7 - - pocl - - large-node - only: - - schedules - -Python 3.6 POCL Slow: - script: - - export PY_EXE=python3.6 - - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=-kslowtest - - export EXTRA_INSTALL="numpy mako" - - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - - ". ./build-and-test-py-project.sh" - tags: - - python3.6 - - pocl - - large-node - only: - - schedules - Python 3.6 POCL Examples: script: + - test -n "$SKIP_EXAMPLES" && exit - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - export EXTRA_INSTALL="numpy mako pyvisfile matplotlib" @@ -75,9 +52,9 @@ Python 3.6 POCL Examples: Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine - - CONDA_ENVIRONMENT=.test-conda-env-py3.yml - - export PYTEST_ADDOPTS=-k-slowtest - - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt + - export CONDA_ENVIRONMENT=.test-conda-env-py3.yml + - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} + - export REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" tags: @@ -90,9 +67,9 @@ Python 3.5 Conda Apple: script: - export LC_ALL=en_US.UTF-8 - export LANG=en_US.UTF-8 - - export PYTEST_ADDOPTS=-k-slowtest - - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml - - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt + - export CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml + - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} + - export REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" tags: -- GitLab From fa4e7e2a4a899afd66636477ad524ed12c6f9b33 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 16:20:34 -0500 Subject: [PATCH 138/268] Move performance model to separate file and add a test. --- pytential/qbx/fmm.py | 393 ----------------------------- pytential/qbx/performance.py | 434 +++++++++++++++++++++++++++++++++ test/test_performance_model.py | 131 ++++++++++ 3 files changed, 565 insertions(+), 393 deletions(-) create mode 100644 pytential/qbx/performance.py create mode 100644 test/test_performance_model.py diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 4be9e5ca..4cfd0c81 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -521,397 +521,4 @@ def drive_fmm(expansion_wrangler, src_weights): # }}} -# {{{ performance data - -def assemble_performance_data(geo_data, uses_pde_expansions, - translation_source_power=None, translation_target_power=None, - translation_max_power=None, - summarize_parallel=None, merge_close_lists=True): - """ - :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM - uses translation operators that make use of the knowledge that the - potential satisfies a PDE. - :arg summarize_parallel: a function of two arguments - *(parallel_array, sym_multipliers)* used to process an array of - workloads of 'parallelizable units'. By default, all workloads are - summed into one number encompassing the total workload. - :arg merge_close_lists: A :class:`bool` indicating whether or not all - boxes requiring direct evaluation should be merged into a single - interaction list. If *False*, *part_direct* and *p2qbxl* will be - suffixed with the originating list as follows: - - * *_neighbor* (List 1) - * *_sep_smaller* (List 3 close) - * *_sep_bigger* (List 4 close). - """ - - # FIXME: This should suport target filtering. - - if summarize_parallel is None: - def summarize_parallel(parallel_array, sym_multipliers): - return np.sum(parallel_array) * sym_multipliers - - from collections import OrderedDict - result = OrderedDict() - - from pymbolic import var - p_fmm = var("p_fmm") - p_qbx = var("p_qbx") - - nqbtl = geo_data.non_qbx_box_target_lists() - - with cl.CommandQueue(geo_data.cl_context) as queue: - tree = geo_data.tree().get(queue=queue) - traversal = geo_data.traversal(merge_close_lists).get(queue=queue) - box_target_counts_nonchild = ( - nqbtl.box_target_counts_nonchild.get(queue=queue)) - - d = tree.dimensions - if uses_pde_expansions: - ncoeffs_fmm = p_fmm ** (d-1) - ncoeffs_qbx = p_qbx ** (d-1) - - if d == 2: - default_translation_source_power = 1 - default_translation_target_power = 1 - default_translation_max_power = 0 - - elif d == 3: - # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. - default_translation_source_power = 0 - default_translation_target_power = 0 - default_translation_max_power = 3 - - else: - raise ValueError("Don't know how to estimate expansion complexities " - "for dimension %d" % d) - - else: - ncoeffs_fmm = p_fmm ** d - ncoeffs_qbx = p_qbx ** d - default_translation_source_power = d - default_translation_target_power = d - - if translation_source_power is None: - translation_source_power = default_translation_source_power - if translation_target_power is None: - translation_target_power = default_translation_target_power - if translation_max_power is None: - translation_max_power = default_translation_max_power - - def xlat_cost(p_source, p_target): - from pymbolic.primitives import Max - return ( - p_source ** translation_source_power - * p_target ** translation_target_power - * Max((p_source, p_target)) ** translation_max_power - ) - - result.update( - nlevels=tree.nlevels, - nboxes=tree.nboxes, - nsources=tree.nsources, - ntargets=tree.ntargets) - - # {{{ construct local multipoles - - result["form_mp"] = tree.nsources*ncoeffs_fmm - - # }}} - - # {{{ propagate multipoles upward - - result["prop_upward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) - - # }}} - - # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) - - def process_direct(): - # box -> nsources * ntargets - npart_direct_list1 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - npart_direct_list3 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - npart_direct_list4 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_boxes): - ntargets = box_target_counts_nonchild[tgt_ibox] - - npart_direct_list1_srcs = 0 - start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] - for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list1_srcs += nsources - - npart_direct_list1[itgt_box] = ntargets * npart_direct_list1_srcs - - if merge_close_lists: - continue - - npart_direct_list3_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_smaller_starts is not None: - start, end = ( - traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list3_srcs += nsources - - npart_direct_list3[itgt_box] = ntargets * npart_direct_list3_srcs - - npart_direct_list4_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_bigger_starts is not None: - start, end = ( - traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list4_srcs += nsources - - npart_direct_list4[itgt_box] = ntargets * npart_direct_list4_srcs - - if merge_close_lists: - result["part_direct"] = summarize_parallel(npart_direct_list1, 1) - else: - result["part_direct_neighbor"] = ( - summarize_parallel(npart_direct_list1, 1)) - result["part_direct_sep_smaller"] = ( - summarize_parallel(npart_direct_list3, 1)) - result["part_direct_sep_bigger"] = ( - summarize_parallel(npart_direct_list4, 1)) - - process_direct() - - # }}} - - # {{{ translate separated siblings' ("list 2") mpoles to local - - def process_list2(): - nm2l = np.zeros(len(traversal.target_or_target_parent_boxes), dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): - start, end = traversal.from_sep_siblings_starts[itgt_box:itgt_box+2] - - nm2l[itgt_box] += end-start - - result["m2l"] = summarize_parallel(nm2l, xlat_cost(p_fmm, p_fmm)) - - process_list2() - - # }}} - - # {{{ evaluate sep. smaller mpoles ("list 3") at particles - - def process_list3(): - nmp_eval = np.zeros( - (tree.nlevels, len(traversal.target_boxes)), - dtype=np.intp) - - assert tree.nlevels == len(traversal.from_sep_smaller_by_level) - - for ilevel, sep_smaller_list in enumerate( - traversal.from_sep_smaller_by_level): - for itgt_box, tgt_ibox in enumerate( - traversal.target_boxes_sep_smaller_by_source_level[ilevel]): - ntargets = box_target_counts_nonchild[tgt_ibox] - start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] - nmp_eval[ilevel, sep_smaller_list.nonempty_indices[itgt_box]] = ( - ntargets * (end-start) - ) - - result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) - - process_list3() - - # }}} - - # {{{ form locals for separated bigger source boxes ("list 4") - - def process_list4(): - nform_local = np.zeros( - len(traversal.target_or_target_parent_boxes), - dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): - start, end = traversal.from_sep_bigger_starts[itgt_box:itgt_box+2] - - nform_local_box = 0 - for src_ibox in traversal.from_sep_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - nform_local_box += nsources - - nform_local[itgt_box] = nform_local_box - - result["form_local"] = summarize_parallel(nform_local, ncoeffs_fmm) - - process_list4() - - # }}} - - # {{{ propagate local_exps downward - - result["prop_downward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) - - # }}} - - # {{{ evaluate locals - - result["eval_part"] = tree.ntargets * ncoeffs_fmm - - # }}} - - # {{{ form global qbx locals - - global_qbx_centers = geo_data.global_qbx_centers() - - # If merge_close_lists is False above, then this builds another traversal - # (which is OK). - qbx_center_to_target_box = geo_data.qbx_center_to_target_box() - center_to_targets_starts = geo_data.center_to_tree_targets().starts - qbx_center_to_target_box_source_level = np.empty( - (tree.nlevels,), dtype=object - ) - - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - geo_data.qbx_center_to_target_box_source_level(src_level) - ) - - with cl.CommandQueue(geo_data.cl_context) as queue: - global_qbx_centers = global_qbx_centers.get( - queue=queue) - qbx_center_to_target_box = qbx_center_to_target_box.get( - queue=queue) - center_to_targets_starts = center_to_targets_starts.get( - queue=queue) - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - qbx_center_to_target_box_source_level[src_level].get(queue=queue) - ) - - def process_form_qbxl(): - ncenters = geo_data.ncenters - - result["ncenters"] = ncenters - - # center -> nsources - np2qbxl_list1 = np.zeros(len(global_qbx_centers), dtype=np.intp) - np2qbxl_list3 = np.zeros(len(global_qbx_centers), dtype=np.intp) - np2qbxl_list4 = np.zeros(len(global_qbx_centers), dtype=np.intp) - - for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - itgt_box = qbx_center_to_target_box[tgt_icenter] - - np2qbxl_list1_srcs = 0 - start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] - for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list1_srcs += nsources - - np2qbxl_list1[itgt_center] = np2qbxl_list1_srcs - - if merge_close_lists: - continue - - np2qbxl_list3_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_smaller_starts is not None: - start, end = ( - traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list3_srcs += nsources - - np2qbxl_list3[itgt_center] = np2qbxl_list3_srcs - - np2qbxl_list4_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_bigger_starts is not None: - start, end = ( - traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list4_srcs += nsources - - np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs - - if merge_close_lists: - result["p2qbxl"] = summarize_parallel(np2qbxl_list1, ncoeffs_qbx) - else: - result["p2qbxl_neighbor"] = ( - summarize_parallel(np2qbxl_list1, ncoeffs_qbx)) - result["p2qbxl_sep_smaller"] = ( - summarize_parallel(np2qbxl_list3, ncoeffs_qbx)) - result["p2qbxl_sep_bigger"] = ( - summarize_parallel(np2qbxl_list4, ncoeffs_qbx)) - - process_form_qbxl() - - # }}} - - # {{{ translate from list 3 multipoles to qbx local expansions - - def process_m2qbxl(): - nm2qbxl = np.zeros( - (tree.nlevels, len(global_qbx_centers)), - dtype=np.intp) - - assert tree.nlevels == len(traversal.from_sep_smaller_by_level) - - for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): - - for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - icontaining_tgt_box = qbx_center_to_target_box_source_level[ - isrc_level][tgt_icenter] - - if icontaining_tgt_box == -1: - continue - - start, stop = ( - ssn.starts[icontaining_tgt_box], - ssn.starts[icontaining_tgt_box+1]) - - nm2qbxl[isrc_level, itgt_center] += stop-start - - result["m2qbxl"] = summarize_parallel(nm2qbxl, xlat_cost(p_fmm, p_qbx)) - - process_m2qbxl() - - # }}} - - # {{{ translate from box local expansions to qbx local expansions - - result["l2qbxl"] = geo_data.ncenters * xlat_cost(p_fmm, p_qbx) - - # }}} - - # {{{ evaluate qbx local expansions - - def process_eval_qbxl(): - nqbx_eval = np.zeros(len(global_qbx_centers), dtype=np.intp) - - for isrc_center, src_icenter in enumerate(global_qbx_centers): - start, end = center_to_targets_starts[src_icenter:src_icenter+2] - nqbx_eval[isrc_center] += end-start - - result["qbxl2p"] = summarize_parallel(nqbx_eval, ncoeffs_qbx) - - process_eval_qbxl() - - # }}} - - return result - -# }}} - # vim: foldmethod=marker diff --git a/pytential/qbx/performance.py b/pytential/qbx/performance.py new file mode 100644 index 00000000..6442a475 --- /dev/null +++ b/pytential/qbx/performance.py @@ -0,0 +1,434 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 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. +""" + + +from six.moves import range +import numpy as np # noqa +import pyopencl as cl # noqa +import pyopencl.array # noqa + + +import logging +logger = logging.getLogger(__name__) + + +__doc__ = """ +.. autofunction:: assemble_performance_data +""" + + +# {{{ assemble_performance_data + +def assemble_performance_data(geo_data, uses_pde_expansions, + translation_source_power=None, translation_target_power=None, + translation_max_power=None, + summarize_parallel=None, merge_close_lists=True): + """ + :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM + uses translation operators that make use of the knowledge that the + potential satisfies a PDE. + :arg summarize_parallel: a function of two arguments + *(parallel_array, sym_multipliers)* used to process an array of + workloads of 'parallelizable units'. By default, all workloads are + summed into one number encompassing the total workload. + :arg merge_close_lists: A :class:`bool` indicating whether or not all + boxes requiring direct evaluation should be merged into a single + interaction list. If *False*, *part_direct* and *p2qbxl* will be + suffixed with the originating list as follows: + + * *_neighbor* (List 1) + * *_sep_smaller* (List 3 close) + * *_sep_bigger* (List 4 close). + """ + + # FIXME: This should suport target filtering. + + if summarize_parallel is None: + def summarize_parallel(parallel_array, sym_multipliers): + return np.sum(parallel_array) * sym_multipliers + + from collections import OrderedDict + result = OrderedDict() + + from pymbolic import var + p_fmm = var("p_fmm") + p_qbx = var("p_qbx") + + nqbtl = geo_data.non_qbx_box_target_lists() + + with cl.CommandQueue(geo_data.cl_context) as queue: + tree = geo_data.tree().get(queue=queue) + traversal = geo_data.traversal(merge_close_lists).get(queue=queue) + box_target_counts_nonchild = ( + nqbtl.box_target_counts_nonchild.get(queue=queue)) + + d = tree.dimensions + if uses_pde_expansions: + ncoeffs_fmm = p_fmm ** (d-1) + ncoeffs_qbx = p_qbx ** (d-1) + + if d == 2: + default_translation_source_power = 1 + default_translation_target_power = 1 + default_translation_max_power = 0 + + elif d == 3: + # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. + default_translation_source_power = 0 + default_translation_target_power = 0 + default_translation_max_power = 3 + + else: + raise ValueError("Don't know how to estimate expansion complexities " + "for dimension %d" % d) + + else: + ncoeffs_fmm = p_fmm ** d + ncoeffs_qbx = p_qbx ** d + default_translation_source_power = d + default_translation_target_power = d + + if translation_source_power is None: + translation_source_power = default_translation_source_power + if translation_target_power is None: + translation_target_power = default_translation_target_power + if translation_max_power is None: + translation_max_power = default_translation_max_power + + def xlat_cost(p_source, p_target): + from pymbolic.primitives import Max + return ( + p_source ** translation_source_power + * p_target ** translation_target_power + * Max((p_source, p_target)) ** translation_max_power + ) + + result.update( + nlevels=tree.nlevels, + nboxes=tree.nboxes, + nsources=tree.nsources, + ntargets=tree.ntargets) + + # {{{ construct local multipoles + + result["form_mp"] = tree.nsources*ncoeffs_fmm + + # }}} + + # {{{ propagate multipoles upward + + result["prop_upward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) + + # }}} + + # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) + + def process_direct(): + # box -> nsources * ntargets + npart_direct_list1 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + npart_direct_list3 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + npart_direct_list4 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_boxes): + ntargets = box_target_counts_nonchild[tgt_ibox] + + npart_direct_list1_srcs = 0 + start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] + for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list1_srcs += nsources + + npart_direct_list1[itgt_box] = ntargets * npart_direct_list1_srcs + + if merge_close_lists: + continue + + npart_direct_list3_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_smaller_starts is not None: + start, end = ( + traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list3_srcs += nsources + + npart_direct_list3[itgt_box] = ntargets * npart_direct_list3_srcs + + npart_direct_list4_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_bigger_starts is not None: + start, end = ( + traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list4_srcs += nsources + + npart_direct_list4[itgt_box] = ntargets * npart_direct_list4_srcs + + if merge_close_lists: + result["part_direct"] = summarize_parallel(npart_direct_list1, 1) + else: + result["part_direct_neighbor"] = ( + summarize_parallel(npart_direct_list1, 1)) + result["part_direct_sep_smaller"] = ( + summarize_parallel(npart_direct_list3, 1)) + result["part_direct_sep_bigger"] = ( + summarize_parallel(npart_direct_list4, 1)) + + process_direct() + + # }}} + + # {{{ translate separated siblings' ("list 2") mpoles to local + + def process_list2(): + nm2l = np.zeros(len(traversal.target_or_target_parent_boxes), dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): + start, end = traversal.from_sep_siblings_starts[itgt_box:itgt_box+2] + + nm2l[itgt_box] += end-start + + result["m2l"] = summarize_parallel(nm2l, xlat_cost(p_fmm, p_fmm)) + + process_list2() + + # }}} + + # {{{ evaluate sep. smaller mpoles ("list 3") at particles + + def process_list3(): + nmp_eval = np.zeros( + (tree.nlevels, len(traversal.target_boxes)), + dtype=np.intp) + + assert tree.nlevels == len(traversal.from_sep_smaller_by_level) + + for ilevel, sep_smaller_list in enumerate( + traversal.from_sep_smaller_by_level): + for itgt_box, tgt_ibox in enumerate( + traversal.target_boxes_sep_smaller_by_source_level[ilevel]): + ntargets = box_target_counts_nonchild[tgt_ibox] + start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] + nmp_eval[ilevel, sep_smaller_list.nonempty_indices[itgt_box]] = ( + ntargets * (end-start) + ) + + result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) + + process_list3() + + # }}} + + # {{{ form locals for separated bigger source boxes ("list 4") + + def process_list4(): + nform_local = np.zeros( + len(traversal.target_or_target_parent_boxes), + dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): + start, end = traversal.from_sep_bigger_starts[itgt_box:itgt_box+2] + + nform_local_box = 0 + for src_ibox in traversal.from_sep_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + nform_local_box += nsources + + nform_local[itgt_box] = nform_local_box + + result["form_local"] = summarize_parallel(nform_local, ncoeffs_fmm) + + process_list4() + + # }}} + + # {{{ propagate local_exps downward + + result["prop_downward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) + + # }}} + + # {{{ evaluate locals + + result["eval_part"] = tree.ntargets * ncoeffs_fmm + + # }}} + + # {{{ form global qbx locals + + global_qbx_centers = geo_data.global_qbx_centers() + + # If merge_close_lists is False above, then this builds another traversal + # (which is OK). + qbx_center_to_target_box = geo_data.qbx_center_to_target_box() + center_to_targets_starts = geo_data.center_to_tree_targets().starts + qbx_center_to_target_box_source_level = np.empty( + (tree.nlevels,), dtype=object + ) + + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + geo_data.qbx_center_to_target_box_source_level(src_level) + ) + + with cl.CommandQueue(geo_data.cl_context) as queue: + global_qbx_centers = global_qbx_centers.get( + queue=queue) + qbx_center_to_target_box = qbx_center_to_target_box.get( + queue=queue) + center_to_targets_starts = center_to_targets_starts.get( + queue=queue) + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + qbx_center_to_target_box_source_level[src_level].get(queue=queue) + ) + + def process_form_qbxl(): + ncenters = geo_data.ncenters + + result["ncenters"] = ncenters + + # center -> nsources + np2qbxl_list1 = np.zeros(len(global_qbx_centers), dtype=np.intp) + np2qbxl_list3 = np.zeros(len(global_qbx_centers), dtype=np.intp) + np2qbxl_list4 = np.zeros(len(global_qbx_centers), dtype=np.intp) + + for itgt_center, tgt_icenter in enumerate(global_qbx_centers): + itgt_box = qbx_center_to_target_box[tgt_icenter] + + np2qbxl_list1_srcs = 0 + start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] + for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list1_srcs += nsources + + np2qbxl_list1[itgt_center] = np2qbxl_list1_srcs + + if merge_close_lists: + continue + + np2qbxl_list3_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_smaller_starts is not None: + start, end = ( + traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list3_srcs += nsources + + np2qbxl_list3[itgt_center] = np2qbxl_list3_srcs + + np2qbxl_list4_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_bigger_starts is not None: + start, end = ( + traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list4_srcs += nsources + + np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs + + if merge_close_lists: + result["p2qbxl"] = summarize_parallel(np2qbxl_list1, ncoeffs_qbx) + else: + result["p2qbxl_neighbor"] = ( + summarize_parallel(np2qbxl_list1, ncoeffs_qbx)) + result["p2qbxl_sep_smaller"] = ( + summarize_parallel(np2qbxl_list3, ncoeffs_qbx)) + result["p2qbxl_sep_bigger"] = ( + summarize_parallel(np2qbxl_list4, ncoeffs_qbx)) + + process_form_qbxl() + + # }}} + + # {{{ translate from list 3 multipoles to qbx local expansions + + def process_m2qbxl(): + nm2qbxl = np.zeros( + (tree.nlevels, len(global_qbx_centers)), + dtype=np.intp) + + assert tree.nlevels == len(traversal.from_sep_smaller_by_level) + + for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): + + for itgt_center, tgt_icenter in enumerate(global_qbx_centers): + icontaining_tgt_box = qbx_center_to_target_box_source_level[ + isrc_level][tgt_icenter] + + if icontaining_tgt_box == -1: + continue + + start, stop = ( + ssn.starts[icontaining_tgt_box], + ssn.starts[icontaining_tgt_box+1]) + + nm2qbxl[isrc_level, itgt_center] += stop-start + + result["m2qbxl"] = summarize_parallel(nm2qbxl, xlat_cost(p_fmm, p_qbx)) + + process_m2qbxl() + + # }}} + + # {{{ translate from box local expansions to qbx local expansions + + result["l2qbxl"] = geo_data.ncenters * xlat_cost(p_fmm, p_qbx) + + # }}} + + # {{{ evaluate qbx local expansions + + def process_eval_qbxl(): + nqbx_eval = np.zeros(len(global_qbx_centers), dtype=np.intp) + + for isrc_center, src_icenter in enumerate(global_qbx_centers): + start, end = center_to_targets_starts[src_icenter:src_icenter+2] + nqbx_eval[isrc_center] += end-start + + result["qbxl2p"] = summarize_parallel(nqbx_eval, ncoeffs_qbx) + + process_eval_qbxl() + + # }}} + + return result + +# }}} + +# vim: foldmethod=marker diff --git a/test/test_performance_model.py b/test/test_performance_model.py new file mode 100644 index 00000000..6658857e --- /dev/null +++ b/test/test_performance_model.py @@ -0,0 +1,131 @@ +from __future__ import division, print_function + +__copyright__ = "Copyright (C) 2018 Matt Wala" + +__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 pytest +from pyopencl.tools import ( # noqa + pytest_generate_tests_for_pyopencl as pytest_generate_tests) + +from pytential import bind, sym, norm # noqa + + +# {{{ global params + +TARGET_ORDER = 8 +OVSMP_FACTOR = 5 +TCF = 0.9 +QBX_ORDER = 5 +FMM_ORDER = 10 + +DEFAULT_LPOT_KWARGS = { + "_box_extent_norm": "l2", + "_from_sep_smaller_crit": "static_l2", + } + +# }}} + + +@pytest.mark.parametrize("dim", (2, 3)) +def test_performance_model(ctx_getter, dim): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + # {{{ get lpot source + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory) + + target_order = TARGET_ORDER + + if dim == 2: + from meshmode.mesh.generation import starfish, make_curve_mesh + mesh = make_curve_mesh(starfish, np.linspace(0, 1, 50), order=target_order) + elif dim == 3: + from meshmode.mesh.generation import generate_icosphere + mesh = generate_icosphere(r=1, order=target_order) + else: + raise ValueError("unknown dimension: %d" % dim) + + pre_density_discr = Discretization( + queue.context, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + + lpot_kwargs = DEFAULT_LPOT_KWARGS.copy() + lpot_kwargs.update( + _expansion_stick_out_factor=TCF, + fmm_order=FMM_ORDER, qbx_order=QBX_ORDER, + ) + + from pytential.qbx import QBXLayerPotentialSource + lpot_source = QBXLayerPotentialSource( + pre_density_discr, OVSMP_FACTOR*target_order, + **lpot_kwargs,) + + lpot_source, _ = lpot_source.with_refinement() + + # }}} + + # {{{ run performance model + + costs = {} + + def inspect_geo_data(insn, bound_expr, geo_data): + from pytential.qbx.performance import assemble_performance_data + costs["costs"] = assemble_performance_data( + geo_data, uses_pde_expansions=True, merge_close_lists=False) + return False + + lpot_source = lpot_source.copy(geometry_data_inspector=inspect_geo_data) + density_discr = lpot_source.density_discr + nodes = density_discr.nodes().with_queue(queue) + sigma = cl.clmath.sin(10 * nodes[0]) + + from sumpy.kernel import LaplaceKernel + sigma_sym = sym.var("sigma") + k_sym = LaplaceKernel(lpot_source.ambient_dim) + sym_op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) + + bound_op = bind(lpot_source, sym_op) + bound_op(queue, sigma=sigma) + + # }}} + + +# You can test individual routines by typing +# $ python test_layer_pot.py 'test_routine()' + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + + +# vim: foldmethod=marker -- GitLab From daadcc6b77e1068c3cac049e8e9968954c28cf58 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 16:56:51 -0500 Subject: [PATCH 139/268] Add a missing argument to test_identity_convergence_slow --- test/test_layer_pot_identity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 4b05c682..e6ec4a9c 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -251,7 +251,7 @@ class DynamicTestCase(object): @pytest.mark.parametrize("case", [ DynamicTestCase(SphereGeometry(), GreenExpr(), 0), ]) -def test_identity_convergence_slow(case): +def test_identity_convergence_slow(ctx_getter, case): test_identity_convergence(case) -- GitLab From 460c2ddd724af1db2f08d364631a1f27cd8a4468 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 18:05:31 -0400 Subject: [PATCH 140/268] [ci skip] Update meshmode description in README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index bababc0a..8b354a93 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ It relies on * `boxtree `_ for FMM tree building * `sumpy `_ for expansions and analytical routines * `modepy `_ for modes and nodes on simplices -* `meshmode `_ for modes and nodes on simplices +* `meshmode `_ for high order discretizations * `loopy `_ for fast array operations * `pytest `_ for automated testing -- GitLab From 1f7f7c398d8f682a2625911ecdfa083d4704840e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 18:13:41 -0400 Subject: [PATCH 141/268] Typo fix --- test/test_performance_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_performance_model.py b/test/test_performance_model.py index 6658857e..fc498121 100644 --- a/test/test_performance_model.py +++ b/test/test_performance_model.py @@ -117,7 +117,7 @@ def test_performance_model(ctx_getter, dim): # You can test individual routines by typing -# $ python test_layer_pot.py 'test_routine()' +# $ python test_performance_model.py 'test_routine()' if __name__ == "__main__": import sys -- GitLab From b2ae8271b0b9384d9e5392cd17ae8237cd87ab51 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 17:16:39 -0500 Subject: [PATCH 142/268] Py2.7 fix --- test/test_performance_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_performance_model.py b/test/test_performance_model.py index fc498121..90a87d1c 100644 --- a/test/test_performance_model.py +++ b/test/test_performance_model.py @@ -78,13 +78,13 @@ def test_performance_model(ctx_getter, dim): lpot_kwargs = DEFAULT_LPOT_KWARGS.copy() lpot_kwargs.update( _expansion_stick_out_factor=TCF, - fmm_order=FMM_ORDER, qbx_order=QBX_ORDER, + fmm_order=FMM_ORDER, qbx_order=QBX_ORDER ) from pytential.qbx import QBXLayerPotentialSource lpot_source = QBXLayerPotentialSource( pre_density_discr, OVSMP_FACTOR*target_order, - **lpot_kwargs,) + **lpot_kwargs) lpot_source, _ = lpot_source.with_refinement() -- GitLab From acfb507a7d7088fee8616b76c90e09fc8f538f37 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 4 Jun 2018 17:24:07 -0500 Subject: [PATCH 143/268] Delete test_perf_data_gathering() --- test/test_layer_pot.py | 71 ------------------------------------------ 1 file changed, 71 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 3e50aa94..8c53d460 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -316,77 +316,6 @@ def test_unregularized_off_surface_fmm_vs_direct(ctx_getter): # }}} -# {{{ test performance data gathering - -def test_perf_data_gathering(ctx_getter, n_arms=5): - cl_ctx = ctx_getter() - queue = cl.CommandQueue(cl_ctx) - - # prevent cache 'splosion - from sympy.core.cache import clear_cache - clear_cache() - - target_order = 8 - - starfish_func = NArmedStarfish(n_arms, 0.8) - mesh = make_curve_mesh( - starfish_func, - np.linspace(0, 1, n_arms * 30), - target_order) - - sigma_sym = sym.var("sigma") - - # The kernel doesn't really matter here - from sumpy.kernel import LaplaceKernel - k_sym = LaplaceKernel(mesh.ambient_dim) - - sym_op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) - - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import ( - InterpolatoryQuadratureSimplexGroupFactory) - pre_density_discr = Discretization( - queue.context, mesh, - InterpolatoryQuadratureSimplexGroupFactory(target_order)) - - results = [] - - def inspect_geo_data(insn, bound_expr, geo_data): - from pytential.qbx.fmm import assemble_performance_data - perf_data = assemble_performance_data(geo_data, uses_pde_expansions=True) - results.append(perf_data) - - return False # no need to do the actual FMM - - from pytential.qbx import QBXLayerPotentialSource - lpot_source = QBXLayerPotentialSource( - pre_density_discr, 4*target_order, - # qbx order and fmm order don't really matter - 10, fmm_order=10, - _expansions_in_tree_have_extent=True, - _expansion_stick_out_factor=0.5, - geometry_data_inspector=inspect_geo_data, - target_association_tolerance=1e-10, - ) - - lpot_source, _ = lpot_source.with_refinement() - - density_discr = lpot_source.density_discr - - if 0: - from meshmode.discretization.visualization import draw_curve - draw_curve(density_discr) - import matplotlib.pyplot as plt - plt.show() - - nodes = density_discr.nodes().with_queue(queue) - sigma = cl.clmath.sin(10 * nodes[0]) - - bind(lpot_source, sym_op)(queue, sigma=sigma) - -# }}} - - # {{{ test 3D jump relations @pytest.mark.parametrize("relation", ["sp", "nxcurls", "div_s"]) -- GitLab From 5d8bda136737b5587799513e3107c34aa4cfbf06 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 5 Jun 2018 20:33:32 -0500 Subject: [PATCH 144/268] Actually fix test_identity_convergence_slow() --- test/test_layer_pot_identity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index e6ec4a9c..49369e2f 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -252,7 +252,7 @@ class DynamicTestCase(object): DynamicTestCase(SphereGeometry(), GreenExpr(), 0), ]) def test_identity_convergence_slow(ctx_getter, case): - test_identity_convergence(case) + test_identity_convergence(ctx_getter, case) @pytest.mark.parametrize("case", [ -- GitLab From 0fc2a08d7a0492d1eb1479839a8b235b2b0ebe53 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 5 Jun 2018 21:11:41 -0500 Subject: [PATCH 145/268] Avoid doing direct eval at a million points. --- test/test_layer_pot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index e851647e..810a50ed 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -183,7 +183,7 @@ def test_off_surface_eval_vs_direct(ctx_getter, do_plot=False): target_association_tolerance=0.05, ).with_refinement() - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500) from pytential.target import PointsTarget ptarget = PointsTarget(fplot.points) from sumpy.kernel import LaplaceKernel -- GitLab From fcc98682fe5e2650c4ff17254cdd1d11da935bb9 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 10 Jun 2018 15:18:23 -0500 Subject: [PATCH 146/268] matrix: flatten resampler and test on refined geometry --- pytential/symbolic/matrix.py | 6 ++++-- test/test_matrix.py | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 84cd46ce..1b1ca5d6 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -198,8 +198,10 @@ class MatrixBuilder(EvaluationMapperBase): waa = source.weights_and_area_elements().get(queue=self.queue) mat[:, :] *= waa - resample_mat = ( - source.resampler.full_resample_matrix(self.queue).get(self.queue)) + from meshmode.discretization.connection import flatten_chained_connection + resampler = flatten_chained_connection(self.queue, source.resampler) + resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) + mat = mat.dot(resample_mat) mat = mat.dot(rec_density) diff --git a/test/test_matrix.py b/test/test_matrix.py index 1d04789d..9f0ff0b4 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -26,9 +26,8 @@ import numpy as np import numpy.linalg as la import pyopencl as cl import pytest -from meshmode.mesh.generation import ( # noqa - ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, - make_curve_mesh) +from meshmode.mesh.generation import \ + ellipse, NArmedStarfish, make_curve_mesh from pytential import bind, sym from functools import partial from sumpy.symbolic import USE_SYMENGINE @@ -40,10 +39,12 @@ from pyopencl.tools import ( # noqa @pytest.mark.skipif(USE_SYMENGINE, reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") -@pytest.mark.parametrize(("k", "layer_pot_id"), - [(0, 1), (0, 2), - (42, 1), (42, 2)]) -def test_matrix_build(ctx_factory, k, layer_pot_id, visualize=False): +@pytest.mark.parametrize("k", [0, 42]) +@pytest.mark.parametrize("curve_f", [ + partial(ellipse, 3), + NArmedStarfish(5, 0.25)]) +@pytest.mark.parametrize("layer_pot_id", [1, 2]) +def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -54,7 +55,6 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, visualize=False): target_order = 7 qbx_order = 4 nelements = 30 - curve_f = partial(ellipse, 3) from sumpy.kernel import LaplaceKernel, HelmholtzKernel if k: @@ -106,7 +106,8 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, visualize=False): if visualize: from sumpy.tools import build_matrix as build_matrix_via_matvec - mat2 = build_matrix_via_matvec(bound_op.scipy_op(queue, "u")) + mat2 = bound_op.scipy_op(queue, "u", dtype=mat.dtype, **knl_kwargs) + mat2 = build_matrix_via_matvec(mat2) print(la.norm((mat - mat2).real, "fro") / la.norm(mat2.real, "fro"), la.norm((mat - mat2).imag, "fro") / la.norm(mat2.imag, "fro")) @@ -135,7 +136,7 @@ def test_matrix_build(ctx_factory, k, layer_pot_id, visualize=False): if is_obj_array(u_sym): u = make_obj_array([ np.random.randn(density_discr.nnodes) - for i in range(len(u_sym)) + for _ in range(len(u_sym)) ]) else: u = np.random.randn(density_discr.nnodes) -- GitLab From cd2b5e2c245916f82390c115a7e070d99b9dea64 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 11 Jun 2018 18:40:32 -0500 Subject: [PATCH 147/268] Refactor performance modeling code. This splits assemble_performance_model() into a PerformanceModel class and TranslationCostModel class. This also breaks the individual processing procedures up into methods. --- pytential/qbx/performance.py | 505 +++++++++++++++++++++++------------ 1 file changed, 335 insertions(+), 170 deletions(-) diff --git a/pytential/qbx/performance.py b/pytential/qbx/performance.py index 6442a475..3d7f2837 100644 --- a/pytential/qbx/performance.py +++ b/pytential/qbx/performance.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2013 Andreas Kloeckner +Copyright (C) 2018 Matt Wala +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -34,117 +37,107 @@ logger = logging.getLogger(__name__) __doc__ = """ +.. autoclass:: PerformanceModel .. autofunction:: assemble_performance_data """ -# {{{ assemble_performance_data +# {{{ translation cost model -def assemble_performance_data(geo_data, uses_pde_expansions, - translation_source_power=None, translation_target_power=None, - translation_max_power=None, - summarize_parallel=None, merge_close_lists=True): - """ - :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM - uses translation operators that make use of the knowledge that the - potential satisfies a PDE. - :arg summarize_parallel: a function of two arguments - *(parallel_array, sym_multipliers)* used to process an array of - workloads of 'parallelizable units'. By default, all workloads are - summed into one number encompassing the total workload. - :arg merge_close_lists: A :class:`bool` indicating whether or not all - boxes requiring direct evaluation should be merged into a single - interaction list. If *False*, *part_direct* and *p2qbxl* will be - suffixed with the originating list as follows: +class TranslationCostModel(object): + """Provides modeled costs for individual translations or evaluations.""" - * *_neighbor* (List 1) - * *_sep_smaller* (List 3 close) - * *_sep_bigger* (List 4 close). - """ + def __init__(self, p_qbx, p_fmm, ncoeffs_qbx, ncoeffs_fmm, + translation_source_power, translation_target_power, + translation_max_power): + self.p_qbx = p_qbx + self.p_fmm = p_fmm + self.ncoeffs_qbx = ncoeffs_qbx + self.ncoeffs_fmm = ncoeffs_fmm + self.translation_source_power = translation_source_power + self.translation_target_power = translation_target_power + self.translation_max_power = translation_max_power - # FIXME: This should suport target filtering. + def direct(self): + return 1 - if summarize_parallel is None: - def summarize_parallel(parallel_array, sym_multipliers): - return np.sum(parallel_array) * sym_multipliers + def p2qbxl(self): + return self.ncoeffs_qbx - from collections import OrderedDict - result = OrderedDict() + qbxl2p = p2qbxl - from pymbolic import var - p_fmm = var("p_fmm") - p_qbx = var("p_qbx") + def p2l(self): + return self.ncoeffs_fmm - nqbtl = geo_data.non_qbx_box_target_lists() + l2p = p2l + p2m = p2l + m2p = p2l - with cl.CommandQueue(geo_data.cl_context) as queue: - tree = geo_data.tree().get(queue=queue) - traversal = geo_data.traversal(merge_close_lists).get(queue=queue) - box_target_counts_nonchild = ( - nqbtl.box_target_counts_nonchild.get(queue=queue)) + def m2m(self): + return self.e2e_cost(self.p_fmm, self.p_fmm) - d = tree.dimensions - if uses_pde_expansions: - ncoeffs_fmm = p_fmm ** (d-1) - ncoeffs_qbx = p_qbx ** (d-1) + l2l = m2m + m2l = m2m - if d == 2: - default_translation_source_power = 1 - default_translation_target_power = 1 - default_translation_max_power = 0 + def m2qbxl(self): + return self.e2e_cost(self.p_fmm, self.p_qbx) - elif d == 3: - # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. - default_translation_source_power = 0 - default_translation_target_power = 0 - default_translation_max_power = 3 + l2qbxl = m2qbxl - else: - raise ValueError("Don't know how to estimate expansion complexities " - "for dimension %d" % d) - - else: - ncoeffs_fmm = p_fmm ** d - ncoeffs_qbx = p_qbx ** d - default_translation_source_power = d - default_translation_target_power = d - - if translation_source_power is None: - translation_source_power = default_translation_source_power - if translation_target_power is None: - translation_target_power = default_translation_target_power - if translation_max_power is None: - translation_max_power = default_translation_max_power - - def xlat_cost(p_source, p_target): + def e2e_cost(self, p_source, p_target): from pymbolic.primitives import Max return ( - p_source ** translation_source_power - * p_target ** translation_target_power - * Max((p_source, p_target)) ** translation_max_power - ) - - result.update( - nlevels=tree.nlevels, - nboxes=tree.nboxes, - nsources=tree.nsources, - ntargets=tree.ntargets) - - # {{{ construct local multipoles - - result["form_mp"] = tree.nsources*ncoeffs_fmm - - # }}} + p_source ** self.translation_source_power + * p_target ** self.translation_target_power + * Max((p_source, p_target)) ** self.translation_max_power) - # {{{ propagate multipoles upward +# }}} - result["prop_upward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) - # }}} +# {{{ performance model + +class PerformanceModel(object): + + def __init__(self, + uses_pde_expansions=True, + translation_source_power=None, + translation_target_power=None, + translation_max_power=None, + summarize_parallel=None, + merge_close_lists=True): + """ + :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM + uses translation operators that make use of the knowledge that the + potential satisfies a PDE. + :arg summarize_parallel: a function of two arguments + *(parallel_array, sym_multipliers)* used to process an array of + workloads of 'parallelizable units'. By default, all workloads are + summed into one number encompassing the total workload. + :arg merge_close_lists: A :class:`bool` indicating whether or not all + boxes requiring direct evaluation should be merged into a single + interaction list. If *False*, *part_direct* and *p2qbxl* will be + suffixed with the originating list as follows: + + * *_neighbor* (List 1) + * *_sep_smaller* (List 3 close) + * *_sep_bigger* (List 4 close). + """ + self.uses_pde_expansions = uses_pde_expansions + self.translation_source_power = translation_source_power + self.translation_target_power = translation_target_power + self.translation_max_power = translation_max_power + if summarize_parallel is None: + summarize_parallel = self.summarize_parallel_default + self.summarize_parallel = summarize_parallel + self.merge_close_lists = merge_close_lists + + @staticmethod + def summarize_parallel_default(parallel_array, sym_multipliers): + return np.sum(parallel_array) * sym_multipliers # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) - def process_direct(): + def process_direct(self, xlat_cost, traversal, tree, box_target_counts_nonchild): # box -> nsources * ntargets npart_direct_list1 = np.zeros(len(traversal.target_boxes), dtype=np.intp) npart_direct_list3 = np.zeros(len(traversal.target_boxes), dtype=np.intp) @@ -162,7 +155,7 @@ def assemble_performance_data(geo_data, uses_pde_expansions, npart_direct_list1[itgt_box] = ntargets * npart_direct_list1_srcs - if merge_close_lists: + if self.merge_close_lists: continue npart_direct_list3_srcs = 0 @@ -191,23 +184,25 @@ def assemble_performance_data(geo_data, uses_pde_expansions, npart_direct_list4[itgt_box] = ntargets * npart_direct_list4_srcs - if merge_close_lists: - result["part_direct"] = summarize_parallel(npart_direct_list1, 1) + result = {} + if self.merge_close_lists: + result["part_direct"] = ( + self.summarize_parallel(npart_direct_list1, xlat_cost.direct())) else: result["part_direct_neighbor"] = ( - summarize_parallel(npart_direct_list1, 1)) + self.summarize_parallel(npart_direct_list1, xlat_cost.direct())) result["part_direct_sep_smaller"] = ( - summarize_parallel(npart_direct_list3, 1)) + self.summarize_parallel(npart_direct_list3, xlat_cost.direct())) result["part_direct_sep_bigger"] = ( - summarize_parallel(npart_direct_list4, 1)) + self.summarize_parallel(npart_direct_list4, xlat_cost.direct())) - process_direct() + return result # }}} # {{{ translate separated siblings' ("list 2") mpoles to local - def process_list2(): + def process_list2(self, xlat_cost, traversal): nm2l = np.zeros(len(traversal.target_or_target_parent_boxes), dtype=np.intp) for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): @@ -215,15 +210,13 @@ def assemble_performance_data(geo_data, uses_pde_expansions, nm2l[itgt_box] += end-start - result["m2l"] = summarize_parallel(nm2l, xlat_cost(p_fmm, p_fmm)) - - process_list2() + return dict(m2l=self.summarize_parallel(nm2l, xlat_cost.m2l())) # }}} # {{{ evaluate sep. smaller mpoles ("list 3") at particles - def process_list3(): + def process_list3(self, xlat_cost, traversal, tree, box_target_counts_nonchild): nmp_eval = np.zeros( (tree.nlevels, len(traversal.target_boxes)), dtype=np.intp) @@ -240,15 +233,14 @@ def assemble_performance_data(geo_data, uses_pde_expansions, ntargets * (end-start) ) - result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) - - process_list3() + return dict( + mp_eval=self.summarize_parallel(nmp_eval, xlat_cost.m2p())) # }}} # {{{ form locals for separated bigger source boxes ("list 4") - def process_list4(): + def process_list4(self, xlat_cost, traversal, tree): nform_local = np.zeros( len(traversal.target_or_target_parent_boxes), dtype=np.intp) @@ -264,55 +256,16 @@ def assemble_performance_data(geo_data, uses_pde_expansions, nform_local[itgt_box] = nform_local_box - result["form_local"] = summarize_parallel(nform_local, ncoeffs_fmm) - - process_list4() - - # }}} - - # {{{ propagate local_exps downward - - result["prop_downward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) - - # }}} - - # {{{ evaluate locals - - result["eval_part"] = tree.ntargets * ncoeffs_fmm + return dict(form_local=( + self.summarize_parallel(nform_local, xlat_cost.p2l()))) # }}} # {{{ form global qbx locals - global_qbx_centers = geo_data.global_qbx_centers() - - # If merge_close_lists is False above, then this builds another traversal - # (which is OK). - qbx_center_to_target_box = geo_data.qbx_center_to_target_box() - center_to_targets_starts = geo_data.center_to_tree_targets().starts - qbx_center_to_target_box_source_level = np.empty( - (tree.nlevels,), dtype=object - ) - - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - geo_data.qbx_center_to_target_box_source_level(src_level) - ) - - with cl.CommandQueue(geo_data.cl_context) as queue: - global_qbx_centers = global_qbx_centers.get( - queue=queue) - qbx_center_to_target_box = qbx_center_to_target_box.get( - queue=queue) - center_to_targets_starts = center_to_targets_starts.get( - queue=queue) - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - qbx_center_to_target_box_source_level[src_level].get(queue=queue) - ) - - def process_form_qbxl(): - ncenters = geo_data.ncenters + def process_form_qbxl(self, xlat_cost, traversal, tree, global_qbx_centers, + qbx_center_to_target_box, ncenters): + result = {} result["ncenters"] = ncenters @@ -333,7 +286,7 @@ def assemble_performance_data(geo_data, uses_pde_expansions, np2qbxl_list1[itgt_center] = np2qbxl_list1_srcs - if merge_close_lists: + if self.merge_close_lists: continue np2qbxl_list3_srcs = 0 @@ -362,23 +315,25 @@ def assemble_performance_data(geo_data, uses_pde_expansions, np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs - if merge_close_lists: - result["p2qbxl"] = summarize_parallel(np2qbxl_list1, ncoeffs_qbx) + if self.merge_close_lists: + result["p2qbxl"] = ( + self.summarize_parallel(np2qbxl_list1, xlat_cost.p2qbxl())) else: result["p2qbxl_neighbor"] = ( - summarize_parallel(np2qbxl_list1, ncoeffs_qbx)) + self.summarize_parallel(np2qbxl_list1, xlat_cost.p2qbxl())) result["p2qbxl_sep_smaller"] = ( - summarize_parallel(np2qbxl_list3, ncoeffs_qbx)) + self.summarize_parallel(np2qbxl_list3, xlat_cost.p2qbxl())) result["p2qbxl_sep_bigger"] = ( - summarize_parallel(np2qbxl_list4, ncoeffs_qbx)) + self.summarize_parallel(np2qbxl_list4, xlat_cost.p2qbxl())) - process_form_qbxl() + return result # }}} # {{{ translate from list 3 multipoles to qbx local expansions - def process_m2qbxl(): + def process_m2qbxl(self, xlat_cost, traversal, tree, global_qbx_centers, + qbx_center_to_target_box_source_level): nm2qbxl = np.zeros( (tree.nlevels, len(global_qbx_centers)), dtype=np.intp) @@ -400,34 +355,244 @@ def assemble_performance_data(geo_data, uses_pde_expansions, nm2qbxl[isrc_level, itgt_center] += stop-start - result["m2qbxl"] = summarize_parallel(nm2qbxl, xlat_cost(p_fmm, p_qbx)) - - process_m2qbxl() - - # }}} - - # {{{ translate from box local expansions to qbx local expansions - - result["l2qbxl"] = geo_data.ncenters * xlat_cost(p_fmm, p_qbx) + return dict(m2qbxl=self.summarize_parallel(nm2qbxl, xlat_cost.m2qbxl())) # }}} # {{{ evaluate qbx local expansions - def process_eval_qbxl(): + def process_eval_qbxl(self, xlat_cost, global_qbx_centers, + center_to_targets_starts): nqbx_eval = np.zeros(len(global_qbx_centers), dtype=np.intp) for isrc_center, src_icenter in enumerate(global_qbx_centers): start, end = center_to_targets_starts[src_icenter:src_icenter+2] nqbx_eval[isrc_center] += end-start - result["qbxl2p"] = summarize_parallel(nqbx_eval, ncoeffs_qbx) - - process_eval_qbxl() + return dict(qbxl2p=self.summarize_parallel(nqbx_eval, xlat_cost.qbxl2p())) # }}} - return result + def __call__(self, geo_data): + # FIXME: This should suport target filtering. + + from collections import OrderedDict + result = OrderedDict() + + nqbtl = geo_data.non_qbx_box_target_lists() + + with cl.CommandQueue(geo_data.cl_context) as queue: + tree = geo_data.tree().get(queue=queue) + traversal = geo_data.traversal(self.merge_close_lists).get(queue=queue) + box_target_counts_nonchild = ( + nqbtl.box_target_counts_nonchild.get(queue=queue)) + + # {{{ set up translation cost model + + from pymbolic import var + p_qbx = var("p_qbx") + p_fmm = var("p_fmm") + + d = tree.dimensions + if self.uses_pde_expansions: + ncoeffs_fmm = p_fmm ** (d-1) + ncoeffs_qbx = p_qbx ** (d-1) + + if d == 2: + default_translation_source_power = 1 + default_translation_target_power = 1 + default_translation_max_power = 0 + + elif d == 3: + # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. + default_translation_source_power = 0 + default_translation_target_power = 0 + default_translation_max_power = 3 + + else: + raise ValueError("Don't know how to estimate expansion complexities " + "for dimension %d" % d) + + else: + ncoeffs_fmm = p_fmm ** d + ncoeffs_qbx = p_qbx ** d + default_translation_source_power = d + default_translation_target_power = d + + translation_source_power = ( + default_translation_source_power + if self.translation_source_power is None + else self.translation_source_power) + + translation_target_power = ( + default_translation_target_power + if self.translation_target_power is None + else self.translation_target_power) + + translation_max_power = ( + default_translation_max_power + if self.translation_max_power is None + else self.translation_max_power) + + xlat_cost = TranslationCostModel( + p_qbx=p_qbx, + p_fmm=p_fmm, + ncoeffs_qbx=ncoeffs_qbx, + ncoeffs_fmm=ncoeffs_fmm, + translation_source_power=translation_source_power, + translation_target_power=translation_target_power, + translation_max_power=translation_max_power) + + # }}} + + result.update( + nlevels=tree.nlevels, + nboxes=tree.nboxes, + nsources=tree.nsources, + ntargets=tree.ntargets) + + # {{{ construct local multipoles + + result["form_mp"] = tree.nsources * xlat_cost.p2m() + + # }}} + + # {{{ propagate multipoles upward + + result["prop_upward"] = tree.nboxes * xlat_cost.m2m() + + # }}} + + # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) + + result.update(self.process_direct( + xlat_cost, traversal, tree, box_target_counts_nonchild)) + + # }}} + + # {{{ translate separated siblings' ("list 2") mpoles to local + + result.update(self.process_list2(xlat_cost, traversal)) + + # }}} + + # {{{ evaluate sep. smaller mpoles ("list 3") at particles + + result.update(self.process_list3( + xlat_cost, traversal, tree, box_target_counts_nonchild)) + + # }}} + + # {{{ form locals for separated bigger source boxes ("list 4") + + result.update(self.process_list4(xlat_cost, traversal, tree)) + + # }}} + + # {{{ propagate local_exps downward + + result["prop_downward"] = tree.nboxes * xlat_cost.l2l() + + # }}} + + # {{{ evaluate locals + + result["eval_part"] = tree.ntargets * xlat_cost.l2p() + + # }}} + + global_qbx_centers = geo_data.global_qbx_centers() + + # If merge_close_lists is False above, then this builds another traversal + # (which is OK). + qbx_center_to_target_box = geo_data.qbx_center_to_target_box() + center_to_targets_starts = geo_data.center_to_tree_targets().starts + qbx_center_to_target_box_source_level = np.empty( + (tree.nlevels,), dtype=object + ) + + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + geo_data.qbx_center_to_target_box_source_level(src_level) + ) + + with cl.CommandQueue(geo_data.cl_context) as queue: + global_qbx_centers = global_qbx_centers.get( + queue=queue) + qbx_center_to_target_box = qbx_center_to_target_box.get( + queue=queue) + center_to_targets_starts = center_to_targets_starts.get( + queue=queue) + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + qbx_center_to_target_box_source_level[src_level].get(queue=queue) + ) + + # {{{ form global qbx locals + + result.update(self.process_form_qbxl( + xlat_cost, traversal, tree, global_qbx_centers, + qbx_center_to_target_box, geo_data.ncenters)) + + # }}} + + # {{{ translate from list 3 multipoles to qbx local expansions + + result.update(self.process_m2qbxl( + xlat_cost, traversal, tree, global_qbx_centers, + qbx_center_to_target_box_source_level)) + + # }}} + + # {{{ translate from box local expansions to qbx local expansions + + result["l2qbxl"] = geo_data.ncenters * xlat_cost.l2qbxl() + + # }}} + + # {{{ evaluate qbx local expansions + + result.update(self.process_eval_qbxl( + xlat_cost, global_qbx_centers, center_to_targets_starts)) + + # }}} + + return result + +# }}} + + +# {{{ assemble_performance_data + +def assemble_performance_data(geo_data, uses_pde_expansions, + translation_source_power=None, translation_target_power=None, + translation_max_power=None, + summarize_parallel=None, merge_close_lists=True): + """ + :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM + uses translation operators that make use of the knowledge that the + potential satisfies a PDE. + :arg summarize_parallel: a function of two arguments + *(parallel_array, sym_multipliers)* used to process an array of + workloads of 'parallelizable units'. By default, all workloads are + summed into one number encompassing the total workload. + :arg merge_close_lists: A :class:`bool` indicating whether or not all + boxes requiring direct evaluation should be merged into a single + interaction list. If *False*, *part_direct* and *p2qbxl* will be + suffixed with the originating list as follows: + + * *_neighbor* (List 1) + * *_sep_smaller* (List 3 close) + * *_sep_bigger* (List 4 close). + """ + + return PerformanceModel( + uses_pde_expansions, + translation_source_power, + translation_target_power, + translation_max_power, + summarize_parallel, + merge_close_lists)(geo_data) # }}} -- GitLab From d544f7f026be5d03973b5652203a181182514d5e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 11 Jun 2018 20:55:18 -0500 Subject: [PATCH 148/268] Various cosmetic fixes --- pytential/qbx/performance.py | 42 +++++++++++------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/pytential/qbx/performance.py b/pytential/qbx/performance.py index 3d7f2837..50ebdff1 100644 --- a/pytential/qbx/performance.py +++ b/pytential/qbx/performance.py @@ -264,10 +264,7 @@ class PerformanceModel(object): # {{{ form global qbx locals def process_form_qbxl(self, xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box, ncenters): - result = {} - - result["ncenters"] = ncenters + qbx_center_to_target_box): # center -> nsources np2qbxl_list1 = np.zeros(len(global_qbx_centers), dtype=np.intp) @@ -315,6 +312,7 @@ class PerformanceModel(object): np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs + result = {} if self.merge_close_lists: result["p2qbxl"] = ( self.summarize_parallel(np2qbxl_list1, xlat_cost.p2qbxl())) @@ -449,7 +447,8 @@ class PerformanceModel(object): nlevels=tree.nlevels, nboxes=tree.nboxes, nsources=tree.nsources, - ntargets=tree.ntargets) + ntargets=tree.ntargets, + ncenters=geo_data.ncenters) # {{{ construct local multipoles @@ -503,18 +502,16 @@ class PerformanceModel(object): global_qbx_centers = geo_data.global_qbx_centers() - # If merge_close_lists is False above, then this builds another traversal + # If self.merge_close_lists is False, then this builds another traversal # (which is OK). qbx_center_to_target_box = geo_data.qbx_center_to_target_box() center_to_targets_starts = geo_data.center_to_tree_targets().starts qbx_center_to_target_box_source_level = np.empty( - (tree.nlevels,), dtype=object - ) + (tree.nlevels,), dtype=object) for src_level in range(tree.nlevels): qbx_center_to_target_box_source_level[src_level] = ( - geo_data.qbx_center_to_target_box_source_level(src_level) - ) + geo_data.qbx_center_to_target_box_source_level(src_level)) with cl.CommandQueue(geo_data.cl_context) as queue: global_qbx_centers = global_qbx_centers.get( @@ -525,14 +522,14 @@ class PerformanceModel(object): queue=queue) for src_level in range(tree.nlevels): qbx_center_to_target_box_source_level[src_level] = ( - qbx_center_to_target_box_source_level[src_level].get(queue=queue) - ) + qbx_center_to_target_box_source_level[src_level] + .get(queue=queue)) # {{{ form global qbx locals result.update(self.process_form_qbxl( xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box, geo_data.ncenters)) + qbx_center_to_target_box)) # }}} @@ -568,22 +565,9 @@ def assemble_performance_data(geo_data, uses_pde_expansions, translation_source_power=None, translation_target_power=None, translation_max_power=None, summarize_parallel=None, merge_close_lists=True): - """ - :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM - uses translation operators that make use of the knowledge that the - potential satisfies a PDE. - :arg summarize_parallel: a function of two arguments - *(parallel_array, sym_multipliers)* used to process an array of - workloads of 'parallelizable units'. By default, all workloads are - summed into one number encompassing the total workload. - :arg merge_close_lists: A :class:`bool` indicating whether or not all - boxes requiring direct evaluation should be merged into a single - interaction list. If *False*, *part_direct* and *p2qbxl* will be - suffixed with the originating list as follows: - - * *_neighbor* (List 1) - * *_sep_smaller* (List 3 close) - * *_sep_bigger* (List 4 close). + """Compute modeled performance using :class:`PerformanceModel`. + + See :class:`PerformanceModel` for parameter documentation. """ return PerformanceModel( -- GitLab From 99ce2f715402e382fb58aa53d7d9b00c1020b75a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 11 Jun 2018 21:01:01 -0500 Subject: [PATCH 149/268] More method refactoring --- pytential/qbx/performance.py | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/pytential/qbx/performance.py b/pytential/qbx/performance.py index 50ebdff1..424fbed5 100644 --- a/pytential/qbx/performance.py +++ b/pytential/qbx/performance.py @@ -371,27 +371,13 @@ class PerformanceModel(object): # }}} - def __call__(self, geo_data): - # FIXME: This should suport target filtering. - - from collections import OrderedDict - result = OrderedDict() - - nqbtl = geo_data.non_qbx_box_target_lists() - - with cl.CommandQueue(geo_data.cl_context) as queue: - tree = geo_data.tree().get(queue=queue) - traversal = geo_data.traversal(self.merge_close_lists).get(queue=queue) - box_target_counts_nonchild = ( - nqbtl.box_target_counts_nonchild.get(queue=queue)) - - # {{{ set up translation cost model + # {{{ set up translation cost model + def get_translation_cost_model(self, d): from pymbolic import var p_qbx = var("p_qbx") p_fmm = var("p_fmm") - d = tree.dimensions if self.uses_pde_expansions: ncoeffs_fmm = p_fmm ** (d-1) ncoeffs_qbx = p_qbx ** (d-1) @@ -432,7 +418,7 @@ class PerformanceModel(object): if self.translation_max_power is None else self.translation_max_power) - xlat_cost = TranslationCostModel( + return TranslationCostModel( p_qbx=p_qbx, p_fmm=p_fmm, ncoeffs_qbx=ncoeffs_qbx, @@ -441,7 +427,21 @@ class PerformanceModel(object): translation_target_power=translation_target_power, translation_max_power=translation_max_power) - # }}} + # }}} + + def __call__(self, geo_data): + # FIXME: This should suport target filtering. + + from collections import OrderedDict + result = OrderedDict() + + nqbtl = geo_data.non_qbx_box_target_lists() + + with cl.CommandQueue(geo_data.cl_context) as queue: + tree = geo_data.tree().get(queue=queue) + traversal = geo_data.traversal(self.merge_close_lists).get(queue=queue) + box_target_counts_nonchild = ( + nqbtl.box_target_counts_nonchild.get(queue=queue)) result.update( nlevels=tree.nlevels, @@ -450,6 +450,8 @@ class PerformanceModel(object): ntargets=tree.ntargets, ncenters=geo_data.ncenters) + xlat_cost = self.get_translation_cost_model(tree.dimensions) + # {{{ construct local multipoles result["form_mp"] = tree.nsources * xlat_cost.p2m() -- GitLab From 41a3ad84d683c6c53d5a883306b2c3e83042331e Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 13 Jun 2018 16:43:19 -0500 Subject: [PATCH 150/268] matrix: move flattening the resampler to QBXLayerPotentialSource --- pytential/qbx/__init__.py | 23 +++++++++++++++++++++++ pytential/symbolic/matrix.py | 3 +-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index ff4c1ade..c86d78fa 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -375,6 +375,29 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): return conn + @property + @memoize_method + def direct_resampler(self): + """ + .. warning:: + + This always returns a + :class:`~meshmode.discretization.connection.DirectDiscretizationConnect`. + In case the geometry has been refined multiple times, a direct + connection can have a large number of groups and/or + interpolation batches, making the single connection scale + significantly worse than the one returned by :attr:`resampler`. + + """ + from meshmode.discretization.connection import \ + flatten_chained_connection + + conn = self.resampler + with cl.CommandQueue(self.cl_context) as queue: + conn = flatten_chained_connection(queue, conn) + + return conn + @property @memoize_method def tree_code_container(self): diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 1b1ca5d6..f0b1d82f 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -198,8 +198,7 @@ class MatrixBuilder(EvaluationMapperBase): waa = source.weights_and_area_elements().get(queue=self.queue) mat[:, :] *= waa - from meshmode.discretization.connection import flatten_chained_connection - resampler = flatten_chained_connection(self.queue, source.resampler) + resampler = source.direct_resampler resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) mat = mat.dot(resample_mat) -- GitLab From 4537381ac4e324fa7a146a5066a9c8e423b07bea Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 13 Jun 2018 16:49:02 -0500 Subject: [PATCH 151/268] qbx: better phrasing --- pytential/qbx/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index c86d78fa..d3cd843a 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -385,9 +385,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): :class:`~meshmode.discretization.connection.DirectDiscretizationConnect`. In case the geometry has been refined multiple times, a direct connection can have a large number of groups and/or - interpolation batches, making the single connection scale - significantly worse than the one returned by :attr:`resampler`. - + interpolation batches, making it scale significantly worse than + the one returned by :attr:`resampler`. """ from meshmode.discretization.connection import \ flatten_chained_connection -- GitLab From 773f439b2e5f45b7fe5d3c3a698b955313550e47 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 17 Jun 2018 14:11:47 -0500 Subject: [PATCH 152/268] direct-solver: add proxy point generator class --- pytential/direct_solver.py | 515 +++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 pytential/direct_solver.py diff --git a/pytential/direct_solver.py b/pytential/direct_solver.py new file mode 100644 index 00000000..72a7729e --- /dev/null +++ b/pytential/direct_solver.py @@ -0,0 +1,515 @@ +from __future__ import division, absolute_import + +__copyright__ = "Copyright (C) 2018 Alexandru Fikl" + +__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 pyopencl as cl +import pyopencl.array # noqa + +from pytools import memoize_method, memoize_in + +import loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION + + +# {{{ point index partitioning + +def _element_node_range(groups, igroup, ielement): + istart = groups[igroup].node_nr_base + \ + groups[igroup].nunit_nodes * ielement + iend = groups[igroup].node_nr_base + \ + groups[igroup].nunit_nodes * (ielement + 1) + return np.arange(istart, iend) + + +def partition_by_nodes(discr, use_tree=True, max_particles_in_box=30): + if use_tree: + from boxtree import box_flags_enum + from boxtree import TreeBuilder + + with cl.CommandQueue(discr.cl_context) as queue: + builder = TreeBuilder(discr.cl_context) + + tree, _ = builder(queue, discr.nodes(), + max_particles_in_box=max_particles_in_box) + + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() + + indices = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] + indices[i] = tree.user_source_ids[box_start:box_end] + + ranges = np.cumsum([0] + [box.shape[0] for box in indices]) + indices = np.hstack(indices) + else: + indices = np.arange(0, discr.nnodes) + ranges = np.arange(0, discr.nnodes + 1, + discr.nnodes // max_particles_in_box) + + assert ranges[-1] == discr.nnodes + return indices, ranges + + +def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): + if use_tree: + from boxtree import box_flags_enum + from boxtree import TreeBuilder + + with cl.CommandQueue(discr.cl_context) as queue: + builder = TreeBuilder(discr.cl_context) + + from pytential.qbx.utils import element_centers_of_mass + elranges = np.cumsum([0] + + [group.nelements for group in discr.mesh.groups]) + elcenters = element_centers_of_mass(discr) + + tree, _ = builder(queue, elcenters, + max_particles_in_box=max_particles_in_box) + + groups = discr.groups + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() + + indices = np.empty(len(leaf_boxes), dtype=np.object) + elements = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] + + ielement = tree.user_source_ids[box_start:box_end] + igroup = np.digitize(ielement, elranges) - 1 + + indices[i] = np.hstack([_element_node_range(groups, j, k) + for j, k in zip(igroup, ielement)]) + else: + groups = discr.groups + elranges = np.cumsum([0] + + [group.nelements for group in discr.mesh.groups]) + + nelements = discr.mesh.nelements + indices = np.array_split(np.arange(0, nelements), + nelements // max_particles_in_box) + for i in range(len(indices)): + ielement = indices[i] + igroup = np.digitize(ielement, elranges) - 1 + + indices[i] = np.hstack([_element_node_range(groups, j, k) + for j, k in zip(igroup, ielement)]) + + ranges = np.cumsum([0] + [box.shape[0] for box in indices]) + indices = np.hstack(indices) + + return indices, ranges + + +def partition_from_coarse(queue, resampler, from_indices, from_ranges): + if not hasattr(resampler, "groups"): + raise ValueError("resampler must be a DirectDiscretizationConnection.") + + # construct ranges + from_discr = resampler.from_discr + from_grp_ranges = np.cumsum([0] + + [grp.nelements for grp in from_discr.mesh.groups]) + from_el_ranges = np.hstack([ + np.arange(grp.node_nr_base, grp.nnodes + 1, grp.nunit_nodes) + for grp in from_discr.groups]) + + # construct coarse element arrays in each from_range + el_indices = np.empty(from_ranges.shape[0] - 1, dtype=np.object) + el_ranges = np.full(from_grp_ranges[-1], -1, dtype=np.int) + for i in range(from_ranges.shape[0] - 1): + irange = np.s_[from_ranges[i]:from_ranges[i + 1]] + el_indices[i] = \ + np.unique(np.digitize(from_indices[irange], from_el_ranges)) - 1 + el_ranges[el_indices[i]] = i + el_indices = np.hstack(el_indices) + + # construct lookup table + to_el_table = [np.full(g.nelements, -1, dtype=np.int) + for g in resampler.to_discr.groups] + + for igrp, grp in enumerate(resampler.groups): + for batch in grp.batches: + to_el_table[igrp][batch.to_element_indices.get(queue)] = \ + from_grp_ranges[igrp] + batch.from_element_indices.get(queue) + + # construct fine node index list + indices = [np.empty(0, dtype=np.int) + for _ in range(from_ranges.shape[0] - 1)] + for igrp in range(len(resampler.groups)): + to_element_indices = \ + np.where(np.isin(to_el_table[igrp], el_indices))[0] + + for i, j in zip(el_ranges[to_el_table[igrp][to_element_indices]], + to_element_indices): + indices[i] = np.hstack([indices[i], + _element_node_range(resampler.to_discr.groups, igrp, j)]) + + ranges = np.cumsum([0] + [box.shape[0] for box in indices]) + indices = np.hstack(indices) + + return indices, ranges + +# }}} + + +# {{{ proxy point generator + +class ProxyGenerator(object): + def __init__(self, queue, places, + ratio=1.5, nproxy=31, target_order=0): + r""" + :arg queue: a :class:`pyopencl.CommandQueue` used to synchronize the + calculations. + :arg places: commonly a :class:`pytential.qbx.LayerPotentialSourceBase`. + :arg ratio: a ratio used to compute the proxy point radius. For proxy + points, we have two radii of interest for a set of points: the + radius :math:`r_{block}` of the smallest ball containing all the + points in a block and the radius :math:`r_{qbx}` of the smallest + ball containing all the QBX expansion balls in the block. If the + ratio :math:`\theta \in [0, 1]`, then the radius of the proxy + ball is computed as: + + .. math:: + + r = (1 - \theta) r_{block} + \theta r_{qbx}. + + If the ratio :math:`\theta > 1`, the the radius is simply: + + .. math:: + + r = \theta r_{qbx}. + + :arg nproxy: number of proxy points for each block. + :arg target_order: order of the discretization of proxy points. Can + be quite small, since proxy points are evaluated point-to-point + at the moment. + """ + + self.queue = queue + self.places = places + self.context = self.queue.context + self.ratio = abs(ratio) + self.nproxy = int(abs(nproxy)) + self.target_order = target_order + self.dim = self.places.ambient_dim + + if self.dim == 2: + from meshmode.mesh.generation import ellipse, make_curve_mesh + + self.mesh = make_curve_mesh(lambda t: ellipse(1.0, t), + np.linspace(0.0, 1.0, self.nproxy + 1), + self.nproxy) + elif self.dim == 3: + from meshmode.mesh.generation import generate_icosphere + + self.mesh = generate_icosphere(1, self.nproxy) + else: + raise ValueError("unsupported ambient dimension") + + @memoize_method + def get_kernel(self): + if self.ratio < 1.0: + radius_expr = "(1.0 - ratio) * rblk + ratio * rqbx" + else: + radius_expr = "ratio * rqbx" + + knl = lp.make_kernel([ + "{[irange]: 0 <= irange < nranges}", + "{[iblk]: 0 <= iblk < nblock}", + "{[idim]: 0 <= idim < dim}" + ], + [""" + for irange + <> blk_start = ranges[irange] + <> blk_end = ranges[irange + 1] + <> nblock = blk_end - blk_start + + proxy_center[idim, irange] = 1.0 / blk_length * \ + reduce(sum, iblk, nodes[idim, indices[blk_start + iblk]]) \ + {{dup=idim:iblk}} + + <> rblk = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + (proxy_center[idim, irange] - + nodes[idim, indices[blk_start + iblk]]) ** 2))) + <> rqbx_int = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + (proxy_center[idim, irange] - + center_int[idim, indices[blk_start + iblk]]) ** 2)) + \ + expansion_radii[indices[blk_start + iblk]]) + <> rqbx_ext = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + (proxy_center[idim, irange] - + center_ext[idim, indices[blk_start + iblk]]) ** 2)) + \ + expansion_radii[indices[blk_start + iblk]]) + + <> rqbx = if(rqbx_ext < rqbx_int, rqbx_int, rqbx_ext) + proxy_radius[irange] = {radius_expr} + end + """.format(radius_expr=radius_expr)], + [ + lp.GlobalArg("nodes", None, + shape=(self.dim, "nnodes")), + lp.GlobalArg("center_int", None, + shape=(self.dim, "nnodes"), dim_tags="sep,C"), + lp.GlobalArg("center_ext", None, + shape=(self.dim, "nnodes"), dim_tags="sep,C"), + lp.GlobalArg("expansion_radii", None, + shape="nnodes"), + lp.GlobalArg("ranges", None, + shape="nranges + 1"), + lp.GlobalArg("indices", None, + shape="nindices"), + lp.GlobalArg("proxy_center", None, + shape=(self.dim, "nranges")), + lp.GlobalArg("proxy_radius", None, + shape="nranges"), + lp.ValueArg("nnodes", np.int64), + lp.ValueArg("nranges", None), + lp.ValueArg("nindices", np.int64) + ], + name="proxy_kernel", + assumptions="dim>=1 and nranges>=1", + fixed_parameters=dict( + dim=self.dim, + ratio=self.ratio), + lang_version=MOST_RECENT_LANGUAGE_VERSION) + + knl = lp.tag_inames(knl, "idim*:unr") + + return knl + + @memoize_method + def get_optimized_kernel(self): + knl = self.get_kernel() + knl = lp.split_iname(knl, "irange", 128, outer_tag="g.0") + + return knl + + def get_proxies(self, indices, ranges, **kwargs): + """ + :arg indices: a set of indices around which to construct proxy balls. + :arg ranges: an array of size `nranges + 1` used to index into the + set of indices. Each one of the `nranges` ranges will get a proxy + ball of a predefined size. + + :returns: `centers` is a list of centers for each proxy ball. + :returns: `radii` is a list of radii for each proxy ball. + :returns: `proxies` is a set of proxy points. + :returns: `pxyranges` is an array used to index into the list of + proxy points. + """ + + from pytential.qbx.utils import get_centers_on_side + + knl = self.get_kernel() + _, (centers_dev, radii_dev,) = knl(self.queue, + nodes=self.places.density_discr.nodes(), + center_int=get_centers_on_side(self.places, -1), + center_ext=get_centers_on_side(self.places, +1), + expansion_radii=self.places._expansion_radii("nsources"), + indices=indices, ranges=ranges, **kwargs) + centers = centers_dev.get() + radii = radii_dev.get() + + from meshmode.mesh.processing import affine_map + proxies = np.empty(ranges.shape[0] - 1, dtype=np.object) + + for i in range(ranges.shape[0] - 1): + mesh = affine_map(self.mesh, + A=(radii[i] * np.eye(self.dim)), + b=centers[:, i].reshape(-1)) + proxies[i] = mesh.vertices + + pxyranges = np.cumsum([0] + [p.shape[-1] for p in proxies]) + proxies = make_obj_array([ + cl.array.to_device(self.queue, np.hstack([p[idim] for p in proxies])) + for idim in range(self.dim)]) + centers_dev = make_obj_array([ + centers_dev[idim].with_queue(self.queue).copy() + for idim in range(self.dim)]) + + return centers_dev, radii_dev, proxies, pxyranges + + def get_neighbors(self, indices, ranges, centers, radii): + """ + :arg indices: indices for a subset of discretization nodes. + :arg ranges: array used to index into the :attr:`indices` array. + :arg centers: centers for each proxy ball. + :arg radii: radii for each proxy ball. + + :returns: `neighbors` is a set of indices into the full set of + discretization nodes (already mapped through the given set `indices`). + It contains all nodes that are inside a given proxy ball :math:`i`, + but not contained in the range :math:`i`, given by + `indices[ranges[i]:ranges[i + 1]]`. + :returns: `nbrranges` is an array used to index into the set of + neighbors and return the subset for a given proxy ball. + """ + + nranges = radii.shape[0] + 1 + sources = self.places.density_discr.nodes().get(self.queue) + sources = make_obj_array([ + cl.array.to_device(self.queue, sources[idim, indices]) + for idim in range(self.dim)]) + + # construct tree + from boxtree import TreeBuilder + builder = TreeBuilder(self.context) + tree, _ = builder(self.queue, sources, max_particles_in_box=30) + + from boxtree.area_query import AreaQueryBuilder + builder = AreaQueryBuilder(self.context) + query, _ = builder(self.queue, tree, centers, radii) + + # find nodes inside each proxy ball + tree = tree.get(self.queue) + query = query.get(self.queue) + centers_ = np.vstack([centers[idim].get(self.queue) + for idim in range(self.dim)]) + radii_ = radii.get(self.queue) + + neighbors = np.empty(nranges - 1, dtype=np.object) + for iproxy in range(nranges - 1): + # get list of boxes intersecting the current ball + istart = query.leaves_near_ball_starts[iproxy] + iend = query.leaves_near_ball_starts[iproxy + 1] + iboxes = query.leaves_near_ball_lists[istart:iend] + + # get nodes inside the boxes + istart = tree.box_source_starts[iboxes] + iend = istart + tree.box_source_counts_cumul[iboxes] + isources = np.hstack([np.arange(s, e) + for s, e in zip(istart, iend)]) + nodes = np.vstack([tree.sources[idim][isources] + for idim in range(self.dim)]) + isources = tree.user_source_ids[isources] + + # get nodes inside the ball but outside the current range + center = centers_[:, iproxy].reshape(-1, 1) + radius = radii_[iproxy] + mask = (la.norm(nodes - center, axis=0) < radius) & \ + ((isources < ranges[iproxy]) | (ranges[iproxy + 1] <= isources)) + + neighbors[iproxy] = indices[isources[mask]] + + nbrranges = np.cumsum([0] + [n.shape[0] for n in neighbors]) + neighbors = np.hstack(neighbors) + + return (cl.array.to_device(self.queue, neighbors), + cl.array.to_device(self.queue, nbrranges)) + + def __call__(self, indices, ranges, **kwargs): + """ + :arg indices: Set of indices for points in :attr:`places`. + :arg ranges: Set of ranges around which to build a set of proxy + points. For each range, this builds a ball of proxy points centered + at the center of mass of the points in the range with a radius + defined by :attr:`ratio`. + + :returns: `skeletons` A set of skeleton points for each range, + which are supposed to contain all the necessary information about + the farfield interactions. This set of points contains a set of + proxy points constructed around each range and the closest neighbors + that are inside the proxy ball. + :returns: `sklranges` An array used to index into the skeleton + points. + """ + + @memoize_in(self, "concat_skl_knl") + def knl(): + loopy_knl = lp.make_kernel([ + "{[irange, idim]: 0 <= irange < nranges and \ + 0 <= idim < dim}", + "{[ipxy, ingb]: 0 <= ipxy < npxyblock and \ + 0 <= ingb < nngbblock}" + ], + """ + for irange + <> pxystart = pxyranges[irange] + <> pxyend = pxyranges[irange + 1] + <> npxyblock = pxyend - pxystart + + <> ngbstart = nbrranges[irange] + <> ngbend = nbrranges[irange + 1] + <> nngbblock = ngbend - ngbstart + + <> sklstart = pxyranges[irange] + nbrranges[irange] + skeletons[idim, sklstart + ipxy] = \ + proxies[idim, pxystart + ipxy] \ + {id_prefix=write_pxy,nosync=write_ngb} + skeletons[idim, sklstart + npxyblock + ingb] = \ + sources[idim, neighbors[ngbstart + ingb]] \ + {id_prefix=write_ngb,nosync=write_pxy} + end + """, + [ + lp.GlobalArg("sources", None, + shape=(self.dim, "nsources")), + lp.GlobalArg("proxies", None, + shape=(self.dim, "nproxies"), dim_tags="sep,C"), + lp.GlobalArg("neighbors", None, + shape="nneighbors"), + lp.GlobalArg("pxyranges", None, + shape="nranges + 1"), + lp.GlobalArg("nbrranges", None, + shape="nranges + 1"), + lp.GlobalArg("skeletons", None, + shape=(self.dim, "nproxies + nneighbors")), + lp.ValueArg("nsources", np.int32), + lp.ValueArg("nproxies", np.int32), + lp.ValueArg("nneighbors", np.int32), + "..." + ], + name="concat_skl", + default_offset=lp.auto, + fixed_parameters=dict(dim=self.dim), + lang_version=MOST_RECENT_LANGUAGE_VERSION) + + loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") + # loopy_knl = lp.split_iname(loopy_knl, "irange", 16, inner_tag="l.0") + return loopy_knl + + # construct point arrays + sources = self.places.density_discr.nodes() + centers, radii, proxies, pxyranges = \ + self.get_proxies(indices, ranges, **kwargs) + neighbors, nbrranges = \ + self.get_neighbors(indices, ranges, centers, radii) + + # construct joint array + _, (skeletons,) = knl()(self.queue, + sources=sources, proxies=proxies, neighbors=neighbors, + pxyranges=pxyranges, nbrranges=nbrranges) + sklranges = np.array([p + n for p, n in zip(pxyranges, nbrranges)]) + + return skeletons, sklranges + + +# }}} + +# vim: foldmethod=marker -- GitLab From f5e10470baa49371469c36d8e95412a35e1210ed Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 17 Jun 2018 19:15:32 -0500 Subject: [PATCH 153/268] direct-solver: add tests for the proxy point generator --- pytential/direct_solver.py | 162 ++++++++------- test/test_direct_solver.py | 415 +++++++++++++++++++++++++++++++++++++ 2 files changed, 500 insertions(+), 77 deletions(-) create mode 100644 test/test_direct_solver.py diff --git a/pytential/direct_solver.py b/pytential/direct_solver.py index 72a7729e..0a864a10 100644 --- a/pytential/direct_solver.py +++ b/pytential/direct_solver.py @@ -23,10 +23,12 @@ THE SOFTWARE. """ import numpy as np +import numpy.linalg as la import pyopencl as cl import pyopencl.array # noqa +from pytools.obj_array import make_obj_array from pytools import memoize_method, memoize_in import loopy as lp @@ -182,43 +184,38 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): # {{{ proxy point generator class ProxyGenerator(object): - def __init__(self, queue, places, - ratio=1.5, nproxy=31, target_order=0): + def __init__(self, queue, source, ratio=1.5, nproxy=31): r""" - :arg queue: a :class:`pyopencl.CommandQueue` used to synchronize the - calculations. - :arg places: commonly a :class:`pytential.qbx.LayerPotentialSourceBase`. - :arg ratio: a ratio used to compute the proxy point radius. For proxy - points, we have two radii of interest for a set of points: the - radius :math:`r_{block}` of the smallest ball containing all the - points in a block and the radius :math:`r_{qbx}` of the smallest - ball containing all the QBX expansion balls in the block. If the - ratio :math:`\theta \in [0, 1]`, then the radius of the proxy - ball is computed as: + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg source: a :class:`pytential.qbx.LayerPotentialSourceBase`. + :arg ratio: a ratio used to compute the proxy point radius. The radius + is computed in the :math:`L_2` norm, resulting in a circle or + sphere of proxy points. For QBX, we have two radii of interest + for a set of points: the radius :math:`r_{block}` of the + smallest ball containing all the points and the radius + :math:`r_{qbx}` of the smallest ball containing all the QBX + expansion balls in the block. If the ratio :math:`\theta \in + [0, 1]`, then the radius of the proxy ball is .. math:: r = (1 - \theta) r_{block} + \theta r_{qbx}. - If the ratio :math:`\theta > 1`, the the radius is simply: + If the ratio :math:`\theta > 1`, the the radius is simply .. math:: r = \theta r_{qbx}. - :arg nproxy: number of proxy points for each block. - :arg target_order: order of the discretization of proxy points. Can - be quite small, since proxy points are evaluated point-to-point - at the moment. + :arg nproxy: number of proxy points. """ self.queue = queue - self.places = places + self.source = source self.context = self.queue.context self.ratio = abs(ratio) self.nproxy = int(abs(nproxy)) - self.target_order = target_order - self.dim = self.places.ambient_dim + self.dim = self.source.ambient_dim if self.dim == 2: from meshmode.mesh.generation import ellipse, make_curve_mesh @@ -240,34 +237,34 @@ class ProxyGenerator(object): else: radius_expr = "ratio * rqbx" + # NOTE: centers of mass are computed using a second-order approximation + # that currently matches what's in `element_centers_of_mass`. knl = lp.make_kernel([ "{[irange]: 0 <= irange < nranges}", - "{[iblk]: 0 <= iblk < nblock}", + "{[i]: 0 <= i < npoints}", "{[idim]: 0 <= idim < dim}" ], [""" for irange - <> blk_start = ranges[irange] - <> blk_end = ranges[irange + 1] - <> nblock = blk_end - blk_start + <> npoints = ranges[irange + 1] - ranges[irange] + proxy_center[idim, irange] = 1.0 / npoints * \ + reduce(sum, i, nodes[idim, indices[i + ranges[irange]]]) \ + {{dup=idim:i}} - proxy_center[idim, irange] = 1.0 / blk_length * \ - reduce(sum, iblk, nodes[idim, indices[blk_start + iblk]]) \ - {{dup=idim:iblk}} - - <> rblk = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + <> rblk = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - nodes[idim, indices[blk_start + iblk]]) ** 2))) - <> rqbx_int = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + nodes[idim, indices[i + ranges[irange]]]) ** 2))) + + <> rqbx_int = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_int[idim, indices[blk_start + iblk]]) ** 2)) + \ - expansion_radii[indices[blk_start + iblk]]) - <> rqbx_ext = simul_reduce(max, iblk, sqrt(simul_reduce(sum, idim, \ + center_int[idim, indices[i + ranges[irange]]]) ** 2)) + \ + expansion_radii[indices[i + ranges[irange]]]) + <> rqbx_ext = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_ext[idim, indices[blk_start + iblk]]) ** 2)) + \ - expansion_radii[indices[blk_start + iblk]]) - + center_ext[idim, indices[i + ranges[irange]]]) ** 2)) + \ + expansion_radii[indices[i + ranges[irange]]]) <> rqbx = if(rqbx_ext < rqbx_int, rqbx_int, rqbx_ext) + proxy_radius[irange] = {radius_expr} end """.format(radius_expr=radius_expr)], @@ -292,7 +289,7 @@ class ProxyGenerator(object): lp.ValueArg("nranges", None), lp.ValueArg("nindices", np.int64) ], - name="proxy_kernel", + name="proxy_generator_knl", assumptions="dim>=1 and nranges>=1", fixed_parameters=dict( dim=self.dim, @@ -311,27 +308,33 @@ class ProxyGenerator(object): return knl def get_proxies(self, indices, ranges, **kwargs): - """ + """Generate proxy points for each given range of source points in + the discretization :attr:`source`. + :arg indices: a set of indices around which to construct proxy balls. - :arg ranges: an array of size `nranges + 1` used to index into the - set of indices. Each one of the `nranges` ranges will get a proxy - ball of a predefined size. - - :returns: `centers` is a list of centers for each proxy ball. - :returns: `radii` is a list of radii for each proxy ball. - :returns: `proxies` is a set of proxy points. - :returns: `pxyranges` is an array used to index into the list of - proxy points. + :arg ranges: an array of size `(nranges + 1,)` used to index into the + set of indices. Each one of the `nranges` ranges will get a proxy + ball. + + :return: a tuple of `(centers, radii, proxies, pryranges)`, where + each value is a :class:`pyopencl.array.Array` of integers. The + sizes of the arrays are as follows: `centers` is of size + `(2, nranges)`, `radii` is of size `(nranges,)`, `pryranges` is + of size `(nranges + 1,)` and `proxies` is of size + `(2, nranges * nproxies)`. The proxy points in a range :math:`i` + can be obtained by a slice `proxies[pryranges[i]:pryranges[i + 1]]` + and are all at a distance `radii[i]` from the range center + `centers[i]`. """ from pytential.qbx.utils import get_centers_on_side knl = self.get_kernel() _, (centers_dev, radii_dev,) = knl(self.queue, - nodes=self.places.density_discr.nodes(), - center_int=get_centers_on_side(self.places, -1), - center_ext=get_centers_on_side(self.places, +1), - expansion_radii=self.places._expansion_radii("nsources"), + nodes=self.source.density_discr.nodes(), + center_int=get_centers_on_side(self.source, -1), + center_ext=get_centers_on_side(self.source, +1), + expansion_radii=self.source._expansion_radii("nsources"), indices=indices, ranges=ranges, **kwargs) centers = centers_dev.get() radii = radii_dev.get() @@ -356,23 +359,26 @@ class ProxyGenerator(object): return centers_dev, radii_dev, proxies, pxyranges def get_neighbors(self, indices, ranges, centers, radii): - """ - :arg indices: indices for a subset of discretization nodes. + """Generate a set of neighboring points for each range of source + points in :attr:`source`. Neighboring points are defined as all + the points inside a proxy ball :math:`i` that do not also belong to + the set of source points in the same range :math:`i`. + + :arg indices: indices for a subset of the source points. :arg ranges: array used to index into the :attr:`indices` array. :arg centers: centers for each proxy ball. :arg radii: radii for each proxy ball. - :returns: `neighbors` is a set of indices into the full set of - discretization nodes (already mapped through the given set `indices`). - It contains all nodes that are inside a given proxy ball :math:`i`, - but not contained in the range :math:`i`, given by - `indices[ranges[i]:ranges[i + 1]]`. - :returns: `nbrranges` is an array used to index into the set of - neighbors and return the subset for a given proxy ball. + :return: a tuple `(neighbours, nbrranges)` where is value is a + :class:`pyopencl.array.Array` of integers. For a range :math:`i` + in `nbrranges`, the corresponding slice of the `neighbours` array + is a subset of :attr:`indices` such that all points are inside + the proxy ball centered at `centers[i]` of radius `radii[i]` + that is not included in `indices[ranges[i]:ranges[i + 1]]`. """ nranges = radii.shape[0] + 1 - sources = self.places.density_discr.nodes().get(self.queue) + sources = self.source.density_discr.nodes().get(self.queue) sources = make_obj_array([ cl.array.to_device(self.queue, sources[idim, indices]) for idim in range(self.dim)]) @@ -425,19 +431,19 @@ class ProxyGenerator(object): def __call__(self, indices, ranges, **kwargs): """ - :arg indices: Set of indices for points in :attr:`places`. - :arg ranges: Set of ranges around which to build a set of proxy - points. For each range, this builds a ball of proxy points centered - at the center of mass of the points in the range with a radius - defined by :attr:`ratio`. - - :returns: `skeletons` A set of skeleton points for each range, - which are supposed to contain all the necessary information about - the farfield interactions. This set of points contains a set of - proxy points constructed around each range and the closest neighbors - that are inside the proxy ball. - :returns: `sklranges` An array used to index into the skeleton - points. + :arg indices: set of indices for points in :attr:`source`. + :arg ranges: set of ranges around which to build a set of proxy + points. For each range, this builds a ball of proxy points centered + at the center of mass of the points in the range with a radius + defined by :attr:`ratio`. + + :returns: a tuple `(skeletons, sklranges)` where each value is a + :class:`pyopencl.array.Array`. For a range :math:`i`, we can + get the slice using `skeletons[sklranges[i]:sklranges[i + 1]]`. + The skeleton points in a range represents the union of a set + of generated proxy points and all the source points inside the + proxy ball that do not also belong to the current range in + :attr:`indices`. """ @memoize_in(self, "concat_skl_knl") @@ -487,15 +493,17 @@ class ProxyGenerator(object): ], name="concat_skl", default_offset=lp.auto, + silenced_warnings="write_race(write_*)", fixed_parameters=dict(dim=self.dim), lang_version=MOST_RECENT_LANGUAGE_VERSION) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - # loopy_knl = lp.split_iname(loopy_knl, "irange", 16, inner_tag="l.0") + loopy_knl = lp.split_iname(loopy_knl, "irange", 128, outer_tag="g.0") + return loopy_knl # construct point arrays - sources = self.places.density_discr.nodes() + sources = self.source.density_discr.nodes() centers, radii, proxies, pxyranges = \ self.get_proxies(indices, ranges, **kwargs) neighbors, nbrranges = \ diff --git a/test/test_direct_solver.py b/test/test_direct_solver.py new file mode 100644 index 00000000..3c86ac8e --- /dev/null +++ b/test/test_direct_solver.py @@ -0,0 +1,415 @@ +from __future__ import division, absolute_import, print_function + +__copyright__ = "Copyright (C) 2018 Alexandru Fikl" + +__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 time + +import numpy as np +import numpy.linalg as la + +import pyopencl as cl +import pytest + +from pytential import sym +from meshmode.mesh.generation import ( # noqa + ellipse, NArmedStarfish, generate_torus, make_curve_mesh) + +from pyopencl.tools import ( # noqa + pytest_generate_tests_for_pyopencl + as pytest_generate_tests) + + +def create_data(queue, + target_order=7, + qbx_order=4, + nelements=30, + curve_f=None, + ndim=2, k=0, + lpot_idx=2): + + if curve_f is None: + curve_f = NArmedStarfish(5, 0.25) + # curve_f = partial(ellipse, 3) + + from sumpy.kernel import LaplaceKernel, HelmholtzKernel + if k: + knl = HelmholtzKernel(ndim) + knl_kwargs = {"k": k} + else: + knl = LaplaceKernel(ndim) + knl_kwargs = {} + + if lpot_idx == 1: + u_sym = sym.var("u") + op = sym.D(knl, u_sym, **knl_kwargs) + else: + u_sym = sym.var("u") + op = sym.S(knl, u_sym, **knl_kwargs) + + if ndim == 2: + mesh = make_curve_mesh(curve_f, + np.linspace(0, 1, nelements + 1), + target_order) + else: + mesh = generate_torus(10.0, 2.0, order=target_order) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from pytential.qbx import QBXLayerPotentialSource + pre_density_discr = Discretization( + queue.context, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4 * target_order, + qbx_order, + # Don't use FMM for now + fmm_order=False).with_refinement() + + return qbx, op, u_sym + + +def create_indices(qbx, nblks, + factor=0.6, + random=True, + method='elements', + use_tree=True, + use_stage2=False): + from pytential.direct_solver import ( + partition_by_nodes, partition_by_elements) + + if use_stage2: + density_discr = qbx.quad_stage2_density_discr + else: + density_discr = qbx.density_discr + + if method == 'nodes': + nnodes = density_discr.nnodes + else: + nnodes = density_discr.mesh.nelements + max_particles_in_box = nnodes // nblks + + if method == "nodes": + indices, ranges = partition_by_nodes(density_discr, + use_tree=use_tree, max_particles_in_box=max_particles_in_box) + elif method == "elements": + indices, ranges = partition_by_elements(density_discr, + use_tree=use_tree, max_particles_in_box=max_particles_in_box) + else: + raise ValueError("unknown method: {}".format(method)) + + # take a subset of the points + if abs(factor - 1.0) > 1.0e-14: + indices_ = np.empty(ranges.shape[0] - 1, dtype=np.object) + for i in range(ranges.shape[0] - 1): + iidx = indices[np.s_[ranges[i]:ranges[i + 1]]] + isize = int(factor * len(iidx)) + + if random: + indices_[i] = np.sort(np.random.choice(iidx, + size=isize, replace=False)) + else: + indices_[i] = iidx[:isize] + + ranges = np.cumsum([0] + [r.shape[0] for r in indices_]) + indices = np.hstack(indices_) + + return indices, ranges + + +@pytest.mark.parametrize("method", ["nodes", "elements"]) +@pytest.mark.parametrize("use_tree", [True, False]) +@pytest.mark.parametrize("ndim", [2, 3]) +def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): + def plot_indices(pid, discr, indices, ranges): + import matplotlib.pyplot as pt + + rangefile = os.path.join(os.path.dirname(__file__), + "test_partition_{}_{}_ranges_{}.png".format(method, + "tree" if use_tree else "linear", pid)) + pointfile = os.path.join(os.path.dirname(__file__), + "test_partition_{}_{}_{}d_{}.png".format(method, + "tree" if use_tree else "linear", ndim, pid)) + + pt.figure(figsize=(10, 8)) + pt.plot(np.diff(ranges)) + pt.savefig(rangefile, dpi=300) + pt.clf() + + if ndim == 2: + sources = discr.nodes().get(queue) + + pt.figure(figsize=(10, 8)) + for i in range(nblks): + isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + pt.plot(sources[0][isrc], sources[1][isrc], 'o') + + pt.xlim([-1.5, 1.5]) + pt.ylim([-1.5, 1.5]) + pt.savefig(pointfile, dpi=300) + pt.clf() + elif ndim == 3: + from meshmode.discretization.visualization import make_visualizer + marker = -42.0 * np.ones(discr.nnodes) + + for i in range(nblks): + isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + marker[isrc] = 10.0 * (i + 1.0) + + vis = make_visualizer(queue, discr, 10) + vis.write_vtk_file(pointfile, [ + ("marker", cl.array.to_device(queue, marker)) + ]) + + + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + from sympy.core.cache import clear_cache + clear_cache() + qbx = create_data(queue, ndim=ndim)[0] + + nblks = 10 + t_start = time.time() + srcindices, srcranges = create_indices(qbx, nblks, + method=method, use_tree=use_tree, factor=0.6) + nblks = srcranges.shape[0] - 1 + t_end = time.time() + if verbose: + print('Time: {:.5f}s'.format(t_end - t_start)) + + if verbose: + discr = qbx.resampler.from_discr + plot_indices(0, discr, srcindices, srcranges) + + if method == "elements": + from meshmode.discretization.connection import flatten_chained_connection + resampler = flatten_chained_connection(queue, qbx.resampler) + + from pytential.direct_solver import partition_from_coarse + t_start = time.time() + srcindices_, srcranges_ = \ + partition_from_coarse(queue, resampler, srcindices, srcranges) + t_end = time.time() + if verbose: + print('Time: {:.5f}s'.format(t_end - t_start)) + + sources = resampler.from_discr.nodes().get(queue) + sources_ = resampler.to_discr.nodes().get(queue) + for i in range(srcranges.shape[0] - 1): + isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + isrc_ = srcindices_[np.s_[srcranges_[i]:srcranges_[i + 1]]] + + for j in range(ndim): + assert np.min(sources_[j][isrc_]) <= np.min(sources[j][isrc]) + assert np.max(sources_[j][isrc_]) >= np.max(sources[j][isrc]) + + if verbose: + discr = qbx.resampler.to_discr + plot_indices(1, discr, srcindices, srcranges) + + +@pytest.mark.parametrize("ndim", [2, 3]) +def test_proxy_generator(ctx_factory, ndim, verbose=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + # prevent cache explosion + from sympy.core.cache import clear_cache + clear_cache() + qbx = create_data(queue, ndim=ndim)[0] + + nblks = 10 + srcindices, srcranges = create_indices(qbx, nblks) + nblks = srcranges.shape[0] - 1 + + from pytential.direct_solver import ProxyGenerator + gen = ProxyGenerator(queue, qbx, ratio=1.1) + centers, radii, proxies, pxyranges = \ + gen.get_proxies(srcindices, srcranges) + + if verbose: + knl = gen.get_kernel() + knl = lp.add_dtypes(knl, { + "nodes": np.float64, + "center_int": np.float64, + "center_ext": np.float64, + "expansion_radii": np.float64, + "ranges": np.int64, + "indices": np.int64, + "nnodes": np.int64, + "nranges": np.int64, + "nindices": np.int64}) + print(knl) + print(lp.generate_code_v2(knl).device_code()) + + proxies = np.vstack([p.get(queue) for p in proxies]) + if verbose: + if qbx.ambient_dim == 2: + import matplotlib.pyplot as pt + from pytential.qbx.utils import get_centers_on_side + + density_nodes = qbx.density_discr.nodes().get(queue) + ci = get_centers_on_side(qbx, -1) + ci = np.vstack([c.get(queue) for c in ci]) + ce = get_centers_on_side(qbx, +1) + ce = np.vstack([c.get(queue) for c in ce]) + r = qbx._expansion_radii("nsources").get(queue) + + for i in range(nblks): + isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] + + pt.figure(figsize=(10, 8)) + axis = pt.gca() + for j in isrc: + c = pt.Circle(ci[:, j], r[j], color='k', alpha=0.1) + axis.add_artist(c) + c = pt.Circle(ce[:, j], r[j], color='k', alpha=0.1) + axis.add_artist(c) + + pt.plot(density_nodes[0], density_nodes[1], + 'ko', ms=2.0, alpha=0.5) + pt.plot(density_nodes[0, srcindices], density_nodes[1, srcindices], + 'o', ms=2.0) + pt.plot(density_nodes[0, isrc], density_nodes[1, isrc], + 'o', ms=2.0) + pt.plot(proxies[0, ipxy], proxies[1, ipxy], + 'o', ms=2.0) + pt.xlim([-1.5, 1.5]) + pt.ylim([-1.5, 1.5]) + filename = os.path.join(os.path.dirname(__file__), + "test_proxy_generator_{}d_{:04}.png".format(ndim, i)) + pt.savefig(filename, dpi=300) + pt.clf() + else: + from meshmode.discretization.visualization import make_visualizer + from meshmode.mesh.generation import make_group_from_vertices + from meshmode.mesh.processing import ( # noqa + affine_map, merge_disjoint_meshes) + from meshmode.discretization import Discretization + from meshmode.mesh import TensorProductElementGroup, Mesh + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + + centers = np.vstack([c.get(queue) for c in centers]) + radii = radii.get(queue) + + for i in range(nblks): + mesh = affine_map(gen.mesh, + A=(radii[i] * np.eye(ndim)), + b=centers[:, i].reshape(-1)) + + mesh = merge_disjoint_meshes([mesh, qbx.density_discr.mesh]) + discr = Discretization(ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(10)) + + vis = make_visualizer(queue, discr, 10) + filename = os.path.join(os.path.dirname(__file__), + "test_proxy_generator_{}d_{:04}.vtu".format(ndim, i)) + vis.write_vtk_file(filename, []) + + +@pytest.mark.parametrize("ndim", [2, 3]) +def test_area_query(ctx_factory, ndim, verbose=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + from sympy.core.cache import clear_cache + clear_cache() + qbx = create_data(queue, ndim=ndim)[0] + + nblks = 10 + srcindices, srcranges = create_indices(qbx, nblks) + nblks = srcranges.shape[0] - 1 + + # generate proxy points + from pytential.direct_solver import ProxyGenerator + gen = ProxyGenerator(queue, qbx, ratio=1.1) + centers, radii, _, _ = gen.get_proxies(srcindices, srcranges) + neighbors, ngbranges = gen.get_neighbors(srcindices, srcranges, centers, radii) + skeleton_nodes, sklranges = gen(srcindices, srcranges) + + neighbors = neighbors.get(queue) + if verbose: + if ndim == 2: + import matplotlib.pyplot as pt + density_nodes = qbx.density_discr.nodes().get(queue) + skeleton_nodes = skeleton_nodes.get(queue) + + for i in range(nblks): + isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] + iskl = np.s_[sklranges[i]:sklranges[i + 1]] + + pt.figure(figsize=(10, 8)) + pt.plot(density_nodes[0], density_nodes[1], + 'ko', ms=2.0, alpha=0.5) + pt.plot(density_nodes[0, srcindices], density_nodes[1, srcindices], + 'o', ms=2.0) + pt.plot(density_nodes[0, isrc], density_nodes[1, isrc], + 'o', ms=2.0) + pt.plot(density_nodes[0, ingb], density_nodes[1, ingb], + 'o', ms=2.0) + pt.plot(skeleton_nodes[0, iskl], skeleton_nodes[1, iskl], + 'x', ms=2.0) + pt.xlim([-1.5, 1.5]) + pt.ylim([-1.5, 1.5]) + + filename = os.path.join(os.path.dirname(__file__), + "test_area_query_{}d_{:04}.png".format(ndim, i)) + pt.savefig(filename, dpi=300) + pt.clf() + elif ndim == 3: + from meshmode.discretization.visualization import make_visualizer + marker = np.empty(qbx.density_discr.nnodes) + + for i in range(nblks): + isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] + + # TODO: some way to turn off some of the interpolations + # would help visualize this better. + marker.fill(0.0) + marker[srcindices] = 0.0 + marker[isrc] = -42.0 + marker[ingb] = +42.0 + marker_dev = cl.array.to_device(queue, marker) + + vis = make_visualizer(queue, qbx.density_discr, 10) + filename = os.path.join(os.path.dirname(__file__), + "test_area_query_{}d_{:04}.vtu".format(ndim, i)) + vis.write_vtk_file(filename, [ + ("marker", marker_dev), + ]) + + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + from pytest import main + main([__file__]) + +# vim: fdm=marker -- GitLab From bb22b807d37429905929ca30a341bb54a4ed5696 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 17 Jun 2018 19:39:42 -0500 Subject: [PATCH 154/268] direct-solver: add some more docs --- pytential/direct_solver.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pytential/direct_solver.py b/pytential/direct_solver.py index 0a864a10..45bb0c4d 100644 --- a/pytential/direct_solver.py +++ b/pytential/direct_solver.py @@ -46,6 +46,21 @@ def _element_node_range(groups, igroup, ielement): def partition_by_nodes(discr, use_tree=True, max_particles_in_box=30): + """Generate clusters / ranges of nodes. The partition is created at the + lowest level of granularity, i.e. nodes. This results in balanced ranges + of points, but will split elements across different ranges. + + :arg discr: a :class:`~meshmode.discretization.Discretization`. + :arg use_tree: if True, node partitions are generated using a + :class:`boxtree.TreeBuilder`, which leads to geometrically close + points to belong to the same partition. If False, a simple linear + partition is constructed. + :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. + + :return: a tuple `(indices, ranges)` of integer arrays. The indices + in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + """ + if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder @@ -78,6 +93,22 @@ def partition_by_nodes(discr, use_tree=True, max_particles_in_box=30): def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): + """Generate clusters / ranges of points. The partiton is created at the + element level, so that all the nodes belonging to an element belong to + the same range. This can result in slightly larger difference in size + between the ranges, but can be very useful when the individual partitions + need to be resampled, integrated, etc. + + :arg discr: a :class:`~meshmode.discretization.Discretization`. + :arg use_tree: if True, node partitions are generated using a + :class:`boxtree.TreeBuilder`, which leads to geometrically close + points to belong to the same partition. If False, a simple linear + partition is constructed. + :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. + + :return: a tuple `(indices, ranges)` of integer arrays. The indices + in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + """ if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder @@ -131,6 +162,22 @@ def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): def partition_from_coarse(queue, resampler, from_indices, from_ranges): + """Generate a partition of nodes from an existing partition on a + coarser discretization. The new partition is generated based on element + refinement relationships in :attr:`resampler`, so the existing partition + needs to be created using :func:`partition_by_element`. + + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg resampler: a + :class:`~meshmode.discretization.connection.DirectDiscretizationConnection`. + :arg from_indices: a set of indices into the nodes in + :attr:`resampler.from_discr`. + :arg from_ranges: array used to index into :attr:`from_indices`. + + :return: a tuple `(indices, ranges)` of integer arrays. The indices + in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + """ + if not hasattr(resampler, "groups"): raise ValueError("resampler must be a DirectDiscretizationConnection.") -- GitLab From b812f9fc6379f2f89d71cc2ecc84e3159d3c57a1 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 17 Jun 2018 19:44:17 -0500 Subject: [PATCH 155/268] direct-solver: flake8 fixes --- pytential/direct_solver.py | 26 ++++++++++++++++---------- test/test_direct_solver.py | 31 +++++++++++-------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/pytential/direct_solver.py b/pytential/direct_solver.py index 45bb0c4d..565c17be 100644 --- a/pytential/direct_solver.py +++ b/pytential/direct_solver.py @@ -72,7 +72,7 @@ def partition_by_nodes(discr, use_tree=True, max_particles_in_box=30): max_particles_in_box=max_particles_in_box) tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & + leaf_boxes, = (tree.box_flags & box_flags_enum.HAS_CHILDREN == 0).nonzero() indices = np.empty(len(leaf_boxes), dtype=np.object) @@ -126,11 +126,10 @@ def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): groups = discr.groups tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & + leaf_boxes, = (tree.box_flags & box_flags_enum.HAS_CHILDREN == 0).nonzero() indices = np.empty(len(leaf_boxes), dtype=np.object) - elements = np.empty(len(leaf_boxes), dtype=np.object) for i, ibox in enumerate(leaf_boxes): box_start = tree.box_source_starts[ibox] box_end = box_start + tree.box_source_counts_cumul[ibox] @@ -165,12 +164,19 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): """Generate a partition of nodes from an existing partition on a coarser discretization. The new partition is generated based on element refinement relationships in :attr:`resampler`, so the existing partition - needs to be created using :func:`partition_by_element`. + needs to be created using :func:`partition_by_element`, since we assume + that each range contains all the nodes in an element. + + The new partition will have the same number of ranges as the old partition. + The nodes inside each range in the new partition are all the nodes in + :attr:`resampler.to_discr` that belong to the same region as the nodes + in the same range from :attr:`resampler.from_discr`. These nodes are + obtained using :attr:`mesmode.discretization.connection.InterpolationBatch`. :arg queue: a :class:`pyopencl.CommandQueue`. :arg resampler: a :class:`~meshmode.discretization.connection.DirectDiscretizationConnection`. - :arg from_indices: a set of indices into the nodes in + :arg from_indices: a set of indices into the nodes in :attr:`resampler.from_discr`. :arg from_ranges: array used to index into :attr:`from_indices`. @@ -366,11 +372,11 @@ class ProxyGenerator(object): :return: a tuple of `(centers, radii, proxies, pryranges)`, where each value is a :class:`pyopencl.array.Array` of integers. The sizes of the arrays are as follows: `centers` is of size - `(2, nranges)`, `radii` is of size `(nranges,)`, `pryranges` is - of size `(nranges + 1,)` and `proxies` is of size - `(2, nranges * nproxies)`. The proxy points in a range :math:`i` - can be obtained by a slice `proxies[pryranges[i]:pryranges[i + 1]]` - and are all at a distance `radii[i]` from the range center + `(2, nranges)`, `radii` is of size `(nranges,)`, `pryranges` is + of size `(nranges + 1,)` and `proxies` is of size + `(2, nranges * nproxies)`. The proxy points in a range :math:`i` + can be obtained by a slice `proxies[pryranges[i]:pryranges[i + 1]]` + and are all at a distance `radii[i]` from the range center `centers[i]`. """ diff --git a/test/test_direct_solver.py b/test/test_direct_solver.py index 3c86ac8e..57d58466 100644 --- a/test/test_direct_solver.py +++ b/test/test_direct_solver.py @@ -25,15 +25,14 @@ THE SOFTWARE. import time import numpy as np -import numpy.linalg as la - import pyopencl as cl -import pytest +import loopy as lp from pytential import sym from meshmode.mesh.generation import ( # noqa ellipse, NArmedStarfish, generate_torus, make_curve_mesh) +import pytest from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) @@ -144,12 +143,10 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): def plot_indices(pid, discr, indices, ranges): import matplotlib.pyplot as pt - rangefile = os.path.join(os.path.dirname(__file__), - "test_partition_{}_{}_ranges_{}.png".format(method, - "tree" if use_tree else "linear", pid)) - pointfile = os.path.join(os.path.dirname(__file__), - "test_partition_{}_{}_{}d_{}.png".format(method, - "tree" if use_tree else "linear", ndim, pid)) + rangefile = "test_partition_{}_{}_ranges_{}.png".format(method, + "tree" if use_tree else "linear", pid) + pointfile = "test_partition_{}_{}_{}d_{}.png".format(method, + "tree" if use_tree else "linear", ndim, pid) pt.figure(figsize=(10, 8)) pt.plot(np.diff(ranges)) @@ -181,7 +178,6 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): ("marker", cl.array.to_device(queue, marker)) ]) - ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -298,17 +294,15 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): 'o', ms=2.0) pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) - filename = os.path.join(os.path.dirname(__file__), - "test_proxy_generator_{}d_{:04}.png".format(ndim, i)) + + filename = "test_proxy_generator_{}d_{:04}.png".format(ndim, i) pt.savefig(filename, dpi=300) pt.clf() else: from meshmode.discretization.visualization import make_visualizer - from meshmode.mesh.generation import make_group_from_vertices from meshmode.mesh.processing import ( # noqa affine_map, merge_disjoint_meshes) from meshmode.discretization import Discretization - from meshmode.mesh import TensorProductElementGroup, Mesh from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory @@ -325,8 +319,7 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): InterpolatoryQuadratureSimplexGroupFactory(10)) vis = make_visualizer(queue, discr, 10) - filename = os.path.join(os.path.dirname(__file__), - "test_proxy_generator_{}d_{:04}.vtu".format(ndim, i)) + filename = "test_proxy_generator_{}d_{:04}.vtu".format(ndim, i) vis.write_vtk_file(filename, []) @@ -376,8 +369,7 @@ def test_area_query(ctx_factory, ndim, verbose=False): pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) - filename = os.path.join(os.path.dirname(__file__), - "test_area_query_{}d_{:04}.png".format(ndim, i)) + filename = "test_area_query_{}d_{:04}.png".format(ndim, i) pt.savefig(filename, dpi=300) pt.clf() elif ndim == 3: @@ -397,8 +389,7 @@ def test_area_query(ctx_factory, ndim, verbose=False): marker_dev = cl.array.to_device(queue, marker) vis = make_visualizer(queue, qbx.density_discr, 10) - filename = os.path.join(os.path.dirname(__file__), - "test_area_query_{}d_{:04}.vtu".format(ndim, i)) + filename = "test_area_query_{}d_{:04}.vtu".format(ndim, i) vis.write_vtk_file(filename, [ ("marker", marker_dev), ]) -- GitLab From c190d028892d1f0e1d90efb31205b7126f74870a Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 17 Jun 2018 20:47:05 -0500 Subject: [PATCH 156/268] direct-solver: fix tests with visualize=True --- pytential/direct_solver.py | 5 +-- test/test_direct_solver.py | 67 +++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/pytential/direct_solver.py b/pytential/direct_solver.py index 565c17be..80b1f473 100644 --- a/pytential/direct_solver.py +++ b/pytential/direct_solver.py @@ -402,6 +402,7 @@ class ProxyGenerator(object): proxies[i] = mesh.vertices pxyranges = np.cumsum([0] + [p.shape[-1] for p in proxies]) + pxyranges = cl.array.to_device(self.queue, pxyranges) proxies = make_obj_array([ cl.array.to_device(self.queue, np.hstack([p[idim] for p in proxies])) for idim in range(self.dim)]) @@ -566,11 +567,11 @@ class ProxyGenerator(object): _, (skeletons,) = knl()(self.queue, sources=sources, proxies=proxies, neighbors=neighbors, pxyranges=pxyranges, nbrranges=nbrranges) - sklranges = np.array([p + n for p, n in zip(pxyranges, nbrranges)]) + sklranges = np.array([p + n for p, n in zip(pxyranges.get(self.queue), + nbrranges.get(self.queue))]) return skeletons, sklranges - # }}} # vim: foldmethod=marker diff --git a/test/test_direct_solver.py b/test/test_direct_solver.py index 57d58466..858c7434 100644 --- a/test/test_direct_solver.py +++ b/test/test_direct_solver.py @@ -22,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import os import time import numpy as np @@ -89,7 +90,7 @@ def create_data(queue, def create_indices(qbx, nblks, - factor=0.6, + factor=1.0, random=True, method='elements', use_tree=True, @@ -139,17 +140,15 @@ def create_indices(qbx, nblks, @pytest.mark.parametrize("method", ["nodes", "elements"]) @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): def plot_indices(pid, discr, indices, ranges): import matplotlib.pyplot as pt - rangefile = "test_partition_{}_{}_ranges_{}.png".format(method, - "tree" if use_tree else "linear", pid) - pointfile = "test_partition_{}_{}_{}d_{}.png".format(method, - "tree" if use_tree else "linear", ndim, pid) - pt.figure(figsize=(10, 8)) pt.plot(np.diff(ranges)) + + rangefile = "test_partition_{}_{}_ranges_{}.png".format(method, + "tree" if use_tree else "linear", pid) pt.savefig(rangefile, dpi=300) pt.clf() @@ -157,15 +156,24 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): sources = discr.nodes().get(queue) pt.figure(figsize=(10, 8)) + pt.plot(sources[0], sources[1], 'ko', alpha=0.5) for i in range(nblks): isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] pt.plot(sources[0][isrc], sources[1][isrc], 'o') - pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) + + pointfile = "test_partition_{}_{}_{}d_{}.png".format(method, + "tree" if use_tree else "linear", ndim, pid) pt.savefig(pointfile, dpi=300) pt.clf() elif ndim == 3: + from meshmode.discretization import NoninterpolatoryElementGroupError + try: + discr.groups[0].basis() + except NoninterpolatoryElementGroupError: + return + from meshmode.discretization.visualization import make_visualizer marker = -42.0 * np.ones(discr.nnodes) @@ -174,6 +182,12 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): marker[isrc] = 10.0 * (i + 1.0) vis = make_visualizer(queue, discr, 10) + + pointfile = "test_partition_{}_{}_{}d_{}.vtu".format(method, + "tree" if use_tree else "linear", ndim, pid) + if os.path.isfile(pointfile): + os.remove(pointfile) + vis.write_vtk_file(pointfile, [ ("marker", cl.array.to_device(queue, marker)) ]) @@ -188,13 +202,13 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): nblks = 10 t_start = time.time() srcindices, srcranges = create_indices(qbx, nblks, - method=method, use_tree=use_tree, factor=0.6) + method=method, use_tree=use_tree, factor=1.0) nblks = srcranges.shape[0] - 1 t_end = time.time() - if verbose: + if visualize: print('Time: {:.5f}s'.format(t_end - t_start)) - if verbose: + if visualize: discr = qbx.resampler.from_discr plot_indices(0, discr, srcindices, srcranges) @@ -207,7 +221,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): srcindices_, srcranges_ = \ partition_from_coarse(queue, resampler, srcindices, srcranges) t_end = time.time() - if verbose: + if visualize: print('Time: {:.5f}s'.format(t_end - t_start)) sources = resampler.from_discr.nodes().get(queue) @@ -220,13 +234,13 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, verbose=False): assert np.min(sources_[j][isrc_]) <= np.min(sources[j][isrc]) assert np.max(sources_[j][isrc_]) >= np.max(sources[j][isrc]) - if verbose: - discr = qbx.resampler.to_discr - plot_indices(1, discr, srcindices, srcranges) + if visualize: + discr = resampler.to_discr + plot_indices(1, discr, srcindices_, srcranges_) @pytest.mark.parametrize("ndim", [2, 3]) -def test_proxy_generator(ctx_factory, ndim, verbose=False): +def test_proxy_generator(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -236,7 +250,8 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): qbx = create_data(queue, ndim=ndim)[0] nblks = 10 - srcindices, srcranges = create_indices(qbx, nblks) + srcindices, srcranges = create_indices(qbx, nblks, + method='nodes', factor=0.6) nblks = srcranges.shape[0] - 1 from pytential.direct_solver import ProxyGenerator @@ -244,7 +259,7 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): centers, radii, proxies, pxyranges = \ gen.get_proxies(srcindices, srcranges) - if verbose: + if visualize: knl = gen.get_kernel() knl = lp.add_dtypes(knl, { "nodes": np.float64, @@ -260,7 +275,7 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): print(lp.generate_code_v2(knl).device_code()) proxies = np.vstack([p.get(queue) for p in proxies]) - if verbose: + if visualize: if qbx.ambient_dim == 2: import matplotlib.pyplot as pt from pytential.qbx.utils import get_centers_on_side @@ -271,6 +286,7 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): ce = get_centers_on_side(qbx, +1) ce = np.vstack([c.get(queue) for c in ce]) r = qbx._expansion_radii("nsources").get(queue) + pxyranges = pxyranges.get(queue) for i in range(nblks): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] @@ -320,11 +336,13 @@ def test_proxy_generator(ctx_factory, ndim, verbose=False): vis = make_visualizer(queue, discr, 10) filename = "test_proxy_generator_{}d_{:04}.vtu".format(ndim, i) + if os.path.isfile(filename): + os.remove(filename) vis.write_vtk_file(filename, []) @pytest.mark.parametrize("ndim", [2, 3]) -def test_area_query(ctx_factory, ndim, verbose=False): +def test_area_query(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -333,7 +351,8 @@ def test_area_query(ctx_factory, ndim, verbose=False): qbx = create_data(queue, ndim=ndim)[0] nblks = 10 - srcindices, srcranges = create_indices(qbx, nblks) + srcindices, srcranges = create_indices(qbx, nblks, + method='nodes', factor=0.6) nblks = srcranges.shape[0] - 1 # generate proxy points @@ -344,7 +363,8 @@ def test_area_query(ctx_factory, ndim, verbose=False): skeleton_nodes, sklranges = gen(srcindices, srcranges) neighbors = neighbors.get(queue) - if verbose: + ngbranges = ngbranges.get(queue) + if visualize: if ndim == 2: import matplotlib.pyplot as pt density_nodes = qbx.density_discr.nodes().get(queue) @@ -390,6 +410,9 @@ def test_area_query(ctx_factory, ndim, verbose=False): vis = make_visualizer(queue, qbx.density_discr, 10) filename = "test_area_query_{}d_{:04}.vtu".format(ndim, i) + if os.path.isfile(filename): + os.remove(filename) + vis.write_vtk_file(filename, [ ("marker", marker_dev), ]) -- GitLab From 105f1632b0f04438c90d5b29a7eebf58a656b8e1 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 20 Jun 2018 19:05:06 -0500 Subject: [PATCH 157/268] move proxy code to a new linalg folder --- pytential/{direct_solver.py => linalg/proxy.py} | 0 test/{test_direct_solver.py => test_linalg_proxy.py} | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename pytential/{direct_solver.py => linalg/proxy.py} (100%) rename test/{test_direct_solver.py => test_linalg_proxy.py} (98%) diff --git a/pytential/direct_solver.py b/pytential/linalg/proxy.py similarity index 100% rename from pytential/direct_solver.py rename to pytential/linalg/proxy.py diff --git a/test/test_direct_solver.py b/test/test_linalg_proxy.py similarity index 98% rename from test/test_direct_solver.py rename to test/test_linalg_proxy.py index 858c7434..bf3223b3 100644 --- a/test/test_direct_solver.py +++ b/test/test_linalg_proxy.py @@ -95,7 +95,7 @@ def create_indices(qbx, nblks, method='elements', use_tree=True, use_stage2=False): - from pytential.direct_solver import ( + from pytential.linalg.proxy import ( partition_by_nodes, partition_by_elements) if use_stage2: @@ -216,7 +216,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): from meshmode.discretization.connection import flatten_chained_connection resampler = flatten_chained_connection(queue, qbx.resampler) - from pytential.direct_solver import partition_from_coarse + from pytential.linalg.proxy import partition_from_coarse t_start = time.time() srcindices_, srcranges_ = \ partition_from_coarse(queue, resampler, srcindices, srcranges) @@ -254,7 +254,7 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): method='nodes', factor=0.6) nblks = srcranges.shape[0] - 1 - from pytential.direct_solver import ProxyGenerator + from pytential.linalg.proxy import ProxyGenerator gen = ProxyGenerator(queue, qbx, ratio=1.1) centers, radii, proxies, pxyranges = \ gen.get_proxies(srcindices, srcranges) @@ -356,7 +356,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): nblks = srcranges.shape[0] - 1 # generate proxy points - from pytential.direct_solver import ProxyGenerator + from pytential.linalg.proxy import ProxyGenerator gen = ProxyGenerator(queue, qbx, ratio=1.1) centers, radii, _, _ = gen.get_proxies(srcindices, srcranges) neighbors, ngbranges = gen.get_neighbors(srcindices, srcranges, centers, radii) -- GitLab From 5cd88ed18c07561fdf3fa84a7de11581f998fe1f Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 20 Jun 2018 20:48:10 -0500 Subject: [PATCH 158/268] direct-solver: make all the functions return cl arrays --- doc/index.rst | 1 + doc/linalg.rst | 9 ++ pytential/linalg/proxy.py | 211 +++++++++++++++++++++++--------------- test/test_linalg_proxy.py | 46 ++++++--- 4 files changed, 168 insertions(+), 99 deletions(-) create mode 100644 doc/linalg.rst diff --git a/doc/index.rst b/doc/index.rst index d56c8bb5..772a936a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,6 +17,7 @@ Contents discretization symbolic + linalg tools misc diff --git a/doc/linalg.rst b/doc/linalg.rst new file mode 100644 index 00000000..d5e78a6e --- /dev/null +++ b/doc/linalg.rst @@ -0,0 +1,9 @@ +Linear Algebra Routines +======================= + +Hierarchical Direct Solver +-------------------------- + +.. automodule:: pytential.linalg.proxy + +.. vim: sw=4:tw=75 diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 80b1f473..d00dd410 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -22,11 +22,13 @@ 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 import pyopencl as cl import pyopencl.array # noqa +from pyopencl.array import to_device from pytools.obj_array import make_obj_array from pytools import memoize_method, memoize_in @@ -35,70 +37,92 @@ import loopy as lp from loopy.version import MOST_RECENT_LANGUAGE_VERSION +__doc__ = """ +Proxy Point Generation +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: partition_by_nodes + +.. autofunction:: partition_by_elements + +.. autofunction:: partition_from_coarse + +.. autoclass:: ProxyGenerator +""" + + # {{{ point index partitioning -def _element_node_range(groups, igroup, ielement): - istart = groups[igroup].node_nr_base + \ - groups[igroup].nunit_nodes * ielement - iend = groups[igroup].node_nr_base + \ - groups[igroup].nunit_nodes * (ielement + 1) +def _element_node_range(group, ielement): + istart = group.node_nr_base + group.nunit_nodes * ielement + iend = group.node_nr_base + group.nunit_nodes * (ielement + 1) + return np.arange(istart, iend) -def partition_by_nodes(discr, use_tree=True, max_particles_in_box=30): +def partition_by_nodes(queue, discr, + use_tree=True, + max_particles_in_box=30): """Generate clusters / ranges of nodes. The partition is created at the lowest level of granularity, i.e. nodes. This results in balanced ranges of points, but will split elements across different ranges. + :arg queue: a :class:`pyopencl.CommandQueue`. :arg discr: a :class:`~meshmode.discretization.Discretization`. - :arg use_tree: if True, node partitions are generated using a + :arg use_tree: if `True`, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close - points to belong to the same partition. If False, a simple linear + points to belong to the same partition. If `False`, a simple linear partition is constructed. :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. - :return: a tuple `(indices, ranges)` of integer arrays. The indices - in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` + integer arrays. The indices in a range can be retrieved using + `indices[ranges[i]:ranges[i + 1]]`. """ if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder - with cl.CommandQueue(discr.cl_context) as queue: - builder = TreeBuilder(discr.cl_context) + builder = TreeBuilder(discr.cl_context) - tree, _ = builder(queue, discr.nodes(), - max_particles_in_box=max_particles_in_box) + tree, _ = builder(queue, discr.nodes(), + max_particles_in_box=max_particles_in_box) - tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() - indices = np.empty(len(leaf_boxes), dtype=np.object) - for i, ibox in enumerate(leaf_boxes): - box_start = tree.box_source_starts[ibox] - box_end = box_start + tree.box_source_counts_cumul[ibox] - indices[i] = tree.user_source_ids[box_start:box_end] + indices = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] + indices[i] = tree.user_source_ids[box_start:box_end] - ranges = np.cumsum([0] + [box.shape[0] for box in indices]) - indices = np.hstack(indices) + ranges = to_device(queue, + np.cumsum([0] + [box.shape[0] for box in indices])) + indices = to_device(queue, np.hstack(indices)) else: - indices = np.arange(0, discr.nnodes) - ranges = np.arange(0, discr.nnodes + 1, - discr.nnodes // max_particles_in_box) + indices = cl.array.arange(queue, 0, discr.nnodes, + dtype=np.int) + ranges = cl.array.arange(queue, 0, discr.nnodes + 1, + discr.nnodes // max_particles_in_box, + dtype=np.int) assert ranges[-1] == discr.nnodes return indices, ranges -def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): - """Generate clusters / ranges of points. The partiton is created at the +def partition_by_elements(queue, discr, + use_tree=True, + max_particles_in_box=10): + """Generate clusters / ranges of points. The partition is created at the element level, so that all the nodes belonging to an element belong to - the same range. This can result in slightly larger difference in size + the same range. This can result in slightly larger differences in size between the ranges, but can be very useful when the individual partitions need to be resampled, integrated, etc. + :arg queue: a :class:`pyopencl.CommandQueue`. :arg discr: a :class:`~meshmode.discretization.Discretization`. :arg use_tree: if True, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close @@ -106,57 +130,57 @@ def partition_by_elements(discr, use_tree=True, max_particles_in_box=10): partition is constructed. :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. - :return: a tuple `(indices, ranges)` of integer arrays. The indices - in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` + integer arrays. The indices in a range can be retrieved using + `indices[ranges[i]:ranges[i + 1]]`. """ if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder - with cl.CommandQueue(discr.cl_context) as queue: - builder = TreeBuilder(discr.cl_context) + builder = TreeBuilder(discr.cl_context) - from pytential.qbx.utils import element_centers_of_mass - elranges = np.cumsum([0] + - [group.nelements for group in discr.mesh.groups]) - elcenters = element_centers_of_mass(discr) + from pytential.qbx.utils import element_centers_of_mass + elranges = np.cumsum([group.nelements for group in discr.mesh.groups]) + elcenters = element_centers_of_mass(discr) - tree, _ = builder(queue, elcenters, - max_particles_in_box=max_particles_in_box) + tree, _ = builder(queue, elcenters, + max_particles_in_box=max_particles_in_box) - groups = discr.groups - tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + groups = discr.groups + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() - indices = np.empty(len(leaf_boxes), dtype=np.object) - for i, ibox in enumerate(leaf_boxes): - box_start = tree.box_source_starts[ibox] - box_end = box_start + tree.box_source_counts_cumul[ibox] + indices = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] - ielement = tree.user_source_ids[box_start:box_end] - igroup = np.digitize(ielement, elranges) - 1 + ielement = tree.user_source_ids[box_start:box_end] + igroup = np.digitize(ielement, elranges) - indices[i] = np.hstack([_element_node_range(groups, j, k) - for j, k in zip(igroup, ielement)]) + indices[i] = np.hstack([_element_node_range(groups[j], k) + for j, k in zip(igroup, ielement)]) else: - groups = discr.groups - elranges = np.cumsum([0] + - [group.nelements for group in discr.mesh.groups]) - nelements = discr.mesh.nelements - indices = np.array_split(np.arange(0, nelements), - nelements // max_particles_in_box) - for i in range(len(indices)): - ielement = indices[i] - igroup = np.digitize(ielement, elranges) - 1 + elements = np.array_split(np.arange(0, nelements), + nelements // max_particles_in_box) - indices[i] = np.hstack([_element_node_range(groups, j, k) - for j, k in zip(igroup, ielement)]) + elranges = np.cumsum([g.nelements for g in discr.groups]) + elgroups = [np.digitize(elements[i], elranges) + for i in range(len(elements))] + + indices = np.empty(len(elements), dtype=np.object) + for i in range(indices.shape[0]): + indices[i] = np.hstack([_element_node_range(discr.groups[j], k) + for j, k in zip(elgroups[i], elements[i])]) - ranges = np.cumsum([0] + [box.shape[0] for box in indices]) - indices = np.hstack(indices) + ranges = to_device(queue, + np.cumsum([0] + [box.shape[0] for box in indices])) + indices = to_device(queue, np.hstack(indices)) + assert ranges[-1] == discr.nnodes return indices, ranges @@ -180,13 +204,18 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): :attr:`resampler.from_discr`. :arg from_ranges: array used to index into :attr:`from_indices`. - :return: a tuple `(indices, ranges)` of integer arrays. The indices - in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. + :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` + integer arrays. The indices in a range can be retrieved using + `indices[ranges[i]:ranges[i + 1]]`. """ if not hasattr(resampler, "groups"): raise ValueError("resampler must be a DirectDiscretizationConnection.") + if isinstance(from_ranges, cl.array.Array): + from_indices = from_indices.get(queue) + from_ranges = from_ranges.get(queue) + # construct ranges from_discr = resampler.from_discr from_grp_ranges = np.cumsum([0] + @@ -224,10 +253,11 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): for i, j in zip(el_ranges[to_el_table[igrp][to_element_indices]], to_element_indices): indices[i] = np.hstack([indices[i], - _element_node_range(resampler.to_discr.groups, igrp, j)]) + _element_node_range(resampler.to_discr.groups[igrp], j)]) - ranges = np.cumsum([0] + [box.shape[0] for box in indices]) - indices = np.hstack(indices) + ranges = to_device(queue, + np.cumsum([0] + [box.shape[0] for box in indices])) + indices = to_device(queue, np.hstack(indices)) return indices, ranges @@ -364,13 +394,14 @@ class ProxyGenerator(object): """Generate proxy points for each given range of source points in the discretization :attr:`source`. - :arg indices: a set of indices around which to construct proxy balls. - :arg ranges: an array of size `(nranges + 1,)` used to index into the - set of indices. Each one of the `nranges` ranges will get a proxy - ball. + :arg indices: a :class:`pyopencl.array.Array` of indices around + which to construct proxy balls. + :arg ranges: an :class:`pyopencl.array.Array` of size `(nranges + 1,)` + used to index into :attr:`indices`. Each one of the `nranges` + ranges will get a proxy ball. :return: a tuple of `(centers, radii, proxies, pryranges)`, where - each value is a :class:`pyopencl.array.Array` of integers. The + each value is a :class:`pyopencl.array.Array`. The sizes of the arrays are as follows: `centers` is of size `(2, nranges)`, `radii` is of size `(nranges,)`, `pryranges` is of size `(nranges + 1,)` and `proxies` is of size @@ -418,12 +449,16 @@ class ProxyGenerator(object): the points inside a proxy ball :math:`i` that do not also belong to the set of source points in the same range :math:`i`. - :arg indices: indices for a subset of the source points. - :arg ranges: array used to index into the :attr:`indices` array. - :arg centers: centers for each proxy ball. - :arg radii: radii for each proxy ball. + :arg indices: a :class:`pyopencl.array.Array` of indices for a subset + of the source points. + :arg ranges: a :class:`pyopencl.array.Array` used to index into + the :attr:`indices` array. + :arg centers: a :class:`pyopencl.array.Array` containing the center + of each proxy ball. + :arg radii: a :class:`pyopencl.array.Array` containing the radius + of each proxy ball. - :return: a tuple `(neighbours, nbrranges)` where is value is a + :return: a tuple `(neighbours, nbrranges)` where each value is a :class:`pyopencl.array.Array` of integers. For a range :math:`i` in `nbrranges`, the corresponding slice of the `neighbours` array is a subset of :attr:`indices` such that all points are inside @@ -431,6 +466,10 @@ class ProxyGenerator(object): that is not included in `indices[ranges[i]:ranges[i + 1]]`. """ + if isinstance(indices, cl.array.Array): + indices = indices.get(self.queue) + ranges = ranges.get(self.queue) + nranges = radii.shape[0] + 1 sources = self.source.density_discr.nodes().get(self.queue) sources = make_obj_array([ @@ -480,14 +519,16 @@ class ProxyGenerator(object): nbrranges = np.cumsum([0] + [n.shape[0] for n in neighbors]) neighbors = np.hstack(neighbors) - return (cl.array.to_device(self.queue, neighbors), - cl.array.to_device(self.queue, nbrranges)) + return (to_device(self.queue, neighbors), + to_device(self.queue, nbrranges)) def __call__(self, indices, ranges, **kwargs): """ - :arg indices: set of indices for points in :attr:`source`. - :arg ranges: set of ranges around which to build a set of proxy - points. For each range, this builds a ball of proxy points centered + :arg indices: a :class:`pyopencl.array.Array` of indices for points + in :attr:`source`. + :arg ranges: a :class:`pyopencl.array.Array` describing the ranges + from :attr:`indices` around which to build proxy points. For each + range, this builds a ball of proxy points centered at the center of mass of the points in the range with a radius defined by :attr:`ratio`. diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index bf3223b3..915cc4b8 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -27,6 +27,7 @@ import time import numpy as np import pyopencl as cl +from pyopencl.array import to_device import loopy as lp from pytential import sym @@ -89,7 +90,7 @@ def create_data(queue, return qbx, op, u_sym -def create_indices(qbx, nblks, +def create_indices(queue, qbx, nblks, factor=1.0, random=True, method='elements', @@ -98,6 +99,9 @@ def create_indices(qbx, nblks, from pytential.linalg.proxy import ( partition_by_nodes, partition_by_elements) + if method == 'elements': + factor = 1.0 + if use_stage2: density_discr = qbx.quad_stage2_density_discr else: @@ -110,16 +114,19 @@ def create_indices(qbx, nblks, max_particles_in_box = nnodes // nblks if method == "nodes": - indices, ranges = partition_by_nodes(density_discr, + indices, ranges = partition_by_nodes(queue, density_discr, use_tree=use_tree, max_particles_in_box=max_particles_in_box) elif method == "elements": - indices, ranges = partition_by_elements(density_discr, + indices, ranges = partition_by_elements(queue, density_discr, use_tree=use_tree, max_particles_in_box=max_particles_in_box) else: raise ValueError("unknown method: {}".format(method)) # take a subset of the points if abs(factor - 1.0) > 1.0e-14: + indices = indices.get(queue) + ranges = ranges.get(queue) + indices_ = np.empty(ranges.shape[0] - 1, dtype=np.object) for i in range(ranges.shape[0] - 1): iidx = indices[np.s_[ranges[i]:ranges[i + 1]]] @@ -131,8 +138,9 @@ def create_indices(qbx, nblks, else: indices_[i] = iidx[:isize] - ranges = np.cumsum([0] + [r.shape[0] for r in indices_]) - indices = np.hstack(indices_) + ranges = to_device(queue, + np.cumsum([0] + [r.shape[0] for r in indices_])) + indices = to_device(queue, np.hstack(indices_)) return indices, ranges @@ -141,7 +149,7 @@ def create_indices(qbx, nblks, @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): - def plot_indices(pid, discr, indices, ranges): + def _plot_indices(pid, discr, indices, ranges): import matplotlib.pyplot as pt pt.figure(figsize=(10, 8)) @@ -201,7 +209,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): nblks = 10 t_start = time.time() - srcindices, srcranges = create_indices(qbx, nblks, + srcindices, srcranges = create_indices(queue, qbx, nblks, method=method, use_tree=use_tree, factor=1.0) nblks = srcranges.shape[0] - 1 t_end = time.time() @@ -210,13 +218,12 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): if visualize: discr = qbx.resampler.from_discr - plot_indices(0, discr, srcindices, srcranges) + _plot_indices(0, discr, srcindices.get(queue), srcranges.get(queue)) if method == "elements": - from meshmode.discretization.connection import flatten_chained_connection - resampler = flatten_chained_connection(queue, qbx.resampler) - from pytential.linalg.proxy import partition_from_coarse + resampler = qbx.direct_resampler + t_start = time.time() srcindices_, srcranges_ = \ partition_from_coarse(queue, resampler, srcindices, srcranges) @@ -224,6 +231,11 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): if visualize: print('Time: {:.5f}s'.format(t_end - t_start)) + srcindices = srcindices.get(queue) + srcranges = srcranges.get(queue) + srcindices_ = srcindices_.get(queue) + srcranges_ = srcranges_.get(queue) + sources = resampler.from_discr.nodes().get(queue) sources_ = resampler.to_discr.nodes().get(queue) for i in range(srcranges.shape[0] - 1): @@ -236,7 +248,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): if visualize: discr = resampler.to_discr - plot_indices(1, discr, srcindices_, srcranges_) + _plot_indices(1, discr, srcindices_, srcranges_) @pytest.mark.parametrize("ndim", [2, 3]) @@ -250,7 +262,7 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): qbx = create_data(queue, ndim=ndim)[0] nblks = 10 - srcindices, srcranges = create_indices(qbx, nblks, + srcindices, srcranges = create_indices(queue, qbx, nblks, method='nodes', factor=0.6) nblks = srcranges.shape[0] - 1 @@ -275,6 +287,9 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): print(lp.generate_code_v2(knl).device_code()) proxies = np.vstack([p.get(queue) for p in proxies]) + srcindices = srcindices.get(queue) + srcranges = srcranges.get(queue) + if visualize: if qbx.ambient_dim == 2: import matplotlib.pyplot as pt @@ -351,7 +366,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): qbx = create_data(queue, ndim=ndim)[0] nblks = 10 - srcindices, srcranges = create_indices(qbx, nblks, + srcindices, srcranges = create_indices(queue, qbx, nblks, method='nodes', factor=0.6) nblks = srcranges.shape[0] - 1 @@ -364,6 +379,9 @@ def test_area_query(ctx_factory, ndim, visualize=False): neighbors = neighbors.get(queue) ngbranges = ngbranges.get(queue) + srcindices = srcindices.get(queue) + srcranges = srcranges.get(queue) + if visualize: if ndim == 2: import matplotlib.pyplot as pt -- GitLab From cb7bea8f7e0cc9fdbd58f98d0fe35cf272fa13ce Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 10:01:49 -0500 Subject: [PATCH 159/268] add missing __init__ --- pytential/linalg/__init__.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 pytential/linalg/__init__.py diff --git a/pytential/linalg/__init__.py b/pytential/linalg/__init__.py new file mode 100644 index 00000000..b4a471df --- /dev/null +++ b/pytential/linalg/__init__.py @@ -0,0 +1,23 @@ +from __future__ import division + +__copyright__ = "Copyright (C) 2018 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. +""" -- GitLab From cd019e2643c36df89818738bd9853c73d237487d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 11:44:30 -0500 Subject: [PATCH 160/268] direct-solver: clean up proxy tests a bit --- pytential/linalg/proxy.py | 12 +- test/test_linalg_proxy.py | 324 +++++++++++++++++--------------------- 2 files changed, 153 insertions(+), 183 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index d00dd410..fd29315c 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -566,6 +566,8 @@ class ProxyGenerator(object): skeletons[idim, sklstart + npxyblock + ingb] = \ sources[idim, neighbors[ngbstart + ingb]] \ {id_prefix=write_ngb,nosync=write_pxy} + sklranges[irange + 1] = sklranges[irange] + \ + npxyblock + nngbblock end """, [ @@ -581,6 +583,8 @@ class ProxyGenerator(object): shape="nranges + 1"), lp.GlobalArg("skeletons", None, shape=(self.dim, "nproxies + nneighbors")), + lp.GlobalArg("sklranges", None, + shape="nranges + 1"), lp.ValueArg("nsources", np.int32), lp.ValueArg("nproxies", np.int32), lp.ValueArg("nneighbors", np.int32), @@ -605,11 +609,11 @@ class ProxyGenerator(object): self.get_neighbors(indices, ranges, centers, radii) # construct joint array - _, (skeletons,) = knl()(self.queue, + sklranges = cl.array.zeros(self.queue, ranges.shape, dtype=np.int) + _, (skeletons, sklranges) = knl()(self.queue, sources=sources, proxies=proxies, neighbors=neighbors, - pxyranges=pxyranges, nbrranges=nbrranges) - sklranges = np.array([p + n for p, n in zip(pxyranges.get(self.queue), - nbrranges.get(self.queue))]) + pxyranges=pxyranges, nbrranges=nbrranges, + sklranges=sklranges) return skeletons, sklranges diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index 915cc4b8..e3c6fdab 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -40,89 +40,70 @@ from pyopencl.tools import ( # noqa as pytest_generate_tests) -def create_data(queue, +def _build_qbx_discr(queue, + ndim=2, + nelements=30, target_order=7, qbx_order=4, - nelements=30, - curve_f=None, - ndim=2, k=0, - lpot_idx=2): + curve_f=None): if curve_f is None: curve_f = NArmedStarfish(5, 0.25) - # curve_f = partial(ellipse, 3) - - from sumpy.kernel import LaplaceKernel, HelmholtzKernel - if k: - knl = HelmholtzKernel(ndim) - knl_kwargs = {"k": k} - else: - knl = LaplaceKernel(ndim) - knl_kwargs = {} - - if lpot_idx == 1: - u_sym = sym.var("u") - op = sym.D(knl, u_sym, **knl_kwargs) - else: - u_sym = sym.var("u") - op = sym.S(knl, u_sym, **knl_kwargs) if ndim == 2: mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements + 1), target_order) - else: + elif ndim == 3: mesh = generate_torus(10.0, 2.0, order=target_order) + else: + raise ValueError("unsupported ambient dimension") from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource - pre_density_discr = Discretization( + density_discr = Discretization( queue.context, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4 * target_order, - qbx_order, - # Don't use FMM for now + qbx, _ = QBXLayerPotentialSource(density_discr, + fine_order=4 * target_order, + qbx_order=qbx_order, fmm_order=False).with_refinement() - return qbx, op, u_sym + return qbx + +def _build_block_index(queue, discr, + nblks=10, + factor=1.0, + method='elements', + use_tree=True): -def create_indices(queue, qbx, nblks, - factor=1.0, - random=True, - method='elements', - use_tree=True, - use_stage2=False): from pytential.linalg.proxy import ( partition_by_nodes, partition_by_elements) if method == 'elements': factor = 1.0 - if use_stage2: - density_discr = qbx.quad_stage2_density_discr - else: - density_discr = qbx.density_discr - if method == 'nodes': - nnodes = density_discr.nnodes + nnodes = discr.nnodes else: - nnodes = density_discr.mesh.nelements + nnodes = discr.mesh.nelements max_particles_in_box = nnodes // nblks + # create index ranges if method == "nodes": - indices, ranges = partition_by_nodes(queue, density_discr, + indices, ranges = partition_by_nodes(queue, discr, use_tree=use_tree, max_particles_in_box=max_particles_in_box) elif method == "elements": - indices, ranges = partition_by_elements(queue, density_discr, + indices, ranges = partition_by_elements(queue, discr, use_tree=use_tree, max_particles_in_box=max_particles_in_box) else: raise ValueError("unknown method: {}".format(method)) - # take a subset of the points + # randomly pick a subset of points if abs(factor - 1.0) > 1.0e-14: indices = indices.get(queue) ranges = ranges.get(queue) @@ -132,11 +113,8 @@ def create_indices(queue, qbx, nblks, iidx = indices[np.s_[ranges[i]:ranges[i + 1]]] isize = int(factor * len(iidx)) - if random: - indices_[i] = np.sort(np.random.choice(iidx, - size=isize, replace=False)) - else: - indices_[i] = iidx[:isize] + indices_[i] = np.sort( + np.random.choice(iidx, size=isize, replace=False)) ranges = to_device(queue, np.cumsum([0] + [r.shape[0] for r in indices_])) @@ -145,110 +123,121 @@ def create_indices(queue, qbx, nblks, return indices, ranges -@pytest.mark.parametrize("method", ["nodes", "elements"]) -@pytest.mark.parametrize("use_tree", [True, False]) -@pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): - def _plot_indices(pid, discr, indices, ranges): - import matplotlib.pyplot as pt +def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): + import matplotlib.pyplot as pt + args = [ + kwargs.get("method", "unknown"), + "tree" if kwargs.get("use_tree", False) else "linear", + kwargs.get("pid", "stage1"), + discr.ambient_dim + ] - pt.figure(figsize=(10, 8)) - pt.plot(np.diff(ranges)) + if isinstance(indices, cl.array.Array): + indices = indices.get() + ranges = ranges.get() - rangefile = "test_partition_{}_{}_ranges_{}.png".format(method, - "tree" if use_tree else "linear", pid) - pt.savefig(rangefile, dpi=300) - pt.clf() + pt.figure(figsize=(10, 8), dpi=300) + pt.plot(np.diff(ranges)) + pt.savefig("test_partition_{0}_{1}_{3}d_ranges_{2}.png".format(*args)) + pt.clf() - if ndim == 2: - sources = discr.nodes().get(queue) + if discr.ambient_dim == 2: + sources = discr.nodes().get(queue) - pt.figure(figsize=(10, 8)) + pt.figure(figsize=(10, 8), dpi=300) + + if indices.shape[0] != discr.nnodes: pt.plot(sources[0], sources[1], 'ko', alpha=0.5) - for i in range(nblks): - isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] - pt.plot(sources[0][isrc], sources[1][isrc], 'o') - pt.xlim([-1.5, 1.5]) - pt.ylim([-1.5, 1.5]) - - pointfile = "test_partition_{}_{}_{}d_{}.png".format(method, - "tree" if use_tree else "linear", ndim, pid) - pt.savefig(pointfile, dpi=300) - pt.clf() - elif ndim == 3: - from meshmode.discretization import NoninterpolatoryElementGroupError - try: - discr.groups[0].basis() - except NoninterpolatoryElementGroupError: - return + for i in range(ranges.shape[0] - 1): + isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + pt.plot(sources[0][isrc], sources[1][isrc], 'o') - from meshmode.discretization.visualization import make_visualizer - marker = -42.0 * np.ones(discr.nnodes) + pt.xlim([-1.5, 1.5]) + pt.ylim([-1.5, 1.5]) + pt.savefig("test_partition_{0}_{1}_{3}d_{2}.png".format(*args)) + pt.clf() + elif discr.ambient_dim == 3: + from meshmode.discretization import NoninterpolatoryElementGroupError + try: + discr.groups[0].basis() + except NoninterpolatoryElementGroupError: + return + + from meshmode.discretization.visualization import make_visualizer + marker = -42.0 * np.ones(discr.nnodes) - for i in range(nblks): - isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] - marker[isrc] = 10.0 * (i + 1.0) + for i in range(ranges.shape[0] - 1): + isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + marker[isrc] = 10.0 * (i + 1.0) + + vis = make_visualizer(queue, discr, 10) - vis = make_visualizer(queue, discr, 10) + filename = "test_partition_{0}_{1}_{3}d_{2}.png".format(*args) + if os.path.isfile(filename): + os.remove(filename) - pointfile = "test_partition_{}_{}_{}d_{}.vtu".format(method, - "tree" if use_tree else "linear", ndim, pid) - if os.path.isfile(pointfile): - os.remove(pointfile) + vis.write_vtk_file(filename, [ + ("marker", cl.array.to_device(queue, marker)) + ]) - vis.write_vtk_file(pointfile, [ - ("marker", cl.array.to_device(queue, marker)) - ]) +@pytest.mark.parametrize("method", ["nodes", "elements"]) +@pytest.mark.parametrize("use_tree", [True, False]) +@pytest.mark.parametrize("ndim", [2, 3]) +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - from sympy.core.cache import clear_cache - clear_cache() - qbx = create_data(queue, ndim=ndim)[0] + qbx = _build_qbx_discr(queue, ndim=ndim) + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + method=method, use_tree=use_tree, factor=0.6) - nblks = 10 - t_start = time.time() - srcindices, srcranges = create_indices(queue, qbx, nblks, - method=method, use_tree=use_tree, factor=1.0) - nblks = srcranges.shape[0] - 1 - t_end = time.time() - if visualize: - print('Time: {:.5f}s'.format(t_end - t_start)) + +@pytest.mark.parametrize("use_tree", [True, False]) +@pytest.mark.parametrize("ndim", [2, 3]) +def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + qbx = _build_qbx_discr(queue, ndim=ndim) + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + method="elements", use_tree=use_tree) if visualize: discr = qbx.resampler.from_discr - _plot_indices(0, discr, srcindices.get(queue), srcranges.get(queue)) + _plot_partition_indices(queue, discr, srcindices, srcranges, + method="elements", use_tree=use_tree, pid="stage1") - if method == "elements": - from pytential.linalg.proxy import partition_from_coarse - resampler = qbx.direct_resampler + from pytential.linalg.proxy import partition_from_coarse + resampler = qbx.direct_resampler - t_start = time.time() - srcindices_, srcranges_ = \ - partition_from_coarse(queue, resampler, srcindices, srcranges) - t_end = time.time() - if visualize: - print('Time: {:.5f}s'.format(t_end - t_start)) + t_start = time.time() + srcindices_, srcranges_ = \ + partition_from_coarse(queue, resampler, srcindices, srcranges) + t_end = time.time() + if visualize: + print('Time: {:.5f}s'.format(t_end - t_start)) + + srcindices = srcindices.get() + srcranges = srcranges.get() + srcindices_ = srcindices_.get() + srcranges_ = srcranges_.get() - srcindices = srcindices.get(queue) - srcranges = srcranges.get(queue) - srcindices_ = srcindices_.get(queue) - srcranges_ = srcranges_.get(queue) + sources = resampler.from_discr.nodes().get(queue) + sources_ = resampler.to_discr.nodes().get(queue) - sources = resampler.from_discr.nodes().get(queue) - sources_ = resampler.to_discr.nodes().get(queue) - for i in range(srcranges.shape[0] - 1): - isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - isrc_ = srcindices_[np.s_[srcranges_[i]:srcranges_[i + 1]]] + for i in range(srcranges.shape[0] - 1): + isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + isrc_ = srcindices_[np.s_[srcranges_[i]:srcranges_[i + 1]]] - for j in range(ndim): - assert np.min(sources_[j][isrc_]) <= np.min(sources[j][isrc]) - assert np.max(sources_[j][isrc_]) >= np.max(sources[j][isrc]) + for j in range(ndim): + assert np.min(sources_[j][isrc_]) <= np.min(sources[j][isrc]) + assert np.max(sources_[j][isrc_]) >= np.max(sources[j][isrc]) - if visualize: - discr = resampler.to_discr - _plot_indices(1, discr, srcindices_, srcranges_) + if visualize: + discr = resampler.to_discr + _plot_partition_indices(queue, discr, srcindices_, srcranges_, + method="elements", use_tree=use_tree, pid="stage2") @pytest.mark.parametrize("ndim", [2, 3]) @@ -256,39 +245,22 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - # prevent cache explosion - from sympy.core.cache import clear_cache - clear_cache() - qbx = create_data(queue, ndim=ndim)[0] - - nblks = 10 - srcindices, srcranges = create_indices(queue, qbx, nblks, - method='nodes', factor=0.6) - nblks = srcranges.shape[0] - 1 + qbx = _build_qbx_discr(queue, ndim=ndim) + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + method='nodes', factor=0.6) from pytential.linalg.proxy import ProxyGenerator gen = ProxyGenerator(queue, qbx, ratio=1.1) - centers, radii, proxies, pxyranges = \ - gen.get_proxies(srcindices, srcranges) + centers, radii, proxies, pxyranges = gen.get_proxies(srcindices, srcranges) if visualize: - knl = gen.get_kernel() - knl = lp.add_dtypes(knl, { - "nodes": np.float64, - "center_int": np.float64, - "center_ext": np.float64, - "expansion_radii": np.float64, - "ranges": np.int64, - "indices": np.int64, - "nnodes": np.int64, - "nranges": np.int64, - "nindices": np.int64}) - print(knl) - print(lp.generate_code_v2(knl).device_code()) - - proxies = np.vstack([p.get(queue) for p in proxies]) - srcindices = srcindices.get(queue) - srcranges = srcranges.get(queue) + srcindices = srcindices.get() + srcranges = srcranges.get() + + centers = np.vstack([c.get() for c in centers]) + radii = radii.get() + proxies = np.vstack([p.get() for p in proxies]) + pxyranges = pxyranges.get() if visualize: if qbx.ambient_dim == 2: @@ -301,9 +273,8 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): ce = get_centers_on_side(qbx, +1) ce = np.vstack([c.get(queue) for c in ce]) r = qbx._expansion_radii("nsources").get(queue) - pxyranges = pxyranges.get(queue) - for i in range(nblks): + for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] @@ -337,10 +308,7 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - centers = np.vstack([c.get(queue) for c in centers]) - radii = radii.get(queue) - - for i in range(nblks): + for i in range(srcranges.shape[0] - 1): mesh = affine_map(gen.mesh, A=(radii[i] * np.eye(ndim)), b=centers[:, i].reshape(-1)) @@ -361,34 +329,32 @@ def test_area_query(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) - from sympy.core.cache import clear_cache - clear_cache() - qbx = create_data(queue, ndim=ndim)[0] - - nblks = 10 - srcindices, srcranges = create_indices(queue, qbx, nblks, - method='nodes', factor=0.6) - nblks = srcranges.shape[0] - 1 + qbx = _build_qbx_discr(queue, ndim=ndim) + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + method='nodes', factor=0.6) # generate proxy points from pytential.linalg.proxy import ProxyGenerator gen = ProxyGenerator(queue, qbx, ratio=1.1) centers, radii, _, _ = gen.get_proxies(srcindices, srcranges) neighbors, ngbranges = gen.get_neighbors(srcindices, srcranges, centers, radii) - skeleton_nodes, sklranges = gen(srcindices, srcranges) + skeletons, sklranges = gen(srcindices, srcranges) + + if visualize: + srcindices = srcindices.get() + srcranges = srcranges.get() - neighbors = neighbors.get(queue) - ngbranges = ngbranges.get(queue) - srcindices = srcindices.get(queue) - srcranges = srcranges.get(queue) + neighbors = neighbors.get() + ngbranges = ngbranges.get() if visualize: if ndim == 2: import matplotlib.pyplot as pt density_nodes = qbx.density_discr.nodes().get(queue) - skeleton_nodes = skeleton_nodes.get(queue) + skeletons = skeletons.get(queue) + sklranges = sklranges.get(queue) - for i in range(nblks): + for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] iskl = np.s_[sklranges[i]:sklranges[i + 1]] @@ -402,7 +368,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): 'o', ms=2.0) pt.plot(density_nodes[0, ingb], density_nodes[1, ingb], 'o', ms=2.0) - pt.plot(skeleton_nodes[0, iskl], skeleton_nodes[1, iskl], + pt.plot(skeletons[0, iskl], skeletons[1, iskl], 'x', ms=2.0) pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) @@ -414,7 +380,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): from meshmode.discretization.visualization import make_visualizer marker = np.empty(qbx.density_discr.nnodes) - for i in range(nblks): + for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] -- GitLab From 5b113f48ec4a0d56f575b7566eaa0b6e7eddcc0f Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 15:27:40 -0500 Subject: [PATCH 161/268] direct-solver-proxy: split up proxy generator into smaller functions --- pytential/linalg/proxy.py | 412 ++++++++++++++++++++------------------ test/test_linalg_proxy.py | 74 ++++--- 2 files changed, 264 insertions(+), 222 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index fd29315c..2bd1aa3d 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -30,8 +30,10 @@ import pyopencl as cl import pyopencl.array # noqa from pyopencl.array import to_device +from boxtree.tools import DeviceDataRecord + from pytools.obj_array import make_obj_array -from pytools import memoize_method, memoize_in +from pytools import memoize_method, memoize import loopy as lp from loopy.version import MOST_RECENT_LANGUAGE_VERSION @@ -41,13 +43,14 @@ __doc__ = """ Proxy Point Generation ~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: ProxyGenerator + .. autofunction:: partition_by_nodes .. autofunction:: partition_by_elements .. autofunction:: partition_from_coarse -.. autoclass:: ProxyGenerator """ @@ -267,10 +270,9 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): # {{{ proxy point generator class ProxyGenerator(object): - def __init__(self, queue, source, ratio=1.5, nproxy=31): + def __init__(self, source, ratio=1.5, nproxy=30, **kwargs): r""" - :arg queue: a :class:`pyopencl.CommandQueue`. - :arg source: a :class:`pytential.qbx.LayerPotentialSourceBase`. + :arg discr: a :class:`pytential.qbx.QBXLayerPotentialBase`. :arg ratio: a ratio used to compute the proxy point radius. The radius is computed in the :math:`L_2` norm, resulting in a circle or sphere of proxy points. For QBX, we have two radii of interest @@ -293,23 +295,21 @@ class ProxyGenerator(object): :arg nproxy: number of proxy points. """ - self.queue = queue self.source = source - self.context = self.queue.context self.ratio = abs(ratio) self.nproxy = int(abs(nproxy)) - self.dim = self.source.ambient_dim + self.ambient_dim = source.density_discr.ambient_dim - if self.dim == 2: + if self.ambient_dim == 2: from meshmode.mesh.generation import ellipse, make_curve_mesh - self.mesh = make_curve_mesh(lambda t: ellipse(1.0, t), - np.linspace(0.0, 1.0, self.nproxy + 1), - self.nproxy) - elif self.dim == 3: + self.ref_mesh = make_curve_mesh(lambda t: ellipse(1.0, t), + np.linspace(0.0, 1.0, self.nproxy + 1), + self.nproxy) + elif self.ambient_dim == 3: from meshmode.mesh.generation import generate_icosphere - self.mesh = generate_icosphere(1, self.nproxy) + self.ref_mesh = generate_icosphere(1, self.nproxy) else: raise ValueError("unsupported ambient dimension") @@ -321,7 +321,7 @@ class ProxyGenerator(object): radius_expr = "ratio * rqbx" # NOTE: centers of mass are computed using a second-order approximation - # that currently matches what's in `element_centers_of_mass`. + # that currently matches what is in `element_centers_of_mass`. knl = lp.make_kernel([ "{[irange]: 0 <= irange < nranges}", "{[i]: 0 <= i < npoints}", @@ -329,23 +329,23 @@ class ProxyGenerator(object): ], [""" for irange - <> npoints = ranges[irange + 1] - ranges[irange] + <> npoints = srcranges[irange + 1] - srcranges[irange] proxy_center[idim, irange] = 1.0 / npoints * \ - reduce(sum, i, nodes[idim, indices[i + ranges[irange]]]) \ + reduce(sum, i, nodes[idim, srcindices[i + srcranges[irange]]]) \ {{dup=idim:i}} <> rblk = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - nodes[idim, indices[i + ranges[irange]]]) ** 2))) + nodes[idim, srcindices[i + srcranges[irange]]]) ** 2))) <> rqbx_int = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_int[idim, indices[i + ranges[irange]]]) ** 2)) + \ - expansion_radii[indices[i + ranges[irange]]]) + center_int[idim, srcindices[i + srcranges[irange]]]) ** 2)) + \ + expansion_radii[srcindices[i + srcranges[irange]]]) <> rqbx_ext = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_ext[idim, indices[i + ranges[irange]]]) ** 2)) + \ - expansion_radii[indices[i + ranges[irange]]]) + center_ext[idim, srcindices[i + srcranges[irange]]]) ** 2)) + \ + expansion_radii[srcindices[i + srcranges[irange]]]) <> rqbx = if(rqbx_ext < rqbx_int, rqbx_int, rqbx_ext) proxy_radius[irange] = {radius_expr} @@ -353,19 +353,19 @@ class ProxyGenerator(object): """.format(radius_expr=radius_expr)], [ lp.GlobalArg("nodes", None, - shape=(self.dim, "nnodes")), + shape=(self.ambient_dim, "nnodes")), lp.GlobalArg("center_int", None, - shape=(self.dim, "nnodes"), dim_tags="sep,C"), + shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), lp.GlobalArg("center_ext", None, - shape=(self.dim, "nnodes"), dim_tags="sep,C"), + shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), lp.GlobalArg("expansion_radii", None, shape="nnodes"), - lp.GlobalArg("ranges", None, + lp.GlobalArg("srcranges", None, shape="nranges + 1"), - lp.GlobalArg("indices", None, + lp.GlobalArg("srcindices", None, shape="nindices"), lp.GlobalArg("proxy_center", None, - shape=(self.dim, "nranges")), + shape=(self.ambient_dim, "nranges")), lp.GlobalArg("proxy_radius", None, shape="nranges"), lp.ValueArg("nnodes", np.int64), @@ -375,7 +375,7 @@ class ProxyGenerator(object): name="proxy_generator_knl", assumptions="dim>=1 and nranges>=1", fixed_parameters=dict( - dim=self.dim, + dim=self.ambient_dim, ratio=self.ratio), lang_version=MOST_RECENT_LANGUAGE_VERSION) @@ -390,23 +390,24 @@ class ProxyGenerator(object): return knl - def get_proxies(self, indices, ranges, **kwargs): + def __call__(self, queue, srcindices, srcranges, **kwargs): """Generate proxy points for each given range of source points in - the discretization :attr:`source`. + the discretization in :attr:`source`. - :arg indices: a :class:`pyopencl.array.Array` of indices around + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg srcindices: a :class:`pyopencl.array.Array` of srcindices around which to construct proxy balls. - :arg ranges: an :class:`pyopencl.array.Array` of size `(nranges + 1,)` - used to index into :attr:`indices`. Each one of the `nranges` + :arg srcranges: an :class:`pyopencl.array.Array` of size `(nranges + 1,)` + used to index into :attr:`srcindices`. Each one of the `nranges` ranges will get a proxy ball. - :return: a tuple of `(centers, radii, proxies, pryranges)`, where + :return: a tuple of `(centers, radii, proxies, pxyranges)`, where each value is a :class:`pyopencl.array.Array`. The sizes of the arrays are as follows: `centers` is of size - `(2, nranges)`, `radii` is of size `(nranges,)`, `pryranges` is + `(2, nranges)`, `radii` is of size `(nranges,)`, `pxyranges` is of size `(nranges + 1,)` and `proxies` is of size `(2, nranges * nproxies)`. The proxy points in a range :math:`i` - can be obtained by a slice `proxies[pryranges[i]:pryranges[i + 1]]` + can be obtained by a slice `proxies[pxyranges[i]:pxyranges[i + 1]]` and are all at a distance `radii[i]` from the range center `centers[i]`. """ @@ -414,86 +415,100 @@ class ProxyGenerator(object): from pytential.qbx.utils import get_centers_on_side knl = self.get_kernel() - _, (centers_dev, radii_dev,) = knl(self.queue, + _, (centers_dev, radii_dev,) = knl(queue, nodes=self.source.density_discr.nodes(), center_int=get_centers_on_side(self.source, -1), center_ext=get_centers_on_side(self.source, +1), expansion_radii=self.source._expansion_radii("nsources"), - indices=indices, ranges=ranges, **kwargs) + srcindices=srcindices, srcranges=srcranges, **kwargs) centers = centers_dev.get() radii = radii_dev.get() from meshmode.mesh.processing import affine_map - proxies = np.empty(ranges.shape[0] - 1, dtype=np.object) + proxies = np.empty(srcranges.shape[0] - 1, dtype=np.object) - for i in range(ranges.shape[0] - 1): - mesh = affine_map(self.mesh, - A=(radii[i] * np.eye(self.dim)), + for i in range(srcranges.shape[0] - 1): + mesh = affine_map(self.ref_mesh, + A=(radii[i] * np.eye(self.ambient_dim)), b=centers[:, i].reshape(-1)) proxies[i] = mesh.vertices - pxyranges = np.cumsum([0] + [p.shape[-1] for p in proxies]) - pxyranges = cl.array.to_device(self.queue, pxyranges) + pxyranges = cl.array.arange(queue, 0, + proxies.shape[0] * proxies[0].shape[-1] + 1, proxies[0].shape[-1], + dtype=srcranges.dtype) proxies = make_obj_array([ - cl.array.to_device(self.queue, np.hstack([p[idim] for p in proxies])) - for idim in range(self.dim)]) - centers_dev = make_obj_array([ - centers_dev[idim].with_queue(self.queue).copy() - for idim in range(self.dim)]) - - return centers_dev, radii_dev, proxies, pxyranges - - def get_neighbors(self, indices, ranges, centers, radii): - """Generate a set of neighboring points for each range of source - points in :attr:`source`. Neighboring points are defined as all - the points inside a proxy ball :math:`i` that do not also belong to - the set of source points in the same range :math:`i`. - - :arg indices: a :class:`pyopencl.array.Array` of indices for a subset - of the source points. - :arg ranges: a :class:`pyopencl.array.Array` used to index into - the :attr:`indices` array. - :arg centers: a :class:`pyopencl.array.Array` containing the center - of each proxy ball. - :arg radii: a :class:`pyopencl.array.Array` containing the radius - of each proxy ball. - - :return: a tuple `(neighbours, nbrranges)` where each value is a - :class:`pyopencl.array.Array` of integers. For a range :math:`i` - in `nbrranges`, the corresponding slice of the `neighbours` array - is a subset of :attr:`indices` such that all points are inside - the proxy ball centered at `centers[i]` of radius `radii[i]` - that is not included in `indices[ranges[i]:ranges[i + 1]]`. - """ - - if isinstance(indices, cl.array.Array): - indices = indices.get(self.queue) - ranges = ranges.get(self.queue) + cl.array.to_device(queue, np.hstack([p[idim] for p in proxies])) + for idim in range(self.ambient_dim)]) + centers = make_obj_array([ + centers_dev[idim].with_queue(queue).copy() + for idim in range(self.ambient_dim)]) + + assert pxyranges[-1] == proxies[0].shape[0] + return proxies, pxyranges, centers, radii_dev + + +def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, + max_nodes_in_box=30, **kwargs): + """Generate a set of neighboring points for each range of points in + :attr:`discr`. Neighboring points of a range :math:`i` are defined + as all the points inside the proxy ball :math:`i` that do not also + belong to the range itself. + + :arg discr: a :class:`meshmode.discretization.Discretization`. + :arg srcindices: an array of indices for a subset of the nodes in + :attr:`discr`. + :arg srcranges: an array used to index into the :attr:`srcindices` array. + :arg pxycenters: an array containing the center of each proxy ball. + :arg pxyradii: an array containing the radius of each proxy ball. + + :return: a tuple `(neighbours, nbrranges)` where each value is a + :class:`pyopencl.array.Array` of integers. For a range :math:`i` + in `nbrranges`, the corresponding slice of the `neighbours` array + is a subset of :attr:`srcindices` such that all points are inside + the proxy ball centered at `centers[i]` of radius `radii[i]` + that is not included in `srcindices[srcranges[i]:srcranges[i + 1]]`. + """ - nranges = radii.shape[0] + 1 - sources = self.source.density_discr.nodes().get(self.queue) + with cl.CommandQueue(discr.cl_context) as queue: + if isinstance(srcindices, cl.array.Array): + srcindices = srcindices.get(queue) + if isinstance(srcranges, cl.array.Array): + srcranges = srcranges.get(queue) + + # NOTE: this is used for multiple reasons: + # * TreeBuilder takes object arrays + # * `srcndices` can be a small subset of nodes, so this will save + # some work + # * `srcindices` may reorder the array returned by nodes(), so this + # makes sure that we have the same order in tree.user_source_ids + # and friends + sources = discr.nodes().get(queue) sources = make_obj_array([ - cl.array.to_device(self.queue, sources[idim, indices]) - for idim in range(self.dim)]) + cl.array.to_device(queue, sources[idim, srcindices]) + for idim in range(discr.ambient_dim)]) # construct tree from boxtree import TreeBuilder - builder = TreeBuilder(self.context) - tree, _ = builder(self.queue, sources, max_particles_in_box=30) + builder = TreeBuilder(discr.cl_context) + tree, _ = builder(queue, sources, + max_particles_in_box=max_nodes_in_box) from boxtree.area_query import AreaQueryBuilder - builder = AreaQueryBuilder(self.context) - query, _ = builder(self.queue, tree, centers, radii) + builder = AreaQueryBuilder(discr.cl_context) + query, _ = builder(queue, tree, pxycenters, pxyradii) # find nodes inside each proxy ball - tree = tree.get(self.queue) - query = query.get(self.queue) - centers_ = np.vstack([centers[idim].get(self.queue) - for idim in range(self.dim)]) - radii_ = radii.get(self.queue) - - neighbors = np.empty(nranges - 1, dtype=np.object) - for iproxy in range(nranges - 1): + tree = tree.get(queue) + query = query.get(queue) + + if isinstance(pxycenters[0], cl.array.Array): + pxycenters = np.vstack([pxycenters[idim].get(queue) + for idim in range(discr.ambient_dim)]) + if isinstance(pxyradii, cl.array.Array): + pxyradii = pxyradii.get(queue) + + neighbors = np.empty(srcranges.shape[0] - 1, dtype=np.object) + for iproxy in range(srcranges.shape[0] - 1): # get list of boxes intersecting the current ball istart = query.leaves_near_ball_starts[iproxy] iend = query.leaves_near_ball_starts[iproxy + 1] @@ -505,117 +520,124 @@ class ProxyGenerator(object): isources = np.hstack([np.arange(s, e) for s, e in zip(istart, iend)]) nodes = np.vstack([tree.sources[idim][isources] - for idim in range(self.dim)]) + for idim in range(discr.ambient_dim)]) isources = tree.user_source_ids[isources] # get nodes inside the ball but outside the current range - center = centers_[:, iproxy].reshape(-1, 1) - radius = radii_[iproxy] + center = pxycenters[:, iproxy].reshape(-1, 1) + radius = pxyradii[iproxy] mask = (la.norm(nodes - center, axis=0) < radius) & \ - ((isources < ranges[iproxy]) | (ranges[iproxy + 1] <= isources)) + ((isources < srcranges[iproxy]) | (srcranges[iproxy + 1] <= isources)) - neighbors[iproxy] = indices[isources[mask]] + neighbors[iproxy] = srcindices[isources[mask]] - nbrranges = np.cumsum([0] + [n.shape[0] for n in neighbors]) - neighbors = np.hstack(neighbors) + nbrranges = to_device(queue, + np.cumsum([0] + [n.shape[0] for n in neighbors])) + neighbors = to_device(queue, np.hstack(neighbors)) - return (to_device(self.queue, neighbors), - to_device(self.queue, nbrranges)) + return neighbors, nbrranges - def __call__(self, indices, ranges, **kwargs): - """ - :arg indices: a :class:`pyopencl.array.Array` of indices for points - in :attr:`source`. - :arg ranges: a :class:`pyopencl.array.Array` describing the ranges - from :attr:`indices` around which to build proxy points. For each - range, this builds a ball of proxy points centered - at the center of mass of the points in the range with a radius - defined by :attr:`ratio`. - - :returns: a tuple `(skeletons, sklranges)` where each value is a - :class:`pyopencl.array.Array`. For a range :math:`i`, we can - get the slice using `skeletons[sklranges[i]:sklranges[i + 1]]`. - The skeleton points in a range represents the union of a set - of generated proxy points and all the source points inside the - proxy ball that do not also belong to the current range in - :attr:`indices`. - """ - @memoize_in(self, "concat_skl_knl") - def knl(): - loopy_knl = lp.make_kernel([ - "{[irange, idim]: 0 <= irange < nranges and \ - 0 <= idim < dim}", - "{[ipxy, ingb]: 0 <= ipxy < npxyblock and \ - 0 <= ingb < nngbblock}" - ], - """ - for irange - <> pxystart = pxyranges[irange] - <> pxyend = pxyranges[irange + 1] - <> npxyblock = pxyend - pxystart - - <> ngbstart = nbrranges[irange] - <> ngbend = nbrranges[irange + 1] - <> nngbblock = ngbend - ngbstart - - <> sklstart = pxyranges[irange] + nbrranges[irange] - skeletons[idim, sklstart + ipxy] = \ - proxies[idim, pxystart + ipxy] \ - {id_prefix=write_pxy,nosync=write_ngb} - skeletons[idim, sklstart + npxyblock + ingb] = \ - sources[idim, neighbors[ngbstart + ingb]] \ - {id_prefix=write_ngb,nosync=write_pxy} - sklranges[irange + 1] = sklranges[irange] + \ - npxyblock + nngbblock - end - """, - [ - lp.GlobalArg("sources", None, - shape=(self.dim, "nsources")), - lp.GlobalArg("proxies", None, - shape=(self.dim, "nproxies"), dim_tags="sep,C"), - lp.GlobalArg("neighbors", None, - shape="nneighbors"), - lp.GlobalArg("pxyranges", None, - shape="nranges + 1"), - lp.GlobalArg("nbrranges", None, - shape="nranges + 1"), - lp.GlobalArg("skeletons", None, - shape=(self.dim, "nproxies + nneighbors")), - lp.GlobalArg("sklranges", None, - shape="nranges + 1"), - lp.ValueArg("nsources", np.int32), - lp.ValueArg("nproxies", np.int32), - lp.ValueArg("nneighbors", np.int32), - "..." - ], - name="concat_skl", - default_offset=lp.auto, - silenced_warnings="write_race(write_*)", - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) - - loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.split_iname(loopy_knl, "irange", 128, outer_tag="g.0") - - return loopy_knl - - # construct point arrays - sources = self.source.density_discr.nodes() - centers, radii, proxies, pxyranges = \ - self.get_proxies(indices, ranges, **kwargs) - neighbors, nbrranges = \ - self.get_neighbors(indices, ranges, centers, radii) - - # construct joint array - sklranges = cl.array.zeros(self.queue, ranges.shape, dtype=np.int) - _, (skeletons, sklranges) = knl()(self.queue, - sources=sources, proxies=proxies, neighbors=neighbors, - pxyranges=pxyranges, nbrranges=nbrranges, +def build_skeleton_list(source, srcindices, srcranges, **kwargs): + """ + :arg source: a :class:`pytential.qbx.QBXLayerPotentialBase`. + :arg srcindices: a :class:`pyopencl.array.Array` of indices for points + in :attr:`source`. + :arg srcranges: a :class:`pyopencl.array.Array` describing the ranges + from :attr:`srcindices` around which to build proxy points. For each + range, this builds a ball of proxy points centered + at the center of mass of the points in the range with a radius + defined by :attr:`ratio`. + :arg kwargs: additional arguments passed to :class:`ProxyGenerator` + or :func:`build_neighbor_list`. + + :returns: a tuple `(skeletons, sklranges)` where each value is a + :class:`pyopencl.array.Array`. For a range :math:`i`, we can + get the slice using `skeletons[sklranges[i]:sklranges[i + 1]]`. + The skeleton points in a range represents the union of a set + of generated proxy points and all the source points inside the + proxy ball that do not also belong to the current range in + :attr:`indices`. + """ + + @memoize + def knl(): + loopy_knl = lp.make_kernel([ + "{[irange, idim]: 0 <= irange < nranges and \ + 0 <= idim < dim}", + "{[ipxy, ingb]: 0 <= ipxy < npxyblock and \ + 0 <= ingb < nngbblock}" + ], + """ + for irange + <> pxystart = pxyranges[irange] + <> pxyend = pxyranges[irange + 1] + <> npxyblock = pxyend - pxystart + + <> ngbstart = nbrranges[irange] + <> ngbend = nbrranges[irange + 1] + <> nngbblock = ngbend - ngbstart + + <> sklstart = pxyranges[irange] + nbrranges[irange] + skeletons[idim, sklstart + ipxy] = \ + proxies[idim, pxystart + ipxy] \ + {id_prefix=write_pxy,nosync=write_ngb} + skeletons[idim, sklstart + npxyblock + ingb] = \ + sources[idim, neighbors[ngbstart + ingb]] \ + {id_prefix=write_ngb,nosync=write_pxy} + sklranges[irange + 1] = sklranges[irange] + \ + npxyblock + nngbblock + end + """, + [ + lp.GlobalArg("sources", None, + shape=(source.ambient_dim, "nsources")), + lp.GlobalArg("proxies", None, + shape=(source.ambient_dim, "nproxies"), dim_tags="sep,C"), + lp.GlobalArg("neighbors", None, + shape="nneighbors"), + lp.GlobalArg("pxyranges", None, + shape="nranges + 1"), + lp.GlobalArg("nbrranges", None, + shape="nranges + 1"), + lp.GlobalArg("skeletons", None, + shape=(source.ambient_dim, "nproxies + nneighbors")), + lp.GlobalArg("sklranges", None, + shape="nranges + 1"), + lp.ValueArg("nsources", np.int32), + lp.ValueArg("nproxies", np.int32), + lp.ValueArg("nneighbors", np.int32), + "..." + ], + name="concat_skl", + default_offset=lp.auto, + silenced_warnings="write_race(write_*)", + fixed_parameters=dict(dim=source.ambient_dim), + lang_version=MOST_RECENT_LANGUAGE_VERSION) + + loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") + loopy_knl = lp.split_iname(loopy_knl, "irange", 128, outer_tag="g.0") + + return loopy_knl + + with cl.CommandQueue(source.cl_context) as queue: + proxy = ProxyGenerator(source, **kwargs) + proxies, pxyranges, pxycenters, pxyradii = \ + proxy(queue, srcindices, srcranges) + + neighbors, nbrranges = build_neighbor_list(source.density_discr, + srcindices, srcranges, pxycenters, pxyradii, **kwargs) + + sklranges = cl.array.zeros(queue, srcranges.shape, dtype=np.int) + _, (skeletons, sklranges) = knl()(queue, + sources=source.density_discr.nodes(), + proxies=proxies, + neighbors=neighbors, + pxyranges=pxyranges, + nbrranges=nbrranges, sklranges=sklranges) - return skeletons, sklranges + return skeletons, sklranges # }}} diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index e3c6fdab..f4055b27 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -26,6 +26,8 @@ import os import time import numpy as np +import numpy.linalg as la + import pyopencl as cl from pyopencl.array import to_device @@ -112,6 +114,7 @@ def _build_block_index(queue, discr, for i in range(ranges.shape[0] - 1): iidx = indices[np.s_[ranges[i]:ranges[i + 1]]] isize = int(factor * len(iidx)) + isize = max(1, min(isize, len(iidx))) indices_[i] = np.sort( np.random.choice(iidx, size=isize, replace=False)) @@ -184,7 +187,7 @@ def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): @pytest.mark.parametrize("method", ["nodes", "elements"]) @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -195,7 +198,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): +def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -241,27 +244,35 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) -def test_proxy_generator(ctx_factory, ndim, visualize=False): +@pytest.mark.parametrize("factor", [1.0, 0.6]) +def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) srcindices, srcranges = _build_block_index(queue, qbx.density_discr, - method='nodes', factor=0.6) + method='nodes', factor=factor) from pytential.linalg.proxy import ProxyGenerator - gen = ProxyGenerator(queue, qbx, ratio=1.1) - centers, radii, proxies, pxyranges = gen.get_proxies(srcindices, srcranges) + generator = ProxyGenerator(qbx, ratio=1.1) + proxies, pxyranges, pxycenters, pxyradii = \ + generator(queue, srcindices, srcranges) + + proxies = np.vstack([p.get() for p in proxies]) + pxyranges = pxyranges.get() + pxycenters = np.vstack([c.get() for c in pxycenters]) + pxyradii = pxyradii.get() + + for i in range(srcranges.shape[0] - 1): + ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] + + r = la.norm(proxies[:, ipxy] - pxycenters[:, i].reshape(-1, 1), axis=0) + assert np.allclose(r - pxyradii[i], 0.0, atol=1.0e-14) if visualize: srcindices = srcindices.get() srcranges = srcranges.get() - centers = np.vstack([c.get() for c in centers]) - radii = radii.get() - proxies = np.vstack([p.get() for p in proxies]) - pxyranges = pxyranges.get() - if visualize: if qbx.ambient_dim == 2: import matplotlib.pyplot as pt @@ -309,9 +320,9 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): InterpolatoryQuadratureSimplexGroupFactory for i in range(srcranges.shape[0] - 1): - mesh = affine_map(gen.mesh, - A=(radii[i] * np.eye(ndim)), - b=centers[:, i].reshape(-1)) + mesh = affine_map(generator.ref_mesh, + A=(pxyradii[i] * np.eye(ndim)), + b=pxycenters[:, i].reshape(-1)) mesh = merge_disjoint_meshes([mesh, qbx.density_discr.mesh]) discr = Discretization(ctx, mesh, @@ -325,27 +336,36 @@ def test_proxy_generator(ctx_factory, ndim, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) -def test_area_query(ctx_factory, ndim, visualize=False): +@pytest.mark.parametrize("factor", [1.0, 0.6]) +def test_area_query(ctx_factory, ndim, factor, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) srcindices, srcranges = _build_block_index(queue, qbx.density_discr, - method='nodes', factor=0.6) + method='nodes', factor=factor) # generate proxy points from pytential.linalg.proxy import ProxyGenerator - gen = ProxyGenerator(queue, qbx, ratio=1.1) - centers, radii, _, _ = gen.get_proxies(srcindices, srcranges) - neighbors, ngbranges = gen.get_neighbors(srcindices, srcranges, centers, radii) - skeletons, sklranges = gen(srcindices, srcranges) + generator = ProxyGenerator(qbx, ratio=1.1) + _, _, pxycenters, pxyradii = generator(queue, srcindices, srcranges) - if visualize: - srcindices = srcindices.get() - srcranges = srcranges.get() + from pytential.linalg.proxy import build_neighbor_list, build_skeleton_list + neighbors, nbrranges = build_neighbor_list(qbx.density_discr, + srcindices, srcranges, pxycenters, pxyradii) + skeletons, sklranges = build_skeleton_list(qbx, srcindices, srcranges, + ratio=1.1) + + srcindices = srcindices.get() + srcranges = srcranges.get() + neighbors = neighbors.get() + nbrranges = nbrranges.get() + + for i in range(srcranges.shape[0] - 1): + isrc = np.s_[srcranges[i]:srcranges[i + 1]] + inbr = np.s_[nbrranges[i]:nbrranges[i + 1]] - neighbors = neighbors.get() - ngbranges = ngbranges.get() + assert not np.any(np.isin(neighbors[inbr], srcindices[isrc])) if visualize: if ndim == 2: @@ -356,7 +376,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] + ingb = neighbors[nbrranges[i]:nbrranges[i + 1]] iskl = np.s_[sklranges[i]:sklranges[i + 1]] pt.figure(figsize=(10, 8)) @@ -382,7 +402,7 @@ def test_area_query(ctx_factory, ndim, visualize=False): for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = neighbors[ngbranges[i]:ngbranges[i + 1]] + ingb = neighbors[nbrranges[i]:nbrranges[i + 1]] # TODO: some way to turn off some of the interpolations # would help visualize this better. -- GitLab From 437e655618cf2b9cd693e10b9127d0f65db7386c Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 15:58:25 -0500 Subject: [PATCH 162/268] direct-solver-proxy: fix some docs --- doc/conf.py | 1 + pytential/linalg/proxy.py | 162 ++++++++++++++++++++------------------ test/test_linalg_proxy.py | 27 +++---- 3 files changed, 98 insertions(+), 92 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 81b15205..6df4af25 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -271,6 +271,7 @@ intersphinx_mapping = { 'http://docs.python.org/': None, 'http://documen.tician.de/boxtree/': None, 'http://docs.scipy.org/doc/numpy/': None, + 'http://documen.tician.de/meshmode/': None, 'http://documen.tician.de/modepy/': None, 'http://documen.tician.de/pyopencl/': None, 'http://documen.tician.de/pytools/': None, diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 2bd1aa3d..f8fb9845 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -30,8 +30,6 @@ import pyopencl as cl import pyopencl.array # noqa from pyopencl.array import to_device -from boxtree.tools import DeviceDataRecord - from pytools.obj_array import make_obj_array from pytools import memoize_method, memoize @@ -51,6 +49,9 @@ Proxy Point Generation .. autofunction:: partition_from_coarse +.. autofunction:: build_neighbor_list + +.. autofunction:: build_skeleton_list """ @@ -65,18 +66,18 @@ def _element_node_range(group, ielement): def partition_by_nodes(queue, discr, use_tree=True, - max_particles_in_box=30): + max_nodes_in_box=30): """Generate clusters / ranges of nodes. The partition is created at the lowest level of granularity, i.e. nodes. This results in balanced ranges of points, but will split elements across different ranges. :arg queue: a :class:`pyopencl.CommandQueue`. - :arg discr: a :class:`~meshmode.discretization.Discretization`. + :arg discr: a :class:`meshmode.discretization.Discretization`. :arg use_tree: if `True`, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close points to belong to the same partition. If `False`, a simple linear partition is constructed. - :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. + :arg max_nodes_in_box: passed to :class:`boxtree.TreeBuilder`. :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` integer arrays. The indices in a range can be retrieved using @@ -90,7 +91,7 @@ def partition_by_nodes(queue, discr, builder = TreeBuilder(discr.cl_context) tree, _ = builder(queue, discr.nodes(), - max_particles_in_box=max_particles_in_box) + max_particles_in_box=max_nodes_in_box) tree = tree.get(queue) leaf_boxes, = (tree.box_flags & @@ -109,7 +110,7 @@ def partition_by_nodes(queue, discr, indices = cl.array.arange(queue, 0, discr.nnodes, dtype=np.int) ranges = cl.array.arange(queue, 0, discr.nnodes + 1, - discr.nnodes // max_particles_in_box, + discr.nnodes // max_nodes_in_box, dtype=np.int) assert ranges[-1] == discr.nnodes @@ -118,7 +119,7 @@ def partition_by_nodes(queue, discr, def partition_by_elements(queue, discr, use_tree=True, - max_particles_in_box=10): + max_elements_in_box=10): """Generate clusters / ranges of points. The partition is created at the element level, so that all the nodes belonging to an element belong to the same range. This can result in slightly larger differences in size @@ -126,12 +127,12 @@ def partition_by_elements(queue, discr, need to be resampled, integrated, etc. :arg queue: a :class:`pyopencl.CommandQueue`. - :arg discr: a :class:`~meshmode.discretization.Discretization`. + :arg discr: a :class:`meshmode.discretization.Discretization`. :arg use_tree: if True, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close points to belong to the same partition. If False, a simple linear partition is constructed. - :arg max_particles_in_box: passed to :class:`boxtree.TreeBuilder`. + :arg max_elements_in_box: passed to :class:`boxtree.TreeBuilder`. :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` integer arrays. The indices in a range can be retrieved using @@ -148,7 +149,7 @@ def partition_by_elements(queue, discr, elcenters = element_centers_of_mass(discr) tree, _ = builder(queue, elcenters, - max_particles_in_box=max_particles_in_box) + max_particles_in_box=max_elements_in_box) groups = discr.groups tree = tree.get(queue) @@ -168,7 +169,7 @@ def partition_by_elements(queue, discr, else: nelements = discr.mesh.nelements elements = np.array_split(np.arange(0, nelements), - nelements // max_particles_in_box) + nelements // max_elements_in_box) elranges = np.cumsum([g.nelements for g in discr.groups]) elgroups = [np.digitize(elements[i], elranges) @@ -197,12 +198,11 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): The new partition will have the same number of ranges as the old partition. The nodes inside each range in the new partition are all the nodes in :attr:`resampler.to_discr` that belong to the same region as the nodes - in the same range from :attr:`resampler.from_discr`. These nodes are - obtained using :attr:`mesmode.discretization.connection.InterpolationBatch`. + in the same range from :attr:`resampler.from_discr`. :arg queue: a :class:`pyopencl.CommandQueue`. :arg resampler: a - :class:`~meshmode.discretization.connection.DirectDiscretizationConnection`. + :class:`meshmode.discretization.connection.DirectDiscretizationConnection`. :arg from_indices: a set of indices into the nodes in :attr:`resampler.from_discr`. :arg from_ranges: array used to index into :attr:`from_indices`. @@ -270,31 +270,32 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): # {{{ proxy point generator class ProxyGenerator(object): - def __init__(self, source, ratio=1.5, nproxy=30, **kwargs): - r""" - :arg discr: a :class:`pytential.qbx.QBXLayerPotentialBase`. - :arg ratio: a ratio used to compute the proxy point radius. The radius - is computed in the :math:`L_2` norm, resulting in a circle or - sphere of proxy points. For QBX, we have two radii of interest - for a set of points: the radius :math:`r_{block}` of the - smallest ball containing all the points and the radius - :math:`r_{qbx}` of the smallest ball containing all the QBX - expansion balls in the block. If the ratio :math:`\theta \in - [0, 1]`, then the radius of the proxy ball is + r""" + :arg discr: a :class:`pytential.qbx.QBXLayerPotentialSource`. + :arg nproxy: number of proxy points. + :arg ratio: a ratio used to compute the proxy point radius. The radius + is computed in the :math:`L_2` norm, resulting in a circle or + sphere of proxy points. For QBX, we have two radii of interest + for a set of points: the radius :math:`r_{block}` of the + smallest ball containing all the points and the radius + :math:`r_{qbx}` of the smallest ball containing all the QBX + expansion balls in the block. If the ratio :math:`\theta \in + [0, 1]`, then the radius of the proxy ball is - .. math:: + .. math:: - r = (1 - \theta) r_{block} + \theta r_{qbx}. + r = (1 - \theta) r_{block} + \theta r_{qbx}. - If the ratio :math:`\theta > 1`, the the radius is simply + If the ratio :math:`\theta > 1`, the the radius is simply - .. math:: + .. math:: - r = \theta r_{qbx}. + r = \theta r_{qbx}. - :arg nproxy: number of proxy points. - """ + .. automethod:: __call__ + """ + def __init__(self, source, nproxy=30, ratio=1.1, **kwargs): self.source = source self.ratio = abs(ratio) self.nproxy = int(abs(nproxy)) @@ -316,9 +317,10 @@ class ProxyGenerator(object): @memoize_method def get_kernel(self): if self.ratio < 1.0: - radius_expr = "(1.0 - ratio) * rblk + ratio * rqbx" + radius_expr = "(1.0 - {ratio}) * rblk + {ratio} * rqbx" else: - radius_expr = "ratio * rqbx" + radius_expr = "{ratio} * rqbx" + radius_expr = radius_expr.format(ratio=self.ratio) # NOTE: centers of mass are computed using a second-order approximation # that currently matches what is in `element_centers_of_mass`. @@ -329,23 +331,25 @@ class ProxyGenerator(object): ], [""" for irange + <> ioffset = srcranges[irange] <> npoints = srcranges[irange + 1] - srcranges[irange] + proxy_center[idim, irange] = 1.0 / npoints * \ - reduce(sum, i, nodes[idim, srcindices[i + srcranges[irange]]]) \ + reduce(sum, i, nodes[idim, srcindices[i + ioffset]]) \ {{dup=idim:i}} <> rblk = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - nodes[idim, srcindices[i + srcranges[irange]]]) ** 2))) + nodes[idim, srcindices[i + ioffset]]) ** 2))) <> rqbx_int = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_int[idim, srcindices[i + srcranges[irange]]]) ** 2)) + \ - expansion_radii[srcindices[i + srcranges[irange]]]) + center_int[idim, srcindices[i + ioffset]]) ** 2)) + \ + expansion_radii[srcindices[i + ioffset]]) <> rqbx_ext = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - center_ext[idim, srcindices[i + srcranges[irange]]]) ** 2)) + \ - expansion_radii[srcindices[i + srcranges[irange]]]) + center_ext[idim, srcindices[i + ioffset]]) ** 2)) + \ + expansion_radii[srcindices[i + ioffset]]) <> rqbx = if(rqbx_ext < rqbx_int, rqbx_int, rqbx_ext) proxy_radius[irange] = {radius_expr} @@ -374,9 +378,7 @@ class ProxyGenerator(object): ], name="proxy_generator_knl", assumptions="dim>=1 and nranges>=1", - fixed_parameters=dict( - dim=self.ambient_dim, - ratio=self.ratio), + fixed_parameters=dict(dim=self.ambient_dim), lang_version=MOST_RECENT_LANGUAGE_VERSION) knl = lp.tag_inames(knl, "idim*:unr") @@ -395,21 +397,21 @@ class ProxyGenerator(object): the discretization in :attr:`source`. :arg queue: a :class:`pyopencl.CommandQueue`. - :arg srcindices: a :class:`pyopencl.array.Array` of srcindices around + :arg srcindices: a :class:`pyopencl.array.Array` of indices around which to construct proxy balls. :arg srcranges: an :class:`pyopencl.array.Array` of size `(nranges + 1,)` used to index into :attr:`srcindices`. Each one of the `nranges` ranges will get a proxy ball. - :return: a tuple of `(centers, radii, proxies, pxyranges)`, where - each value is a :class:`pyopencl.array.Array`. The - sizes of the arrays are as follows: `centers` is of size - `(2, nranges)`, `radii` is of size `(nranges,)`, `pxyranges` is + :return: a tuple of `(proxies, pxyranges, pxycenters, pxyranges)`, where + each element is a :class:`pyopencl.array.Array`. The + sizes of the arrays are as follows: `pxycenters` is of size + `(2, nranges)`, `pxyradii` is of size `(nranges,)`, `pxyranges` is of size `(nranges + 1,)` and `proxies` is of size - `(2, nranges * nproxies)`. The proxy points in a range :math:`i` + `(2, nranges * nproxy)`. The proxy points in a range :math:`i` can be obtained by a slice `proxies[pxyranges[i]:pxyranges[i + 1]]` - and are all at a distance `radii[i]` from the range center - `centers[i]`. + and are all at a distance `pxyradii[i]` from the range center + `pxycenters[i]`. """ from pytential.qbx.utils import get_centers_on_side @@ -461,12 +463,9 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, :arg pxycenters: an array containing the center of each proxy ball. :arg pxyradii: an array containing the radius of each proxy ball. - :return: a tuple `(neighbours, nbrranges)` where each value is a - :class:`pyopencl.array.Array` of integers. For a range :math:`i` - in `nbrranges`, the corresponding slice of the `neighbours` array - is a subset of :attr:`srcindices` such that all points are inside - the proxy ball centered at `centers[i]` of radius `radii[i]` - that is not included in `srcindices[srcranges[i]:srcranges[i + 1]]`. + :return: a tuple `(nbrindices, nbrranges)`, where each value is a + :class:`pyopencl.array.Array`. For a range :math:`i`, we can + get the slice using `nbrindices[nbrranges[i]:nbrranges[i + 1]]`. """ with cl.CommandQueue(discr.cl_context) as queue: @@ -507,7 +506,7 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, if isinstance(pxyradii, cl.array.Array): pxyradii = pxyradii.get(queue) - neighbors = np.empty(srcranges.shape[0] - 1, dtype=np.object) + nbrindices = np.empty(srcranges.shape[0] - 1, dtype=np.object) for iproxy in range(srcranges.shape[0] - 1): # get list of boxes intersecting the current ball istart = query.leaves_near_ball_starts[iproxy] @@ -527,20 +526,31 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, center = pxycenters[:, iproxy].reshape(-1, 1) radius = pxyradii[iproxy] mask = (la.norm(nodes - center, axis=0) < radius) & \ - ((isources < srcranges[iproxy]) | (srcranges[iproxy + 1] <= isources)) + ((isources < srcranges[iproxy]) | + (srcranges[iproxy + 1] <= isources)) - neighbors[iproxy] = srcindices[isources[mask]] + nbrindices[iproxy] = srcindices[isources[mask]] nbrranges = to_device(queue, - np.cumsum([0] + [n.shape[0] for n in neighbors])) - neighbors = to_device(queue, np.hstack(neighbors)) + np.cumsum([0] + [n.shape[0] for n in nbrindices])) + nbrindices = to_device(queue, np.hstack(nbrindices)) - return neighbors, nbrranges + return nbrindices, nbrranges def build_skeleton_list(source, srcindices, srcranges, **kwargs): - """ - :arg source: a :class:`pytential.qbx.QBXLayerPotentialBase`. + """Generate sets of skeleton points for each given range of indices + in the :attr:`source` discretization. Skeleton points are meant to + model the interactions of a set of points. They are composed of two + parts: + + - a set of proxy points (or balls) around a given range, which + models farfield interactions. + + - a set of neighboring points that are inside the proxy balls, but + do not belong to the given range, which model nearby interactions. + + :arg source: a :class:`pytential.qbx.QBXLayerPotentialSource`. :arg srcindices: a :class:`pyopencl.array.Array` of indices for points in :attr:`source`. :arg srcranges: a :class:`pyopencl.array.Array` describing the ranges @@ -551,13 +561,9 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): :arg kwargs: additional arguments passed to :class:`ProxyGenerator` or :func:`build_neighbor_list`. - :returns: a tuple `(skeletons, sklranges)` where each value is a + :returns: a tuple `(skeletons, sklranges)`, where each value is a :class:`pyopencl.array.Array`. For a range :math:`i`, we can get the slice using `skeletons[sklranges[i]:sklranges[i + 1]]`. - The skeleton points in a range represents the union of a set - of generated proxy points and all the source points inside the - proxy ball that do not also belong to the current range in - :attr:`indices`. """ @memoize @@ -583,7 +589,7 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): proxies[idim, pxystart + ipxy] \ {id_prefix=write_pxy,nosync=write_ngb} skeletons[idim, sklstart + npxyblock + ingb] = \ - sources[idim, neighbors[ngbstart + ingb]] \ + sources[idim, nbrindices[ngbstart + ingb]] \ {id_prefix=write_ngb,nosync=write_pxy} sklranges[irange + 1] = sklranges[irange] + \ npxyblock + nngbblock @@ -594,19 +600,19 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): shape=(source.ambient_dim, "nsources")), lp.GlobalArg("proxies", None, shape=(source.ambient_dim, "nproxies"), dim_tags="sep,C"), - lp.GlobalArg("neighbors", None, - shape="nneighbors"), + lp.GlobalArg("nbrindices", None, + shape="nnbrindices"), lp.GlobalArg("pxyranges", None, shape="nranges + 1"), lp.GlobalArg("nbrranges", None, shape="nranges + 1"), lp.GlobalArg("skeletons", None, - shape=(source.ambient_dim, "nproxies + nneighbors")), + shape=(source.ambient_dim, "nproxies + nnbrindices")), lp.GlobalArg("sklranges", None, shape="nranges + 1"), lp.ValueArg("nsources", np.int32), lp.ValueArg("nproxies", np.int32), - lp.ValueArg("nneighbors", np.int32), + lp.ValueArg("nnbrindices", np.int32), "..." ], name="concat_skl", @@ -625,15 +631,15 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): proxies, pxyranges, pxycenters, pxyradii = \ proxy(queue, srcindices, srcranges) - neighbors, nbrranges = build_neighbor_list(source.density_discr, + nbrindices, nbrranges = build_neighbor_list(source.density_discr, srcindices, srcranges, pxycenters, pxyradii, **kwargs) sklranges = cl.array.zeros(queue, srcranges.shape, dtype=np.int) _, (skeletons, sklranges) = knl()(queue, sources=source.density_discr.nodes(), proxies=proxies, - neighbors=neighbors, pxyranges=pxyranges, + nbrindices=nbrindices, nbrranges=nbrranges, sklranges=sklranges) diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index f4055b27..0274bfed 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -98,10 +98,10 @@ def _build_block_index(queue, discr, # create index ranges if method == "nodes": indices, ranges = partition_by_nodes(queue, discr, - use_tree=use_tree, max_particles_in_box=max_particles_in_box) + use_tree=use_tree, max_nodes_in_box=max_particles_in_box) elif method == "elements": indices, ranges = partition_by_elements(queue, discr, - use_tree=use_tree, max_particles_in_box=max_particles_in_box) + use_tree=use_tree, max_elements_in_box=max_particles_in_box) else: raise ValueError("unknown method: {}".format(method)) @@ -187,7 +187,7 @@ def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): @pytest.mark.parametrize("method", ["nodes", "elements"]) @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -198,7 +198,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): +def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -245,7 +245,7 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): +def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -337,7 +337,7 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_area_query(ctx_factory, ndim, factor, visualize=True): +def test_area_query(ctx_factory, ndim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -347,25 +347,24 @@ def test_area_query(ctx_factory, ndim, factor, visualize=True): # generate proxy points from pytential.linalg.proxy import ProxyGenerator - generator = ProxyGenerator(qbx, ratio=1.1) + generator = ProxyGenerator(qbx) _, _, pxycenters, pxyradii = generator(queue, srcindices, srcranges) from pytential.linalg.proxy import build_neighbor_list, build_skeleton_list - neighbors, nbrranges = build_neighbor_list(qbx.density_discr, + nbrindices, nbrranges = build_neighbor_list(qbx.density_discr, srcindices, srcranges, pxycenters, pxyradii) - skeletons, sklranges = build_skeleton_list(qbx, srcindices, srcranges, - ratio=1.1) + skeletons, sklranges = build_skeleton_list(qbx, srcindices, srcranges) srcindices = srcindices.get() srcranges = srcranges.get() - neighbors = neighbors.get() + nbrindices = nbrindices.get() nbrranges = nbrranges.get() for i in range(srcranges.shape[0] - 1): isrc = np.s_[srcranges[i]:srcranges[i + 1]] inbr = np.s_[nbrranges[i]:nbrranges[i + 1]] - assert not np.any(np.isin(neighbors[inbr], srcindices[isrc])) + assert not np.any(np.isin(nbrindices[inbr], srcindices[isrc])) if visualize: if ndim == 2: @@ -376,7 +375,7 @@ def test_area_query(ctx_factory, ndim, factor, visualize=True): for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = neighbors[nbrranges[i]:nbrranges[i + 1]] + ingb = nbrindices[nbrranges[i]:nbrranges[i + 1]] iskl = np.s_[sklranges[i]:sklranges[i + 1]] pt.figure(figsize=(10, 8)) @@ -402,7 +401,7 @@ def test_area_query(ctx_factory, ndim, factor, visualize=True): for i in range(srcranges.shape[0] - 1): isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = neighbors[nbrranges[i]:nbrranges[i + 1]] + ingb = nbrindices[nbrranges[i]:nbrranges[i + 1]] # TODO: some way to turn off some of the interpolations # would help visualize this better. -- GitLab From 514d14d430d850bd85562a3aa5e06df32922ff0e Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 16:06:57 -0500 Subject: [PATCH 163/268] flake8 fixes --- test/test_linalg_proxy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index 0274bfed..6345c775 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -31,8 +31,6 @@ import numpy.linalg as la import pyopencl as cl from pyopencl.array import to_device -import loopy as lp -from pytential import sym from meshmode.mesh.generation import ( # noqa ellipse, NArmedStarfish, generate_torus, make_curve_mesh) @@ -203,7 +201,7 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, method="elements", use_tree=use_tree) if visualize: -- GitLab From bb33283fdefc4d3c9de2ae5662d40f076798bf74 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 21 Jun 2018 16:26:31 -0500 Subject: [PATCH 164/268] make sure qbx_order is provided --- pytential/qbx/__init__.py | 6 ++++++ test/test_global_qbx.py | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index d3cd843a..b3bb102f 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -106,6 +106,12 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): # {{{ argument processing + if fine_order is None: + raise ValueError("fine_order must be provided.") + + if qbx_order is None: + raise ValueError("qbx_order must be provided.") + if target_stick_out_factor is not _not_provided: from warnings import warn warn("target_stick_out_factor has been renamed to " diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index d0128819..d6ffeaf4 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -96,7 +96,9 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): from pytential.qbx.utils import TreeCodeContainer - lpot_source = QBXLayerPotentialSource(discr, order) + lpot_source = QBXLayerPotentialSource(discr, + fine_order=order, + qbx_order=order // 2) del discr expansion_disturbance_tolerance = 0.025 @@ -239,7 +241,8 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, discr = Discretization(cl_ctx, mesh, factory) - lpot_source, conn = QBXLayerPotentialSource(discr, order).with_refinement() + lpot_source, conn = QBXLayerPotentialSource(discr, + fine_order=order, qbx_order=order // 2).with_refinement() del discr from pytential.qbx.utils import get_interleaved_centers @@ -417,7 +420,8 @@ def test_target_association_failure(ctx_getter): InterpolatoryQuadratureSimplexGroupFactory factory = InterpolatoryQuadratureSimplexGroupFactory(order) discr = Discretization(cl_ctx, mesh, factory) - lpot_source = QBXLayerPotentialSource(discr, order) + lpot_source = QBXLayerPotentialSource(discr, + fine_order=order, qbx_order=order // 2) # }}} -- GitLab From 507945da14d8a837670b1f7b95bf35cae41e05cb Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 21 Jun 2018 17:30:32 -0500 Subject: [PATCH 165/268] Fix Sphinx warnings. --- doc/discretization.rst | 2 +- pytential/qbx/target_assoc.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/discretization.rst b/doc/discretization.rst index ad2eb738..ac4ca526 100644 --- a/doc/discretization.rst +++ b/doc/discretization.rst @@ -16,7 +16,7 @@ and you can start computing. .. automodule:: pytential.qbx Unregularized discretization -------- +---------------------------- .. automodule:: pytential.unregularized diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index f51e920f..11b70c4b 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -91,8 +91,6 @@ Return values .. autoclass:: QBXTargetAssociation -.. autoclass:: QBXTargetAssociationFailedException - Target association driver ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -727,7 +725,7 @@ def associate_targets_to_qbx_centers(lpot_source, wrangler, The side request can take on the values in :ref:`qbx-side-request-table`. - :raises QBXTargetAssociationFailedException: + :raises pytential.qbx.QBXTargetAssociationFailedException: when target association failed to find a center for a target. The returned exception object contains suggested refine flags. -- GitLab From 7db97cd548c3d9929064d1bd0a80e7cf3e1d8d22 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 21 Jun 2018 17:56:07 -0500 Subject: [PATCH 166/268] Revert "Merge branch 'master' of https://gitlab.tiker.net/inducer/pytential" This reverts commit 44f71a6fb9e2c8c9d8b1b7694d9c4ec1185f4d9b, reversing changes made to b933285a4592da7b057d9f2110503a87499de542. --- .gitlab-ci.yml | 40 ++- README.rst | 2 +- pytential/qbx/fmm.py | 393 +++++++++++++++++++++ pytential/qbx/performance.py | 585 -------------------------------- test/test_layer_pot.py | 73 +++- test/test_layer_pot_identity.py | 40 +-- test/test_performance_model.py | 131 ------- 7 files changed, 505 insertions(+), 759 deletions(-) delete mode 100644 pytential/qbx/performance.py delete mode 100644 test/test_performance_model.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6b6cf7a7..750bf6f4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,12 @@ -# Environment variables -# -# * PYTEST_ADDOPTS is used to filter test runs. The default value is "-k-slowtest", -# which skips the slow running tests. -# * SKIP_EXAMPLES, if non-empty, can be used to skip the examples job. - -Python 2.7 POCL: +Python 3.5 POCL: script: - - export PY_EXE=python2.7 + - export PY_EXE=python3.5 - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: - - python2.7 + - python3.5 - pocl - large-node except: @@ -23,7 +16,6 @@ Python 3.6 POCL: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - export EXTRA_INSTALL="numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" @@ -36,7 +28,6 @@ Python 3.6 POCL: Python 3.6 POCL Examples: script: - - test -n "$SKIP_EXAMPLES" && exit - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - export EXTRA_INSTALL="numpy mako pyvisfile matplotlib" @@ -52,9 +43,8 @@ Python 3.6 POCL Examples: Python 3.5 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine - - export CONDA_ENVIRONMENT=.test-conda-env-py3.yml - - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - - export REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt + - CONDA_ENVIRONMENT=.test-conda-env-py3.yml + - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" tags: @@ -63,13 +53,27 @@ Python 3.5 Conda: except: - tags +Python 2.7 POCL: + script: + - export PY_EXE=python2.7 + - export PYOPENCL_TEST=portable + - export EXTRA_INSTALL="numpy mako" + - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh + - ". ./build-and-test-py-project.sh" + tags: + - python2.7 + - pocl + - large-node + except: + - tags + Python 3.5 Conda Apple: script: - export LC_ALL=en_US.UTF-8 - export LANG=en_US.UTF-8 - - export CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml - - export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} - - export REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt + - export PYTEST_ADDOPTS=-k-slowtest + - CONDA_ENVIRONMENT=.test-conda-env-py3-macos.yml + - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" tags: diff --git a/README.rst b/README.rst index 8b354a93..bababc0a 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ It relies on * `boxtree `_ for FMM tree building * `sumpy `_ for expansions and analytical routines * `modepy `_ for modes and nodes on simplices -* `meshmode `_ for high order discretizations +* `meshmode `_ for modes and nodes on simplices * `loopy `_ for fast array operations * `pytest `_ for automated testing diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 4cfd0c81..4be9e5ca 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -521,4 +521,397 @@ def drive_fmm(expansion_wrangler, src_weights): # }}} +# {{{ performance data + +def assemble_performance_data(geo_data, uses_pde_expansions, + translation_source_power=None, translation_target_power=None, + translation_max_power=None, + summarize_parallel=None, merge_close_lists=True): + """ + :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM + uses translation operators that make use of the knowledge that the + potential satisfies a PDE. + :arg summarize_parallel: a function of two arguments + *(parallel_array, sym_multipliers)* used to process an array of + workloads of 'parallelizable units'. By default, all workloads are + summed into one number encompassing the total workload. + :arg merge_close_lists: A :class:`bool` indicating whether or not all + boxes requiring direct evaluation should be merged into a single + interaction list. If *False*, *part_direct* and *p2qbxl* will be + suffixed with the originating list as follows: + + * *_neighbor* (List 1) + * *_sep_smaller* (List 3 close) + * *_sep_bigger* (List 4 close). + """ + + # FIXME: This should suport target filtering. + + if summarize_parallel is None: + def summarize_parallel(parallel_array, sym_multipliers): + return np.sum(parallel_array) * sym_multipliers + + from collections import OrderedDict + result = OrderedDict() + + from pymbolic import var + p_fmm = var("p_fmm") + p_qbx = var("p_qbx") + + nqbtl = geo_data.non_qbx_box_target_lists() + + with cl.CommandQueue(geo_data.cl_context) as queue: + tree = geo_data.tree().get(queue=queue) + traversal = geo_data.traversal(merge_close_lists).get(queue=queue) + box_target_counts_nonchild = ( + nqbtl.box_target_counts_nonchild.get(queue=queue)) + + d = tree.dimensions + if uses_pde_expansions: + ncoeffs_fmm = p_fmm ** (d-1) + ncoeffs_qbx = p_qbx ** (d-1) + + if d == 2: + default_translation_source_power = 1 + default_translation_target_power = 1 + default_translation_max_power = 0 + + elif d == 3: + # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. + default_translation_source_power = 0 + default_translation_target_power = 0 + default_translation_max_power = 3 + + else: + raise ValueError("Don't know how to estimate expansion complexities " + "for dimension %d" % d) + + else: + ncoeffs_fmm = p_fmm ** d + ncoeffs_qbx = p_qbx ** d + default_translation_source_power = d + default_translation_target_power = d + + if translation_source_power is None: + translation_source_power = default_translation_source_power + if translation_target_power is None: + translation_target_power = default_translation_target_power + if translation_max_power is None: + translation_max_power = default_translation_max_power + + def xlat_cost(p_source, p_target): + from pymbolic.primitives import Max + return ( + p_source ** translation_source_power + * p_target ** translation_target_power + * Max((p_source, p_target)) ** translation_max_power + ) + + result.update( + nlevels=tree.nlevels, + nboxes=tree.nboxes, + nsources=tree.nsources, + ntargets=tree.ntargets) + + # {{{ construct local multipoles + + result["form_mp"] = tree.nsources*ncoeffs_fmm + + # }}} + + # {{{ propagate multipoles upward + + result["prop_upward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) + + # }}} + + # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) + + def process_direct(): + # box -> nsources * ntargets + npart_direct_list1 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + npart_direct_list3 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + npart_direct_list4 = np.zeros(len(traversal.target_boxes), dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_boxes): + ntargets = box_target_counts_nonchild[tgt_ibox] + + npart_direct_list1_srcs = 0 + start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] + for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list1_srcs += nsources + + npart_direct_list1[itgt_box] = ntargets * npart_direct_list1_srcs + + if merge_close_lists: + continue + + npart_direct_list3_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_smaller_starts is not None: + start, end = ( + traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list3_srcs += nsources + + npart_direct_list3[itgt_box] = ntargets * npart_direct_list3_srcs + + npart_direct_list4_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_bigger_starts is not None: + start, end = ( + traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + npart_direct_list4_srcs += nsources + + npart_direct_list4[itgt_box] = ntargets * npart_direct_list4_srcs + + if merge_close_lists: + result["part_direct"] = summarize_parallel(npart_direct_list1, 1) + else: + result["part_direct_neighbor"] = ( + summarize_parallel(npart_direct_list1, 1)) + result["part_direct_sep_smaller"] = ( + summarize_parallel(npart_direct_list3, 1)) + result["part_direct_sep_bigger"] = ( + summarize_parallel(npart_direct_list4, 1)) + + process_direct() + + # }}} + + # {{{ translate separated siblings' ("list 2") mpoles to local + + def process_list2(): + nm2l = np.zeros(len(traversal.target_or_target_parent_boxes), dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): + start, end = traversal.from_sep_siblings_starts[itgt_box:itgt_box+2] + + nm2l[itgt_box] += end-start + + result["m2l"] = summarize_parallel(nm2l, xlat_cost(p_fmm, p_fmm)) + + process_list2() + + # }}} + + # {{{ evaluate sep. smaller mpoles ("list 3") at particles + + def process_list3(): + nmp_eval = np.zeros( + (tree.nlevels, len(traversal.target_boxes)), + dtype=np.intp) + + assert tree.nlevels == len(traversal.from_sep_smaller_by_level) + + for ilevel, sep_smaller_list in enumerate( + traversal.from_sep_smaller_by_level): + for itgt_box, tgt_ibox in enumerate( + traversal.target_boxes_sep_smaller_by_source_level[ilevel]): + ntargets = box_target_counts_nonchild[tgt_ibox] + start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] + nmp_eval[ilevel, sep_smaller_list.nonempty_indices[itgt_box]] = ( + ntargets * (end-start) + ) + + result["mp_eval"] = summarize_parallel(nmp_eval, ncoeffs_fmm) + + process_list3() + + # }}} + + # {{{ form locals for separated bigger source boxes ("list 4") + + def process_list4(): + nform_local = np.zeros( + len(traversal.target_or_target_parent_boxes), + dtype=np.intp) + + for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): + start, end = traversal.from_sep_bigger_starts[itgt_box:itgt_box+2] + + nform_local_box = 0 + for src_ibox in traversal.from_sep_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + nform_local_box += nsources + + nform_local[itgt_box] = nform_local_box + + result["form_local"] = summarize_parallel(nform_local, ncoeffs_fmm) + + process_list4() + + # }}} + + # {{{ propagate local_exps downward + + result["prop_downward"] = tree.nboxes * xlat_cost(p_fmm, p_fmm) + + # }}} + + # {{{ evaluate locals + + result["eval_part"] = tree.ntargets * ncoeffs_fmm + + # }}} + + # {{{ form global qbx locals + + global_qbx_centers = geo_data.global_qbx_centers() + + # If merge_close_lists is False above, then this builds another traversal + # (which is OK). + qbx_center_to_target_box = geo_data.qbx_center_to_target_box() + center_to_targets_starts = geo_data.center_to_tree_targets().starts + qbx_center_to_target_box_source_level = np.empty( + (tree.nlevels,), dtype=object + ) + + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + geo_data.qbx_center_to_target_box_source_level(src_level) + ) + + with cl.CommandQueue(geo_data.cl_context) as queue: + global_qbx_centers = global_qbx_centers.get( + queue=queue) + qbx_center_to_target_box = qbx_center_to_target_box.get( + queue=queue) + center_to_targets_starts = center_to_targets_starts.get( + queue=queue) + for src_level in range(tree.nlevels): + qbx_center_to_target_box_source_level[src_level] = ( + qbx_center_to_target_box_source_level[src_level].get(queue=queue) + ) + + def process_form_qbxl(): + ncenters = geo_data.ncenters + + result["ncenters"] = ncenters + + # center -> nsources + np2qbxl_list1 = np.zeros(len(global_qbx_centers), dtype=np.intp) + np2qbxl_list3 = np.zeros(len(global_qbx_centers), dtype=np.intp) + np2qbxl_list4 = np.zeros(len(global_qbx_centers), dtype=np.intp) + + for itgt_center, tgt_icenter in enumerate(global_qbx_centers): + itgt_box = qbx_center_to_target_box[tgt_icenter] + + np2qbxl_list1_srcs = 0 + start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] + for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list1_srcs += nsources + + np2qbxl_list1[itgt_center] = np2qbxl_list1_srcs + + if merge_close_lists: + continue + + np2qbxl_list3_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_smaller_starts is not None: + start, end = ( + traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list3_srcs += nsources + + np2qbxl_list3[itgt_center] = np2qbxl_list3_srcs + + np2qbxl_list4_srcs = 0 + + # Could be None, if not using targets with extent. + if traversal.from_sep_close_bigger_starts is not None: + start, end = ( + traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) + for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: + nsources = tree.box_source_counts_nonchild[src_ibox] + + np2qbxl_list4_srcs += nsources + + np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs + + if merge_close_lists: + result["p2qbxl"] = summarize_parallel(np2qbxl_list1, ncoeffs_qbx) + else: + result["p2qbxl_neighbor"] = ( + summarize_parallel(np2qbxl_list1, ncoeffs_qbx)) + result["p2qbxl_sep_smaller"] = ( + summarize_parallel(np2qbxl_list3, ncoeffs_qbx)) + result["p2qbxl_sep_bigger"] = ( + summarize_parallel(np2qbxl_list4, ncoeffs_qbx)) + + process_form_qbxl() + + # }}} + + # {{{ translate from list 3 multipoles to qbx local expansions + + def process_m2qbxl(): + nm2qbxl = np.zeros( + (tree.nlevels, len(global_qbx_centers)), + dtype=np.intp) + + assert tree.nlevels == len(traversal.from_sep_smaller_by_level) + + for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): + + for itgt_center, tgt_icenter in enumerate(global_qbx_centers): + icontaining_tgt_box = qbx_center_to_target_box_source_level[ + isrc_level][tgt_icenter] + + if icontaining_tgt_box == -1: + continue + + start, stop = ( + ssn.starts[icontaining_tgt_box], + ssn.starts[icontaining_tgt_box+1]) + + nm2qbxl[isrc_level, itgt_center] += stop-start + + result["m2qbxl"] = summarize_parallel(nm2qbxl, xlat_cost(p_fmm, p_qbx)) + + process_m2qbxl() + + # }}} + + # {{{ translate from box local expansions to qbx local expansions + + result["l2qbxl"] = geo_data.ncenters * xlat_cost(p_fmm, p_qbx) + + # }}} + + # {{{ evaluate qbx local expansions + + def process_eval_qbxl(): + nqbx_eval = np.zeros(len(global_qbx_centers), dtype=np.intp) + + for isrc_center, src_icenter in enumerate(global_qbx_centers): + start, end = center_to_targets_starts[src_icenter:src_icenter+2] + nqbx_eval[isrc_center] += end-start + + result["qbxl2p"] = summarize_parallel(nqbx_eval, ncoeffs_qbx) + + process_eval_qbxl() + + # }}} + + return result + +# }}} + # vim: foldmethod=marker diff --git a/pytential/qbx/performance.py b/pytential/qbx/performance.py deleted file mode 100644 index 424fbed5..00000000 --- a/pytential/qbx/performance.py +++ /dev/null @@ -1,585 +0,0 @@ -from __future__ import division, absolute_import - -__copyright__ = """ -Copyright (C) 2013 Andreas Kloeckner -Copyright (C) 2018 Matt Wala -""" - -__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. -""" - - -from six.moves import range -import numpy as np # noqa -import pyopencl as cl # noqa -import pyopencl.array # noqa - - -import logging -logger = logging.getLogger(__name__) - - -__doc__ = """ -.. autoclass:: PerformanceModel -.. autofunction:: assemble_performance_data -""" - - -# {{{ translation cost model - -class TranslationCostModel(object): - """Provides modeled costs for individual translations or evaluations.""" - - def __init__(self, p_qbx, p_fmm, ncoeffs_qbx, ncoeffs_fmm, - translation_source_power, translation_target_power, - translation_max_power): - self.p_qbx = p_qbx - self.p_fmm = p_fmm - self.ncoeffs_qbx = ncoeffs_qbx - self.ncoeffs_fmm = ncoeffs_fmm - self.translation_source_power = translation_source_power - self.translation_target_power = translation_target_power - self.translation_max_power = translation_max_power - - def direct(self): - return 1 - - def p2qbxl(self): - return self.ncoeffs_qbx - - qbxl2p = p2qbxl - - def p2l(self): - return self.ncoeffs_fmm - - l2p = p2l - p2m = p2l - m2p = p2l - - def m2m(self): - return self.e2e_cost(self.p_fmm, self.p_fmm) - - l2l = m2m - m2l = m2m - - def m2qbxl(self): - return self.e2e_cost(self.p_fmm, self.p_qbx) - - l2qbxl = m2qbxl - - def e2e_cost(self, p_source, p_target): - from pymbolic.primitives import Max - return ( - p_source ** self.translation_source_power - * p_target ** self.translation_target_power - * Max((p_source, p_target)) ** self.translation_max_power) - -# }}} - - -# {{{ performance model - -class PerformanceModel(object): - - def __init__(self, - uses_pde_expansions=True, - translation_source_power=None, - translation_target_power=None, - translation_max_power=None, - summarize_parallel=None, - merge_close_lists=True): - """ - :arg uses_pde_expansions: A :class:`bool` indicating whether the FMM - uses translation operators that make use of the knowledge that the - potential satisfies a PDE. - :arg summarize_parallel: a function of two arguments - *(parallel_array, sym_multipliers)* used to process an array of - workloads of 'parallelizable units'. By default, all workloads are - summed into one number encompassing the total workload. - :arg merge_close_lists: A :class:`bool` indicating whether or not all - boxes requiring direct evaluation should be merged into a single - interaction list. If *False*, *part_direct* and *p2qbxl* will be - suffixed with the originating list as follows: - - * *_neighbor* (List 1) - * *_sep_smaller* (List 3 close) - * *_sep_bigger* (List 4 close). - """ - self.uses_pde_expansions = uses_pde_expansions - self.translation_source_power = translation_source_power - self.translation_target_power = translation_target_power - self.translation_max_power = translation_max_power - if summarize_parallel is None: - summarize_parallel = self.summarize_parallel_default - self.summarize_parallel = summarize_parallel - self.merge_close_lists = merge_close_lists - - @staticmethod - def summarize_parallel_default(parallel_array, sym_multipliers): - return np.sum(parallel_array) * sym_multipliers - - # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) - - def process_direct(self, xlat_cost, traversal, tree, box_target_counts_nonchild): - # box -> nsources * ntargets - npart_direct_list1 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - npart_direct_list3 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - npart_direct_list4 = np.zeros(len(traversal.target_boxes), dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_boxes): - ntargets = box_target_counts_nonchild[tgt_ibox] - - npart_direct_list1_srcs = 0 - start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] - for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list1_srcs += nsources - - npart_direct_list1[itgt_box] = ntargets * npart_direct_list1_srcs - - if self.merge_close_lists: - continue - - npart_direct_list3_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_smaller_starts is not None: - start, end = ( - traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list3_srcs += nsources - - npart_direct_list3[itgt_box] = ntargets * npart_direct_list3_srcs - - npart_direct_list4_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_bigger_starts is not None: - start, end = ( - traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - npart_direct_list4_srcs += nsources - - npart_direct_list4[itgt_box] = ntargets * npart_direct_list4_srcs - - result = {} - if self.merge_close_lists: - result["part_direct"] = ( - self.summarize_parallel(npart_direct_list1, xlat_cost.direct())) - else: - result["part_direct_neighbor"] = ( - self.summarize_parallel(npart_direct_list1, xlat_cost.direct())) - result["part_direct_sep_smaller"] = ( - self.summarize_parallel(npart_direct_list3, xlat_cost.direct())) - result["part_direct_sep_bigger"] = ( - self.summarize_parallel(npart_direct_list4, xlat_cost.direct())) - - return result - - # }}} - - # {{{ translate separated siblings' ("list 2") mpoles to local - - def process_list2(self, xlat_cost, traversal): - nm2l = np.zeros(len(traversal.target_or_target_parent_boxes), dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): - start, end = traversal.from_sep_siblings_starts[itgt_box:itgt_box+2] - - nm2l[itgt_box] += end-start - - return dict(m2l=self.summarize_parallel(nm2l, xlat_cost.m2l())) - - # }}} - - # {{{ evaluate sep. smaller mpoles ("list 3") at particles - - def process_list3(self, xlat_cost, traversal, tree, box_target_counts_nonchild): - nmp_eval = np.zeros( - (tree.nlevels, len(traversal.target_boxes)), - dtype=np.intp) - - assert tree.nlevels == len(traversal.from_sep_smaller_by_level) - - for ilevel, sep_smaller_list in enumerate( - traversal.from_sep_smaller_by_level): - for itgt_box, tgt_ibox in enumerate( - traversal.target_boxes_sep_smaller_by_source_level[ilevel]): - ntargets = box_target_counts_nonchild[tgt_ibox] - start, end = sep_smaller_list.starts[itgt_box:itgt_box+2] - nmp_eval[ilevel, sep_smaller_list.nonempty_indices[itgt_box]] = ( - ntargets * (end-start) - ) - - return dict( - mp_eval=self.summarize_parallel(nmp_eval, xlat_cost.m2p())) - - # }}} - - # {{{ form locals for separated bigger source boxes ("list 4") - - def process_list4(self, xlat_cost, traversal, tree): - nform_local = np.zeros( - len(traversal.target_or_target_parent_boxes), - dtype=np.intp) - - for itgt_box, tgt_ibox in enumerate(traversal.target_or_target_parent_boxes): - start, end = traversal.from_sep_bigger_starts[itgt_box:itgt_box+2] - - nform_local_box = 0 - for src_ibox in traversal.from_sep_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - nform_local_box += nsources - - nform_local[itgt_box] = nform_local_box - - return dict(form_local=( - self.summarize_parallel(nform_local, xlat_cost.p2l()))) - - # }}} - - # {{{ form global qbx locals - - def process_form_qbxl(self, xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box): - - # center -> nsources - np2qbxl_list1 = np.zeros(len(global_qbx_centers), dtype=np.intp) - np2qbxl_list3 = np.zeros(len(global_qbx_centers), dtype=np.intp) - np2qbxl_list4 = np.zeros(len(global_qbx_centers), dtype=np.intp) - - for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - itgt_box = qbx_center_to_target_box[tgt_icenter] - - np2qbxl_list1_srcs = 0 - start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] - for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list1_srcs += nsources - - np2qbxl_list1[itgt_center] = np2qbxl_list1_srcs - - if self.merge_close_lists: - continue - - np2qbxl_list3_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_smaller_starts is not None: - start, end = ( - traversal.from_sep_close_smaller_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_smaller_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list3_srcs += nsources - - np2qbxl_list3[itgt_center] = np2qbxl_list3_srcs - - np2qbxl_list4_srcs = 0 - - # Could be None, if not using targets with extent. - if traversal.from_sep_close_bigger_starts is not None: - start, end = ( - traversal.from_sep_close_bigger_starts[itgt_box:itgt_box+2]) - for src_ibox in traversal.from_sep_close_bigger_lists[start:end]: - nsources = tree.box_source_counts_nonchild[src_ibox] - - np2qbxl_list4_srcs += nsources - - np2qbxl_list4[itgt_center] = np2qbxl_list4_srcs - - result = {} - if self.merge_close_lists: - result["p2qbxl"] = ( - self.summarize_parallel(np2qbxl_list1, xlat_cost.p2qbxl())) - else: - result["p2qbxl_neighbor"] = ( - self.summarize_parallel(np2qbxl_list1, xlat_cost.p2qbxl())) - result["p2qbxl_sep_smaller"] = ( - self.summarize_parallel(np2qbxl_list3, xlat_cost.p2qbxl())) - result["p2qbxl_sep_bigger"] = ( - self.summarize_parallel(np2qbxl_list4, xlat_cost.p2qbxl())) - - return result - - # }}} - - # {{{ translate from list 3 multipoles to qbx local expansions - - def process_m2qbxl(self, xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box_source_level): - nm2qbxl = np.zeros( - (tree.nlevels, len(global_qbx_centers)), - dtype=np.intp) - - assert tree.nlevels == len(traversal.from_sep_smaller_by_level) - - for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): - - for itgt_center, tgt_icenter in enumerate(global_qbx_centers): - icontaining_tgt_box = qbx_center_to_target_box_source_level[ - isrc_level][tgt_icenter] - - if icontaining_tgt_box == -1: - continue - - start, stop = ( - ssn.starts[icontaining_tgt_box], - ssn.starts[icontaining_tgt_box+1]) - - nm2qbxl[isrc_level, itgt_center] += stop-start - - return dict(m2qbxl=self.summarize_parallel(nm2qbxl, xlat_cost.m2qbxl())) - - # }}} - - # {{{ evaluate qbx local expansions - - def process_eval_qbxl(self, xlat_cost, global_qbx_centers, - center_to_targets_starts): - nqbx_eval = np.zeros(len(global_qbx_centers), dtype=np.intp) - - for isrc_center, src_icenter in enumerate(global_qbx_centers): - start, end = center_to_targets_starts[src_icenter:src_icenter+2] - nqbx_eval[isrc_center] += end-start - - return dict(qbxl2p=self.summarize_parallel(nqbx_eval, xlat_cost.qbxl2p())) - - # }}} - - # {{{ set up translation cost model - - def get_translation_cost_model(self, d): - from pymbolic import var - p_qbx = var("p_qbx") - p_fmm = var("p_fmm") - - if self.uses_pde_expansions: - ncoeffs_fmm = p_fmm ** (d-1) - ncoeffs_qbx = p_qbx ** (d-1) - - if d == 2: - default_translation_source_power = 1 - default_translation_target_power = 1 - default_translation_max_power = 0 - - elif d == 3: - # Based on a reading of FMMlib, i.e. a point-and-shoot FMM. - default_translation_source_power = 0 - default_translation_target_power = 0 - default_translation_max_power = 3 - - else: - raise ValueError("Don't know how to estimate expansion complexities " - "for dimension %d" % d) - - else: - ncoeffs_fmm = p_fmm ** d - ncoeffs_qbx = p_qbx ** d - default_translation_source_power = d - default_translation_target_power = d - - translation_source_power = ( - default_translation_source_power - if self.translation_source_power is None - else self.translation_source_power) - - translation_target_power = ( - default_translation_target_power - if self.translation_target_power is None - else self.translation_target_power) - - translation_max_power = ( - default_translation_max_power - if self.translation_max_power is None - else self.translation_max_power) - - return TranslationCostModel( - p_qbx=p_qbx, - p_fmm=p_fmm, - ncoeffs_qbx=ncoeffs_qbx, - ncoeffs_fmm=ncoeffs_fmm, - translation_source_power=translation_source_power, - translation_target_power=translation_target_power, - translation_max_power=translation_max_power) - - # }}} - - def __call__(self, geo_data): - # FIXME: This should suport target filtering. - - from collections import OrderedDict - result = OrderedDict() - - nqbtl = geo_data.non_qbx_box_target_lists() - - with cl.CommandQueue(geo_data.cl_context) as queue: - tree = geo_data.tree().get(queue=queue) - traversal = geo_data.traversal(self.merge_close_lists).get(queue=queue) - box_target_counts_nonchild = ( - nqbtl.box_target_counts_nonchild.get(queue=queue)) - - result.update( - nlevels=tree.nlevels, - nboxes=tree.nboxes, - nsources=tree.nsources, - ntargets=tree.ntargets, - ncenters=geo_data.ncenters) - - xlat_cost = self.get_translation_cost_model(tree.dimensions) - - # {{{ construct local multipoles - - result["form_mp"] = tree.nsources * xlat_cost.p2m() - - # }}} - - # {{{ propagate multipoles upward - - result["prop_upward"] = tree.nboxes * xlat_cost.m2m() - - # }}} - - # {{{ direct evaluation to point targets (lists 1, 3 close, 4 close) - - result.update(self.process_direct( - xlat_cost, traversal, tree, box_target_counts_nonchild)) - - # }}} - - # {{{ translate separated siblings' ("list 2") mpoles to local - - result.update(self.process_list2(xlat_cost, traversal)) - - # }}} - - # {{{ evaluate sep. smaller mpoles ("list 3") at particles - - result.update(self.process_list3( - xlat_cost, traversal, tree, box_target_counts_nonchild)) - - # }}} - - # {{{ form locals for separated bigger source boxes ("list 4") - - result.update(self.process_list4(xlat_cost, traversal, tree)) - - # }}} - - # {{{ propagate local_exps downward - - result["prop_downward"] = tree.nboxes * xlat_cost.l2l() - - # }}} - - # {{{ evaluate locals - - result["eval_part"] = tree.ntargets * xlat_cost.l2p() - - # }}} - - global_qbx_centers = geo_data.global_qbx_centers() - - # If self.merge_close_lists is False, then this builds another traversal - # (which is OK). - qbx_center_to_target_box = geo_data.qbx_center_to_target_box() - center_to_targets_starts = geo_data.center_to_tree_targets().starts - qbx_center_to_target_box_source_level = np.empty( - (tree.nlevels,), dtype=object) - - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - geo_data.qbx_center_to_target_box_source_level(src_level)) - - with cl.CommandQueue(geo_data.cl_context) as queue: - global_qbx_centers = global_qbx_centers.get( - queue=queue) - qbx_center_to_target_box = qbx_center_to_target_box.get( - queue=queue) - center_to_targets_starts = center_to_targets_starts.get( - queue=queue) - for src_level in range(tree.nlevels): - qbx_center_to_target_box_source_level[src_level] = ( - qbx_center_to_target_box_source_level[src_level] - .get(queue=queue)) - - # {{{ form global qbx locals - - result.update(self.process_form_qbxl( - xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box)) - - # }}} - - # {{{ translate from list 3 multipoles to qbx local expansions - - result.update(self.process_m2qbxl( - xlat_cost, traversal, tree, global_qbx_centers, - qbx_center_to_target_box_source_level)) - - # }}} - - # {{{ translate from box local expansions to qbx local expansions - - result["l2qbxl"] = geo_data.ncenters * xlat_cost.l2qbxl() - - # }}} - - # {{{ evaluate qbx local expansions - - result.update(self.process_eval_qbxl( - xlat_cost, global_qbx_centers, center_to_targets_starts)) - - # }}} - - return result - -# }}} - - -# {{{ assemble_performance_data - -def assemble_performance_data(geo_data, uses_pde_expansions, - translation_source_power=None, translation_target_power=None, - translation_max_power=None, - summarize_parallel=None, merge_close_lists=True): - """Compute modeled performance using :class:`PerformanceModel`. - - See :class:`PerformanceModel` for parameter documentation. - """ - - return PerformanceModel( - uses_pde_expansions, - translation_source_power, - translation_target_power, - translation_max_power, - summarize_parallel, - merge_close_lists)(geo_data) - -# }}} - -# vim: foldmethod=marker diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 810a50ed..66445fee 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -183,7 +183,7 @@ def test_off_surface_eval_vs_direct(ctx_getter, do_plot=False): target_association_tolerance=0.05, ).with_refinement() - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) from pytential.target import PointsTarget ptarget = PointsTarget(fplot.points) from sumpy.kernel import LaplaceKernel @@ -315,6 +315,77 @@ def test_unregularized_off_surface_fmm_vs_direct(ctx_getter): # }}} +# {{{ test performance data gathering + +def test_perf_data_gathering(ctx_getter, n_arms=5): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + # prevent cache 'splosion + from sympy.core.cache import clear_cache + clear_cache() + + target_order = 8 + + starfish_func = NArmedStarfish(n_arms, 0.8) + mesh = make_curve_mesh( + starfish_func, + np.linspace(0, 1, n_arms * 30), + target_order) + + sigma_sym = sym.var("sigma") + + # The kernel doesn't really matter here + from sumpy.kernel import LaplaceKernel + k_sym = LaplaceKernel(mesh.ambient_dim) + + sym_op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory) + pre_density_discr = Discretization( + queue.context, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + + results = [] + + def inspect_geo_data(insn, bound_expr, geo_data): + from pytential.qbx.fmm import assemble_performance_data + perf_data = assemble_performance_data(geo_data, uses_pde_expansions=True) + results.append(perf_data) + + return False # no need to do the actual FMM + + from pytential.qbx import QBXLayerPotentialSource + lpot_source = QBXLayerPotentialSource( + pre_density_discr, 4*target_order, + # qbx order and fmm order don't really matter + 10, fmm_order=10, + _expansions_in_tree_have_extent=True, + _expansion_stick_out_factor=0.5, + geometry_data_inspector=inspect_geo_data, + target_association_tolerance=1e-10, + ) + + lpot_source, _ = lpot_source.with_refinement() + + density_discr = lpot_source.density_discr + + if 0: + from meshmode.discretization.visualization import draw_curve + draw_curve(density_discr) + import matplotlib.pyplot as plt + plt.show() + + nodes = density_discr.nodes().with_queue(queue) + sigma = cl.clmath.sin(10 * nodes[0]) + + bind(lpot_source, sym_op)(queue, sigma=sigma) + +# }}} + + # {{{ test 3D jump relations @pytest.mark.parametrize("relation", ["sp", "nxcurls", "div_s"]) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 49369e2f..942b6ed2 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -233,12 +233,12 @@ class DynamicTestCase(object): if (self.geometry.mesh_name == "sphere" and self.k != 0 and self.fmm_backend == "sumpy"): - raise ValueError("both direct eval and generating the FMM kernels " + pytest.skip("both direct eval and generating the FMM kernels " "are too slow") if (self.geometry.mesh_name == "sphere" and self.expr.zero_op_name == "green_grad"): - raise ValueError("does not achieve sufficient precision") + pytest.skip("does not achieve sufficient precision") if self.fmm_backend == "fmmlib": pytest.importorskip("pyfmmlib") @@ -246,28 +246,22 @@ class DynamicTestCase(object): # {{{ integral identity tester - -@pytest.mark.slowtest -@pytest.mark.parametrize("case", [ - DynamicTestCase(SphereGeometry(), GreenExpr(), 0), -]) -def test_identity_convergence_slow(ctx_getter, case): - test_identity_convergence(ctx_getter, case) - - @pytest.mark.parametrize("case", [ - # 2d - DynamicTestCase(StarfishGeometry(), GreenExpr(), 0), - DynamicTestCase(StarfishGeometry(), GreenExpr(), 1.2), - DynamicTestCase(StarfishGeometry(), GradGreenExpr(), 0), - DynamicTestCase(StarfishGeometry(), GradGreenExpr(), 1.2), - DynamicTestCase(StarfishGeometry(), ZeroCalderonExpr(), 0), - DynamicTestCase(StarfishGeometry(), GreenExpr(), 0, fmm_backend="fmmlib"), - DynamicTestCase(StarfishGeometry(), GreenExpr(), 1.2, fmm_backend="fmmlib"), - # 3d - DynamicTestCase(SphereGeometry(), GreenExpr(), 0, fmm_backend="fmmlib"), - DynamicTestCase(SphereGeometry(), GreenExpr(), 1.2, fmm_backend="fmmlib") -]) + tc + for geom in [ + StarfishGeometry(), + SphereGeometry(), + ] + for tc in [ + DynamicTestCase(geom, GreenExpr(), 0), + DynamicTestCase(geom, GreenExpr(), 1.2), + DynamicTestCase(geom, GradGreenExpr(), 0), + DynamicTestCase(geom, GradGreenExpr(), 1.2), + DynamicTestCase(geom, ZeroCalderonExpr(), 0), + + DynamicTestCase(geom, GreenExpr(), 0, fmm_backend="fmmlib"), + DynamicTestCase(geom, GreenExpr(), 1.2, fmm_backend="fmmlib"), + ]]) def test_identity_convergence(ctx_getter, case, visualize=False): logging.basicConfig(level=logging.INFO) diff --git a/test/test_performance_model.py b/test/test_performance_model.py deleted file mode 100644 index 90a87d1c..00000000 --- a/test/test_performance_model.py +++ /dev/null @@ -1,131 +0,0 @@ -from __future__ import division, print_function - -__copyright__ = "Copyright (C) 2018 Matt Wala" - -__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 pytest -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) - -from pytential import bind, sym, norm # noqa - - -# {{{ global params - -TARGET_ORDER = 8 -OVSMP_FACTOR = 5 -TCF = 0.9 -QBX_ORDER = 5 -FMM_ORDER = 10 - -DEFAULT_LPOT_KWARGS = { - "_box_extent_norm": "l2", - "_from_sep_smaller_crit": "static_l2", - } - -# }}} - - -@pytest.mark.parametrize("dim", (2, 3)) -def test_performance_model(ctx_getter, dim): - cl_ctx = ctx_getter() - queue = cl.CommandQueue(cl_ctx) - - # {{{ get lpot source - - from meshmode.discretization import Discretization - from meshmode.discretization.poly_element import ( - InterpolatoryQuadratureSimplexGroupFactory) - - target_order = TARGET_ORDER - - if dim == 2: - from meshmode.mesh.generation import starfish, make_curve_mesh - mesh = make_curve_mesh(starfish, np.linspace(0, 1, 50), order=target_order) - elif dim == 3: - from meshmode.mesh.generation import generate_icosphere - mesh = generate_icosphere(r=1, order=target_order) - else: - raise ValueError("unknown dimension: %d" % dim) - - pre_density_discr = Discretization( - queue.context, mesh, - InterpolatoryQuadratureSimplexGroupFactory(target_order)) - - lpot_kwargs = DEFAULT_LPOT_KWARGS.copy() - lpot_kwargs.update( - _expansion_stick_out_factor=TCF, - fmm_order=FMM_ORDER, qbx_order=QBX_ORDER - ) - - from pytential.qbx import QBXLayerPotentialSource - lpot_source = QBXLayerPotentialSource( - pre_density_discr, OVSMP_FACTOR*target_order, - **lpot_kwargs) - - lpot_source, _ = lpot_source.with_refinement() - - # }}} - - # {{{ run performance model - - costs = {} - - def inspect_geo_data(insn, bound_expr, geo_data): - from pytential.qbx.performance import assemble_performance_data - costs["costs"] = assemble_performance_data( - geo_data, uses_pde_expansions=True, merge_close_lists=False) - return False - - lpot_source = lpot_source.copy(geometry_data_inspector=inspect_geo_data) - density_discr = lpot_source.density_discr - nodes = density_discr.nodes().with_queue(queue) - sigma = cl.clmath.sin(10 * nodes[0]) - - from sumpy.kernel import LaplaceKernel - sigma_sym = sym.var("sigma") - k_sym = LaplaceKernel(lpot_source.ambient_dim) - sym_op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) - - bound_op = bind(lpot_source, sym_op) - bound_op(queue, sigma=sigma) - - # }}} - - -# You can test individual routines by typing -# $ python test_performance_model.py 'test_routine()' - -if __name__ == "__main__": - import sys - if len(sys.argv) > 1: - exec(sys.argv[1]) - else: - from pytest import main - main([__file__]) - - -# vim: foldmethod=marker -- GitLab From 5d333006141f20dbad4dec6a296a33ce784b5fd8 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 22 Jun 2018 10:37:49 -0500 Subject: [PATCH 167/268] add a comment to tests using qbx_order that don't need it --- test/test_global_qbx.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index d6ffeaf4..aacc53e5 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -97,8 +97,8 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): from pytential.qbx.utils import TreeCodeContainer lpot_source = QBXLayerPotentialSource(discr, - fine_order=order, - qbx_order=order // 2) + qbx_order=order, # not used in refinement + fine_order=order) del discr expansion_disturbance_tolerance = 0.025 @@ -242,7 +242,8 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, discr = Discretization(cl_ctx, mesh, factory) lpot_source, conn = QBXLayerPotentialSource(discr, - fine_order=order, qbx_order=order // 2).with_refinement() + qbx_order=order, # not used in target association + fine_order=order).with_refinement() del discr from pytential.qbx.utils import get_interleaved_centers @@ -421,7 +422,8 @@ def test_target_association_failure(ctx_getter): factory = InterpolatoryQuadratureSimplexGroupFactory(order) discr = Discretization(cl_ctx, mesh, factory) lpot_source = QBXLayerPotentialSource(discr, - fine_order=order, qbx_order=order // 2) + qbx_order=order, # not used in target association + fine_order=order) # }}} -- GitLab From 5937ce8229174b4cbb645c374ae178a9bc14cfa4 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 22 Jun 2018 11:23:18 -0500 Subject: [PATCH 168/268] flake8 --- test/test_global_qbx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index aacc53e5..86832d6e 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -97,7 +97,7 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): from pytential.qbx.utils import TreeCodeContainer lpot_source = QBXLayerPotentialSource(discr, - qbx_order=order, # not used in refinement + qbx_order=order, # not used in refinement fine_order=order) del discr @@ -242,7 +242,7 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements, discr = Discretization(cl_ctx, mesh, factory) lpot_source, conn = QBXLayerPotentialSource(discr, - qbx_order=order, # not used in target association + qbx_order=order, # not used in target association fine_order=order).with_refinement() del discr @@ -422,7 +422,7 @@ def test_target_association_failure(ctx_getter): factory = InterpolatoryQuadratureSimplexGroupFactory(order) discr = Discretization(cl_ctx, mesh, factory) lpot_source = QBXLayerPotentialSource(discr, - qbx_order=order, # not used in target association + qbx_order=order, # not used in target association fine_order=order) # }}} -- GitLab From 7ff59983afb16fa3c074e0eff9dede64f09d86db Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 24 Jun 2018 18:48:18 -0500 Subject: [PATCH 169/268] direct-solver: change default max_particles_in_box to None --- pytential/linalg/proxy.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index f8fb9845..ea04b4e9 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -66,7 +66,7 @@ def _element_node_range(group, ielement): def partition_by_nodes(queue, discr, use_tree=True, - max_nodes_in_box=30): + max_nodes_in_box=None): """Generate clusters / ranges of nodes. The partition is created at the lowest level of granularity, i.e. nodes. This results in balanced ranges of points, but will split elements across different ranges. @@ -84,6 +84,10 @@ def partition_by_nodes(queue, discr, `indices[ranges[i]:ranges[i + 1]]`. """ + if max_nodes_in_box is None: + # FIXME: this is just an arbitrary value + max_nodes_in_box = 32 + if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder @@ -119,7 +123,7 @@ def partition_by_nodes(queue, discr, def partition_by_elements(queue, discr, use_tree=True, - max_elements_in_box=10): + max_elements_in_box=None): """Generate clusters / ranges of points. The partition is created at the element level, so that all the nodes belonging to an element belong to the same range. This can result in slightly larger differences in size @@ -138,6 +142,14 @@ def partition_by_elements(queue, discr, integer arrays. The indices in a range can be retrieved using `indices[ranges[i]:ranges[i + 1]]`. """ + + if max_elements_in_box is None: + # NOTE: keep in sync with partition_by_nodes + max_nodes_in_box = 32 + + nunit_nodes = int(np.mean([g.nunit_nodes for g in discr.groups])) + max_elements_in_box = max_nodes_in_box // nunit_nodes + if use_tree: from boxtree import box_flags_enum from boxtree import TreeBuilder @@ -450,7 +462,7 @@ class ProxyGenerator(object): def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, - max_nodes_in_box=30, **kwargs): + max_nodes_in_box=None, **kwargs): """Generate a set of neighboring points for each range of points in :attr:`discr`. Neighboring points of a range :math:`i` are defined as all the points inside the proxy ball :math:`i` that do not also @@ -468,6 +480,10 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, get the slice using `nbrindices[nbrranges[i]:nbrranges[i + 1]]`. """ + if max_nodes_in_box is None: + # FIXME: this is a fairly arbitrary value + max_nodes_in_box = 32 + with cl.CommandQueue(discr.cl_context) as queue: if isinstance(srcindices, cl.array.Array): srcindices = srcindices.get(queue) -- GitLab From 276b01a2ae5f6aba158c7c43da4c5c233e9d6b40 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 24 Jun 2018 19:03:09 -0500 Subject: [PATCH 170/268] direct-solver: let loopy guess more of the kernel arguments --- pytential/linalg/proxy.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index ea04b4e9..6a46f145 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -374,19 +374,12 @@ class ProxyGenerator(object): shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), lp.GlobalArg("center_ext", None, shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), - lp.GlobalArg("expansion_radii", None, - shape="nnodes"), - lp.GlobalArg("srcranges", None, - shape="nranges + 1"), - lp.GlobalArg("srcindices", None, - shape="nindices"), lp.GlobalArg("proxy_center", None, shape=(self.ambient_dim, "nranges")), lp.GlobalArg("proxy_radius", None, shape="nranges"), - lp.ValueArg("nnodes", np.int64), - lp.ValueArg("nranges", None), - lp.ValueArg("nindices", np.int64) + lp.ValueArg("nnodes", np.int), + "..." ], name="proxy_generator_knl", assumptions="dim>=1 and nranges>=1", @@ -618,17 +611,11 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): shape=(source.ambient_dim, "nproxies"), dim_tags="sep,C"), lp.GlobalArg("nbrindices", None, shape="nnbrindices"), - lp.GlobalArg("pxyranges", None, - shape="nranges + 1"), - lp.GlobalArg("nbrranges", None, - shape="nranges + 1"), lp.GlobalArg("skeletons", None, shape=(source.ambient_dim, "nproxies + nnbrindices")), - lp.GlobalArg("sklranges", None, - shape="nranges + 1"), - lp.ValueArg("nsources", np.int32), - lp.ValueArg("nproxies", np.int32), - lp.ValueArg("nnbrindices", np.int32), + lp.ValueArg("nsources", np.int), + lp.ValueArg("nproxies", np.int), + lp.ValueArg("nnbrindices", np.int), "..." ], name="concat_skl", -- GitLab From 99fe4659afe8a11bce8d9eba66df99d43bd0dbd5 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 27 Jun 2018 21:09:31 -0500 Subject: [PATCH 171/268] Fix divide by zero error. --- examples/fmm-error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fmm-error.py b/examples/fmm-error.py index 110ec66b..c3350786 100644 --- a/examples/fmm-error.py +++ b/examples/fmm-error.py @@ -78,7 +78,7 @@ def main(): import matplotlib matplotlib.use('Agg') - im = fplot.show_scalar_in_matplotlib(np.log10(np.abs(err))) + im = fplot.show_scalar_in_matplotlib(np.log10(np.abs(err) + 1e-17)) from matplotlib.colors import Normalize im.set_norm(Normalize(vmin=-12, vmax=0)) -- GitLab From a5dc1ff649e0186356686e24f67f5d1900c32488 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 27 Jun 2018 20:31:19 -0500 Subject: [PATCH 172/268] direct-solver: port to new sumpy BlockIndexRanges and other fixes --- .test-conda-env-py3-requirements.txt | 2 +- pytential/linalg/proxy.py | 420 +++++++++++++-------------- requirements.txt | 2 +- test/test_linalg_proxy.py | 178 ++++++------ 4 files changed, 299 insertions(+), 303 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index fa6c0426..c5ef1e9b 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/fikl2/sumpy@block-index-additions git+https://github.com/inducer/meshmode diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 6a46f145..6a1d4cfe 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -32,6 +32,7 @@ from pyopencl.array import to_device from pytools.obj_array import make_obj_array from pytools import memoize_method, memoize +from sumpy.tools import BlockIndexRanges import loopy as lp from loopy.version import MOST_RECENT_LANGUAGE_VERSION @@ -49,9 +50,9 @@ Proxy Point Generation .. autofunction:: partition_from_coarse -.. autofunction:: build_neighbor_list +.. autofunction:: gather_block_neighbor_points -.. autofunction:: build_skeleton_list +.. autofunction:: gather_block_interaction_points """ @@ -64,14 +65,13 @@ def _element_node_range(group, ielement): return np.arange(istart, iend) -def partition_by_nodes(queue, discr, +def partition_by_nodes(discr, use_tree=True, max_nodes_in_box=None): """Generate clusters / ranges of nodes. The partition is created at the lowest level of granularity, i.e. nodes. This results in balanced ranges of points, but will split elements across different ranges. - :arg queue: a :class:`pyopencl.CommandQueue`. :arg discr: a :class:`meshmode.discretization.Discretization`. :arg use_tree: if `True`, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close @@ -79,49 +79,50 @@ def partition_by_nodes(queue, discr, partition is constructed. :arg max_nodes_in_box: passed to :class:`boxtree.TreeBuilder`. - :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` - integer arrays. The indices in a range can be retrieved using - `indices[ranges[i]:ranges[i + 1]]`. + :return: a :class:`sumpy.tools.BlockIndexRanges`. """ if max_nodes_in_box is None: # FIXME: this is just an arbitrary value max_nodes_in_box = 32 - if use_tree: - from boxtree import box_flags_enum - from boxtree import TreeBuilder + with cl.CommandQueue(discr.cl_context) as queue: + if use_tree: + from boxtree import box_flags_enum + from boxtree import TreeBuilder - builder = TreeBuilder(discr.cl_context) + builder = TreeBuilder(discr.cl_context) - tree, _ = builder(queue, discr.nodes(), - max_particles_in_box=max_nodes_in_box) + tree, _ = builder(queue, discr.nodes(), + max_particles_in_box=max_nodes_in_box) - tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() - indices = np.empty(len(leaf_boxes), dtype=np.object) - for i, ibox in enumerate(leaf_boxes): - box_start = tree.box_source_starts[ibox] - box_end = box_start + tree.box_source_counts_cumul[ibox] - indices[i] = tree.user_source_ids[box_start:box_end] + indices = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] + indices[i] = tree.user_source_ids[box_start:box_end] - ranges = to_device(queue, - np.cumsum([0] + [box.shape[0] for box in indices])) - indices = to_device(queue, np.hstack(indices)) - else: - indices = cl.array.arange(queue, 0, discr.nnodes, - dtype=np.int) - ranges = cl.array.arange(queue, 0, discr.nnodes + 1, - discr.nnodes // max_nodes_in_box, - dtype=np.int) + ranges = to_device(queue, + np.cumsum([0] + [box.shape[0] for box in indices])) + indices = to_device(queue, np.hstack(indices)) + else: + indices = cl.array.arange(queue, 0, discr.nnodes, + dtype=np.int) + ranges = cl.array.arange(queue, 0, discr.nnodes + 1, + discr.nnodes // max_nodes_in_box, + dtype=np.int) + assert ranges[-1] == discr.nnodes - assert ranges[-1] == discr.nnodes - return indices, ranges + return BlockIndexRanges(discr.cl_context, + indices.with_queue(None), + ranges.with_queue(None)) -def partition_by_elements(queue, discr, +def partition_by_elements(discr, use_tree=True, max_elements_in_box=None): """Generate clusters / ranges of points. The partition is created at the @@ -130,7 +131,6 @@ def partition_by_elements(queue, discr, between the ranges, but can be very useful when the individual partitions need to be resampled, integrated, etc. - :arg queue: a :class:`pyopencl.CommandQueue`. :arg discr: a :class:`meshmode.discretization.Discretization`. :arg use_tree: if True, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close @@ -138,9 +138,7 @@ def partition_by_elements(queue, discr, partition is constructed. :arg max_elements_in_box: passed to :class:`boxtree.TreeBuilder`. - :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` - integer arrays. The indices in a range can be retrieved using - `indices[ranges[i]:ranges[i + 1]]`. + :return: a :class:`sumpy.tools.BlockIndexRanges`. """ if max_elements_in_box is None: @@ -150,57 +148,60 @@ def partition_by_elements(queue, discr, nunit_nodes = int(np.mean([g.nunit_nodes for g in discr.groups])) max_elements_in_box = max_nodes_in_box // nunit_nodes - if use_tree: - from boxtree import box_flags_enum - from boxtree import TreeBuilder + with cl.CommandQueue(discr.cl_context) as queue: + if use_tree: + from boxtree import box_flags_enum + from boxtree import TreeBuilder - builder = TreeBuilder(discr.cl_context) + builder = TreeBuilder(discr.cl_context) - from pytential.qbx.utils import element_centers_of_mass - elranges = np.cumsum([group.nelements for group in discr.mesh.groups]) - elcenters = element_centers_of_mass(discr) + from pytential.qbx.utils import element_centers_of_mass + elranges = np.cumsum([group.nelements for group in discr.mesh.groups]) + elcenters = element_centers_of_mass(discr) - tree, _ = builder(queue, elcenters, - max_particles_in_box=max_elements_in_box) + tree, _ = builder(queue, elcenters, + max_particles_in_box=max_elements_in_box) - groups = discr.groups - tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + groups = discr.groups + tree = tree.get(queue) + leaf_boxes, = (tree.box_flags & + box_flags_enum.HAS_CHILDREN == 0).nonzero() - indices = np.empty(len(leaf_boxes), dtype=np.object) - for i, ibox in enumerate(leaf_boxes): - box_start = tree.box_source_starts[ibox] - box_end = box_start + tree.box_source_counts_cumul[ibox] + indices = np.empty(len(leaf_boxes), dtype=np.object) + for i, ibox in enumerate(leaf_boxes): + box_start = tree.box_source_starts[ibox] + box_end = box_start + tree.box_source_counts_cumul[ibox] - ielement = tree.user_source_ids[box_start:box_end] - igroup = np.digitize(ielement, elranges) + ielement = tree.user_source_ids[box_start:box_end] + igroup = np.digitize(ielement, elranges) - indices[i] = np.hstack([_element_node_range(groups[j], k) - for j, k in zip(igroup, ielement)]) - else: - nelements = discr.mesh.nelements - elements = np.array_split(np.arange(0, nelements), - nelements // max_elements_in_box) + indices[i] = np.hstack([_element_node_range(groups[j], k) + for j, k in zip(igroup, ielement)]) + else: + nelements = discr.mesh.nelements + elements = np.array_split(np.arange(0, nelements), + nelements // max_elements_in_box) - elranges = np.cumsum([g.nelements for g in discr.groups]) - elgroups = [np.digitize(elements[i], elranges) - for i in range(len(elements))] + elranges = np.cumsum([g.nelements for g in discr.groups]) + elgroups = [np.digitize(elements[i], elranges) + for i in range(len(elements))] - indices = np.empty(len(elements), dtype=np.object) - for i in range(indices.shape[0]): - indices[i] = np.hstack([_element_node_range(discr.groups[j], k) - for j, k in zip(elgroups[i], elements[i])]) + indices = np.empty(len(elements), dtype=np.object) + for i in range(indices.shape[0]): + indices[i] = np.hstack([_element_node_range(discr.groups[j], k) + for j, k in zip(elgroups[i], elements[i])]) - ranges = to_device(queue, - np.cumsum([0] + [box.shape[0] for box in indices])) - indices = to_device(queue, np.hstack(indices)) + ranges = to_device(queue, + np.cumsum([0] + [b.shape[0] for b in indices])) + indices = to_device(queue, np.hstack(indices)) + assert ranges[-1] == discr.nnodes - assert ranges[-1] == discr.nnodes - return indices, ranges + return BlockIndexRanges(discr.cl_context, + indices.with_queue(None), + ranges.with_queue(None)) -def partition_from_coarse(queue, resampler, from_indices, from_ranges): +def partition_from_coarse(resampler, from_indices): """Generate a partition of nodes from an existing partition on a coarser discretization. The new partition is generated based on element refinement relationships in :attr:`resampler`, so the existing partition @@ -212,69 +213,64 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): :attr:`resampler.to_discr` that belong to the same region as the nodes in the same range from :attr:`resampler.from_discr`. - :arg queue: a :class:`pyopencl.CommandQueue`. :arg resampler: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection`. - :arg from_indices: a set of indices into the nodes in - :attr:`resampler.from_discr`. - :arg from_ranges: array used to index into :attr:`from_indices`. + :arg from_indices: a :class:`sumpy.tools.BlockIndexRanges`. - :return: a tuple `(indices, ranges)` of :class:`pyopencl.array.Array` - integer arrays. The indices in a range can be retrieved using - `indices[ranges[i]:ranges[i + 1]]`. + :return: a tuple :class:`sumpy.tools.BlockIndexRanges`. """ if not hasattr(resampler, "groups"): raise ValueError("resampler must be a DirectDiscretizationConnection.") - if isinstance(from_ranges, cl.array.Array): + with cl.CommandQueue(resampler.cl_context) as queue: from_indices = from_indices.get(queue) - from_ranges = from_ranges.get(queue) - - # construct ranges - from_discr = resampler.from_discr - from_grp_ranges = np.cumsum([0] + - [grp.nelements for grp in from_discr.mesh.groups]) - from_el_ranges = np.hstack([ - np.arange(grp.node_nr_base, grp.nnodes + 1, grp.nunit_nodes) - for grp in from_discr.groups]) - - # construct coarse element arrays in each from_range - el_indices = np.empty(from_ranges.shape[0] - 1, dtype=np.object) - el_ranges = np.full(from_grp_ranges[-1], -1, dtype=np.int) - for i in range(from_ranges.shape[0] - 1): - irange = np.s_[from_ranges[i]:from_ranges[i + 1]] - el_indices[i] = \ - np.unique(np.digitize(from_indices[irange], from_el_ranges)) - 1 - el_ranges[el_indices[i]] = i - el_indices = np.hstack(el_indices) - - # construct lookup table - to_el_table = [np.full(g.nelements, -1, dtype=np.int) - for g in resampler.to_discr.groups] - - for igrp, grp in enumerate(resampler.groups): - for batch in grp.batches: - to_el_table[igrp][batch.to_element_indices.get(queue)] = \ - from_grp_ranges[igrp] + batch.from_element_indices.get(queue) - - # construct fine node index list - indices = [np.empty(0, dtype=np.int) - for _ in range(from_ranges.shape[0] - 1)] - for igrp in range(len(resampler.groups)): - to_element_indices = \ - np.where(np.isin(to_el_table[igrp], el_indices))[0] - - for i, j in zip(el_ranges[to_el_table[igrp][to_element_indices]], - to_element_indices): - indices[i] = np.hstack([indices[i], - _element_node_range(resampler.to_discr.groups[igrp], j)]) - - ranges = to_device(queue, - np.cumsum([0] + [box.shape[0] for box in indices])) - indices = to_device(queue, np.hstack(indices)) - - return indices, ranges + + # construct ranges + from_discr = resampler.from_discr + from_grp_ranges = np.cumsum([0] + + [grp.nelements for grp in from_discr.mesh.groups]) + from_el_ranges = np.hstack([ + np.arange(grp.node_nr_base, grp.nnodes + 1, grp.nunit_nodes) + for grp in from_discr.groups]) + + # construct coarse element arrays in each from_range + el_indices = np.empty(from_indices.nblocks, dtype=np.object) + el_ranges = np.full(from_grp_ranges[-1], -1, dtype=np.int) + for i in range(from_indices.nblocks): + ifrom = from_indices.block_indices(i) + el_indices[i] = np.unique(np.digitize(ifrom, from_el_ranges)) - 1 + el_ranges[el_indices[i]] = i + el_indices = np.hstack(el_indices) + + # construct lookup table + to_el_table = [np.full(g.nelements, -1, dtype=np.int) + for g in resampler.to_discr.groups] + + for igrp, grp in enumerate(resampler.groups): + for batch in grp.batches: + to_el_table[igrp][batch.to_element_indices.get(queue)] = \ + from_grp_ranges[igrp] + batch.from_element_indices.get(queue) + + # construct fine node index list + indices = [np.empty(0, dtype=np.int) + for _ in range(from_indices.nblocks)] + for igrp in range(len(resampler.groups)): + to_element_indices = \ + np.where(np.isin(to_el_table[igrp], el_indices))[0] + + for i, j in zip(el_ranges[to_el_table[igrp][to_element_indices]], + to_element_indices): + indices[i] = np.hstack([indices[i], + _element_node_range(resampler.to_discr.groups[igrp], j)]) + + ranges = to_device(queue, + np.cumsum([0] + [b.shape[0] for b in indices])) + indices = to_device(queue, np.hstack(indices)) + + return BlockIndexRanges(resampler.cl_context, + indices.with_queue(None), + ranges.with_queue(None)) # }}} @@ -283,10 +279,16 @@ def partition_from_coarse(queue, resampler, from_indices, from_ranges): class ProxyGenerator(object): r""" - :arg discr: a :class:`pytential.qbx.QBXLayerPotentialSource`. - :arg nproxy: number of proxy points. - :arg ratio: a ratio used to compute the proxy point radius. The radius - is computed in the :math:`L_2` norm, resulting in a circle or + .. attribute:: nproxy + .. attribute:: ambient_dim + .. attribute:: source + + A :class:`pytential.qbx.QBXLayerPotentialSource`. + + .. attribute:: ratio + + A ratio used to compute the proxy point radius. The radius + is computed in the :math:`\ell^2` norm, resulting in a circle or sphere of proxy points. For QBX, we have two radii of interest for a set of points: the radius :math:`r_{block}` of the smallest ball containing all the points and the radius @@ -304,14 +306,21 @@ class ProxyGenerator(object): r = \theta r_{qbx}. + .. attribute:: ref_mesh + + Reference mesh. Can be used to construct a mesh for a proxy + ball :math:`i` by translating it to `center[i]` and scaling by + `radii[i]`, as obtained by :meth:`__call__` (see + :meth:`meshmode.mesh.processing.affine_map`). + .. automethod:: __call__ """ - def __init__(self, source, nproxy=30, ratio=1.1, **kwargs): + def __init__(self, source, nproxy=None, ratio=None, **kwargs): self.source = source - self.ratio = abs(ratio) - self.nproxy = int(abs(nproxy)) self.ambient_dim = source.density_discr.ambient_dim + self.ratio = 1.1 if ratio is None else ratio + self.nproxy = 32 if nproxy is None else nproxy if self.ambient_dim == 2: from meshmode.mesh.generation import ellipse, make_curve_mesh @@ -347,12 +356,12 @@ class ProxyGenerator(object): <> npoints = srcranges[irange + 1] - srcranges[irange] proxy_center[idim, irange] = 1.0 / npoints * \ - reduce(sum, i, nodes[idim, srcindices[i + ioffset]]) \ + reduce(sum, i, sources[idim, srcindices[i + ioffset]]) \ {{dup=idim:i}} <> rblk = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - - nodes[idim, srcindices[i + ioffset]]) ** 2))) + sources[idim, srcindices[i + ioffset]]) ** 2))) <> rqbx_int = simul_reduce(max, i, sqrt(simul_reduce(sum, idim, \ (proxy_center[idim, irange] - @@ -368,17 +377,17 @@ class ProxyGenerator(object): end """.format(radius_expr=radius_expr)], [ - lp.GlobalArg("nodes", None, - shape=(self.ambient_dim, "nnodes")), + lp.GlobalArg("sources", None, + shape=(self.ambient_dim, "nsources")), lp.GlobalArg("center_int", None, - shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), + shape=(self.ambient_dim, "nsources"), dim_tags="sep,C"), lp.GlobalArg("center_ext", None, - shape=(self.ambient_dim, "nnodes"), dim_tags="sep,C"), + shape=(self.ambient_dim, "nsources"), dim_tags="sep,C"), lp.GlobalArg("proxy_center", None, shape=(self.ambient_dim, "nranges")), lp.GlobalArg("proxy_radius", None, shape="nranges"), - lp.ValueArg("nnodes", np.int), + lp.ValueArg("nsources", np.int), "..." ], name="proxy_generator_knl", @@ -397,16 +406,12 @@ class ProxyGenerator(object): return knl - def __call__(self, queue, srcindices, srcranges, **kwargs): + def __call__(self, queue, indices, **kwargs): """Generate proxy points for each given range of source points in the discretization in :attr:`source`. :arg queue: a :class:`pyopencl.CommandQueue`. - :arg srcindices: a :class:`pyopencl.array.Array` of indices around - which to construct proxy balls. - :arg srcranges: an :class:`pyopencl.array.Array` of size `(nranges + 1,)` - used to index into :attr:`srcindices`. Each one of the `nranges` - ranges will get a proxy ball. + :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. :return: a tuple of `(proxies, pxyranges, pxycenters, pxyranges)`, where each element is a :class:`pyopencl.array.Array`. The @@ -423,18 +428,19 @@ class ProxyGenerator(object): knl = self.get_kernel() _, (centers_dev, radii_dev,) = knl(queue, - nodes=self.source.density_discr.nodes(), + sources=self.source.density_discr.nodes(), center_int=get_centers_on_side(self.source, -1), center_ext=get_centers_on_side(self.source, +1), expansion_radii=self.source._expansion_radii("nsources"), - srcindices=srcindices, srcranges=srcranges, **kwargs) + srcindices=indices.indices, + srcranges=indices.ranges, **kwargs) centers = centers_dev.get() radii = radii_dev.get() from meshmode.mesh.processing import affine_map - proxies = np.empty(srcranges.shape[0] - 1, dtype=np.object) + proxies = np.empty(indices.nblocks, dtype=np.object) - for i in range(srcranges.shape[0] - 1): + for i in range(indices.nblocks): mesh = affine_map(self.ref_mesh, A=(radii[i] * np.eye(self.ambient_dim)), b=centers[:, i].reshape(-1)) @@ -442,7 +448,7 @@ class ProxyGenerator(object): pxyranges = cl.array.arange(queue, 0, proxies.shape[0] * proxies[0].shape[-1] + 1, proxies[0].shape[-1], - dtype=srcranges.dtype) + dtype=indices.ranges.dtype) proxies = make_obj_array([ cl.array.to_device(queue, np.hstack([p[idim] for p in proxies])) for idim in range(self.ambient_dim)]) @@ -454,23 +460,19 @@ class ProxyGenerator(object): return proxies, pxyranges, centers, radii_dev -def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, - max_nodes_in_box=None, **kwargs): +def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, + max_nodes_in_box=None, **kwargs): """Generate a set of neighboring points for each range of points in :attr:`discr`. Neighboring points of a range :math:`i` are defined as all the points inside the proxy ball :math:`i` that do not also belong to the range itself. :arg discr: a :class:`meshmode.discretization.Discretization`. - :arg srcindices: an array of indices for a subset of the nodes in - :attr:`discr`. - :arg srcranges: an array used to index into the :attr:`srcindices` array. + :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. :arg pxycenters: an array containing the center of each proxy ball. :arg pxyradii: an array containing the radius of each proxy ball. - :return: a tuple `(nbrindices, nbrranges)`, where each value is a - :class:`pyopencl.array.Array`. For a range :math:`i`, we can - get the slice using `nbrindices[nbrranges[i]:nbrranges[i + 1]]`. + :return: a tuple :class:`sumpy.tools.BlockIndexRanges`. """ if max_nodes_in_box is None: @@ -478,21 +480,18 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, max_nodes_in_box = 32 with cl.CommandQueue(discr.cl_context) as queue: - if isinstance(srcindices, cl.array.Array): - srcindices = srcindices.get(queue) - if isinstance(srcranges, cl.array.Array): - srcranges = srcranges.get(queue) + indices = indices.get(queue) - # NOTE: this is used for multiple reasons: + # NOTE: this is constructed for multiple reasons: # * TreeBuilder takes object arrays - # * `srcndices` can be a small subset of nodes, so this will save + # * `srcindices` can be a small subset of nodes, so this will save # some work # * `srcindices` may reorder the array returned by nodes(), so this # makes sure that we have the same order in tree.user_source_ids # and friends sources = discr.nodes().get(queue) sources = make_obj_array([ - cl.array.to_device(queue, sources[idim, srcindices]) + cl.array.to_device(queue, sources[idim, indices.indices]) for idim in range(discr.ambient_dim)]) # construct tree @@ -515,8 +514,8 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, if isinstance(pxyradii, cl.array.Array): pxyradii = pxyradii.get(queue) - nbrindices = np.empty(srcranges.shape[0] - 1, dtype=np.object) - for iproxy in range(srcranges.shape[0] - 1): + nbrindices = np.empty(indices.nblocks, dtype=np.object) + for iproxy in range(indices.nblocks): # get list of boxes intersecting the current ball istart = query.leaves_near_ball_starts[iproxy] iend = query.leaves_near_ball_starts[iproxy + 1] @@ -535,44 +534,42 @@ def build_neighbor_list(discr, srcindices, srcranges, pxycenters, pxyradii, center = pxycenters[:, iproxy].reshape(-1, 1) radius = pxyradii[iproxy] mask = (la.norm(nodes - center, axis=0) < radius) & \ - ((isources < srcranges[iproxy]) | - (srcranges[iproxy + 1] <= isources)) + ((isources < indices.ranges[iproxy]) | + (indices.ranges[iproxy + 1] <= isources)) - nbrindices[iproxy] = srcindices[isources[mask]] + nbrindices[iproxy] = indices.indices[isources[mask]] nbrranges = to_device(queue, np.cumsum([0] + [n.shape[0] for n in nbrindices])) nbrindices = to_device(queue, np.hstack(nbrindices)) - return nbrindices, nbrranges + return BlockIndexRanges(discr.cl_context, + nbrindices.with_queue(None), + nbrranges.with_queue(None)) -def build_skeleton_list(source, srcindices, srcranges, **kwargs): - """Generate sets of skeleton points for each given range of indices - in the :attr:`source` discretization. Skeleton points are meant to - model the interactions of a set of points. They are composed of two - parts: +def gather_block_interaction_points(source, indices, + ratio=None, + nproxy=None, + max_nodes_in_box=None): + """Generate sets of interaction points for each given range of indices + in the :attr:`source` discretization. For each input range of indices, + the corresponding output range of points is consists of: - - a set of proxy points (or balls) around a given range, which - models farfield interactions. + - a set of proxy points (or balls) around the range, which + model farfield interactions. These are constructed using + :class:`ProxyGenerator`. - a set of neighboring points that are inside the proxy balls, but do not belong to the given range, which model nearby interactions. + These are constructed with :meth:`gather_block_neighbor_points`. :arg source: a :class:`pytential.qbx.QBXLayerPotentialSource`. - :arg srcindices: a :class:`pyopencl.array.Array` of indices for points - in :attr:`source`. - :arg srcranges: a :class:`pyopencl.array.Array` describing the ranges - from :attr:`srcindices` around which to build proxy points. For each - range, this builds a ball of proxy points centered - at the center of mass of the points in the range with a radius - defined by :attr:`ratio`. - :arg kwargs: additional arguments passed to :class:`ProxyGenerator` - or :func:`build_neighbor_list`. - - :returns: a tuple `(skeletons, sklranges)`, where each value is a + :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. + + :return: a tuple `(nodes, ranges)`, where each value is a :class:`pyopencl.array.Array`. For a range :math:`i`, we can - get the slice using `skeletons[sklranges[i]:sklranges[i + 1]]`. + get the slice using `nodes[ranges[i]:ranges[i + 1]]`. """ @memoize @@ -593,15 +590,14 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): <> ngbend = nbrranges[irange + 1] <> nngbblock = ngbend - ngbstart - <> sklstart = pxyranges[irange] + nbrranges[irange] - skeletons[idim, sklstart + ipxy] = \ + <> istart = pxyranges[irange] + nbrranges[irange] + nodes[idim, istart + ipxy] = \ proxies[idim, pxystart + ipxy] \ {id_prefix=write_pxy,nosync=write_ngb} - skeletons[idim, sklstart + npxyblock + ingb] = \ + nodes[idim, istart + npxyblock + ingb] = \ sources[idim, nbrindices[ngbstart + ingb]] \ {id_prefix=write_ngb,nosync=write_pxy} - sklranges[irange + 1] = sklranges[irange] + \ - npxyblock + nngbblock + ranges[irange + 1] = ranges[irange] + npxyblock + nngbblock end """, [ @@ -611,14 +607,14 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): shape=(source.ambient_dim, "nproxies"), dim_tags="sep,C"), lp.GlobalArg("nbrindices", None, shape="nnbrindices"), - lp.GlobalArg("skeletons", None, + lp.GlobalArg("nodes", None, shape=(source.ambient_dim, "nproxies + nnbrindices")), lp.ValueArg("nsources", np.int), lp.ValueArg("nproxies", np.int), lp.ValueArg("nnbrindices", np.int), "..." ], - name="concat_skl", + name="concat_proxy_and_neighbors", default_offset=lp.auto, silenced_warnings="write_race(write_*)", fixed_parameters=dict(dim=source.ambient_dim), @@ -630,23 +626,23 @@ def build_skeleton_list(source, srcindices, srcranges, **kwargs): return loopy_knl with cl.CommandQueue(source.cl_context) as queue: - proxy = ProxyGenerator(source, **kwargs) - proxies, pxyranges, pxycenters, pxyradii = \ - proxy(queue, srcindices, srcranges) + generator = ProxyGenerator(source, ratio=ratio, nproxy=nproxy) + proxies, pxyranges, pxycenters, pxyradii = generator(queue, indices) - nbrindices, nbrranges = build_neighbor_list(source.density_discr, - srcindices, srcranges, pxycenters, pxyradii, **kwargs) + neighbors = gather_block_neighbor_points(source.density_discr, + indices, pxycenters, pxyradii, + max_nodes_in_box=max_nodes_in_box) - sklranges = cl.array.zeros(queue, srcranges.shape, dtype=np.int) - _, (skeletons, sklranges) = knl()(queue, + ranges = cl.array.zeros(queue, indices.nblocks + 1, dtype=np.int) + _, (nodes, ranges) = knl()(queue, sources=source.density_discr.nodes(), proxies=proxies, pxyranges=pxyranges, - nbrindices=nbrindices, - nbrranges=nbrranges, - sklranges=sklranges) + nbrindices=neighbors.indices, + nbrranges=neighbors.ranges, + ranges=ranges) - return skeletons, sklranges + return nodes.with_queue(None), ranges.with_queue(None) # }}} diff --git a/requirements.txt b/requirements.txt index 8925c34e..9cd4ee99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/fik2/sumpy@block-index-additions git+https://github.com/inducer/pyfmmlib diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index 6345c775..a9d628f4 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -31,6 +31,7 @@ import numpy.linalg as la import pyopencl as cl from pyopencl.array import to_device +from sumpy.tools import BlockIndexRanges from meshmode.mesh.generation import ( # noqa ellipse, NArmedStarfish, generate_torus, make_curve_mesh) @@ -75,11 +76,11 @@ def _build_qbx_discr(queue, return qbx -def _build_block_index(queue, discr, - nblks=10, - factor=1.0, - method='elements', - use_tree=True): +def _build_block_index(discr, + nblks=10, + factor=1.0, + method='elements', + use_tree=True): from pytential.linalg.proxy import ( partition_by_nodes, partition_by_elements) @@ -94,38 +95,46 @@ def _build_block_index(queue, discr, max_particles_in_box = nnodes // nblks # create index ranges - if method == "nodes": - indices, ranges = partition_by_nodes(queue, discr, - use_tree=use_tree, max_nodes_in_box=max_particles_in_box) - elif method == "elements": - indices, ranges = partition_by_elements(queue, discr, - use_tree=use_tree, max_elements_in_box=max_particles_in_box) + if method == 'nodes': + indices = partition_by_nodes(discr, + use_tree=use_tree, + max_nodes_in_box=max_particles_in_box) + elif method == 'elements': + indices = partition_by_elements(discr, + use_tree=use_tree, + max_elements_in_box=max_particles_in_box) else: - raise ValueError("unknown method: {}".format(method)) + raise ValueError('unknown method: {}'.format(method)) # randomly pick a subset of points if abs(factor - 1.0) > 1.0e-14: - indices = indices.get(queue) - ranges = ranges.get(queue) + with cl.CommandQueue(discr.cl_context) as queue: + indices = indices.get(queue) + + indices_ = np.empty(indices.nblocks, dtype=np.object) + for i in range(indices.nblocks): + iidx = indices.block_indices(i) + isize = int(factor * len(iidx)) + isize = max(1, min(isize, len(iidx))) - indices_ = np.empty(ranges.shape[0] - 1, dtype=np.object) - for i in range(ranges.shape[0] - 1): - iidx = indices[np.s_[ranges[i]:ranges[i + 1]]] - isize = int(factor * len(iidx)) - isize = max(1, min(isize, len(iidx))) + indices_[i] = np.sort( + np.random.choice(iidx, size=isize, replace=False)) - indices_[i] = np.sort( - np.random.choice(iidx, size=isize, replace=False)) + ranges_ = to_device(queue, + np.cumsum([0] + [r.shape[0] for r in indices_])) + indices_ = to_device(queue, np.hstack(indices_)) - ranges = to_device(queue, - np.cumsum([0] + [r.shape[0] for r in indices_])) - indices = to_device(queue, np.hstack(indices_)) + indices = BlockIndexRanges(discr.cl_context, + indices_.with_queue(None), + ranges_.with_queue(None)) - return indices, ranges + return indices -def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): +def _plot_partition_indices(queue, discr, indices, **kwargs): import matplotlib.pyplot as pt + indices = indices.get(queue) + args = [ kwargs.get("method", "unknown"), "tree" if kwargs.get("use_tree", False) else "linear", @@ -133,12 +142,8 @@ def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): discr.ambient_dim ] - if isinstance(indices, cl.array.Array): - indices = indices.get() - ranges = ranges.get() - pt.figure(figsize=(10, 8), dpi=300) - pt.plot(np.diff(ranges)) + pt.plot(np.diff(indices.ranges)) pt.savefig("test_partition_{0}_{1}_{3}d_ranges_{2}.png".format(*args)) pt.clf() @@ -147,10 +152,10 @@ def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): pt.figure(figsize=(10, 8), dpi=300) - if indices.shape[0] != discr.nnodes: + if indices.indices.shape[0] != discr.nnodes: pt.plot(sources[0], sources[1], 'ko', alpha=0.5) - for i in range(ranges.shape[0] - 1): - isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + for i in range(indices.nblocks): + isrc = indices.block_indices(i) pt.plot(sources[0][isrc], sources[1][isrc], 'o') pt.xlim([-1.5, 1.5]) @@ -167,8 +172,8 @@ def _plot_partition_indices(queue, discr, indices, ranges, **kwargs): from meshmode.discretization.visualization import make_visualizer marker = -42.0 * np.ones(discr.nnodes) - for i in range(ranges.shape[0] - 1): - isrc = indices[np.s_[ranges[i]:ranges[i + 1]]] + for i in range(indices.nblocks): + isrc = indices.block_indices(i) marker[isrc] = 10.0 * (i + 1.0) vis = make_visualizer(queue, discr, 10) @@ -190,7 +195,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices = _build_block_index(qbx.density_discr, method=method, use_tree=use_tree, factor=0.6) @@ -201,35 +206,32 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices = _build_block_index(qbx.density_discr, method="elements", use_tree=use_tree) if visualize: discr = qbx.resampler.from_discr - _plot_partition_indices(queue, discr, srcindices, srcranges, + _plot_partition_indices(queue, discr, srcindices, method="elements", use_tree=use_tree, pid="stage1") from pytential.linalg.proxy import partition_from_coarse resampler = qbx.direct_resampler t_start = time.time() - srcindices_, srcranges_ = \ - partition_from_coarse(queue, resampler, srcindices, srcranges) + srcindices_ = partition_from_coarse(resampler, srcindices) t_end = time.time() if visualize: print('Time: {:.5f}s'.format(t_end - t_start)) - srcindices = srcindices.get() - srcranges = srcranges.get() - srcindices_ = srcindices_.get() - srcranges_ = srcranges_.get() + srcindices = srcindices.get(queue) + srcindices_ = srcindices_.get(queue) sources = resampler.from_discr.nodes().get(queue) sources_ = resampler.to_discr.nodes().get(queue) - for i in range(srcranges.shape[0] - 1): - isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - isrc_ = srcindices_[np.s_[srcranges_[i]:srcranges_[i + 1]]] + for i in range(srcindices.nblocks): + isrc = srcindices.block_indices(i) + isrc_ = srcindices_.block_indices(i) for j in range(ndim): assert np.min(sources_[j][isrc_]) <= np.min(sources[j][isrc]) @@ -237,7 +239,7 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): if visualize: discr = resampler.to_discr - _plot_partition_indices(queue, discr, srcindices_, srcranges_, + _plot_partition_indices(queue, discr, srcindices_, method="elements", use_tree=use_tree, pid="stage2") @@ -248,29 +250,25 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) from pytential.linalg.proxy import ProxyGenerator generator = ProxyGenerator(qbx, ratio=1.1) - proxies, pxyranges, pxycenters, pxyradii = \ - generator(queue, srcindices, srcranges) + proxies, pxyranges, pxycenters, pxyradii = generator(queue, srcindices) proxies = np.vstack([p.get() for p in proxies]) pxyranges = pxyranges.get() pxycenters = np.vstack([c.get() for c in pxycenters]) pxyradii = pxyradii.get() - for i in range(srcranges.shape[0] - 1): + for i in range(srcindices.nblocks): ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] r = la.norm(proxies[:, ipxy] - pxycenters[:, i].reshape(-1, 1), axis=0) assert np.allclose(r - pxyradii[i], 0.0, atol=1.0e-14) - if visualize: - srcindices = srcindices.get() - srcranges = srcranges.get() - + srcindices = srcindices.get(queue) if visualize: if qbx.ambient_dim == 2: import matplotlib.pyplot as pt @@ -283,8 +281,8 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): ce = np.vstack([c.get(queue) for c in ce]) r = qbx._expansion_radii("nsources").get(queue) - for i in range(srcranges.shape[0] - 1): - isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] + for i in range(srcindices.nblocks): + isrc = srcindices.block_indices(i) ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] pt.figure(figsize=(10, 8)) @@ -297,7 +295,8 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): pt.plot(density_nodes[0], density_nodes[1], 'ko', ms=2.0, alpha=0.5) - pt.plot(density_nodes[0, srcindices], density_nodes[1, srcindices], + pt.plot(density_nodes[0, srcindices.indices], + density_nodes[1, srcindices.indices], 'o', ms=2.0) pt.plot(density_nodes[0, isrc], density_nodes[1, isrc], 'o', ms=2.0) @@ -317,7 +316,7 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - for i in range(srcranges.shape[0] - 1): + for i in range(srcindices.nblocks): mesh = affine_map(generator.ref_mesh, A=(pxyradii[i] * np.eye(ndim)), b=pxycenters[:, i].reshape(-1)) @@ -335,57 +334,58 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_area_query(ctx_factory, ndim, factor, visualize=False): +def test_interaction_points(ctx_factory, ndim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) # generate proxy points from pytential.linalg.proxy import ProxyGenerator generator = ProxyGenerator(qbx) - _, _, pxycenters, pxyradii = generator(queue, srcindices, srcranges) + _, _, pxycenters, pxyradii = generator(queue, srcindices) - from pytential.linalg.proxy import build_neighbor_list, build_skeleton_list - nbrindices, nbrranges = build_neighbor_list(qbx.density_discr, - srcindices, srcranges, pxycenters, pxyradii) - skeletons, sklranges = build_skeleton_list(qbx, srcindices, srcranges) + from pytential.linalg.proxy import ( # noqa + gather_block_neighbor_points, + gather_block_interaction_points) + nbrindices = gather_block_neighbor_points(qbx.density_discr, + srcindices, pxycenters, pxyradii) + nodes, ranges = gather_block_interaction_points(qbx, srcindices) - srcindices = srcindices.get() - srcranges = srcranges.get() - nbrindices = nbrindices.get() - nbrranges = nbrranges.get() + srcindices = srcindices.get(queue) + nbrindices = nbrindices.get(queue) - for i in range(srcranges.shape[0] - 1): - isrc = np.s_[srcranges[i]:srcranges[i + 1]] - inbr = np.s_[nbrranges[i]:nbrranges[i + 1]] + for i in range(srcindices.nblocks): + isrc = srcindices.block_indices(i) + inbr = nbrindices.block_indices(i) - assert not np.any(np.isin(nbrindices[inbr], srcindices[isrc])) + assert not np.any(np.isin(inbr, isrc)) if visualize: if ndim == 2: import matplotlib.pyplot as pt density_nodes = qbx.density_discr.nodes().get(queue) - skeletons = skeletons.get(queue) - sklranges = sklranges.get(queue) + nodes = nodes.get(queue) + ranges = ranges.get(queue) - for i in range(srcranges.shape[0] - 1): - isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = nbrindices[nbrranges[i]:nbrranges[i + 1]] - iskl = np.s_[sklranges[i]:sklranges[i + 1]] + for i in range(srcindices.nblocks): + isrc = srcindices.block_indices(i) + inbr = nbrindices.block_indices(i) + iall = np.s_[ranges[i]:ranges[i + 1]] pt.figure(figsize=(10, 8)) pt.plot(density_nodes[0], density_nodes[1], 'ko', ms=2.0, alpha=0.5) - pt.plot(density_nodes[0, srcindices], density_nodes[1, srcindices], + pt.plot(density_nodes[0, srcindices.indices], + density_nodes[1, srcindices.indices], 'o', ms=2.0) pt.plot(density_nodes[0, isrc], density_nodes[1, isrc], 'o', ms=2.0) - pt.plot(density_nodes[0, ingb], density_nodes[1, ingb], + pt.plot(density_nodes[0, inbr], density_nodes[1, inbr], 'o', ms=2.0) - pt.plot(skeletons[0, iskl], skeletons[1, iskl], + pt.plot(nodes[0, iall], nodes[1, iall], 'x', ms=2.0) pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) @@ -397,16 +397,16 @@ def test_area_query(ctx_factory, ndim, factor, visualize=False): from meshmode.discretization.visualization import make_visualizer marker = np.empty(qbx.density_discr.nnodes) - for i in range(srcranges.shape[0] - 1): - isrc = srcindices[np.s_[srcranges[i]:srcranges[i + 1]]] - ingb = nbrindices[nbrranges[i]:nbrranges[i + 1]] + for i in range(srcindices.nblocks): + isrc = srcindices.block_indices(i) + inbr = nbrindices.block_indices(i) # TODO: some way to turn off some of the interpolations # would help visualize this better. marker.fill(0.0) marker[srcindices] = 0.0 marker[isrc] = -42.0 - marker[ingb] = +42.0 + marker[inbr] = +42.0 marker_dev = cl.array.to_device(queue, marker) vis = make_visualizer(queue, qbx.density_discr, 10) -- GitLab From 64836e16fddcf0153651b9c97908288fd83d0469 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 27 Jun 2018 21:19:47 -0500 Subject: [PATCH 173/268] tests: fix visualization errors --- test/test_linalg_proxy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index a9d628f4..fc2b0653 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -190,7 +190,7 @@ def _plot_partition_indices(queue, discr, indices, **kwargs): @pytest.mark.parametrize("method", ["nodes", "elements"]) @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -201,7 +201,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): +def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -245,7 +245,7 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): +def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -334,7 +334,7 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_interaction_points(ctx_factory, ndim, factor, visualize=False): +def test_interaction_points(ctx_factory, ndim, factor, visualize=True): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -404,7 +404,7 @@ def test_interaction_points(ctx_factory, ndim, factor, visualize=False): # TODO: some way to turn off some of the interpolations # would help visualize this better. marker.fill(0.0) - marker[srcindices] = 0.0 + marker[srcindices.indices] = 0.0 marker[isrc] = -42.0 marker[inbr] = +42.0 marker_dev = cl.array.to_device(queue, marker) -- GitLab From ea10f0756803a470ba53b6ce01b2530fbb99b88d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 27 Jun 2018 21:25:06 -0500 Subject: [PATCH 174/268] flake8 fixes --- pytential/linalg/proxy.py | 4 ++-- test/test_linalg_proxy.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 6a1d4cfe..bf2fbca5 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -316,7 +316,7 @@ class ProxyGenerator(object): .. automethod:: __call__ """ - def __init__(self, source, nproxy=None, ratio=None, **kwargs): + def __init__(self, source, nproxy=None, ratio=None): self.source = source self.ambient_dim = source.density_discr.ambient_dim self.ratio = 1.1 if ratio is None else ratio @@ -461,7 +461,7 @@ class ProxyGenerator(object): def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, - max_nodes_in_box=None, **kwargs): + max_nodes_in_box=None): """Generate a set of neighboring points for each range of points in :attr:`discr`. Neighboring points of a range :math:`i` are defined as all the points inside the proxy ball :math:`i` that do not also diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index fc2b0653..05fb8949 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -195,8 +195,10 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): queue = cl.CommandQueue(ctx) qbx = _build_qbx_discr(queue, ndim=ndim) - srcindices = _build_block_index(qbx.density_discr, - method=method, use_tree=use_tree, factor=0.6) + _build_block_index(qbx.density_discr, + method=method, + use_tree=use_tree, + factor=0.6) @pytest.mark.parametrize("use_tree", [True, False]) -- GitLab From 7a268fe3b5622faf9a2bcc43a342294f4805635a Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 27 Jun 2018 21:26:48 -0500 Subject: [PATCH 175/268] fix typo in requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9cd4ee99..a1a00691 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/fik2/sumpy@block-index-additions +git+https://gitlab.tiker.net/fikl2/sumpy@block-index-additions git+https://github.com/inducer/pyfmmlib -- GitLab From 07f11df14845e104345487a31a8d7ca2a26575bd Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 28 Jun 2018 08:14:01 -0500 Subject: [PATCH 176/268] test: turn off visualization by default --- pytential/linalg/proxy.py | 8 ++++---- test/test_linalg_proxy.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index bf2fbca5..32f1633c 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -68,7 +68,7 @@ def _element_node_range(group, ielement): def partition_by_nodes(discr, use_tree=True, max_nodes_in_box=None): - """Generate clusters / ranges of nodes. The partition is created at the + """Generate equally sized ranges of nodes. The partition is created at the lowest level of granularity, i.e. nodes. This results in balanced ranges of points, but will split elements across different ranges. @@ -125,7 +125,7 @@ def partition_by_nodes(discr, def partition_by_elements(discr, use_tree=True, max_elements_in_box=None): - """Generate clusters / ranges of points. The partition is created at the + """Generate equally sized ranges of points. The partition is created at the element level, so that all the nodes belonging to an element belong to the same range. This can result in slightly larger differences in size between the ranges, but can be very useful when the individual partitions @@ -210,8 +210,8 @@ def partition_from_coarse(resampler, from_indices): The new partition will have the same number of ranges as the old partition. The nodes inside each range in the new partition are all the nodes in - :attr:`resampler.to_discr` that belong to the same region as the nodes - in the same range from :attr:`resampler.from_discr`. + :attr:`resampler.to_discr` that were refined from elements in the same + range from :attr:`resampler.from_discr`. :arg resampler: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection`. diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index 05fb8949..ccf0c3e1 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -190,7 +190,7 @@ def _plot_partition_indices(queue, discr, indices, **kwargs): @pytest.mark.parametrize("method", ["nodes", "elements"]) @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): +def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -203,7 +203,7 @@ def test_partition_points(ctx_factory, method, use_tree, ndim, visualize=True): @pytest.mark.parametrize("use_tree", [True, False]) @pytest.mark.parametrize("ndim", [2, 3]) -def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): +def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -247,7 +247,7 @@ def test_partition_coarse(ctx_factory, use_tree, ndim, visualize=True): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): +def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -336,7 +336,7 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=True): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_interaction_points(ctx_factory, ndim, factor, visualize=True): +def test_interaction_points(ctx_factory, ndim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) -- GitLab From d2b9b029b14201d1ebe3af31a943406852f891a0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 13:58:37 -0500 Subject: [PATCH 177/268] 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 96e3bfb6f717fd92981502545b2026269d10d6c0 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 15:06:59 -0500 Subject: [PATCH 178/268] update requirements --- .test-conda-env-py3-requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index c5ef1e9b..fa6c0426 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/fikl2/sumpy@block-index-additions +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/meshmode diff --git a/requirements.txt b/requirements.txt index a1a00691..8925c34e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,5 +7,5 @@ git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/fikl2/sumpy@block-index-additions +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib -- GitLab From 9f34bd3d1e91d4bcc38be5fedcacf7dd9ee1f183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 29 Jun 2018 16:24:43 -0400 Subject: [PATCH 179/268] Upgrade sympy to 1.1.1 for CI, upgrade Conda-based CIs to Py3.6 --- .gitlab-ci.yml | 4 ++-- .test-conda-env-py3-macos.yml | 2 +- .test-conda-env-py3.yml | 2 +- requirements.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 750bf6f4..3220e44b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ Python 3.6 POCL Examples: except: - tags -Python 3.5 Conda: +Python 3.6 Conda: script: - export SUMPY_FORCE_SYMBOLIC_BACKEND=symengine - CONDA_ENVIRONMENT=.test-conda-env-py3.yml @@ -67,7 +67,7 @@ Python 2.7 POCL: except: - tags -Python 3.5 Conda Apple: +Python 3.6 Conda Apple: script: - export LC_ALL=en_US.UTF-8 - export LANG=en_US.UTF-8 diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml index 807cd696..9d36c780 100644 --- a/.test-conda-env-py3-macos.yml +++ b/.test-conda-env-py3-macos.yml @@ -9,7 +9,7 @@ dependencies: - pocl=1.0 - islpy - pyopencl -- python=3.5 +- python=3.6 - symengine=0.3.0 - python-symengine=0.3.0 - pyfmmlib diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index 37c60864..8d60a1f0 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -9,7 +9,7 @@ dependencies: - pocl=1.0 - islpy - pyopencl -- python=3.5 +- python=3.6 - symengine=0.3.0 - python-symengine=0.3.0 - pyfmmlib diff --git a/requirements.txt b/requirements.txt index 8925c34e..6d1e4cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ numpy git+git://github.com/inducer/pymbolic -sympy==1.0 +sympy==1.1.1 git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy -- GitLab From 796ff821a3f2796ee1ea87fb0ec318a4e2c06af0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 29 Jun 2018 15:28:19 -0500 Subject: [PATCH 180/268] 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 181/268] 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 182/268] 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 b802994c02ddc64f3c71d85d5fefae6744656c6f Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 19:07:31 -0500 Subject: [PATCH 183/268] direct-solver: change reference proxy ball generation --- pytential/linalg/proxy.py | 92 +++++++++++++++++++++++++++------------ test/test_linalg_proxy.py | 6 ++- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 32f1633c..36d3a9cd 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -277,10 +277,57 @@ def partition_from_coarse(resampler, from_indices): # {{{ proxy point generator +def _generate_unit_sphere(ambient_dim, approx_npoints): + """Generate uniform points on a unit sphere. + + :arg ambient_dim: dimension of the ambient space. + :arg approx_npoints: approximate number of points to generate. If the + ambient space is 3D, this will not generate the exact number of points. + :return: array of shape ``(ambient_dim, npoints)``, where ``npoints`` + will not generally be the same as `approx_npoints`. + """ + + if ambient_dim == 2: + t = np.linspace(0.0, 2.0 * np.pi, approx_npoints) + points = np.vstack([np.cos(t), np.sin(t)]) + elif ambient_dim == 3: + # https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf + a = 4.0 * np.pi / approx_npoints + m_theta = int(np.round(np.pi / np.sqrt(a))) + d_theta = np.pi / m_theta + d_phi = a / d_theta + + points = [] + for m in range(m_theta): + theta = np.pi * (m + 0.5) / m_theta + m_phi = int(np.round(2.0 * np.pi * np.sin(theta) / d_phi)) + + for n in range(m_phi): + phi = 2.0 * np.pi * n / m_phi + points.append(np.array([np.sin(theta) * np.cos(phi), + np.sin(theta) * np.sin(phi), + np.cos(theta)])) + + for i in range(ambient_dim): + for sign in [-1, 1]: + pole = np.zeros(ambient_dim) + pole[i] = sign + points.append(pole) + + points = np.array(points).T + else: + raise ValueError("ambient_dim > 3 not supported.") + + return points + + class ProxyGenerator(object): r""" - .. attribute:: nproxy .. attribute:: ambient_dim + .. attribute:: nproxy + + Approximate number of proxy points. In 2D, this is the exact + number of proxy points, but in 3D .. attribute:: source A :class:`pytential.qbx.QBXLayerPotentialSource`. @@ -306,12 +353,11 @@ class ProxyGenerator(object): r = \theta r_{qbx}. - .. attribute:: ref_mesh + .. attribute:: ref_points - Reference mesh. Can be used to construct a mesh for a proxy - ball :math:`i` by translating it to `center[i]` and scaling by - `radii[i]`, as obtained by :meth:`__call__` (see - :meth:`meshmode.mesh.processing.affine_map`). + Reference points on a unit ball. Can be used to construct the points + around a proxy ball :math:`i` by translating them to `center[i]` and + scaling by `radii[i]`, as obtained by :meth:`__call__`. .. automethod:: __call__ """ @@ -322,18 +368,8 @@ class ProxyGenerator(object): self.ratio = 1.1 if ratio is None else ratio self.nproxy = 32 if nproxy is None else nproxy - if self.ambient_dim == 2: - from meshmode.mesh.generation import ellipse, make_curve_mesh - - self.ref_mesh = make_curve_mesh(lambda t: ellipse(1.0, t), - np.linspace(0.0, 1.0, self.nproxy + 1), - self.nproxy) - elif self.ambient_dim == 3: - from meshmode.mesh.generation import generate_icosphere - - self.ref_mesh = generate_icosphere(1, self.nproxy) - else: - raise ValueError("unsupported ambient dimension") + self.ref_points = \ + _generate_unit_sphere(self.ambient_dim, self.nproxy) @memoize_method def get_kernel(self): @@ -424,6 +460,9 @@ class ProxyGenerator(object): `pxycenters[i]`. """ + def _affine_map(v, A, b): + return np.dot(A, v) + b + from pytential.qbx.utils import get_centers_on_side knl = self.get_kernel() @@ -437,17 +476,16 @@ class ProxyGenerator(object): centers = centers_dev.get() radii = radii_dev.get() - from meshmode.mesh.processing import affine_map proxies = np.empty(indices.nblocks, dtype=np.object) - for i in range(indices.nblocks): - mesh = affine_map(self.ref_mesh, - A=(radii[i] * np.eye(self.ambient_dim)), - b=centers[:, i].reshape(-1)) - proxies[i] = mesh.vertices - - pxyranges = cl.array.arange(queue, 0, - proxies.shape[0] * proxies[0].shape[-1] + 1, proxies[0].shape[-1], + proxies[i] = _affine_map(self.ref_points, + A=(radii[i] * np.eye(self.ambient_dim)), + b=centers[:, i].reshape(-1, 1)) + + pxyranges = cl.array.arange(queue, + 0, + proxies.shape[0] * proxies[0].shape[1] + 1, + proxies[0].shape[1], dtype=indices.ranges.dtype) proxies = make_obj_array([ cl.array.to_device(queue, np.hstack([p[idim] for p in proxies])) diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index ccf0c3e1..e8a063ca 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -318,8 +318,12 @@ def test_proxy_generator(ctx_factory, ndim, factor, visualize=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory + from meshmode.mesh.generation import generate_icosphere + ref_mesh = generate_icosphere(1, generator.nproxy) + + # NOTE: this does not plot the actual proxy points for i in range(srcindices.nblocks): - mesh = affine_map(generator.ref_mesh, + mesh = affine_map(ref_mesh, A=(pxyradii[i] * np.eye(ndim)), b=pxycenters[:, i].reshape(-1)) -- GitLab From 45744817acbe1b7ae4e82c23ec182877b384cb58 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 19:28:02 -0500 Subject: [PATCH 184/268] fix some doc issues --- pytential/linalg/proxy.py | 78 +++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 36d3a9cd..f7dc6e6d 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -45,13 +45,10 @@ Proxy Point Generation .. autoclass:: ProxyGenerator .. autofunction:: partition_by_nodes - .. autofunction:: partition_by_elements - .. autofunction:: partition_from_coarse .. autofunction:: gather_block_neighbor_points - .. autofunction:: gather_block_interaction_points """ @@ -73,9 +70,9 @@ def partition_by_nodes(discr, of points, but will split elements across different ranges. :arg discr: a :class:`meshmode.discretization.Discretization`. - :arg use_tree: if `True`, node partitions are generated using a + :arg use_tree: if ``True``, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close - points to belong to the same partition. If `False`, a simple linear + points to belong to the same partition. If ``False``, a simple linear partition is constructed. :arg max_nodes_in_box: passed to :class:`boxtree.TreeBuilder`. @@ -132,9 +129,9 @@ def partition_by_elements(discr, need to be resampled, integrated, etc. :arg discr: a :class:`meshmode.discretization.Discretization`. - :arg use_tree: if True, node partitions are generated using a + :arg use_tree: if ``True``, node partitions are generated using a :class:`boxtree.TreeBuilder`, which leads to geometrically close - points to belong to the same partition. If False, a simple linear + points to belong to the same partition. If ``False``, a simple linear partition is constructed. :arg max_elements_in_box: passed to :class:`boxtree.TreeBuilder`. @@ -204,20 +201,20 @@ def partition_by_elements(discr, def partition_from_coarse(resampler, from_indices): """Generate a partition of nodes from an existing partition on a coarser discretization. The new partition is generated based on element - refinement relationships in :attr:`resampler`, so the existing partition - needs to be created using :func:`partition_by_element`, since we assume - that each range contains all the nodes in an element. + refinement relationships in *resampler*, so the existing partition + needs to be created using :func:`partition_by_elements`, + since we assume that each range contains all the nodes in an element. The new partition will have the same number of ranges as the old partition. The nodes inside each range in the new partition are all the nodes in - :attr:`resampler.to_discr` that were refined from elements in the same - range from :attr:`resampler.from_discr`. + *resampler.to_discr* that were refined from elements in the same + range from *resampler.from_discr*. :arg resampler: a :class:`meshmode.discretization.connection.DirectDiscretizationConnection`. :arg from_indices: a :class:`sumpy.tools.BlockIndexRanges`. - :return: a tuple :class:`sumpy.tools.BlockIndexRanges`. + :return: a :class:`sumpy.tools.BlockIndexRanges`. """ if not hasattr(resampler, "groups"): @@ -284,7 +281,7 @@ def _generate_unit_sphere(ambient_dim, approx_npoints): :arg approx_npoints: approximate number of points to generate. If the ambient space is 3D, this will not generate the exact number of points. :return: array of shape ``(ambient_dim, npoints)``, where ``npoints`` - will not generally be the same as `approx_npoints`. + will not generally be the same as ``approx_npoints``. """ if ambient_dim == 2: @@ -326,8 +323,8 @@ class ProxyGenerator(object): .. attribute:: ambient_dim .. attribute:: nproxy - Approximate number of proxy points. In 2D, this is the exact - number of proxy points, but in 3D + Number of proxy points. + .. attribute:: source A :class:`pytential.qbx.QBXLayerPotentialSource`. @@ -356,20 +353,21 @@ class ProxyGenerator(object): .. attribute:: ref_points Reference points on a unit ball. Can be used to construct the points - around a proxy ball :math:`i` by translating them to `center[i]` and - scaling by `radii[i]`, as obtained by :meth:`__call__`. + around a proxy ball :math:`i` by translating them to ``center[i]`` and + scaling by ``radii[i]``, as obtained by :meth:`__call__`. .. automethod:: __call__ """ - def __init__(self, source, nproxy=None, ratio=None): + def __init__(self, source, approx_nproxy=None, ratio=None): self.source = source self.ambient_dim = source.density_discr.ambient_dim self.ratio = 1.1 if ratio is None else ratio - self.nproxy = 32 if nproxy is None else nproxy + approx_nproxy = 32 if approx_nproxy is None else approx_nproxy self.ref_points = \ - _generate_unit_sphere(self.ambient_dim, self.nproxy) + _generate_unit_sphere(self.ambient_dim, approx_nproxy) + self.nproxy = self.ref_points.shape[1] @memoize_method def get_kernel(self): @@ -426,7 +424,7 @@ class ProxyGenerator(object): lp.ValueArg("nsources", np.int), "..." ], - name="proxy_generator_knl", + name="find_proxy_radii_knl", assumptions="dim>=1 and nranges>=1", fixed_parameters=dict(dim=self.ambient_dim), lang_version=MOST_RECENT_LANGUAGE_VERSION) @@ -449,15 +447,15 @@ class ProxyGenerator(object): :arg queue: a :class:`pyopencl.CommandQueue`. :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. - :return: a tuple of `(proxies, pxyranges, pxycenters, pxyranges)`, where - each element is a :class:`pyopencl.array.Array`. The - sizes of the arrays are as follows: `pxycenters` is of size - `(2, nranges)`, `pxyradii` is of size `(nranges,)`, `pxyranges` is - of size `(nranges + 1,)` and `proxies` is of size - `(2, nranges * nproxy)`. The proxy points in a range :math:`i` - can be obtained by a slice `proxies[pxyranges[i]:pxyranges[i + 1]]` - and are all at a distance `pxyradii[i]` from the range center - `pxycenters[i]`. + :return: a tuple of ``(proxies, pxyranges, pxycenters, pxyranges)``, + where each element is a :class:`pyopencl.array.Array`. The + sizes of the arrays are as follows: ``pxycenters`` is of size + ``(2, nranges)``, ``pxyradii`` is of size ``(nranges,)``, + ``pxyranges`` is of size ``(nranges + 1,)`` and ``proxies`` is + of size ``(2, nranges * nproxy)``. The proxy points in a range + :math:`i` can be obtained by a slice + ``proxies[pxyranges[i]:pxyranges[i + 1]]`` and are all at a + distance ``pxyradii[i]`` from the range center ``pxycenters[i]``. """ def _affine_map(v, A, b): @@ -501,7 +499,7 @@ class ProxyGenerator(object): def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, max_nodes_in_box=None): """Generate a set of neighboring points for each range of points in - :attr:`discr`. Neighboring points of a range :math:`i` are defined + *discr*. Neighboring points of a range :math:`i` are defined as all the points inside the proxy ball :math:`i` that do not also belong to the range itself. @@ -510,7 +508,7 @@ def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, :arg pxycenters: an array containing the center of each proxy ball. :arg pxyradii: an array containing the radius of each proxy ball. - :return: a tuple :class:`sumpy.tools.BlockIndexRanges`. + :return: a :class:`sumpy.tools.BlockIndexRanges`. """ if max_nodes_in_box is None: @@ -588,10 +586,10 @@ def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, def gather_block_interaction_points(source, indices, ratio=None, - nproxy=None, + approx_nproxy=None, max_nodes_in_box=None): """Generate sets of interaction points for each given range of indices - in the :attr:`source` discretization. For each input range of indices, + in the *source* discretization. For each input range of indices, the corresponding output range of points is consists of: - a set of proxy points (or balls) around the range, which @@ -600,14 +598,14 @@ def gather_block_interaction_points(source, indices, - a set of neighboring points that are inside the proxy balls, but do not belong to the given range, which model nearby interactions. - These are constructed with :meth:`gather_block_neighbor_points`. + These are constructed with :func:`gather_block_neighbor_points`. :arg source: a :class:`pytential.qbx.QBXLayerPotentialSource`. :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. - :return: a tuple `(nodes, ranges)`, where each value is a + :return: a tuple ``(nodes, ranges)``, where each value is a :class:`pyopencl.array.Array`. For a range :math:`i`, we can - get the slice using `nodes[ranges[i]:ranges[i + 1]]`. + get the slice using ``nodes[ranges[i]:ranges[i + 1]]``. """ @memoize @@ -664,7 +662,9 @@ def gather_block_interaction_points(source, indices, return loopy_knl with cl.CommandQueue(source.cl_context) as queue: - generator = ProxyGenerator(source, ratio=ratio, nproxy=nproxy) + generator = ProxyGenerator(source, + ratio=ratio, + approx_nproxy=approx_nproxy) proxies, pxyranges, pxycenters, pxyradii = generator(queue, indices) neighbors = gather_block_neighbor_points(source.density_discr, -- GitLab From c2b38a854fd3d1090d23de24f60e668f1e711a3d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 19:31:46 -0500 Subject: [PATCH 185/268] make nproxy a property instead --- pytential/linalg/proxy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index f7dc6e6d..d5b869aa 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -367,7 +367,10 @@ class ProxyGenerator(object): approx_nproxy = 32 if approx_nproxy is None else approx_nproxy self.ref_points = \ _generate_unit_sphere(self.ambient_dim, approx_nproxy) - self.nproxy = self.ref_points.shape[1] + + @property + def nproxy(self): + return self.ref_points.shape[1] @memoize_method def get_kernel(self): -- GitLab From 31502b931bb2981e3e316cd6ac04b1d147e3396b Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 19:34:06 -0500 Subject: [PATCH 186/268] clearer docs --- pytential/linalg/proxy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index d5b869aa..55ddcf61 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -323,7 +323,7 @@ class ProxyGenerator(object): .. attribute:: ambient_dim .. attribute:: nproxy - Number of proxy points. + Number of proxy points in a single proxy ball. .. attribute:: source @@ -331,7 +331,7 @@ class ProxyGenerator(object): .. attribute:: ratio - A ratio used to compute the proxy point radius. The radius + A ratio used to compute the proxy ball radius. The radius is computed in the :math:`\ell^2` norm, resulting in a circle or sphere of proxy points. For QBX, we have two radii of interest for a set of points: the radius :math:`r_{block}` of the @@ -353,7 +353,7 @@ class ProxyGenerator(object): .. attribute:: ref_points Reference points on a unit ball. Can be used to construct the points - around a proxy ball :math:`i` by translating them to ``center[i]`` and + of a proxy ball :math:`i` by translating them to ``center[i]`` and scaling by ``radii[i]``, as obtained by :meth:`__call__`. .. automethod:: __call__ -- GitLab From 288480615365e49906164fd70e59c2fe87728d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Sat, 30 Jun 2018 01:00:21 -0400 Subject: [PATCH 187/268] Add ack for Matt's sphere sampler --- pytential/linalg/proxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index 55ddcf61..abb8e8de 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -289,6 +289,8 @@ def _generate_unit_sphere(ambient_dim, approx_npoints): points = np.vstack([np.cos(t), np.sin(t)]) elif ambient_dim == 3: # https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf + # code by Matt Wala from + # https://github.com/mattwala/gigaqbx-accuracy-experiments/blob/d56ed063ffd7843186f4fe05d2a5b5bfe6ef420c/translation_accuracy.py#L23 a = 4.0 * np.pi / approx_npoints m_theta = int(np.round(np.pi / np.sqrt(a))) d_theta = np.pi / m_theta -- GitLab From 869d7d160edc4c203e49f2c8f5fdf36a1eb1dbe9 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Jul 2018 12:18:23 -0500 Subject: [PATCH 188/268] Update Helmholtz betterplane test to match QBX3D paper --- test/test_scalar_int_eq.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 5bd8385d..22d99cab 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -303,8 +303,8 @@ class ElliptiplaneIntEqTestCase(IntEqTestCase): class BetterplaneIntEqTestCase(IntEqTestCase): name = "betterplane" - default_helmholtz_k = 10 - resolutions = [0.3] + default_helmholtz_k = 20 + resolutions = [0.2] # refine_on_helmholtz_k = False fmm_backend = "fmmlib" @@ -325,7 +325,7 @@ class BetterplaneIntEqTestCase(IntEqTestCase): # kidding? gmres_tol = 1e-5 - vis_grid_spacing = (0.04, 0.2, 0.04) + vis_grid_spacing = (0.025, 0.2, 0.025) vis_extend_factor = 0.2 def get_mesh(self, resolution, target_order): @@ -471,6 +471,8 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): % qbx.density_discr.mesh.nelements) print("%d stage-2 elements after refinement" % qbx.stage2_density_discr.mesh.nelements) + print("quad stage-2 elements have %d nodes" + % qbx.quad_stage2_density_discr.groups[0].nunit_nodes) density_discr = qbx.density_discr -- GitLab From 49ac891c0ce5170f38c190c6790aae6497820462 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 2 Jul 2018 12:50:28 -0500 Subject: [PATCH 189/268] 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 190/268] 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 191/268] 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 c7327a9d4e2b4ab0367439009724efef4e8558d4 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 6 Jul 2018 18:18:11 -0500 Subject: [PATCH 192/268] Adapt FMM interface for timing data collection changes --- pytential/qbx/fmm.py | 76 ++++++++++++++++++++++++++++++++--------- pytential/qbx/fmmlib.py | 5 +++ requirements.txt | 4 +-- setup.py | 2 +- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 4be9e5ca..4d131d7f 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -33,6 +33,7 @@ from sumpy.fmm import (SumpyExpansionWranglerCodeContainer, from pytools import memoize_method from pytential.qbx.interactions import P2QBXLFromCSR, M2QBXL, L2QBXL, QBXL2P +from boxtree.fmm import TimingRecorder from pytools import log_process, ProcessLogger import logging @@ -195,7 +196,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), # {{{ qbx-related @log_process(logger) - def form_global_qbx_locals(self, src_weights): + def form_global_qbx_locals(self, src_weights, timing_data=None): local_exps = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -229,10 +230,13 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), assert local_exps is result result.add_event(evt) + self.update_timing_data("form_global_qbx_locals", timing_data, [evt]) + return result @log_process(logger) - def translate_box_multipoles_to_qbx_local(self, multipole_exps): + def translate_box_multipoles_to_qbx_local(self, multipole_exps, + timing_data=None): qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -241,6 +245,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), traversal = geo_data.traversal() + events = [] + wait_for = multipole_exps.events for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): @@ -273,15 +279,21 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **self.kernel_extra_kwargs) + events.append(evt) + wait_for = [evt] assert qbx_expansions_res is qbx_expansions qbx_expansions.add_event(evt) + self.update_timing_data( + "translate_box_multipoles_to_qbx_local", + timing_data, events) + return qbx_expansions @log_process(logger) - def translate_box_local_to_qbx_local(self, local_exps): + def translate_box_local_to_qbx_local(self, local_exps, timing_data=None): qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -290,6 +302,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), trav = geo_data.traversal() wait_for = local_exps.events + events = [] for isrc_level in range(geo_data.tree().nlevels): l2qbxl = self.code.l2qbxl( @@ -318,15 +331,21 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **self.kernel_extra_kwargs) + events.append(evt) + wait_for = [evt] assert qbx_expansions_res is qbx_expansions qbx_expansions.add_event(evt) + self.update_timing_data( + "translate_box_local_to_qbx_local", + timing_data, events) + return qbx_expansions @log_process(logger) - def eval_qbx_expansions(self, qbx_expansions): + def eval_qbx_expansions(self, qbx_expansions, timing_data=None): pot = self.full_output_zeros() geo_data = self.geo_data @@ -356,6 +375,8 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), for pot_i, pot_res_i in zip(pot, pot_res): assert pot_i is pot_res_i + self.update_timing_data("eval_qbx_expansions", timing_data, [evt]) + return pot # }}} @@ -365,7 +386,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), # {{{ FMM top-level -def drive_fmm(expansion_wrangler, src_weights): +def drive_fmm(expansion_wrangler, src_weights, timing_data=None): """Top-level driver routine for the QBX fast multipole calculation. :arg geo_data: A :class:`QBXFMMGeometryData` instance. @@ -373,6 +394,8 @@ def drive_fmm(expansion_wrangler, src_weights): :class:`ExpansionWranglerInterface`. :arg src_weights: Source 'density/weights/charges'. Passed unmodified to *expansion_wrangler*. + :arg timing_data: Either *None* or a dictionary that collects + timing data. Returns the potentials computed by *expansion_wrangler*. @@ -383,6 +406,7 @@ def drive_fmm(expansion_wrangler, src_weights): geo_data = wrangler.geo_data traversal = geo_data.traversal() tree = traversal.tree + recorder = TimingRecorder() # Interface guidelines: Attributes of the tree are assumed to be known # to the expansion wrangler and should not be passed. @@ -396,7 +420,8 @@ def drive_fmm(expansion_wrangler, src_weights): mpole_exps = wrangler.form_multipoles( traversal.level_start_source_box_nrs, traversal.source_boxes, - src_weights) + src_weights, + timing_data=recorder.next()) # }}} @@ -405,7 +430,8 @@ def drive_fmm(expansion_wrangler, src_weights): wrangler.coarsen_multipoles( traversal.level_start_source_parent_box_nrs, traversal.source_parent_boxes, - mpole_exps) + mpole_exps, + timing_data=recorder.next()) # }}} @@ -415,7 +441,8 @@ def drive_fmm(expansion_wrangler, src_weights): traversal.target_boxes, traversal.neighbor_source_boxes_starts, traversal.neighbor_source_boxes_lists, - src_weights) + src_weights, + timing_data=recorder.next()) # }}} @@ -426,7 +453,8 @@ def drive_fmm(expansion_wrangler, src_weights): traversal.target_or_target_parent_boxes, traversal.from_sep_siblings_starts, traversal.from_sep_siblings_lists, - mpole_exps) + mpole_exps, + timing_data=recorder.next()) # }}} @@ -438,7 +466,8 @@ def drive_fmm(expansion_wrangler, src_weights): non_qbx_potentials = non_qbx_potentials + wrangler.eval_multipoles( traversal.target_boxes_sep_smaller_by_source_level, traversal.from_sep_smaller_by_level, - mpole_exps) + mpole_exps, + timing_data=recorder.next()) # assert that list 3 close has been merged into list 1 assert traversal.from_sep_close_smaller_starts is None @@ -452,7 +481,8 @@ def drive_fmm(expansion_wrangler, src_weights): traversal.target_or_target_parent_boxes, traversal.from_sep_bigger_starts, traversal.from_sep_bigger_lists, - src_weights) + src_weights, + timing_data=recorder.next()) # assert that list 4 close has been merged into list 1 assert traversal.from_sep_close_bigger_starts is None @@ -464,7 +494,8 @@ def drive_fmm(expansion_wrangler, src_weights): wrangler.refine_locals( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, - local_exps) + local_exps, + timing_data=recorder.next()) # }}} @@ -473,22 +504,30 @@ def drive_fmm(expansion_wrangler, src_weights): non_qbx_potentials = non_qbx_potentials + wrangler.eval_locals( traversal.level_start_target_box_nrs, traversal.target_boxes, - local_exps) + local_exps, + timing_data=recorder.next()) # }}} # {{{ wrangle qbx expansions - qbx_expansions = wrangler.form_global_qbx_locals(src_weights) + qbx_expansions = wrangler.form_global_qbx_locals( + src_weights, + timing_data=recorder.next()) qbx_expansions = qbx_expansions + \ - wrangler.translate_box_multipoles_to_qbx_local(mpole_exps) + wrangler.translate_box_multipoles_to_qbx_local( + mpole_exps, + timing_data=recorder.next()) qbx_expansions = qbx_expansions + \ - wrangler.translate_box_local_to_qbx_local(local_exps) + wrangler.translate_box_local_to_qbx_local( + local_exps, + timing_data=recorder.next()) qbx_potentials = wrangler.eval_qbx_expansions( - qbx_expansions) + qbx_expansions, + timing_data=recorder.next()) # }}} @@ -516,6 +555,9 @@ def drive_fmm(expansion_wrangler, src_weights): fmm_proc.done() + if timing_data is not None: + timing_data.update(recorder.summarize()) + return result # }}} diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 2b6cbf88..ec7d2b8a 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -30,6 +30,7 @@ from boxtree.pyfmmlib_integration import FMMLibExpansionWrangler from sumpy.kernel import LaplaceKernel, HelmholtzKernel +from boxtree.tools import record_time from pytools import log_process import logging @@ -301,6 +302,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ p2qbxl @log_process(logger) + @record_time("timing_data") def form_global_qbx_locals(self, src_weights): geo_data = self.geo_data trav = geo_data.traversal() @@ -352,6 +354,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ m2qbxl @log_process(logger) + @record_time("timing_data") def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_exps = self.qbx_local_expansion_zeros() @@ -463,6 +466,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # }}} @log_process(logger) + @record_time("timing_data") def translate_box_local_to_qbx_local(self, local_exps): qbx_expansions = self.qbx_local_expansion_zeros() @@ -524,6 +528,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): return qbx_expansions @log_process(logger) + @record_time("timing_data") def eval_qbx_expansions(self, qbx_expansions): output = self.full_output_zeros() diff --git a/requirements.txt b/requirements.txt index 6d1e4cce..192d0abf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree +git+https://gitlab.tiker.net/inducer/boxtree@timing-data git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/inducer/sumpy@timing-data git+https://github.com/inducer/pyfmmlib diff --git a/setup.py b/setup.py index 84438494..f9ce7403 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,7 @@ setup(name="pytential", "pytools>=2018.2", "modepy>=2013.3", "pyopencl>=2013.1", - "boxtree>=2013.1", + "boxtree>=2018.1", "pymbolic>=2013.2", "loo.py>=2017.2", "sumpy>=2013.1", -- GitLab From ec416c21186cc0835e47dadacdbeb7004698e05c Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 6 Jul 2018 18:21:14 -0500 Subject: [PATCH 193/268] Fix conda requirements.txt --- .test-conda-env-py3-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index fa6c0426..cb126a56 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ -git+https://gitlab.tiker.net/inducer/boxtree +git+https://gitlab.tiker.net/inducer/boxtree@timing-data git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy +git+https://gitlab.tiker.net/inducer/sumpy@timing-data git+https://github.com/inducer/meshmode -- GitLab From eb005a3b4df108765ebf47cde7f8f95aecf84853 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 10 Jul 2018 01:03:10 -0500 Subject: [PATCH 194/268] WIP --- pytential/qbx/fmm.py | 127 +++++++++++++++++++++------------------- pytential/qbx/fmmlib.py | 10 ++-- 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 4d131d7f..0f4c5f31 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -28,7 +28,7 @@ import numpy as np # noqa import pyopencl as cl # noqa import pyopencl.array # noqa from sumpy.fmm import (SumpyExpansionWranglerCodeContainer, - SumpyExpansionWrangler, level_to_rscale) + SumpyExpansionWrangler, level_to_rscale, SumpyTimingFuture) from pytools import memoize_method from pytential.qbx.interactions import P2QBXLFromCSR, M2QBXL, L2QBXL, QBXL2P @@ -196,7 +196,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), # {{{ qbx-related @log_process(logger) - def form_global_qbx_locals(self, src_weights, timing_data=None): + def form_global_qbx_locals(self, src_weights): local_exps = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -230,13 +230,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), assert local_exps is result result.add_event(evt) - self.update_timing_data("form_global_qbx_locals", timing_data, [evt]) - - return result + return (result, SumpyTimingFuture(self.queue, [evt])) @log_process(logger) - def translate_box_multipoles_to_qbx_local(self, multipole_exps, - timing_data=None): + def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -286,14 +283,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_expansions.add_event(evt) - self.update_timing_data( - "translate_box_multipoles_to_qbx_local", - timing_data, events) - - return qbx_expansions + return (qbx_expansions, SumpyTimingFuture(self.queue, events)) @log_process(logger) - def translate_box_local_to_qbx_local(self, local_exps, timing_data=None): + def translate_box_local_to_qbx_local(self, local_exps): qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data @@ -338,14 +331,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_expansions.add_event(evt) - self.update_timing_data( - "translate_box_local_to_qbx_local", - timing_data, events) - - return qbx_expansions + return (qbx_expansions, SumpyTimingFuture(self.queue, events)) @log_process(logger) - def eval_qbx_expansions(self, qbx_expansions, timing_data=None): + def eval_qbx_expansions(self, qbx_expansions): pot = self.full_output_zeros() geo_data = self.geo_data @@ -375,9 +364,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), for pot_i, pot_res_i in zip(pot, pot_res): assert pot_i is pot_res_i - self.update_timing_data("eval_qbx_expansions", timing_data, [evt]) - - return pot + return (pot, SumpyTimingFuture(self.queue, [evt])) # }}} @@ -417,44 +404,48 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): # {{{ construct local multipoles - mpole_exps = wrangler.form_multipoles( + mpole_exps, timing_future = wrangler.form_multipoles( traversal.level_start_source_box_nrs, traversal.source_boxes, - src_weights, - timing_data=recorder.next()) + src_weights) + + recorder.add("form_multipoles", timing_future) # }}} # {{{ propagate multipoles upward - wrangler.coarsen_multipoles( + mpole_exps, timing_future = wrangler.coarsen_multipoles( traversal.level_start_source_parent_box_nrs, traversal.source_parent_boxes, - mpole_exps, - timing_data=recorder.next()) + mpole_exps) + + recorder.add("coarsen_multipoles", timing_future) # }}} # {{{ direct evaluation from neighbor source boxes ("list 1") - non_qbx_potentials = wrangler.eval_direct( + non_qbx_potentials, timing_future = wrangler.eval_direct( traversal.target_boxes, traversal.neighbor_source_boxes_starts, traversal.neighbor_source_boxes_lists, - src_weights, - timing_data=recorder.next()) + src_weights) + + recorder.add("eval_direct", timing_future) # }}} # {{{ translate separated siblings' ("list 2") mpoles to local - local_exps = wrangler.multipole_to_local( + local_exps, timing_future = wrangler.multipole_to_local( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, traversal.from_sep_siblings_starts, traversal.from_sep_siblings_lists, - mpole_exps, - timing_data=recorder.next()) + mpole_exps) + + recorder.add("multipole_to_local", timing_future) # }}} @@ -463,11 +454,14 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): # (the point of aiming this stage at particles is specifically to keep its # contribution *out* of the downward-propagating local expansions) - non_qbx_potentials = non_qbx_potentials + wrangler.eval_multipoles( + mpole_result, timing_future = wrangler.eval_multipoles( traversal.target_boxes_sep_smaller_by_source_level, traversal.from_sep_smaller_by_level, - mpole_exps, - timing_data=recorder.next()) + mpole_exps) + + recorder.add("eval_multipoles", timing_future) + + non_qbx_potentials = non_qbx_potentials + mpole_result # assert that list 3 close has been merged into list 1 assert traversal.from_sep_close_smaller_starts is None @@ -476,13 +470,16 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): # {{{ form locals for separated bigger source boxes ("list 4") - local_exps = local_exps + wrangler.form_locals( + local_result, timing_future = wrangler.form_locals( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, traversal.from_sep_bigger_starts, traversal.from_sep_bigger_lists, - src_weights, - timing_data=recorder.next()) + src_weights) + + recorder.add("form_locals", timing_future) + + local_exps = local_exps + local_result # assert that list 4 close has been merged into list 1 assert traversal.from_sep_close_bigger_starts is None @@ -491,43 +488,53 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): # {{{ propagate local_exps downward - wrangler.refine_locals( + local_exps, timing_future = wrangler.refine_locals( traversal.level_start_target_or_target_parent_box_nrs, traversal.target_or_target_parent_boxes, - local_exps, - timing_data=recorder.next()) + local_exps) + + recorder.add("refine_locals", timing_future) # }}} # {{{ evaluate locals - non_qbx_potentials = non_qbx_potentials + wrangler.eval_locals( + local_result, timing_future = wrangler.eval_locals( traversal.level_start_target_box_nrs, traversal.target_boxes, - local_exps, - timing_data=recorder.next()) + local_exps) + + recorder.add("eval_locals", timing_future) + + non_qbx_potentials = non_qbx_potentials + local_result # }}} # {{{ wrangle qbx expansions - qbx_expansions = wrangler.form_global_qbx_locals( - src_weights, - timing_data=recorder.next()) + qbx_expansions, timing_future = wrangler.form_global_qbx_locals( + src_weights) + + recorder.add("form_global_qbx_locals", timing_future) + + local_result, timing_future = ( + wrangler.translate_box_multipoles_to_qbx_local(mpole_exps)) + + recorder.add("translate_box_multipoles_to_qbx_local", timing_future) + + qbx_expansions = qbx_expansions + local_result + + local_result, timing_future = ( + wrangler.translate_box_local_to_qbx_local(mpole_exps)) + + recorder.add("translate_box_local_to_qbx_local", timing_future) - qbx_expansions = qbx_expansions + \ - wrangler.translate_box_multipoles_to_qbx_local( - mpole_exps, - timing_data=recorder.next()) + qbx_expansions = qbx_expansions + local_result - qbx_expansions = qbx_expansions + \ - wrangler.translate_box_local_to_qbx_local( - local_exps, - timing_data=recorder.next()) + qbx_potentials, timing_future = wrangler.eval_qbx_expansions( + qbx_expansions) - qbx_potentials = wrangler.eval_qbx_expansions( - qbx_expansions, - timing_data=recorder.next()) + recorder.add("eval_qbx_expansions", timing_future) # }}} diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index ec7d2b8a..dfd03b25 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -30,7 +30,7 @@ from boxtree.pyfmmlib_integration import FMMLibExpansionWrangler from sumpy.kernel import LaplaceKernel, HelmholtzKernel -from boxtree.tools import record_time +from boxtree.tools import return_timing_data from pytools import log_process import logging @@ -302,7 +302,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ p2qbxl @log_process(logger) - @record_time("timing_data") + @return_timing_data def form_global_qbx_locals(self, src_weights): geo_data = self.geo_data trav = geo_data.traversal() @@ -354,7 +354,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # {{{ m2qbxl @log_process(logger) - @record_time("timing_data") + @return_timing_data def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_exps = self.qbx_local_expansion_zeros() @@ -466,7 +466,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): # }}} @log_process(logger) - @record_time("timing_data") + @return_timing_data def translate_box_local_to_qbx_local(self, local_exps): qbx_expansions = self.qbx_local_expansion_zeros() @@ -528,7 +528,7 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): return qbx_expansions @log_process(logger) - @record_time("timing_data") + @return_timing_data def eval_qbx_expansions(self, qbx_expansions): output = self.full_output_zeros() -- GitLab From 6d83ec980f39d488f4380bd3db7b7f385bfdd012 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 10 Jul 2018 09:09:19 -0500 Subject: [PATCH 195/268] Update pytential for changes to boxtree timing API --- pytential/qbx/fmm.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 0f4c5f31..994c8ff7 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -242,10 +242,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), traversal = geo_data.traversal() - events = [] - wait_for = multipole_exps.events + events = [] + for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): m2qbxl = self.code.m2qbxl( self.level_orders[isrc_level], @@ -295,6 +295,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), trav = geo_data.traversal() wait_for = local_exps.events + events = [] for isrc_level in range(geo_data.tree().nlevels): @@ -512,8 +513,7 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): # {{{ wrangle qbx expansions - qbx_expansions, timing_future = wrangler.form_global_qbx_locals( - src_weights) + qbx_expansions, timing_future = wrangler.form_global_qbx_locals(src_weights) recorder.add("form_global_qbx_locals", timing_future) @@ -525,14 +525,13 @@ def drive_fmm(expansion_wrangler, src_weights, timing_data=None): qbx_expansions = qbx_expansions + local_result local_result, timing_future = ( - wrangler.translate_box_local_to_qbx_local(mpole_exps)) + wrangler.translate_box_local_to_qbx_local(local_exps)) recorder.add("translate_box_local_to_qbx_local", timing_future) qbx_expansions = qbx_expansions + local_result - qbx_potentials, timing_future = wrangler.eval_qbx_expansions( - qbx_expansions) + qbx_potentials, timing_future = wrangler.eval_qbx_expansions(qbx_expansions) recorder.add("eval_qbx_expansions", timing_future) -- GitLab From efb88010036aaa44e39946f01ab5e6842504f11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Wed, 11 Jul 2018 15:44:42 -0400 Subject: [PATCH 196/268] Point boxtree/sumpy in requirements.txt back at master --- .test-conda-env-py3-requirements.txt | 4 ++-- requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.test-conda-env-py3-requirements.txt b/.test-conda-env-py3-requirements.txt index cb126a56..fa6c0426 100644 --- a/.test-conda-env-py3-requirements.txt +++ b/.test-conda-env-py3-requirements.txt @@ -1,5 +1,5 @@ -git+https://gitlab.tiker.net/inducer/boxtree@timing-data +git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/pymbolic git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/sumpy@timing-data +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/meshmode diff --git a/requirements.txt b/requirements.txt index 192d0abf..6d1e4cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree@timing-data +git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode -git+https://gitlab.tiker.net/inducer/sumpy@timing-data +git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib -- GitLab From cc74b51060e5edb3663047ae43a7459fbaa3bea3 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 12 Jul 2018 17:37:00 -0500 Subject: [PATCH 197/268] Allow pyfmmlib tests to run on macOS (closes #102). --- .test-conda-env-py3-macos.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml index 9d36c780..987ee8fb 100644 --- a/.test-conda-env-py3-macos.yml +++ b/.test-conda-env-py3-macos.yml @@ -14,4 +14,6 @@ dependencies: - python-symengine=0.3.0 - pyfmmlib - osx-pocl-opencl +# for OpenMP support in pyfmmlib +- defaults::libgfortran # things not in here: loopy boxtree pymbolic meshmode sumpy -- GitLab From 553b7a9400be8d8b48548456596313a2ef810c5c Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 12 Jul 2018 17:55:28 -0500 Subject: [PATCH 198/268] Try again --- .test-conda-env-py3-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml index 987ee8fb..3664a455 100644 --- a/.test-conda-env-py3-macos.yml +++ b/.test-conda-env-py3-macos.yml @@ -15,5 +15,5 @@ dependencies: - pyfmmlib - osx-pocl-opencl # for OpenMP support in pyfmmlib -- defaults::libgfortran +- libgfortran>=3.0.1 # things not in here: loopy boxtree pymbolic meshmode sumpy -- GitLab From 6fd835976b4e4e6202fff0762f4fe4492d31fba6 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 14 Jul 2018 17:56:20 -0500 Subject: [PATCH 199/268] Remove pytest.importorskip('pyfmmlib') in tests --- test/test_layer_pot.py | 2 -- test/test_layer_pot_eigenvalues.py | 3 --- test/test_layer_pot_identity.py | 3 --- test/test_maxwell.py | 2 -- test/test_scalar_int_eq.py | 5 +---- 5 files changed, 1 insertion(+), 14 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 66445fee..b4f4a02f 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -392,8 +392,6 @@ def test_perf_data_gathering(ctx_getter, n_arms=5): def test_3d_jump_relations(ctx_factory, relation, visualize=False): # logging.basicConfig(level=logging.INFO) - pytest.importorskip("pyfmmlib") - cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index b8c9c9fd..cdda0a3c 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -258,9 +258,6 @@ def no_test_sphere_eigenvalues(ctx_getter, mode_m, mode_n, qbx_order, special = pytest.importorskip("scipy.special") - if fmm_backend == "fmmlib": - pytest.importorskip("pyfmmlib") - cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 942b6ed2..c376fdf3 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -240,9 +240,6 @@ class DynamicTestCase(object): and self.expr.zero_op_name == "green_grad"): pytest.skip("does not achieve sufficient precision") - if self.fmm_backend == "fmmlib": - pytest.importorskip("pyfmmlib") - # {{{ integral identity tester diff --git a/test/test_maxwell.py b/test/test_maxwell.py index 914312e9..fa9383f4 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -228,8 +228,6 @@ def test_pec_mfie_extinction(ctx_getter, case, visualize=False): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - pytest.importorskip("pyfmmlib") - np.random.seed(12) knl_kwargs = {"k": case.k} diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 22d99cab..5f8d3bc2 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -871,10 +871,7 @@ def test_integral_equation(ctx_getter, case, visualize=False): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - if case.fmm_backend == "fmmlib": - pytest.importorskip("pyfmmlib") - - if USE_SYMENGINE and case.fmm_backend is None: + if USE_SYMENGINE and case.fmm_backend == "sumpy": pytest.skip("https://gitlab.tiker.net/inducer/sumpy/issues/25") # prevent cache 'splosion -- GitLab From cfd16d49e1517c104442b39c12d2f9e7ff6af113 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 14 Jul 2018 22:20:21 -0500 Subject: [PATCH 200/268] Fix fmm_backend comparison --- test/test_scalar_int_eq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 5f8d3bc2..8830e84b 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -871,7 +871,7 @@ def test_integral_equation(ctx_getter, case, visualize=False): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - if USE_SYMENGINE and case.fmm_backend == "sumpy": + if USE_SYMENGINE and case.fmm_backend is None: pytest.skip("https://gitlab.tiker.net/inducer/sumpy/issues/25") # prevent cache 'splosion -- GitLab From 30db5d03415f6556bb90eaa610fc561c9487cfab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Thu, 19 Jul 2018 09:37:58 -0400 Subject: [PATCH 201/268] Add pyfmmlib as a hard dependency --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f9ce7403..0c13014d 100644 --- a/setup.py +++ b/setup.py @@ -105,6 +105,7 @@ setup(name="pytential", "loo.py>=2017.2", "sumpy>=2013.1", "cgen>=2013.1.2", + "pyfmmlib>=2018.1", "six", ]) -- GitLab From 076a92ab7137aa3bd3d50d64eaa4073b9e7a0009 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 20 Jul 2018 17:07:35 -0500 Subject: [PATCH 202/268] Fix __all__ to be a list of strings --- pytential/qbx/__init__.py | 4 ++-- pytential/unregularized.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index b3bb102f..869f9222 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -951,8 +951,8 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): __all__ = ( - QBXLayerPotentialSource, - QBXTargetAssociationFailedException, + "QBXLayerPotentialSource", + "QBXTargetAssociationFailedException", ) # vim: fdm=marker diff --git a/pytential/unregularized.py b/pytential/unregularized.py index a2fe4ad0..b0d9926a 100644 --- a/pytential/unregularized.py +++ b/pytential/unregularized.py @@ -447,7 +447,7 @@ class _FMMGeometryData(object): __all__ = ( - UnregularizedLayerPotentialSource, + "UnregularizedLayerPotentialSource", ) # vim: fdm=marker -- GitLab From 49fc643f76427e2d3f0671948713dc2430482a27 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 22 Jul 2018 15:47:32 -0500 Subject: [PATCH 203/268] Fix missing argument to normal() --- test/test_layer_pot_eigenvalues.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index cdda0a3c..b10a9076 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -126,7 +126,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, centers_h = [centers[0].get(), centers[1].get()] pt.plot(nodes_h[0], nodes_h[1], "x-") pt.plot(centers_h[0], centers_h[1], "o") - normal = bind(qbx, sym.normal())(queue).as_vector(np.object) + normal = bind(qbx, sym.normal(ambient_dim=2))(queue).as_vector(np.object) pt.quiver(nodes_h[0], nodes_h[1], normal[0].get(), normal[1].get()) pt.gca().set_aspect("equal") -- GitLab From f3d283606c6d85370110db1a9d6795454f5e8680 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 19 Jun 2018 14:33:26 -0500 Subject: [PATCH 204/268] matrix: add a p2p matrix builder --- pytential/symbolic/matrix.py | 124 ++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 24 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index f0b1d82f..c9d91859 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -34,23 +34,68 @@ import pytential.symbolic.primitives as sym from pytential.symbolic.execution import bind +# {{{ helpers + def is_zero(x): return isinstance(x, (int, float, complex, np.number)) and x == 0 +def _resample_arg(queue, source, x): + if not isinstance(x, np.ndarray): + return x + + if len(x.shape) >= 2: + raise RuntimeError("matrix variables in kernel arguments") + + def resample(y): + return source.resampler(queue, cl.array.to_device(queue, y)).get(queue) + + from pytools.obj_array import with_object_array_or_scalar + return with_object_array_or_scalar(resample, x) + + +def _get_layer_potential_args(mapper, expr, source): + kernel_args = {} + for arg_name, arg_expr in six.iteritems(expr.kernel_arguments): + rec_arg = mapper.rec(arg_expr) + kernel_args[arg_name] = _resample_arg(mapper.queue, source, rec_arg) + + return kernel_args + + +def _get_kernel_args(mapper, kernel, expr, source): + # NOTE: copied from pytential.symbolic.primitives.IntG + inner_kernel_args = kernel.get_args() + kernel.get_source_args() + inner_kernel_args = set(arg.loopy_arg.name for arg in inner_kernel_args) + + kernel_args = {} + for arg_name, arg_expr in six.iteritems(expr.kernel_arguments): + if arg_name not in inner_kernel_args: + continue + + rec_arg = mapper.rec(arg_expr) + kernel_args[arg_name] = _resample_arg(mapper.queue, source, rec_arg) + + return kernel_args + +# }}} + +# {{{ QBX layer potential matrix + # FIXME: PyOpenCL doesn't do all the required matrix math yet. # We'll cheat and build the matrix on the host. class MatrixBuilder(EvaluationMapperBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, context): + super(MatrixBuilder, self).__init__(context=context) + self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs self.dep_source = dep_source self.dep_discr = dep_source.density_discr self.places = places - self.context = context def map_variable(self, expr): if expr == self.dep_expr: @@ -152,27 +197,7 @@ class MatrixBuilder(EvaluationMapperBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - - kernel_args = {} - for arg_name, arg_expr in six.iteritems(expr.kernel_arguments): - rec_arg = self.rec(arg_expr) - - if isinstance(rec_arg, np.ndarray): - if len(rec_arg.shape) == 2: - raise RuntimeError("matrix variables in kernel arguments") - if len(rec_arg.shape) == 1: - from pytools.obj_array import with_object_array_or_scalar - - def resample(x): - return ( - source.resampler( - self.queue, - cl.array.to_device(self.queue, x)) - .get(queue=self.queue)) - - rec_arg = with_object_array_or_scalar(resample, rec_arg) - - kernel_args[arg_name] = rec_arg + kernel_args = _get_layer_potential_args(self, expr, source) from sumpy.expansion.local import LineTaylorLocalExpansion local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) @@ -181,8 +206,6 @@ class MatrixBuilder(EvaluationMapperBase): mat_gen = LayerPotentialMatrixGenerator( self.queue.context, (local_expn,)) - assert target_discr is source.density_discr - from pytential.qbx.utils import get_centers_on_side assert abs(expr.qbx_forced_limit) > 0 @@ -244,3 +267,56 @@ class MatrixBuilder(EvaluationMapperBase): result = result.get() return result + +# }}} + + +# {{{ p2p matrix + +class P2PMatrixBuilder(MatrixBuilder): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, + context, exclude_self=True): + super(P2PMatrixBuilder, self).__init__(queue, + dep_expr, other_dep_exprs, dep_source, places, context) + + self.exclude_self = exclude_self + + def map_int_g(self, expr): + source = self.places[expr.source] + target_discr = self.places[expr.target] + + if source.density_discr is not target_discr: + raise NotImplementedError() + + rec_density = self.rec(expr.density) + if is_zero(rec_density): + return 0 + + assert isinstance(rec_density, np.ndarray) + if len(rec_density.shape) != 2: + raise NotImplementedError("layer potentials on non-variables") + + try: + kernel = expr.kernel.inner_kernel + except AttributeError: + kernel = expr.kernel + kernel_args = _get_kernel_args(self, kernel, expr, source) + if self.exclude_self: + kernel_args["target_to_source"] = np.arange(0, target_discr.nnodes) + + from sumpy.p2p import P2PMatrixGenerator + mat_gen = P2PMatrixGenerator( + self.queue.context, (kernel,), exclude_self=self.exclude_self) + + _, (mat,) = mat_gen(self.queue, + targets=target_discr.nodes(), + sources=source.density_discr.nodes(), + **kernel_args) + + mat = mat.get() + mat = mat.dot(rec_density) + + return mat +# }}} + +# vim: foldmethod=marker -- GitLab From b6ee542d0e6fa652991be353583fee374032ca5f Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 19 Jun 2018 14:56:44 -0500 Subject: [PATCH 205/268] matrix: add block matrix builders --- pytential/symbolic/matrix.py | 253 +++++++++++++++++++++++++++++++++-- 1 file changed, 245 insertions(+), 8 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index c9d91859..5a62aa04 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -29,6 +29,8 @@ import pyopencl.array # noqa import six from six.moves import intern +from pytools import memoize_method + from pytential.symbolic.mappers import EvaluationMapperBase import pytential.symbolic.primitives as sym from pytential.symbolic.execution import bind @@ -41,6 +43,9 @@ def is_zero(x): def _resample_arg(queue, source, x): + if source is None: + return x + if not isinstance(x, np.ndarray): return x @@ -80,7 +85,8 @@ def _get_kernel_args(mapper, kernel, expr, source): # }}} -# {{{ QBX layer potential matrix + +# {{{ QBX layer potential matrix builder # FIXME: PyOpenCL doesn't do all the required matrix math yet. # We'll cheat and build the matrix on the host. @@ -207,15 +213,14 @@ class MatrixBuilder(EvaluationMapperBase): self.queue.context, (local_expn,)) from pytential.qbx.utils import get_centers_on_side - assert abs(expr.qbx_forced_limit) > 0 + _, (mat,) = mat_gen(self.queue, target_discr.nodes(), source.quad_stage2_density_discr.nodes(), get_centers_on_side(source, expr.qbx_forced_limit), expansion_radii=self.dep_source._expansion_radii("nsources"), **kernel_args) - mat = mat.get() waa = source.weights_and_area_elements().get(queue=self.queue) @@ -252,9 +257,7 @@ class MatrixBuilder(EvaluationMapperBase): arg, = expr.parameters rec_arg = self.rec(arg) - if ( - isinstance(rec_arg, np.ndarray) - and len(rec_arg.shape) == 2): + if isinstance(rec_arg, np.ndarray) and len(rec_arg.shape) == 2: raise RuntimeError("expression is nonlinear in variable") if isinstance(rec_arg, np.ndarray): @@ -271,7 +274,7 @@ class MatrixBuilder(EvaluationMapperBase): # }}} -# {{{ p2p matrix +# {{{ p2p matrix builder class P2PMatrixBuilder(MatrixBuilder): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, @@ -302,7 +305,8 @@ class P2PMatrixBuilder(MatrixBuilder): kernel = expr.kernel kernel_args = _get_kernel_args(self, kernel, expr, source) if self.exclude_self: - kernel_args["target_to_source"] = np.arange(0, target_discr.nnodes) + kernel_args["target_to_source"] = \ + cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) from sumpy.p2p import P2PMatrixGenerator mat_gen = P2PMatrixGenerator( @@ -319,4 +323,237 @@ class P2PMatrixBuilder(MatrixBuilder): return mat # }}} + +# {{{ block matrix builders + +class MatrixBlockBuilderBase(EvaluationMapperBase): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + places, context, index_set): + super(MatrixBlockBuilderBase, self).__init__(context=context) + + self.queue = queue + self.dep_expr = dep_expr + self.other_dep_exprs = other_dep_exprs + self.dep_source = dep_source + self.dep_discr = dep_source.density_discr + self.places = places + + self.index_set = index_set + + def _map_dep_variable(self): + return np.eye(self.index_set.srcindices.shape[0]) + + def map_variable(self, expr): + if expr == self.dep_expr: + return self._map_dep_variable() + elif expr in self.other_dep_exprs: + return 0 + else: + return super(MatrixBlockBuilderBase, self).map_variable(expr) + + def map_subscript(self, expr): + if expr == self.dep_expr: + return self.variable_identity() + elif expr in self.other_dep_exprs: + return 0 + else: + return super(MatrixBlockBuilderBase, self).map_subscript(expr) + + def map_num_reference_derivative(self, expr): + rec_operand = self.rec(expr.operand) + + assert isinstance(rec_operand, np.ndarray) + if len(rec_operand.shape) == 2: + raise NotImplementedError("derivatives") + + where_discr = self.places[expr.where] + op = sym.NumReferenceDerivative(expr.ref_axes, sym.var("u")) + return bind(where_discr, op)( + self.queue, u=cl.array.to_device(self.queue, rec_operand)).get() + + def map_node_coordinate_component(self, expr): + where_discr = self.places[expr.where] + op = sym.NodeCoordinateComponent(expr.ambient_axis) + return bind(where_discr, op)(self.queue).get() + + def map_call(self, expr): + arg, = expr.parameters + rec_arg = self.rec(arg) + + if isinstance(rec_arg, np.ndarray) and len(rec_arg.shape) == 2: + raise RuntimeError("expression is nonlinear in variable") + + if isinstance(rec_arg, np.ndarray): + rec_arg = cl.array.to_device(self.queue, rec_arg) + + op = expr.function(sym.var("u")) + result = bind(self.dep_source, op)(self.queue, u=rec_arg) + + if isinstance(result, cl.array.Array): + result = result.get() + + return result + + +class MatrixBlockBuilder(MatrixBlockBuilderBase): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + places, context, index_set): + super(MatrixBlockBuilder, self).__init__(queue, + dep_expr, other_dep_exprs, dep_source, places, context, index_set) + + self.dummy = MatrixBlockBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, places, context, index_set) + + @memoize_method + def _oversampled_index_set(self, resampler): + from pytential.direct_solver import partition_from_coarse + srcindices, srcranges = partition_from_coarse(self.queue, resampler, + self.index_set.srcindices, self.index_set.srcranges) + + from sumpy.tools import MatrixBlockIndex + ovsm_index_set = MatrixBlockIndex(self.queue, + self.index_set.tgtindices, srcindices, + self.index_set.tgtranges, srcranges) + + return ovsm_index_set + + def _map_dep_variable(self): + return np.equal(self.index_set.tgtindices.reshape(-1, 1), + self.index_set.srcindices.reshape(1, -1)).astype(np.float64) + + def map_int_g(self, expr): + source = self.places[expr.source] + target_discr = self.places[expr.target] + + if source.density_discr is not target_discr: + raise NotImplementedError() + + rec_density = self.dummy.rec(expr.density) + if is_zero(rec_density): + return 0 + + assert isinstance(rec_density, np.ndarray) + if len(rec_density.shape) != 2: + raise NotImplementedError("layer potentials on non-variables") + + resampler = source.direct_resampler + ovsm_index_set = self._oversampled_index_set(resampler) + + kernel = expr.kernel + kernel_args = _get_layer_potential_args(self, expr, source) + + from sumpy.expansion.local import LineTaylorLocalExpansion + local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) + + from sumpy.qbx import LayerPotentialMatrixBlockGenerator + mat_gen = LayerPotentialMatrixBlockGenerator( + self.queue.context, (local_expn,)) + + from pytential.qbx.utils import get_centers_on_side + assert abs(expr.qbx_forced_limit) > 0 + + _, (mat,) = mat_gen(self.queue, + target_discr.nodes(), + source.quad_stage2_density_discr.nodes(), + get_centers_on_side(source, expr.qbx_forced_limit), + expansion_radii=self.dep_source._expansion_radii("nsources"), + index_set=ovsm_index_set, + **kernel_args) + mat = mat.get() + + ovsm_blkranges = ovsm_index_set.linear_ranges() + ovsm_rowindices, ovsm_colindices = ovsm_index_set.linear_indices() + + waa = source.weights_and_area_elements().get(queue=self.queue) + mat *= waa[ovsm_colindices] + + # TODO: there should be a better way to do the matmat required for + # the resampling + resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) + + rmat = np.empty(ovsm_blkranges.shape[0] - 1, dtype=np.object) + for i in range(ovsm_blkranges.shape[0] - 1): + # TODO: would be nice to move some of this to sumpy.MatrixBlockIndex + ovsm_iblk = np.s_[ovsm_blkranges[i]:ovsm_blkranges[i + 1]] + ovsm_shape = (ovsm_index_set.tgtranges[i + 1] - + ovsm_index_set.tgtranges[i], + ovsm_index_set.srcranges[i + 1] - + ovsm_index_set.srcranges[i]) + mat_blk = mat[ovsm_iblk].reshape(*ovsm_shape) + + itgt = np.s_[ovsm_index_set.srcranges[i]:ovsm_index_set.srcranges[i + 1]] + itgt = ovsm_index_set.srcindices[itgt] + isrc = np.s_[self.index_set.srcranges[i]:self.index_set.srcranges[i + 1]] + isrc = self.index_set.srcindices[isrc] + resample_mat_blk = resample_mat[np.ix_(itgt, isrc)] + + rmat[i] = mat_blk.dot(resample_mat_blk).reshape(-1) + mat = np.hstack(rmat) + + # TODO: multiply with rec_density + + return mat + + +class P2PMatrixBlockBuilder(MatrixBlockBuilderBase): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + places, context, index_set, exclude_self=True): + super(P2PMatrixBlockBuilder, self).__init__(queue, + dep_expr, other_dep_exprs, dep_source, places, context, index_set) + + self.dummy = MatrixBlockBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, places, context, index_set) + self.exclude_self = exclude_self + + def _map_dep_variable(self): + return np.equal(self.index_set.tgtindices.reshape(-1, 1), + self.index_set.srcindices.reshape(1, -1)).astype(np.float64) + + def block(self, i): + itgt = np.s_[self.index_set.tgtranges[i]:self.index_set.tgtranges[i + 1]] + isrc = np.s_[self.index_set.srcranges[i]:self.index_set.srcranges[i + 1]] + + return (itgt, isrc) + + def map_int_g(self, expr): + source = self.places[expr.source] + target_discr = self.places[expr.target] + + if source.density_discr is not target_discr: + raise NotImplementedError() + + rec_density = self.dummy.rec(expr.density) + if is_zero(rec_density): + return 0 + + assert isinstance(rec_density, np.ndarray) + if len(rec_density.shape) != 2: + raise NotImplementedError("layer potentials on non-variables") + + try: + kernel = expr.kernel.inner_kernel + except AttributeError: + kernel = expr.kernel + kernel_args = _get_kernel_args(self, kernel, expr, source) + if self.exclude_self: + kernel_args["target_to_source"] = \ + cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) + + from sumpy.p2p import P2PMatrixBlockGenerator + mat_gen = P2PMatrixBlockGenerator( + self.queue.context, (kernel,), exclude_self=self.exclude_self) + + _, (mat,) = mat_gen(self.queue, + targets=target_discr.nodes(), + sources=source.nodes(), + index_set=self.index_set, + **kernel_args) + mat = mat.get() + + # TODO: need to multiply by rec_density + + return mat + +# }}} + # vim: foldmethod=marker -- GitLab From bed93c255556bce25ce7fef547b5dd01339280a0 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 19 Jun 2018 15:21:36 -0500 Subject: [PATCH 206/268] matrix: add tests for the block matrix builders --- pytential/symbolic/matrix.py | 4 +- test/test_linalg_proxy.py | 1 + test/test_matrix.py | 180 ++++++++++++++++++++++++++++++++++- 3 files changed, 180 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 5a62aa04..06e963ac 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -488,7 +488,7 @@ class MatrixBlockBuilder(MatrixBlockBuilderBase): resample_mat_blk = resample_mat[np.ix_(itgt, isrc)] rmat[i] = mat_blk.dot(resample_mat_blk).reshape(-1) - mat = np.hstack(rmat) + mat = np.hstack(rmat) # TODO: multiply with rec_density @@ -545,7 +545,7 @@ class P2PMatrixBlockBuilder(MatrixBlockBuilderBase): _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), - sources=source.nodes(), + sources=source.density_discr.nodes(), index_set=self.index_set, **kernel_args) mat = mat.get() diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index e8a063ca..f40f4053 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -88,6 +88,7 @@ def _build_block_index(discr, if method == 'elements': factor = 1.0 + density_discr = qbx.density_discr if method == 'nodes': nnodes = discr.nnodes else: diff --git a/test/test_matrix.py b/test/test_matrix.py index 9f0ff0b4..cadb3d65 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -22,16 +22,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from functools import partial + import numpy as np import numpy.linalg as la + import pyopencl as cl -import pytest + +from sumpy.symbolic import USE_SYMENGINE from meshmode.mesh.generation import \ ellipse, NArmedStarfish, make_curve_mesh + from pytential import bind, sym -from functools import partial -from sumpy.symbolic import USE_SYMENGINE +from pytential.symbolic.primitives import DEFAULT_TARGET, DEFAULT_SOURCE +import pytest from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) @@ -155,6 +160,175 @@ def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): assert rel_err < 1e-13 +@pytest.mark.parametrize("ndim", [2, 3]) +@pytest.mark.parametrize("factor", [1.0, 0.6]) +def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + # prevent cache explosion + from sympy.core.cache import clear_cache + clear_cache() + + from test_direct_solver import _create_data, _create_indices + target_order = 2 if ndim == 3 else 7 + qbx, op, u_sym = _create_data(queue, target_order=target_order, ndim=ndim) + + nblks = 10 + srcindices, srcranges = _create_indices(qbx, nblks, + method='nodes', factor=factor, random=True) + tgtindices, tgtranges = _create_indices(qbx, nblks, + method='nodes', factor=factor, random=True) + nblks = srcranges.shape[0] - 1 + assert srcranges.shape[0] == tgtranges.shape[0] + + from pytential.symbolic.execution import prepare_places, prepare_expr + places = prepare_places(qbx) + expr = prepare_expr(places, op) + + from sumpy.tools import MatrixBlockIndex + index_set = MatrixBlockIndex(queue, + tgtindices, srcindices, tgtranges, srcranges) + + from pytential.symbolic.matrix import P2PMatrixBlockBuilder + mbuilder = P2PMatrixBlockBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[DEFAULT_SOURCE], + places=places, + context={}, + index_set=index_set) + blk = mbuilder(expr) + + from pytential.symbolic.matrix import P2PMatrixBuilder + mbuilder = P2PMatrixBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[DEFAULT_SOURCE], + places=places, + context={}) + mat = mbuilder(expr) + + if visualize and ndim == 2: + blk_full = np.zeros_like(mat) + mat_full = np.zeros_like(mat) + + blkranges = index_set.linear_ranges() + rowindices, colindices = index_set.linear_indices() + for i in range(srcranges.shape[0] - 1): + iblk = np.s_[blkranges[i]:blkranges[i + 1]] + itgt = rowindices[iblk] + isrc = colindices[iblk] + + blk_full[itgt, isrc] = blk[iblk] + mat_full[itgt, isrc] = mat[itgt, isrc] + + import matplotlib.pyplot as mp + _, (ax1, ax2) = mp.subplots(1, 2, + figsize=(10, 8), dpi=300, constrained_layout=True) + ax1.imshow(blk_full) + ax1.set_title('P2PMatrixBlockBuilder') + ax2.imshow(mat_full) + ax2.set_title('P2PMatrixBuilder') + mp.savefig("test_p2p_block_{}d_{:.1f}.png".format(ndim, factor)) + + blkranges = index_set.linear_ranges() + rowindices, colindices = index_set.linear_indices() + for i in range(nblks): + iblk = np.s_[blkranges[i]:blkranges[i + 1]] + itgt = rowindices[iblk] + isrc = colindices[iblk] + + eps = 1.0e-14 * la.norm(mat[itgt, isrc]) + if visualize: + print('block[{:04}]: {:.5e}'.format(i, + la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)))) + assert la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)) < eps + + +@pytest.mark.parametrize("ndim", [2, 3]) +def test_qbx_block_builder(ctx_factory, ndim, visualize=False): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + # prevent cache explosion + from sympy.core.cache import clear_cache + clear_cache() + + from test_direct_solver import _create_data, _create_indices + target_order = 2 if ndim == 3 else 7 + qbx, op, u_sym = _create_data(queue, target_order=target_order, ndim=ndim) + + nblks = 10 + tgtindices, tgtranges = _create_indices(qbx, nblks) + srcindices, srcranges = _create_indices(qbx, nblks) + nblks = srcranges.shape[0] - 1 + assert srcranges.shape[0] == tgtranges.shape[0] + + from pytential.symbolic.execution import prepare_places, prepare_expr + places = prepare_places(qbx) + expr = prepare_expr(places, op) + + from sumpy.tools import MatrixBlockIndex + index_set = MatrixBlockIndex(queue, + tgtindices, srcindices, tgtranges, srcranges) + + from pytential.symbolic.matrix import MatrixBlockBuilder + mbuilder = MatrixBlockBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[DEFAULT_SOURCE], + places=places, + context={}, + index_set=index_set) + blk = mbuilder(expr) + + from pytential.symbolic.matrix import MatrixBuilder + mbuilder = MatrixBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[DEFAULT_SOURCE], + places=places, + context={}) + mat = mbuilder(expr) + + if visualize: + blk_full = np.zeros_like(mat) + mat_full = np.zeros_like(mat) + + blkranges = index_set.linear_ranges() + rowindices, colindices = index_set.linear_indices() + for i in range(srcranges.shape[0] - 1): + iblk = np.s_[blkranges[i]:blkranges[i + 1]] + itgt = rowindices[iblk] + isrc = colindices[iblk] + + blk_full[itgt, isrc] = blk[iblk] + mat_full[itgt, isrc] = mat[itgt, isrc] + + import matplotlib.pyplot as mp + _, (ax1, ax2) = mp.subplots(1, 2, + figsize=(10, 8), constrained_layout=True) + ax1.imshow(mat_full) + ax1.set_title('MatrixBuilder') + ax2.imshow(blk_full) + ax2.set_title('MatrixBlockBuilder') + mp.savefig("test_qbx_block_builder.png", dpi=300) + + blkranges = index_set.linear_ranges() + rowindices, colindices = index_set.linear_indices() + for i in range(nblks): + iblk = np.s_[blkranges[i]:blkranges[i + 1]] + itgt = rowindices[iblk] + isrc = colindices[iblk] + + eps = 1.0e-14 * la.norm(mat[itgt, isrc]) + if visualize: + print('block[{:04}]: {:.5e}'.format(i, + la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)))) + assert la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)) < eps + + if __name__ == "__main__": import sys if len(sys.argv) > 1: -- GitLab From 0bc2500a12da07dc7822a919f305e1a485a51824 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 19 Jun 2018 15:25:22 -0500 Subject: [PATCH 207/268] tests: flake8 --- test/test_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_matrix.py b/test/test_matrix.py index cadb3d65..ed440632 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -34,7 +34,7 @@ from meshmode.mesh.generation import \ ellipse, NArmedStarfish, make_curve_mesh from pytential import bind, sym -from pytential.symbolic.primitives import DEFAULT_TARGET, DEFAULT_SOURCE +from pytential.symbolic.primitives import DEFAULT_SOURCE import pytest from pyopencl.tools import ( # noqa -- GitLab From 3aefe76d66df6daadf5ebb06ca754058ffe7ceef Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 24 Jun 2018 19:49:28 -0500 Subject: [PATCH 208/268] direct-solver: modify to latest changes in linalg/proxy --- pytential/symbolic/matrix.py | 9 ++- test/test_linalg_proxy.py | 1 - test/test_matrix.py | 119 ++++++++++++++++++++--------------- 3 files changed, 75 insertions(+), 54 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 06e963ac..034b5ac8 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -406,14 +406,17 @@ class MatrixBlockBuilder(MatrixBlockBuilderBase): @memoize_method def _oversampled_index_set(self, resampler): - from pytential.direct_solver import partition_from_coarse + from pytential.linalg.proxy import partition_from_coarse srcindices, srcranges = partition_from_coarse(self.queue, resampler, self.index_set.srcindices, self.index_set.srcranges) from sumpy.tools import MatrixBlockIndex + # TODO: fix MatrixBlockIndex to work with CL arrays ovsm_index_set = MatrixBlockIndex(self.queue, - self.index_set.tgtindices, srcindices, - self.index_set.tgtranges, srcranges) + self.index_set.tgtindices, + srcindices.get(self.queue), + self.index_set.tgtranges, + srcranges.get(self.queue)) return ovsm_index_set diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index f40f4053..e8a063ca 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -88,7 +88,6 @@ def _build_block_index(discr, if method == 'elements': factor = 1.0 - density_discr = qbx.density_discr if method == 'nodes': nnodes = discr.nnodes else: diff --git a/test/test_matrix.py b/test/test_matrix.py index ed440632..b6810730 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -42,38 +42,25 @@ from pyopencl.tools import ( # noqa as pytest_generate_tests) -@pytest.mark.skipif(USE_SYMENGINE, - reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") -@pytest.mark.parametrize("k", [0, 42]) -@pytest.mark.parametrize("curve_f", [ - partial(ellipse, 3), - NArmedStarfish(5, 0.25)]) -@pytest.mark.parametrize("layer_pot_id", [1, 2]) -def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): - cl_ctx = ctx_factory() - queue = cl.CommandQueue(cl_ctx) - - # prevent cache 'splosion - from sympy.core.cache import clear_cache - clear_cache() - - target_order = 7 - qbx_order = 4 - nelements = 30 - +def _build_op(lpot_id, k=0, ndim=2): from sumpy.kernel import LaplaceKernel, HelmholtzKernel + knl_kwargs = {"qbx_forced_limit": "avg"} if k: - knl = HelmholtzKernel(2) - knl_kwargs = {"k": k} + knl = HelmholtzKernel(ndim) + knl_kwargs["k"] = k else: - knl = LaplaceKernel(2) - knl_kwargs = {} + knl = LaplaceKernel(ndim) - from pytools.obj_array import make_obj_array, is_obj_array - if layer_pot_id == 1: + if lpot_id == 1: + # scalar single-layer potential + u_sym = sym.var("u") + op = sym.S(knl, u_sym, **knl_kwargs) + elif lpot_id == 2: + # scalar double-layer potential u_sym = sym.var("u") - op = sym.Sp(knl, u_sym, **knl_kwargs) - elif layer_pot_id == 2: + op = sym.D(knl, u_sym, **knl_kwargs) + elif lpot_id == 3: + # vector potential u_sym = sym.make_sym_vector("u", 2) u0_sym, u1_sym = u_sym @@ -85,8 +72,29 @@ def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): 0.3 * sym.D(knl, u0_sym, **knl_kwargs) ]) else: - raise ValueError("Unknown layer_pot_id: {}".format(layer_pot_id)) + raise ValueError("Unknown lpot_id: {}".format(lpot_id)) + + return op, u_sym + +@pytest.mark.skipif(USE_SYMENGINE, + reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") +@pytest.mark.parametrize("k", [0, 42]) +@pytest.mark.parametrize("curve_f", [ + partial(ellipse, 3), + NArmedStarfish(5, 0.25)]) +@pytest.mark.parametrize("lpot_id", [1, 3]) +def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): + cl_ctx = ctx_factory() + queue = cl.CommandQueue(cl_ctx) + + # prevent cache 'splosion + from sympy.core.cache import clear_cache + clear_cache() + + target_order = 7 + qbx_order = 4 + nelements = 30 mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements + 1), target_order) @@ -94,16 +102,18 @@ def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - from pytential.qbx import QBXLayerPotentialSource pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + from pytential.qbx import QBXLayerPotentialSource qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4 * target_order, qbx_order, # Don't use FMM for now fmm_order=False).with_refinement() density_discr = qbx.density_discr + + op, u_sym = _build_op(lpot_id, k=k) bound_op = bind(qbx, op) from pytential.symbolic.execution import build_matrix @@ -162,7 +172,9 @@ def test_matrix_build(ctx_factory, k, curve_f, layer_pot_id, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) @pytest.mark.parametrize("factor", [1.0, 0.6]) -def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): +@pytest.mark.parametrize("lpot_id", [1, 2]) +def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, + visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -170,16 +182,15 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): from sympy.core.cache import clear_cache clear_cache() - from test_direct_solver import _create_data, _create_indices + from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 - qbx, op, u_sym = _create_data(queue, target_order=target_order, ndim=ndim) - - nblks = 10 - srcindices, srcranges = _create_indices(qbx, nblks, - method='nodes', factor=factor, random=True) - tgtindices, tgtranges = _create_indices(qbx, nblks, - method='nodes', factor=factor, random=True) - nblks = srcranges.shape[0] - 1 + qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) + op, u_sym = _build_op(lpot_id, ndim=ndim) + + srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + method='nodes', factor=factor) + tgtindices, tgtranges = _build_block_index(queue, qbx.density_discr, + method='nodes', factor=factor) assert srcranges.shape[0] == tgtranges.shape[0] from pytential.symbolic.execution import prepare_places, prepare_expr @@ -187,8 +198,12 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): expr = prepare_expr(places, op) from sumpy.tools import MatrixBlockIndex + # TODO: make MatrixBlockIndex work properly with CL arrays index_set = MatrixBlockIndex(queue, - tgtindices, srcindices, tgtranges, srcranges) + tgtindices.get(queue), + srcindices.get(queue), + tgtranges.get(queue), + srcranges.get(queue)) from pytential.symbolic.matrix import P2PMatrixBlockBuilder mbuilder = P2PMatrixBlockBuilder(queue, @@ -234,7 +249,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): blkranges = index_set.linear_ranges() rowindices, colindices = index_set.linear_indices() - for i in range(nblks): + for i in range(blkranges.shape[0] - 1): iblk = np.s_[blkranges[i]:blkranges[i + 1]] itgt = rowindices[iblk] isrc = colindices[iblk] @@ -247,7 +262,8 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, visualize=False): @pytest.mark.parametrize("ndim", [2, 3]) -def test_qbx_block_builder(ctx_factory, ndim, visualize=False): +@pytest.mark.parametrize("lpot_id", [1]) +def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -255,14 +271,13 @@ def test_qbx_block_builder(ctx_factory, ndim, visualize=False): from sympy.core.cache import clear_cache clear_cache() - from test_direct_solver import _create_data, _create_indices + from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 - qbx, op, u_sym = _create_data(queue, target_order=target_order, ndim=ndim) + qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) + op, u_sym = _build_op(lpot_id, ndim=ndim) - nblks = 10 - tgtindices, tgtranges = _create_indices(qbx, nblks) - srcindices, srcranges = _create_indices(qbx, nblks) - nblks = srcranges.shape[0] - 1 + tgtindices, tgtranges = _build_block_index(queue, qbx.density_discr) + srcindices, srcranges = _build_block_index(queue, qbx.density_discr) assert srcranges.shape[0] == tgtranges.shape[0] from pytential.symbolic.execution import prepare_places, prepare_expr @@ -270,8 +285,12 @@ def test_qbx_block_builder(ctx_factory, ndim, visualize=False): expr = prepare_expr(places, op) from sumpy.tools import MatrixBlockIndex + # TODO: make MatrixBlockIndex work properly with CL arrays index_set = MatrixBlockIndex(queue, - tgtindices, srcindices, tgtranges, srcranges) + tgtindices.get(queue), + srcindices.get(queue), + tgtranges.get(queue), + srcranges.get(queue)) from pytential.symbolic.matrix import MatrixBlockBuilder mbuilder = MatrixBlockBuilder(queue, @@ -317,7 +336,7 @@ def test_qbx_block_builder(ctx_factory, ndim, visualize=False): blkranges = index_set.linear_ranges() rowindices, colindices = index_set.linear_indices() - for i in range(nblks): + for i in range(blkranges.shape[0] - 1): iblk = np.s_[blkranges[i]:blkranges[i + 1]] itgt = rowindices[iblk] isrc = colindices[iblk] -- GitLab From 08e4441a6abec8a5b2f5eda852923ed01d7d0afd Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 24 Jun 2018 20:27:08 -0500 Subject: [PATCH 209/268] tests: add missing import --- test/test_matrix.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/test_matrix.py b/test/test_matrix.py index b6810730..457de6ca 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import, print_function -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2018 Andreas Kloeckner +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,6 +31,7 @@ import numpy as np import numpy.linalg as la import pyopencl as cl +from pytools.obj_array import make_obj_array, is_obj_array from sumpy.symbolic import USE_SYMENGINE from meshmode.mesh.generation import \ @@ -74,7 +78,7 @@ def _build_op(lpot_id, k=0, ndim=2): else: raise ValueError("Unknown lpot_id: {}".format(lpot_id)) - return op, u_sym + return op, u_sym, knl_kwargs @pytest.mark.skipif(USE_SYMENGINE, @@ -113,7 +117,7 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): fmm_order=False).with_refinement() density_discr = qbx.density_discr - op, u_sym = _build_op(lpot_id, k=k) + op, u_sym, knl_kwargs = _build_op(lpot_id, k=k) bound_op = bind(qbx, op) from pytential.symbolic.execution import build_matrix @@ -185,7 +189,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) - op, u_sym = _build_op(lpot_id, ndim=ndim) + op, u_sym, _ = _build_op(lpot_id, ndim=ndim) srcindices, srcranges = _build_block_index(queue, qbx.density_discr, method='nodes', factor=factor) @@ -274,7 +278,7 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) - op, u_sym = _build_op(lpot_id, ndim=ndim) + op, u_sym, _ = _build_op(lpot_id, ndim=ndim) tgtindices, tgtranges = _build_block_index(queue, qbx.density_discr) srcindices, srcranges = _build_block_index(queue, qbx.density_discr) -- GitLab From 9dcdbed4a315f5f4fa1945af836b6dad665eb045 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 29 Jun 2018 15:01:26 -0500 Subject: [PATCH 210/268] port to new modifications in MatrixBlockRanges --- pytential/symbolic/matrix.py | 70 +++++++++++----------------- test/test_matrix.py | 88 +++++++++++++----------------------- 2 files changed, 57 insertions(+), 101 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 034b5ac8..c534d64a 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -341,7 +341,7 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): self.index_set = index_set def _map_dep_variable(self): - return np.eye(self.index_set.srcindices.shape[0]) + return np.eye(self.index_set.col.indices.shape[0]) def map_variable(self, expr): if expr == self.dep_expr: @@ -407,22 +407,19 @@ class MatrixBlockBuilder(MatrixBlockBuilderBase): @memoize_method def _oversampled_index_set(self, resampler): from pytential.linalg.proxy import partition_from_coarse - srcindices, srcranges = partition_from_coarse(self.queue, resampler, - self.index_set.srcindices, self.index_set.srcranges) + srcindices = partition_from_coarse(resampler, self.index_set) - from sumpy.tools import MatrixBlockIndex - # TODO: fix MatrixBlockIndex to work with CL arrays - ovsm_index_set = MatrixBlockIndex(self.queue, - self.index_set.tgtindices, - srcindices.get(self.queue), - self.index_set.tgtranges, - srcranges.get(self.queue)) + from sumpy.tools import MatrixBlockIndexRanges + ovsm_index_set = MatrixBlockIndexRanges(self.queue.context, + self.index_set.row, srcindices) return ovsm_index_set def _map_dep_variable(self): - return np.equal(self.index_set.tgtindices.reshape(-1, 1), - self.index_set.srcindices.reshape(1, -1)).astype(np.float64) + tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) + srcindices = self.index_set.col.indices.get(self.queue).reshape(1, -1) + + return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): source = self.places[expr.source] @@ -462,38 +459,27 @@ class MatrixBlockBuilder(MatrixBlockBuilderBase): expansion_radii=self.dep_source._expansion_radii("nsources"), index_set=ovsm_index_set, **kernel_args) - mat = mat.get() - ovsm_blkranges = ovsm_index_set.linear_ranges() - ovsm_rowindices, ovsm_colindices = ovsm_index_set.linear_indices() + waa = source.weights_and_area_elements().with_queue(self.queue) + mat *= waa[ovsm_index_set.linear_col_indices] - waa = source.weights_and_area_elements().get(queue=self.queue) - mat *= waa[ovsm_colindices] + from sumpy.tools import MatrixBlockIndexRanges + ovsm_col_index = ovsm_index_set.get(self.queue) + ovsm_row_index = MatrixBlockIndexRanges(ovsm_col_index.cl_context, + ovsm_col_index.col, ovsm_col_index.row) - # TODO: there should be a better way to do the matmat required for - # the resampling + mat = mat.get(self.queue) resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) - rmat = np.empty(ovsm_blkranges.shape[0] - 1, dtype=np.object) - for i in range(ovsm_blkranges.shape[0] - 1): - # TODO: would be nice to move some of this to sumpy.MatrixBlockIndex - ovsm_iblk = np.s_[ovsm_blkranges[i]:ovsm_blkranges[i + 1]] - ovsm_shape = (ovsm_index_set.tgtranges[i + 1] - - ovsm_index_set.tgtranges[i], - ovsm_index_set.srcranges[i + 1] - - ovsm_index_set.srcranges[i]) - mat_blk = mat[ovsm_iblk].reshape(*ovsm_shape) - - itgt = np.s_[ovsm_index_set.srcranges[i]:ovsm_index_set.srcranges[i + 1]] - itgt = ovsm_index_set.srcindices[itgt] - isrc = np.s_[self.index_set.srcranges[i]:self.index_set.srcranges[i + 1]] - isrc = self.index_set.srcindices[isrc] - resample_mat_blk = resample_mat[np.ix_(itgt, isrc)] - - rmat[i] = mat_blk.dot(resample_mat_blk).reshape(-1) + rmat = np.empty(ovsm_index_set.nblocks, dtype=np.object) + for i in range(ovsm_index_set.nblocks): + mat_blk = ovsm_col_index.block_take(mat, i) + resample_blk = ovsm_row_index.take(resample_mat, i) + + rmat[i] = mat_blk.dot(resample_blk).reshape(-1) mat = np.hstack(rmat) - # TODO: multiply with rec_density + # TODO:: multiply with rec_density return mat @@ -509,14 +495,10 @@ class P2PMatrixBlockBuilder(MatrixBlockBuilderBase): self.exclude_self = exclude_self def _map_dep_variable(self): - return np.equal(self.index_set.tgtindices.reshape(-1, 1), - self.index_set.srcindices.reshape(1, -1)).astype(np.float64) - - def block(self, i): - itgt = np.s_[self.index_set.tgtranges[i]:self.index_set.tgtranges[i + 1]] - isrc = np.s_[self.index_set.srcranges[i]:self.index_set.srcranges[i + 1]] + tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) + srcindices = self.index_set.col.indices.get(self.queue).reshape(1, -1) - return (itgt, isrc) + return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): source = self.places[expr.source] diff --git a/test/test_matrix.py b/test/test_matrix.py index 457de6ca..3becd048 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -2,7 +2,7 @@ from __future__ import division, absolute_import, print_function __copyright__ = """ Copyright (C) 2015 Andreas Kloeckner -Copyright (C) 2018 Andreas Kloeckner +Copyright (C) 2018 Alexandru Fikl """ __license__ = """ @@ -191,23 +191,17 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) op, u_sym, _ = _build_op(lpot_id, ndim=ndim) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr, + srcindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) - tgtindices, tgtranges = _build_block_index(queue, qbx.density_discr, + tgtindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) - assert srcranges.shape[0] == tgtranges.shape[0] from pytential.symbolic.execution import prepare_places, prepare_expr places = prepare_places(qbx) expr = prepare_expr(places, op) - from sumpy.tools import MatrixBlockIndex - # TODO: make MatrixBlockIndex work properly with CL arrays - index_set = MatrixBlockIndex(queue, - tgtindices.get(queue), - srcindices.get(queue), - tgtranges.get(queue), - srcranges.get(queue)) + from sumpy.tools import MatrixBlockIndexRanges + index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) from pytential.symbolic.matrix import P2PMatrixBlockBuilder mbuilder = P2PMatrixBlockBuilder(queue, @@ -228,19 +222,16 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, context={}) mat = mbuilder(expr) + index_set = index_set.get(queue) if visualize and ndim == 2: blk_full = np.zeros_like(mat) mat_full = np.zeros_like(mat) - blkranges = index_set.linear_ranges() - rowindices, colindices = index_set.linear_indices() - for i in range(srcranges.shape[0] - 1): - iblk = np.s_[blkranges[i]:blkranges[i + 1]] - itgt = rowindices[iblk] - isrc = colindices[iblk] + for i in range(index_set.nblocks): + itgt, isrc = index_set.block_indices(i) - blk_full[itgt, isrc] = blk[iblk] - mat_full[itgt, isrc] = mat[itgt, isrc] + blk_full[np.ix_(itgt, isrc)] = index_set.block_take(blk, i) + mat_full[np.ix_(itgt, isrc)] = index_set.take(mat, i) import matplotlib.pyplot as mp _, (ax1, ax2) = mp.subplots(1, 2, @@ -251,18 +242,14 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, ax2.set_title('P2PMatrixBuilder') mp.savefig("test_p2p_block_{}d_{:.1f}.png".format(ndim, factor)) - blkranges = index_set.linear_ranges() - rowindices, colindices = index_set.linear_indices() - for i in range(blkranges.shape[0] - 1): - iblk = np.s_[blkranges[i]:blkranges[i + 1]] - itgt = rowindices[iblk] - isrc = colindices[iblk] + for i in range(index_set.nblocks): + eps = 1.0e-14 * la.norm(index_set.take(mat, i)) + error = la.norm(index_set.block_take(blk, i) - + index_set.take(mat, i)) - eps = 1.0e-14 * la.norm(mat[itgt, isrc]) if visualize: - print('block[{:04}]: {:.5e}'.format(i, - la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)))) - assert la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)) < eps + print('block[{:04}]: {:.5e}'.format(i, error)) + assert error < eps @pytest.mark.parametrize("ndim", [2, 3]) @@ -280,21 +267,15 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) op, u_sym, _ = _build_op(lpot_id, ndim=ndim) - tgtindices, tgtranges = _build_block_index(queue, qbx.density_discr) - srcindices, srcranges = _build_block_index(queue, qbx.density_discr) - assert srcranges.shape[0] == tgtranges.shape[0] + tgtindices = _build_block_index(qbx.density_discr) + srcindices = _build_block_index(qbx.density_discr) from pytential.symbolic.execution import prepare_places, prepare_expr places = prepare_places(qbx) expr = prepare_expr(places, op) - from sumpy.tools import MatrixBlockIndex - # TODO: make MatrixBlockIndex work properly with CL arrays - index_set = MatrixBlockIndex(queue, - tgtindices.get(queue), - srcindices.get(queue), - tgtranges.get(queue), - srcranges.get(queue)) + from sumpy.tools import MatrixBlockIndexRanges + index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) from pytential.symbolic.matrix import MatrixBlockBuilder mbuilder = MatrixBlockBuilder(queue, @@ -315,19 +296,16 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): context={}) mat = mbuilder(expr) + index_set = index_set.get(queue) if visualize: blk_full = np.zeros_like(mat) mat_full = np.zeros_like(mat) - blkranges = index_set.linear_ranges() - rowindices, colindices = index_set.linear_indices() - for i in range(srcranges.shape[0] - 1): - iblk = np.s_[blkranges[i]:blkranges[i + 1]] - itgt = rowindices[iblk] - isrc = colindices[iblk] + for i in range(index_set.nblocks): + itgt, isrc = index_set.block_indices(i) - blk_full[itgt, isrc] = blk[iblk] - mat_full[itgt, isrc] = mat[itgt, isrc] + blk_full[np.ix_(itgt, isrc)] = index_set.block_take(blk, i) + mat_full[np.ix_(itgt, isrc)] = index_set.take(mat, i) import matplotlib.pyplot as mp _, (ax1, ax2) = mp.subplots(1, 2, @@ -338,18 +316,14 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): ax2.set_title('MatrixBlockBuilder') mp.savefig("test_qbx_block_builder.png", dpi=300) - blkranges = index_set.linear_ranges() - rowindices, colindices = index_set.linear_indices() - for i in range(blkranges.shape[0] - 1): - iblk = np.s_[blkranges[i]:blkranges[i + 1]] - itgt = rowindices[iblk] - isrc = colindices[iblk] + for i in range(index_set.nblocks): + eps = 1.0e-14 * la.norm(index_set.take(mat, i)) + error = la.norm(index_set.block_take(blk, i) - + index_set.take(mat, i)) - eps = 1.0e-14 * la.norm(mat[itgt, isrc]) if visualize: - print('block[{:04}]: {:.5e}'.format(i, - la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)))) - assert la.norm(blk[iblk] - mat[itgt, isrc].reshape(-1)) < eps + print('block[{:04}]: {:.5e}'.format(i, error)) + assert error < eps if __name__ == "__main__": -- GitLab From 766aec716bb6344a40d7bf1d3d668ef8a276bacc Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 3 Jul 2018 13:48:56 -0500 Subject: [PATCH 211/268] direct-solver: rename block builders --- pytential/symbolic/matrix.py | 8 ++++---- test/test_matrix.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index c534d64a..4ea90d11 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -395,10 +395,10 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): return result -class MatrixBlockBuilder(MatrixBlockBuilderBase): +class NearFieldBlockBuilder(MatrixBlockBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, context, index_set): - super(MatrixBlockBuilder, self).__init__(queue, + super(NearFieldBlockBuilder, self).__init__(queue, dep_expr, other_dep_exprs, dep_source, places, context, index_set) self.dummy = MatrixBlockBuilderBase(queue, @@ -484,10 +484,10 @@ class MatrixBlockBuilder(MatrixBlockBuilderBase): return mat -class P2PMatrixBlockBuilder(MatrixBlockBuilderBase): +class FarFieldBlockBuilder(MatrixBlockBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, context, index_set, exclude_self=True): - super(P2PMatrixBlockBuilder, self).__init__(queue, + super(FarFieldBlockBuilder, self).__init__(queue, dep_expr, other_dep_exprs, dep_source, places, context, index_set) self.dummy = MatrixBlockBuilderBase(queue, diff --git a/test/test_matrix.py b/test/test_matrix.py index 3becd048..aac9b86e 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -203,8 +203,8 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) - from pytential.symbolic.matrix import P2PMatrixBlockBuilder - mbuilder = P2PMatrixBlockBuilder(queue, + from pytential.symbolic.matrix import FarFieldBlockBuilder + mbuilder = FarFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], dep_source=places[DEFAULT_SOURCE], @@ -237,7 +237,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, _, (ax1, ax2) = mp.subplots(1, 2, figsize=(10, 8), dpi=300, constrained_layout=True) ax1.imshow(blk_full) - ax1.set_title('P2PMatrixBlockBuilder') + ax1.set_title('FarFieldBlockBuilder') ax2.imshow(mat_full) ax2.set_title('P2PMatrixBuilder') mp.savefig("test_p2p_block_{}d_{:.1f}.png".format(ndim, factor)) @@ -277,8 +277,8 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) - from pytential.symbolic.matrix import MatrixBlockBuilder - mbuilder = MatrixBlockBuilder(queue, + from pytential.symbolic.matrix import NearFieldBlockBuilder + mbuilder = NearFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], dep_source=places[DEFAULT_SOURCE], @@ -313,7 +313,7 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): ax1.imshow(mat_full) ax1.set_title('MatrixBuilder') ax2.imshow(blk_full) - ax2.set_title('MatrixBlockBuilder') + ax2.set_title('NearFieldBlockBuilder') mp.savefig("test_qbx_block_builder.png", dpi=300) for i in range(index_set.nblocks): -- GitLab From cf2785b775360fd2322c60112b7909d45e391fd5 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 26 Jul 2018 17:54:48 -0500 Subject: [PATCH 212/268] matrix: allow building matrix for quad_stage2_density_discr --- pytential/symbolic/execution.py | 172 ++++++++++++++++++------------- pytential/symbolic/mappers.py | 4 + pytential/symbolic/matrix.py | 23 ++--- pytential/symbolic/primitives.py | 29 +++++- test/test_matrix.py | 46 ++++++++- 5 files changed, 187 insertions(+), 87 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index f658a6aa..d5ab2c38 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -36,6 +36,7 @@ import pyopencl.clmath # noqa from loopy.version import MOST_RECENT_LANGUAGE_VERSION from pytools import memoize_in +from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET # FIXME caches: fix up queues @@ -286,11 +287,11 @@ def _domains_default(nresults, places, domains, default_val): raise RuntimeError("'domains is None' requires " "default domain to be defined") dom_name = default_val - return nresults*[dom_name] + return nresults * [dom_name] elif not isinstance(domains, (list, tuple)): dom_name = domains - return nresults*[dom_name] + return nresults * [dom_name] else: return domains @@ -300,31 +301,43 @@ def _domains_default(nresults, places, domains, default_val): # {{{ bound expression +def _get_discretization(places, where, default_discr="density_discr"): + from pytential.source import LayerPotentialSourceBase + from pytential.symbolic.primitives import ( + _QBXSource, _QBXSourceStage2, _QBXSourceQuadStage2) + + if isinstance(where, _QBXSourceStage2): + name = "stage2_density_discr" + elif isinstance(where, _QBXSourceQuadStage2): + name = "quad_stage2_density_discr" + else: + name = default_discr + + if isinstance(where, _QBXSource): + where = where.where + discr = places[where] + + if isinstance(discr, LayerPotentialSourceBase): + return discr, getattr(discr, name) + return discr, discr + + class BoundExpression: - def __init__(self, optemplate, places): - self.optemplate = optemplate + def __init__(self, places, sym_op_expr, sym_op_args=None): self.places = places + self.sym_op_expr = sym_op_expr + self.sym_op_args = sym_op_args self.caches = {} from pytential.symbolic.compiler import OperatorCompiler - self.code = OperatorCompiler(self.places)(optemplate) + self.code = OperatorCompiler(self.places)(sym_op_expr) def get_cache(self, name): return self.caches.setdefault(name, {}) def get_discretization(self, where): - from pytential.symbolic.primitives import _QBXSourceStage2 - if isinstance(where, _QBXSourceStage2): - lpot_source = self.places[where.where] - return lpot_source.stage2_density_discr - - discr = self.places[where] - - from pytential.source import LayerPotentialSourceBase - if isinstance(discr, LayerPotentialSourceBase): - discr = discr.density_discr - + _, discr = _get_discretization(self.places, where) return discr def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): @@ -343,7 +356,6 @@ class BoundExpression: else: nresults = 1 - from pytential.symbolic.primitives import DEFAULT_TARGET domains = _domains_default(nresults, self.places, domains, DEFAULT_TARGET) @@ -375,16 +387,32 @@ class BoundExpression: # {{{ expression prep -def prepare_places(places): - from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET +def _where_default(places, auto_where=None): + if auto_where is None: + if isinstance(places, dict) and DEFAULT_TARGET not in places: + return (DEFAULT_SOURCE, DEFAULT_SOURCE) + else: + return (DEFAULT_SOURCE, DEFAULT_TARGET) + + return auto_where + + +def prepare_places(places, auto_where=None): from meshmode.discretization import Discretization from pytential.source import LayerPotentialSourceBase from pytential.target import TargetBase + where_source, where_target = _where_default(places, auto_where) if isinstance(places, LayerPotentialSourceBase): + from pytential.symbolic.primitives import _QBXSourceQuadStage2 + if isinstance(where_target, _QBXSourceQuadStage2): + target_discr = places.quad_stage2_density_discr + else: + target_discr = places.density_discr + places = { DEFAULT_SOURCE: places, - DEFAULT_TARGET: places.density_discr, + DEFAULT_TARGET: target_discr, } elif isinstance(places, (Discretization, TargetBase)): places = { @@ -420,13 +448,9 @@ def prepare_expr(places, expr, auto_where=None): evaluations, find 'where' attributes automatically. """ - from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET from pytential.source import LayerPotentialSourceBase - from pytential.symbolic.mappers import ( - ToTargetTagger, - DerivativeBinder, - ) + ToTargetTagger, DerivativeBinder) if auto_where is None: if DEFAULT_TARGET in places: @@ -459,12 +483,33 @@ def bind(places, expr, auto_where=None): places = prepare_places(places) expr = prepare_expr(places, expr, auto_where=auto_where) - return BoundExpression(expr, places) + return BoundExpression(places, expr) # {{{ matrix building -def build_matrix(queue, places, expr, input_exprs, domains=None, +def prepare_expression(places, exprs, input_exprs, + domains=None, auto_where=None): + auto_where = _where_default(places, auto_where) + places = prepare_places(places, auto_where=auto_where) + exprs = prepare_expr(places, exprs, auto_where=auto_where) + + from pytools.obj_array import is_obj_array, make_obj_array + if not is_obj_array(exprs): + exprs = make_obj_array([exprs]) + try: + input_exprs = list(input_exprs) + except TypeError: + # not iterable, wrap in a list + input_exprs = [input_exprs] + + domains = _domains_default(len(input_exprs), places, domains, + DEFAULT_SOURCE) + + return places, exprs, input_exprs, domains + + +def build_matrix(queue, places, exprs, input_exprs, domains=None, auto_where=None, context=None): """ :arg queue: a :class:`pyopencl.CommandQueue` used to synchronize @@ -486,62 +531,49 @@ def build_matrix(queue, places, expr, input_exprs, domains=None, evaluations, find 'where' attributes automatically. """ + from pytools import single_valued + from pytential.symbolic.matrix import MatrixBuilder, is_zero + if context is None: context = {} - places = prepare_places(places) - expr = prepare_expr(places, expr, auto_where=auto_where) - - from pytools.obj_array import is_obj_array, make_obj_array - if not is_obj_array(expr): - expr = make_obj_array([expr]) - try: - input_exprs = list(input_exprs) - except TypeError: - # not iterable, wrap in a list - input_exprs = [input_exprs] - - from pytential.symbolic.primitives import DEFAULT_SOURCE - domains = _domains_default(len(input_exprs), places, domains, - DEFAULT_SOURCE) - - nblock_rows = len(expr) + auto_where = _where_default(places, auto_where) + places, exprs, input_exprs, domains = \ + prepare_expression(places, exprs, input_exprs, + domains=domains, + auto_where=auto_where) + auto_where = { + DEFAULT_SOURCE: auto_where[0], + DEFAULT_TARGET: auto_where[1] + } + + nblock_rows = len(exprs) nblock_columns = len(input_exprs) - blocks = np.zeros((nblock_rows, nblock_columns), dtype=np.object) - from pytential.symbolic.matrix import MatrixBuilder, is_zero - dtypes = [] - for ibcol in range(nblock_columns): + dep_source, dep_discr = \ + _get_discretization(places, auto_where[domains[ibcol]]) + mbuilder = MatrixBuilder( queue, dep_expr=input_exprs[ibcol], - other_dep_exprs=( - input_exprs[:ibcol] - + - input_exprs[ibcol+1:]), - dep_source=places[domains[ibcol]], + other_dep_exprs=(input_exprs[:ibcol] + + input_exprs[ibcol + 1:]), + dep_source=dep_source, + dep_discr=dep_discr, places=places, context=context) for ibrow in range(nblock_rows): - block = mbuilder(expr[ibrow]) - - assert ( - is_zero(block) - or isinstance(block, np.ndarray)) - if isinstance(block, np.ndarray): - dtypes.append(block.dtype) + block = mbuilder(exprs[ibrow]) + assert is_zero(block) or isinstance(block, np.ndarray) blocks[ibrow, ibcol] = block - - if isinstance(block, cl.array.Array): + if isinstance(block, np.ndarray): dtypes.append(block.dtype) - from pytools import single_valued - block_row_counts = [ single_valued( blocks[ibrow, ibcol].shape[0] @@ -550,20 +582,20 @@ def build_matrix(queue, places, expr, input_exprs, domains=None, for ibrow in range(nblock_rows)] block_col_counts = [ - places[domains[ibcol]].density_discr.nnodes + single_valued( + blocks[ibrow, ibcol].shape[1] + for ibrow in range(nblock_rows) + if not is_zero(blocks[ibrow, ibcol])) for ibcol in range(nblock_columns)] # "block row starts"/"block column starts" brs = np.cumsum([0] + block_row_counts) bcs = np.cumsum([0] + block_col_counts) - result = np.zeros( - (sum(block_row_counts), sum(block_col_counts)), - dtype=np.find_common_type(dtypes, [])) - + result = np.zeros((brs[-1], bcs[-1]), dtype=np.find_common_type(dtypes, [])) for ibcol in range(nblock_columns): for ibrow in range(nblock_rows): - result[brs[ibrow]:brs[ibrow+1], bcs[ibcol]:bcs[ibcol+1]] = \ + result[brs[ibrow]:brs[ibrow + 1], bcs[ibcol]:bcs[ibcol + 1]] = \ blocks[ibrow, ibcol] return cl.array.to_device(queue, result) diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 9fdbaae4..69349479 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -453,8 +453,12 @@ class QBXPreprocessor(IdentityMapper): # {{{ stringifier def stringify_where(where): + if isinstance(where, prim._QBXSourceStage1): + return "stage1(%s)" % stringify_where(where.where) if isinstance(where, prim._QBXSourceStage2): return "stage2(%s)" % stringify_where(where.where) + if isinstance(where, prim._QBXSourceQuadStage2): + return "quad_stage2(%s)" % stringify_where(where.where) if where is None: return "?" diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 4ea90d11..735782a8 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -92,15 +92,15 @@ def _get_kernel_args(mapper, kernel, expr, source): # We'll cheat and build the matrix on the host. class MatrixBuilder(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, - context): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context): super(MatrixBuilder, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs self.dep_source = dep_source - self.dep_discr = dep_source.density_discr + self.dep_discr = dep_discr self.places = places def map_variable(self, expr): @@ -188,11 +188,10 @@ class MatrixBuilder(EvaluationMapperBase): return vecs_and_scalars def map_int_g(self, expr): - source = self.places[expr.source] - target_discr = self.places[expr.target] - - if source.density_discr is not target_discr: - raise NotImplementedError() + from pytential.symbolic.execution import _get_discretization + source, source_discr = _get_discretization(self.places, expr.source, + default_discr="quad_stage2_density_discr") + _, target_discr = _get_discretization(self.places, expr.target) rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -217,7 +216,7 @@ class MatrixBuilder(EvaluationMapperBase): _, (mat,) = mat_gen(self.queue, target_discr.nodes(), - source.quad_stage2_density_discr.nodes(), + source_discr.nodes(), get_centers_on_side(source, expr.qbx_forced_limit), expansion_radii=self.dep_source._expansion_radii("nsources"), **kernel_args) @@ -277,10 +276,10 @@ class MatrixBuilder(EvaluationMapperBase): # {{{ p2p matrix builder class P2PMatrixBuilder(MatrixBuilder): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, places, - context, exclude_self=True): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context, exclude_self=True): super(P2PMatrixBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, places, context) + dep_expr, other_dep_exprs, dep_source, dep_discr, places, context) self.exclude_self = exclude_self diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 3a297fd1..909dbe5a 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -201,10 +201,10 @@ class DEFAULT_TARGET: # noqa pass -class _QBXSourceStage2(object): - """A symbolic 'where' specifier for the - :attr:`pytential.qbx.QBXLayerPotentialSource.stage2_density_discr` - of the layer potential source identified by :attr:`where`. +class _QBXSource(object): + """A symbolic 'where' specifier for the a density of a + :attr:`pytential.qbx.QBXLayerPotentialSource` + layer potential source identified by :attr:`where`. .. attribute:: where @@ -229,6 +229,27 @@ class _QBXSourceStage2(object): def __ne__(self, other): return not self.__eq__(other) + +class _QBXSourceStage1(_QBXSource): + """An explicit symbolic 'where' specifier for the + :attr:`pytential.qbx.QBXLayerPotentialSource.density_discr` + of the layer potential source identified by :attr:`where`. + """ + + +class _QBXSourceStage2(_QBXSource): + """A symbolic 'where' specifier for the + :attr:`pytential.qbx.QBXLayerPotentialSource.stage2_density_discr` + of the layer potential source identified by :attr:`where`. + """ + + +class _QBXSourceQuadStage2(_QBXSource): + """A symbolic 'where' specifier for the + :attr:`pytential.qbx.QBXLayerPotentialSource.quad_stage2_density_discr` + of the layer potential source identified by :attr:`where`. + """ + # }}} diff --git a/test/test_matrix.py b/test/test_matrix.py index aac9b86e..e168cbc3 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -38,7 +38,8 @@ from meshmode.mesh.generation import \ ellipse, NArmedStarfish, make_curve_mesh from pytential import bind, sym -from pytential.symbolic.primitives import DEFAULT_SOURCE +from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET +from pytential.symbolic.primitives import _QBXSourceStage1, _QBXSourceQuadStage2 import pytest from pyopencl.tools import ( # noqa @@ -218,6 +219,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, dep_expr=u_sym, other_dep_exprs=[], dep_source=places[DEFAULT_SOURCE], + dep_discr=places[DEFAULT_SOURCE].density_discr, places=places, context={}) mat = mbuilder(expr) @@ -292,6 +294,7 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): dep_expr=u_sym, other_dep_exprs=[], dep_source=places[DEFAULT_SOURCE], + dep_discr=places[DEFAULT_SOURCE].density_discr, places=places, context={}) mat = mbuilder(expr) @@ -326,6 +329,47 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): assert error < eps +@pytest.mark.parametrize('where', + [None, + (DEFAULT_SOURCE, DEFAULT_TARGET), + (_QBXSourceStage1(DEFAULT_SOURCE), + _QBXSourceStage1(DEFAULT_TARGET)), + (_QBXSourceQuadStage2(DEFAULT_SOURCE), + _QBXSourceQuadStage2(DEFAULT_TARGET))]) +def test_build_matrix_where(ctx_factory, where): + pytest.skip("wip") + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + + # prevent cache explosion + from sympy.core.cache import clear_cache + clear_cache() + + from test_linalg_proxy import _build_qbx_discr + qbx = _build_qbx_discr(queue, target_order=7, ndim=2) + op, u_sym, _ = _build_op(lpot_id=1, ndim=2) + + from pytential.symbolic.execution import build_matrix + mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) + + if where is None: + source_where, target_where = DEFAULT_SOURCE, DEFAULT_TARGET + else: + source_where, target_where = where + + if isinstance(source_where, _QBXSourceQuadStage2): + n = qbx.quad_stage2_density_discr.nnodes + else: + n = qbx.density_discr.nnodes + + if isinstance(target_where, _QBXSourceQuadStage2): + m = qbx.quad_stage2_density_discr.nnodes + else: + m = qbx.density_discr.nnodes + + assert mat.shape == (m, n) + + if __name__ == "__main__": import sys if len(sys.argv) > 1: -- GitLab From bf8a02a38ff86261ffbf43fb9ac35d06a54f64b6 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 29 Jul 2018 20:43:56 -0500 Subject: [PATCH 213/268] matrix: more work towards supporting different source and target where locations --- pytential/qbx/__init__.py | 2 +- pytential/symbolic/execution.py | 254 +++++++++++++++---------------- pytential/symbolic/mappers.py | 12 +- pytential/symbolic/matrix.py | 43 +++++- pytential/symbolic/primitives.py | 6 +- test/test_matrix.py | 15 +- 6 files changed, 176 insertions(+), 156 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 869f9222..2bbe3d36 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -564,7 +564,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): maxstretch = bind( self, sym._simplex_mapping_max_stretch_factor( self.ambient_dim, - where=sym._QBXSourceStage2(sym.DEFAULT_SOURCE)) + where=sym.QBXSourceStage2(sym.DEFAULT_SOURCE)) )(queue) maxstretch = utils.to_last_dim_length( self.stage2_density_discr, maxstretch, last_dim_length) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index d5ab2c38..2ec5b05f 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -37,6 +37,8 @@ from loopy.version import MOST_RECENT_LANGUAGE_VERSION from pytools import memoize_in from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET +from pytential.symbolic.primitives import ( + QBXSourceStage1, QBXSourceStage2, QBXSourceQuadStage2) # FIXME caches: fix up queues @@ -279,7 +281,7 @@ class MatVecOp: # }}} -# {{{ default for 'domains' parameter +# {{{ expression prep def _domains_default(nresults, places, domains, default_val): if domains is None: @@ -296,30 +298,130 @@ def _domains_default(nresults, places, domains, default_val): else: return domains + +def _where_default(places, auto_where=None): + if auto_where is None: + if not isinstance(places, dict): + return DEFAULT_SOURCE, DEFAULT_TARGET + if DEFAULT_TARGET in places: + return DEFAULT_SOURCE, DEFAULT_TARGET + return tuple(places.keys()) + + return auto_where + + +def prepare_places(places, auto_where=None): + from meshmode.discretization import Discretization + from pytential.source import LayerPotentialSourceBase + from pytential.target import TargetBase + + where_source, where_target = _where_default(places, auto_where=auto_where) + if isinstance(places, LayerPotentialSourceBase): + _, target_discr = _get_discretization(places, where_target) + places = { + where_source: places, + where_target: target_discr, + } + elif isinstance(places, (Discretization, TargetBase)): + places = { + where_target: places, + } + + elif isinstance(places, tuple): + source_discr, target_discr = places + places = { + where_source: source_discr, + where_target: target_discr, + } + del source_discr + del target_discr + + def cast_to_place(discr): + from pytential.target import TargetBase + from pytential.source import PotentialSource + if not isinstance(discr, (Discretization, TargetBase, PotentialSource)): + raise TypeError("must pass discretizations, " + "layer potential sources or targets as 'places'") + return discr + + return dict( + (key, cast_to_place(value)) + for key, value in six.iteritems(places)) + + +def prepare_expr(places, expr, auto_where=None): + """ + :arg places: result of :func:`prepare_places` + :arg auto_where: For simple source-to-self or source-to-target + evaluations, find 'where' attributes automatically. + """ + + from pytential.source import LayerPotentialSourceBase + from pytential.symbolic.mappers import ( + ToTargetTagger, DerivativeBinder) + + auto_where = _where_default(places, auto_where=auto_where) + if auto_where: + expr = ToTargetTagger(*auto_where)(expr) + + expr = DerivativeBinder()(expr) + + for name, place in six.iteritems(places): + if isinstance(place, LayerPotentialSourceBase): + expr = place.preprocess_optemplate(name, places, expr) + + return expr + + +def prepare_expression(places, exprs, input_exprs, + domains=None, auto_where=None): + auto_where = _where_default(places, auto_where) + places = prepare_places(places, auto_where=auto_where) + exprs = prepare_expr(places, exprs, auto_where=auto_where) + + from pytools.obj_array import is_obj_array, make_obj_array + if not is_obj_array(exprs): + exprs = make_obj_array([exprs]) + try: + input_exprs = list(input_exprs) + except TypeError: + # not iterable, wrap in a list + input_exprs = [input_exprs] + + domains = _domains_default(len(input_exprs), places, domains, auto_where[0]) + + return places, exprs, input_exprs, domains + # }}} # {{{ bound expression -def _get_discretization(places, where, default_discr="density_discr"): +def _get_discretization(places, where, default_source=QBXSourceStage1): from pytential.source import LayerPotentialSourceBase - from pytential.symbolic.primitives import ( - _QBXSource, _QBXSourceStage2, _QBXSourceQuadStage2) - if isinstance(where, _QBXSourceStage2): - name = "stage2_density_discr" - elif isinstance(where, _QBXSourceQuadStage2): - name = "quad_stage2_density_discr" - else: - name = default_discr + if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: + where = default_source(where) - if isinstance(where, _QBXSource): - where = where.where - discr = places[where] + if isinstance(places, LayerPotentialSourceBase): + lpot = places + else: + try: + lpot = places[where.where] + except KeyError: + lpot = places[where] + is_lpot = isinstance(lpot, LayerPotentialSourceBase) + + if isinstance(where, QBXSourceStage1): + discr = lpot.density_discr if is_lpot else lpot + elif isinstance(where, QBXSourceStage2): + discr = lpot.stage2_density_discr if is_lpot else lpot + elif isinstance(where, QBXSourceQuadStage2): + discr = lpot.quad_stage2_density_discr if is_lpot else lpot + else: + raise ValueError("Unknown 'where': {}".format(type(where))) - if isinstance(discr, LayerPotentialSourceBase): - return discr, getattr(discr, name) - return discr, discr + return lpot, discr class BoundExpression: @@ -382,95 +484,6 @@ class BoundExpression: exec_mapper = EvaluationMapper(self, queue, args) return self.code.execute(exec_mapper) -# }}} - - -# {{{ expression prep - -def _where_default(places, auto_where=None): - if auto_where is None: - if isinstance(places, dict) and DEFAULT_TARGET not in places: - return (DEFAULT_SOURCE, DEFAULT_SOURCE) - else: - return (DEFAULT_SOURCE, DEFAULT_TARGET) - - return auto_where - - -def prepare_places(places, auto_where=None): - from meshmode.discretization import Discretization - from pytential.source import LayerPotentialSourceBase - from pytential.target import TargetBase - - where_source, where_target = _where_default(places, auto_where) - if isinstance(places, LayerPotentialSourceBase): - from pytential.symbolic.primitives import _QBXSourceQuadStage2 - if isinstance(where_target, _QBXSourceQuadStage2): - target_discr = places.quad_stage2_density_discr - else: - target_discr = places.density_discr - - places = { - DEFAULT_SOURCE: places, - DEFAULT_TARGET: target_discr, - } - elif isinstance(places, (Discretization, TargetBase)): - places = { - DEFAULT_TARGET: places, - } - - elif isinstance(places, tuple): - source_discr, target_discr = places - places = { - DEFAULT_SOURCE: source_discr, - DEFAULT_TARGET: target_discr, - } - del source_discr - del target_discr - - def cast_to_place(discr): - from pytential.target import TargetBase - from pytential.source import PotentialSource - if not isinstance(discr, (Discretization, TargetBase, PotentialSource)): - raise TypeError("must pass discretizations, " - "layer potential sources or targets as 'places'") - return discr - - return dict( - (key, cast_to_place(value)) - for key, value in six.iteritems(places)) - - -def prepare_expr(places, expr, auto_where=None): - """ - :arg places: result of :func:`prepare_places` - :arg auto_where: For simple source-to-self or source-to-target - evaluations, find 'where' attributes automatically. - """ - - from pytential.source import LayerPotentialSourceBase - from pytential.symbolic.mappers import ( - ToTargetTagger, DerivativeBinder) - - if auto_where is None: - if DEFAULT_TARGET in places: - auto_where = DEFAULT_SOURCE, DEFAULT_TARGET - else: - auto_where = DEFAULT_SOURCE, DEFAULT_SOURCE - - if auto_where: - expr = ToTargetTagger(*auto_where)(expr) - - expr = DerivativeBinder()(expr) - - for name, place in six.iteritems(places): - if isinstance(place, LayerPotentialSourceBase): - expr = place.preprocess_optemplate(name, places, expr) - - return expr - -# }}} - def bind(places, expr, auto_where=None): """ @@ -485,29 +498,10 @@ def bind(places, expr, auto_where=None): expr = prepare_expr(places, expr, auto_where=auto_where) return BoundExpression(places, expr) +# }}} -# {{{ matrix building - -def prepare_expression(places, exprs, input_exprs, - domains=None, auto_where=None): - auto_where = _where_default(places, auto_where) - places = prepare_places(places, auto_where=auto_where) - exprs = prepare_expr(places, exprs, auto_where=auto_where) - - from pytools.obj_array import is_obj_array, make_obj_array - if not is_obj_array(exprs): - exprs = make_obj_array([exprs]) - try: - input_exprs = list(input_exprs) - except TypeError: - # not iterable, wrap in a list - input_exprs = [input_exprs] - - domains = _domains_default(len(input_exprs), places, domains, - DEFAULT_SOURCE) - - return places, exprs, input_exprs, domains +# {{{ matrix building def build_matrix(queue, places, exprs, input_exprs, domains=None, auto_where=None, context=None): @@ -537,15 +531,11 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, if context is None: context = {} - auto_where = _where_default(places, auto_where) + auto_where = _where_default(places, auto_where=auto_where) places, exprs, input_exprs, domains = \ prepare_expression(places, exprs, input_exprs, domains=domains, auto_where=auto_where) - auto_where = { - DEFAULT_SOURCE: auto_where[0], - DEFAULT_TARGET: auto_where[1] - } nblock_rows = len(exprs) nblock_columns = len(input_exprs) @@ -554,7 +544,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, dtypes = [] for ibcol in range(nblock_columns): dep_source, dep_discr = \ - _get_discretization(places, auto_where[domains[ibcol]]) + _get_discretization(places, domains[ibcol]) mbuilder = MatrixBuilder( queue, diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 69349479..45f3b84f 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -292,8 +292,10 @@ class ToTargetTagger(LocationTagger): """ def __init__(self, default_source, default_target): - LocationTagger.__init__(self, default_target) - self.operand_rec = LocationTagger(default_source) + LocationTagger.__init__(self, default_target, + default_source=default_source) + self.operand_rec = LocationTagger(default_source, + default_source=default_source) # }}} @@ -453,11 +455,11 @@ class QBXPreprocessor(IdentityMapper): # {{{ stringifier def stringify_where(where): - if isinstance(where, prim._QBXSourceStage1): + if isinstance(where, prim.QBXSourceStage1): return "stage1(%s)" % stringify_where(where.where) - if isinstance(where, prim._QBXSourceStage2): + if isinstance(where, prim.QBXSourceStage2): return "stage2(%s)" % stringify_where(where.where) - if isinstance(where, prim._QBXSourceQuadStage2): + if isinstance(where, prim.QBXSourceQuadStage2): return "quad_stage2(%s)" % stringify_where(where.where) if where is None: diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 735782a8..d6f15922 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -83,6 +83,30 @@ def _get_kernel_args(mapper, kernel, expr, source): return kernel_args + +def _weights_and_area_elements(queue, source, where): + if isinstance(where, sym.QBXSourceQuadStage2): + return source.weights_and_area_elements() + + # NOTE: copied from `weights_and_area_elements`, but using the + # discretization given by `where` and no interpolation + from pytential.symbolic.execution import _get_discretization + discr = _get_discretization(source, where) + + area = bind(discr, sym.area_element(source.ambient_dim, source.dim))(queue) + qweight = bind(discr, sym.QWeight())(queue) + + return area * qweight + + +def _get_centers_on_side(queue, source, qbx_forced_limit): + pass + + +def _get_expansion_radii(queue, source): + pass + + # }}} @@ -189,8 +213,12 @@ class MatrixBuilder(EvaluationMapperBase): def map_int_g(self, expr): from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, expr.source, - default_discr="quad_stage2_density_discr") + if expr.source is sym.DEFAULT_SOURCE: + where_source = sym.QBXSourceQuadStage2(expr.source) + else: + where_source = expr.source + + source, source_discr = _get_discretization(self.places, where_source) _, target_discr = _get_discretization(self.places, expr.target) rec_density = self.rec(expr.density) @@ -222,13 +250,14 @@ class MatrixBuilder(EvaluationMapperBase): **kernel_args) mat = mat.get() - waa = source.weights_and_area_elements().get(queue=self.queue) - mat[:, :] *= waa + waa = _weights_and_area_elements(self.queue, source, where_source) + mat[:, :] *= waa.get(self.queue) - resampler = source.direct_resampler - resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) + if target_discr.nnodes != source_discr.nnodes: + resampler = source.direct_resampler + resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) + mat = mat.dot(resample_mat) - mat = mat.dot(resample_mat) mat = mat.dot(rec_density) return mat diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 909dbe5a..2288ab85 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -230,21 +230,21 @@ class _QBXSource(object): return not self.__eq__(other) -class _QBXSourceStage1(_QBXSource): +class QBXSourceStage1(_QBXSource): """An explicit symbolic 'where' specifier for the :attr:`pytential.qbx.QBXLayerPotentialSource.density_discr` of the layer potential source identified by :attr:`where`. """ -class _QBXSourceStage2(_QBXSource): +class QBXSourceStage2(_QBXSource): """A symbolic 'where' specifier for the :attr:`pytential.qbx.QBXLayerPotentialSource.stage2_density_discr` of the layer potential source identified by :attr:`where`. """ -class _QBXSourceQuadStage2(_QBXSource): +class QBXSourceQuadStage2(_QBXSource): """A symbolic 'where' specifier for the :attr:`pytential.qbx.QBXLayerPotentialSource.quad_stage2_density_discr` of the layer potential source identified by :attr:`where`. diff --git a/test/test_matrix.py b/test/test_matrix.py index e168cbc3..e3cc0c8d 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -39,7 +39,7 @@ from meshmode.mesh.generation import \ from pytential import bind, sym from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET -from pytential.symbolic.primitives import _QBXSourceStage1, _QBXSourceQuadStage2 +from pytential.symbolic.primitives import QBXSourceStage1, QBXSourceQuadStage2 import pytest from pyopencl.tools import ( # noqa @@ -332,12 +332,11 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): @pytest.mark.parametrize('where', [None, (DEFAULT_SOURCE, DEFAULT_TARGET), - (_QBXSourceStage1(DEFAULT_SOURCE), - _QBXSourceStage1(DEFAULT_TARGET)), - (_QBXSourceQuadStage2(DEFAULT_SOURCE), - _QBXSourceQuadStage2(DEFAULT_TARGET))]) + (QBXSourceStage1(DEFAULT_SOURCE), + QBXSourceStage1(DEFAULT_TARGET)), + (QBXSourceQuadStage2(DEFAULT_SOURCE), + QBXSourceQuadStage2(DEFAULT_TARGET))]) def test_build_matrix_where(ctx_factory, where): - pytest.skip("wip") ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -357,12 +356,12 @@ def test_build_matrix_where(ctx_factory, where): else: source_where, target_where = where - if isinstance(source_where, _QBXSourceQuadStage2): + if isinstance(source_where, QBXSourceQuadStage2): n = qbx.quad_stage2_density_discr.nnodes else: n = qbx.density_discr.nnodes - if isinstance(target_where, _QBXSourceQuadStage2): + if isinstance(target_where, QBXSourceQuadStage2): m = qbx.quad_stage2_density_discr.nnodes else: m = qbx.density_discr.nnodes -- GitLab From 72e0821d84e074692bf988ae45ed6e3c8e7c9e59 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 31 Jul 2018 15:07:22 -0500 Subject: [PATCH 214/268] matrix: add code to associate stage2 targets to expansion centers --- pytential/symbolic/mappers.py | 11 +++----- pytential/symbolic/matrix.py | 47 ++++++++++++++++++++++++----------- test/test_matrix.py | 44 ++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 45f3b84f..12b87894 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -399,19 +399,16 @@ class QBXPreprocessor(IdentityMapper): self.places = places def map_int_g(self, expr): - source = self.places[self.source_name] - target_discr = self.places[expr.target] - - from pytential.source import LayerPotentialSourceBase - if isinstance(target_discr, LayerPotentialSourceBase): - target_discr = target_discr.density_discr + from pytential.symbolic.execution import _get_discretization + source, source_discr = _get_discretization(self.places, self.source_name) + _, target_discr = _get_discretization(self.places, expr.target) if expr.qbx_forced_limit == 0: raise ValueError("qbx_forced_limit == 0 was a bad idea and " "is no longer supported. Use qbx_forced_limit == 'avg' " "to request two-sided averaging explicitly if needed.") - is_self = source.density_discr is target_discr + is_self = source_discr is target_discr expr = expr.copy( kernel=expr.kernel, diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index d6f15922..356dacc6 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -99,13 +99,26 @@ def _weights_and_area_elements(queue, source, where): return area * qweight -def _get_centers_on_side(queue, source, qbx_forced_limit): - pass - - -def _get_expansion_radii(queue, source): - pass - +def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_limit): + from pytential.qbx.utils import get_interleaved_centers + centers = get_interleaved_centers(queue, source) + radii = source._expansion_radii('nsources') + + from pytential.qbx.target_assoc import associate_targets_to_qbx_centers + code_container = source.target_association_code_container + assoc = associate_targets_to_qbx_centers( + source, + code_container.get_wrangler(queue), + [(target_discr, qbx_forced_limit)], + target_association_tolerance=1.0e-1) + + centers = [cl.array.take(c, assoc.target_to_center, queue=queue) + for c in centers] + radii = cl.array.take(radii, + (assoc.target_to_center.with_queue(queue) / 2.0).astype(np.int), + queue=queue) + + return centers, radii # }}} @@ -212,14 +225,19 @@ class MatrixBuilder(EvaluationMapperBase): return vecs_and_scalars def map_int_g(self, expr): - from pytential.symbolic.execution import _get_discretization if expr.source is sym.DEFAULT_SOURCE: where_source = sym.QBXSourceQuadStage2(expr.source) else: where_source = expr.source + if expr.target is sym.DEFAULT_TARGET: + where_target = sym.QBXSourceStage1(expr.target) + else: + where_target = expr.target + + from pytential.symbolic.execution import _get_discretization source, source_discr = _get_discretization(self.places, where_source) - _, target_discr = _get_discretization(self.places, expr.target) + _, target_discr = _get_discretization(self.places, where_target) rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -239,14 +257,15 @@ class MatrixBuilder(EvaluationMapperBase): mat_gen = LayerPotentialMatrixGenerator( self.queue.context, (local_expn,)) - from pytential.qbx.utils import get_centers_on_side assert abs(expr.qbx_forced_limit) > 0 + centers, radii = _get_centers_and_expansion_radii(self.queue, + source, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, - target_discr.nodes(), - source_discr.nodes(), - get_centers_on_side(source, expr.qbx_forced_limit), - expansion_radii=self.dep_source._expansion_radii("nsources"), + targets=target_discr.nodes(), + sources=source_discr.nodes(), + centers=centers, + expansion_radii=radii, **kernel_args) mat = mat.get() diff --git a/test/test_matrix.py b/test/test_matrix.py index e3cc0c8d..b266c2e8 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -336,7 +336,7 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): QBXSourceStage1(DEFAULT_TARGET)), (QBXSourceQuadStage2(DEFAULT_SOURCE), QBXSourceQuadStage2(DEFAULT_TARGET))]) -def test_build_matrix_where(ctx_factory, where): +def test_build_matrix_where(ctx_factory, where, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -345,8 +345,48 @@ def test_build_matrix_where(ctx_factory, where): clear_cache() from test_linalg_proxy import _build_qbx_discr - qbx = _build_qbx_discr(queue, target_order=7, ndim=2) + qbx = _build_qbx_discr(queue, nelements=8, target_order=2, ndim=2, + curve_f=partial(ellipse, 1.0)) op, u_sym, _ = _build_op(lpot_id=1, ndim=2) + qbx_forced_limit = -1 + + # associate quad_stage2 targets to centers + from pytential.qbx.target_assoc import associate_targets_to_qbx_centers + + code_container = qbx.target_association_code_container + target_discr = qbx.quad_stage2_density_discr + target_assoc = associate_targets_to_qbx_centers( + qbx, + code_container.get_wrangler(queue), + [(target_discr, qbx_forced_limit)], + target_association_tolerance=1.0e-1).get(queue) + target_assoc = target_assoc.target_to_center + + if qbx.ambient_dim == 2 and visualize: + import matplotlib.pyplot as pt + from pytential.qbx.utils import get_interleaved_centers + sources = qbx.density_discr.nodes().get(queue) + targets = target_discr.nodes().get(queue) + centers = get_interleaved_centers(queue, qbx) + centers = np.vstack(c.get(queue) for c in centers) + radii = qbx._expansion_radii('nsources').get(queue) + + for i in range(centers[0].size): + itgt = np.where(target_assoc == i)[0] + if not len(itgt): + continue + + pt.figure(figsize=(10, 8), dpi=300) + pt.plot(sources[0], sources[1], 'k'); + pt.plot(targets[0], targets[1], 'ko'); + + line = pt.plot(targets[0, itgt], targets[1, itgt], 'o') + c = pt.Circle([centers[0][i], centers[1][i]], radii[i // 2], + color=line[0].get_color(), alpha=0.5) + pt.gca().add_artist(c) + + pt.savefig('test_assoc_quad_targets_to_centers_{:05}.png'.format(i // 2)) + pt.close() from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) -- GitLab From bcebd37ec86531ba1cd58ffb7bf95a44d8040597 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 31 Jul 2018 15:35:29 -0500 Subject: [PATCH 215/268] matrix: port other matrix builders to different where types --- pytential/symbolic/matrix.py | 126 ++++++++++++++++------------------- test/test_matrix.py | 23 ++----- 2 files changed, 64 insertions(+), 85 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 356dacc6..4e6ded89 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -86,7 +86,7 @@ def _get_kernel_args(mapper, kernel, expr, source): def _weights_and_area_elements(queue, source, where): if isinstance(where, sym.QBXSourceQuadStage2): - return source.weights_and_area_elements() + return source.weights_and_area_elements().with_queue(queue) # NOTE: copied from `weights_and_area_elements`, but using the # discretization given by `where` and no interpolation @@ -225,19 +225,18 @@ class MatrixBuilder(EvaluationMapperBase): return vecs_and_scalars def map_int_g(self, expr): - if expr.source is sym.DEFAULT_SOURCE: - where_source = sym.QBXSourceQuadStage2(expr.source) - else: - where_source = expr.source + where_source = expr.source + if where_source is DEFAULT_SOURCE: + where_source = sym.QBXSourceQuadStage2(where_source) - if expr.target is sym.DEFAULT_TARGET: + where_target = expr.target + if where_target is DEFAULT_TARGET where_target = sym.QBXSourceStage1(expr.target) - else: - where_target = expr.target from pytential.symbolic.execution import _get_discretization source, source_discr = _get_discretization(self.places, where_source) _, target_discr = _get_discretization(self.places, where_target) + assert source_discr.nnodes >= target_discr.nnodes rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -332,11 +331,18 @@ class P2PMatrixBuilder(MatrixBuilder): self.exclude_self = exclude_self def map_int_g(self, expr): - source = self.places[expr.source] - target_discr = self.places[expr.target] + where_source = expr.source + if where_source is DEFAULT_SOURCE: + where_source = sym.QBXSourceQuadStage2(where_source) - if source.density_discr is not target_discr: - raise NotImplementedError() + where_target = expr.target + if where_target is DEFAULT_TARGET + where_target = sym.QBXSourceStage1(expr.target) + + from pytential.symbolic.execution import _get_discretization + source, source_discr = _get_discretization(self.places, where_source) + _, target_discr = _get_discretization(self.places, where_target) + assert source_discr.nnodes >= target_discr.nnodes rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -346,11 +352,8 @@ class P2PMatrixBuilder(MatrixBuilder): if len(rec_density.shape) != 2: raise NotImplementedError("layer potentials on non-variables") - try: - kernel = expr.kernel.inner_kernel - except AttributeError: - kernel = expr.kernel - kernel_args = _get_kernel_args(self, kernel, expr, source) + kernel_args = _get_kernel_args(self, + kernel.get_base_kernel(), expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) @@ -361,7 +364,7 @@ class P2PMatrixBuilder(MatrixBuilder): _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), - sources=source.density_discr.nodes(), + sources=source_discr.nodes(), **kernel_args) mat = mat.get() @@ -451,17 +454,6 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): self.dummy = MatrixBlockBuilderBase(queue, dep_expr, other_dep_exprs, dep_source, places, context, index_set) - @memoize_method - def _oversampled_index_set(self, resampler): - from pytential.linalg.proxy import partition_from_coarse - srcindices = partition_from_coarse(resampler, self.index_set) - - from sumpy.tools import MatrixBlockIndexRanges - ovsm_index_set = MatrixBlockIndexRanges(self.queue.context, - self.index_set.row, srcindices) - - return ovsm_index_set - def _map_dep_variable(self): tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) srcindices = self.index_set.col.indices.get(self.queue).reshape(1, -1) @@ -469,10 +461,19 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - source = self.places[expr.source] - target_discr = self.places[expr.target] + where_source = expr.source + if where_source is DEFAULT_SOURCE: + where_source = sym.QBXSourceQuadStage2(where_source) + + where_target = expr.target + if where_target is DEFAULT_TARGET + where_target = sym.QBXSourceQuadStage2(expr.target) + + from pytential.symbolic.execution import _get_discretization + source, source_discr = _get_discretization(self.places, where_source) + _, target_discr = _get_discretization(self.places, where_target) - if source.density_discr is not target_discr: + if source_discr is not target_discr: raise NotImplementedError() rec_density = self.dummy.rec(expr.density) @@ -483,9 +484,6 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): if len(rec_density.shape) != 2: raise NotImplementedError("layer potentials on non-variables") - resampler = source.direct_resampler - ovsm_index_set = self._oversampled_index_set(resampler) - kernel = expr.kernel kernel_args = _get_layer_potential_args(self, expr, source) @@ -498,35 +496,21 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): from pytential.qbx.utils import get_centers_on_side assert abs(expr.qbx_forced_limit) > 0 + centers, radii = _get_centers_and_expansion_radii(queue, + source, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, - target_discr.nodes(), - source.quad_stage2_density_discr.nodes(), - get_centers_on_side(source, expr.qbx_forced_limit), - expansion_radii=self.dep_source._expansion_radii("nsources"), - index_set=ovsm_index_set, + targets=target_discr.nodes(), + sources=source_discr.nodes(), + centers=centers, + expansion_radii=radii, + index_set=index_set, **kernel_args) - waa = source.weights_and_area_elements().with_queue(self.queue) - mat *= waa[ovsm_index_set.linear_col_indices] - - from sumpy.tools import MatrixBlockIndexRanges - ovsm_col_index = ovsm_index_set.get(self.queue) - ovsm_row_index = MatrixBlockIndexRanges(ovsm_col_index.cl_context, - ovsm_col_index.col, ovsm_col_index.row) - - mat = mat.get(self.queue) - resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) + waa = _get_weights_and_area_elements(queue, source, source_where) + mat *= waa[index_set.linear_col_indices] - rmat = np.empty(ovsm_index_set.nblocks, dtype=np.object) - for i in range(ovsm_index_set.nblocks): - mat_blk = ovsm_col_index.block_take(mat, i) - resample_blk = ovsm_row_index.take(resample_mat, i) - - rmat[i] = mat_blk.dot(resample_blk).reshape(-1) - mat = np.hstack(rmat) - - # TODO:: multiply with rec_density + # TODO: multiply with rec_density return mat @@ -548,10 +532,19 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - source = self.places[expr.source] - target_discr = self.places[expr.target] + where_source = expr.source + if where_source is DEFAULT_SOURCE: + where_source = sym.QBXSourceQuadStage2(where_source) + + where_target = expr.target + if where_target is DEFAULT_TARGET + where_target = sym.QBXSourceQuadStage2(expr.target) + + from pytential.symbolic.execution import _get_discretization + source, source_discr = _get_discretization(self.places, where_source) + _, target_discr = _get_discretization(self.places, where_target) - if source.density_discr is not target_discr: + if source_discr is not target_discr: raise NotImplementedError() rec_density = self.dummy.rec(expr.density) @@ -562,11 +555,8 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): if len(rec_density.shape) != 2: raise NotImplementedError("layer potentials on non-variables") - try: - kernel = expr.kernel.inner_kernel - except AttributeError: - kernel = expr.kernel - kernel_args = _get_kernel_args(self, kernel, expr, source) + kernel_args = _get_kernel_args(self, + kernel.get_base_kernel(), expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) @@ -577,7 +567,7 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), - sources=source.density_discr.nodes(), + sources=source_discr.nodes(), index_set=self.index_set, **kernel_args) mat = mat.get() diff --git a/test/test_matrix.py b/test/test_matrix.py index b266c2e8..f36614fd 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -330,8 +330,7 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): @pytest.mark.parametrize('where', - [None, - (DEFAULT_SOURCE, DEFAULT_TARGET), + [(DEFAULT_SOURCE, DEFAULT_TARGET), (QBXSourceStage1(DEFAULT_SOURCE), QBXSourceStage1(DEFAULT_TARGET)), (QBXSourceQuadStage2(DEFAULT_SOURCE), @@ -391,22 +390,12 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) - if where is None: - source_where, target_where = DEFAULT_SOURCE, DEFAULT_TARGET - else: - source_where, target_where = where - - if isinstance(source_where, QBXSourceQuadStage2): - n = qbx.quad_stage2_density_discr.nnodes - else: - n = qbx.density_discr.nnodes - - if isinstance(target_where, QBXSourceQuadStage2): - m = qbx.quad_stage2_density_discr.nnodes - else: - m = qbx.density_discr.nnodes + from pytential.symbolic.execution import _get_discretization + source_where, target_where = where + _, source_discr = _get_discretization(qbx, source_where) + _, target_discr = _get_discretization(qbx, target_where) - assert mat.shape == (m, n) + assert mat.shape == (target_discr.nnodes, source_discr.nnodes) if __name__ == "__main__": -- GitLab From c7dceec90ce02416cef542cdd3f22a1fc3f29e62 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 31 Jul 2018 16:17:27 -0500 Subject: [PATCH 216/268] matrix: fix some tests and bugs --- pytential/symbolic/matrix.py | 54 ++++++++++++++++++++---------------- test/test_matrix.py | 13 +++++---- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 4e6ded89..80f23296 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -84,7 +84,7 @@ def _get_kernel_args(mapper, kernel, expr, source): return kernel_args -def _weights_and_area_elements(queue, source, where): +def _get_weights_and_area_elements(queue, source, where): if isinstance(where, sym.QBXSourceQuadStage2): return source.weights_and_area_elements().with_queue(queue) @@ -105,12 +105,17 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim radii = source._expansion_radii('nsources') from pytential.qbx.target_assoc import associate_targets_to_qbx_centers + if source.density_discr is target_discr: + target_association_tolerance = 1.0e-10 + else: + target_association_tolerance = 1.0e-1 + code_container = source.target_association_code_container assoc = associate_targets_to_qbx_centers( source, code_container.get_wrangler(queue), [(target_discr, qbx_forced_limit)], - target_association_tolerance=1.0e-1) + target_association_tolerance=target_association_tolerance) centers = [cl.array.take(c, assoc.target_to_center, queue=queue) for c in centers] @@ -226,11 +231,11 @@ class MatrixBuilder(EvaluationMapperBase): def map_int_g(self, expr): where_source = expr.source - if where_source is DEFAULT_SOURCE: + if where_source is sym.DEFAULT_SOURCE: where_source = sym.QBXSourceQuadStage2(where_source) where_target = expr.target - if where_target is DEFAULT_TARGET + if where_target is sym.DEFAULT_TARGET: where_target = sym.QBXSourceStage1(expr.target) from pytential.symbolic.execution import _get_discretization @@ -268,7 +273,7 @@ class MatrixBuilder(EvaluationMapperBase): **kernel_args) mat = mat.get() - waa = _weights_and_area_elements(self.queue, source, where_source) + waa = _get_weights_and_area_elements(self.queue, source, where_source) mat[:, :] *= waa.get(self.queue) if target_discr.nnodes != source_discr.nnodes: @@ -332,11 +337,11 @@ class P2PMatrixBuilder(MatrixBuilder): def map_int_g(self, expr): where_source = expr.source - if where_source is DEFAULT_SOURCE: - where_source = sym.QBXSourceQuadStage2(where_source) + if where_source is sym.DEFAULT_SOURCE: + where_source = sym.QBXSourceStage1(where_source) where_target = expr.target - if where_target is DEFAULT_TARGET + if where_target is sym.DEFAULT_TARGET: where_target = sym.QBXSourceStage1(expr.target) from pytential.symbolic.execution import _get_discretization @@ -352,8 +357,8 @@ class P2PMatrixBuilder(MatrixBuilder): if len(rec_density.shape) != 2: raise NotImplementedError("layer potentials on non-variables") - kernel_args = _get_kernel_args(self, - kernel.get_base_kernel(), expr, source) + kernel = expr.kernel.get_base_kernel() + kernel_args = _get_kernel_args(self, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) @@ -462,12 +467,12 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): def map_int_g(self, expr): where_source = expr.source - if where_source is DEFAULT_SOURCE: - where_source = sym.QBXSourceQuadStage2(where_source) + if where_source is sym.DEFAULT_SOURCE: + where_source = sym.QBXSourceStage1(where_source) where_target = expr.target - if where_target is DEFAULT_TARGET - where_target = sym.QBXSourceQuadStage2(expr.target) + if where_target is sym.DEFAULT_TARGET: + where_target = sym.QBXSourceStage1(expr.target) from pytential.symbolic.execution import _get_discretization source, source_discr = _get_discretization(self.places, where_source) @@ -496,7 +501,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): from pytential.qbx.utils import get_centers_on_side assert abs(expr.qbx_forced_limit) > 0 - centers, radii = _get_centers_and_expansion_radii(queue, + centers, radii = _get_centers_and_expansion_radii(self.queue, source, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, @@ -504,11 +509,12 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): sources=source_discr.nodes(), centers=centers, expansion_radii=radii, - index_set=index_set, + index_set=self.index_set, **kernel_args) - waa = _get_weights_and_area_elements(queue, source, source_where) - mat *= waa[index_set.linear_col_indices] + waa = _get_weights_and_area_elements(self.queue, source, where_source) + mat *= waa[self.index_set.linear_col_indices] + mat = mat.get(self.queue) # TODO: multiply with rec_density @@ -533,12 +539,12 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): def map_int_g(self, expr): where_source = expr.source - if where_source is DEFAULT_SOURCE: - where_source = sym.QBXSourceQuadStage2(where_source) + if where_source is sym.DEFAULT_SOURCE: + where_source = sym.QBXSourceStage1(where_source) where_target = expr.target - if where_target is DEFAULT_TARGET - where_target = sym.QBXSourceQuadStage2(expr.target) + if where_target is sym.DEFAULT_TARGET: + where_target = sym.QBXSourceStage1(expr.target) from pytential.symbolic.execution import _get_discretization source, source_discr = _get_discretization(self.places, where_source) @@ -555,8 +561,8 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): if len(rec_density.shape) != 2: raise NotImplementedError("layer potentials on non-variables") - kernel_args = _get_kernel_args(self, - kernel.get_base_kernel(), expr, source) + kernel = expr.kernel.get_base_kernel() + kernel_args = _get_kernel_args(self, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) diff --git a/test/test_matrix.py b/test/test_matrix.py index f36614fd..1629ab5d 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -273,8 +273,10 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): srcindices = _build_block_index(qbx.density_discr) from pytential.symbolic.execution import prepare_places, prepare_expr - places = prepare_places(qbx) - expr = prepare_expr(places, op) + where = (QBXSourceStage1(DEFAULT_SOURCE), + QBXSourceStage1(DEFAULT_TARGET)) + places = prepare_places(qbx, auto_where=where) + expr = prepare_expr(places, op, auto_where=where) from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) @@ -283,18 +285,19 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): mbuilder = NearFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[DEFAULT_SOURCE], + dep_source=places[where[0]], places=places, context={}, index_set=index_set) blk = mbuilder(expr) + from pytential.symbolic.execution import _get_discretization from pytential.symbolic.matrix import MatrixBuilder mbuilder = MatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[DEFAULT_SOURCE], - dep_discr=places[DEFAULT_SOURCE].density_discr, + dep_source=places[where[0]], + dep_discr=_get_discretization(places, where[0])[1], places=places, context={}) mat = mbuilder(expr) -- GitLab From 5c541f642e9fcccc186eecee924473679342ae5d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 31 Jul 2018 18:34:17 -0500 Subject: [PATCH 217/268] matrix: flake8 fixes --- pytential/symbolic/matrix.py | 3 --- test/test_matrix.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 80f23296..200e1e99 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -29,8 +29,6 @@ import pyopencl.array # noqa import six from six.moves import intern -from pytools import memoize_method - from pytential.symbolic.mappers import EvaluationMapperBase import pytential.symbolic.primitives as sym from pytential.symbolic.execution import bind @@ -499,7 +497,6 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): mat_gen = LayerPotentialMatrixBlockGenerator( self.queue.context, (local_expn,)) - from pytential.qbx.utils import get_centers_on_side assert abs(expr.qbx_forced_limit) > 0 centers, radii = _get_centers_and_expansion_radii(self.queue, source, target_discr, expr.qbx_forced_limit) diff --git a/test/test_matrix.py b/test/test_matrix.py index 1629ab5d..842029b1 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -379,8 +379,8 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): continue pt.figure(figsize=(10, 8), dpi=300) - pt.plot(sources[0], sources[1], 'k'); - pt.plot(targets[0], targets[1], 'ko'); + pt.plot(sources[0], sources[1], 'k') + pt.plot(targets[0], targets[1], 'ko') line = pt.plot(targets[0, itgt], targets[1, itgt], 'o') c = pt.Circle([centers[0][i], centers[1][i]], radii[i // 2], -- GitLab From a39fda90acd53cdab377baf8620ddf149de93f68 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 31 Jul 2018 19:52:28 -0500 Subject: [PATCH 218/268] execution: add some docs --- pytential/symbolic/execution.py | 77 ++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 2ec5b05f..17a09fd7 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -284,22 +284,42 @@ class MatVecOp: # {{{ expression prep def _domains_default(nresults, places, domains, default_val): + """ + :arg nresults: number of results. + :arg places: result of :func:`prepare_places`. + :arg domains: recommended domains. + :arg default_val: default value for domains which are not provided. + + :return: a list of domains for each result. If domains is `None`, each + element in the list is *default_val*. If *domains* is not a list + of domains, each element in the resulting list is *domains*. Otherwise, + *domains* is returned as is. + """ if domains is None: if default_val not in places: raise RuntimeError("'domains is None' requires " "default domain to be defined") dom_name = default_val return nresults * [dom_name] - elif not isinstance(domains, (list, tuple)): dom_name = domains return nresults * [dom_name] - else: + assert len(domains) == nresults return domains def _where_default(places, auto_where=None): + """ + :arg places: result of :func:`prepare_places`. + :arg auto_where: identifiers for source and/or target locations. If `None`, + `where` attributes are automatically found. + + :return: if *auto_where* is provided, it is returned as is. If *places* + was obtained from :func:`prepare_places`, the default is given by its + keys. Otherwise, a tuple of `(DEFAULT_SOURCE, DEFAULT_TARGET)` is + returned. + """ if auto_where is None: if not isinstance(places, dict): return DEFAULT_SOURCE, DEFAULT_TARGET @@ -311,6 +331,18 @@ def _where_default(places, auto_where=None): def prepare_places(places, auto_where=None): + """ + :arg places: a mapping of symbolic names to + :class:`~meshmode.discretization.Discretization` objects or a subclass + of :class:`~pytential.target.TargetBase`. + :arg auto_where: identifiers for source and/or target locations. If `None`, + `where` attributes are automatically found. + + :return: a mapping of symbolic names, same as the input if it was already + such a mapping. If not, a mapping is constructed using the values + of *auto_where*, if provided, as keys and appropriate discretization + objects as values. + """ from meshmode.discretization import Discretization from pytential.source import LayerPotentialSourceBase from pytential.target import TargetBase @@ -351,9 +383,13 @@ def prepare_places(places, auto_where=None): def prepare_expr(places, expr, auto_where=None): """ - :arg places: result of :func:`prepare_places` - :arg auto_where: For simple source-to-self or source-to-target - evaluations, find 'where' attributes automatically. + :arg places: result of :func:`prepare_places`. + :arg expr: an array of symbolic expressions. + :arg auto_where: identifiers for source and/or target locations. If `None`, + `where` attributes are automatically found. + + :return: processed symbolic expressions, tagger with the given `where` + identifiers. """ from pytential.source import LayerPotentialSourceBase @@ -375,6 +411,25 @@ def prepare_expr(places, expr, auto_where=None): def prepare_expression(places, exprs, input_exprs, domains=None, auto_where=None): + """ + :arg places: a mapping of symbolic names to + :class:`~meshmode.discretization.Discretization` objects or a subclass + of :class:`~pytential.target.TargetBase`. + :arg exprs: an array or a single symbolic expression. + :arg input_exprs: an array or a single symbolic expression that is taken + as input by *exprs*. + :arg domains: a list of discretization identifiers, indicating the domains + on which the inputs live. If given, each element of the list must be + a key in mapping *places* and correspond to an *auto_where* + identifier. + :arg auto_where: identifiers for source and/or target locations. If `None`, + `where` attributes are automatically found. + + :return: a tuple of `(places, exprs, input_exprs, domains)`, where each + element was appropriately processed so that it can be used by + :class:`BoundExpression`, :func:`build_matrix`, etc. + """ + auto_where = _where_default(places, auto_where) places = prepare_places(places, auto_where=auto_where) exprs = prepare_expr(places, exprs, auto_where=auto_where) @@ -398,6 +453,18 @@ def prepare_expression(places, exprs, input_exprs, # {{{ bound expression def _get_discretization(places, where, default_source=QBXSourceStage1): + """ + :arg places: a mapping of symbolic names to + :class:`~meshmode.discretization.Discretization` objects or a subclass + of :class:`~pytential.target.TargetBase`. + :arg where: identifier for source or target locations. + :arg default_source: specific source location in case `where` is + :class:`pytential.symbolic.primitives.DEFAULT_SOURCE` or + :class:`pytential.symbolic.primitives.DEFAULT_TARGET`. + + :return: a :class:`~meshmode.discretization.Discretization`, from + *places* corresponding to *where*. + """ from pytential.source import LayerPotentialSourceBase if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: -- GitLab From a2d1c9773a719d8dea80cea24fdc0bb98e1d087e Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 3 Aug 2018 19:44:14 -0500 Subject: [PATCH 219/268] execution: first try at wrapping places handling in a class --- pytential/symbolic/execution.py | 64 ++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 17a09fd7..d9f64ee8 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -452,6 +452,64 @@ def prepare_expression(places, exprs, input_exprs, # {{{ bound expression +class BindingLocation(object): + def __init__(self, places, auto_where=None): + from meshmode.discretization import Discretization + from pytential.source import LayerPotentialSourceBase, PotentialSource + from pytential.target import TargetBase + + if auto_where is None: + auto_where = DEFAULT_SOURCE, DEFAULT_TARGET + where_source, where_target = auto_where + + def get_lpot_discr(where): + if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: + where = QBXSourceStage1(where) + + if isinstance(where, QBXSourceStage1): + return places.density_discr + if isinstance(where, QBXSourceStage2): + return places.stage2_density_discr + if isinstance(where, QBXSourceQuadStage2): + return places.quad_stage2_density_discr + + return None + + self.places = {} + if isinstance(places, LayerPotentialSourceBase): + self[where_source] = get_lpot_discr(places, where_source) + self[where_target] = get_lpot_discr(places, where_target) + elif isinstance(places, (Discretization, TargetBase)): + self[where_target] = places + elif isinstance(places, tuple): + source_discr, target_discr = places + self[where_source] = source_discr + self[where_target] = target_discr + else: + for key, value in six.iteritems(places): + self[key] = value + + for p in six.itervalues(self.places): + if not isinstance(p, (PotentialSource, TargetBase, Discretization)): + raise TypeError("Must pass discretization, targets or " + "layer potential sources as 'places'") + + self.source = None + if isinstance(places, LayerPotentialSourceBase): + self.source = places + + self.caches = {} + + def __getitem__(self, where): + return self.places[where] + + def __setitem__(self, where, discr): + self.places[where] = discr + + def get_cache(self, name): + return self.caches.setdefault(name, {}) + + def _get_discretization(places, where, default_source=QBXSourceStage1): """ :arg places: a mapping of symbolic names to @@ -491,12 +549,10 @@ def _get_discretization(places, where, default_source=QBXSourceStage1): return lpot, discr -class BoundExpression: - def __init__(self, places, sym_op_expr, sym_op_args=None): +class BoundExpression(object): + def __init__(self, places, sym_op_expr): self.places = places self.sym_op_expr = sym_op_expr - self.sym_op_args = sym_op_args - self.caches = {} from pytential.symbolic.compiler import OperatorCompiler -- GitLab From 04092e6904d1ee6a882db52eb871db4ae856466e Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 3 Aug 2018 19:50:35 -0500 Subject: [PATCH 220/268] matrix: add a note about target association tolerance --- pytential/symbolic/matrix.py | 62 ++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 200e1e99..63691696 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -84,42 +84,50 @@ def _get_kernel_args(mapper, kernel, expr, source): def _get_weights_and_area_elements(queue, source, where): if isinstance(where, sym.QBXSourceQuadStage2): - return source.weights_and_area_elements().with_queue(queue) - - # NOTE: copied from `weights_and_area_elements`, but using the - # discretization given by `where` and no interpolation - from pytential.symbolic.execution import _get_discretization - discr = _get_discretization(source, where) + waa = source.weights_and_area_elements().with_queue(queue) + else: + # NOTE: copied from `weights_and_area_elements`, but using the + # discretization given by `where` and no interpolation + from pytential.symbolic.execution import _get_discretization + discr = _get_discretization(source, where) - area = bind(discr, sym.area_element(source.ambient_dim, source.dim))(queue) - qweight = bind(discr, sym.QWeight())(queue) + area = bind(discr, sym.area_element(source.ambient_dim, source.dim))(queue) + qweight = bind(discr, sym.QWeight())(queue) + waa = area * qweight - return area * qweight + return waa def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_limit): - from pytential.qbx.utils import get_interleaved_centers - centers = get_interleaved_centers(queue, source) - radii = source._expansion_radii('nsources') - - from pytential.qbx.target_assoc import associate_targets_to_qbx_centers + # NOTE: skip expensive target association if source.density_discr is target_discr: - target_association_tolerance = 1.0e-10 + from pytential.qbx.utils import get_centers_on_side + centers = get_centers_on_side(source, qbx_forced_limit) + radii = source._expansion_radii('nsources') else: + from pytential.qbx.utils import get_interleaved_centers + centers = get_interleaved_centers(queue, source) + radii = source._expansion_radii('nsources') + + # NOTE: using a very small tolerance to make sure all the stage2 + # targets are associated to a center. We can't use the user provided + # source.target_association_tolerance here because it will likely be + # way too small. target_association_tolerance = 1.0e-1 - code_container = source.target_association_code_container - assoc = associate_targets_to_qbx_centers( - source, - code_container.get_wrangler(queue), - [(target_discr, qbx_forced_limit)], - target_association_tolerance=target_association_tolerance) - - centers = [cl.array.take(c, assoc.target_to_center, queue=queue) - for c in centers] - radii = cl.array.take(radii, - (assoc.target_to_center.with_queue(queue) / 2.0).astype(np.int), - queue=queue) + from pytential.qbx.target_assoc import associate_targets_to_qbx_centers + code_container = source.target_association_code_container + assoc = associate_targets_to_qbx_centers( + source, + code_container.get_wrangler(queue), + [(target_discr, qbx_forced_limit)], + target_association_tolerance=target_association_tolerance) + + centers = [cl.array.take(c, assoc.target_to_center, queue=queue) + for c in centers] + radii = cl.array.take(radii, + (assoc.target_to_center.with_queue(queue) / 2.0).astype(np.int), + queue=queue) return centers, radii -- GitLab From 05149897384bb16df446ac91f5781037556b5281 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 5 Aug 2018 20:02:01 -0500 Subject: [PATCH 221/268] execution: use the new places class everywhere --- pytential/symbolic/compiler.py | 4 +- pytential/symbolic/execution.py | 291 ++++++++++---------------------- pytential/symbolic/mappers.py | 5 +- pytential/symbolic/matrix.py | 124 +++++--------- test/test_matrix.py | 63 ++++--- 5 files changed, 175 insertions(+), 312 deletions(-) diff --git a/pytential/symbolic/compiler.py b/pytential/symbolic/compiler.py index 17d23ebf..ecb1fdca 100644 --- a/pytential/symbolic/compiler.py +++ b/pytential/symbolic/compiler.py @@ -224,7 +224,7 @@ class ComputePotentialInstruction(Instruction): ", ".join(args), "\n ".join(lines)) def get_exec_function(self, exec_mapper): - source = exec_mapper.bound_expr.places[self.source] + source = exec_mapper.bound_expr.places.lpot return source.exec_compute_potential_insn def __hash__(self): @@ -444,7 +444,7 @@ class OperatorCompiler(IdentityMapper): def op_group_features(self, expr): from pytential.symbolic.primitives import hashable_kernel_args return ( - self.places[expr.source].op_group_features(expr) + self.places.lpot.op_group_features(expr) + hashable_kernel_args(expr.kernel_arguments)) @memoize_method diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index d9f64ee8..0217be3d 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -283,7 +283,7 @@ class MatVecOp: # {{{ expression prep -def _domains_default(nresults, places, domains, default_val): +def _prepare_domains(nresults, places, domains, default_val): """ :arg nresults: number of results. :arg places: result of :func:`prepare_places`. @@ -309,79 +309,7 @@ def _domains_default(nresults, places, domains, default_val): return domains -def _where_default(places, auto_where=None): - """ - :arg places: result of :func:`prepare_places`. - :arg auto_where: identifiers for source and/or target locations. If `None`, - `where` attributes are automatically found. - - :return: if *auto_where* is provided, it is returned as is. If *places* - was obtained from :func:`prepare_places`, the default is given by its - keys. Otherwise, a tuple of `(DEFAULT_SOURCE, DEFAULT_TARGET)` is - returned. - """ - if auto_where is None: - if not isinstance(places, dict): - return DEFAULT_SOURCE, DEFAULT_TARGET - if DEFAULT_TARGET in places: - return DEFAULT_SOURCE, DEFAULT_TARGET - return tuple(places.keys()) - - return auto_where - - -def prepare_places(places, auto_where=None): - """ - :arg places: a mapping of symbolic names to - :class:`~meshmode.discretization.Discretization` objects or a subclass - of :class:`~pytential.target.TargetBase`. - :arg auto_where: identifiers for source and/or target locations. If `None`, - `where` attributes are automatically found. - - :return: a mapping of symbolic names, same as the input if it was already - such a mapping. If not, a mapping is constructed using the values - of *auto_where*, if provided, as keys and appropriate discretization - objects as values. - """ - from meshmode.discretization import Discretization - from pytential.source import LayerPotentialSourceBase - from pytential.target import TargetBase - - where_source, where_target = _where_default(places, auto_where=auto_where) - if isinstance(places, LayerPotentialSourceBase): - _, target_discr = _get_discretization(places, where_target) - places = { - where_source: places, - where_target: target_discr, - } - elif isinstance(places, (Discretization, TargetBase)): - places = { - where_target: places, - } - - elif isinstance(places, tuple): - source_discr, target_discr = places - places = { - where_source: source_discr, - where_target: target_discr, - } - del source_discr - del target_discr - - def cast_to_place(discr): - from pytential.target import TargetBase - from pytential.source import PotentialSource - if not isinstance(discr, (Discretization, TargetBase, PotentialSource)): - raise TypeError("must pass discretizations, " - "layer potential sources or targets as 'places'") - return discr - - return dict( - (key, cast_to_place(value)) - for key, value in six.iteritems(places)) - - -def prepare_expr(places, expr, auto_where=None): +def _prepare_expr(places, expr): """ :arg places: result of :func:`prepare_places`. :arg expr: an array of symbolic expressions. @@ -396,57 +324,13 @@ def prepare_expr(places, expr, auto_where=None): from pytential.symbolic.mappers import ( ToTargetTagger, DerivativeBinder) - auto_where = _where_default(places, auto_where=auto_where) - if auto_where: - expr = ToTargetTagger(*auto_where)(expr) - + expr = ToTargetTagger(*places.where)(expr) expr = DerivativeBinder()(expr) - - for name, place in six.iteritems(places): - if isinstance(place, LayerPotentialSourceBase): - expr = place.preprocess_optemplate(name, places, expr) + if isinstance(places.lpot, LayerPotentialSourceBase): + expr = places.lpot.preprocess_optemplate(places.source, places, expr) return expr - -def prepare_expression(places, exprs, input_exprs, - domains=None, auto_where=None): - """ - :arg places: a mapping of symbolic names to - :class:`~meshmode.discretization.Discretization` objects or a subclass - of :class:`~pytential.target.TargetBase`. - :arg exprs: an array or a single symbolic expression. - :arg input_exprs: an array or a single symbolic expression that is taken - as input by *exprs*. - :arg domains: a list of discretization identifiers, indicating the domains - on which the inputs live. If given, each element of the list must be - a key in mapping *places* and correspond to an *auto_where* - identifier. - :arg auto_where: identifiers for source and/or target locations. If `None`, - `where` attributes are automatically found. - - :return: a tuple of `(places, exprs, input_exprs, domains)`, where each - element was appropriately processed so that it can be used by - :class:`BoundExpression`, :func:`build_matrix`, etc. - """ - - auto_where = _where_default(places, auto_where) - places = prepare_places(places, auto_where=auto_where) - exprs = prepare_expr(places, exprs, auto_where=auto_where) - - from pytools.obj_array import is_obj_array, make_obj_array - if not is_obj_array(exprs): - exprs = make_obj_array([exprs]) - try: - input_exprs = list(input_exprs) - except TypeError: - # not iterable, wrap in a list - input_exprs = [input_exprs] - - domains = _domains_default(len(input_exprs), places, domains, auto_where[0]) - - return places, exprs, input_exprs, domains - # }}} @@ -460,93 +344,91 @@ class BindingLocation(object): if auto_where is None: auto_where = DEFAULT_SOURCE, DEFAULT_TARGET - where_source, where_target = auto_where - - def get_lpot_discr(where): - if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: - where = QBXSourceStage1(where) - - if isinstance(where, QBXSourceStage1): - return places.density_discr - if isinstance(where, QBXSourceStage2): - return places.stage2_density_discr - if isinstance(where, QBXSourceQuadStage2): - return places.quad_stage2_density_discr - - return None + self.where = auto_where + self.lpot = None self.places = {} if isinstance(places, LayerPotentialSourceBase): - self[where_source] = get_lpot_discr(places, where_source) - self[where_target] = get_lpot_discr(places, where_target) + self.lpot = places + self.places[self.source] = self[self.source] + self.places[self.target] = self[self.target] elif isinstance(places, (Discretization, TargetBase)): - self[where_target] = places + self.places[self.target] = places elif isinstance(places, tuple): source_discr, target_discr = places - self[where_source] = source_discr - self[where_target] = target_discr + if isinstance(source_discr, PotentialSource): + self.lpot = source_discr + if isinstance(source_discr, LayerPotentialSourceBase): + source_discr = self[self.source] + + self.places[self.source] = source_discr + self.places[self.target] = target_discr else: for key, value in six.iteritems(places): - self[key] = value + self.places[key] = value for p in six.itervalues(self.places): if not isinstance(p, (PotentialSource, TargetBase, Discretization)): raise TypeError("Must pass discretization, targets or " "layer potential sources as 'places'") - self.source = None - if isinstance(places, LayerPotentialSourceBase): - self.source = places - self.caches = {} + @property + def source(self): + return self.where[0] + + @property + def target(self): + return self.where[1] + + def _get_lpot_discretization(self, where): + from pytential.source import LayerPotentialSourceBase + if not isinstance(self.lpot, LayerPotentialSourceBase): + return None + + if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: + where = QBXSourceStage1(where) + + if isinstance(where, QBXSourceStage1): + return self.lpot.density_discr + if isinstance(where, QBXSourceStage2): + return self.lpot.stage2_density_discr + if isinstance(where, QBXSourceQuadStage2): + return self.lpot.quad_stage2_density_discr + + return None + def __getitem__(self, where): - return self.places[where] + if where in self.places: + discr = self.places[where] + else: + discr = self._get_lpot_discretization(where) - def __setitem__(self, where, discr): - self.places[where] = discr + if discr is None: + from pytential.symbolic.mappers import stringify_where + raise KeyError("Identifier '{}' not in places".format( + stringify_where(where))) - def get_cache(self, name): - return self.caches.setdefault(name, {}) + return discr + def __iter__(self): + return iter(self.places.keys()) -def _get_discretization(places, where, default_source=QBXSourceStage1): - """ - :arg places: a mapping of symbolic names to - :class:`~meshmode.discretization.Discretization` objects or a subclass - of :class:`~pytential.target.TargetBase`. - :arg where: identifier for source or target locations. - :arg default_source: specific source location in case `where` is - :class:`pytential.symbolic.primitives.DEFAULT_SOURCE` or - :class:`pytential.symbolic.primitives.DEFAULT_TARGET`. - - :return: a :class:`~meshmode.discretization.Discretization`, from - *places* corresponding to *where*. - """ - from pytential.source import LayerPotentialSourceBase + def keys(self): + return self.places.keys() - if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: - where = default_source(where) + def values(self): + return self.places.values() - if isinstance(places, LayerPotentialSourceBase): - lpot = places - else: - try: - lpot = places[where.where] - except KeyError: - lpot = places[where] - is_lpot = isinstance(lpot, LayerPotentialSourceBase) - - if isinstance(where, QBXSourceStage1): - discr = lpot.density_discr if is_lpot else lpot - elif isinstance(where, QBXSourceStage2): - discr = lpot.stage2_density_discr if is_lpot else lpot - elif isinstance(where, QBXSourceQuadStage2): - discr = lpot.quad_stage2_density_discr if is_lpot else lpot - else: - raise ValueError("Unknown 'where': {}".format(type(where))) + def items(self): + return self.places.items() - return lpot, discr + def __hash__(self): + return hash(tuple(self.values())) + + def get_cache(self, name): + return self.caches.setdefault(name, {}) class BoundExpression(object): @@ -559,11 +441,10 @@ class BoundExpression(object): self.code = OperatorCompiler(self.places)(sym_op_expr) def get_cache(self, name): - return self.caches.setdefault(name, {}) + return self.places.get_cache(name) def get_discretization(self, where): - _, discr = _get_discretization(self.places, where) - return discr + return self.places[where] def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): """ @@ -581,7 +462,7 @@ class BoundExpression(object): else: nresults = 1 - domains = _domains_default(nresults, self.places, domains, + domains = _prepare_domains(nresults, self.places, domains, DEFAULT_TARGET) total_dofs = 0 @@ -617,8 +498,9 @@ def bind(places, expr, auto_where=None): evaluations, find 'where' attributes automatically. """ - places = prepare_places(places) - expr = prepare_expr(places, expr, auto_where=auto_where) + places = BindingLocation(places, auto_where=auto_where) + expr = _prepare_expr(places, expr) + return BoundExpression(places, expr) # }}} @@ -648,34 +530,36 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, evaluations, find 'where' attributes automatically. """ - from pytools import single_valued - from pytential.symbolic.matrix import MatrixBuilder, is_zero - if context is None: context = {} - auto_where = _where_default(places, auto_where=auto_where) - places, exprs, input_exprs, domains = \ - prepare_expression(places, exprs, input_exprs, - domains=domains, - auto_where=auto_where) + from pytools.obj_array import is_obj_array, make_obj_array + places = BindingLocation(places, auto_where=auto_where) + exprs = _prepare_expr(places, exprs) + if not is_obj_array(exprs): + exprs = make_obj_array([exprs]) + try: + input_exprs = list(input_exprs) + except TypeError: + # not iterable, wrap in a list + input_exprs = [input_exprs] + + domains = _prepare_domains(len(input_exprs), places, domains, places.source) + + from pytential.symbolic.matrix import MatrixBuilder, is_zero nblock_rows = len(exprs) nblock_columns = len(input_exprs) blocks = np.zeros((nblock_rows, nblock_columns), dtype=np.object) dtypes = [] for ibcol in range(nblock_columns): - dep_source, dep_discr = \ - _get_discretization(places, domains[ibcol]) - mbuilder = MatrixBuilder( queue, dep_expr=input_exprs[ibcol], other_dep_exprs=(input_exprs[:ibcol] + input_exprs[ibcol + 1:]), - dep_source=dep_source, - dep_discr=dep_discr, + dep_discr=places[domains[ibcol]], places=places, context=context) @@ -687,6 +571,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, if isinstance(block, np.ndarray): dtypes.append(block.dtype) + from pytools import single_valued block_row_counts = [ single_valued( blocks[ibrow, ibcol].shape[0] diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index 12b87894..a8696efe 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -399,9 +399,8 @@ class QBXPreprocessor(IdentityMapper): self.places = places def map_int_g(self, expr): - from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, self.source_name) - _, target_discr = _get_discretization(self.places, expr.target) + source_discr = self.places[self.source_name] + target_discr = self.places[expr.target] if expr.qbx_forced_limit == 0: raise ValueError("qbx_forced_limit == 0 was a bad idea and " diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 63691696..8c9a354e 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -82,25 +82,23 @@ def _get_kernel_args(mapper, kernel, expr, source): return kernel_args -def _get_weights_and_area_elements(queue, source, where): - if isinstance(where, sym.QBXSourceQuadStage2): +def _get_weights_and_area_elements(queue, source, source_discr): + if source.quad_stage2_density_discr is source_discr: waa = source.weights_and_area_elements().with_queue(queue) else: # NOTE: copied from `weights_and_area_elements`, but using the # discretization given by `where` and no interpolation - from pytential.symbolic.execution import _get_discretization - discr = _get_discretization(source, where) - - area = bind(discr, sym.area_element(source.ambient_dim, source.dim))(queue) - qweight = bind(discr, sym.QWeight())(queue) + area = bind(source_discr, + sym.area_element(source.ambient_dim, source.dim))(queue) + qweight = bind(source_discr, sym.QWeight())(queue) waa = area * qweight return waa def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_limit): - # NOTE: skip expensive target association if source.density_discr is target_discr: + # NOTE: skip expensive target association from pytential.qbx.utils import get_centers_on_side centers = get_centers_on_side(source, qbx_forced_limit) radii = source._expansion_radii('nsources') @@ -140,14 +138,13 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim # We'll cheat and build the matrix on the host. class MatrixBuilder(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, places, context): super(MatrixBuilder, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs - self.dep_source = dep_source self.dep_discr = dep_discr self.places = places @@ -236,18 +233,13 @@ class MatrixBuilder(EvaluationMapperBase): return vecs_and_scalars def map_int_g(self, expr): + # TODO: should this go into QBXPreprocessor / LocationTagger where_source = expr.source if where_source is sym.DEFAULT_SOURCE: - where_source = sym.QBXSourceQuadStage2(where_source) - - where_target = expr.target - if where_target is sym.DEFAULT_TARGET: - where_target = sym.QBXSourceStage1(expr.target) + where_source = sym.QBXSourceQuadStage2(expr.source) - from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, where_source) - _, target_discr = _get_discretization(self.places, where_target) - assert source_discr.nnodes >= target_discr.nnodes + source_discr = self.places[where_source] + target_discr = self.places[expr.target] rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -258,10 +250,10 @@ class MatrixBuilder(EvaluationMapperBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - kernel_args = _get_layer_potential_args(self, expr, source) + kernel_args = _get_layer_potential_args(self, expr, self.places.lpot) from sumpy.expansion.local import LineTaylorLocalExpansion - local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) + local_expn = LineTaylorLocalExpansion(kernel, self.places.lpot.qbx_order) from sumpy.qbx import LayerPotentialMatrixGenerator mat_gen = LayerPotentialMatrixGenerator( @@ -269,7 +261,7 @@ class MatrixBuilder(EvaluationMapperBase): assert abs(expr.qbx_forced_limit) > 0 centers, radii = _get_centers_and_expansion_radii(self.queue, - source, target_discr, expr.qbx_forced_limit) + self.places.lpot, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), @@ -279,11 +271,13 @@ class MatrixBuilder(EvaluationMapperBase): **kernel_args) mat = mat.get() - waa = _get_weights_and_area_elements(self.queue, source, where_source) + waa = _get_weights_and_area_elements(self.queue, self.places.lpot, source_discr) mat[:, :] *= waa.get(self.queue) if target_discr.nnodes != source_discr.nnodes: - resampler = source.direct_resampler + assert target_discr.nnodes < source_discr.nnodes + + resampler = self.places.lpot.direct_resampler resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) mat = mat.dot(resample_mat) @@ -321,7 +315,7 @@ class MatrixBuilder(EvaluationMapperBase): rec_arg = cl.array.to_device(self.queue, rec_arg) op = expr.function(sym.var("u")) - result = bind(self.dep_source, op)(self.queue, u=rec_arg) + result = bind(self.places.lpot, op)(self.queue, u=rec_arg) if isinstance(result, cl.array.Array): result = result.get() @@ -334,26 +328,16 @@ class MatrixBuilder(EvaluationMapperBase): # {{{ p2p matrix builder class P2PMatrixBuilder(MatrixBuilder): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, places, context, exclude_self=True): super(P2PMatrixBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, places, context) + dep_expr, other_dep_exprs, dep_discr, places, context) self.exclude_self = exclude_self def map_int_g(self, expr): - where_source = expr.source - if where_source is sym.DEFAULT_SOURCE: - where_source = sym.QBXSourceStage1(where_source) - - where_target = expr.target - if where_target is sym.DEFAULT_TARGET: - where_target = sym.QBXSourceStage1(expr.target) - - from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, where_source) - _, target_discr = _get_discretization(self.places, where_target) - assert source_discr.nnodes >= target_discr.nnodes + source_discr = self.places[expr.source] + target_discr = self.places[expr.target] rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -364,7 +348,7 @@ class P2PMatrixBuilder(MatrixBuilder): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self, kernel, expr, source) + kernel_args = _get_kernel_args(self, kernel, expr, self.places.lpot) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) @@ -388,17 +372,15 @@ class P2PMatrixBuilder(MatrixBuilder): # {{{ block matrix builders class MatrixBlockBuilderBase(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, places, context, index_set): super(MatrixBlockBuilderBase, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs - self.dep_source = dep_source - self.dep_discr = dep_source.density_discr + self.dep_discr = dep_discr self.places = places - self.index_set = index_set def _map_dep_variable(self): @@ -448,7 +430,7 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): rec_arg = cl.array.to_device(self.queue, rec_arg) op = expr.function(sym.var("u")) - result = bind(self.dep_source, op)(self.queue, u=rec_arg) + result = bind(self.place.lpot, op)(self.queue, u=rec_arg) if isinstance(result, cl.array.Array): result = result.get() @@ -457,13 +439,13 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): class NearFieldBlockBuilder(MatrixBlockBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, places, context, index_set): super(NearFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, places, context, index_set) + dep_expr, other_dep_exprs, dep_discr, places, context, index_set) self.dummy = MatrixBlockBuilderBase(queue, - dep_expr, other_dep_exprs, dep_source, places, context, index_set) + dep_expr, other_dep_exprs, dep_discr, places, context, index_set) def _map_dep_variable(self): tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) @@ -472,17 +454,8 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - where_source = expr.source - if where_source is sym.DEFAULT_SOURCE: - where_source = sym.QBXSourceStage1(where_source) - - where_target = expr.target - if where_target is sym.DEFAULT_TARGET: - where_target = sym.QBXSourceStage1(expr.target) - - from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, where_source) - _, target_discr = _get_discretization(self.places, where_target) + source_discr = self.places[expr.source] + target_discr = self.places[expr.target] if source_discr is not target_discr: raise NotImplementedError() @@ -496,10 +469,10 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - kernel_args = _get_layer_potential_args(self, expr, source) + kernel_args = _get_layer_potential_args(self, expr, self.places.lpot) from sumpy.expansion.local import LineTaylorLocalExpansion - local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) + local_expn = LineTaylorLocalExpansion(kernel, self.places.lpot.qbx_order) from sumpy.qbx import LayerPotentialMatrixBlockGenerator mat_gen = LayerPotentialMatrixBlockGenerator( @@ -507,7 +480,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): assert abs(expr.qbx_forced_limit) > 0 centers, radii = _get_centers_and_expansion_radii(self.queue, - source, target_discr, expr.qbx_forced_limit) + self.places.lpot, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), @@ -517,7 +490,8 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): index_set=self.index_set, **kernel_args) - waa = _get_weights_and_area_elements(self.queue, source, where_source) + waa = _get_weights_and_area_elements(self.queue, + self.places.lpot, source_discr) mat *= waa[self.index_set.linear_col_indices] mat = mat.get(self.queue) @@ -527,13 +501,13 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): class FarFieldBlockBuilder(MatrixBlockBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, + def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, places, context, index_set, exclude_self=True): super(FarFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, places, context, index_set) + dep_expr, other_dep_exprs, dep_discr, places, context, index_set) self.dummy = MatrixBlockBuilderBase(queue, - dep_expr, other_dep_exprs, dep_source, places, context, index_set) + dep_expr, other_dep_exprs, dep_discr, places, context, index_set) self.exclude_self = exclude_self def _map_dep_variable(self): @@ -543,20 +517,8 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - where_source = expr.source - if where_source is sym.DEFAULT_SOURCE: - where_source = sym.QBXSourceStage1(where_source) - - where_target = expr.target - if where_target is sym.DEFAULT_TARGET: - where_target = sym.QBXSourceStage1(expr.target) - - from pytential.symbolic.execution import _get_discretization - source, source_discr = _get_discretization(self.places, where_source) - _, target_discr = _get_discretization(self.places, where_target) - - if source_discr is not target_discr: - raise NotImplementedError() + source_discr = self.places[expr.source] + target_discr = self.places[expr.target] rec_density = self.dummy.rec(expr.density) if is_zero(rec_density): @@ -567,7 +529,7 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self, kernel, expr, source) + kernel_args = _get_kernel_args(self, kernel, expr, self.places.lpot) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) diff --git a/test/test_matrix.py b/test/test_matrix.py index 842029b1..58a7ea71 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -47,9 +47,23 @@ from pyopencl.tools import ( # noqa as pytest_generate_tests) -def _build_op(lpot_id, k=0, ndim=2): +def _build_op(lpot_id, + k=0, + ndim=2, + qbx_forced_limit=-1, + source=None, + target=None): + if source is DEFAULT_SOURCE: + source = QBXSourceQuadStage2(source) + if target is DEFAULT_TARGET: + target = QBXSourceStage1(target) + knl_kwargs = { + "qbx_forced_limit": qbx_forced_limit, + "source": source, + "target": target + } + from sumpy.kernel import LaplaceKernel, HelmholtzKernel - knl_kwargs = {"qbx_forced_limit": "avg"} if k: knl = HelmholtzKernel(ndim) knl_kwargs["k"] = k @@ -171,7 +185,7 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): abs_err = la.norm(res_mat - res_matvec, np.inf) rel_err = abs_err / la.norm(res_matvec, np.inf) - print(abs_err, rel_err) + print("AbsErr {:.5e} RelErr {:.5e}".format(abs_err, rel_err)) assert rel_err < 1e-13 @@ -197,9 +211,9 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, tgtindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) - from pytential.symbolic.execution import prepare_places, prepare_expr - places = prepare_places(qbx) - expr = prepare_expr(places, op) + from pytential.symbolic.execution import BindingLocation, _prepare_expr + places = BindingLocation(qbx) + expr = _prepare_expr(places, op) from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) @@ -208,7 +222,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, mbuilder = FarFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[DEFAULT_SOURCE], + dep_discr=places[DEFAULT_SOURCE], places=places, context={}, index_set=index_set) @@ -218,8 +232,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, mbuilder = P2PMatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[DEFAULT_SOURCE], - dep_discr=places[DEFAULT_SOURCE].density_discr, + dep_discr=places[DEFAULT_SOURCE], places=places, context={}) mat = mbuilder(expr) @@ -272,11 +285,14 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): tgtindices = _build_block_index(qbx.density_discr) srcindices = _build_block_index(qbx.density_discr) - from pytential.symbolic.execution import prepare_places, prepare_expr - where = (QBXSourceStage1(DEFAULT_SOURCE), - QBXSourceStage1(DEFAULT_TARGET)) - places = prepare_places(qbx, auto_where=where) - expr = prepare_expr(places, op, auto_where=where) + # NOTE: NearFieldBlockBuilder only does stage1/stage1 or stage2/stage2, + # so we need to hardcode the discr for MatrixBuilder too, since the + # defaults are different + where = (QBXSourceStage1(DEFAULT_SOURCE), QBXSourceStage1(DEFAULT_TARGET)) + + from pytential.symbolic.execution import BindingLocation, _prepare_expr + places = BindingLocation(qbx, auto_where=where) + expr = _prepare_expr(places, op) from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) @@ -285,19 +301,17 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): mbuilder = NearFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[where[0]], + dep_discr=places[where[0]], places=places, context={}, index_set=index_set) blk = mbuilder(expr) - from pytential.symbolic.execution import _get_discretization from pytential.symbolic.matrix import MatrixBuilder mbuilder = MatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[where[0]], - dep_discr=_get_discretization(places, where[0])[1], + dep_discr=places[where[0]], places=places, context={}) mat = mbuilder(expr) @@ -349,8 +363,11 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): from test_linalg_proxy import _build_qbx_discr qbx = _build_qbx_discr(queue, nelements=8, target_order=2, ndim=2, curve_f=partial(ellipse, 1.0)) - op, u_sym, _ = _build_op(lpot_id=1, ndim=2) + qbx_forced_limit = -1 + op, u_sym, _ = _build_op(lpot_id=1, ndim=2, + source=where[0], target=where[1], + qbx_forced_limit=qbx_forced_limit) # associate quad_stage2 targets to centers from pytential.qbx.target_assoc import associate_targets_to_qbx_centers @@ -393,10 +410,10 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) - from pytential.symbolic.execution import _get_discretization - source_where, target_where = where - _, source_discr = _get_discretization(qbx, source_where) - _, target_discr = _get_discretization(qbx, target_where) + from pytential.symbolic.execution import BindingLocation + places = BindingLocation(qbx, auto_where=where) + source_discr = places[where[0]] + target_discr = places[where[1]] assert mat.shape == (target_discr.nnodes, source_discr.nnodes) -- GitLab From 9577be8faec5b3768eddade68ec99b704c8f2a79 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Sun, 5 Aug 2018 20:07:39 -0500 Subject: [PATCH 222/268] flake8 and doc fixes --- pytential/symbolic/execution.py | 13 +++++-------- pytential/symbolic/matrix.py | 3 ++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 0217be3d..d6d2a88d 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -286,7 +286,7 @@ class MatVecOp: def _prepare_domains(nresults, places, domains, default_val): """ :arg nresults: number of results. - :arg places: result of :func:`prepare_places`. + :arg places: :class:`pytential.symbolic.execution.BindingLocation`. :arg domains: recommended domains. :arg default_val: default value for domains which are not provided. @@ -311,13 +311,10 @@ def _prepare_domains(nresults, places, domains, default_val): def _prepare_expr(places, expr): """ - :arg places: result of :func:`prepare_places`. - :arg expr: an array of symbolic expressions. - :arg auto_where: identifiers for source and/or target locations. If `None`, - `where` attributes are automatically found. - - :return: processed symbolic expressions, tagger with the given `where` - identifiers. + :arg places: :class:`pytential.symbolic.execution.BindingLocation`. + :arg expr: a symbolic expression. + :return: processed symbolic expressions, tagged with the appropriate + `where` identifier from places, etc. """ from pytential.source import LayerPotentialSourceBase diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 8c9a354e..3e9a6c9d 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -271,7 +271,8 @@ class MatrixBuilder(EvaluationMapperBase): **kernel_args) mat = mat.get() - waa = _get_weights_and_area_elements(self.queue, self.places.lpot, source_discr) + waa = _get_weights_and_area_elements(self.queue, + self.places.lpot, source_discr) mat[:, :] *= waa.get(self.queue) if target_discr.nnodes != source_discr.nnodes: -- GitLab From a4446126a3be19eaea6c2ef463d1a620a9de6946 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 6 Aug 2018 19:19:57 -0500 Subject: [PATCH 223/268] Point to boxtree 2018.2 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6d1e4cce..83394272 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree +git+https://gitlab.tiker.net/inducer/boxtree@reindex-list4-close git+https://github.com/inducer/meshmode git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib diff --git a/setup.py b/setup.py index 0c13014d..e2a1ae90 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,7 @@ setup(name="pytential", "pytools>=2018.2", "modepy>=2013.3", "pyopencl>=2013.1", - "boxtree>=2018.1", + "boxtree>=2018.2", "pymbolic>=2013.2", "loo.py>=2017.2", "sumpy>=2013.1", -- GitLab From cab0e898fcc7610a6680312544878bfe6af5cb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Mon, 6 Aug 2018 20:41:00 -0400 Subject: [PATCH 224/268] Point requirements.txt back at boxtree master --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 83394272..6d1e4cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ git+https://github.com/inducer/modepy git+https://github.com/inducer/pyopencl git+https://github.com/inducer/islpy git+https://github.com/inducer/loopy -git+https://gitlab.tiker.net/inducer/boxtree@reindex-list4-close +git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode git+https://gitlab.tiker.net/inducer/sumpy git+https://github.com/inducer/pyfmmlib -- GitLab From 5f82195bc812a3323fb446bbe35f5ce4769ac020 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 6 Aug 2018 19:56:30 -0500 Subject: [PATCH 225/268] Fix overcounting of eval_locals. Closes #103 --- pytential/qbx/fmm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 994c8ff7..46d7ae88 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -809,7 +809,8 @@ def assemble_performance_data(geo_data, uses_pde_expansions, # {{{ evaluate locals - result["eval_part"] = tree.ntargets * ncoeffs_fmm + non_qbx_box_targets = geo_data.non_qbx_box_target_lists() + result["eval_part"] = non_qbx_box_targets.nfiltered_targets * ncoeffs_fmm # }}} -- GitLab From ccdcb980b34b2ffdbed577518c5c0dea3b7434b5 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 9 Aug 2018 20:26:52 -0500 Subject: [PATCH 226/268] execution: more work on making the geometry collection ready. * renamed to GeometryCollection * the `sources` are always a LayerPotentialSourceBase of some sort. * added a get_discretization that gets the actual mesh and used that in most places. * added process_optemplate to the PotentialSource base class, to match the documentation. --- pytential/source.py | 34 ++++-- pytential/symbolic/compiler.py | 4 +- pytential/symbolic/execution.py | 186 +++++++++++++++----------------- pytential/symbolic/mappers.py | 4 +- pytential/symbolic/matrix.py | 90 +++++++++------- test/test_matrix.py | 69 ++++++------ 6 files changed, 198 insertions(+), 189 deletions(-) diff --git a/pytential/source.py b/pytential/source.py index 6420494e..6110972e 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -38,7 +38,7 @@ __doc__ = """ class PotentialSource(object): """ - .. method:: preprocess_optemplate(name, expr) + .. method:: preprocess_optemplate(name, discretizations, expr) .. method:: op_group_features(expr) @@ -49,29 +49,43 @@ class PotentialSource(object): :class:`pytential.symbolic.primitives.IntG`. """ + def preprocess_optemplate(name, discretizations, expr): + return expr + # {{{ point potential source class PointPotentialSource(PotentialSource): """ - .. attribute:: points + .. attribute:: nodes - An :class:`pyopencl.array.Array` of shape ``[ambient_dim, npoints]``. + An :class:`pyopencl.array.Array` of shape ``[ambient_dim, nnodes]``. .. attribute:: nnodes """ - def __init__(self, cl_context, points): + def __init__(self, cl_context, nodes): self.cl_context = cl_context - self.points = points + self._nodes = nodes + + @property + def points(self): + from warnings import warn + warn("'points' has been renamed to nodes().", + DeprecationWarning, stacklevel=2) + + return self._nodes + + def nodes(self): + return self._nodes @property def real_dtype(self): - return self.points.dtype + return self._nodes.dtype @property def nnodes(self): - return self.points.shape[-1] + return self._nodes.shape[-1] @property def complex_dtype(self): @@ -82,7 +96,7 @@ class PointPotentialSource(PotentialSource): @property def ambient_dim(self): - return self.points.shape[0] + return self._nodes.shape[0] def op_group_features(self, expr): from sumpy.kernel import AxisTargetDerivativeRemover @@ -129,7 +143,7 @@ class PointPotentialSource(PotentialSource): p2p = self.get_p2p(insn.kernels) evt, output_for_each_kernel = p2p(queue, - target_discr.nodes(), self.points, + target_discr.nodes(), self._nodes, [strengths], **kernel_args) result.append((o.name, output_for_each_kernel[o.kernel_index])) @@ -139,7 +153,7 @@ class PointPotentialSource(PotentialSource): @memoize_method def weights_and_area_elements(self): with cl.CommandQueue(self.cl_context) as queue: - result = cl.array.empty(queue, self.points.shape[-1], + result = cl.array.empty(queue, self._nodes.shape[-1], dtype=self.real_dtype) result.fill(1) diff --git a/pytential/symbolic/compiler.py b/pytential/symbolic/compiler.py index ecb1fdca..17d23ebf 100644 --- a/pytential/symbolic/compiler.py +++ b/pytential/symbolic/compiler.py @@ -224,7 +224,7 @@ class ComputePotentialInstruction(Instruction): ", ".join(args), "\n ".join(lines)) def get_exec_function(self, exec_mapper): - source = exec_mapper.bound_expr.places.lpot + source = exec_mapper.bound_expr.places[self.source] return source.exec_compute_potential_insn def __hash__(self): @@ -444,7 +444,7 @@ class OperatorCompiler(IdentityMapper): def op_group_features(self, expr): from pytential.symbolic.primitives import hashable_kernel_args return ( - self.places.lpot.op_group_features(expr) + self.places[expr.source].op_group_features(expr) + hashable_kernel_args(expr.kernel_arguments)) @memoize_method diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index d6d2a88d..a83301fa 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -283,48 +283,49 @@ class MatVecOp: # {{{ expression prep -def _prepare_domains(nresults, places, domains, default_val): +def _prepare_domains(nresults, places, domains, default_domain): """ :arg nresults: number of results. - :arg places: :class:`pytential.symbolic.execution.BindingLocation`. + :arg places: :class:`pytential.symbolic.execution.GeometryCollection`. :arg domains: recommended domains. - :arg default_val: default value for domains which are not provided. + :arg default_domain: default value for domains which are not provided. :return: a list of domains for each result. If domains is `None`, each - element in the list is *default_val*. If *domains* is not a list - of domains, each element in the resulting list is *domains*. Otherwise, - *domains* is returned as is. + element in the list is *default_domain*. If *domains* is a scalar + (i.e., not a *list* or *tuple*), each element in the list is + *domains*. Otherwise, *domains* is returned as is. """ if domains is None: - if default_val not in places: + if default_domain not in places: raise RuntimeError("'domains is None' requires " - "default domain to be defined") - dom_name = default_val + "default domain to be defined in places") + dom_name = default_domain return nresults * [dom_name] - elif not isinstance(domains, (list, tuple)): + if not isinstance(domains, (list, tuple)): dom_name = domains return nresults * [dom_name] - else: - assert len(domains) == nresults - return domains + + assert len(domains) == nresults + return domains def _prepare_expr(places, expr): """ - :arg places: :class:`pytential.symbolic.execution.BindingLocation`. + :arg places: :class:`pytential.symbolic.execution.GeometryCollection`. :arg expr: a symbolic expression. :return: processed symbolic expressions, tagged with the appropriate `where` identifier from places, etc. """ - from pytential.source import LayerPotentialSourceBase + from pytential.source import PotentialSource from pytential.symbolic.mappers import ( ToTargetTagger, DerivativeBinder) expr = ToTargetTagger(*places.where)(expr) expr = DerivativeBinder()(expr) - if isinstance(places.lpot, LayerPotentialSourceBase): - expr = places.lpot.preprocess_optemplate(places.source, places, expr) + for name, place in six.iteritems(places.places): + if isinstance(places, PotentialSource): + expr = p.preprocess_optemplate(name, places, expr) return expr @@ -333,7 +334,7 @@ def _prepare_expr(places, expr): # {{{ bound expression -class BindingLocation(object): +class GeometryCollection(object): def __init__(self, places, auto_where=None): from meshmode.discretization import Discretization from pytential.source import LayerPotentialSourceBase, PotentialSource @@ -343,31 +344,24 @@ class BindingLocation(object): auto_where = DEFAULT_SOURCE, DEFAULT_TARGET self.where = auto_where - self.lpot = None self.places = {} if isinstance(places, LayerPotentialSourceBase): - self.lpot = places - self.places[self.source] = self[self.source] - self.places[self.target] = self[self.target] + self.places[self.source] = places + self.places[self.target] = \ + self.get_discretization(self.target, lpot=places) elif isinstance(places, (Discretization, TargetBase)): self.places[self.target] = places elif isinstance(places, tuple): source_discr, target_discr = places - if isinstance(source_discr, PotentialSource): - self.lpot = source_discr - if isinstance(source_discr, LayerPotentialSourceBase): - source_discr = self[self.source] - self.places[self.source] = source_discr self.places[self.target] = target_discr else: - for key, value in six.iteritems(places): - self.places[key] = value + self.places = places.copy() for p in six.itervalues(self.places): if not isinstance(p, (PotentialSource, TargetBase, Discretization)): raise TypeError("Must pass discretization, targets or " - "layer potential sources as 'places'") + "layer potential sources as 'places'.") self.caches = {} @@ -379,51 +373,38 @@ class BindingLocation(object): def target(self): return self.where[1] - def _get_lpot_discretization(self, where): - from pytential.source import LayerPotentialSourceBase - if not isinstance(self.lpot, LayerPotentialSourceBase): - return None - + def get_discretization(self, where, lpot=None): if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: where = QBXSourceStage1(where) + if lpot is None: + if where in self.places: + lpot = self.places[where] + else: + lpot = self.places.get(where.where, None) + + from pytential.source import LayerPotentialSourceBase + if not isinstance(lpot, LayerPotentialSourceBase): + return lpot + if isinstance(where, QBXSourceStage1): - return self.lpot.density_discr + return lpot.density_discr if isinstance(where, QBXSourceStage2): - return self.lpot.stage2_density_discr + return lpot.stage2_density_discr if isinstance(where, QBXSourceQuadStage2): - return self.lpot.quad_stage2_density_discr + return lpot.quad_stage2_density_discr - return None + raise ValueError('Unknown `where` identifier: {}'.format(type(where))) def __getitem__(self, where): - if where in self.places: - discr = self.places[where] - else: - discr = self._get_lpot_discretization(where) - - if discr is None: - from pytential.symbolic.mappers import stringify_where - raise KeyError("Identifier '{}' not in places".format( - stringify_where(where))) - - return discr - - def __iter__(self): - return iter(self.places.keys()) - - def keys(self): - return self.places.keys() + return self.places[where] - def values(self): - return self.places.values() + def __contains__(self, where): + return where in self.places def items(self): return self.places.items() - def __hash__(self): - return hash(tuple(self.values())) - def get_cache(self, name): return self.caches.setdefault(name, {}) @@ -441,7 +422,7 @@ class BoundExpression(object): return self.places.get_cache(name) def get_discretization(self, where): - return self.places[where] + return self.places.get_discretization(where) def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): """ @@ -488,14 +469,15 @@ class BoundExpression(object): def bind(places, expr, auto_where=None): """ - :arg places: a mapping of symbolic names to - :class:`pytential.discretization.Discretization` objects or a subclass - of :class:`pytential.discretization.target.TargetBase`. - :arg auto_where: For simple source-to-self or source-to-target + :arg places: a collection or mapping of symbolic names to + :class:`meshmode.discretization.Discretization` objects, subclasses + of :class:`pytential.source.PotentialSource` or subclasses of + :class:`pytential.target.TargetBase`. + :arg auto_where: for simple source-to-self or source-to-target evaluations, find 'where' attributes automatically. """ - places = BindingLocation(places, auto_where=auto_where) + places = GeometryCollection(places, auto_where=auto_where) expr = _prepare_expr(places, expr) return BoundExpression(places, expr) @@ -505,14 +487,44 @@ def bind(places, expr, auto_where=None): # {{{ matrix building +def _bmat(blocks, dtypes): + from pytools import single_valued + from pytential.symbolic.matrix import is_zero + + nrows = blocks.shape[0] + ncolumns = blocks.shape[1] + + # "block row starts"/"block column starts" + brs = np.cumsum([0] + + [single_valued(blocks[ibrow, ibcol].shape[0] + for ibcol in range(ncolumns) + if not is_zero(blocks[ibrow, ibcol])) + for ibrow in range(nrows)]) + + bcs = np.cumsum([0] + + [single_valued(blocks[ibrow, ibcol].shape[1] + for ibrow in range(nrows) + if not is_zero(blocks[ibrow, ibcol])) + for ibcol in range(ncolumns)]) + + result = np.zeros((brs[-1], bcs[-1]), + dtype=np.find_common_type(dtypes, [])) + for ibcol in range(ncolumns): + for ibrow in range(nrows): + result[brs[ibrow]:brs[ibrow + 1], bcs[ibcol]:bcs[ibcol + 1]] = \ + blocks[ibrow, ibcol] + + return result + + def build_matrix(queue, places, exprs, input_exprs, domains=None, auto_where=None, context=None): """ :arg queue: a :class:`pyopencl.CommandQueue` used to synchronize the calculation. - :arg places: a mapping of symbolic names to - :class:`pytential.discretization.Discretization` objects or a subclass - of :class:`pytential.discretization.target.TargetBase`. + :arg places: a collection or mapping of symbolic names to + :class:`meshmode.discretization.Discretization` objects, subclasses + of :class:`pytential.source.PotentialSource` or subclasses of :arg input_exprs: An object array of expressions corresponding to the input block columns of the matrix. @@ -521,7 +533,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, *None* values indicating the domains on which each component of the solution vector lives. *None* values indicate that the component is a scalar. If *None*, - :class:`pytential.symbolic.primitives.DEFAULT_TARGET`, is required + :class:`pytential.symbolic.primitives.DEFAULT_SOURCE`, is required to be a key in :attr:`places`. :arg auto_where: For simple source-to-self or source-to-target evaluations, find 'where' attributes automatically. @@ -531,7 +543,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, context = {} from pytools.obj_array import is_obj_array, make_obj_array - places = BindingLocation(places, auto_where=auto_where) + places = GeometryCollection(places, auto_where=auto_where) exprs = _prepare_expr(places, exprs) if not is_obj_array(exprs): @@ -556,7 +568,8 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, dep_expr=input_exprs[ibcol], other_dep_exprs=(input_exprs[:ibcol] + input_exprs[ibcol + 1:]), - dep_discr=places[domains[ibcol]], + dep_source=places[domains[ibcol]], + dep_discr=places.get_discretization(domains[ibcol]), places=places, context=context) @@ -568,32 +581,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, if isinstance(block, np.ndarray): dtypes.append(block.dtype) - from pytools import single_valued - block_row_counts = [ - single_valued( - blocks[ibrow, ibcol].shape[0] - for ibcol in range(nblock_columns) - if not is_zero(blocks[ibrow, ibcol])) - for ibrow in range(nblock_rows)] - - block_col_counts = [ - single_valued( - blocks[ibrow, ibcol].shape[1] - for ibrow in range(nblock_rows) - if not is_zero(blocks[ibrow, ibcol])) - for ibcol in range(nblock_columns)] - - # "block row starts"/"block column starts" - brs = np.cumsum([0] + block_row_counts) - bcs = np.cumsum([0] + block_col_counts) - - result = np.zeros((brs[-1], bcs[-1]), dtype=np.find_common_type(dtypes, [])) - for ibcol in range(nblock_columns): - for ibrow in range(nblock_rows): - result[brs[ibrow]:brs[ibrow + 1], bcs[ibcol]:bcs[ibcol + 1]] = \ - blocks[ibrow, ibcol] - - return cl.array.to_device(queue, result) + return cl.array.to_device(queue, _bmat(blocks, dtypes)) # }}} diff --git a/pytential/symbolic/mappers.py b/pytential/symbolic/mappers.py index a8696efe..00c43689 100644 --- a/pytential/symbolic/mappers.py +++ b/pytential/symbolic/mappers.py @@ -399,8 +399,8 @@ class QBXPreprocessor(IdentityMapper): self.places = places def map_int_g(self, expr): - source_discr = self.places[self.source_name] - target_discr = self.places[expr.target] + source_discr = self.places.get_discretization(self.source_name) + target_discr = self.places.get_discretization(expr.target) if expr.qbx_forced_limit == 0: raise ValueError("qbx_forced_limit == 0 was a bad idea and " diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 3e9a6c9d..79db0f8d 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -41,7 +41,8 @@ def is_zero(x): def _resample_arg(queue, source, x): - if source is None: + from pytential.source import LayerPotentialSourceBase + if not isinstance(source, LayerPotentialSourceBase): return x if not isinstance(x, np.ndarray): @@ -138,13 +139,14 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim # We'll cheat and build the matrix on the host. class MatrixBuilder(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, - places, context): + def __init__(self, queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, context): super(MatrixBuilder, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs + self.dep_source = dep_source self.dep_discr = dep_discr self.places = places @@ -233,13 +235,14 @@ class MatrixBuilder(EvaluationMapperBase): return vecs_and_scalars def map_int_g(self, expr): - # TODO: should this go into QBXPreprocessor / LocationTagger + # TODO: should this go into QBXPreprocessor / LocationTagger? where_source = expr.source if where_source is sym.DEFAULT_SOURCE: where_source = sym.QBXSourceQuadStage2(expr.source) - source_discr = self.places[where_source] - target_discr = self.places[expr.target] + source = self.places[expr.source] + source_discr = self.places.get_discretization(where_source) + target_discr = self.places.get_discretization(expr.target) rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -250,10 +253,10 @@ class MatrixBuilder(EvaluationMapperBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - kernel_args = _get_layer_potential_args(self, expr, self.places.lpot) + kernel_args = _get_layer_potential_args(self, expr, source) from sumpy.expansion.local import LineTaylorLocalExpansion - local_expn = LineTaylorLocalExpansion(kernel, self.places.lpot.qbx_order) + local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) from sumpy.qbx import LayerPotentialMatrixGenerator mat_gen = LayerPotentialMatrixGenerator( @@ -261,7 +264,7 @@ class MatrixBuilder(EvaluationMapperBase): assert abs(expr.qbx_forced_limit) > 0 centers, radii = _get_centers_and_expansion_radii(self.queue, - self.places.lpot, target_discr, expr.qbx_forced_limit) + source, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), @@ -271,14 +274,13 @@ class MatrixBuilder(EvaluationMapperBase): **kernel_args) mat = mat.get() - waa = _get_weights_and_area_elements(self.queue, - self.places.lpot, source_discr) + waa = _get_weights_and_area_elements(self.queue, source, source_discr) mat[:, :] *= waa.get(self.queue) if target_discr.nnodes != source_discr.nnodes: assert target_discr.nnodes < source_discr.nnodes - resampler = self.places.lpot.direct_resampler + resampler = source.direct_resampler resample_mat = resampler.full_resample_matrix(self.queue).get(self.queue) mat = mat.dot(resample_mat) @@ -316,7 +318,7 @@ class MatrixBuilder(EvaluationMapperBase): rec_arg = cl.array.to_device(self.queue, rec_arg) op = expr.function(sym.var("u")) - result = bind(self.places.lpot, op)(self.queue, u=rec_arg) + result = bind(self.dep_source, op)(self.queue, u=rec_arg) if isinstance(result, cl.array.Array): result = result.get() @@ -329,16 +331,18 @@ class MatrixBuilder(EvaluationMapperBase): # {{{ p2p matrix builder class P2PMatrixBuilder(MatrixBuilder): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, - places, context, exclude_self=True): + def __init__(self, queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, context, exclude_self=True): super(P2PMatrixBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_discr, places, context) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) self.exclude_self = exclude_self def map_int_g(self, expr): - source_discr = self.places[expr.source] - target_discr = self.places[expr.target] + source = self.places[expr.source] + source_discr = self.places.get_discretization(expr.source) + target_discr = self.places.get_discretization(expr.target) rec_density = self.rec(expr.density) if is_zero(rec_density): @@ -349,7 +353,7 @@ class P2PMatrixBuilder(MatrixBuilder): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self, kernel, expr, self.places.lpot) + kernel_args = _get_kernel_args(self, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) @@ -373,13 +377,14 @@ class P2PMatrixBuilder(MatrixBuilder): # {{{ block matrix builders class MatrixBlockBuilderBase(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, - places, context, index_set): + def __init__(self, queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, index_set, context): super(MatrixBlockBuilderBase, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs + self.dep_source = dep_source self.dep_discr = dep_discr self.places = places self.index_set = index_set @@ -431,7 +436,7 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): rec_arg = cl.array.to_device(self.queue, rec_arg) op = expr.function(sym.var("u")) - result = bind(self.place.lpot, op)(self.queue, u=rec_arg) + result = bind(self.dep_source, op)(self.queue, u=rec_arg) if isinstance(result, cl.array.Array): result = result.get() @@ -440,13 +445,15 @@ class MatrixBlockBuilderBase(EvaluationMapperBase): class NearFieldBlockBuilder(MatrixBlockBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, - places, context, index_set): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context): super(NearFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_discr, places, context, index_set) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) self.dummy = MatrixBlockBuilderBase(queue, - dep_expr, other_dep_exprs, dep_discr, places, context, index_set) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) def _map_dep_variable(self): tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) @@ -455,8 +462,9 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - source_discr = self.places[expr.source] - target_discr = self.places[expr.target] + source = self.places[expr.source] + source_discr = self.places.get_discretization(expr.source) + target_discr = self.places.get_discretization(expr.target) if source_discr is not target_discr: raise NotImplementedError() @@ -470,10 +478,10 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - kernel_args = _get_layer_potential_args(self, expr, self.places.lpot) + kernel_args = _get_layer_potential_args(self, expr, source) from sumpy.expansion.local import LineTaylorLocalExpansion - local_expn = LineTaylorLocalExpansion(kernel, self.places.lpot.qbx_order) + local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) from sumpy.qbx import LayerPotentialMatrixBlockGenerator mat_gen = LayerPotentialMatrixBlockGenerator( @@ -481,7 +489,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): assert abs(expr.qbx_forced_limit) > 0 centers, radii = _get_centers_and_expansion_radii(self.queue, - self.places.lpot, target_discr, expr.qbx_forced_limit) + source, target_discr, expr.qbx_forced_limit) _, (mat,) = mat_gen(self.queue, targets=target_discr.nodes(), @@ -491,8 +499,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): index_set=self.index_set, **kernel_args) - waa = _get_weights_and_area_elements(self.queue, - self.places.lpot, source_discr) + waa = _get_weights_and_area_elements(self.queue, source, source_discr) mat *= waa[self.index_set.linear_col_indices] mat = mat.get(self.queue) @@ -502,13 +509,15 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): class FarFieldBlockBuilder(MatrixBlockBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, dep_discr, - places, context, index_set, exclude_self=True): + def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context, exclude_self=True): super(FarFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_discr, places, context, index_set) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) self.dummy = MatrixBlockBuilderBase(queue, - dep_expr, other_dep_exprs, dep_discr, places, context, index_set) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) self.exclude_self = exclude_self def _map_dep_variable(self): @@ -518,8 +527,9 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): - source_discr = self.places[expr.source] - target_discr = self.places[expr.target] + source = self.places[expr.source] + source_discr = self.places.get_discretization(expr.source) + target_discr = self.places.get_discretization(expr.target) rec_density = self.dummy.rec(expr.density) if is_zero(rec_density): @@ -530,7 +540,7 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self, kernel, expr, self.places.lpot) + kernel_args = _get_kernel_args(self, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) diff --git a/test/test_matrix.py b/test/test_matrix.py index 58a7ea71..487fcf8a 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -50,45 +50,37 @@ from pyopencl.tools import ( # noqa def _build_op(lpot_id, k=0, ndim=2, - qbx_forced_limit=-1, - source=None, - target=None): - if source is DEFAULT_SOURCE: - source = QBXSourceQuadStage2(source) - if target is DEFAULT_TARGET: - target = QBXSourceStage1(target) - knl_kwargs = { - "qbx_forced_limit": qbx_forced_limit, - "source": source, - "target": target - } + qbx_forced_limit=-1): from sumpy.kernel import LaplaceKernel, HelmholtzKernel if k: knl = HelmholtzKernel(ndim) - knl_kwargs["k"] = k + knl_kwargs = {"k": k} else: knl = LaplaceKernel(ndim) + knl_kwargs = {} + lpot_kwargs = {"qbx_forced_limit": qbx_forced_limit} + lpot_kwargs.update(knl_kwargs) if lpot_id == 1: # scalar single-layer potential u_sym = sym.var("u") - op = sym.S(knl, u_sym, **knl_kwargs) + op = sym.S(knl, u_sym, **lpot_kwargs) elif lpot_id == 2: # scalar double-layer potential u_sym = sym.var("u") - op = sym.D(knl, u_sym, **knl_kwargs) + op = sym.D(knl, u_sym, **lpot_kwargs) elif lpot_id == 3: # vector potential u_sym = sym.make_sym_vector("u", 2) u0_sym, u1_sym = u_sym op = make_obj_array([ - sym.Sp(knl, u0_sym, **knl_kwargs) + - sym.D(knl, u1_sym, **knl_kwargs), + sym.Sp(knl, u0_sym, **lpot_kwargs) + + sym.D(knl, u1_sym, **lpot_kwargs), - sym.S(knl, 0.4 * u0_sym, **knl_kwargs) + - 0.3 * sym.D(knl, u0_sym, **knl_kwargs) + sym.S(knl, 0.4 * u0_sym, **lpot_kwargs) + + 0.3 * sym.D(knl, u0_sym, **lpot_kwargs) ]) else: raise ValueError("Unknown lpot_id: {}".format(lpot_id)) @@ -211,9 +203,11 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, tgtindices = _build_block_index(qbx.density_discr, method='nodes', factor=factor) - from pytential.symbolic.execution import BindingLocation, _prepare_expr - places = BindingLocation(qbx) + from pytential.symbolic.execution import GeometryCollection + from pytential.symbolic.execution import _prepare_expr, _prepare_domains + places = GeometryCollection(qbx) expr = _prepare_expr(places, op) + domains = _prepare_domains(1, places, None, places.source) from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) @@ -222,17 +216,19 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, mbuilder = FarFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_discr=places[DEFAULT_SOURCE], + dep_source=places[domains[0]], + dep_discr=places.get_discretization(domains[0]), places=places, - context={}, - index_set=index_set) + index_set=index_set, + context={}) blk = mbuilder(expr) from pytential.symbolic.matrix import P2PMatrixBuilder mbuilder = P2PMatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_discr=places[DEFAULT_SOURCE], + dep_source=places[domains[0]], + dep_discr=places.get_discretization(domains[0]), places=places, context={}) mat = mbuilder(expr) @@ -290,8 +286,8 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): # defaults are different where = (QBXSourceStage1(DEFAULT_SOURCE), QBXSourceStage1(DEFAULT_TARGET)) - from pytential.symbolic.execution import BindingLocation, _prepare_expr - places = BindingLocation(qbx, auto_where=where) + from pytential.symbolic.execution import GeometryCollection, _prepare_expr + places = GeometryCollection(qbx, auto_where=where) expr = _prepare_expr(places, op) from sumpy.tools import MatrixBlockIndexRanges @@ -301,17 +297,19 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): mbuilder = NearFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_discr=places[where[0]], + dep_source=places[where[0]], + dep_discr=places.get_discretization(where[0]), places=places, - context={}, - index_set=index_set) + index_set=index_set, + context={}) blk = mbuilder(expr) from pytential.symbolic.matrix import MatrixBuilder mbuilder = MatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_discr=places[where[0]], + dep_source=places[where[0]], + dep_discr=places.get_discretization(where[0]), places=places, context={}) mat = mbuilder(expr) @@ -366,7 +364,6 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): qbx_forced_limit = -1 op, u_sym, _ = _build_op(lpot_id=1, ndim=2, - source=where[0], target=where[1], qbx_forced_limit=qbx_forced_limit) # associate quad_stage2 targets to centers @@ -410,10 +407,10 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) - from pytential.symbolic.execution import BindingLocation - places = BindingLocation(qbx, auto_where=where) - source_discr = places[where[0]] - target_discr = places[where[1]] + from pytential.symbolic.execution import GeometryCollection + places = GeometryCollection(qbx, auto_where=where) + source_discr = places.get_discretization(where[0]) + target_discr = places.get_discretization(where[1]) assert mat.shape == (target_discr.nnodes, source_discr.nnodes) -- GitLab From 5188a405013c7ff29deaa2b7be2d702aff457f02 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Thu, 9 Aug 2018 20:57:54 -0500 Subject: [PATCH 227/268] execution: fix typo in prepare_expr --- pytential/source.py | 2 +- pytential/symbolic/execution.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pytential/source.py b/pytential/source.py index 6110972e..3bd58e46 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -49,7 +49,7 @@ class PotentialSource(object): :class:`pytential.symbolic.primitives.IntG`. """ - def preprocess_optemplate(name, discretizations, expr): + def preprocess_optemplate(self, name, discretizations, expr): return expr diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index a83301fa..db00c8ed 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -317,15 +317,16 @@ def _prepare_expr(places, expr): `where` identifier from places, etc. """ - from pytential.source import PotentialSource + from pytential.source import LayerPotentialSourceBase from pytential.symbolic.mappers import ( ToTargetTagger, DerivativeBinder) expr = ToTargetTagger(*places.where)(expr) expr = DerivativeBinder()(expr) + for name, place in six.iteritems(places.places): - if isinstance(places, PotentialSource): - expr = p.preprocess_optemplate(name, places, expr) + if isinstance(place, LayerPotentialSourceBase): + expr = place.preprocess_optemplate(name, places, expr) return expr -- GitLab From 451dffce306e9eb6afc65794350ce768ef9a387d Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 10 Aug 2018 10:16:59 -0500 Subject: [PATCH 228/268] matrix: fix a target_association bug and add more tests --- pytential/symbolic/execution.py | 10 ++-- pytential/symbolic/matrix.py | 6 +-- test/test_matrix.py | 85 ++++++++++++++++++--------------- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index db00c8ed..3eca37df 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -403,8 +403,8 @@ class GeometryCollection(object): def __contains__(self, where): return where in self.places - def items(self): - return self.places.items() + def copy(self): + return GeometryCollection(self.places, auto_where=self.where) def get_cache(self, name): return self.caches.setdefault(name, {}) @@ -478,7 +478,8 @@ def bind(places, expr, auto_where=None): evaluations, find 'where' attributes automatically. """ - places = GeometryCollection(places, auto_where=auto_where) + if not isinstance(places, GeometryCollection): + places = GeometryCollection(places, auto_where=auto_where) expr = _prepare_expr(places, expr) return BoundExpression(places, expr) @@ -544,7 +545,8 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, context = {} from pytools.obj_array import is_obj_array, make_obj_array - places = GeometryCollection(places, auto_where=auto_where) + if not isinstance(places, GeometryCollection): + places = GeometryCollection(places, auto_where=auto_where) exprs = _prepare_expr(places, exprs) if not is_obj_array(exprs): diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 79db0f8d..f8c659c7 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -106,7 +106,7 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim else: from pytential.qbx.utils import get_interleaved_centers centers = get_interleaved_centers(queue, source) - radii = source._expansion_radii('nsources') + radii = source._expansion_radii('ncenters') # NOTE: using a very small tolerance to make sure all the stage2 # targets are associated to a center. We can't use the user provided @@ -124,9 +124,7 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim centers = [cl.array.take(c, assoc.target_to_center, queue=queue) for c in centers] - radii = cl.array.take(radii, - (assoc.target_to_center.with_queue(queue) / 2.0).astype(np.int), - queue=queue) + radii = cl.array.take(radii, assoc.target_to_center, queue=queue) return centers, radii diff --git a/test/test_matrix.py b/test/test_matrix.py index 487fcf8a..d16dd32a 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -366,44 +366,7 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): op, u_sym, _ = _build_op(lpot_id=1, ndim=2, qbx_forced_limit=qbx_forced_limit) - # associate quad_stage2 targets to centers - from pytential.qbx.target_assoc import associate_targets_to_qbx_centers - - code_container = qbx.target_association_code_container - target_discr = qbx.quad_stage2_density_discr - target_assoc = associate_targets_to_qbx_centers( - qbx, - code_container.get_wrangler(queue), - [(target_discr, qbx_forced_limit)], - target_association_tolerance=1.0e-1).get(queue) - target_assoc = target_assoc.target_to_center - - if qbx.ambient_dim == 2 and visualize: - import matplotlib.pyplot as pt - from pytential.qbx.utils import get_interleaved_centers - sources = qbx.density_discr.nodes().get(queue) - targets = target_discr.nodes().get(queue) - centers = get_interleaved_centers(queue, qbx) - centers = np.vstack(c.get(queue) for c in centers) - radii = qbx._expansion_radii('nsources').get(queue) - - for i in range(centers[0].size): - itgt = np.where(target_assoc == i)[0] - if not len(itgt): - continue - - pt.figure(figsize=(10, 8), dpi=300) - pt.plot(sources[0], sources[1], 'k') - pt.plot(targets[0], targets[1], 'ko') - - line = pt.plot(targets[0, itgt], targets[1, itgt], 'o') - c = pt.Circle([centers[0][i], centers[1][i]], radii[i // 2], - color=line[0].get_color(), alpha=0.5) - pt.gca().add_artist(c) - - pt.savefig('test_assoc_quad_targets_to_centers_{:05}.png'.format(i // 2)) - pt.close() - + # build full QBX matrix from pytential.symbolic.execution import build_matrix mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) @@ -414,6 +377,52 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): assert mat.shape == (target_discr.nnodes, source_discr.nnodes) + # build full p2p matrix + from pytential.symbolic.execution import _prepare_expr + op = _prepare_expr(places, op) + + from pytential.symbolic.matrix import P2PMatrixBuilder + mbuilder = P2PMatrixBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[where[0]], + dep_discr=places.get_discretization(where[0]), + places=places, + context={}) + mat = mbuilder(op) + + assert mat.shape == (target_discr.nnodes, source_discr.nnodes) + + # build block qbx and p2p matrices + from test_linalg_proxy import _build_block_index + srcindices = _build_block_index(source_discr, method='nodes', factor=0.6) + tgtindices = _build_block_index(target_discr, method='nodes', factor=0.6) + + from sumpy.tools import MatrixBlockIndexRanges + index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) + + from pytential.symbolic.matrix import NearFieldBlockBuilder + mbuilder = NearFieldBlockBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[where[0]], + dep_discr=places.get_discretization(where[0]), + places=places, + index_set=index_set, + context={}) + mat = mbuilder(op) + + from pytential.symbolic.matrix import FarFieldBlockBuilder + mbuilder = FarFieldBlockBuilder(queue, + dep_expr=u_sym, + other_dep_exprs=[], + dep_source=places[where[0]], + dep_discr=places.get_discretization(where[0]), + places=places, + index_set=index_set, + context={}) + mat = mbuilder(op) + if __name__ == "__main__": import sys -- GitLab From defcbf55f0d473607bee31ab1fddbdf17b854172 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Fri, 10 Aug 2018 10:39:26 -0500 Subject: [PATCH 229/268] matrix: add some docs --- pytential/symbolic/execution.py | 16 ++++++------ pytential/symbolic/matrix.py | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 3eca37df..b1e8eb4a 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -295,6 +295,7 @@ def _prepare_domains(nresults, places, domains, default_domain): (i.e., not a *list* or *tuple*), each element in the list is *domains*. Otherwise, *domains* is returned as is. """ + if domains is None: if default_domain not in places: raise RuntimeError("'domains is None' requires " @@ -522,15 +523,16 @@ def _bmat(blocks, dtypes): def build_matrix(queue, places, exprs, input_exprs, domains=None, auto_where=None, context=None): """ - :arg queue: a :class:`pyopencl.CommandQueue` used to synchronize - the calculation. - :arg places: a collection or mapping of symbolic names to + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg places: a collection or mapping of symbolic names (see also + :class:`pytential.symbolic.execution.GeometryCollection`) to :class:`meshmode.discretization.Discretization` objects, subclasses of :class:`pytential.source.PotentialSource` or subclasses of - :arg input_exprs: An object array of expressions corresponding to the - input block columns of the matrix. - - May also be a single expression. + :class:`pytential.target.TargetBase`. + :arg exprs: an array of expressions corresponding to the output block + rows of the matrix. May also be a single expression. + :arg input_exprs: an array of expressions corresponding to the + input block columns of the matrix. May also be a single expression. :arg domains: a list of discretization identifiers (see 'places') or *None* values indicating the domains on which each component of the solution vector lives. *None* values indicate that the component diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index f8c659c7..16396f6c 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -41,6 +41,16 @@ def is_zero(x): def _resample_arg(queue, source, x): + """ + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase` subclass. + If it is not a layer potential source, no resampling is done. + :arg x: a :class:`numpy.ndarray`. + + :return: a resampled :class:`numpy.ndarray` (see + :method:`pytential.source.LayerPotentialSourceBase.resampler`). + """ + from pytential.source import LayerPotentialSourceBase if not isinstance(source, LayerPotentialSourceBase): return x @@ -59,6 +69,14 @@ def _resample_arg(queue, source, x): def _get_layer_potential_args(mapper, expr, source): + """ + :arg mapper: a :class:`pytential.symbolic.primitives.EvaluationMapperBase`. + :arg expr: symbolic layer potential expression. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. + + :return: a mapping of kernel arguments *expr.kernel_arguments*. + """ + kernel_args = {} for arg_name, arg_expr in six.iteritems(expr.kernel_arguments): rec_arg = mapper.rec(arg_expr) @@ -68,6 +86,15 @@ def _get_layer_potential_args(mapper, expr, source): def _get_kernel_args(mapper, kernel, expr, source): + """ + :arg mapper: a :class:`pytential.symbolic.primitives.EvaluationMapperBase`. + :arg kernel: a :class:`sumpy.kernel.Kernel`. + :arg expr: symbolic layer potential expression. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. + + :return: a mapping of kernel parameters included in *expr*. + """ + # NOTE: copied from pytential.symbolic.primitives.IntG inner_kernel_args = kernel.get_args() + kernel.get_source_args() inner_kernel_args = set(arg.loopy_arg.name for arg in inner_kernel_args) @@ -84,6 +111,14 @@ def _get_kernel_args(mapper, kernel, expr, source): def _get_weights_and_area_elements(queue, source, source_discr): + """ + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase` subclass. + :arg source_discr: a :class:`meshmode.discretization.Discretization`. + + :return: quadrature weights for each node in *source_discr* + """ + if source.quad_stage2_density_discr is source_discr: waa = source.weights_and_area_elements().with_queue(queue) else: @@ -98,6 +133,15 @@ def _get_weights_and_area_elements(queue, source, source_discr): def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_limit): + """ + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase` subclass. + :arg target_discr: a :class:`meshmode.discretization.Discretization`. + :arg qbx_forced_limit: an integer (*+1* or *-1*). + + :return: a tuple of `(centers, radii)` for each node in *target_discr*. + """ + if source.density_discr is target_discr: # NOTE: skip expensive target association from pytential.qbx.utils import get_centers_on_side -- GitLab From 96fb8e7e1d0f06521435ff0496779e9294792b3a Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 14 Aug 2018 20:53:28 -0500 Subject: [PATCH 230/268] matrix: fix issues with block matrix builders. Mainly added a more complicated operator in test_matrix. This exposed: * issues with the different map_variable implementations. * issues with resampling when unnneded, etc. --- pytential/symbolic/matrix.py | 276 ++++++++++++++++------------------- test/test_matrix.py | 43 +++--- 2 files changed, 153 insertions(+), 166 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 16396f6c..4a93421d 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -77,6 +77,13 @@ def _get_layer_potential_args(mapper, expr, source): :return: a mapping of kernel arguments *expr.kernel_arguments*. """ + # skip resampling if source and target are the same + from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET + if ((expr.source is not DEFAULT_SOURCE) and + (expr.target is not DEFAULT_TARGET) and + (type(expr.source) is type(expr.target))): + source = None + kernel_args = {} for arg_name, arg_expr in six.iteritems(expr.kernel_arguments): rec_arg = mapper.rec(arg_expr) @@ -174,16 +181,12 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim # }}} +# {{{ base class for matrix builders -# {{{ QBX layer potential matrix builder - -# FIXME: PyOpenCL doesn't do all the required matrix math yet. -# We'll cheat and build the matrix on the host. - -class MatrixBuilder(EvaluationMapperBase): +class MatrixBuilderBase(EvaluationMapperBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context): - super(MatrixBuilder, self).__init__(context=context) + super(MatrixBuilderBase, self).__init__(context=context) self.queue = queue self.dep_expr = dep_expr @@ -192,21 +195,38 @@ class MatrixBuilder(EvaluationMapperBase): self.dep_discr = dep_discr self.places = places + self.dep_nnodes = dep_discr.nnodes + + # {{{ + + def get_dep_variable(self): + return np.eye(self.dep_nnodes, dtype=np.float64) + + def is_kind_vector(self, x): + return len(x.shape) == 1 + + def is_kind_matrix(self, x): + return len(x.shape) == 2 + + # }}} + + # {{{ map_xxx implementation + def map_variable(self, expr): if expr == self.dep_expr: - return np.eye(self.dep_discr.nnodes, dtype=np.float64) + return self.get_dep_variable() elif expr in self.other_dep_exprs: return 0 else: - return super(MatrixBuilder, self).map_variable(expr) + return super(MatrixBuilderBase, self).map_variable(expr) def map_subscript(self, expr): if expr == self.dep_expr: - return np.eye(self.dep_discr.nnodes, dtype=np.float64) + return self.get_dep_variable() elif expr in self.other_dep_exprs: return 0 else: - return super(MatrixBuilder, self).map_subscript(expr) + return super(MatrixBuilderBase, self).map_subscript(expr) def map_sum(self, expr): sum_kind = None @@ -223,13 +243,12 @@ class MatrixBuilder(EvaluationMapperBase): continue if isinstance(rec_child, np.ndarray): - if len(rec_child.shape) == 2: + if self.is_kind_matrix(rec_child): term_kind = term_kind_matrix - elif len(rec_child.shape) == 1: + elif self.is_kind_vector(rec_child): term_kind = term_kind_vector else: raise RuntimeError("unexpected array rank") - else: term_kind = term_kind_scalar @@ -248,34 +267,84 @@ class MatrixBuilder(EvaluationMapperBase): mat_result = None vecs_and_scalars = 1 - for term in expr.children: - rec_term = self.rec(term) + for child in expr.children: + rec_child = self.rec(child) - if is_zero(rec_term): + if is_zero(rec_child): return 0 - if isinstance(rec_term, (np.number, int, float, complex)): - vecs_and_scalars = vecs_and_scalars * rec_term - elif isinstance(rec_term, np.ndarray): - if len(rec_term.shape) == 2: + if isinstance(rec_child, (np.number, int, float, complex)): + vecs_and_scalars = vecs_and_scalars * rec_child + elif isinstance(rec_child, np.ndarray): + if self.is_kind_matrix(rec_child): if mat_result is not None: raise RuntimeError("expression is nonlinear in %s" % self.dep_expr) else: - mat_result = rec_term + mat_result = rec_child else: - vecs_and_scalars = vecs_and_scalars * rec_term + vecs_and_scalars = vecs_and_scalars * rec_child if mat_result is not None: - if ( - isinstance(vecs_and_scalars, np.ndarray) - and len(vecs_and_scalars.shape) == 1): + if (isinstance(vecs_and_scalars, np.ndarray) + and self.is_kind_vector(vecs_and_scalars)): vecs_and_scalars = vecs_and_scalars[:, np.newaxis] return mat_result * vecs_and_scalars else: return vecs_and_scalars + def map_num_reference_derivative(self, expr): + rec_operand = self.rec(expr.operand) + + assert isinstance(rec_operand, np.ndarray) + if self.is_kind_matrix(rec_operand): + raise NotImplementedError("derivatives") + + where_discr = self.places[expr.where] + op = sym.NumReferenceDerivative(expr.ref_axes, sym.var("u")) + return bind(where_discr, op)( + self.queue, u=cl.array.to_device(self.queue, rec_operand)).get() + + def map_node_coordinate_component(self, expr): + where_discr = self.places[expr.where] + op = sym.NodeCoordinateComponent(expr.ambient_axis) + return bind(where_discr, op)(self.queue).get() + + def map_call(self, expr): + arg, = expr.parameters + rec_arg = self.rec(arg) + + if isinstance(rec_arg, np.ndarray) and self.is_kind_matrix(rec_arg): + raise RuntimeError("expression is nonlinear in variable") + + if isinstance(rec_arg, np.ndarray): + rec_arg = cl.array.to_device(self.queue, rec_arg) + + op = expr.function(sym.var("u")) + result = bind(self.dep_source, op)(self.queue, u=rec_arg) + + if isinstance(result, cl.array.Array): + result = result.get() + + return result + + # }}} + +# }}} + + +# {{{ QBX layer potential matrix builder + +# FIXME: PyOpenCL doesn't do all the required matrix math yet. +# We'll cheat and build the matrix on the host. + +class MatrixBuilder(MatrixBuilderBase): + def __init__(self, queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, context): + super(MatrixBuilder, self).__init__(queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, context) + def map_int_g(self, expr): # TODO: should this go into QBXPreprocessor / LocationTagger? where_source = expr.source @@ -291,7 +360,7 @@ class MatrixBuilder(EvaluationMapperBase): return 0 assert isinstance(rec_density, np.ndarray) - if len(rec_density.shape) != 2: + if not self.is_kind_matrix(rec_density): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel @@ -320,6 +389,7 @@ class MatrixBuilder(EvaluationMapperBase): mat[:, :] *= waa.get(self.queue) if target_discr.nnodes != source_discr.nnodes: + # NOTE: we only resample sources assert target_discr.nnodes < source_discr.nnodes resampler = source.direct_resampler @@ -330,49 +400,12 @@ class MatrixBuilder(EvaluationMapperBase): return mat - # IntGdSource should have been removed by a preprocessor - - def map_num_reference_derivative(self, expr): - rec_operand = self.rec(expr.operand) - - assert isinstance(rec_operand, np.ndarray) - if len(rec_operand.shape) == 2: - raise NotImplementedError("derivatives") - - where_discr = self.places[expr.where] - op = sym.NumReferenceDerivative(expr.ref_axes, sym.var("u")) - return bind(where_discr, op)( - self.queue, u=cl.array.to_device(self.queue, rec_operand)).get() - - def map_node_coordinate_component(self, expr): - where_discr = self.places[expr.where] - op = sym.NodeCoordinateComponent(expr.ambient_axis) - return bind(where_discr, op)(self.queue).get() - - def map_call(self, expr): - arg, = expr.parameters - rec_arg = self.rec(arg) - - if isinstance(rec_arg, np.ndarray) and len(rec_arg.shape) == 2: - raise RuntimeError("expression is nonlinear in variable") - - if isinstance(rec_arg, np.ndarray): - rec_arg = cl.array.to_device(self.queue, rec_arg) - - op = expr.function(sym.var("u")) - result = bind(self.dep_source, op)(self.queue, u=rec_arg) - - if isinstance(result, cl.array.Array): - result = result.get() - - return result - # }}} # {{{ p2p matrix builder -class P2PMatrixBuilder(MatrixBuilder): +class P2PMatrixBuilder(MatrixBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context, exclude_self=True): super(P2PMatrixBuilder, self).__init__(queue, @@ -391,7 +424,7 @@ class P2PMatrixBuilder(MatrixBuilder): return 0 assert isinstance(rec_density, np.ndarray) - if len(rec_density.shape) != 2: + if not self.is_kind_matrix(rec_density): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() @@ -418,72 +451,33 @@ class P2PMatrixBuilder(MatrixBuilder): # {{{ block matrix builders -class MatrixBlockBuilderBase(EvaluationMapperBase): +class MatrixBlockBuilderBase(MatrixBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context): - super(MatrixBlockBuilderBase, self).__init__(context=context) + super(MatrixBlockBuilderBase, self).__init__(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) - self.queue = queue - self.dep_expr = dep_expr - self.other_dep_exprs = other_dep_exprs - self.dep_source = dep_source - self.dep_discr = dep_discr - self.places = places self.index_set = index_set + self.dep_nnodes = index_set.col.indices.size - def _map_dep_variable(self): - return np.eye(self.index_set.col.indices.shape[0]) - - def map_variable(self, expr): - if expr == self.dep_expr: - return self._map_dep_variable() - elif expr in self.other_dep_exprs: - return 0 - else: - return super(MatrixBlockBuilderBase, self).map_variable(expr) - - def map_subscript(self, expr): - if expr == self.dep_expr: - return self.variable_identity() - elif expr in self.other_dep_exprs: - return 0 - else: - return super(MatrixBlockBuilderBase, self).map_subscript(expr) - - def map_num_reference_derivative(self, expr): - rec_operand = self.rec(expr.operand) - - assert isinstance(rec_operand, np.ndarray) - if len(rec_operand.shape) == 2: - raise NotImplementedError("derivatives") - - where_discr = self.places[expr.where] - op = sym.NumReferenceDerivative(expr.ref_axes, sym.var("u")) - return bind(where_discr, op)( - self.queue, u=cl.array.to_device(self.queue, rec_operand)).get() - - def map_node_coordinate_component(self, expr): - where_discr = self.places[expr.where] - op = sym.NodeCoordinateComponent(expr.ambient_axis) - return bind(where_discr, op)(self.queue).get() - - def map_call(self, expr): - arg, = expr.parameters - rec_arg = self.rec(arg) - - if isinstance(rec_arg, np.ndarray) and len(rec_arg.shape) == 2: - raise RuntimeError("expression is nonlinear in variable") - - if isinstance(rec_arg, np.ndarray): - rec_arg = cl.array.to_device(self.queue, rec_arg) + def get_dep_variable(self): + # NOTE: blocks are stored linearly, so the identity matrix for the + # variables themselves also needs to be flattened + tgtindices = self.index_set.linear_row_indices.get(self.queue) + srcindices = self.index_set.linear_col_indices.get(self.queue) - op = expr.function(sym.var("u")) - result = bind(self.dep_source, op)(self.queue, u=rec_arg) + return np.equal(tgtindices, srcindices).astype(np.float64) - if isinstance(result, cl.array.Array): - result = result.get() + def is_kind_vector(self, x): + # NOTE: since matrices are flattened, the only way to differentiate + # them from a vector is by size + return x.size == self.index_set.row.indices.size - return result + def is_kind_matrix(self, x): + # NOTE: since matrices are flattened, we recognize them by checking + # if they have the right size + return x.size == self.index_set.linear_row_indices.size class NearFieldBlockBuilder(MatrixBlockBuilderBase): @@ -493,15 +487,9 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context) - self.dummy = MatrixBlockBuilderBase(queue, + self.dummy = MatrixBuilderBase(queue, dep_expr, other_dep_exprs, dep_source, dep_discr, - places, index_set, context) - - def _map_dep_variable(self): - tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) - srcindices = self.index_set.col.indices.get(self.queue).reshape(1, -1) - - return np.equal(tgtindices, srcindices).astype(np.float64) + places, context) def map_int_g(self, expr): source = self.places[expr.source] @@ -511,16 +499,16 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): if source_discr is not target_discr: raise NotImplementedError() - rec_density = self.dummy.rec(expr.density) + rec_density = self.rec(expr.density) if is_zero(rec_density): return 0 assert isinstance(rec_density, np.ndarray) - if len(rec_density.shape) != 2: + if not self.is_kind_matrix(rec_density): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel - kernel_args = _get_layer_potential_args(self, expr, source) + kernel_args = _get_layer_potential_args(self.dummy, expr, source) from sumpy.expansion.local import LineTaylorLocalExpansion local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) @@ -557,32 +545,26 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context) - self.dummy = MatrixBlockBuilderBase(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, index_set, context) self.exclude_self = exclude_self - - def _map_dep_variable(self): - tgtindices = self.index_set.row.indices.get(self.queue).reshape(-1, 1) - srcindices = self.index_set.col.indices.get(self.queue).reshape(1, -1) - - return np.equal(tgtindices, srcindices).astype(np.float64) + self.dummy = MatrixBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) def map_int_g(self, expr): source = self.places[expr.source] source_discr = self.places.get_discretization(expr.source) target_discr = self.places.get_discretization(expr.target) - rec_density = self.dummy.rec(expr.density) + rec_density = self.rec(expr.density) if is_zero(rec_density): return 0 assert isinstance(rec_density, np.ndarray) - if len(rec_density.shape) != 2: + if not self.is_kind_matrix(rec_density): raise NotImplementedError("layer potentials on non-variables") kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self, kernel, expr, source) + kernel_args = _get_kernel_args(self.dummy, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) diff --git a/test/test_matrix.py b/test/test_matrix.py index d16dd32a..5aaae91f 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -50,7 +50,7 @@ from pyopencl.tools import ( # noqa def _build_op(lpot_id, k=0, ndim=2, - qbx_forced_limit=-1): + qbx_forced_limit="avg"): from sumpy.kernel import LaplaceKernel, HelmholtzKernel if k: @@ -76,15 +76,16 @@ def _build_op(lpot_id, u0_sym, u1_sym = u_sym op = make_obj_array([ - sym.Sp(knl, u0_sym, **lpot_kwargs) + - sym.D(knl, u1_sym, **lpot_kwargs), - - sym.S(knl, 0.4 * u0_sym, **lpot_kwargs) + - 0.3 * sym.D(knl, u0_sym, **lpot_kwargs) + sym.Sp(knl, u0_sym, **lpot_kwargs) + + sym.D(knl, u1_sym, **lpot_kwargs), + sym.S(knl, 0.4 * u0_sym, **lpot_kwargs) + + 0.3 * sym.D(knl, u0_sym, **lpot_kwargs) ]) else: raise ValueError("Unknown lpot_id: {}".format(lpot_id)) + op = 0.5 * u_sym + op + return op, u_sym, knl_kwargs @@ -94,7 +95,7 @@ def _build_op(lpot_id, @pytest.mark.parametrize("curve_f", [ partial(ellipse, 3), NArmedStarfish(5, 0.25)]) -@pytest.mark.parametrize("lpot_id", [1, 3]) +@pytest.mark.parametrize("lpot_id", [2, 3]) def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) @@ -212,26 +213,26 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) - from pytential.symbolic.matrix import FarFieldBlockBuilder - mbuilder = FarFieldBlockBuilder(queue, + from pytential.symbolic.matrix import P2PMatrixBuilder + mbuilder = P2PMatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], dep_source=places[domains[0]], dep_discr=places.get_discretization(domains[0]), places=places, - index_set=index_set, context={}) - blk = mbuilder(expr) + mat = mbuilder(expr) - from pytential.symbolic.matrix import P2PMatrixBuilder - mbuilder = P2PMatrixBuilder(queue, + from pytential.symbolic.matrix import FarFieldBlockBuilder + mbuilder = FarFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], dep_source=places[domains[0]], dep_discr=places.get_discretization(domains[0]), places=places, + index_set=index_set, context={}) - mat = mbuilder(expr) + blk = mbuilder(expr) index_set = index_set.get(queue) if visualize and ndim == 2: @@ -263,9 +264,11 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, assert error < eps +@pytest.mark.parametrize("factor", [1.0, 0.6]) @pytest.mark.parametrize("ndim", [2, 3]) -@pytest.mark.parametrize("lpot_id", [1]) -def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): +@pytest.mark.parametrize("lpot_id", [1, 2]) +def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, + visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -278,9 +281,6 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) op, u_sym, _ = _build_op(lpot_id, ndim=ndim) - tgtindices = _build_block_index(qbx.density_discr) - srcindices = _build_block_index(qbx.density_discr) - # NOTE: NearFieldBlockBuilder only does stage1/stage1 or stage2/stage2, # so we need to hardcode the discr for MatrixBuilder too, since the # defaults are different @@ -289,8 +289,13 @@ def test_qbx_block_builder(ctx_factory, ndim, lpot_id, visualize=False): from pytential.symbolic.execution import GeometryCollection, _prepare_expr places = GeometryCollection(qbx, auto_where=where) expr = _prepare_expr(places, op) + density_discr = places.get_discretization(where[0]) from sumpy.tools import MatrixBlockIndexRanges + srcindices = _build_block_index(density_discr, + method='nodes', factor=factor) + tgtindices = _build_block_index(density_discr, + method='nodes', factor=factor) index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) from pytential.symbolic.matrix import NearFieldBlockBuilder -- GitLab From 836d3d0f6d46219ceeb322f5e805825376d39caa Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Tue, 14 Aug 2018 21:04:49 -0500 Subject: [PATCH 231/268] matrix: flake8 fixes --- pytential/symbolic/execution.py | 20 ++++++++++---------- pytential/symbolic/matrix.py | 7 ++++--- test/test_matrix.py | 8 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index b1e8eb4a..4ebd2831 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -498,16 +498,16 @@ def _bmat(blocks, dtypes): ncolumns = blocks.shape[1] # "block row starts"/"block column starts" - brs = np.cumsum([0] + - [single_valued(blocks[ibrow, ibcol].shape[0] - for ibcol in range(ncolumns) - if not is_zero(blocks[ibrow, ibcol])) + brs = np.cumsum([0] + + [single_valued(blocks[ibrow, ibcol].shape[0] + for ibcol in range(ncolumns) + if not is_zero(blocks[ibrow, ibcol])) for ibrow in range(nrows)]) - bcs = np.cumsum([0] + - [single_valued(blocks[ibrow, ibcol].shape[1] - for ibrow in range(nrows) - if not is_zero(blocks[ibrow, ibcol])) + bcs = np.cumsum([0] + + [single_valued(blocks[ibrow, ibcol].shape[1] + for ibrow in range(nrows) + if not is_zero(blocks[ibrow, ibcol])) for ibcol in range(ncolumns)]) result = np.zeros((brs[-1], bcs[-1]), @@ -571,8 +571,8 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, mbuilder = MatrixBuilder( queue, dep_expr=input_exprs[ibcol], - other_dep_exprs=(input_exprs[:ibcol] + - input_exprs[ibcol + 1:]), + other_dep_exprs=(input_exprs[:ibcol] + + input_exprs[ibcol + 1:]), dep_source=places[domains[ibcol]], dep_discr=places.get_discretization(domains[ibcol]), places=places, diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 4a93421d..6a1e619b 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -79,9 +79,9 @@ def _get_layer_potential_args(mapper, expr, source): # skip resampling if source and target are the same from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET - if ((expr.source is not DEFAULT_SOURCE) and - (expr.target is not DEFAULT_TARGET) and - (type(expr.source) is type(expr.target))): + if ((expr.source is not DEFAULT_SOURCE) + and (expr.target is not DEFAULT_TARGET) + and (isinstance(expr.source, type(expr.target)))): source = None kernel_args = {} @@ -181,6 +181,7 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim # }}} + # {{{ base class for matrix builders class MatrixBuilderBase(EvaluationMapperBase): diff --git a/test/test_matrix.py b/test/test_matrix.py index 5aaae91f..0c26019c 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -256,8 +256,8 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, for i in range(index_set.nblocks): eps = 1.0e-14 * la.norm(index_set.take(mat, i)) - error = la.norm(index_set.block_take(blk, i) - - index_set.take(mat, i)) + error = la.norm(index_set.block_take(blk, i) + - index_set.take(mat, i)) if visualize: print('block[{:04}]: {:.5e}'.format(i, error)) @@ -341,8 +341,8 @@ def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, for i in range(index_set.nblocks): eps = 1.0e-14 * la.norm(index_set.take(mat, i)) - error = la.norm(index_set.block_take(blk, i) - - index_set.take(mat, i)) + error = la.norm(index_set.block_take(blk, i) + - index_set.take(mat, i)) if visualize: print('block[{:04}]: {:.5e}'.format(i, error)) -- GitLab From 7f1416a3e72172d2611b5b2073892528a00b2caf Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 15 Aug 2018 20:05:29 -0500 Subject: [PATCH 232/268] execution: add some docs to GeometryCollection --- pytential/symbolic/execution.py | 76 ++++++++++++++++++++++++--------- pytential/symbolic/matrix.py | 59 +++++++++++++------------ test/test_matrix.py | 12 +++--- 3 files changed, 96 insertions(+), 51 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 4ebd2831..6ca61346 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -286,7 +286,7 @@ class MatVecOp: def _prepare_domains(nresults, places, domains, default_domain): """ :arg nresults: number of results. - :arg places: :class:`pytential.symbolic.execution.GeometryCollection`. + :arg places: a :class:`pytential.symbolic.execution.GeometryCollection`. :arg domains: recommended domains. :arg default_domain: default value for domains which are not provided. @@ -302,7 +302,7 @@ def _prepare_domains(nresults, places, domains, default_domain): "default domain to be defined in places") dom_name = default_domain return nresults * [dom_name] - if not isinstance(domains, (list, tuple)): + elif not isinstance(domains, (list, tuple)): dom_name = domains return nresults * [dom_name] @@ -337,10 +337,34 @@ def _prepare_expr(places, expr): # {{{ bound expression class GeometryCollection(object): + """A collection of geometry-related objects. This class is meant to hold + a specific combination of sources and targets and cache any common + expressions specific to it, e.g. metric terms, etc. + + .. method:: __getitem__ + .. method:: get_discretization + .. method:: get_cache + """ + def __init__(self, places, auto_where=None): + """ + :arg places: a scalar, tuple of or mapping of symbolic names to + geometry objects. Supported objects are + :class:`~pytential.source.PotentialSource`, + :class:`~potential.target.TargetBase` and + :class:`~meshmode.discretization.Discretization`. + :arg auto_where: location identifier for each geometry object, used + to denote specific discretizations, e.g. in the case where + *places* is a :class:`~pytential.source.LayerPotentialSourceBase`. + By default, we assume + :class:`~pytential.symbolic.primitives.DEFAULT_SOURCE` and + :class:`~pytential.symbolic.primitives.DEFAULT_TARGET` for + sources and targets, respectively. + """ + + from pytential.target import TargetBase from meshmode.discretization import Discretization from pytential.source import LayerPotentialSourceBase, PotentialSource - from pytential.target import TargetBase if auto_where is None: auto_where = DEFAULT_SOURCE, DEFAULT_TARGET @@ -350,7 +374,7 @@ class GeometryCollection(object): if isinstance(places, LayerPotentialSourceBase): self.places[self.source] = places self.places[self.target] = \ - self.get_discretization(self.target, lpot=places) + self._get_lpot_discretization(self.target, places) elif isinstance(places, (Discretization, TargetBase)): self.places[self.target] = places elif isinstance(places, tuple): @@ -375,20 +399,14 @@ class GeometryCollection(object): def target(self): return self.where[1] - def get_discretization(self, where, lpot=None): - if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: - where = QBXSourceStage1(where) - - if lpot is None: - if where in self.places: - lpot = self.places[where] - else: - lpot = self.places.get(where.where, None) - + def _get_lpot_discretization(self, where, lpot): from pytential.source import LayerPotentialSourceBase if not isinstance(lpot, LayerPotentialSourceBase): return lpot + if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: + where = QBXSourceStage1(where) + if isinstance(where, QBXSourceStage1): return lpot.density_discr if isinstance(where, QBXSourceStage2): @@ -396,7 +414,28 @@ class GeometryCollection(object): if isinstance(where, QBXSourceQuadStage2): return lpot.quad_stage2_density_discr - raise ValueError('Unknown `where` identifier: {}'.format(type(where))) + raise ValueError('unknown `where` identifier: {}'.format(type(where))) + + def get_discretization(self, where): + """ + :arg where: location identifier. + + :return: a geometry object in the collection corresponding to the + key *where*. If it is a + :class:`~pytential.source.LayerPotentialSourceBase`, we look for + the corresponding :class:`~meshmode.discretization.Discretization` + in its attributes instead. + """ + + if where in self.places: + lpot = self.places[where] + else: + lpot = self.places.get(getattr(where, 'where', None), None) + + if lpot is None: + raise KeyError('`where` not in the collection: {}'.format(where)) + + return self._get_lpot_discretization(where, lpot) def __getitem__(self, where): return self.places[where] @@ -471,10 +510,9 @@ class BoundExpression(object): def bind(places, expr, auto_where=None): """ - :arg places: a collection or mapping of symbolic names to - :class:`meshmode.discretization.Discretization` objects, subclasses - of :class:`pytential.source.PotentialSource` or subclasses of - :class:`pytential.target.TargetBase`. + :arg places: a :class:`pytential.symbolic.execution.GeometryCollection`. + Alternatively, any list or mapping that is a valid argument for its + constructor can also be used. :arg auto_where: for simple source-to-self or source-to-target evaluations, find 'where' attributes automatically. """ diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 6a1e619b..01e60caf 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -70,11 +70,11 @@ def _resample_arg(queue, source, x): def _get_layer_potential_args(mapper, expr, source): """ - :arg mapper: a :class:`pytential.symbolic.primitives.EvaluationMapperBase`. + :arg mapper: a :class:`pytential.symbolic.matrix.MatrixBuilderBase`. :arg expr: symbolic layer potential expression. :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. - :return: a mapping of kernel arguments *expr.kernel_arguments*. + :return: a mapping of kernel arguments evaluated by the *mapper*. """ # skip resampling if source and target are the same @@ -94,12 +94,12 @@ def _get_layer_potential_args(mapper, expr, source): def _get_kernel_args(mapper, kernel, expr, source): """ - :arg mapper: a :class:`pytential.symbolic.primitives.EvaluationMapperBase`. + :arg mapper: a :class:`pytential.symbolic.matrix.MatrixBuilderBase`. :arg kernel: a :class:`sumpy.kernel.Kernel`. :arg expr: symbolic layer potential expression. :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. - :return: a mapping of kernel parameters included in *expr*. + :return: a mapping of kernel arguments evaluated by the *mapper*. """ # NOTE: copied from pytential.symbolic.primitives.IntG @@ -120,10 +120,10 @@ def _get_kernel_args(mapper, kernel, expr, source): def _get_weights_and_area_elements(queue, source, source_discr): """ :arg queue: a :class:`pyopencl.CommandQueue`. - :arg source: a :class:`pytential.source.LayerPotentialSourceBase` subclass. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. :arg source_discr: a :class:`meshmode.discretization.Discretization`. - :return: quadrature weights for each node in *source_discr* + :return: quadrature weights for each node in *source_discr*. """ if source.quad_stage2_density_discr is source_discr: @@ -142,7 +142,7 @@ def _get_weights_and_area_elements(queue, source, source_discr): def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_limit): """ :arg queue: a :class:`pyopencl.CommandQueue`. - :arg source: a :class:`pytential.source.LayerPotentialSourceBase` subclass. + :arg source: a :class:`pytential.source.LayerPotentialSourceBase`. :arg target_discr: a :class:`meshmode.discretization.Discretization`. :arg qbx_forced_limit: an integer (*+1* or *-1*). @@ -187,6 +187,20 @@ def _get_centers_and_expansion_radii(queue, source, target_discr, qbx_forced_lim class MatrixBuilderBase(EvaluationMapperBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context): + """ + :arg queue: a :class:`pyopencl.CommandQueue`. + :arg dep_expr: symbolic expression for the input block column + that the builder is evaluating. + :arg other_dep_exprs: symbolic expressions for the remaining input + block columns. + :arg dep_source: a :class:`pytential.source.LayerPotentialSourceBase` + for the given *dep_expr*. + :arg dep_discr: a concerete :class:`meshmode.discretization.Discretization` + for the given *dep_expr*. + :arg places: a :class:`pytential.symbolic.execution.GeometryCollection` + for all the sources and targets the builder is expected to + encounter. + """ super(MatrixBuilderBase, self).__init__(context=context) self.queue = queue @@ -347,7 +361,6 @@ class MatrixBuilder(MatrixBuilderBase): dep_source, dep_discr, places, context) def map_int_g(self, expr): - # TODO: should this go into QBXPreprocessor / LocationTagger? where_source = expr.source if where_source is sym.DEFAULT_SOURCE: where_source = sym.QBXSourceQuadStage2(expr.source) @@ -463,12 +476,10 @@ class MatrixBlockBuilderBase(MatrixBuilderBase): self.dep_nnodes = index_set.col.indices.size def get_dep_variable(self): - # NOTE: blocks are stored linearly, so the identity matrix for the - # variables themselves also needs to be flattened - tgtindices = self.index_set.linear_row_indices.get(self.queue) - srcindices = self.index_set.linear_col_indices.get(self.queue) - - return np.equal(tgtindices, srcindices).astype(np.float64) + # NOTE: block builders only support identity operators acting on + # the density. If other operators are needed, the user is meant to + # do the required matrix-matrix products + return 1.0 def is_kind_vector(self, x): # NOTE: since matrices are flattened, the only way to differentiate @@ -504,9 +515,8 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): if is_zero(rec_density): return 0 - assert isinstance(rec_density, np.ndarray) - if not self.is_kind_matrix(rec_density): - raise NotImplementedError("layer potentials on non-variables") + if not np.isscalar(rec_density): + raise NotImplementedError() kernel = expr.kernel kernel_args = _get_layer_potential_args(self.dummy, expr, source) @@ -532,16 +542,14 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): waa = _get_weights_and_area_elements(self.queue, source, source_discr) mat *= waa[self.index_set.linear_col_indices] - mat = mat.get(self.queue) - - # TODO: multiply with rec_density + mat = rec_density * mat.get(self.queue) return mat class FarFieldBlockBuilder(MatrixBlockBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, - places, index_set, context, exclude_self=True): + places, index_set, context, exclude_self=False): super(FarFieldBlockBuilder, self).__init__(queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context) @@ -560,9 +568,8 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): if is_zero(rec_density): return 0 - assert isinstance(rec_density, np.ndarray) - if not self.is_kind_matrix(rec_density): - raise NotImplementedError("layer potentials on non-variables") + if not np.isscalar(rec_density): + raise NotImplementedError() kernel = expr.kernel.get_base_kernel() kernel_args = _get_kernel_args(self.dummy, kernel, expr, source) @@ -579,9 +586,7 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): sources=source_discr.nodes(), index_set=self.index_set, **kernel_args) - mat = mat.get() - - # TODO: need to multiply by rec_density + mat = rec_density * mat.get(self.queue) return mat diff --git a/test/test_matrix.py b/test/test_matrix.py index 0c26019c..69f923db 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -65,11 +65,11 @@ def _build_op(lpot_id, if lpot_id == 1: # scalar single-layer potential u_sym = sym.var("u") - op = sym.S(knl, u_sym, **lpot_kwargs) + op = sym.S(knl, 0.3 * u_sym, **lpot_kwargs) elif lpot_id == 2: # scalar double-layer potential u_sym = sym.var("u") - op = sym.D(knl, u_sym, **lpot_kwargs) + op = sym.D(knl, 0.3 * u_sym, **lpot_kwargs) elif lpot_id == 3: # vector potential u_sym = sym.make_sym_vector("u", 2) @@ -84,7 +84,7 @@ def _build_op(lpot_id, else: raise ValueError("Unknown lpot_id: {}".format(lpot_id)) - op = 0.5 * u_sym + op + # op = 0.5 * u_sym + op return op, u_sym, knl_kwargs @@ -220,7 +220,8 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, dep_source=places[domains[0]], dep_discr=places.get_discretization(domains[0]), places=places, - context={}) + context={}, + exclude_self=True) mat = mbuilder(expr) from pytential.symbolic.matrix import FarFieldBlockBuilder @@ -231,7 +232,8 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, dep_discr=places.get_discretization(domains[0]), places=places, index_set=index_set, - context={}) + context={}, + exclude_self=True) blk = mbuilder(expr) index_set = index_set.get(queue) -- GitLab From 4c6373744c8a23b59fe261ee9c3e244a3bd3a0ed Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 15 Aug 2018 20:31:50 -0500 Subject: [PATCH 233/268] matrix: make sure block builders don't allow composition --- pytential/symbolic/matrix.py | 121 +++++++++++++++++++++++------------ test/test_matrix.py | 9 +-- 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 01e60caf..77cc31ae 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -346,6 +346,44 @@ class MatrixBuilderBase(EvaluationMapperBase): # }}} + +class MatrixBlockBuilderBase(MatrixBuilderBase): + """Evaluate individual blocks of a matrix operator. + + Unlike, e.g. :class:`MatrixBuilder`, matrix block builders are + significantly reduced in scope. They are basically just meant + to evaluate linear combinations of layer potential operators. + For example, they do not support composition of operators because we + assume that each operator acts directly on the density. + """ + + def __init__(self, queue, dep_expr, other_dep_exprs, + dep_source, dep_discr, places, index_set, context): + """ + :arg index_set: a :class:`sumpy.tools.MatrixBlockIndexRanges` class + describing which blocks are going to be evaluated. + """ + + super(MatrixBlockBuilderBase, self).__init__(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) + + self.index_set = index_set + self.dep_nnodes = index_set.col.indices.size + + def get_dep_variable(self): + return 1.0 + + def is_kind_vector(self, x): + # NOTE: since matrices are flattened, the only way to differentiate + # them from a vector is by size + return x.size == self.index_set.row.indices.size + + def is_kind_matrix(self, x): + # NOTE: since matrices are flattened, we recognize them by checking + # if they have the right size + return x.size == self.index_set.linear_row_indices.size + # }}} @@ -465,43 +503,31 @@ class P2PMatrixBuilder(MatrixBuilderBase): # {{{ block matrix builders -class MatrixBlockBuilderBase(MatrixBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, - dep_source, dep_discr, places, index_set, context): - super(MatrixBlockBuilderBase, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, context) - - self.index_set = index_set - self.dep_nnodes = index_set.col.indices.size - - def get_dep_variable(self): - # NOTE: block builders only support identity operators acting on - # the density. If other operators are needed, the user is meant to - # do the required matrix-matrix products - return 1.0 - - def is_kind_vector(self, x): - # NOTE: since matrices are flattened, the only way to differentiate - # them from a vector is by size - return x.size == self.index_set.row.indices.size - - def is_kind_matrix(self, x): - # NOTE: since matrices are flattened, we recognize them by checking - # if they have the right size - return x.size == self.index_set.linear_row_indices.size - - class NearFieldBlockBuilder(MatrixBlockBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context): super(NearFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, index_set, context) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) + + # NOTE: we need additional mappers to redirect some operations: + # * mat_mapper is used to compute any kernel arguments that need to + # be computed on the full discretization, ignoring our index_set, + # e.g the normal in a double layer potential + # * blk_mapper is used to recursively compute the density to + # a layer potential operator to ensure there is no composition + self.mat_mapper = MatrixBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) + self.blk_mapper = MatrixBlockBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) - self.dummy = MatrixBuilderBase(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, context) + def get_dep_variable(self): + tgtindices = self.index_set.linear_row_indices.get(self.queue) + srcindices = self.index_set.linear_col_indices.get(self.queue) + + return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): source = self.places[expr.source] @@ -511,7 +537,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): if source_discr is not target_discr: raise NotImplementedError() - rec_density = self.rec(expr.density) + rec_density = self.blk_mapper.rec(expr.density) if is_zero(rec_density): return 0 @@ -519,7 +545,7 @@ class NearFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError() kernel = expr.kernel - kernel_args = _get_layer_potential_args(self.dummy, expr, source) + kernel_args = _get_layer_potential_args(self.mat_mapper, expr, source) from sumpy.expansion.local import LineTaylorLocalExpansion local_expn = LineTaylorLocalExpansion(kernel, source.qbx_order) @@ -551,20 +577,33 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, places, index_set, context, exclude_self=False): super(FarFieldBlockBuilder, self).__init__(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, index_set, context) + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) + # NOTE: same mapper issues as in the NearFieldBlockBuilder self.exclude_self = exclude_self - self.dummy = MatrixBuilderBase(queue, - dep_expr, other_dep_exprs, dep_source, dep_discr, - places, context) + self.mat_mapper = MatrixBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, context) + self.blk_mapper = MatrixBlockBuilderBase(queue, + dep_expr, other_dep_exprs, dep_source, dep_discr, + places, index_set, context) + + def get_dep_variable(self): + tgtindices = self.index_set.linear_row_indices.get(self.queue) + srcindices = self.index_set.linear_col_indices.get(self.queue) + + return np.equal(tgtindices, srcindices).astype(np.float64) def map_int_g(self, expr): source = self.places[expr.source] source_discr = self.places.get_discretization(expr.source) target_discr = self.places.get_discretization(expr.target) - rec_density = self.rec(expr.density) + if source_discr is not target_discr: + raise NotImplementedError() + + rec_density = self.blk_mapper.rec(expr.density) if is_zero(rec_density): return 0 @@ -572,7 +611,7 @@ class FarFieldBlockBuilder(MatrixBlockBuilderBase): raise NotImplementedError() kernel = expr.kernel.get_base_kernel() - kernel_args = _get_kernel_args(self.dummy, kernel, expr, source) + kernel_args = _get_kernel_args(self.mat_mapper, kernel, expr, source) if self.exclude_self: kernel_args["target_to_source"] = \ cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) diff --git a/test/test_matrix.py b/test/test_matrix.py index 69f923db..02eaca0d 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -65,11 +65,12 @@ def _build_op(lpot_id, if lpot_id == 1: # scalar single-layer potential u_sym = sym.var("u") - op = sym.S(knl, 0.3 * u_sym, **lpot_kwargs) + op = sym.S(knl, u_sym, **lpot_kwargs) elif lpot_id == 2: - # scalar double-layer potential + # scalar combination of layer potentials u_sym = sym.var("u") - op = sym.D(knl, 0.3 * u_sym, **lpot_kwargs) + op = sym.S(knl, 0.3 * u_sym, **lpot_kwargs) \ + + sym.D(knl, 0.5 * u_sym, **lpot_kwargs) elif lpot_id == 3: # vector potential u_sym = sym.make_sym_vector("u", 2) @@ -84,7 +85,7 @@ def _build_op(lpot_id, else: raise ValueError("Unknown lpot_id: {}".format(lpot_id)) - # op = 0.5 * u_sym + op + op = 0.5 * u_sym + op return op, u_sym, knl_kwargs -- GitLab From 79369904e7c71e09485404715625fc62d594becd Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 15 Aug 2018 21:08:56 -0500 Subject: [PATCH 234/268] execution: remove some properties from GeometryCollection --- pytential/symbolic/execution.py | 37 +++++++++++++-------------------- test/test_matrix.py | 5 +++-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 6ca61346..71300d83 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -367,20 +367,22 @@ class GeometryCollection(object): from pytential.source import LayerPotentialSourceBase, PotentialSource if auto_where is None: - auto_where = DEFAULT_SOURCE, DEFAULT_TARGET - self.where = auto_where + source_where, target_where = DEFAULT_SOURCE, DEFAULT_TARGET + else: + source_where, target_where = auto_where + self.where = (source_where, target_where) self.places = {} if isinstance(places, LayerPotentialSourceBase): - self.places[self.source] = places - self.places[self.target] = \ - self._get_lpot_discretization(self.target, places) + self.places[source_where] = places + self.places[target_where] = \ + self._get_lpot_discretization(target_where, places) elif isinstance(places, (Discretization, TargetBase)): - self.places[self.target] = places + self.places[target_where] = places elif isinstance(places, tuple): source_discr, target_discr = places - self.places[self.source] = source_discr - self.places[self.target] = target_discr + self.places[source_where] = source_discr + self.places[target_where] = target_discr else: self.places = places.copy() @@ -391,14 +393,6 @@ class GeometryCollection(object): self.caches = {} - @property - def source(self): - return self.where[0] - - @property - def target(self): - return self.where[1] - def _get_lpot_discretization(self, where, lpot): from pytential.source import LayerPotentialSourceBase if not isinstance(lpot, LayerPotentialSourceBase): @@ -562,11 +556,9 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, auto_where=None, context=None): """ :arg queue: a :class:`pyopencl.CommandQueue`. - :arg places: a collection or mapping of symbolic names (see also - :class:`pytential.symbolic.execution.GeometryCollection`) to - :class:`meshmode.discretization.Discretization` objects, subclasses - of :class:`pytential.source.PotentialSource` or subclasses of - :class:`pytential.target.TargetBase`. + :arg places: a :class:`pytential.symbolic.execution.GeometryCollection`. + Alternatively, any list or mapping that is a valid argument for its + constructor can also be used. :arg exprs: an array of expressions corresponding to the output block rows of the matrix. May also be a single expression. :arg input_exprs: an array of expressions corresponding to the @@ -597,7 +589,8 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, # not iterable, wrap in a list input_exprs = [input_exprs] - domains = _prepare_domains(len(input_exprs), places, domains, places.source) + domains = _prepare_domains(len(input_exprs), places, domains, + DEFAULT_SOURCE) from pytential.symbolic.matrix import MatrixBuilder, is_zero nblock_rows = len(exprs) diff --git a/test/test_matrix.py b/test/test_matrix.py index 02eaca0d..3a11bf3a 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -209,7 +209,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, from pytential.symbolic.execution import _prepare_expr, _prepare_domains places = GeometryCollection(qbx) expr = _prepare_expr(places, op) - domains = _prepare_domains(1, places, None, places.source) + domains = _prepare_domains(1, places, None, DEFAULT_SOURCE) from sumpy.tools import MatrixBlockIndexRanges index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) @@ -376,7 +376,8 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): # build full QBX matrix from pytential.symbolic.execution import build_matrix - mat = build_matrix(queue, qbx, op, u_sym, auto_where=where) + mat = build_matrix(queue, qbx, op, u_sym, + auto_where=where, domains=where[0]) from pytential.symbolic.execution import GeometryCollection places = GeometryCollection(qbx, auto_where=where) -- GitLab From 26d00306de3ba2245014c1b623debb69e51d7463 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 15 Aug 2018 21:35:43 -0500 Subject: [PATCH 235/268] add some copyrights --- pytential/symbolic/execution.py | 5 ++++- pytential/symbolic/matrix.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 71300d83..a6528211 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2013 Andreas Kloeckner +Copyright (C) 2018 Alexandru Fikl +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 77cc31ae..9d0c2b2f 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import -__copyright__ = "Copyright (C) 2015 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2015 Andreas Kloeckner +Copyright (C) 2018 Alexandru Fikl +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy -- GitLab From b5e22bbc003277c1502dc753cbb6410b8966c271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Tue, 21 Aug 2018 13:07:39 -0400 Subject: [PATCH 236/268] Preinstall pybind11 for pyopencl --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3220e44b..ea980802 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ Python 3.5 POCL: script: - export PY_EXE=python3.5 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako" + - export EXTRA_INSTALL="pybind11 numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -16,7 +16,7 @@ Python 3.6 POCL: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako" + - export EXTRA_INSTALL="pybind11 numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -30,7 +30,7 @@ Python 3.6 POCL Examples: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako pyvisfile matplotlib" + - export EXTRA_INSTALL="pybind11 numpy mako pyvisfile matplotlib" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-py-project-and-run-examples.sh - ". ./build-py-project-and-run-examples.sh" tags: @@ -57,7 +57,7 @@ Python 2.7 POCL: script: - export PY_EXE=python2.7 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="numpy mako" + - export EXTRA_INSTALL="pybind11 numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -84,7 +84,7 @@ Python 3.6 Conda Apple: Documentation: script: - - EXTRA_INSTALL="numpy mako" + - EXTRA_INSTALL="pybind11 numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-docs.sh - ". ./build-docs.sh" tags: -- GitLab From fd1eb9ff262718b482813ed7bcabfa1392d5d163 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Wed, 22 Aug 2018 18:59:35 -0500 Subject: [PATCH 237/268] execution: store default source and target place ids in GeometryCollection --- pytential/symbolic/execution.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index a6528211..87b32df1 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -325,7 +325,7 @@ def _prepare_expr(places, expr): from pytential.symbolic.mappers import ( ToTargetTagger, DerivativeBinder) - expr = ToTargetTagger(*places.where)(expr) + expr = ToTargetTagger(*places._default_place_ids)(expr) expr = DerivativeBinder()(expr) for name, place in six.iteritems(places.places): @@ -372,8 +372,13 @@ class GeometryCollection(object): if auto_where is None: source_where, target_where = DEFAULT_SOURCE, DEFAULT_TARGET else: + # NOTE: keeping this here to make sure auto_where unpacks into + # just the two elements source_where, target_where = auto_where - self.where = (source_where, target_where) + + self._default_source_place = source_where + self._default_target_place = target_where + self._auto_place_ids = (source_where, target_where) self.places = {} if isinstance(places, LayerPotentialSourceBase): -- GitLab From 3a48efcf4aba6aecf65d0658d1f76f00864af5b7 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 24 Aug 2018 13:24:54 -0500 Subject: [PATCH 238/268] Re-enable test_sphere_eigenvalues() --- test/test_layer_pot_eigenvalues.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index b10a9076..c5f927c2 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -252,7 +252,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, "sumpy", "fmmlib", ]) -def no_test_sphere_eigenvalues(ctx_getter, mode_m, mode_n, qbx_order, +def test_sphere_eigenvalues(ctx_getter, mode_m, mode_n, qbx_order, fmm_backend): logging.basicConfig(level=logging.INFO) -- GitLab From e337e88756ff5cc94f7c0c5019b7c6535be6f580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Fri, 24 Aug 2018 14:58:16 -0400 Subject: [PATCH 239/268] Install scipy to make sphere eigenvalue test run --- .gitlab-ci.yml | 6 +++--- .test-conda-env-py3-macos.yml | 1 + .test-conda-env-py3.yml | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea980802..173861e7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ Python 3.5 POCL: script: - export PY_EXE=python3.5 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="pybind11 numpy mako" + - export EXTRA_INSTALL="pybind11 numpy scipy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -16,7 +16,7 @@ Python 3.6 POCL: script: - export PY_EXE=python3.6 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="pybind11 numpy mako" + - export EXTRA_INSTALL="pybind11 numpy scipy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -57,7 +57,7 @@ Python 2.7 POCL: script: - export PY_EXE=python2.7 - export PYOPENCL_TEST=portable - - export EXTRA_INSTALL="pybind11 numpy mako" + - export EXTRA_INSTALL="pybind11 scipy numpy mako" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml index 3664a455..7c0601cb 100644 --- a/.test-conda-env-py3-macos.yml +++ b/.test-conda-env-py3-macos.yml @@ -6,6 +6,7 @@ dependencies: - git - conda-forge::numpy - conda-forge::sympy +- scipy - pocl=1.0 - islpy - pyopencl diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index 8d60a1f0..16e05373 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -4,6 +4,7 @@ channels: - defaults dependencies: - git +- scipy - conda-forge::numpy - conda-forge::sympy - pocl=1.0 -- GitLab From 3283f73d4a3b3facc4871668266ad4ff7d62bc61 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 27 Aug 2018 08:33:49 -0500 Subject: [PATCH 240/268] Add default value to position args of __new__ --- pytential/symbolic/primitives.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 3a297fd1..ee893ce7 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -378,12 +378,12 @@ class NumReferenceDerivative(DiscretizationProperty): reference coordinates. """ - def __new__(cls, ref_axes, operand, where=None): + def __new__(cls, ref_axes=None, operand=None, where=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the # coefficients in the multivector. - if isinstance(operand, (np.ndarray)): + if isinstance(operand, np.ndarray): def make_op(operand_i): return cls(ref_axes, operand_i, where=where) @@ -750,7 +750,7 @@ def _scaled_max_curvature(ambient_dim, dim=None, where=None): # {{{ operators class SingleScalarOperandExpression(Expression): - def __new__(cls, operand): + def __new__(cls, operand=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the # coefficients in the multivector. @@ -792,7 +792,7 @@ def integral(ambient_dim, dim, operand, where=None): class SingleScalarOperandExpressionWithWhere(Expression): - def __new__(cls, operand, where=None): + def __new__(cls, operand=None, where=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the # coefficients in the multivector. @@ -982,7 +982,7 @@ class IntG(Expression): where :math:`\sigma` is *density*. """ - def __new__(cls, kernel, density, *args, **kwargs): + def __new__(cls, kernel=None, density=None, *args, **kwargs): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the # coefficients in the multivector. -- GitLab From 0c82577817018841f36c77db82018f37d3c6bca7 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 27 Aug 2018 08:52:25 -0500 Subject: [PATCH 241/268] Add init_arg_names to subclasses of Expression --- pytential/symbolic/primitives.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index ee893ce7..8c566a50 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -328,6 +328,8 @@ class DiscretizationProperty(Expression): further arguments. """ + init_arg_names = ("where",) + def __init__(self, where=None): """ :arg where: |where-blurb| @@ -348,6 +350,9 @@ class QWeight(DiscretizationProperty): class NodeCoordinateComponent(DiscretizationProperty): + + init_arg_names = ("ambient_axis", "where") + def __init__(self, ambient_axis, where=None): """ :arg where: |where-blurb| @@ -378,6 +383,8 @@ class NumReferenceDerivative(DiscretizationProperty): reference coordinates. """ + init_arg_names = ("ref_axes", "operand", "where") + def __new__(cls, ref_axes=None, operand=None, where=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the @@ -750,6 +757,9 @@ def _scaled_max_curvature(ambient_dim, dim=None, where=None): # {{{ operators class SingleScalarOperandExpression(Expression): + + init_arg_names = ("operand",) + def __new__(cls, operand=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the @@ -792,6 +802,9 @@ def integral(ambient_dim, dim, operand, where=None): class SingleScalarOperandExpressionWithWhere(Expression): + + init_arg_names = ("operand", "where") + def __new__(cls, operand=None, where=None): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the @@ -842,6 +855,8 @@ class Ones(Expression): discretization. """ + init_arg_names = ("where",) + def __init__(self, where=None): self.where = where @@ -870,6 +885,9 @@ def mean(ambient_dim, dim, operand, where=None): class IterativeInverse(Expression): + + init_arg_names = ("expression", "rhs", "variable_name", "extra_vars", "where") + def __init__(self, expression, rhs, variable_name, extra_vars={}, where=None): self.expression = expression @@ -982,6 +1000,9 @@ class IntG(Expression): where :math:`\sigma` is *density*. """ + init_arg_names = ("kernel", "density", "qbx_forced_limit", "source", "target", + "kernel_arguments") + def __new__(cls, kernel=None, density=None, *args, **kwargs): # If the constructor is handed a multivector object, return an # object array of the operator applied to each of the -- GitLab From 26091ddb1429dbf5073607efcfb652c014320e3d Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 27 Aug 2018 09:49:03 -0500 Subject: [PATCH 242/268] Convert IntG.kernel_arguments back to dict upon unpickling --- pytential/symbolic/primitives.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 8c566a50..ea7b1fc0 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1045,8 +1045,8 @@ class IntG(Expression): :arg kernel_arguments: A dictionary mapping named :class:`sumpy.kernel.Kernel` arguments (see :meth:`sumpy.kernel.Kernel.get_args` - and :meth:`sumpy.kernel.Kernel.get_source_args` - to expressions that determine them) + and :meth:`sumpy.kernel.Kernel.get_source_args`) + to expressions that determine them :arg source: The symbolic name of the source discretization. This name is bound to a concrete :class:`pytential.source.LayerPotentialSourceBase` @@ -1135,6 +1135,17 @@ class IntG(Expression): self.source, self.target, hashable_kernel_args(self.kernel_arguments)) + def __setstate__(self, state): + # Overwrite pymbolic.Expression.__setstate__ + assert len(self.init_arg_names) == len(state), type(self) + for name, value in zip(self.init_arg_names, state): + if name == 'kernel_arguments': + value = dict(value) + for dict_name in value: + value[dict_name] = np.array(value[dict_name], dtype=object) + + setattr(self, name, value) + mapper_method = intern("map_int_g") -- GitLab From af12f31a9450f1d57e09464287f81c18ced7b83b Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 27 Aug 2018 09:53:51 -0500 Subject: [PATCH 243/268] Add a test case for pickling expressions --- test/test_symbolic.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/test_symbolic.py b/test/test_symbolic.py index 8b2e7cb4..c0a35ffa 100644 --- a/test/test_symbolic.py +++ b/test/test_symbolic.py @@ -169,8 +169,27 @@ def test_tangential_onb(ctx_factory): # }}} +# {{{ test_expr_picking + +def test_expr_picking(): + from sumpy.kernel import LaplaceKernel + import pickle + import pytential + + op = pytential.sym.D( + LaplaceKernel(2), pytential.sym.var("sigma"), qbx_forced_limit=-2 + ) + + pickled_op = pickle.dumps(op) + after_pickle_op = pickle.loads(pickled_op) + + assert op == after_pickle_op + +# }}} + + # You can test individual routines by typing -# $ python test_tools.py 'test_routine()' +# $ python test_symbolic.py 'test_routine()' if __name__ == "__main__": import sys -- GitLab From 3821f99eb3eb6da99db4441ed95d75755f60c46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Mon, 27 Aug 2018 17:06:57 -0400 Subject: [PATCH 244/268] Doc/import structure tweaks --- doc/symbolic.rst | 2 ++ pytential/__init__.py | 1 + pytential/symbolic/execution.py | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/symbolic.rst b/doc/symbolic.rst index e6de9534..86d580f6 100644 --- a/doc/symbolic.rst +++ b/doc/symbolic.rst @@ -13,6 +13,8 @@ Binding an operator to a discretization .. currentmodule:: pytential +.. autoclass:: GeometryCollection + .. autofunction:: bind PDE operators diff --git a/pytential/__init__.py b/pytential/__init__.py index 1d07499d..8c1f8f2e 100644 --- a/pytential/__init__.py +++ b/pytential/__init__.py @@ -25,6 +25,7 @@ THE SOFTWARE. import numpy as np import pytential.symbolic.primitives as sym +import pytential.symbolic.execution as GeometryCollection from pytential.symbolic.execution import bind from pytools import memoize_on_first_arg diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index a6528211..481fc0c8 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -340,7 +340,8 @@ def _prepare_expr(places, expr): # {{{ bound expression class GeometryCollection(object): - """A collection of geometry-related objects. This class is meant to hold + """A mapping from symbolic identifiers ("place IDs", typically strings) + of layer potential. This class is meant to hold a specific combination of sources and targets and cache any common expressions specific to it, e.g. metric terms, etc. -- GitLab From 2f78b2598ab4cb5dd9a59ce82cc80c3bf53f9c7f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 27 Aug 2018 19:28:03 -0500 Subject: [PATCH 245/268] Fix some deprecations in Helmholtz Dirichlet example --- examples/helmholtz-dirichlet.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index 22f8fa8a..847e5c3f 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -96,8 +96,10 @@ def main(): bdry_op_sym = (-loc_sign*0.5*sigma_sym + sqrt_w*( - alpha*sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k")) - - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k")) + alpha*sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k"), + qbx_forced_limit=+1) + - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k"), + qbx_forced_limit="avg") )) # }}} -- GitLab From 5046b86e69046de9b5eb8c0db7128c12dc48a38d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 27 Aug 2018 19:28:17 -0500 Subject: [PATCH 246/268] Remove futures support from execution runtime --- pytential/qbx/__init__.py | 4 ++-- pytential/symbolic/compiler.py | 41 +++++++++++++++------------------ pytential/symbolic/execution.py | 2 +- pytential/unregularized.py | 4 ++-- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 869f9222..e1e9a321 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -780,7 +780,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): (o.name, all_potentials_on_every_tgt[o.kernel_index][tgt_slice])) - return result, [] + return result # }}} @@ -941,7 +941,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): result.append((o.name, output_for_each_kernel[o.kernel_index])) - return result, [] + return result # }}} diff --git a/pytential/symbolic/compiler.py b/pytential/symbolic/compiler.py index 17d23ebf..456044e7 100644 --- a/pytential/symbolic/compiler.py +++ b/pytential/symbolic/compiler.py @@ -315,7 +315,7 @@ class Code(object): return "\n".join(lines) - # {{{ dynamic scheduler (generates static schedules by self-observation) + # {{{ dynamic scheduler class NoInstructionAvailable(Exception): pass @@ -372,38 +372,35 @@ class Code(object): done_insns = set() while True: - insn = None discardable_vars = [] + insn = None - # pick the next insn - if insn is None: - try: - insn, discardable_vars = self.get_next_step( - frozenset(list(context.keys())), - frozenset(done_insns)) + try: + insn, discardable_vars = self.get_next_step( + frozenset(list(context.keys())), + frozenset(done_insns)) - except self.NoInstructionAvailable: - # no available instructions: we're done - break - else: - for name in discardable_vars: - del context[name] + except self.NoInstructionAvailable: + # no available instructions: we're done + break + else: + for name in discardable_vars: + del context[name] - done_insns.add(insn) - assignments, new_futures = ( - insn.get_exec_function(exec_mapper) - (exec_mapper.queue, insn, exec_mapper.bound_expr, - exec_mapper)) + done_insns.add(insn) + assignments = ( + insn.get_exec_function(exec_mapper) + (exec_mapper.queue, insn, exec_mapper.bound_expr, + exec_mapper)) - if insn is not None: + assignees = insn.get_assignees() for target, value in assignments: if pre_assign_check is not None: pre_assign_check(target, value) + assert target in assignees context[target] = value - assert not new_futures - if len(done_insns) < len(self.instructions): print("Unreachable instructions:") for insn in set(self.instructions) - done_insns: diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index f658a6aa..45840de2 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -173,7 +173,7 @@ class EvaluationMapper(EvaluationMapperBase): def exec_assign(self, queue, insn, bound_expr, evaluate): return [(name, evaluate(expr)) - for name, expr in zip(insn.names, insn.exprs)], [] + for name, expr in zip(insn.names, insn.exprs)] # {{{ functions diff --git a/pytential/unregularized.py b/pytential/unregularized.py index b0d9926a..e7fe1b3e 100644 --- a/pytential/unregularized.py +++ b/pytential/unregularized.py @@ -187,7 +187,7 @@ class UnregularizedLayerPotentialSource(LayerPotentialSourceBase): result.append((o.name, output_for_each_kernel[o.kernel_index])) - return result, [] + return result # {{{ fmm-based execution @@ -288,7 +288,7 @@ class UnregularizedLayerPotentialSource(LayerPotentialSourceBase): # }}} - return result, [] + return result # }}} -- GitLab From 1823e1a63bf65cd2593f4f9a3078cd84faa706c3 Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Mon, 27 Aug 2018 19:30:43 -0500 Subject: [PATCH 247/268] matrix: cleanup tests --- pytential/__init__.py | 2 +- pytential/symbolic/execution.py | 11 +- test/test_matrix.py | 176 +++++++++++++++++++++----------- 3 files changed, 123 insertions(+), 66 deletions(-) diff --git a/pytential/__init__.py b/pytential/__init__.py index 8c1f8f2e..942476f3 100644 --- a/pytential/__init__.py +++ b/pytential/__init__.py @@ -25,7 +25,7 @@ THE SOFTWARE. import numpy as np import pytential.symbolic.primitives as sym -import pytential.symbolic.execution as GeometryCollection +from pytential.symbolic.execution import GeometryCollection from pytential.symbolic.execution import bind from pytools import memoize_on_first_arg diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index bd39a1b2..d1e581bb 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -379,7 +379,7 @@ class GeometryCollection(object): self._default_source_place = source_where self._default_target_place = target_where - self._auto_place_ids = (source_where, target_where) + self._default_place_ids = (source_where, target_where) self.places = {} if isinstance(places, LayerPotentialSourceBase): @@ -407,7 +407,8 @@ class GeometryCollection(object): if not isinstance(lpot, LayerPotentialSourceBase): return lpot - if where is DEFAULT_SOURCE or where is DEFAULT_TARGET: + from pytential.symbolic.primitives import _QBXSource + if not isinstance(where, _QBXSource): where = QBXSourceStage1(where) if isinstance(where, QBXSourceStage1): @@ -575,8 +576,8 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, :arg domains: a list of discretization identifiers (see 'places') or *None* values indicating the domains on which each component of the solution vector lives. *None* values indicate that the component - is a scalar. If *None*, - :class:`pytential.symbolic.primitives.DEFAULT_SOURCE`, is required + is a scalar. If *None*, *auto_where* or, if it is not provided, + :class:`~pytential.symbolic.primitives.DEFAULT_SOURCE` is required to be a key in :attr:`places`. :arg auto_where: For simple source-to-self or source-to-target evaluations, find 'where' attributes automatically. @@ -599,7 +600,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, input_exprs = [input_exprs] domains = _prepare_domains(len(input_exprs), places, domains, - DEFAULT_SOURCE) + places._default_source_place) from pytential.symbolic.matrix import MatrixBuilder, is_zero nblock_rows = len(exprs) diff --git a/test/test_matrix.py b/test/test_matrix.py index 3a11bf3a..95291380 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -31,11 +31,13 @@ import numpy as np import numpy.linalg as la import pyopencl as cl +import pyopencl.array # noqa + from pytools.obj_array import make_obj_array, is_obj_array from sumpy.symbolic import USE_SYMENGINE -from meshmode.mesh.generation import \ - ellipse, NArmedStarfish, make_curve_mesh +from meshmode.mesh.generation import ( # noqa + ellipse, NArmedStarfish, make_curve_mesh, generate_torus) from pytential import bind, sym from pytential.symbolic.primitives import DEFAULT_SOURCE, DEFAULT_TARGET @@ -46,6 +48,77 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +def _build_qbx_discr(queue, + ndim=2, + nelements=30, + target_order=7, + qbx_order=4, + curve_f=None): + + if curve_f is None: + curve_f = NArmedStarfish(5, 0.25) + + if ndim == 2: + mesh = make_curve_mesh(curve_f, + np.linspace(0, 1, nelements + 1), + target_order) + elif ndim == 3: + mesh = generate_torus(10.0, 2.0, order=target_order) + else: + raise ValueError("unsupported ambient dimension") + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + from pytential.qbx import QBXLayerPotentialSource + density_discr = Discretization( + queue.context, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + + qbx, _ = QBXLayerPotentialSource(density_discr, + fine_order=4 * target_order, + qbx_order=qbx_order, + fmm_order=False).with_refinement() + + return qbx + + +def _build_block_index(discr, nblks=10, factor=1.0): + nnodes = discr.nnodes + max_particles_in_box = nnodes // nblks + + from pytential.linalg.proxy import partition_by_nodes + indices = partition_by_nodes(discr, use_tree=True, + max_nodes_in_box=max_particles_in_box) + + # randomly pick a subset of points + from sumpy.tools import MatrixBlockIndexRanges, BlockIndexRanges + if abs(factor - 1.0) > 1.0e-14: + with cl.CommandQueue(discr.cl_context) as queue: + indices = indices.get(queue) + + indices_ = np.empty(indices.nblocks, dtype=np.object) + for i in range(indices.nblocks): + iidx = indices.block_indices(i) + isize = int(factor * len(iidx)) + isize = max(1, min(isize, len(iidx))) + + indices_[i] = np.sort( + np.random.choice(iidx, size=isize, replace=False)) + + ranges_ = cl.array.to_device(queue, + np.cumsum([0] + [r.shape[0] for r in indices_])) + indices_ = cl.array.to_device(queue, np.hstack(indices_)) + + indices = BlockIndexRanges(discr.cl_context, + indices_.with_queue(None), + ranges_.with_queue(None)) + + indices = MatrixBlockIndexRanges(indices.cl_context, + indices, indices) + + return indices + def _build_op(lpot_id, k=0, @@ -90,6 +163,17 @@ def _build_op(lpot_id, return op, u_sym, knl_kwargs +def _max_block_error(mat, blk, index_set): + error = -np.inf + for i in range(index_set.nblocks): + mat_i = index_set.take(mat, i) + blk_i = index_set.block_take(blk, i) + + error = max(error, la.norm(mat_i - blk_i) / la.norm(mat_i)) + + return error + + @pytest.mark.skipif(USE_SYMENGINE, reason="https://gitlab.tiker.net/inducer/sumpy/issues/25") @pytest.mark.parametrize("k", [0, 42]) @@ -195,15 +279,10 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, from sympy.core.cache import clear_cache clear_cache() - from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) op, u_sym, _ = _build_op(lpot_id, ndim=ndim) - - srcindices = _build_block_index(qbx.density_discr, - method='nodes', factor=factor) - tgtindices = _build_block_index(qbx.density_discr, - method='nodes', factor=factor) + index_set = _build_block_index(qbx.density_discr, factor=factor) from pytential.symbolic.execution import GeometryCollection from pytential.symbolic.execution import _prepare_expr, _prepare_domains @@ -211,9 +290,6 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, expr = _prepare_expr(places, op) domains = _prepare_domains(1, places, None, DEFAULT_SOURCE) - from sumpy.tools import MatrixBlockIndexRanges - index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) - from pytential.symbolic.matrix import P2PMatrixBuilder mbuilder = P2PMatrixBuilder(queue, dep_expr=u_sym, @@ -257,14 +333,7 @@ def test_p2p_block_builder(ctx_factory, factor, ndim, lpot_id, ax2.set_title('P2PMatrixBuilder') mp.savefig("test_p2p_block_{}d_{:.1f}.png".format(ndim, factor)) - for i in range(index_set.nblocks): - eps = 1.0e-14 * la.norm(index_set.take(mat, i)) - error = la.norm(index_set.block_take(blk, i) - - index_set.take(mat, i)) - - if visualize: - print('block[{:04}]: {:.5e}'.format(i, error)) - assert error < eps + assert _max_block_error(mat, blk, index_set) < 1.0e-14 @pytest.mark.parametrize("factor", [1.0, 0.6]) @@ -279,7 +348,6 @@ def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, from sympy.core.cache import clear_cache clear_cache() - from test_linalg_proxy import _build_qbx_discr, _build_block_index target_order = 2 if ndim == 3 else 7 qbx = _build_qbx_discr(queue, target_order=target_order, ndim=ndim) op, u_sym, _ = _build_op(lpot_id, ndim=ndim) @@ -295,11 +363,7 @@ def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, density_discr = places.get_discretization(where[0]) from sumpy.tools import MatrixBlockIndexRanges - srcindices = _build_block_index(density_discr, - method='nodes', factor=factor) - tgtindices = _build_block_index(density_discr, - method='nodes', factor=factor) - index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) + index_set = _build_block_index(density_discr, factor=factor) from pytential.symbolic.matrix import NearFieldBlockBuilder mbuilder = NearFieldBlockBuilder(queue, @@ -342,23 +406,16 @@ def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, ax2.set_title('NearFieldBlockBuilder') mp.savefig("test_qbx_block_builder.png", dpi=300) - for i in range(index_set.nblocks): - eps = 1.0e-14 * la.norm(index_set.take(mat, i)) - error = la.norm(index_set.block_take(blk, i) - - index_set.take(mat, i)) - - if visualize: - print('block[{:04}]: {:.5e}'.format(i, error)) - assert error < eps + assert _max_block_error(mat, blk, index_set) < 1.0e-14 -@pytest.mark.parametrize('where', +@pytest.mark.parametrize('place_id', [(DEFAULT_SOURCE, DEFAULT_TARGET), (QBXSourceStage1(DEFAULT_SOURCE), QBXSourceStage1(DEFAULT_TARGET)), (QBXSourceQuadStage2(DEFAULT_SOURCE), QBXSourceQuadStage2(DEFAULT_TARGET))]) -def test_build_matrix_where(ctx_factory, where, visualize=False): +def test_build_matrix_places(ctx_factory, place_id, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) @@ -366,7 +423,6 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): from sympy.core.cache import clear_cache clear_cache() - from test_linalg_proxy import _build_qbx_discr qbx = _build_qbx_discr(queue, nelements=8, target_order=2, ndim=2, curve_f=partial(ellipse, 1.0)) @@ -374,17 +430,20 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): op, u_sym, _ = _build_op(lpot_id=1, ndim=2, qbx_forced_limit=qbx_forced_limit) + from pytential.symbolic.execution import GeometryCollection + places = GeometryCollection(qbx, auto_where=place_id) + source_discr = places.get_discretization(place_id[0]) + target_discr = places.get_discretization(place_id[1]) + + index_set = _build_block_index(source_discr, factor=0.6) + # build full QBX matrix from pytential.symbolic.execution import build_matrix - mat = build_matrix(queue, qbx, op, u_sym, - auto_where=where, domains=where[0]) - - from pytential.symbolic.execution import GeometryCollection - places = GeometryCollection(qbx, auto_where=where) - source_discr = places.get_discretization(where[0]) - target_discr = places.get_discretization(where[1]) + qbx_mat = build_matrix(queue, qbx, op, u_sym, + auto_where=place_id, domains=place_id[0]) + qbx_mat = qbx_mat.get(queue) - assert mat.shape == (target_discr.nnodes, source_discr.nnodes) + assert qbx_mat.shape == (target_discr.nnodes, source_discr.nnodes) # build full p2p matrix from pytential.symbolic.execution import _prepare_expr @@ -394,43 +453,40 @@ def test_build_matrix_where(ctx_factory, where, visualize=False): mbuilder = P2PMatrixBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[where[0]], - dep_discr=places.get_discretization(where[0]), + dep_source=places[place_id[0]], + dep_discr=places.get_discretization(place_id[0]), places=places, context={}) - mat = mbuilder(op) + p2p_mat = mbuilder(op) - assert mat.shape == (target_discr.nnodes, source_discr.nnodes) + assert p2p_mat.shape == (target_discr.nnodes, source_discr.nnodes) # build block qbx and p2p matrices - from test_linalg_proxy import _build_block_index - srcindices = _build_block_index(source_discr, method='nodes', factor=0.6) - tgtindices = _build_block_index(target_discr, method='nodes', factor=0.6) - - from sumpy.tools import MatrixBlockIndexRanges - index_set = MatrixBlockIndexRanges(ctx, tgtindices, srcindices) - from pytential.symbolic.matrix import NearFieldBlockBuilder mbuilder = NearFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[where[0]], - dep_discr=places.get_discretization(where[0]), + dep_source=places[place_id[0]], + dep_discr=places.get_discretization(place_id[0]), places=places, index_set=index_set, context={}) mat = mbuilder(op) + if place_id[0] is not DEFAULT_SOURCE: + assert _max_block_error(qbx_mat, mat, index_set.get(queue)) < 1.0e-14 from pytential.symbolic.matrix import FarFieldBlockBuilder mbuilder = FarFieldBlockBuilder(queue, dep_expr=u_sym, other_dep_exprs=[], - dep_source=places[where[0]], - dep_discr=places.get_discretization(where[0]), + dep_source=places[place_id[0]], + dep_discr=places.get_discretization(place_id[0]), places=places, index_set=index_set, - context={}) + context={}, + exclude_self=True) mat = mbuilder(op) + assert _max_block_error(p2p_mat, mat, index_set.get(queue)) < 1.0e-14 if __name__ == "__main__": -- GitLab From b1faa7f885f5545d35f7c5c08649b8edf39c325f Mon Sep 17 00:00:00 2001 From: Alex Fikl Date: Mon, 27 Aug 2018 19:35:31 -0500 Subject: [PATCH 248/268] test_matrix: flake8 fixes --- pytential/__init__.py | 2 +- test/test_matrix.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pytential/__init__.py b/pytential/__init__.py index 942476f3..d28e8bdb 100644 --- a/pytential/__init__.py +++ b/pytential/__init__.py @@ -25,7 +25,7 @@ THE SOFTWARE. import numpy as np import pytential.symbolic.primitives as sym -from pytential.symbolic.execution import GeometryCollection +from pytential.symbolic.execution import GeometryCollection # noqa from pytential.symbolic.execution import bind from pytools import memoize_on_first_arg diff --git a/test/test_matrix.py b/test/test_matrix.py index 95291380..d912b2e7 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -48,6 +48,7 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) + def _build_qbx_discr(queue, ndim=2, nelements=30, @@ -361,8 +362,6 @@ def test_qbx_block_builder(ctx_factory, factor, ndim, lpot_id, places = GeometryCollection(qbx, auto_where=where) expr = _prepare_expr(places, op) density_discr = places.get_discretization(where[0]) - - from sumpy.tools import MatrixBlockIndexRanges index_set = _build_block_index(density_discr, factor=factor) from pytential.symbolic.matrix import NearFieldBlockBuilder -- GitLab From 5a33bfd74b5ef3daa6ad6b5df11162ea5a14cced Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 27 Aug 2018 20:53:31 -0500 Subject: [PATCH 249/268] Fix PointPotentialSource to also not return futures --- pytential/source.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytential/source.py b/pytential/source.py index 6420494e..a8d0cba5 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -134,7 +134,7 @@ class PointPotentialSource(PotentialSource): result.append((o.name, output_for_each_kernel[o.kernel_index])) - return result, [] + return result @memoize_method def weights_and_area_elements(self): @@ -268,3 +268,5 @@ class LayerPotentialSourceBase(PotentialSource): # }}} # }}} + +# vim: foldmethod=marker -- GitLab From ced67e4acf6986bba4248542b3e6d63a6b49f153 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 27 Aug 2018 23:56:01 -0500 Subject: [PATCH 250/268] Eliminate futures return in performance data collection path --- pytential/qbx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index e1e9a321..18b356be 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -757,7 +757,7 @@ class QBXLayerPotentialSource(LayerPotentialSourceBase): if self.geometry_data_inspector is not None: perform_fmm = self.geometry_data_inspector(insn, bound_expr, geo_data) if not perform_fmm: - return [(o.name, 0) for o in insn.outputs], [] + return [(o.name, 0) for o in insn.outputs] # }}} -- GitLab From 5e82132ec1826382a251aeced8328d518fa8c823 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Sat, 1 Sep 2018 21:16:33 -0500 Subject: [PATCH 251/268] Call __init__ upon unpickling for IntG --- pytential/symbolic/primitives.py | 8 +------- test/test_symbolic.py | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index ea7b1fc0..1a5f0f1f 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1138,13 +1138,7 @@ class IntG(Expression): def __setstate__(self, state): # Overwrite pymbolic.Expression.__setstate__ assert len(self.init_arg_names) == len(state), type(self) - for name, value in zip(self.init_arg_names, state): - if name == 'kernel_arguments': - value = dict(value) - for dict_name in value: - value[dict_name] = np.array(value[dict_name], dtype=object) - - setattr(self, name, value) + self.__init__(*state) mapper_method = intern("map_int_g") diff --git a/test/test_symbolic.py b/test/test_symbolic.py index c0a35ffa..0c140d04 100644 --- a/test/test_symbolic.py +++ b/test/test_symbolic.py @@ -169,9 +169,9 @@ def test_tangential_onb(ctx_factory): # }}} -# {{{ test_expr_picking +# {{{ test_expr_pickling -def test_expr_picking(): +def test_expr_pickling(): from sumpy.kernel import LaplaceKernel import pickle import pytential -- GitLab From 481eb6543b4bea63c360b436257eaf4b5431a2f0 Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Mon, 3 Sep 2018 23:48:42 -0500 Subject: [PATCH 252/268] Test pickling kernel wrappers. [ci skip] because it contains bug. --- test/test_symbolic.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/test/test_symbolic.py b/test/test_symbolic.py index 0c140d04..2f5633d3 100644 --- a/test/test_symbolic.py +++ b/test/test_symbolic.py @@ -172,18 +172,29 @@ def test_tangential_onb(ctx_factory): # {{{ test_expr_pickling def test_expr_pickling(): - from sumpy.kernel import LaplaceKernel + from sumpy.kernel import LaplaceKernel, AxisTargetDerivative import pickle import pytential - op = pytential.sym.D( - LaplaceKernel(2), pytential.sym.var("sigma"), qbx_forced_limit=-2 - ) - - pickled_op = pickle.dumps(op) - after_pickle_op = pickle.loads(pickled_op) - - assert op == after_pickle_op + ops_for_testing = [ + pytential.sym.d_dx( + 2, + pytential.sym.D( + LaplaceKernel(2), pytential.sym.var("sigma"), qbx_forced_limit=-2 + ) + ), + pytential.sym.D( + AxisTargetDerivative(0, LaplaceKernel(2)), + pytential.sym.var("sigma"), + qbx_forced_limit=-2 + ) + ] + + for op in ops_for_testing: + pickled_op = pickle.dumps(op) + after_pickle_op = pickle.loads(pickled_op) + + assert op == after_pickle_op # }}} -- GitLab From 8c78372ab38772a5f8b1cdc829f6b4ca4e72b26c Mon Sep 17 00:00:00 2001 From: Hao Gao Date: Sat, 8 Sep 2018 23:36:46 -0500 Subject: [PATCH 253/268] Bug fix --- pytential/symbolic/primitives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 1a5f0f1f..94a8b125 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -1156,7 +1156,7 @@ def _insert_source_derivative_into_kernel(kernel): kernel, dir_vec_name=_DIR_VEC_NAME) else: return kernel.replace_inner_kernel( - _insert_source_derivative_into_kernel(kernel.kernel)) + _insert_source_derivative_into_kernel(kernel.inner_kernel)) def _get_dir_vec(dsource, ambient_dim): -- GitLab From 4658c114e7f5807b1782b24dc820ae38f8de19e0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 11 Sep 2018 23:29:47 -0500 Subject: [PATCH 254/268] Doc tweaks --- pytential/source.py | 2 +- pytential/symbolic/execution.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pytential/source.py b/pytential/source.py index e76b97c3..9334dff7 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -38,7 +38,7 @@ __doc__ = """ class PotentialSource(object): """ - .. method:: preprocess_optemplate(name, discretizations, expr) + .. automethod:: preprocess_optemplate .. method:: op_group_features(expr) diff --git a/pytential/symbolic/execution.py b/pytential/symbolic/execution.py index 78aa9349..976e352f 100644 --- a/pytential/symbolic/execution.py +++ b/pytential/symbolic/execution.py @@ -341,9 +341,13 @@ def _prepare_expr(places, expr): class GeometryCollection(object): """A mapping from symbolic identifiers ("place IDs", typically strings) - of layer potential. This class is meant to hold - a specific combination of sources and targets and cache any common - expressions specific to it, e.g. metric terms, etc. + to 'geometries', where a geometry can be a + :class:`pytential.source.PotentialSource` + or a :class:`pytential.target.TargetBase`. + This class is meant to hold a specific combination of sources and targets + serve to host caches of information derived from them, e.g. FMM trees + of subsets of them, as well as related common subexpressions such as + metric terms. .. method:: __getitem__ .. method:: get_discretization @@ -523,6 +527,7 @@ def bind(places, expr, auto_where=None): if not isinstance(places, GeometryCollection): places = GeometryCollection(places, auto_where=auto_where) + expr = _prepare_expr(places, expr) return BoundExpression(places, expr) -- GitLab From 6cc7f1779b486b0de8b53b64e898de87a4b15ffb Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 13 Sep 2018 14:55:59 -0500 Subject: [PATCH 255/268] fmmlib backend: implement OpenMP-parallel local -> QBX local --- pytential/qbx/fmmlib.py | 92 +++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index dfd03b25..30106b19 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -473,57 +473,85 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): geo_data = self.geo_data if geo_data.ncenters == 0: return qbx_expansions + trav = geo_data.traversal() qbx_center_to_target_box = geo_data.qbx_center_to_target_box() qbx_centers = geo_data.centers() qbx_radii = geo_data.expansion_radii() - locloc = self.get_translation_routine("%ddlocloc") + global_qbx_centers = geo_data.global_qbx_centers() + is_global_qbx_center = np.zeros(geo_data.ncenters, dtype=int) + is_global_qbx_center[global_qbx_centers] = 1 + + locloc = self.get_translation_routine("%ddlocloc", vec_suffix="_qbx") - for isrc_level in range(geo_data.tree().nlevels): + nlevels = geo_data.tree().nlevels + + box_to_rscale = np.empty(geo_data.tree().nboxes, dtype=np.float) + for isrc_level in range(nlevels): lev_box_start, lev_box_stop = self.tree.level_start_box_nrs[ isrc_level:isrc_level+2] - locals_level_start_ibox, locals_view = \ - self.local_expansions_view(local_exps, isrc_level) - assert locals_level_start_ibox == lev_box_start + box_to_rscale[lev_box_start:lev_box_stop] = self.level_to_rscale(isrc_level) - kwargs = {} - kwargs.update(self.kernel_kwargs) + box_centers = self._get_single_box_centers_array() - for tgt_icenter in range(geo_data.ncenters): - if self.dim == 3 and self.eqn_letter == "h": - # Yuck: This keeps overwriting 'radius' in the dict. - kwargs["radius"] = 0.5 * ( - geo_data.expansion_radii()[tgt_icenter]) + # This translates from target box to global box numbers. + qbx_center_to_box = trav.target_boxes[qbx_center_to_target_box] - isrc_box = qbx_center_to_target_box[tgt_icenter] + kwargs = {} + kwargs.update(self.kernel_kwargs) - tgt_center = qbx_centers[:, tgt_icenter] + for isrc_level in range(nlevels): + lev_box_start, lev_box_stop = self.tree.level_start_box_nrs[ + isrc_level:isrc_level+2] - # The box's expansions which we're translating here - # (our source) is, globally speaking, a target box. + locals_level_start_ibox, locals_view = \ + self.local_expansions_view(local_exps, isrc_level) - src_ibox = trav.target_boxes[isrc_box] + assert locals_level_start_ibox == lev_box_start - # Is the box number on the level currently under - # consideration? - in_range = (lev_box_start <= src_ibox and src_ibox < lev_box_stop) + # Find used QBX centers that are on this level. (This is not ideal, + # but we're supplied a mapping of QBX centers to boxes and we have + # to invert that in some way.) + curr_level_qbx_centers = np.flatnonzero( + is_global_qbx_center + & (lev_box_start <= qbx_center_to_box) + & (qbx_center_to_box < lev_box_stop)) - if in_range: - src_center = self.tree.box_centers[:, src_ibox] - tmp_loc_exp = locloc( - rscale1=self.level_to_rscale(isrc_level), - center1=src_center, - expn1=locals_view[ - src_ibox - locals_level_start_ibox].T, + if curr_level_qbx_centers.size == 0: + continue - rscale2=qbx_radii[tgt_icenter], - center2=tgt_center, - nterms2=self.qbx_order, + icurr_level_qbx_center_to_box = ( + qbx_center_to_box[curr_level_qbx_centers]) - **kwargs)[..., 0].T + if self.dim == 3 and self.eqn_letter == "h": + kwargs["radius"] = 0.5 * ( + geo_data.expansion_radii()[curr_level_qbx_centers]) + + # This returns either the expansion or a tuple (ier, expn). + rvals = locloc( + rscale1=box_to_rscale, + rscale1_offsets=icurr_level_qbx_center_to_box, + center1=box_centers, + center1_offsets=icurr_level_qbx_center_to_box, + expn1=locals_view.T, + expn1_offsets=icurr_level_qbx_center_to_box - lev_box_start, + nterms1=self.level_nterms[isrc_level], + nterms2=self.qbx_order, + rscale2=qbx_radii, + rscale2_offsets=curr_level_qbx_centers, + center2=qbx_centers, + center2_offsets=curr_level_qbx_centers, + **kwargs) + + if isinstance(rvals, tuple): + ier, expn2 = rvals + if ier.any(): + raise RuntimeError("locloc failed") + else: + expn2 = rvals - qbx_expansions[tgt_icenter] += tmp_loc_exp + qbx_expansions[curr_level_qbx_centers] += expn2.T return qbx_expansions -- GitLab From ef789d1586006fc4980497e84b3d57791079600b Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 13 Sep 2018 14:57:12 -0500 Subject: [PATCH 256/268] Point requirements.txt to pyfmmlib's locloc-qbx branch --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6d1e4cce..40cda04a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode git+https://gitlab.tiker.net/inducer/sumpy -git+https://github.com/inducer/pyfmmlib +git+https://gitlab.tiker.net/inducer/pyfmmlib@locloc-qbx -- GitLab From 052bb8922df090a970e50ebeb57a3e94062aa989 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 13 Sep 2018 15:11:37 -0500 Subject: [PATCH 257/268] Exit early if there are no centers with targets --- pytential/qbx/fmmlib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index 30106b19..fd50d50c 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -471,7 +471,9 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data - if geo_data.ncenters == 0: + global_qbx_centers = geo_data.global_qbx_centers() + + if global_qbx_centers.size == 0: return qbx_expansions trav = geo_data.traversal() @@ -479,7 +481,6 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): qbx_centers = geo_data.centers() qbx_radii = geo_data.expansion_radii() - global_qbx_centers = geo_data.global_qbx_centers() is_global_qbx_center = np.zeros(geo_data.ncenters, dtype=int) is_global_qbx_center[global_qbx_centers] = 1 -- GitLab From 9551476042447302a0881e12da81a0848c576d21 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 13 Sep 2018 15:14:36 -0500 Subject: [PATCH 258/268] flake8 fix --- pytential/qbx/fmmlib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index fd50d50c..bf233b76 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -492,7 +492,8 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): for isrc_level in range(nlevels): lev_box_start, lev_box_stop = self.tree.level_start_box_nrs[ isrc_level:isrc_level+2] - box_to_rscale[lev_box_start:lev_box_stop] = self.level_to_rscale(isrc_level) + box_to_rscale[lev_box_start:lev_box_stop] = ( + self.level_to_rscale(isrc_level)) box_centers = self._get_single_box_centers_array() -- GitLab From d9b445ec433f9f4c4672d27cc6910ab1eaa201e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Thu, 13 Sep 2018 17:50:12 -0400 Subject: [PATCH 259/268] Point requirements.txt back to pyfmmlib master --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 40cda04a..dd15a69e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ git+https://github.com/inducer/loopy git+https://gitlab.tiker.net/inducer/boxtree git+https://github.com/inducer/meshmode git+https://gitlab.tiker.net/inducer/sumpy -git+https://gitlab.tiker.net/inducer/pyfmmlib@locloc-qbx +git+https://gitlab.tiker.net/inducer/pyfmmlib -- GitLab From 30799afa93f3c6694c3aaafe4fced1ad91a9a24b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 18 Sep 2018 15:47:12 -0500 Subject: [PATCH 260/268] Improve install instructions link in README --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index bababc0a..dfc5fe49 100644 --- a/README.rst +++ b/README.rst @@ -11,6 +11,9 @@ potentials (and, sooner or later, volume potentials). It also knows how to set up meshes and solve integral equations. +See `here `_ +for easy, self-contained installation instructions for Linux and macOS. + It relies on * `numpy `_ for arrays @@ -25,11 +28,12 @@ and, indirectly, * `PyOpenCL `_ as computational infrastructure -PyOpenCL is likely the only package you'll have to install -by hand, all the others will be installed automatically. +.. image:: https://badge.fury.io/py/pytential.png + :target: http://pypi.python.org/pypi/pytential Resources: +* `installation instructions `_ * `documentation `_ * `wiki home page `_ * `source code via git `_ -- GitLab From c928ccd71d48a20ebc0848bef7126077900cb20d Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Fri, 21 Sep 2018 11:54:19 -0500 Subject: [PATCH 261/268] Fix OSX pocl install line (thanks @isuruf) --- doc/misc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/misc.rst b/doc/misc.rst index 7a3d9aba..198b87da 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -31,7 +31,7 @@ This set of instructions is intended for 64-bit Linux and macOS computers. #. ``conda config --add channels conda-forge`` -#. (*macOS only*) ``conda install osx-pocl-opencl`` +#. (*macOS only*) ``conda install osx-pocl-opencl pocl pyopencl`` #. ``conda install git pip pocl islpy pyopencl sympy pyfmmlib pytest`` -- GitLab From 9f8b658c511234d028fd07bc6ca2f8954dd84dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kl=C3=B6ckner?= Date: Sun, 30 Sep 2018 14:46:12 -0400 Subject: [PATCH 262/268] Allow failure of Conda Apple CI job (#112 on Gitlab) --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 173861e7..9526444e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -76,6 +76,10 @@ Python 3.6 Conda Apple: - REQUIREMENTS_TXT=.test-conda-env-py3-requirements.txt - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project-within-miniconda.sh - ". ./build-and-test-py-project-within-miniconda.sh" + + # https://gitlab.tiker.net/inducer/pytential/issues/112 + allow_failure: true + tags: - apple except: -- GitLab From 6970c65d64bd17e8de1b2099487e7189537dbbb9 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 26 Oct 2018 17:22:42 -0500 Subject: [PATCH 263/268] Flake8 3.6.0 fixes (closes #114) --- pytential/linalg/proxy.py | 18 +++++------ pytential/qbx/direct.py | 12 +++---- pytential/qbx/fmmlib.py | 8 ++--- pytential/solve.py | 6 ++-- pytential/symbolic/pde/cahn_hilliard.py | 6 ++-- pytential/symbolic/pde/maxwell/__init__.py | 18 +++++------ pytential/symbolic/pde/maxwell/waveguide.py | 6 ++-- pytential/symbolic/primitives.py | 9 ++---- pytential/symbolic/stokes.py | 4 +-- test/extra_curve_data.py | 36 ++++++++++----------- test/test_global_qbx.py | 8 ++--- test/test_layer_pot.py | 3 +- test/test_layer_pot_eigenvalues.py | 12 +++---- test/test_scalar_int_eq.py | 3 +- 14 files changed, 68 insertions(+), 81 deletions(-) diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index abb8e8de..ffc3e0aa 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -94,8 +94,8 @@ def partition_by_nodes(discr, max_particles_in_box=max_nodes_in_box) tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + leaf_boxes, = (tree.box_flags + & box_flags_enum.HAS_CHILDREN == 0).nonzero() indices = np.empty(len(leaf_boxes), dtype=np.object) for i, ibox in enumerate(leaf_boxes): @@ -161,8 +161,8 @@ def partition_by_elements(discr, groups = discr.groups tree = tree.get(queue) - leaf_boxes, = (tree.box_flags & - box_flags_enum.HAS_CHILDREN == 0).nonzero() + leaf_boxes, = (tree.box_flags + & box_flags_enum.HAS_CHILDREN == 0).nonzero() indices = np.empty(len(leaf_boxes), dtype=np.object) for i, ibox in enumerate(leaf_boxes): @@ -225,8 +225,8 @@ def partition_from_coarse(resampler, from_indices): # construct ranges from_discr = resampler.from_discr - from_grp_ranges = np.cumsum([0] + - [grp.nelements for grp in from_discr.mesh.groups]) + from_grp_ranges = np.cumsum( + [0] + [grp.nelements for grp in from_discr.mesh.groups]) from_el_ranges = np.hstack([ np.arange(grp.node_nr_base, grp.nnodes + 1, grp.nunit_nodes) for grp in from_discr.groups]) @@ -574,9 +574,9 @@ def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, # get nodes inside the ball but outside the current range center = pxycenters[:, iproxy].reshape(-1, 1) radius = pxyradii[iproxy] - mask = (la.norm(nodes - center, axis=0) < radius) & \ - ((isources < indices.ranges[iproxy]) | - (indices.ranges[iproxy + 1] <= isources)) + mask = ((la.norm(nodes - center, axis=0) < radius) + & ((isources < indices.ranges[iproxy]) + | (indices.ranges[iproxy + 1] <= isources))) nbrindices[iproxy] = indices.indices[isources[mask]] diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index 7ad1782c..496259c9 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -45,8 +45,8 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): from sumpy.tools import gather_loopy_source_arguments arguments = ( - gather_loopy_source_arguments(self.kernels) + - [ + gather_loopy_source_arguments(self.kernels) + + [ lp.GlobalArg("src", None, shape=(self.dim, "nsources"), order="C"), lp.GlobalArg("tgt", None, @@ -62,11 +62,11 @@ class LayerPotentialOnTargetAndCenterSubset(LayerPotentialBase): lp.ValueArg("nsources", np.int32), lp.ValueArg("ntargets", np.int32), lp.ValueArg("ntargets_total", np.int32), - lp.ValueArg("ncenters_total", np.int32)] + - [lp.GlobalArg("strength_%d" % i, None, + lp.ValueArg("ncenters_total", np.int32)] + + [lp.GlobalArg("strength_%d" % i, None, shape="nsources", order="C") - for i in range(self.strength_count)] + - [lp.GlobalArg("result_%d" % i, self.value_dtypes[i], + for i in range(self.strength_count)] + + [lp.GlobalArg("result_%d" % i, self.value_dtypes[i], shape="ntargets_total", order="C") for i in range(len(self.kernels))]) diff --git a/pytential/qbx/fmmlib.py b/pytential/qbx/fmmlib.py index bf233b76..2d21a3d2 100644 --- a/pytential/qbx/fmmlib.py +++ b/pytential/qbx/fmmlib.py @@ -385,15 +385,15 @@ class QBXFMMLibExpansionWrangler(FMMLibExpansionWrangler): kwargs = {} if self.dim == 3 and self.eqn_letter == "h": - kwargs["radius"] = (0.5 * - geo_data.expansion_radii()[geo_data.global_qbx_centers()]) + kwargs["radius"] = (0.5 + * geo_data.expansion_radii()[geo_data.global_qbx_centers()]) nsrc_boxes_per_gqbx_center = np.zeros(icontaining_tgt_box_vec.shape, dtype=traversal.tree.box_id_dtype) mask = (icontaining_tgt_box_vec != -1) nsrc_boxes_per_gqbx_center[mask] = ( - ssn.starts[icontaining_tgt_box_vec[mask] + 1] - - ssn.starts[icontaining_tgt_box_vec[mask]] + ssn.starts[icontaining_tgt_box_vec[mask] + 1] + - ssn.starts[icontaining_tgt_box_vec[mask]] ) nsrc_boxes = np.sum(nsrc_boxes_per_gqbx_center) diff --git a/pytential/solve.py b/pytential/solve.py index e46fa5ea..dce9d8a3 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -208,9 +208,9 @@ def _gmres(A, b, restart=None, tol=None, x0=None, dot=None, # noqa else: print("*** WARNING: non-monotonic residuals in GMRES") - if (stall_iterations and - len(residual_norms) > stall_iterations and - norm_r > ( + if (stall_iterations + and len(residual_norms) > stall_iterations + and norm_r > ( residual_norms[-stall_iterations] / no_progress_factor)): diff --git a/pytential/symbolic/pde/cahn_hilliard.py b/pytential/symbolic/pde/cahn_hilliard.py index fd6ec482..3263f18e 100644 --- a/pytential/symbolic/pde/cahn_hilliard.py +++ b/pytential/symbolic/pde/cahn_hilliard.py @@ -54,12 +54,10 @@ class CahnHilliardOperator(L2WeightedPDEOperator): return ( # FIXME: Verify scaling -1/(2*np.pi*(lam1**2-lam2**2)) / hhk_scaling - * - ( + * ( op_map(sym.S(hhk, density, k=1j*lam1, qbx_forced_limit=qbx_forced_limit)) - - - op_map(sym.S(hhk, density, k=1j*lam2, + - op_map(sym.S(hhk, density, k=1j*lam2, qbx_forced_limit=qbx_forced_limit)))) else: return ( diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index 86fdd993..2a98217a 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -42,7 +42,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. @@ -230,14 +230,14 @@ class MuellerAugmentedMFIEOperator(object): grad = partial(sym.grad, 3) - E0 = sym.cse(1j*omega*mu0*eps0*S(Jxyz, k=k0) + - mu0*curl_S(Mxyz, k=k0) - grad(S(u.rho_e, k=k0)), "E0") - H0 = sym.cse(-1j*omega*mu0*eps0*S(Mxyz, k=k0) + - eps0*curl_S(Jxyz, k=k0) + grad(S(u.rho_m, k=k0)), "H0") - E1 = sym.cse(1j*omega*mu1*eps1*S(Jxyz, k=k1) + - mu1*curl_S(Mxyz, k=k1) - grad(S(u.rho_e, k=k1)), "E1") - H1 = sym.cse(-1j*omega*mu1*eps1*S(Mxyz, k=k1) + - eps1*curl_S(Jxyz, k=k1) + grad(S(u.rho_m, k=k1)), "H1") + E0 = sym.cse(1j*omega*mu0*eps0*S(Jxyz, k=k0) + + mu0*curl_S(Mxyz, k=k0) - grad(S(u.rho_e, k=k0)), "E0") + H0 = sym.cse(-1j*omega*mu0*eps0*S(Mxyz, k=k0) + + eps0*curl_S(Jxyz, k=k0) + grad(S(u.rho_m, k=k0)), "H0") + E1 = sym.cse(1j*omega*mu1*eps1*S(Jxyz, k=k1) + + mu1*curl_S(Mxyz, k=k1) - grad(S(u.rho_e, k=k1)), "E1") + H1 = sym.cse(-1j*omega*mu1*eps1*S(Mxyz, k=k1) + + eps1*curl_S(Jxyz, k=k1) + grad(S(u.rho_m, k=k1)), "H1") F1 = (xyz_to_tangential(sym.n_cross(H1-H0) + 0.5*(eps0+eps1)*Jxyz)) F2 = (sym.n_dot(eps1*E1-eps0*E0) + 0.5*(eps1+eps0)*u.rho_e) diff --git a/pytential/symbolic/pde/maxwell/waveguide.py b/pytential/symbolic/pde/maxwell/waveguide.py index 6a303570..4049cf17 100644 --- a/pytential/symbolic/pde/maxwell/waveguide.py +++ b/pytential/symbolic/pde/maxwell/waveguide.py @@ -561,8 +561,7 @@ class Dielectric2DBoundaryOperatorBase(L2WeightedPDEOperator): for term in bc) is_necessary = ( (self.ez_enabled and any_significant_e) - or - (self.hz_enabled and any_significant_h)) + or (self.hz_enabled and any_significant_h)) # Only keep tangential modes for TEM. Otherwise, # no jump in H already implies jump condition on @@ -588,8 +587,7 @@ class Dielectric2DBoundaryOperatorBase(L2WeightedPDEOperator): def is_field_present(self, field_kind): return ( (field_kind == self.field_kind_e and self.ez_enabled) - or - (field_kind == self.field_kind_h and self.hz_enabled)) + or (field_kind == self.field_kind_h and self.hz_enabled)) def make_unknown(self, name): num_densities = ( diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 5ee758a4..de679e68 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -287,8 +287,7 @@ def make_sym_surface_mv(name, ambient_dim, dim, where=None): return sum( var("%s%d" % (name, i)) - * - cse(MultiVector(vec), "tangent%d" % i, cse_scope.DISCRETIZATION) + * cse(MultiVector(vec), "tangent%d" % i, cse_scope.DISCRETIZATION) for i, vec in enumerate(par_grad.T)) @@ -901,8 +900,7 @@ def area(ambient_dim, dim, where=None): def mean(ambient_dim, dim, operand, where=None): return ( integral(ambient_dim, dim, operand, where) - / - area(ambient_dim, dim, where)) + / area(ambient_dim, dim, where)) class IterativeInverse(Expression): @@ -1105,8 +1103,7 @@ class IntG(Expression): karg.loopy_arg.name for karg in ( kernel.get_args() - + - kernel.get_source_args())) + + kernel.get_source_args())) kernel_arguments = kernel_arguments.copy() if kwargs: diff --git a/pytential/symbolic/stokes.py b/pytential/symbolic/stokes.py index adfc23d5..7e47bdb2 100644 --- a/pytential/symbolic/stokes.py +++ b/pytential/symbolic/stokes.py @@ -476,8 +476,8 @@ class StressletWrapper(object): for j in range(self.dim): sym_expr[comp] = sym_expr[comp] + ( dir_vec_sym[j] * mu_sym * ( - sym_grad_matrix[comp][j] + - sym_grad_matrix[j][comp]) + sym_grad_matrix[comp][j] + + sym_grad_matrix[j][comp]) ) return sym_expr diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index c6679953..4d2dacca 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -84,8 +84,8 @@ class Segment(Curve): def __call__(self, ts): return ( - self.start[:, np.newaxis] + - ts * (self.end - self.start)[:, np.newaxis]) + self.start[:, np.newaxis] + + ts * (self.end - self.start)[:, np.newaxis]) class Arc(Curve): @@ -134,12 +134,12 @@ class Arc(Curve): def __call__(self, t): if self.theta_increasing: thetas = ( - self.theta_range[0] + - t * (self.theta_range[1] - self.theta_range[0])) + self.theta_range[0] + + t * (self.theta_range[1] - self.theta_range[0])) else: thetas = ( - self.theta_range[1] - - t * (self.theta_range[1] - self.theta_range[0])) + self.theta_range[1] + - t * (self.theta_range[1] - self.theta_range[0])) val = (self.r * np.exp(1j * thetas)) + self.center return np.array([val.real, val.imag]) @@ -149,19 +149,19 @@ class Arc(Curve): # To avoid issues with crossing non-smooth regions, make sure the number of # panels given to this function (for make_curve_mesh) is a multiple of 8. horseshoe = ( - Segment((0, 0), (-5, 0)) + - Arc((-5, 0), (-5.5, -0.5), (-5, -1)) + - Segment((-5, -1), (0, -1)) + - Arc((0, -1), (1.5, 0.5), (0, 2)) + - Segment((0, 2), (-5, 2)) + - Arc((-5, 2), (-5.5, 1.5), (-5, 1)) + - Segment((-5, 1), (0, 1)) + - Arc((0, 1), (0.5, 0.5), (0, 0)) + Segment((0, 0), (-5, 0)) + + Arc((-5, 0), (-5.5, -0.5), (-5, -1)) + + Segment((-5, -1), (0, -1)) + + Arc((0, -1), (1.5, 0.5), (0, 2)) + + Segment((0, 2), (-5, 2)) + + Arc((-5, 2), (-5.5, 1.5), (-5, 1)) + + Segment((-5, 1), (0, 1)) + + Arc((0, 1), (0.5, 0.5), (0, 0)) ) # unit square unit_square = ( - Segment((1, -1), (1, 1)) + - Segment((1, 1), (-1, 1)) + - Segment((-1, 1), (-1, -1)) + - Segment((-1, -1), (1, -1))) + Segment((1, -1), (1, 1)) + + Segment((1, 1), (-1, 1)) + + Segment((-1, 1), (-1, -1)) + + Segment((-1, -1), (1, -1))) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 86832d6e..7f700fa1 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -144,8 +144,8 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): # =distance(centers of panel 1, panel 2) dist = ( la.norm(( - all_centers[..., np.newaxis] - - nodes[:, np.newaxis, ...]).T, + all_centers[..., np.newaxis] + - nodes[:, np.newaxis, ...]).T, axis=-1) .min()) @@ -169,8 +169,8 @@ def run_source_refinement_test(ctx_getter, mesh, order, helmholtz_k=None): # =distance(centers of panel 1, panel 2) dist = ( la.norm(( - all_centers[..., np.newaxis] - - nodes[:, np.newaxis, ...]).T, + all_centers[..., np.newaxis] + - nodes[:, np.newaxis, ...]).T, axis=-1) .min()) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index b4f4a02f..4b5d70b6 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -488,8 +488,7 @@ def test_3d_jump_relations(ctx_factory, relation, visualize=False): err = ( norm(qbx, queue, jump_identity, np.inf) - / - norm(qbx, queue, density, np.inf)) + / norm(qbx, queue, density, np.inf)) print("ERROR", qbx.h_max, err) eoc_rec.add_data_point(qbx.h_max, err) diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index c5f927c2..b4e986d4 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -166,8 +166,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, s_err = ( norm(density_discr, queue, s_sigma - s_sigma_ref) - / - norm(density_discr, queue, s_sigma_ref)) + / norm(density_discr, queue, s_sigma_ref)) s_eoc_rec.add_data_point(qbx.h_max, s_err) # }}} @@ -198,8 +197,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, d_err = ( norm(density_discr, queue, d_sigma - d_sigma_ref) - / - d_ref_norm) + / d_ref_norm) d_eoc_rec.add_data_point(qbx.h_max, d_err) # }}} @@ -218,8 +216,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order, sp_err = ( norm(density_discr, queue, sp_sigma - sp_sigma_ref) - / - norm(density_discr, queue, sigma)) + / norm(density_discr, queue, sigma)) sp_eoc_rec.add_data_point(qbx.h_max, sp_err) # }}} @@ -277,8 +274,7 @@ def test_sphere_eigenvalues(ctx_getter, mode_m, mode_n, qbx_order, def rel_err(comp, ref): return ( norm(density_discr, queue, comp - ref) - / - norm(density_discr, queue, ref)) + / norm(density_discr, queue, ref)) for nrefinements in [0, 1]: from meshmode.mesh.generation import generate_icosphere diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index 8830e84b..72d08fcf 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -713,8 +713,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize): rel_grad_err_inf = ( la.norm(grad_err[0].get(), np.inf) - / - la.norm(grad_ref[0].get(), np.inf)) + / la.norm(grad_ref[0].get(), np.inf)) print("rel_grad_err_inf: %g" % rel_grad_err_inf) -- GitLab From 4fb160235cc211d5fd0fb0b55c396a51d85cc829 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 26 Oct 2018 17:24:50 -0500 Subject: [PATCH 264/268] Fix another flake8 warning --- 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 2a98217a..8bdab9df 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -71,7 +71,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. -- GitLab From 2ba767f658caef16503023348460b78e8ec595ce Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 2 Nov 2018 21:47:02 -0500 Subject: [PATCH 265/268] QBXExpansionWrangler: Fix early-exit return values to conform to timing interface When there are no used QBX centers, the Sumpy wrangler returns early, but it wasn't returning a TimingResult as it should have --- pytential/qbx/fmm.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 46d7ae88..0058d87e 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -198,10 +198,11 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), @log_process(logger) def form_global_qbx_locals(self, src_weights): local_exps = self.qbx_local_expansion_zeros() + events = [] geo_data = self.geo_data if len(geo_data.global_qbx_centers()) == 0: - return local_exps + return (local_exps, SumpyTimingFuture(self.queue, events)) traversal = geo_data.traversal() @@ -227,25 +228,27 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **kwargs) + events.append(evt) + assert local_exps is result result.add_event(evt) - return (result, SumpyTimingFuture(self.queue, [evt])) + return (result, SumpyTimingFuture(self.queue, events)) @log_process(logger) def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_expansions = self.qbx_local_expansion_zeros() + events = [] + geo_data = self.geo_data if geo_data.ncenters == 0: - return qbx_expansions + return (qbx_expansions, SumpyTimingFuture(self.queue, events)) traversal = geo_data.traversal() wait_for = multipole_exps.events - events = [] - for isrc_level, ssn in enumerate(traversal.from_sep_smaller_by_level): m2qbxl = self.code.m2qbxl( self.level_orders[isrc_level], @@ -290,14 +293,14 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), qbx_expansions = self.qbx_local_expansion_zeros() geo_data = self.geo_data + events = [] if geo_data.ncenters == 0: - return qbx_expansions + return (qbx_expansions, SumpyTimingFuture(self.queue, events)) + trav = geo_data.traversal() wait_for = local_exps.events - events = [] - for isrc_level in range(geo_data.tree().nlevels): l2qbxl = self.code.l2qbxl( self.level_orders[isrc_level], @@ -339,8 +342,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), pot = self.full_output_zeros() geo_data = self.geo_data + events = [] + if len(geo_data.global_qbx_centers()) == 0: - return pot + return (pot, SumpyTimingFuture(self.queue, events)) ctt = geo_data.center_to_tree_targets() @@ -365,7 +370,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), for pot_i, pot_res_i in zip(pot, pot_res): assert pot_i is pot_res_i - return (pot, SumpyTimingFuture(self.queue, [evt])) + return (pot, SumpyTimingFuture(self.queue, events)) # }}} -- GitLab From c84cf97072c3ed39b257b50e3325f9e9699ab4a5 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 2 Nov 2018 21:51:07 -0500 Subject: [PATCH 266/268] Whitespace consistency --- pytential/qbx/fmm.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 0058d87e..badf6300 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -229,7 +229,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **kwargs) events.append(evt) - assert local_exps is result result.add_event(evt) @@ -238,7 +237,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), @log_process(logger) def translate_box_multipoles_to_qbx_local(self, multipole_exps): qbx_expansions = self.qbx_local_expansion_zeros() - events = [] geo_data = self.geo_data @@ -280,7 +278,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **self.kernel_extra_kwargs) events.append(evt) - wait_for = [evt] assert qbx_expansions_res is qbx_expansions @@ -294,6 +291,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), geo_data = self.geo_data events = [] + if geo_data.ncenters == 0: return (qbx_expansions, SumpyTimingFuture(self.queue, events)) @@ -329,7 +327,6 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), **self.kernel_extra_kwargs) events.append(evt) - wait_for = [evt] assert qbx_expansions_res is qbx_expansions -- GitLab From 3dbd2e4d502b0620b5e6a2bce039f57a391e0f08 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 6 Nov 2018 21:26:11 -0600 Subject: [PATCH 267/268] 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 268/268] 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