diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dc64f57c..f483c67a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.x' - name: "Main Script" run: | curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/prepare-and-run-flake8.sh @@ -32,7 +32,7 @@ jobs: - uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.x' - name: "Main Script" run: | set -e @@ -51,18 +51,35 @@ jobs: . ./prepare-and-run-pylint.sh pytential test/test_*.py pytest3: - name: Pytest on Py3 + name: Pytest Linux runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: "Main Script" run: | - set -e grep -v symengine .test-conda-env-py3.yml > .test-conda-env.yml CONDA_ENVIRONMENT=.test-conda-env.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 + pytest3mac: + name: Pytest Mac + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: "Main Script" + run: | + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + grep -v symengine .test-conda-env-py3.yml > .test-conda-env.yml + echo "- compilers" >> .test-conda-env.yml + echo "- llvm-openmp" >> .test-conda-env.yml + CONDA_ENVIRONMENT=.test-conda-env.yml + export PYTEST_ADDOPTS=${PYTEST_ADDOPTS:--k-slowtest} + set -o xtrace + 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 + pytest3symengine: name: Pytest on Py3 with SymEngine runs-on: ubuntu-latest @@ -76,4 +93,3 @@ jobs: . ./build-and-test-py-project-within-miniconda.sh # vim: sw=4 - diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f9c12b45f..dae0a27cd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -73,29 +73,6 @@ Python 3 Conda: reports: junit: test/pytest.xml -Python 3 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 CC=clang - # https://stackoverflow.com/q/60934005; https://reviews.llvm.org/D71579 - - export LDFLAGS="-mlinker-version=519" - - set -o xtrace - - 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 - retry: 2 - - artifacts: - reports: - junit: test/pytest.xml - Documentation: script: - EXTRA_INSTALL="Cython pybind11 numpy mako" diff --git a/.test-conda-env-py3-macos.yml b/.test-conda-env-py3-macos.yml deleted file mode 100644 index 901576dcf..000000000 --- a/.test-conda-env-py3-macos.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: test-conda-env-py3-macos -channels: -- conda-forge -- nodefaults -dependencies: -- git -- conda-forge::numpy -- conda-forge::sympy -- scipy -- pocl -- islpy -- pyopencl -- python>=3.6 -- python-symengine=0.6.0 -- pyfmmlib -# for OpenMP support in pyfmmlib -- libgfortran>=3.0.1 -- clangdev -- openmp -- cython -- gmsh - -- pip -- pip: - - git+https://github.com/inducer/pytools - - 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://github.com/inducer/meshmode diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index 748855b09..fb8bb92d0 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -23,4 +23,4 @@ dependencies: - git+https://github.com/inducer/pymbolic - git+https://github.com/inducer/loopy - git+https://gitlab.tiker.net/inducer/sumpy - - git+https://github.com/inducer/meshmode + - git+https://gitlab.tiker.net/inducer/meshmode@array-context diff --git a/examples/cost.py b/examples/cost.py index 71c116804..0070aaa64 100644 --- a/examples/cost.py +++ b/examples/cost.py @@ -2,6 +2,8 @@ import pyopencl as cl import numpy as np +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw from pytential import sym, bind from pytools import one @@ -26,7 +28,7 @@ TESTING_ARMS = (20,) -def starfish_lpot_source(queue, n_arms): +def starfish_lpot_source(actx, n_arms): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) @@ -39,7 +41,7 @@ def starfish_lpot_source(queue, n_arms): TARGET_ORDER) pre_density_discr = Discretization( - queue.context, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(TARGET_ORDER)) lpot_kwargs = DEFAULT_LPOT_KWARGS.copy() @@ -60,14 +62,14 @@ def starfish_lpot_source(queue, n_arms): # }}} -def training_geometries(queue): +def training_geometries(actx): for n_arms in TRAINING_ARMS: - yield starfish_lpot_source(queue, n_arms) + yield starfish_lpot_source(actx, n_arms) -def test_geometries(queue): +def test_geometries(actx): for n_arms in TESTING_ARMS: - yield starfish_lpot_source(queue, n_arms) + yield starfish_lpot_source(actx, n_arms) def get_bound_op(places): @@ -79,15 +81,15 @@ def get_bound_op(places): return bind(places, op) -def get_test_density(queue, density_discr): - nodes = density_discr.nodes().with_queue(queue) - sigma = cl.clmath.sin(10 * nodes[0]) - +def get_test_density(actx, density_discr): + nodes = thaw(actx, density_discr.nodes()) + sigma = actx.np.sin(10 * nodes[0]) return sigma def calibrate_cost_model(ctx): queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) from pytential.qbx.cost import CostModel, estimate_calibration_params cost_model = CostModel() @@ -95,7 +97,7 @@ def calibrate_cost_model(ctx): model_results = [] timing_results = [] - for lpot_source in training_geometries(queue): + for lpot_source in training_geometries(actx): lpot_source = lpot_source.copy(cost_model=cost_model) from pytential import GeometryCollection @@ -103,16 +105,17 @@ def calibrate_cost_model(ctx): density_discr = places.get_discretization(places.auto_source.geometry) bound_op = get_bound_op(places) - sigma = get_test_density(queue, density_discr) + sigma = get_test_density(actx, density_discr) - cost_S = bound_op.get_modeled_cost(queue, sigma=sigma) + cost_S = bound_op.get_modeled_cost(actx, sigma=sigma) # Warm-up run. - bound_op.eval(queue, {"sigma": sigma}) + bound_op.eval({"sigma": sigma}, array_context=actx) for _ in range(RUNS): timing_data = {} - bound_op.eval(queue, {"sigma": sigma}, timing_data=timing_data) + bound_op.eval({"sigma": sigma}, array_context=actx, + timing_data=timing_data) model_results.append(one(cost_S.values())) timing_results.append(one(timing_data.values())) @@ -125,8 +128,9 @@ def calibrate_cost_model(ctx): def test_cost_model(ctx, cost_model): queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - for lpot_source in test_geometries(queue): + for lpot_source in test_geometries(actx): lpot_source = lpot_source.copy(cost_model=cost_model) from pytential import GeometryCollection @@ -134,20 +138,21 @@ def test_cost_model(ctx, cost_model): density_discr = places.get_discretization(places.auto_source.geometry) bound_op = get_bound_op(places) - sigma = get_test_density(queue, density_discr) + sigma = get_test_density(actx, density_discr) - cost_S = bound_op.get_modeled_cost(queue, sigma=sigma) + cost_S = bound_op.get_modeled_cost(actx, sigma=sigma) model_result = ( one(cost_S.values()) .get_predicted_times(merge_close_lists=True)) # Warm-up run. - bound_op.eval(queue, {"sigma": sigma}) + bound_op.eval({"sigma": sigma}, array_context=actx) temp_timing_results = [] for _ in range(RUNS): timing_data = {} - bound_op.eval(queue, {"sigma": sigma}, timing_data=timing_data) + bound_op.eval({"sigma": sigma}, + array_context=actx, timing_data=timing_data) temp_timing_results.append(one(timing_data.values())) timing_result = {} diff --git a/examples/fmm-error.py b/examples/fmm-error.py index a6d19bb15..ea50d70f6 100644 --- a/examples/fmm-error.py +++ b/examples/fmm-error.py @@ -1,6 +1,8 @@ from __future__ import division import numpy as np import pyopencl as cl +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw from meshmode.mesh.generation import ( # noqa make_curve_mesh, starfish, ellipse, drop) from sumpy.visualization import FieldPlotter @@ -13,6 +15,7 @@ def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) target_order = 16 qbx_order = 3 @@ -37,13 +40,13 @@ def main(): InterpolatoryQuadratureSimplexGroupFactory pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) unaccel_qbx = QBXLayerPotentialSource( pre_density_discr, fine_order=2*target_order, qbx_order=qbx_order, fmm_order=False, - target_association_tolerance=.05 + target_association_tolerance=.05, ) from pytential.target import PointsTarget @@ -57,24 +60,35 @@ def main(): }) density_discr = places.get_discretization("unaccel_qbx") - nodes = density_discr.nodes().with_queue(queue) - angle = cl.clmath.atan2(nodes[1], nodes[0]) + nodes = thaw(actx, density_discr.nodes()) + angle = actx.np.atan2(nodes[1], nodes[0]) 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) + if k: + kernel_kwargs = {"k": sym.var("k")} + else: + kernel_kwargs = {} + + def get_op(): + kwargs = dict(qbx_forced_limit=None) + kwargs.update(kernel_kwargs) + # return sym.d_dx(2, sym.S(kernel, sym.var("sigma"), **kwargs)) + # return sym.D(kernel, sym.var("sigma"), **kwargs) + return sym.S(kernel, sym.var("sigma"), **kwargs) + + op = get_op() - sigma = cl.clmath.cos(mode_nr*angle) + sigma = actx.np.cos(mode_nr*angle) if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + for i, elem in np.ndenumerate(sigma): + sigma[i] = elem.astype(np.complex128) fld_in_vol = bind(places, op, auto_where=("unaccel_qbx", "targets"))( - queue, sigma=sigma, k=k).get() + actx, sigma=sigma, k=k).get() fmm_fld_in_vol = bind(places, op, auto_where=("qbx", "targets"))( - queue, sigma=sigma, k=k).get() + actx, sigma=sigma, k=k).get() err = fmm_fld_in_vol-fld_in_vol diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index 75115da4a..aee8c3908 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -3,6 +3,7 @@ import pyopencl as cl import pyopencl.clmath # noqa +from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory @@ -29,6 +30,7 @@ def main(mesh_name="ellipse", visualize=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.mesh.generation import ellipse, make_curve_mesh from functools import partial @@ -67,7 +69,7 @@ def main(mesh_name="ellipse", visualize=False): raise ValueError("unknown mesh name: {}".format(mesh_name)) pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import ( @@ -79,7 +81,7 @@ def main(mesh_name="ellipse", visualize=False): from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500) - targets = cl.array.to_device(queue, fplot.points) + targets = actx.from_numpy(fplot.points) from pytential import GeometryCollection places = GeometryCollection({ @@ -120,21 +122,22 @@ def main(mesh_name="ellipse", visualize=False): # {{{ fix rhs and solve - nodes = density_discr.nodes().with_queue(queue) + from meshmode.dof_array import thaw + nodes = thaw(actx, density_discr.nodes()) k_vec = np.array([2, 1]) k_vec = k * k_vec / la.norm(k_vec, 2) def u_incoming_func(x): - return cl.clmath.exp( + return actx.np.exp( 1j * (x[0] * k_vec[0] + x[1] * k_vec[1])) bc = -u_incoming_func(nodes) - bvp_rhs = bind(places, sqrt_w*sym.var("bc"))(queue, bc=bc) + bvp_rhs = bind(places, sqrt_w*sym.var("bc"))(actx, bc=bc) from pytential.solve import gmres gmres_result = gmres( - bound_op.scipy_op(queue, sigma_sym.name, dtype=np.complex128, k=k), + bound_op.scipy_op(actx, sigma_sym.name, dtype=np.complex128, k=k), bvp_rhs, tol=1e-8, progress=True, stall_iterations=0, hard_failure=True) @@ -152,15 +155,18 @@ def u_incoming_func(x): - sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, **repr_kwargs)) u_incoming = u_incoming_func(targets) - ones_density = density_discr.zeros(queue) - ones_density.fill(1) + ones_density = density_discr.zeros(actx) + for elem in ones_density: + elem.fill(1) - indicator = bind(places, sym.D(LaplaceKernel(2), sigma_sym, **repr_kwargs))( - queue, sigma=ones_density).get() + indicator = actx.to_numpy( + bind(places, sym.D(LaplaceKernel(2), sigma_sym, **repr_kwargs))( + actx, sigma=ones_density)) try: - fld_in_vol = bind(places, representation_sym)( - queue, sigma=gmres_result.solution, k=k).get() + fld_in_vol = actx.to_numpy( + bind(places, representation_sym)( + actx, sigma=gmres_result.solution, k=k)) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("helmholtz-dirichlet-failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)) @@ -171,7 +177,7 @@ def u_incoming_func(x): fplot.write_vtk_file("helmholtz-dirichlet-potential.vts", [ ("potential", fld_in_vol), ("indicator", indicator), - ("u_incoming", u_incoming.get()), + ("u_incoming", actx.to_numpy(u_incoming)), ]) # }}} diff --git a/examples/laplace-dirichlet-3d.py b/examples/laplace-dirichlet-3d.py index 984f1de10..7f6bba458 100644 --- a/examples/laplace-dirichlet-3d.py +++ b/examples/laplace-dirichlet-3d.py @@ -3,6 +3,7 @@ import pyopencl as cl import pyopencl.clmath # noqa +from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory @@ -28,6 +29,7 @@ def main(mesh_name="torus", visualize=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) if mesh_name == "torus": rout = 10 @@ -61,7 +63,7 @@ def main(mesh_name="torus", visualize=False): raise ValueError("unknown mesh name: {}".format(mesh_name)) pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import ( @@ -73,7 +75,7 @@ def main(mesh_name="torus", visualize=False): from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(3), extent=20, npoints=50) - targets = cl.array.to_device(queue, fplot.points) + targets = actx.from_numpy(fplot.points) from pytential import GeometryCollection places = GeometryCollection({ @@ -109,33 +111,39 @@ def main(mesh_name="torus", visualize=False): # {{{ fix rhs and solve - nodes = density_discr.nodes().with_queue(queue) + from meshmode.dof_array import thaw, flatten, unflatten + nodes = thaw(actx, density_discr.nodes()) source = np.array([rout, 0, 0]) def u_incoming_func(x): + from pytools.obj_array import obj_array_vectorize + x = obj_array_vectorize(actx.to_numpy, flatten(x)) + x = np.array(list(x)) # return 1/cl.clmath.sqrt( (x[0] - source[0])**2 # +(x[1] - source[1])**2 # +(x[2] - source[2])**2 ) - return 1.0/la.norm(x.get()-source[:, None], axis=0) + return 1.0/la.norm(x - source[:, None], axis=0) - bc = cl.array.to_device(queue, u_incoming_func(nodes)) + bc = unflatten(actx, + density_discr, + actx.from_numpy(u_incoming_func(nodes))) - bvp_rhs = bind(places, sqrt_w*sym.var("bc"))(queue, bc=bc) + bvp_rhs = bind(places, sqrt_w*sym.var("bc"))(actx, bc=bc) from pytential.solve import gmres gmres_result = gmres( - bound_op.scipy_op(queue, "sigma", dtype=np.float64), + bound_op.scipy_op(actx, "sigma", dtype=np.float64), bvp_rhs, tol=1e-14, progress=True, stall_iterations=0, hard_failure=True) sigma = bind(places, sym.var("sigma")/sqrt_w)( - queue, sigma=gmres_result.solution) + actx, sigma=gmres_result.solution) # }}} from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, density_discr, 20) + bdry_vis = make_visualizer(actx, density_discr, 20) bdry_vis.write_vtk_file("laplace.vtu", [ ("sigma", sigma), ]) @@ -151,8 +159,8 @@ def u_incoming_func(x): + sym.D(kernel, inv_sqrt_w_sigma, **repr_kwargs)) try: - fld_in_vol = bind(places, representation_sym)( - queue, sigma=sigma).get() + fld_in_vol = actx.to_numpy( + bind(places, representation_sym)(actx, sigma=sigma)) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("laplace-dirichlet-3d-failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)), diff --git a/examples/layerpot-3d.py b/examples/layerpot-3d.py index ecace75de..78112858c 100644 --- a/examples/layerpot-3d.py +++ b/examples/layerpot-3d.py @@ -1,5 +1,5 @@ -from __future__ import division - +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw import numpy as np import pyopencl as cl @@ -22,6 +22,7 @@ def main(mesh_name="ellipsoid"): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) if mesh_name == "ellipsoid": cad_file_name = "geometries/ellipsoid.step" @@ -55,7 +56,7 @@ def main(mesh_name="ellipsoid"): InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, fmm_order=qbx_order + 3, @@ -71,8 +72,8 @@ def main(mesh_name="ellipsoid"): }, auto_where="qbx") density_discr = places.get_discretization("qbx") - nodes = density_discr.nodes().with_queue(queue) - angle = cl.clmath.atan2(nodes[1], nodes[0]) + nodes = thaw(actx, density_discr.nodes()) + angle = actx.np.atan2(nodes[1], nodes[0]) if k: kernel = HelmholtzKernel(3) @@ -83,18 +84,22 @@ def main(mesh_name="ellipsoid"): 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 = actx.np.cos(mode_nr*angle) if 0: - sigma = 0*angle + from meshmode.dof_array import flatten, unflatten + sigma = flatten(0 * angle) from random import randrange for i in range(5): sigma[randrange(len(sigma))] = 1 + sigma = unflatten(actx, density_discr, sigma) if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + for i, elem in np.ndenumerate(sigma): + sigma[i] = elem.astype(np.complex128) - fld_in_vol = bind(places, op, auto_where=("qbx", "targets"))( - queue, sigma=sigma, k=k).get() + fld_in_vol = actx.to_numpy( + bind(places, op, auto_where=("qbx", "targets"))( + actx, sigma=sigma, k=k)) #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file("layerpot-3d-potential.vts", [ @@ -102,11 +107,10 @@ def main(mesh_name="ellipsoid"): ]) bdry_normals = bind(places, - sym.normal(density_discr.ambient_dim))(queue).as_vector(dtype=object) + sym.normal(density_discr.ambient_dim))(actx).as_vector(dtype=object) from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, density_discr, target_order) - + bdry_vis = make_visualizer(actx, density_discr, target_order) bdry_vis.write_vtk_file("layerpot-3d-density.vtu", [ ("sigma", sigma), ("bdry_normals", bdry_normals), diff --git a/examples/layerpot.py b/examples/layerpot.py index e01a24eb8..40a060fb4 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -36,12 +36,15 @@ def main(curve_fn=starfish, visualize=True): target_order) from pytential.qbx import QBXLayerPotentialSource + from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory + actx = PyOpenCLArrayContext(queue) + pre_density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, fmm_order=qbx_order+3, @@ -56,10 +59,12 @@ def main(curve_fn=starfish, visualize=True): "qbx": qbx, "targets": PointsTarget(targets_dev), }, auto_where="qbx") + density_discr = places.get_discretization("qbx") - nodes = density_discr.nodes().with_queue(queue) - angle = cl.clmath.atan2(nodes[1], nodes[0]) + from meshmode.dof_array import thaw + nodes = thaw(actx, density_discr.nodes()) + angle = actx.np.atan2(nodes[1], nodes[0]) if k: kernel = HelmholtzKernel(2) @@ -75,22 +80,26 @@ def op(**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) + sigma = actx.np.cos(mode_nr*angle) if 0: - sigma = 0*angle + from meshmode.dof_array import flatten, unflatten + sigma = flatten(0 * angle) from random import randrange for i in range(5): sigma[randrange(len(sigma))] = 1 + sigma = unflatten(actx, density_discr, sigma) if isinstance(kernel, HelmholtzKernel): - sigma = sigma.astype(np.complex128) + for i, elem in np.ndenumerate(sigma): + sigma[i] = elem.astype(np.complex128) bound_bdry_op = bind(places, op()) if visualize: - fld_in_vol = bind(places, op( - source="qbx", - target="targets", - qbx_forced_limit=None))(queue, sigma=sigma, k=k).get() + fld_in_vol = actx.to_numpy( + bind(places, op( + source="qbx", + target="targets", + qbx_forced_limit=None))(actx, sigma=sigma, k=k)) if enable_mayavi: fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) @@ -100,13 +109,9 @@ def op(**kwargs): ]) if 0: - def apply_op(density): - return bound_bdry_op( - queue, sigma=cl.array.to_device(queue, density), k=k).get() - + apply_op = bound_bdry_op.scipy_op(actx, "sigma", np.float64, k=k) from sumpy.tools import build_matrix - n = len(sigma) - mat = build_matrix(apply_op, dtype=np.float64, shape=(n, n)) + mat = build_matrix(apply_op) import matplotlib.pyplot as pt pt.imshow(mat) @@ -116,18 +121,20 @@ def apply_op(density): if enable_mayavi: # {{{ plot boundary field - fld_on_bdry = bound_bdry_op(queue, sigma=sigma, k=k).get() + from pytential.utils import flatten_to_numpy + + fld_on_bdry = flatten_to_numpy( + actx, bound_bdry_op(actx, sigma=sigma, k=k)) + nodes_host = flatten_to_numpy(actx, density_discr.nodes()) - 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/scaling-study.py b/examples/scaling-study.py index 21a85019f..a14a5aed0 100644 --- a/examples/scaling-study.py +++ b/examples/scaling-study.py @@ -2,6 +2,7 @@ import pyopencl as cl import pyopencl.clmath # noqa +from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory @@ -58,11 +59,12 @@ def timing_run(nx, ny, visualize=False): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) mesh = make_mesh(nx=nx, ny=ny, visualize=visualize) density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import ( @@ -76,7 +78,7 @@ def timing_run(nx, ny, visualize=False): if visualize: from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) - targets = PointsTarget(cl.array.to_device(queue, fplot.points)) + targets = PointsTarget(actx.from_numpy(fplot.points)) places.update({ "plot-targets": targets, @@ -119,10 +121,12 @@ def timing_run(nx, ny, visualize=False): # {{{ fix rhs and solve mode_nr = 3 - nodes = density_discr.nodes().with_queue(queue) - angle = cl.clmath.atan2(nodes[1], nodes[0]) - sigma = cl.clmath.cos(mode_nr*angle) + from meshmode.dof_array import thaw + nodes = thaw(actx, density_discr.nodes()) + angle = actx.np.atan2(nodes[1], nodes[0]) + + sigma = actx.np.cos(mode_nr*angle) # }}} @@ -134,17 +138,17 @@ def timing_run(nx, ny, visualize=False): bound_op = bind(places, sym_op) print("FMM WARM-UP RUN 1: %5d elements" % mesh.nelements) - bound_op(queue, sigma=sigma, k=k) + bound_op(actx, sigma=sigma, k=k) queue.finish() print("FMM WARM-UP RUN 2: %5d elements" % mesh.nelements) - bound_op(queue, sigma=sigma, k=k) + bound_op(actx, sigma=sigma, k=k) queue.finish() from time import time t_start = time() - bound_op(queue, sigma=sigma, k=k) - queue.finish() + bound_op(actx, sigma=sigma, k=k) + actx.queue.finish() elapsed = time() - t_start print("FMM TIMING RUN: %5d elements -> %g s" diff --git a/pytential/__init__.py b/pytential/__init__.py index 728ce196f..1f548deba 100644 --- a/pytential/__init__.py +++ b/pytential/__init__.py @@ -66,8 +66,8 @@ def _integral_op(discr): discr.ambient_dim, discr.dim, sym.var("integrand"))) -def integral(discr, queue, x): - return _integral_op(discr)(queue, integrand=x) +def integral(discr, x): + return _integral_op(discr)(integrand=x) @memoize_on_first_arg @@ -97,23 +97,26 @@ def _norm_inf_op(discr, num_components): return bind(discr, sym.NodeMax(max_arg)) -def norm(discr, queue, x, p=2): +def norm(discr, x, p=2): from pymbolic.geometric_algebra import MultiVector if isinstance(x, MultiVector): x = x.as_vector(np.object) + from meshmode.dof_array import DOFArray num_components = None - if isinstance(x, np.ndarray): + if (isinstance(x, np.ndarray) + and x.dtype.char == "O" + and not isinstance(x, DOFArray)): num_components, = x.shape if p == 2: norm_op = _norm_2_op(discr, num_components) from math import sqrt - return sqrt(norm_op(queue, integrand=x)) + return sqrt(norm_op(integrand=x)) elif p == np.inf or p == "inf": norm_op = _norm_inf_op(discr, num_components) - norm_res = norm_op(queue, arg=x) + norm_res = norm_op(arg=x) if isinstance(norm_res, np.ndarray): return max(norm_res) else: diff --git a/pytential/linalg/proxy.py b/pytential/linalg/proxy.py index ba2e2ea96..0f79148e8 100644 --- a/pytential/linalg/proxy.py +++ b/pytential/linalg/proxy.py @@ -26,10 +26,6 @@ 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 from sumpy.tools import BlockIndexRanges @@ -54,16 +50,7 @@ # {{{ point index partitioning -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_nodes_in_box=None): +def partition_by_nodes(actx, discr, use_tree=True, max_nodes_in_box=None): """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. @@ -82,112 +69,42 @@ def partition_by_nodes(discr, # FIXME: this is just an arbitrary value max_nodes_in_box = 32 - with cl.CommandQueue(discr.cl_context) as queue: - if use_tree: - from boxtree import box_flags_enum - from boxtree import TreeBuilder + if use_tree: + from boxtree import box_flags_enum + from boxtree import TreeBuilder - builder = TreeBuilder(discr.cl_context) + builder = TreeBuilder(actx.context) - tree, _ = builder(queue, discr.nodes(), + from meshmode.dof_array import flatten, thaw + tree, _ = builder(actx.queue, + flatten(thaw(actx, 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(actx.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) - assert ranges[-1] == discr.nnodes - - return BlockIndexRanges(discr.cl_context, - indices.with_queue(None), - ranges.with_queue(None)) - - -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 *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 - *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`. + ranges = actx.from_numpy( + np.cumsum([0] + [box.shape[0] for box in indices]) + ) + indices = actx.from_numpy(np.hstack(indices)) + else: + indices = actx.from_numpy(np.arange(0, discr.ndofs, dtype=np.int)) + ranges = actx.from_numpy(np.arange( + 0, + discr.ndofs + 1, + discr.ndofs // max_nodes_in_box, dtype=np.int)) - :return: a :class:`sumpy.tools.BlockIndexRanges`. - """ + assert ranges[-1] == discr.ndofs - if not hasattr(resampler, "groups"): - raise ValueError("resampler must be a DirectDiscretizationConnection.") - - with cl.CommandQueue(resampler.cl_context) as queue: - from_indices = from_indices.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_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)) + return BlockIndexRanges(actx.context, + actx.freeze(indices), actx.freeze(ranges)) # }}} @@ -340,7 +257,7 @@ def get_kernel(self): """.format(radius_expr=radius_expr)], [ lp.GlobalArg("sources", None, - shape=(self.ambient_dim, "nsources")), + shape=(self.ambient_dim, "nsources"), dim_tags="sep,C"), lp.GlobalArg("center_int", None, shape=(self.ambient_dim, "nsources"), dim_tags="sep,C"), lp.GlobalArg("center_ext", None, @@ -367,11 +284,11 @@ def get_optimized_kernel(self): return knl - def __call__(self, queue, source_dd, indices, **kwargs): + def __call__(self, actx, source_dd, indices, **kwargs): """Generate proxy points for each given range of source points in the discretization in *source_dd*. - :arg queue: a :class:`pyopencl.CommandQueue`. + :arg actx: a :class:`~meshmode.array_context.ArrayContext`. :arg source_dd: a :class:`~pytential.symbolic.primitives.DOFDescriptor` for the discretization on which the proxy points are to be generated. @@ -397,47 +314,51 @@ def _affine_map(v, A, b): source_dd.geometry, source_dd.discr_stage) radii = bind(self.places, sym.expansion_radii( - self.ambient_dim, dofdesc=source_dd))(queue) + self.ambient_dim, dofdesc=source_dd))(actx) center_int = bind(self.places, sym.expansion_centers( - self.ambient_dim, -1, dofdesc=source_dd))(queue) + self.ambient_dim, -1, dofdesc=source_dd))(actx) center_ext = bind(self.places, sym.expansion_centers( - self.ambient_dim, +1, dofdesc=source_dd))(queue) + self.ambient_dim, +1, dofdesc=source_dd))(actx) + from meshmode.dof_array import flatten, thaw knl = self.get_kernel() - _, (centers_dev, radii_dev,) = knl(queue, - sources=discr.nodes(), - center_int=center_int, - center_ext=center_ext, - expansion_radii=radii, + _, (centers_dev, radii_dev,) = knl(actx.queue, + sources=flatten(thaw(actx, discr.nodes())), + center_int=flatten(center_int), + center_ext=flatten(center_ext), + expansion_radii=flatten(radii), srcindices=indices.indices, srcranges=indices.ranges, **kwargs) - centers = centers_dev.get() - radii = radii_dev.get() + from pytential.utils import flatten_to_numpy + centers = flatten_to_numpy(actx, centers_dev) + radii = flatten_to_numpy(actx, radii_dev) proxies = np.empty(indices.nblocks, dtype=np.object) for i in range(indices.nblocks): 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) + pxyranges = actx.from_numpy(np.arange( + 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])) - for idim in range(self.ambient_dim)]) + actx.freeze(actx.from_numpy(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)]) + actx.freeze(centers_dev[idim]) + for idim in range(self.ambient_dim) + ]) assert pxyranges[-1] == proxies[0].shape[0] - return proxies, pxyranges, centers, radii_dev + return proxies, actx.freeze(pxyranges), centers, actx.freeze(radii_dev) -def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, - max_nodes_in_box=None): +def gather_block_neighbor_points(actx, discr, indices, pxycenters, pxyradii, + max_nodes_in_box=None): """Generate a set of neighboring points for each range of points in *discr*. Neighboring points of a range :math:`i` are defined as all the points inside the proxy ball :math:`i` that do not also @@ -455,79 +376,77 @@ def gather_block_neighbor_points(discr, indices, pxycenters, pxyradii, # FIXME: this is a fairly arbitrary value max_nodes_in_box = 32 - with cl.CommandQueue(discr.cl_context) as queue: - indices = indices.get(queue) - - # NOTE: this is constructed for multiple reasons: - # * TreeBuilder takes object arrays - # * `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, indices.indices]) - for idim in range(discr.ambient_dim)]) - - # construct tree - from boxtree import TreeBuilder - 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(discr.cl_context) - query, _ = builder(queue, tree, pxycenters, pxyradii) - - # find nodes inside each proxy ball - 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) - - 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] - 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(discr.ambient_dim)]) - isources = tree.user_source_ids[isources] - - # 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))) - - 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 BlockIndexRanges(discr.cl_context, - nbrindices.with_queue(None), - nbrranges.with_queue(None)) - - -def gather_block_interaction_points(places, source_dd, indices, - radius_factor=None, - approx_nproxy=None, - max_nodes_in_box=None): + indices = indices.get(actx.queue) + + # NOTE: this is constructed for multiple reasons: + # * TreeBuilder takes object arrays + # * `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 + from pytential.utils import flatten_to_numpy + sources = flatten_to_numpy(actx, discr.nodes()) + sources = make_obj_array([ + actx.from_numpy(sources[idim][indices.indices]) + for idim in range(discr.ambient_dim)]) + + # construct tree + from boxtree import TreeBuilder + builder = TreeBuilder(actx.context) + tree, _ = builder(actx.queue, sources, + max_particles_in_box=max_nodes_in_box) + + from boxtree.area_query import AreaQueryBuilder + builder = AreaQueryBuilder(actx.context) + query, _ = builder(actx.queue, tree, pxycenters, pxyradii) + + # find nodes inside each proxy ball + tree = tree.get(actx.queue) + query = query.get(actx.queue) + + pxycenters = np.vstack([ + actx.to_numpy(pxycenters[idim]) + for idim in range(discr.ambient_dim) + ]) + pxyradii = actx.to_numpy(pxyradii) + + 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] + 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(discr.ambient_dim)]) + isources = tree.user_source_ids[isources] + + # 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))) + + nbrindices[iproxy] = indices.indices[isources[mask]] + + nbrranges = actx.from_numpy(np.cumsum([0] + [n.shape[0] for n in nbrindices])) + nbrindices = actx.from_numpy(np.hstack(nbrindices)) + + return BlockIndexRanges(actx.context, + actx.freeze(nbrindices), actx.freeze(nbrranges)) + + +def gather_block_interaction_points(actx, places, source_dd, indices, + radius_factor=None, + approx_nproxy=None, + max_nodes_in_box=None): """Generate sets of interaction points for each given range of indices in the *source* discretization. For each input range of indices, the corresponding output range of points is consists of: @@ -583,7 +502,7 @@ def knl(): """, [ lp.GlobalArg("sources", None, - shape=(lpot_source.ambient_dim, "nsources")), + shape=(lpot_source.ambient_dim, "nsources"), dim_tags="sep,C"), lp.GlobalArg("proxies", None, shape=(lpot_source.ambient_dim, "nproxies"), dim_tags="sep,C"), lp.GlobalArg("nbrindices", None, @@ -607,28 +526,28 @@ def knl(): return loopy_knl lpot_source = places.get_geometry(source_dd.geometry) - with cl.CommandQueue(lpot_source.cl_context) as queue: - generator = ProxyGenerator(places, - radius_factor=radius_factor, - approx_nproxy=approx_nproxy) - proxies, pxyranges, pxycenters, pxyradii = \ - generator(queue, source_dd, indices) - - discr = places.get_discretization(source_dd.geometry, source_dd.discr_stage) - neighbors = gather_block_neighbor_points(discr, - indices, pxycenters, pxyradii, - max_nodes_in_box=max_nodes_in_box) - - ranges = cl.array.zeros(queue, indices.nblocks + 1, dtype=np.int) - _, (nodes, ranges) = knl()(queue, - sources=discr.nodes(), - proxies=proxies, - pxyranges=pxyranges, - nbrindices=neighbors.indices, - nbrranges=neighbors.ranges, - ranges=ranges) - - return nodes.with_queue(None), ranges.with_queue(None) + generator = ProxyGenerator(places, + radius_factor=radius_factor, + approx_nproxy=approx_nproxy) + proxies, pxyranges, pxycenters, pxyradii = \ + generator(actx, source_dd, indices) + + discr = places.get_discretization(source_dd.geometry, source_dd.discr_stage) + neighbors = gather_block_neighbor_points(actx, discr, + indices, pxycenters, pxyradii, + max_nodes_in_box=max_nodes_in_box) + + from meshmode.dof_array import flatten, thaw + ranges = actx.zeros(indices.nblocks + 1, dtype=np.int) + _, (nodes, ranges) = knl()(actx.queue, + sources=flatten(thaw(actx, discr.nodes())), + proxies=proxies, + pxyranges=pxyranges, + nbrindices=neighbors.indices, + nbrranges=neighbors.ranges, + ranges=ranges) + + return actx.freeze(nodes), actx.freeze(ranges) # }}} diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index a23dd3d71..db87e79ac 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import division, absolute_import +from __future__ import annotations __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" @@ -25,8 +24,11 @@ import six +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import flatten, unflatten, thaw import numpy as np -from pytools import memoize_method +from pytools import memoize_method, memoize_in + from pytential.qbx.target_assoc import QBXTargetAssociationFailedException from pytential.source import LayerPotentialSourceBase @@ -335,24 +337,57 @@ def copy( # }}} + # {{{ code containers + @property - @memoize_method def tree_code_container(self): - from pytential.qbx.utils import TreeCodeContainer - return TreeCodeContainer(self.cl_context) + @memoize_in(self._setup_actx, ( + QBXLayerPotentialSource, "tree_code_container")) + def make_container(): + from pytential.qbx.utils import TreeCodeContainer + return TreeCodeContainer(self._setup_actx) + return make_container() @property - @memoize_method def refiner_code_container(self): - from pytential.qbx.refinement import RefinerCodeContainer - return RefinerCodeContainer(self.cl_context, self.tree_code_container) + @memoize_in(self._setup_actx, ( + QBXLayerPotentialSource, "refiner_code_container")) + def make_container(): + from pytential.qbx.refinement import RefinerCodeContainer + return RefinerCodeContainer( + self._setup_actx, self.tree_code_container) + return make_container() @property - @memoize_method def target_association_code_container(self): - from pytential.qbx.target_assoc import TargetAssociationCodeContainer - return TargetAssociationCodeContainer( - self.cl_context, self.tree_code_container) + @memoize_in(self._setup_actx, ( + QBXLayerPotentialSource, "target_association_code_container")) + def make_container(): + from pytential.qbx.target_assoc import TargetAssociationCodeContainer + return TargetAssociationCodeContainer( + self._setup_actx, self.tree_code_container) + return make_container() + + @property + def qbx_fmm_geometry_data_code_container(self): + @memoize_in(self._setup_actx, ( + QBXLayerPotentialSource, + "qbx_fmm_geometry_data_code_container")) + def make_container( + debug, ambient_dim, well_sep_is_n_away, + from_sep_smaller_crit): + from pytential.qbx.geometry import QBXFMMGeometryDataCodeContainer + return QBXFMMGeometryDataCodeContainer( + self._setup_actx, + ambient_dim, self.tree_code_container, debug, + _well_sep_is_n_away=well_sep_is_n_away, + _from_sep_smaller_crit=from_sep_smaller_crit) + + return make_container( + self.debug, self.ambient_dim, + self._well_sep_is_n_away, self._from_sep_smaller_crit) + + # }}} # {{{ internal API @@ -371,7 +406,7 @@ def qbx_fmm_geometry_data(self, places, name, from pytential.qbx.geometry import QBXFMMGeometryData return QBXFMMGeometryData(places, name, - self.qbx_fmm_code_getter, + self.qbx_fmm_geometry_data_code_container, target_discrs_and_qbx_sides, target_association_tolerance=self.target_association_tolerance, tree_kind=self._tree_kind, @@ -445,8 +480,8 @@ def drive_cost_model( cost_model_result = ( self.cost_model(wrangler, geo_data, kernel, kernel_arguments)) - from pytools.obj_array import with_object_array_or_scalar - output_placeholder = with_object_array_or_scalar( + from pytools.obj_array import obj_array_vectorize + output_placeholder = obj_array_vectorize( wrangler.finalize_potentials, wrangler.full_output_zeros() ) @@ -471,15 +506,6 @@ def _dispatch_compute_potential_insn(self, queue, insn, bound_expr, return func(queue, insn, bound_expr, evaluate, **extra_args) - @property - @memoize_method - def qbx_fmm_code_getter(self): - from pytential.qbx.geometry import QBXFMMGeometryCodeGetter - return QBXFMMGeometryCodeGetter(self.cl_context, self.ambient_dim, - self.tree_code_container, debug=self.debug, - _well_sep_is_n_away=self._well_sep_is_n_away, - _from_sep_smaller_crit=self._from_sep_smaller_crit) - # {{{ fmm-based execution @memoize_method @@ -542,8 +568,8 @@ def get_target_discrs_and_qbx_sides(self, insn, bound_expr): return target_name_and_side_to_number, tuple(target_discrs_and_qbx_sides) - def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate, - fmm_driver): + def exec_compute_potential_insn_fmm(self, actx: PyOpenCLArrayContext, + insn, bound_expr, evaluate, fmm_driver): """ :arg fmm_driver: A function that accepts four arguments: *wrangler*, *strength*, *geo_data*, *kernel*, *kernel_arguments* @@ -572,21 +598,23 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate, from pytential import bind, sym waa = bind(bound_expr.places, sym.weights_and_area_elements( - self.ambient_dim, dofdesc=insn.source))(queue) - strengths = waa * evaluate(insn.density).with_queue(queue) + self.ambient_dim, dofdesc=insn.source))(actx) + density = evaluate(insn.density) + strengths = waa * density + flat_strengths = flatten(strengths) out_kernels = tuple(knl for knl in insn.kernels) fmm_kernel = self.get_fmm_kernel(out_kernels) output_and_expansion_dtype = ( - self.get_fmm_output_and_expansion_dtype(fmm_kernel, strengths)) + self.get_fmm_output_and_expansion_dtype(fmm_kernel, flat_strengths)) kernel_extra_kwargs, source_extra_kwargs = ( self.get_fmm_expansion_wrangler_extra_kwargs( - queue, out_kernels, geo_data.tree().user_source_ids, + actx, out_kernels, geo_data.tree().user_source_ids, insn.kernel_arguments, evaluate)) wrangler = self.expansion_wrangler_code_container( fmm_kernel, out_kernels).get_wrangler( - queue, geo_data, output_and_expansion_dtype, + actx.queue, geo_data, output_and_expansion_dtype, self.qbx_order, self.fmm_level_to_order, source_extra_kwargs=source_extra_kwargs, @@ -594,7 +622,7 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate, _use_target_specific_qbx=self._use_target_specific_qbx) from pytential.qbx.geometry import target_state - if (geo_data.user_target_to_center().with_queue(queue) + if (actx.thaw(geo_data.user_target_to_center()) == target_state.FAILED).any().get(): raise RuntimeError("geometry has failed targets") @@ -610,20 +638,28 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate, # Execute global QBX. all_potentials_on_every_target, extra_outputs = ( fmm_driver( - wrangler, strengths, geo_data, fmm_kernel, kernel_extra_kwargs)) + wrangler, flat_strengths, geo_data, + fmm_kernel, kernel_extra_kwargs)) - result = [] + results = [] for o in insn.outputs: target_side_number = target_name_and_side_to_number[ o.target_name, o.qbx_forced_limit] + target_discr, _ = target_discrs_and_qbx_sides[target_side_number] target_slice = slice(*geo_data.target_info().target_discr_starts[ target_side_number:target_side_number+2]) - result.append((o.name, - all_potentials_on_every_target[o.kernel_index][target_slice])) + result = all_potentials_on_every_target[o.kernel_index][target_slice] + + from meshmode.discretization import Discretization + if isinstance(target_discr, Discretization): + from meshmode.dof_array import unflatten + result = unflatten(actx, target_discr, result) - return result, extra_outputs + results.append((o.name, result)) + + return results, extra_outputs # }}} @@ -681,7 +717,7 @@ def get_qbx_target_numberer(self, dtype): *count = item; """) - def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate, + def exec_compute_potential_insn_direct(self, actx, insn, bound_expr, evaluate, return_timing_data): from pytential import bind, sym if return_timing_data: @@ -695,19 +731,23 @@ def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate, p2p = None lpot_applier_on_tgt_subset = None + from pytential.utils import flatten_if_needed kernel_args = {} for arg_name, arg_expr in six.iteritems(insn.kernel_arguments): - kernel_args[arg_name] = evaluate(arg_expr) + kernel_args[arg_name] = flatten_if_needed(actx, evaluate(arg_expr)) waa = bind(bound_expr.places, sym.weights_and_area_elements( - self.ambient_dim, dofdesc=insn.source))(queue) - strengths = waa * evaluate(insn.density).with_queue(queue) + self.ambient_dim, dofdesc=insn.source))(actx) + strengths = waa * evaluate(insn.density) + + from meshmode.discretization import Discretization + flat_strengths = flatten(strengths) source_discr = bound_expr.places.get_discretization( insn.source.geometry, insn.source.discr_stage) # FIXME: Do this all at once - result = [] + results = [] for o in insn.outputs: source_dd = insn.source.copy(discr_stage=o.target_name.discr_stage) target_discr = bound_expr.places.get_discretization( @@ -722,31 +762,41 @@ def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate, assert abs(o.qbx_forced_limit) > 0 expansion_radii = bind(bound_expr.places, sym.expansion_radii( - self.ambient_dim, dofdesc=o.target_name))(queue) + self.ambient_dim, dofdesc=o.target_name))(actx) centers = bind(bound_expr.places, sym.expansion_centers( self.ambient_dim, o.qbx_forced_limit, - dofdesc=o.target_name))(queue) + dofdesc=o.target_name))(actx) evt, output_for_each_kernel = lpot_applier( - queue, target_discr.nodes(), - source_discr.nodes(), - centers, - [strengths], - expansion_radii=expansion_radii, + actx.queue, + flatten(thaw(actx, target_discr.nodes())), + flatten(thaw(actx, source_discr.nodes())), + flatten(centers), + [flat_strengths], + expansion_radii=flatten(expansion_radii), **kernel_args) - result.append((o.name, output_for_each_kernel[o.kernel_index])) + + result = output_for_each_kernel[o.kernel_index] + if isinstance(target_discr, Discretization): + result = unflatten(actx, target_discr, result) + + results.append((o.name, result)) else: # no on-disk kernel caching if p2p is None: - p2p = self.get_p2p(insn.kernels) + p2p = self.get_p2p(actx, insn.kernels) if lpot_applier_on_tgt_subset is None: lpot_applier_on_tgt_subset = self.get_lpot_applier_on_tgt_subset( insn.kernels) + queue = actx.queue + + flat_targets = flatten_if_needed(actx, target_discr.nodes()) + flat_sources = flatten(thaw(actx, source_discr.nodes())) + evt, output_for_each_kernel = p2p(queue, - target_discr.nodes(), - source_discr.nodes(), - [strengths], **kernel_args) + flat_targets, flat_sources, + [flat_strengths], **kernel_args) qbx_forced_limit = o.qbx_forced_limit if qbx_forced_limit is None: @@ -779,7 +829,7 @@ def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate, if (o.qbx_forced_limit is not None and abs(o.qbx_forced_limit) == 1 - and qbx_tgt_count < target_discr.nnodes): + and qbx_tgt_count < target_discr.ndofs): raise RuntimeError("Did not find a matching QBX center " "for some targets") @@ -794,19 +844,23 @@ def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate, if qbx_tgt_count: lpot_applier_on_tgt_subset( queue, - targets=target_discr.nodes(), - sources=source_discr.nodes(), - centers=geo_data.centers(), - expansion_radii=geo_data.expansion_radii(), - strengths=[strengths], + targets=flat_targets, + sources=flat_sources, + centers=geo_data.flat_centers(), + expansion_radii=geo_data.flat_expansion_radii(), + strengths=[flat_strengths], qbx_tgt_numbers=qbx_tgt_numbers, qbx_center_numbers=qbx_center_numbers, **tgt_subset_kwargs) - result.append((o.name, output_for_each_kernel[o.kernel_index])) + result = output_for_each_kernel[o.kernel_index] + if isinstance(target_discr, Discretization): + result = unflatten(actx, target_discr, result) + + results.append((o.name, result)) timing_data = {} - return result, timing_data + return results, timing_data # }}} diff --git a/pytential/qbx/direct.py b/pytential/qbx/direct.py index eac3c7fc8..1bcee7c04 100644 --- a/pytential/qbx/direct.py +++ b/pytential/qbx/direct.py @@ -109,7 +109,11 @@ def get_kernel(self): def __call__(self, queue, targets, sources, centers, strengths, expansion_radii, **kwargs): - knl = self.get_cached_optimized_kernel() + from sumpy.tools import is_obj_array_like + knl = self.get_cached_optimized_kernel( + targets_is_obj_array=is_obj_array_like(targets), + sources_is_obj_array=is_obj_array_like(sources), + centers_is_obj_array=is_obj_array_like(centers)) for i, dens in enumerate(strengths): kwargs["strength_%d" % i] = dens diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 8b9ea4292..5185376e4 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -225,8 +225,8 @@ def form_global_qbx_locals(self, src_weights): self.queue, global_qbx_centers=geo_data.global_qbx_centers(), qbx_center_to_target_box=geo_data.qbx_center_to_target_box(), - qbx_centers=geo_data.centers(), - qbx_expansion_radii=geo_data.expansion_radii(), + qbx_centers=geo_data.flat_centers(), + qbx_expansion_radii=geo_data.flat_expansion_radii(), source_box_starts=starts, source_box_lists=lists, @@ -268,8 +268,8 @@ def translate_box_multipoles_to_qbx_local(self, multipole_exps): ), centers=self.tree.box_centers, - qbx_centers=geo_data.centers(), - qbx_expansion_radii=geo_data.expansion_radii(), + qbx_centers=geo_data.flat_centers(), + qbx_expansion_radii=geo_data.flat_expansion_radii(), src_expansions=source_mpoles_view, src_base_ibox=source_level_start_ibox, @@ -321,8 +321,8 @@ def translate_box_local_to_qbx_local(self, local_exps): target_base_ibox=target_level_start_ibox, centers=self.tree.box_centers, - qbx_centers=geo_data.centers(), - qbx_expansion_radii=geo_data.expansion_radii(), + qbx_centers=geo_data.flat_centers(), + qbx_expansion_radii=geo_data.flat_expansion_radii(), expansions=target_locals_view, qbx_expansions=qbx_expansions, @@ -356,8 +356,8 @@ def eval_qbx_expansions(self, qbx_expansions): qbxl2p = self.code.qbxl2p(self.qbx_order) evt, pot_res = qbxl2p(self.queue, - qbx_centers=geo_data.centers(), - qbx_expansion_radii=geo_data.expansion_radii(), + qbx_centers=geo_data.flat_centers(), + qbx_expansion_radii=geo_data.flat_expansion_radii(), global_qbx_centers=geo_data.global_qbx_centers(), @@ -582,8 +582,8 @@ def reorder_and_finalize_potentials(x): # potential back into a CL array. return wrangler.finalize_potentials(x[tree.sorted_target_ids]) - from pytools.obj_array import with_object_array_or_scalar - result = with_object_array_or_scalar( + from pytools.obj_array import obj_array_vectorize + result = obj_array_vectorize( reorder_and_finalize_potentials, all_potentials_in_tree_order) # }}} diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 7a991ddfa..ace246b7a 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -28,6 +28,9 @@ import pyopencl as cl import pyopencl.array # noqa from pytools import memoize_method +from pytools.obj_array import obj_array_vectorize +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import flatten, thaw from boxtree.tools import DeviceDataRecord from boxtree.pyfmmlib_integration import FMMLibRotationDataInterface import loopy as lp @@ -76,7 +79,7 @@ Geometry description code container ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: QBXFMMGeometryCodeGetter +.. autoclass:: QBXFMMGeometryDataCodeContainer :members: :undoc-members: @@ -109,16 +112,21 @@ class target_state(Enum): # noqa FAILED = -2 -class QBXFMMGeometryCodeGetter(TreeCodeContainerMixin): - def __init__(self, cl_context, ambient_dim, tree_code_container, debug, +class QBXFMMGeometryDataCodeContainer(TreeCodeContainerMixin): + def __init__(self, actx: PyOpenCLArrayContext, ambient_dim, + tree_code_container, debug, _well_sep_is_n_away, _from_sep_smaller_crit): - self.cl_context = cl_context + self.array_context = actx self.ambient_dim = ambient_dim self.tree_code_container = tree_code_container self.debug = debug self._well_sep_is_n_away = _well_sep_is_n_away self._from_sep_smaller_crit = _from_sep_smaller_crit + @property + def cl_context(self): + return self.array_context.context + @memoize_method def copy_targets_kernel(self): knl = lp.make_kernel( @@ -324,7 +332,7 @@ class QBXFMMGeometryData(FMMLibRotationDataInterface): .. attribute:: code_getter - The :class:`QBXFMMGeometryCodeGetter` for this object. + The :class:`QBXFMMGeometryDataCodeContainer` for this object. .. attribute:: target_discrs_and_qbx_sides @@ -348,7 +356,7 @@ class QBXFMMGeometryData(FMMLibRotationDataInterface): .. rubric :: Expansion centers .. attribute:: ncenters - .. automethod:: centers() + .. automethod:: flat_centers() .. rubric :: Methods @@ -401,49 +409,54 @@ def __init__(self, places, source_dd, def ambient_dim(self): return self.lpot_source.ambient_dim - @property - def cl_context(self): - return self.lpot_source.cl_context - @property def coord_dtype(self): return self.lpot_source.density_discr.real_dtype + @property + def array_context(self): + return self.code_getter.array_context + + @property + def cl_context(self): + return self.code_getter.cl_context + # {{{ centers/radii @property def ncenters(self): - return len(self.centers()[0]) + return len(self.flat_centers()[0]) @memoize_method - def centers(self): - """ Return an object array of (interleaved) center coordinates. + def flat_centers(self): + """Return an object array of (interleaved) center coordinates. ``coord_t [ambient_dim][ncenters]`` """ from pytential import bind, sym - from pytools.obj_array import make_obj_array - with cl.CommandQueue(self.cl_context) as queue: - centers = bind(self.places, sym.interleaved_expansion_centers( + centers = bind(self.places, sym.interleaved_expansion_centers( self.ambient_dim, - dofdesc=self.source_dd.to_stage1()))(queue) - return make_obj_array([ax.with_queue(None) for ax in centers]) + dofdesc=self.source_dd.to_stage1()))(self.array_context) + return obj_array_vectorize(self.array_context.freeze, flatten(centers)) @memoize_method - def expansion_radii(self): - """Return an array of radii associated with the (interleaved) + def flat_expansion_radii(self): + """Return an array of radii associated with the (interleaved) expansion centers. ``coord_t [ncenters]`` """ from pytential import bind, sym - with cl.CommandQueue(self.cl_context) as queue: - return bind(self.places, sym.expansion_radii( - self.ambient_dim, - granularity=sym.GRANULARITY_CENTER, - dofdesc=self.source_dd.to_stage1()))(queue) + radii = bind(self.places, + sym.expansion_radii( + self.ambient_dim, + granularity=sym.GRANULARITY_CENTER, + dofdesc=self.source_dd.to_stage1()))( + self.array_context) + + return self.array_context.freeze(flatten(radii)) # }}} @@ -453,36 +466,41 @@ def expansion_radii(self): def target_info(self): """Return a :class:`TargetInfo`. |cached|""" - code_getter = self.code_getter - with cl.CommandQueue(self.cl_context) as queue: - ntargets = self.ncenters - target_discr_starts = [] + from pytential.utils import flatten_if_needed - for target_discr, qbx_side in self.target_discrs_and_qbx_sides: - target_discr_starts.append(ntargets) - ntargets += target_discr.nnodes + code_getter = self.code_getter + queue = self.array_context.queue + ntargets = self.ncenters + target_discr_starts = [] + for target_discr, qbx_side in self.target_discrs_and_qbx_sides: target_discr_starts.append(ntargets) + ntargets += target_discr.ndofs - targets = cl.array.empty( - self.cl_context, (self.ambient_dim, ntargets), - self.coord_dtype) + target_discr_starts.append(ntargets) + + targets = cl.array.empty( + self.cl_context, (self.ambient_dim, ntargets), + self.coord_dtype) + code_getter.copy_targets_kernel()( + queue, + targets=targets[:, :self.ncenters], + points=self.flat_centers()) + + for start, (target_discr, _) in zip( + target_discr_starts, self.target_discrs_and_qbx_sides): code_getter.copy_targets_kernel()( queue, - targets=targets[:, :self.ncenters], - points=self.centers()) + targets=targets[:, + start:start+target_discr.ndofs], + points=flatten_if_needed( + self.array_context, + target_discr.nodes())) - for start, (target_discr, _) in zip( - target_discr_starts, self.target_discrs_and_qbx_sides): - code_getter.copy_targets_kernel()( - queue, - targets=targets[:, start:start+target_discr.nnodes], - points=target_discr.nodes()) - - return TargetInfo( - targets=targets, - target_discr_starts=target_discr_starts, - ntargets=ntargets).with_queue(None) + return TargetInfo( + targets=targets, + target_discr_starts=target_discr_starts, + ntargets=ntargets).with_queue(None) def target_side_preferences(self): """Return one big array combining all the data from @@ -492,7 +510,7 @@ def target_side_preferences(self): tgt_info = self.target_info() - with cl.CommandQueue(self.cl_context) as queue: + with cl.CommandQueue(self.array_context.context) as queue: target_side_preferences = cl.array.empty( queue, tgt_info.ntargets, np.int8) target_side_preferences[:self.ncenters] = 0 @@ -500,7 +518,7 @@ def target_side_preferences(self): for tdstart, (target_discr, qbx_side) in \ zip(tgt_info.target_discr_starts, self.target_discrs_and_qbx_sides): - target_side_preferences[tdstart:tdstart+target_discr.nnodes] \ + target_side_preferences[tdstart:tdstart+target_discr.ndofs] \ = qbx_side return target_side_preferences.with_queue(None) @@ -521,52 +539,54 @@ def tree(self): lpot_source = self.lpot_source target_info = self.target_info() - with cl.CommandQueue(self.cl_context) as queue: - from pytential import sym - quad_stage2_discr = self.places.get_discretization( - self.source_dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) - - nsources = quad_stage2_discr.nnodes - nparticles = nsources + target_info.ntargets - - target_radii = None - if lpot_source._expansions_in_tree_have_extent: - target_radii = cl.array.zeros(queue, target_info.ntargets, - self.coord_dtype) - target_radii[:self.ncenters] = self.expansion_radii() - - refine_weights = cl.array.empty(queue, nparticles, dtype=np.int32) - - # 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() - - tree, _ = code_getter.build_tree()(queue, - particles=quad_stage2_discr.nodes(), - targets=target_info.targets, - target_radii=target_radii, - max_leaf_refine_weight=lpot_source._max_leaf_refine_weight, - refine_weights=refine_weights, - debug=self.debug, - stick_out_factor=lpot_source._expansion_stick_out_factor, - extent_norm=lpot_source._box_extent_norm, - kind=self.tree_kind) + queue = self.array_context.queue - if self.debug: - tgt_count_2 = cl.array.sum( - tree.box_target_counts_nonchild, queue=queue).get() + from pytential import sym + quad_stage2_discr = self.places.get_discretization( + self.source_dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) - assert (tree.ntargets == tgt_count_2), (tree.ntargets, tgt_count_2) + nsources = sum(grp.ndofs for grp in quad_stage2_discr.groups) + nparticles = nsources + target_info.ntargets - return tree + target_radii = None + if lpot_source._expansions_in_tree_have_extent: + target_radii = cl.array.zeros(queue, target_info.ntargets, + self.coord_dtype) + target_radii[:self.ncenters] = self.flat_expansion_radii() + + refine_weights = cl.array.empty(queue, nparticles, dtype=np.int32) + + # 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() + + tree, _ = code_getter.build_tree()(queue, + particles=flatten(thaw( + self.array_context, quad_stage2_discr.nodes())), + targets=target_info.targets, + target_radii=target_radii, + max_leaf_refine_weight=lpot_source._max_leaf_refine_weight, + refine_weights=refine_weights, + debug=self.debug, + stick_out_factor=lpot_source._expansion_stick_out_factor, + extent_norm=lpot_source._box_extent_norm, + kind=self.tree_kind) + + if self.debug: + tgt_count_2 = cl.array.sum( + tree.box_target_counts_nonchild, queue=queue).get() + + assert (tree.ntargets == tgt_count_2), (tree.ntargets, tgt_count_2) + + return tree # }}} @@ -761,31 +781,32 @@ def user_target_to_center(self): from pytential.target import PointsTarget - with cl.CommandQueue(self.cl_context) as queue: - target_side_prefs = (self - .target_side_preferences()[self.ncenters:].get(queue=queue)) - - target_discrs_and_qbx_sides = [( - PointsTarget(target_info.targets[:, self.ncenters:]), - target_side_prefs.astype(np.int32))] - - target_association_wrangler = ( - self.lpot_source.target_association_code_container - .get_wrangler(queue)) - - tgt_assoc_result = associate_targets_to_qbx_centers( - self.places, - self.source_dd, - target_association_wrangler, - target_discrs_and_qbx_sides, - target_association_tolerance=( - self.target_association_tolerance), - debug=self.debug) - - result = cl.array.empty(queue, target_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 + queue = self.array_context.queue + + target_side_prefs = (self + .target_side_preferences()[self.ncenters:].get(queue=queue)) + + target_discrs_and_qbx_sides = [( + PointsTarget(target_info.targets[:, self.ncenters:]), + target_side_prefs.astype(np.int32))] + + target_association_wrangler = ( + self.lpot_source.target_association_code_container + .get_wrangler(self.array_context)) + + tgt_assoc_result = associate_targets_to_qbx_centers( + self.places, + self.source_dd, + target_association_wrangler, + target_discrs_and_qbx_sides, + target_association_tolerance=( + self.target_association_tolerance), + debug=self.debug) + + result = cl.array.empty(queue, target_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 return result.with_queue(None) @@ -918,7 +939,7 @@ def plot(self, draw_circles=False, draw_center_numbers=False, # {{{ draw centers and circles - centers = self.centers() + centers = self.flat_centers() centers = [ centers[0].get(queue), centers[1].get(queue)] @@ -937,7 +958,7 @@ def plot(self, draw_circles=False, draw_center_numbers=False, if draw_circles: for icenter, (cx, cy, r) in enumerate(zip( centers[0], centers[1], - self.expansion_radii().get(queue))): + self.flat_expansion_radii().get(queue))): ax.add_artist( pt.Circle((cx, cy), r, fill=False, ls="dotted", lw=1)) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index b3c28ee6e..dbcfe657b 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import division, absolute_import, print_function +from __future__ import annotations __copyright__ = """ Copyright (C) 2013 Andreas Kloeckner @@ -29,6 +29,8 @@ import loopy as lp from loopy.version import MOST_RECENT_LANGUAGE_VERSION +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import flatten import numpy as np import pyopencl as cl @@ -219,8 +221,8 @@ class RefinerCodeContainer(TreeCodeContainerMixin): - def __init__(self, cl_context, tree_code_container): - self.cl_context = cl_context + def __init__(self, actx: PyOpenCLArrayContext, tree_code_container): + self.array_context = actx self.tree_code_container = tree_code_container @memoize_method @@ -228,7 +230,7 @@ def expansion_disk_undisturbed_by_sources_checker( self, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, particle_id_dtype, max_levels): return EXPANSION_DISK_UNDISTURBED_BY_SOURCES_CHECKER.generate( - self.cl_context, + self.array_context.context, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, max_levels, extra_type_aliases=(("particle_id_t", particle_id_dtype),)) @@ -238,7 +240,7 @@ def sufficient_source_quadrature_resolution_checker( self, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, particle_id_dtype, max_levels): return SUFFICIENT_SOURCE_QUADRATURE_RESOLUTION_CHECKER.generate( - self.cl_context, + self.array_context.context, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, max_levels, extra_type_aliases=(("particle_id_t", particle_id_dtype),)) @@ -268,11 +270,11 @@ def element_prop_threshold_checker(self): knl = lp.split_iname(knl, "ielement", 128, inner_tag="l.0", outer_tag="g.0") return knl - def get_wrangler(self, queue): + def get_wrangler(self): """ :arg queue: """ - return RefinerWrangler(self, queue) + return RefinerWrangler(self.array_context, self) # }}} @@ -309,9 +311,10 @@ def check_expansion_disks_undisturbed_by_sources(self, unwrap_args = AreaQueryElementwiseTemplate.unwrap_args from pytential import bind, sym - center_danger_zone_radii = bind(stage1_density_discr, + center_danger_zone_radii = flatten( + bind(stage1_density_discr, sym.expansion_radii(stage1_density_discr.ambient_dim, - granularity=sym.GRANULARITY_CENTER))(self.queue) + granularity=sym.GRANULARITY_CENTER))(self.array_context)) evt = knl( *unwrap_args( @@ -367,9 +370,11 @@ def check_sufficient_source_quadrature_resolution(self, from pytential import bind, sym dd = sym.as_dofdesc(sym.GRANULARITY_ELEMENT).to_stage2() - source_danger_zone_radii_by_panel = bind(stage2_density_discr, - sym._source_danger_zone_radii( - stage2_density_discr.ambient_dim, dofdesc=dd))(self.queue) + source_danger_zone_radii_by_panel = flatten( + bind(stage2_density_discr, + sym._source_danger_zone_radii( + stage2_density_discr.ambient_dim, dofdesc=dd)) + (self.array_context)) unwrap_args = AreaQueryElementwiseTemplate.unwrap_args evt = knl( @@ -407,6 +412,10 @@ def check_element_prop_threshold(self, element_property, threshold, refine_flags if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() + from pytential.utils import flatten_if_needed + element_property = flatten_if_needed( + self.array_context, element_property) + evt, out = knl(self.queue, element_property=element_property, refine_flags=refine_flags, @@ -436,8 +445,10 @@ def refine(self, density_discr, refiner, refine_flags, factory, debug): 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) + from meshmode.discretization.connection import ( + make_refinement_connection) + conn = make_refinement_connection( + self.array_context, refiner, density_discr, factory) return conn @@ -485,7 +496,7 @@ def _warn_max_iterations(violated_criteria, expansion_disturbance_tolerance): RefinerNotConvergedWarning) -def _visualize_refinement(queue, discr, +def _visualize_refinement(actx: PyOpenCLArrayContext, discr, niter, stage_nr, stage_name, flags, visualize=False): if not visualize: return @@ -498,18 +509,18 @@ def _visualize_refinement(queue, discr, stage_name, np.sum(flags), discr.mesh.nelements, stage_nr) from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr, 3) + vis = make_visualizer(actx, discr, 3) assert len(flags) == discr.mesh.nelements flags = flags.astype(np.bool) - nodes_flags = np.zeros(discr.nnodes) + nodes_flags = np.zeros(discr.ndofs) for grp in discr.groups: meg = grp.mesh_el_group grp.view(nodes_flags)[ flags[meg.element_nr_base:meg.nelements+meg.element_nr_base]] = 1 - nodes_flags = cl.array.to_device(queue, nodes_flags) + nodes_flags = actx.from_numpy(nodes_flags) vis_data = [ ("refine_flags", nodes_flags), ] @@ -517,7 +528,7 @@ def _visualize_refinement(queue, discr, if 0: from pytential import sym, bind bdry_normals = bind(discr, sym.normal(discr.ambient_dim))( - queue).as_vector(dtype=object) + actx).as_vector(dtype=object) vis_data.append(("bdry_normals", bdry_normals),) vis.write_vtk_file("refinement-%s-%03d.vtu" % (stage_name, niter), vis_data) @@ -529,7 +540,7 @@ def _make_quad_stage2_discr(lpot_source, stage2_density_discr): QuadratureSimplexGroupFactory return Discretization( - lpot_source.cl_context, + lpot_source._setup_actx, stage2_density_discr.mesh, QuadratureSimplexGroupFactory(lpot_source.fine_order), lpot_source.real_dtype) @@ -583,6 +594,8 @@ def _refine_qbx_stage1(lpot_source, density_discr, iter_violated_criteria = ["start"] niter = 0 + actx = wrangler.array_context + stage1_density_discr = density_discr while iter_violated_criteria: iter_violated_criteria = [] @@ -602,7 +615,7 @@ def _refine_qbx_stage1(lpot_source, density_discr, quad_resolution = bind(stage1_density_discr, sym._quad_resolution(stage1_density_discr.ambient_dim, - dofdesc=sym.GRANULARITY_ELEMENT))(wrangler.queue) + dofdesc=sym.GRANULARITY_ELEMENT))(actx) violates_kernel_length_scale = \ wrangler.check_element_prop_threshold( @@ -612,7 +625,7 @@ def _refine_qbx_stage1(lpot_source, density_discr, if violates_kernel_length_scale: iter_violated_criteria.append("kernel length scale") - _visualize_refinement(wrangler.queue, stage1_density_discr, + _visualize_refinement(actx, stage1_density_discr, niter, 1, "kernel-length-scale", refine_flags, visualize=visualize) @@ -622,7 +635,7 @@ def _refine_qbx_stage1(lpot_source, density_discr, scaled_max_curv = bind(stage1_density_discr, sym.ElementwiseMax(sym._scaled_max_curvature( stage1_density_discr.ambient_dim), - dofdesc=sym.GRANULARITY_ELEMENT))(wrangler.queue) + dofdesc=sym.GRANULARITY_ELEMENT))(actx) violates_scaled_max_curv = \ wrangler.check_element_prop_threshold( @@ -764,7 +777,8 @@ def _refine_qbx_stage2(lpot_source, stage1_density_discr, def _refine_qbx_quad_stage2(lpot_source, stage2_density_discr): from meshmode.discretization.connection import make_same_mesh_connection discr = _make_quad_stage2_discr(lpot_source, stage2_density_discr) - conn = make_same_mesh_connection(discr, stage2_density_discr) + conn = make_same_mesh_connection( + lpot_source._setup_actx, discr, stage2_density_discr) return discr, conn @@ -898,7 +912,7 @@ def get_from_cache(from_ds, to_ds): # {{{ refine_geometry_collection -def refine_geometry_collection(queue, places, +def refine_geometry_collection(places, group_factory=None, refine_discr_stage=None, kernel_length_scale=None, @@ -945,7 +959,7 @@ def refine_geometry_collection(queue, places, continue _refine_for_global_qbx(places, dofdesc, - lpot_source.refiner_code_container.get_wrangler(queue), + lpot_source.refiner_code_container.get_wrangler(), group_factory=group_factory, kernel_length_scale=kernel_length_scale, scaled_max_curvature_threshold=scaled_max_curvature_threshold, diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 39b226ab4..64aecae6d 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -36,6 +36,8 @@ from boxtree.area_query import AreaQueryElementwiseTemplate from boxtree.tools import InlineBinarySearch from cgen import Enum +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import flatten from pytential.qbx.utils import ( QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS, TreeWranglerBase, TreeCodeContainerMixin) @@ -444,10 +446,14 @@ class QBXTargetAssociation(DeviceDataRecord): class TargetAssociationCodeContainer(TreeCodeContainerMixin): - def __init__(self, cl_context, tree_code_container): - self.cl_context = cl_context + def __init__(self, actx: PyOpenCLArrayContext, tree_code_container): + self.array_context = actx self.tree_code_container = tree_code_container + @property + def cl_context(self): + return self.array_context.context + @memoize_method def target_marker(self, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, particle_id_dtype, max_levels): @@ -489,8 +495,8 @@ def space_invader_query(self): from boxtree.area_query import SpaceInvaderQueryBuilder return SpaceInvaderQueryBuilder(self.cl_context) - def get_wrangler(self, queue): - return TargetAssociationWrangler(self, queue) + def get_wrangler(self, actx: PyOpenCLArrayContext): + return TargetAssociationWrangler(actx, code_container=self) class TargetAssociationWrangler(TreeWranglerBase): @@ -521,9 +527,11 @@ def mark_targets(self, places, dofdesc, source_slice = tree.sorted_target_ids[tree.qbx_user_source_slice] sources = [ axis.with_queue(self.queue)[source_slice] for axis in tree.sources] - tunnel_radius_by_source = bind(places, - sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc))( - self.queue) + + tunnel_radius_by_source = flatten( + bind(places, + sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc)) + (self.array_context)) # Target-marking algorithm (TGTMARK): # @@ -620,9 +628,9 @@ def find_centers(self, places, dofdesc, expansion_radii_by_center = bind(places, sym.expansion_radii( ambient_dim, granularity=sym.GRANULARITY_CENTER, - dofdesc=dofdesc))(self.queue) - expansion_radii_by_center_with_tolerance = \ - expansion_radii_by_center * (1 + target_association_tolerance) + dofdesc=dofdesc))(self.array_context) + expansion_radii_by_center_with_tolerance = flatten( + expansion_radii_by_center * (1 + target_association_tolerance)) # Idea: # @@ -716,11 +724,13 @@ def mark_panels_for_refinement(self, places, dofdesc, source_slice = tree.user_source_ids[tree.qbx_user_source_slice] sources = [ axis.with_queue(self.queue)[source_slice] for axis in tree.sources] - tunnel_radius_by_source = bind(places, - sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc))( - self.queue) - # See (TGTMARK) above for algorithm. + tunnel_radius_by_source = flatten( + bind(places, + sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc)) + (self.array_context)) + + # see (TGTMARK) above for algorithm. box_to_search_dist, evt = self.code_container.space_invader_query()( self.queue, @@ -731,10 +741,6 @@ def mark_panels_for_refinement(self, places, dofdesc, wait_for=wait_for) wait_for = [evt] - tunnel_radius_by_source = bind(places, - sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc))( - self.queue) - evt = knl( *unwrap_args( tree, peer_lists, @@ -767,17 +773,17 @@ def mark_panels_for_refinement(self, places, dofdesc, return (found_panel_to_refine == 1).all().get() def make_target_flags(self, target_discrs_and_qbx_sides): - ntargets = sum(discr.nnodes for discr, _ in target_discrs_and_qbx_sides) + ntargets = sum(discr.ndofs for discr, _ in target_discrs_and_qbx_sides) target_flags = cl.array.empty(self.queue, ntargets, dtype=np.int32) offset = 0 for discr, flags in target_discrs_and_qbx_sides: if np.isscalar(flags): - target_flags[offset:offset + discr.nnodes].fill(flags) + target_flags[offset:offset + discr.ndofs].fill(flags) else: - assert len(flags) == discr.nnodes - target_flags[offset:offset + discr.nnodes] = flags - offset += discr.nnodes + assert len(flags) == discr.ndofs + target_flags[offset:offset + discr.ndofs] = flags + offset += discr.ndofs target_flags.finish() return target_flags diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index b872152a2..da804df5c 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import division, absolute_import, print_function +from __future__ import annotations __copyright__ = """ Copyright (C) 2016 Matt Wala @@ -28,6 +28,7 @@ import numpy as np from boxtree.tree import Tree +from meshmode.array_context import PyOpenCLArrayContext import pyopencl as cl import pyopencl.array # noqa from pytools import memoize_method @@ -72,23 +73,23 @@ class TreeCodeContainer(object): - def __init__(self, cl_context): - self.cl_context = cl_context + def __init__(self, actx: PyOpenCLArrayContext): + self.array_context = actx @memoize_method def build_tree(self): from boxtree.tree_build import TreeBuilder - return TreeBuilder(self.cl_context) + return TreeBuilder(self.array_context.context) @memoize_method def peer_list_finder(self): from boxtree.area_query import PeerListFinder - return PeerListFinder(self.cl_context) + return PeerListFinder(self.array_context.context) @memoize_method def particle_list_filter(self): from boxtree.tree import ParticleListFilter - return ParticleListFilter(self.cl_context) + return ParticleListFilter(self.array_context.context) # }}} @@ -116,9 +117,13 @@ def particle_list_filter(self): class TreeWranglerBase(object): - def __init__(self, code_container, queue): + def __init__(self, array_context: PyOpenCLArrayContext, code_container): self.code_container = code_container - self.queue = queue + self.array_context = array_context + + @property + def queue(self): + return self.array_context.queue def build_tree(self, places, targets_list=(), sources_list=(), use_stage2_discr=False): @@ -126,7 +131,7 @@ def build_tree(self, places, targets_list=(), sources_list=(), plfilt = self.code_container.particle_list_filter() return build_tree_with_qbx_metadata( - self.queue, places, tb, plfilt, + self.array_context, places, tb, plfilt, sources_list=sources_list, targets_list=targets_list, use_stage2_discr=use_stage2_discr) @@ -226,8 +231,8 @@ class TreeWithQBXMetadata(Tree): @log_process(logger) -def build_tree_with_qbx_metadata(queue, places, - tree_builder, particle_list_filter, +def build_tree_with_qbx_metadata(actx: PyOpenCLArrayContext, + places, tree_builder, particle_list_filter, sources_list=(), targets_list=(), use_stage2_discr=False): """Return a :class:`TreeWithQBXMetadata` built from the given layer @@ -240,7 +245,7 @@ def build_tree_with_qbx_metadata(queue, places, :class:`~pytential.symbolic.primitives.QBX_SOURCE_STAGE1`. * targets from ``targets_list``. - :arg queue: An instance of :class:`pyopencl.CommandQueue` + :arg actx: A :class:`PyOpenCLArrayContext` :arg places: An instance of :class:`~pytential.symbolic.execution.GeometryCollection`. :arg targets_list: A list of :class:`pytential.target.TargetBase` @@ -274,15 +279,20 @@ def build_tree_with_qbx_metadata(queue, places, def _make_centers(discr): return bind(discr, sym.interleaved_expansion_centers( - discr.ambient_dim))(queue) + discr.ambient_dim))(actx) stage1_density_discr = stage1_density_discrs[0] density_discr = density_discrs[0] - sources = density_discr.nodes() - centers = _make_centers(stage1_density_discr) - targets = (tgt.nodes() for tgt in targets_list) + from meshmode.dof_array import flatten, thaw + from pytential.utils import flatten_if_needed + sources = flatten(thaw(actx, density_discr.nodes())) + centers = flatten(_make_centers(stage1_density_discr)) + targets = [ + flatten_if_needed(actx, tgt.nodes()) + for tgt in targets_list] + queue = actx.queue particles = tuple( cl.array.concatenate(dim_coords, queue=queue) for dim_coords in zip(sources, centers, *targets)) @@ -294,7 +304,7 @@ def _make_centers(discr): ncenters = len(centers[0]) # Each source gets an interior / exterior center. assert 2 * nsources == ncenters or use_stage2_discr - ntargets = sum(tgt.nnodes for tgt in targets_list) + ntargets = sum(tgt.ndofs for tgt in targets_list) # Slices qbx_user_source_slice = slice(0, nsources) @@ -333,7 +343,7 @@ def _make_centers(discr): box_to_class = ( particle_list_filter .filter_target_lists_in_user_order(queue, tree, flags) - .with_queue(queue)) + .with_queue(actx.queue)) if fixup: box_to_class.target_lists += fixup @@ -347,12 +357,14 @@ def _make_centers(discr): qbx_panel_to_source_starts = cl.array.empty( queue, npanels + 1, dtype=tree.particle_id_dtype) el_offset = 0 + node_nr_base = 0 for group in density_discr.groups: qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ - cl.array.arange(queue, group.node_nr_base, - group.node_nr_base + group.nnodes, - group.nunit_nodes, + cl.array.arange(queue, node_nr_base, + node_nr_base + group.ndofs, + group.nunit_dofs, dtype=tree.particle_id_dtype) + node_nr_base += group.ndofs el_offset += group.nelements qbx_panel_to_source_starts[-1] = nsources @@ -418,11 +430,11 @@ def ncenters(self): def centers(self): return np.array([ ci.get(queue=self.queue) - for ci in self.geo_data.centers()]) + for ci in self.geo_data.flat_centers()]) @memoize_method def expansion_radii(self): - return self.geo_data.expansion_radii().get(queue=self.queue) + return self.geo_data.flat_expansion_radii().get(queue=self.queue) @memoize_method def global_qbx_centers(self): diff --git a/pytential/solve.py b/pytential/solve.py index c2e932432..9a12cff47 100644 --- a/pytential/solve.py +++ b/pytential/solve.py @@ -22,9 +22,6 @@ THE SOFTWARE. """ - -from six.moves import range - __doc__ = """ .. autofunction:: gmres @@ -37,52 +34,24 @@ """ -def get_array_module(vec): - try: - from pyopencl.tools import array_module - from pytools.obj_array import is_obj_array - if is_obj_array(vec): - return array_module(vec[0]) - else: - return array_module(vec) - except ImportError: - return np - - -# {{{ block system support - -class VectorChopper(object): - def __init__(self, structured_vec): - from pytools.obj_array import is_obj_array - self.is_structured = is_obj_array(structured_vec) - self.array_module = get_array_module(structured_vec) - - if self.is_structured: - self.slices = [] - num_dofs = 0 - for entry in structured_vec: - if isinstance(entry, self.array_module.ndarray): - length = len(entry) - else: - length = 1 - - self.slices.append(slice(num_dofs, num_dofs+length)) - num_dofs += length - - def stack(self, vec): - if not self.is_structured: - return vec - - return self.array_module.hstack(vec) - - def chop(self, vec): - if not self.is_structured: - return vec - - from pytools.obj_array import make_obj_array - return make_obj_array([vec[slc] for slc in self.slices]) - -# }}} +import numpy as np +from numbers import Number +import pyopencl as cl +import pyopencl.array # noqa +from pytools.obj_array import obj_array_vectorize_n_args + + +def structured_vdot(x, y): + # vdot() implementation that is aware of scalars and host or + # PyOpenCL arrays. It also recurses down nested object arrays. + if (isinstance(x, Number) + or (isinstance(x, np.ndarray) and x.dtype.char != "O")): + return np.vdot(x, y) + elif isinstance(x, cl.array.Array): + return cl.array.vdot(x, y).get() + else: + assert isinstance(x, np.ndarray) and x.dtype.char == "O" + return sum(obj_array_vectorize_n_args(structured_vdot, x, y)) # {{{ gmres @@ -92,7 +61,6 @@ def chop(self, vec): # Necessary because SciPy gmres is not reentrant and thus does # not allow recursive solves. -import numpy as np from pytools import Record @@ -274,11 +242,8 @@ def norm(x): # {{{ progress reporting class ResidualPrinter: - def __init__(self, inner_product=None): + def __init__(self, inner_product=structured_vdot): self.count = 0 - if inner_product is None: - inner_product = np.vdot - self.inner_product = inner_product def __call__(self, resid): @@ -298,7 +263,7 @@ def __call__(self, resid): # {{{ entrypoint def gmres(op, rhs, restart=None, tol=None, x0=None, - inner_product=None, + inner_product=structured_vdot, maxiter=None, hard_failure=None, no_progress_factor=None, stall_iterations=None, callback=None, progress=False, require_monotonicity=True): @@ -320,32 +285,20 @@ def gmres(op, rhs, restart=None, tol=None, x0=None, :return: a :class:`GMRESResult` """ - amod = get_array_module(rhs) - - chopper = VectorChopper(rhs) - stacked_rhs = chopper.stack(rhs) - - stacked_x0 = x0 - if stacked_x0 is not None: - stacked_x0 = chopper.stack(stacked_x0) - - if inner_product is None: - inner_product = amod.vdot - if callback is None: if progress: callback = ResidualPrinter(inner_product) else: callback = None - result = _gmres(op, stacked_rhs, restart=restart, tol=tol, x0=stacked_x0, + result = _gmres(op, rhs, restart=restart, tol=tol, x0=x0, dot=inner_product, maxiter=maxiter, hard_failure=hard_failure, no_progress_factor=no_progress_factor, stall_iterations=stall_iterations, callback=callback, require_monotonicity=require_monotonicity) - return result.copy(solution=chopper.chop(result.solution)) + return result # }}} @@ -367,10 +320,7 @@ def lu(op, rhs, show_spectrum=False): pt.plot(ev.real, ev.imag, "o") pt.show() - chopper = VectorChopper(rhs) - return chopper.chop( - la.solve(mat, - chopper.stack(rhs))) + return la.solve(mat, rhs) # }}} diff --git a/pytential/source.py b/pytential/source.py index 7ed794abf..19d90ae7a 100644 --- a/pytential/source.py +++ b/pytential/source.py @@ -26,7 +26,7 @@ import numpy as np # noqa: F401 import pyopencl as cl # noqa: F401 import six -from pytools import memoize_method +from pytools import memoize_in from sumpy.fmm import UnableToCollectTimingData @@ -53,23 +53,51 @@ class PotentialSource(object): def preprocess_optemplate(self, name, discretizations, expr): return expr + @property + def real_dtype(self): + raise NotImplementedError + + @property + def complex_dtype(self): + raise NotImplementedError + + def get_p2p(self, actx, kernels): + raise NotImplementedError + + +class _SumpyP2PMixin(object): + + def get_p2p(self, actx, kernels): + @memoize_in(actx, (_SumpyP2PMixin, "p2p")) + def p2p(kernels): + from pytools import any + if any(knl.is_complex_valued for knl in kernels): + value_dtype = self.complex_dtype + else: + value_dtype = self.real_dtype + + from sumpy.p2p import P2P + return P2P(actx.context, + kernels, exclude_self=False, value_dtypes=value_dtype) + + return p2p(kernels) + # {{{ point potential source -class PointPotentialSource(PotentialSource): +class PointPotentialSource(_SumpyP2PMixin, PotentialSource): """ .. attribute:: nodes - An :class:`pyopencl.array.Array` of shape ``[ambient_dim, nnodes]``. + An :class:`pyopencl.array.Array` of shape ``[ambient_dim, ndofs]``. - .. attribute:: nnodes + .. attribute:: ndofs .. automethod:: cost_model_compute_potential_insn .. automethod:: exec_compute_potential_insn """ - def __init__(self, cl_context, nodes): - self.cl_context = cl_context + def __init__(self, nodes): self._nodes = nodes @property @@ -88,8 +116,9 @@ def real_dtype(self): return self._nodes.dtype @property - def nnodes(self): - return self._nodes.shape[-1] + def ndofs(self): + for coord_ary in self._nodes: + return coord_ary.shape[0] @property def complex_dtype(self): @@ -111,27 +140,11 @@ def op_group_features(self, expr): return result - @memoize_method - def get_p2p(self, kernels): - # needs to be separate method for caching - - from pytools import any - if any(knl.is_complex_valued for knl in kernels): - value_dtype = self.complex_dtype - else: - value_dtype = self.real_dtype - - from sumpy.p2p import P2P - p2p = P2P(self.cl_context, - kernels, exclude_self=False, value_dtypes=value_dtype) - - return p2p - - def cost_model_compute_potential_insn(self, queue, insn, bound_expr, + def cost_model_compute_potential_insn(self, actx, insn, bound_expr, evaluate, costs): raise NotImplementedError - def exec_compute_potential_insn(self, queue, insn, bound_expr, evaluate, + def exec_compute_potential_insn(self, actx, insn, bound_expr, evaluate, return_timing_data): if return_timing_data: from warnings import warn @@ -145,33 +158,41 @@ def exec_compute_potential_insn(self, queue, insn, bound_expr, evaluate, for arg_name, arg_expr in six.iteritems(insn.kernel_arguments): kernel_args[arg_name] = evaluate(arg_expr) - strengths = evaluate(insn.density).with_queue(queue).copy() + strengths = evaluate(insn.density) # FIXME: Do this all at once - result = [] + results = [] for o in insn.outputs: target_discr = bound_expr.places.get_discretization( o.target_name.geometry, o.target_name.discr_stage) # no on-disk kernel caching if p2p is None: - p2p = self.get_p2p(insn.kernels) + p2p = self.get_p2p(actx, insn.kernels) - evt, output_for_each_kernel = p2p(queue, - target_discr.nodes(), self._nodes, + from pytential.utils import flatten_if_needed + evt, output_for_each_kernel = p2p(actx.queue, + flatten_if_needed(actx, target_discr.nodes()), + self._nodes, [strengths], **kernel_args) - result.append((o.name, output_for_each_kernel[o.kernel_index])) + from meshmode.discretization import Discretization + result = output_for_each_kernel[o.kernel_index] + if isinstance(target_discr, Discretization): + from meshmode.dof_array import unflatten + result = unflatten(actx, target_discr, result) + + results.append((o.name, result)) timing_data = {} - return result, timing_data + return results, timing_data # }}} # {{{ layer potential source -class LayerPotentialSourceBase(PotentialSource): +class LayerPotentialSourceBase(_SumpyP2PMixin, PotentialSource): """A discretization of a layer potential using panel-based geometry, with support for refinement and upsampling. @@ -197,13 +218,17 @@ def __init__(self, density_discr): def ambient_dim(self): return self.density_discr.ambient_dim + @property + def _setup_actx(self): + return self.density_discr._setup_actx + @property def dim(self): return self.density_discr.dim @property def cl_context(self): - return self.density_discr.cl_context + return self.density_discr._setup_actx.context @property def real_dtype(self): @@ -213,22 +238,6 @@ def real_dtype(self): def complex_dtype(self): return self.density_discr.complex_dtype - @memoize_method - def get_p2p(self, kernels): - # needs to be separate method for caching - - from pytools import any - if any(knl.is_complex_valued for knl in kernels): - value_dtype = self.density_discr.complex_dtype - else: - value_dtype = self.density_discr.real_dtype - - from sumpy.p2p import P2P - p2p = P2P(self.cl_context, - kernels, exclude_self=False, value_dtypes=value_dtype) - - return p2p - # {{{ fmm setup helpers def get_fmm_kernel(self, kernels): @@ -252,10 +261,12 @@ def get_fmm_output_and_expansion_dtype(self, base_kernel, strengths): return self.real_dtype def get_fmm_expansion_wrangler_extra_kwargs( - self, queue, out_kernels, tree_user_source_ids, arguments, evaluator): + self, actx, out_kernels, tree_user_source_ids, arguments, evaluator): # This contains things like the Helmholtz parameter k or # the normal directions for double layers. + queue = actx.queue + def reorder_sources(source_array): if isinstance(source_array, cl.array.Array): return (source_array @@ -269,15 +280,17 @@ def reorder_sources(source_array): source_extra_kwargs = {} from sumpy.tools import gather_arguments, gather_source_arguments - from pytools.obj_array import with_object_array_or_scalar + from pytools.obj_array import obj_array_vectorize + from pytential.utils import flatten_if_needed + for func, var_dict in [ (gather_arguments, kernel_extra_kwargs), (gather_source_arguments, source_extra_kwargs), ]: for arg in func(out_kernels): - var_dict[arg.name] = with_object_array_or_scalar( + var_dict[arg.name] = obj_array_vectorize( reorder_sources, - evaluator(arguments[arg.name])) + flatten_if_needed(actx, evaluator(arguments[arg.name]))) return kernel_extra_kwargs, source_extra_kwargs diff --git a/pytential/symbolic/compiler.py b/pytential/symbolic/compiler.py index 3fcfbf2ec..eb01532d9 100644 --- a/pytential/symbolic/compiler.py +++ b/pytential/symbolic/compiler.py @@ -270,13 +270,13 @@ def gen_expr_arrow(expr, target_node): for dep in insn.get_dependencies(): gen_expr_arrow(dep, node_names[insn]) - from pytools.obj_array import is_obj_array + code_res = code.result - if is_obj_array(code.result): - for subexp in code.result: + if isinstance(code_res, np.ndarray) and code_res.dtype.char == "O": + for subexp in code_res: gen_expr_arrow(subexp, "result") else: - gen_expr_arrow(code.result, "result") + gen_expr_arrow(code_res, "result") return "digraph dataflow {\n%s\n}\n" % "\n".join(result) @@ -331,7 +331,7 @@ def get_next_step(self, available_names, done_insns): discardable_vars = set(available_names) - needed_vars # {{{ make sure results do not get discarded - from pytools.obj_array import with_object_array_or_scalar + from pytools.obj_array import obj_array_vectorize from pytential.symbolic.mappers import DependencyMapper dm = DependencyMapper(composite_leaves=False) @@ -347,7 +347,7 @@ def remove_result_variable(result_expr): assert isinstance(var, Variable) discardable_vars.discard(var.name) - with_object_array_or_scalar(remove_result_variable, self.result) + obj_array_vectorize(remove_result_variable, self.result) # }}} return argmax2(available_insns), discardable_vars @@ -387,9 +387,9 @@ def execute(self, exec_mapper, pre_assign_check=None): done_insns.add(insn) assignments = ( - self.get_exec_function(insn, exec_mapper) - (exec_mapper.queue, insn, exec_mapper.bound_expr, - exec_mapper)) + self.get_exec_function(insn, exec_mapper)( + exec_mapper.array_context, + insn, exec_mapper.bound_expr, exec_mapper)) assignees = insn.get_assignees() for target, value in assignments: @@ -412,8 +412,8 @@ def execute(self, exec_mapper, pre_assign_check=None): raise RuntimeError("not all instructions are reachable" "--did you forget to pass a value for a placeholder?") - from pytools.obj_array import with_object_array_or_scalar - return with_object_array_or_scalar(exec_mapper, self.result) + from pytools.obj_array import obj_array_vectorize + return obj_array_vectorize(exec_mapper, self.result) # }}} @@ -480,8 +480,8 @@ def __call__(self, expr): # Put the toplevel expressions into variables as well. - from pytools.obj_array import with_object_array_or_scalar - result = with_object_array_or_scalar(self.assign_to_new_var, result) + from pytools.obj_array import obj_array_vectorize + result = obj_array_vectorize(self.assign_to_new_var, result) return Code(self.code, result) diff --git a/pytential/symbolic/dof_connection.py b/pytential/symbolic/dof_connection.py index 9d23fb73d..4863652c6 100644 --- a/pytential/symbolic/dof_connection.py +++ b/pytential/symbolic/dof_connection.py @@ -27,12 +27,12 @@ """ import six -import pyopencl as cl -import pyopencl.array # noqa -from pytools import memoize +from meshmode.array_context import PyOpenCLArrayContext # noqa +from meshmode.dof_array import DOFArray +import numpy as np +from pytools import memoize_in import loopy as lp -from loopy.version import MOST_RECENT_LANGUAGE_VERSION __doc__ = """ @@ -69,7 +69,11 @@ def from_discr(self): def to_discr(self): return self.discr - def __call__(self, queue, vec): + @property + def array_context(self): + return self.discr._setup_actx + + def __call__(self, ary): raise NotImplementedError() @@ -85,61 +89,66 @@ class CenterGranularityConnection(GranularityConnection): def __init__(self, discr): super(CenterGranularityConnection, self).__init__(discr) - @memoize - def kernel(self): - knl = lp.make_kernel( - "[srclen, dstlen] -> {[i]: 0 <= i < srclen}", - """ - dst[2*i] = src1[i] - dst[2*i + 1] = src2[i] - """, - [ - lp.GlobalArg("src1", shape="srclen"), - lp.GlobalArg("src2", shape="srclen"), - lp.GlobalArg("dst", shape="dstlen"), - "..." - ], - name="node_interleaver_knl", - 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 - - def __call__(self, queue, vecs): + def _interleave_dof_arrays(self, ary1, ary2): + if not isinstance(ary1, DOFArray) or not isinstance(ary2, DOFArray): + raise TypeError("non-array passed to connection") + + @memoize_in(self.array_context, + (CenterGranularityConnection, "interleave")) + def prg(): + from meshmode.array_context import make_loopy_program + return make_loopy_program( + """{[iel, idof]: 0<=iel 1 + + def flatten(self, ary): + # Return a flat version of *ary*. The returned value is suitable for + # use with solvers whose API expects a one-dimensional array. + if not self._operator_uses_obj_array: + ary = [ary] + + result = self.array_context.empty(self.total_dofs, self.dtype) + from pytential.utils import flatten_if_needed + for res_i, (start, end) in zip(ary, self.starts_and_ends): + result[start:end] = flatten_if_needed(self.array_context, res_i) + return result - do_split = len(self.starts_and_ends) > 1 - from pytools.obj_array import make_obj_array + def unflatten(self, ary): + # Convert a flat version of *ary* into a structured version. + components = [] + for discr, (start, end) in zip(self.discrs, self.starts_and_ends): + component = ary[start:end] + from meshmode.discretization import Discretization + if isinstance(discr, Discretization): + from meshmode.dof_array import unflatten + component = unflatten(self.array_context, discr, component) + components.append(component) + + if self._operator_uses_obj_array: + from pytools.obj_array import make_obj_array + return make_obj_array(components) + else: + return components[0] - if do_split: - x = make_obj_array( - [x[start:end] for start, end in self.starts_and_ends]) + def matvec(self, x): + # Three types of inputs are supported: + # * flat NumPy arrays + # => output is a flat NumPy array + # * flat PyOpenCL arrays + # => output is a flat PyOpenCL array + # * structured arrays (object arrays/DOFArrays) + # => output has same structure as input + if isinstance(x, np.ndarray) and x.dtype.char != "O": + x = self.array_context.from_numpy(x) + flat = True + host = True + assert x.shape == (self.total_dofs,) + elif isinstance(x, cl.array.Array): + flat = True + host = False + assert x.shape == (self.total_dofs,) + elif isinstance(x, np.ndarray) and x.dtype.char == "O": + flat = False + host = False + else: + raise ValueError("unsupported input type") args = self.extra_args.copy() - args[self.arg_name] = x - result = self.bound_expr(self.queue, **args) - - if do_split: - # re-join what was split - joined_result = cl.array.empty(self.queue, self.total_dofs, - self.dtype) - for res_i, (start, end) in zip(result, self.starts_and_ends): - joined_result[start:end] = res_i - result = joined_result + args[self.arg_name] = self.unflatten(x) if flat else x + result = self.bound_expr(self.array_context, **args) - if out_host: - result = result.get() + if flat: + result = self.flatten(result) + if host: + result = self.array_context.to_numpy(result) return result @@ -632,25 +671,8 @@ def __init__(self, places, auto_where=None): raise TypeError("Values in 'places' must be discretization, targets " "or layer potential sources.") - # check cl_context - from pytools import is_single_valued - cl_contexts = [] - for p in six.itervalues(self.places): - if isinstance(p, (PotentialSource, Discretization)): - cl_contexts.append(p.cl_context) - elif isinstance(p, TargetBase): - nodes = p.nodes()[0] - if isinstance(nodes, cl.array.Array) and nodes.queue is not None: - cl_contexts.append(nodes.queue.context) - else: - raise ValueError("unexpected value type in 'places'") - - if not is_single_valued(cl_contexts): - raise RuntimeError("All 'places' must have the same CL context.") - - self.cl_context = cl_contexts[0] - # check ambient_dim + from pytools import is_single_valued ambient_dims = [p.ambient_dim for p in six.itervalues(self.places)] if not is_single_valued(ambient_dims): raise RuntimeError("All 'places' must have the same ambient dimension.") @@ -719,12 +741,11 @@ def _get_qbx_discretization(self, geometry, discr_stage): from pytential import sym from pytential.qbx.refinement import _refine_for_global_qbx - with cl.CommandQueue(lpot_source.cl_context) as queue: - # NOTE: this adds the required discretizations to the cache - dofdesc = sym.DOFDescriptor(geometry, discr_stage) - _refine_for_global_qbx(self, dofdesc, - lpot_source.refiner_code_container.get_wrangler(queue), - _copy_collection=False) + # NOTE: this adds the required discretizations to the cache + dofdesc = sym.DOFDescriptor(geometry, discr_stage) + _refine_for_global_qbx(self, dofdesc, + lpot_source.refiner_code_container.get_wrangler(), + _copy_collection=False) discr = self._get_discr_from_cache(geometry, discr_stage) @@ -829,7 +850,9 @@ def get_modeled_cost(self, queue, **args): self.code.execute(cost_model_mapper) return cost_model_mapper.get_modeled_cost() - def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): + def scipy_op( + self, actx: PyOpenCLArrayContext, arg_name, dtype, + domains=None, **extra_args): """ :arg domains: a list of discretization identifiers or *None* values indicating the domains on which each component of the @@ -842,8 +865,7 @@ def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): and returning :class:`pyopencl.array.Array` arrays. """ - from pytools.obj_array import is_obj_array - if is_obj_array(self.code.result): + if isinstance(self.code.result, np.ndarray): nresults = len(self.code.result) else: nresults = 1 @@ -852,15 +874,18 @@ def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): self.places, domains, self.places.auto_target) total_dofs = 0 + discrs = [] starts_and_ends = [] for dom_name in domains: if dom_name is None: + discr = None size = 1 else: discr = self.places.get_discretization( dom_name.geometry, dom_name.discr_stage) - size = discr.nnodes + size = discr.ndofs + discrs.append(discr) starts_and_ends.append((total_dofs, total_dofs+size)) total_dofs += size @@ -869,10 +894,11 @@ def scipy_op(self, queue, arg_name, dtype, domains=None, **extra_args): # fair, since these operators are usually only used # 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) + return MatVecOp(self, actx, + arg_name, dtype, total_dofs, discrs, starts_and_ends, extra_args) - def eval(self, queue, context=None, timing_data=None): + def eval(self, context=None, timing_data=None, + array_context: Optional[PyOpenCLArrayContext] = None): """Evaluate the expression in *self*, using the :class:`pyopencl.CommandQueue` *queue* and the input variables given in the dictionary *context*. @@ -880,25 +906,81 @@ def eval(self, queue, context=None, timing_data=None): :arg timing_data: A dictionary into which timing data will be inserted during evaluation. (experimental) + :arg array_context: only needs to be supplied if no instances of + :class:`~meshmode.dof_array.DOFArray` with a + :class:`~meshmode.array_context.PyOpenCLArrayContext` + are supplied as part of *context*. :returns: the value of the expression, as a scalar, :class:`pyopencl.array.Array`, or an object array of these. """ if context is None: context = {} + + # {{{ figure array context + + array_contexts = [] + if array_context is not None: + if not isinstance(array_context, PyOpenCLArrayContext): + raise TypeError( + "first argument (if supplied) must be a " + "PyOpenCLArrayContext") + + array_contexts.append(array_context) + del array_context + + def look_for_array_contexts(ary): + if isinstance(ary, DOFArray): + if ary.array_context is not None: + array_contexts.append(ary.array_context) + elif isinstance(ary, np.ndarray) and ary.dtype.char == "O": + for idx in np.ndindex(ary.shape): + look_for_array_contexts(ary[idx]) + else: + pass + + for key, val in context.items(): + look_for_array_contexts(val) + + if array_contexts: + from pytools import is_single_valued + if not is_single_valued(array_contexts): + raise ValueError("arguments do not agree on an array context") + + array_context = array_contexts[0] + else: + array_context = None + + # }}} + exec_mapper = EvaluationMapper( - self, queue, context, timing_data=timing_data) + self, array_context, context, timing_data=timing_data) return self.code.execute(exec_mapper) - def __call__(self, queue, **args): + def __call__(self, *args, **kwargs): """Evaluate the expression in *self*, using the :class:`pyopencl.CommandQueue` *queue* and the input variables given in the dictionary *context*. :returns: the value of the expression, as a scalar, - :class:`pyopencl.array.Array`, or an object array of these. + :class:`meshmode.dof_array.DOFArray`, or an object array of + these. """ - return self.eval(queue, args) + array_context = None + if len(args) == 1: + array_context, = args + if not isinstance(array_context, PyOpenCLArrayContext): + raise TypeError("first positional argument (if given) " + "must be of type PyOpenCLArrayContext") + + elif not args: + pass + + else: + raise TypeError("More than one positional argument supplied. " + "None or an ArrayContext expected.") + + return self.eval(kwargs, array_context=array_context) def bind(places, expr, auto_where=None): @@ -956,10 +1038,10 @@ def _bmat(blocks, dtypes): return result -def build_matrix(queue, places, exprs, input_exprs, domains=None, +def build_matrix(actx, places, exprs, input_exprs, domains=None, auto_where=None, context=None): """ - :arg queue: a :class:`pyopencl.CommandQueue`. + :arg actx: a :class:`~meshmode.array_context.ArrayContext`. :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. @@ -981,13 +1063,14 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, context = {} from pytential import GeometryCollection - from pytools.obj_array import is_obj_array, make_obj_array if not isinstance(places, GeometryCollection): places = GeometryCollection(places, auto_where=auto_where) exprs = _prepare_expr(places, exprs, auto_where=auto_where) - if not is_obj_array(exprs): + if not (isinstance(exprs, np.ndarray) and exprs.dtype.char == "O"): + from pytools.obj_array import make_obj_array exprs = make_obj_array([exprs]) + try: input_exprs = list(input_exprs) except TypeError: @@ -1009,7 +1092,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, domains[ibcol].geometry, domains[ibcol].discr_stage) mbuilder = MatrixBuilder( - queue, + actx, dep_expr=input_exprs[ibcol], other_dep_exprs=(input_exprs[:ibcol] + input_exprs[ibcol + 1:]), @@ -1026,7 +1109,7 @@ def build_matrix(queue, places, exprs, input_exprs, domains=None, if isinstance(block, np.ndarray): dtypes.append(block.dtype) - return cl.array.to_device(queue, _bmat(blocks, dtypes)) + return actx.from_numpy(_bmat(blocks, dtypes)) # }}} diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index d63f30549..dc0eb3734 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -26,14 +26,14 @@ """ import numpy as np -import pyopencl as cl # noqa -import pyopencl.array # noqa import six from six.moves import intern from pytools import memoize_method from pytential.symbolic.mappers import EvaluationMapperBase +from pytential.utils import ( + flatten_if_needed, flatten_to_numpy, unflatten_from_numpy) # {{{ helpers @@ -56,7 +56,9 @@ def _get_layer_potential_args(mapper, expr, include_args=None): and arg_name not in include_args): continue - kernel_args[arg_name] = mapper.rec(arg_expr) + kernel_args[arg_name] = flatten_if_needed(mapper.array_context, + mapper.rec(arg_expr) + ) return kernel_args @@ -66,7 +68,7 @@ def _get_layer_potential_args(mapper, expr, include_args=None): # {{{ base classes for matrix builders class MatrixBuilderBase(EvaluationMapperBase): - def __init__(self, queue, dep_expr, other_dep_exprs, + def __init__(self, actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context): """ :arg queue: a :class:`pyopencl.CommandQueue`. @@ -84,7 +86,7 @@ def __init__(self, queue, dep_expr, other_dep_exprs, """ super(MatrixBuilderBase, self).__init__(context=context) - self.queue = queue + self.array_context = actx self.dep_expr = dep_expr self.other_dep_exprs = other_dep_exprs self.dep_source = dep_source @@ -94,7 +96,7 @@ def __init__(self, queue, dep_expr, other_dep_exprs, # {{{ def get_dep_variable(self): - return np.eye(self.dep_discr.nnodes, dtype=np.float64) + return np.eye(self.dep_discr.ndofs, dtype=np.float64) def is_kind_vector(self, x): return len(x.shape) == 1 @@ -196,17 +198,25 @@ def map_num_reference_derivative(self, expr): if self.is_kind_matrix(rec_operand): raise NotImplementedError("derivatives") - rec_operand = cl.array.to_device(self.queue, rec_operand) + dofdesc = expr.dofdesc op = sym.NumReferenceDerivative( ref_axes=expr.ref_axes, operand=sym.var("u"), - dofdesc=expr.dofdesc) - return bind(self.places, op)(self.queue, u=rec_operand).get() + dofdesc=dofdesc) + + discr = self.places.get_discretization(dofdesc.geometry, dofdesc.discr_stage) + rec_operand = unflatten_from_numpy(self.array_context, discr, rec_operand) + + return flatten_to_numpy(self.array_context, + bind(self.places, op)(self.array_context, u=rec_operand) + ) def map_node_coordinate_component(self, expr): from pytential import bind, sym op = sym.NodeCoordinateComponent(expr.ambient_axis, dofdesc=expr.dofdesc) - return bind(self.places, op)(self.queue).get() + return flatten_to_numpy(self.array_context, + bind(self.places, op)(self.array_context) + ) def map_call(self, expr): arg, = expr.parameters @@ -215,17 +225,13 @@ def map_call(self, expr): 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) - - from pytential import bind, sym - op = expr.function(sym.var("u")) - result = bind(self.places, op)(self.queue, u=rec_arg) - - if isinstance(result, cl.array.Array): - result = result.get() - - return result + from numbers import Number + if isinstance(rec_arg, Number): + return getattr(np, expr.function.name)(rec_arg) + else: + rec_arg = unflatten_from_numpy(self.array_context, None, rec_arg) + result = getattr(self.array_context.np, expr.function.name)(rec_arg) + return flatten_to_numpy(self.array_context, result) # }}} @@ -240,14 +246,14 @@ class MatrixBlockBuilderBase(MatrixBuilderBase): assume that each operator acts directly on the density. """ - def __init__(self, queue, dep_expr, other_dep_exprs, + def __init__(self, actx, 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, + super(MatrixBlockBuilderBase, self).__init__(actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context) self.index_set = index_set @@ -259,7 +265,7 @@ def _mat_mapper(self): # be computed on the full discretization, ignoring our index_set, # e.g the normal in a double layer potential - return MatrixBuilderBase(self.queue, + return MatrixBuilderBase(self.array_context, self.dep_expr, self.other_dep_exprs, self.dep_source, @@ -272,7 +278,7 @@ def _blk_mapper(self): # blk_mapper is used to recursively compute the density to # a layer potential operator to ensure there is no composition - return MatrixBlockBuilderBase(self.queue, + return MatrixBlockBuilderBase(self.array_context, self.dep_expr, self.other_dep_exprs, self.dep_source, @@ -302,9 +308,10 @@ def is_kind_matrix(self, x): # We'll cheat and build the matrix on the host. class MatrixBuilder(MatrixBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, + def __init__(self, actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context): - super(MatrixBuilder, self).__init__(queue, dep_expr, other_dep_exprs, + super(MatrixBuilder, self).__init__( + actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context) def map_interpolation(self, expr): @@ -313,13 +320,17 @@ def map_interpolation(self, expr): if expr.to_dd.discr_stage != sym.QBX_SOURCE_QUAD_STAGE2: raise RuntimeError("can only interpolate to QBX_SOURCE_QUAD_STAGE2") operand = self.rec(expr.operand) + actx = self.array_context if isinstance(operand, (int, float, complex, np.number)): return operand elif isinstance(operand, np.ndarray) and operand.ndim == 1: conn = self.places.get_connection(expr.from_dd, expr.to_dd) - return conn(self.queue, - cl.array.to_device(self.queue, operand)).get(self.queue) + discr = self.places.get_discretization( + expr.from_dd.geometry, expr.from_dd.discr_stage) + + operand = unflatten_from_numpy(actx, discr, operand) + return flatten_to_numpy(actx, conn(operand)) elif isinstance(operand, np.ndarray) and operand.ndim == 2: cache = self.places._get_cache("direct_resampler") key = (expr.from_dd.geometry, @@ -333,8 +344,8 @@ def map_interpolation(self, expr): flatten_chained_connection conn = self.places.get_connection(expr.from_dd, expr.to_dd) - conn = flatten_chained_connection(self.queue, conn) - mat = conn.full_resample_matrix(self.queue).get(self.queue) + conn = flatten_chained_connection(actx, conn) + mat = actx.to_numpy(conn.full_resample_matrix(actx)) # FIXME: the resample matrix is slow to compute and very big # to store, so caching it may not be the best idea @@ -359,6 +370,7 @@ def map_int_g(self, expr): if not self.is_kind_matrix(rec_density): raise NotImplementedError("layer potentials on non-variables") + actx = self.array_context kernel = expr.kernel kernel_args = _get_layer_potential_args(self, expr) @@ -366,31 +378,31 @@ def map_int_g(self, expr): local_expn = LineTaylorLocalExpansion(kernel, lpot_source.qbx_order) from sumpy.qbx import LayerPotentialMatrixGenerator - mat_gen = LayerPotentialMatrixGenerator( - self.queue.context, (local_expn,)) + mat_gen = LayerPotentialMatrixGenerator(actx.context, (local_expn,)) assert abs(expr.qbx_forced_limit) > 0 from pytential import bind, sym radii = bind(self.places, sym.expansion_radii( source_discr.ambient_dim, - dofdesc=expr.target))(self.queue) + dofdesc=expr.target))(actx) centers = bind(self.places, sym.expansion_centers( source_discr.ambient_dim, expr.qbx_forced_limit, - dofdesc=expr.target))(self.queue) - - _, (mat,) = mat_gen(self.queue, - targets=target_discr.nodes(), - sources=source_discr.nodes(), - centers=centers, - expansion_radii=radii, + dofdesc=expr.target))(actx) + + from meshmode.dof_array import flatten, thaw + _, (mat,) = mat_gen(actx.queue, + targets=flatten(thaw(actx, target_discr.nodes())), + sources=flatten(thaw(actx, source_discr.nodes())), + centers=flatten(centers), + expansion_radii=flatten(radii), **kernel_args) - mat = mat.get() + mat = actx.to_numpy(mat) waa = bind(self.places, sym.weights_and_area_elements( source_discr.ambient_dim, - dofdesc=expr.source))(self.queue) - mat[:, :] *= waa.get(self.queue) + dofdesc=expr.source))(actx) + mat[:, :] *= actx.to_numpy(flatten(waa)) mat = mat.dot(rec_density) return mat @@ -401,9 +413,9 @@ def map_int_g(self, expr): # {{{ p2p matrix builder class P2PMatrixBuilder(MatrixBuilderBase): - def __init__(self, queue, dep_expr, other_dep_exprs, + def __init__(self, actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context, exclude_self=True): - super(P2PMatrixBuilder, self).__init__(queue, + super(P2PMatrixBuilder, self).__init__(actx, dep_expr, other_dep_exprs, dep_source, dep_discr, places, context) @@ -430,25 +442,26 @@ def map_int_g(self, expr): kernel_args = kernel.get_args() + kernel.get_source_args() kernel_args = set(arg.loopy_arg.name for arg in kernel_args) + actx = self.array_context kernel_args = _get_layer_potential_args(self, expr, include_args=kernel_args) if self.exclude_self: - kernel_args["target_to_source"] = \ - cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) + kernel_args["target_to_source"] = actx.from_numpy( + np.arange(0, target_discr.ndofs, dtype=np.int) + ) from sumpy.p2p import P2PMatrixGenerator - mat_gen = P2PMatrixGenerator( - self.queue.context, (kernel,), exclude_self=self.exclude_self) + mat_gen = P2PMatrixGenerator(actx.context, (kernel,), + exclude_self=self.exclude_self) - _, (mat,) = mat_gen(self.queue, - targets=target_discr.nodes(), - sources=source_discr.nodes(), + from meshmode.dof_array import flatten, thaw + _, (mat,) = mat_gen(actx.queue, + targets=flatten(thaw(actx, target_discr.nodes())), + sources=flatten(thaw(actx, source_discr.nodes())), **kernel_args) - mat = mat.get() - mat = mat.dot(rec_density) + return actx.to_numpy(mat).dot(rec_density) - return mat # }}} @@ -462,8 +475,9 @@ def __init__(self, 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) + queue = self.array_context.queue + tgtindices = self.index_set.linear_row_indices.get(queue) + srcindices = self.index_set.linear_col_indices.get(queue) return np.equal(tgtindices, srcindices).astype(np.float64) @@ -484,6 +498,7 @@ def map_int_g(self, expr): if not np.isscalar(rec_density): raise NotImplementedError + actx = self.array_context kernel = expr.kernel kernel_args = _get_layer_potential_args(self._mat_mapper, expr) @@ -491,34 +506,34 @@ def map_int_g(self, expr): local_expn = LineTaylorLocalExpansion(kernel, lpot_source.qbx_order) from sumpy.qbx import LayerPotentialMatrixBlockGenerator - mat_gen = LayerPotentialMatrixBlockGenerator( - self.queue.context, (local_expn,)) + mat_gen = LayerPotentialMatrixBlockGenerator(actx.context, (local_expn,)) assert abs(expr.qbx_forced_limit) > 0 from pytential import bind, sym radii = bind(self.places, sym.expansion_radii( source_discr.ambient_dim, - dofdesc=expr.target))(self.queue) + dofdesc=expr.target))(actx) centers = bind(self.places, sym.expansion_centers( source_discr.ambient_dim, expr.qbx_forced_limit, - dofdesc=expr.target))(self.queue) - - _, (mat,) = mat_gen(self.queue, - targets=target_discr.nodes(), - sources=source_discr.nodes(), - centers=centers, - expansion_radii=radii, + dofdesc=expr.target))(actx) + + from meshmode.dof_array import flatten, thaw + _, (mat,) = mat_gen(actx.queue, + targets=flatten(thaw(actx, target_discr.nodes())), + sources=flatten(thaw(actx, source_discr.nodes())), + centers=flatten(centers), + expansion_radii=flatten(radii), index_set=self.index_set, **kernel_args) waa = bind(self.places, sym.weights_and_area_elements( source_discr.ambient_dim, - dofdesc=expr.source))(self.queue) - mat *= waa[self.index_set.linear_col_indices] - mat = rec_density * mat.get(self.queue) + dofdesc=expr.source))(actx) + waa = flatten(waa) - return mat + mat *= waa[self.index_set.linear_col_indices] + return rec_density * actx.to_numpy(mat) class FarFieldBlockBuilder(MatrixBlockBuilderBase): @@ -530,8 +545,9 @@ def __init__(self, queue, dep_expr, other_dep_exprs, dep_source, dep_discr, self.exclude_self = exclude_self 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) + queue = self.array_context.queue + tgtindices = self.index_set.linear_row_indices.get(queue) + srcindices = self.index_set.linear_col_indices.get(queue) return np.equal(tgtindices, srcindices).astype(np.float64) @@ -558,24 +574,26 @@ def map_int_g(self, expr): kernel_args = kernel.get_args() + kernel.get_source_args() kernel_args = set(arg.loopy_arg.name for arg in kernel_args) + actx = self.array_context kernel_args = _get_layer_potential_args(self._mat_mapper, expr, include_args=kernel_args) if self.exclude_self: - kernel_args["target_to_source"] = \ - cl.array.arange(self.queue, 0, target_discr.nnodes, dtype=np.int) + kernel_args["target_to_source"] = actx.from_numpy( + np.arange(0, target_discr.ndofs, dtype=np.int) + ) from sumpy.p2p import P2PMatrixBlockGenerator - mat_gen = P2PMatrixBlockGenerator( - self.queue.context, (kernel,), exclude_self=self.exclude_self) + mat_gen = P2PMatrixBlockGenerator(actx.context, (kernel,), + exclude_self=self.exclude_self) - _, (mat,) = mat_gen(self.queue, - targets=target_discr.nodes(), - sources=source_discr.nodes(), + from meshmode.dof_array import flatten, thaw + _, (mat,) = mat_gen(actx.queue, + targets=flatten(thaw(actx, target_discr.nodes())), + sources=flatten(thaw(actx, source_discr.nodes())), index_set=self.index_set, **kernel_args) - mat = rec_density * mat.get(self.queue) - return mat + return rec_density * actx.to_numpy(mat) # }}} diff --git a/pytential/symbolic/pde/maxwell/__init__.py b/pytential/symbolic/pde/maxwell/__init__.py index d89d393c4..f7148cfd0 100644 --- a/pytential/symbolic/pde/maxwell/__init__.py +++ b/pytential/symbolic/pde/maxwell/__init__.py @@ -61,7 +61,7 @@ def get_sym_maxwell_point_source(kernel, jxyz, k): # https://en.wikipedia.org/w/index.php?title=Maxwell%27s_equations&oldid=798940325#Alternative_formulations # (Vector calculus/Potentials/Any Gauge) # assumed time dependence exp(-1j*omega*t) - return sym.join_fields( + return sym.flat_obj_array( 1j*k*A, sym.curl(A)) @@ -108,7 +108,7 @@ def get_sym_maxwell_plane_wave(amplitude_vec, v, omega, e = amplitude_vec * sym.exp(1j*np.dot(n*omega, x)) - return sym.join_fields(e, c_inv * sym.cross(n, e)) + return sym.flat_obj_array(e, c_inv * sym.cross(n, e)) # }}} @@ -180,7 +180,7 @@ def scattered_volume_field(self, Jt, rho, qbx_forced_limit=None): E_scat = 1j*self.k*A - sym.grad(3, phi) H_scat = sym.curl(A) - return sym.join_fields(E_scat, H_scat) + return sym.flat_obj_array(E_scat, H_scat) # }}} @@ -248,13 +248,13 @@ def curl_S(dens, k): # sign flip included F4 = -sym.n_dot(mu1*H1-mu0*H0) + 0.5*(mu1+mu0)*u.rho_m # noqa pylint:disable=invalid-unary-operand-type - return sym.join_fields(F1, F2, F3, F4) + return sym.flat_obj_array(F1, F2, F3, F4) def rhs(self, Einc_xyz, Hinc_xyz): mu1 = self.mus[1] eps1 = self.epss[1] - return sym.join_fields( + return sym.flat_obj_array( xyz_to_tangential(sym.n_cross(Hinc_xyz)), sym.n_dot(eps1*Einc_xyz), xyz_to_tangential(sym.n_cross(Einc_xyz)), @@ -280,7 +280,7 @@ def curl_S(dens): E0 = 1j*k*eps*S(Jxyz) + mu*curl_S(Mxyz) - grad(S(u.rho_e)) H0 = -1j*k*mu*S(Mxyz) + eps*curl_S(Jxyz) + grad(S(u.rho_m)) - return sym.join_fields(E0, H0) + return sym.flat_obj_array(E0, H0) # }}} diff --git a/pytential/symbolic/pde/maxwell/generalized_debye.py b/pytential/symbolic/pde/maxwell/generalized_debye.py index 1b6ea8ee9..df7fd9f50 100644 --- a/pytential/symbolic/pde/maxwell/generalized_debye.py +++ b/pytential/symbolic/pde/maxwell/generalized_debye.py @@ -153,8 +153,8 @@ def volume_field_base(self, r, q, j): E = 1j*k*A - grad_phi - curl_S_volume(k, m) H = curl_S_volume(k, j) + 1j*k*Q - grad_psi - from pytools.obj_array import join_fields - return join_fields(E, H) + from pytools.obj_array import flat_obj_array + return flat_obj_array(E, H) def integral_equation(self, *args, **kwargs): nxnxE, ndotH = self.boundary_field(*args) @@ -178,8 +178,8 @@ def integral_equation(self, *args, **kwargs): E_minus_grad_phi = 1j*k*A - curl_S_volume(k, m) from hellskitchen.fmm import DifferenceKernel - from pytools.obj_array import join_fields - return join_fields( + from pytools.obj_array import flat_obj_array + return flat_obj_array( eh_op, # FIXME: These are inefficient. They compute a full volume field, # but only actually use the line part of it. @@ -262,10 +262,10 @@ def inv_rank_one_coeff(u): r_coeff = inv_rank_one_coeff(r_tilde) q_coeff = inv_rank_one_coeff(q_tilde) - from pytools.obj_array import join_fields + from pytools.obj_array import flat_obj_array factors = self.cluster_points() - fix = join_fields( + fix = flat_obj_array( factors[0]*s_ones*r_coeff, factors[1]*Ones()*q_coeff, ) @@ -376,10 +376,10 @@ def inv_rank_one_coeff(u): r_coeff = inv_rank_one_coeff(r_tilde) q_coeff = inv_rank_one_coeff(q_tilde) - from pytools.obj_array import join_fields + from pytools.obj_array import flat_obj_array factors = self.cluster_points() - fix = join_fields( + fix = flat_obj_array( factors[0]*s_ones*(r_coeff), factors[1]*Ones()*(q_coeff), ) diff --git a/pytential/symbolic/pde/maxwell/waveguide.py b/pytential/symbolic/pde/maxwell/waveguide.py index 3a1d3a63d..aab6e9426 100644 --- a/pytential/symbolic/pde/maxwell/waveguide.py +++ b/pytential/symbolic/pde/maxwell/waveguide.py @@ -28,16 +28,9 @@ ^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: SecondKindInfZMuellerOperator - -2D Dielectric (old-style) -^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. autoclass:: DielectricSRep2DBoundaryOperator -.. autoclass:: DielectricSDRep2DBoundaryOperator """ import numpy as np -from collections import namedtuple from six.moves import range from pytential import sym @@ -353,632 +346,4 @@ def a23_expr(phi): # }}} - -# {{{ old-style waveguide - -class Dielectric2DBoundaryOperatorBase(L2WeightedPDEOperator): - r""" - Solves the following system of BVPs on :math:`\mathbb{R}^2`, in which - a disjoint family of domains :math:`\Omega_i` is embedded: - - .. math:: - - \triangle E + (k_i^2-\beta^2) E = 0\quad \text{on $\Omega_i$}\\ - \triangle H + (k_i^2-\beta^2) H = 0\quad \text{on $\Omega_i$}\\ - [H] = 0 \text{ on $\partial \Omega_i$},\quad - [E] = 0 \text{ on $\partial \Omega_i$}\\ - \left[ \frac{k_0}{k^2-\beta^2} \partial_{\hat n}H\right] = 0 - \quad\text{on $\partial \Omega_i$},\quad\\ - \left[ \frac{k_0}{k^2-\beta^2} \partial_{\hat n}E\right] = 0 - \quad\text{on $\partial \Omega_i$} - - :math:`E` and :math:`H` are assumed to be of the form - - .. math:: - - E(x,y,z,t)=E(x,y)e^{i(\beta z-\omega t) - H(x,y,z,t)=H(x,y)e^{i(\beta z-\omega t) - - where :math:`[\cdot]` denotes the jump across an interface, and :math:`k` - (without an index) denotes the value of :math:`k` on either side of the - interface, for the purpose of computing the jump. :math:`\hat n` denotes - the unit normal of the interface. - - .. automethod:: make_unknown - .. automethod:: representation_outer - .. automethod:: representation_inner - .. automethod:: operator - """ - - field_kind_e = 0 - field_kind_h = 1 - field_kinds = [field_kind_e, field_kind_h] - - side_in = 0 - side_out = 1 - sides = [side_in, side_out] - side_to_sign = { - side_in: -1, - side_out: 1, - } - - dir_none = 0 - dir_normal = 1 - dir_tangential = 2 - - BCTermDescriptor = namedtuple("BCDescriptor", - "i_interface direction field_kind coeff_inner coeff_outer".split()) - - # {{{ constructor - - def __init__(self, mode, k_vacuum, domain_k_exprs, beta, - interfaces, use_l2_weighting=None): - """ - :attr mode: one of 'te', 'tm', 'tem' - :attr k_vacuum: A symbolic expression for the wave number in vacuum. - May be a string, which will be interpreted as a variable name. - :attr interfaces: a tuple of tuples - ``(outer_domain, inner_domain, interface_id)``, - where *outer_domain* and *inner_domain* are indices into - *domain_k_names*, - and *interface_id* is a symbolic name for the discretization of the - interface. 'outer' designates the side of the interface to which - the normal points. - :attr domain_k_exprs: a tuple of variable names of the Helmholtz - parameter *k*, to be used inside each part of the source geometry. - May also be a tuple of strings, which will be transformed into - variable references of the corresponding names. - :attr beta: A symbolic expression for the wave number in the :math:`z` - direction. May be a string, which will be interpreted as a variable - name. - """ - - if use_l2_weighting is None: - use_l2_weighting = False - - from sumpy.kernel import HelmholtzKernel - self.kernel = HelmholtzKernel(2, allow_evanescent=True) - - super(Dielectric2DBoundaryOperatorBase, self).__init__( - self.kernel, - use_l2_weighting=use_l2_weighting) - - if mode == "te": - self.ez_enabled = False - self.hz_enabled = True - elif mode == "tm": - self.ez_enabled = True - self.hz_enabled = False - elif mode == "tem": - self.ez_enabled = True - self.hz_enabled = True - else: - raise ValueError("invalid mode '%s'" % mode) - - self.interfaces = interfaces - - fk_e = self.field_kind_e - fk_h = self.field_kind_h - - dir_none = self.dir_none - dir_normal = self.dir_normal - dir_tangential = self.dir_tangential - - if isinstance(beta, str): - beta = sym.var(beta) - beta = sym.cse(beta, "beta") - - if isinstance(k_vacuum, str): - k_vacuum = sym.var(k_vacuum) - k_vacuum = sym.cse(k_vacuum, "k_vac") - - self.domain_k_exprs = [ - sym.var(k_expr) - if isinstance(k_expr, str) - else sym.cse(k_expr, "k%d" % idom) - for idom, k_expr in enumerate(domain_k_exprs)] - del domain_k_exprs - - # Note the case of k/K! - # "K" is the 2D Helmholtz parameter. - # "k" is the 3D Helmholtz parameter. - - self.domain_K_exprs = [ - sym.cse((k_expr**2-beta**2)**0.5, "K%d" % i) - for i, k_expr in enumerate(self.domain_k_exprs)] - - # {{{ build bc list - - # list of tuples, where each tuple consists of BCTermDescriptor instances - - all_bcs = [] - for i_interface, (outer_domain, inner_domain, _) in ( - enumerate(self.interfaces)): - k_outer = self.domain_k_exprs[outer_domain] - k_inner = self.domain_k_exprs[inner_domain] - - all_bcs += [ - ( # [E] = 0 - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_none, - field_kind=fk_e, - coeff_outer=1, - coeff_inner=-1), - ), - ( # [H] = 0 - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_none, - field_kind=fk_h, - coeff_outer=1, - coeff_inner=-1), - ), - ( - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_tangential, - field_kind=fk_e, - coeff_outer=beta/(k_outer**2-beta**2), - coeff_inner=-beta/(k_inner**2-beta**2)), - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_normal, - field_kind=fk_h, - coeff_outer=sym.cse(-k_vacuum/(k_outer**2-beta**2)), - coeff_inner=sym.cse(k_vacuum/(k_inner**2-beta**2))), - ), - ( - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_tangential, - field_kind=fk_h, - coeff_outer=beta/(k_outer**2-beta**2), - coeff_inner=-beta/(k_inner**2-beta**2)), - self.BCTermDescriptor( - i_interface=i_interface, - direction=dir_normal, - field_kind=fk_e, - coeff_outer=sym.cse( - (k_outer**2/k_vacuum)/(k_outer**2-beta**2)), - coeff_inner=sym.cse( - -(k_inner**2/k_vacuum) - / (k_inner**2-beta**2))) - ), - ] - - del k_outer - del k_inner - - self.bcs = [] - for bc in all_bcs: - any_significant_e = any( - term.field_kind == fk_e - and term.direction in [dir_normal, dir_none] - for term in bc) - any_significant_h = any( - term.field_kind == fk_h - and term.direction in [dir_normal, dir_none] - for term in bc) - is_necessary = ( - (self.ez_enabled and any_significant_e) - or (self.hz_enabled and any_significant_h)) - - # Only keep tangential modes for TEM. Otherwise, - # no jump in H already implies jump condition on - # tangential derivative. - is_tem = self.ez_enabled and self.hz_enabled - terms = tuple( - term - for term in bc - if term.direction != dir_tangential - or is_tem) - - if is_necessary: - self.bcs.append(terms) - - assert (len(all_bcs) - * (int(self.ez_enabled) + int(self.hz_enabled)) // 2 - == len(self.bcs)) - - # }}} - - # }}} - - def 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)) - - def make_unknown(self, name): - num_densities = ( - 2 - * (int(self.ez_enabled) + int(self.hz_enabled)) - * len(self.interfaces)) - - assert num_densities == len(self.bcs) - - return sym.make_sym_vector(name, num_densities) - - def bc_term_to_operator_contrib(self, term, side, raw_potential_op, - density, discrete): - potential_op = raw_potential_op - - side_sign = self.side_to_sign[side] - - domain_outer, domain_inner, interface_id = \ - self.interfaces[term.i_interface] - if side == self.side_in: - K_expr = self.domain_K_exprs[domain_inner] # noqa - bc_coeff = term.coeff_inner - elif side == self.side_out: - K_expr = self.domain_K_exprs[domain_outer] # noqa - bc_coeff = term.coeff_outer - else: - raise ValueError("invalid value of 'side'") - - potential_op = potential_op( - self.kernel, density, source=interface_id, - k=K_expr) - - if term.direction == self.dir_none: - if raw_potential_op is sym.S: - jump_term = 0 - elif raw_potential_op is sym.D: - jump_term = (side_sign*0.5) * discrete - else: - assert False, raw_potential_op - elif term.direction == self.dir_normal: - potential_op = sym.normal_derivative( - 2, potential_op, dofdesc=interface_id) - - if raw_potential_op is sym.S: - # S' - jump_term = (-side_sign*0.5) * discrete - elif raw_potential_op is sym.D: - jump_term = 0 - else: - assert False, raw_potential_op - - elif term.direction == self.dir_tangential: - potential_op = sym.tangential_derivative( - raw_potential_op( - self.kernel, density, source=interface_id, - k=K_expr, qbx_forced_limit=side_sign), - interface_id).a.as_scalar() - - # Some of these may have jumps, but QBX does the dirty - # work here by directly computing the limit. - jump_term = 0 - - else: - raise ValueError("invalid direction") - - potential_op = ( - jump_term - + self.get_sqrt_weight(interface_id)*potential_op) - - del jump_term - - contrib = bc_coeff * potential_op - - if (raw_potential_op is sym.D - and term.direction == self.dir_normal): - # FIXME The hypersingular part should perhaps be - # treated specially to avoid cancellation. - pass - - return contrib - - -# {{{ single-layer representation - -class DielectricSRep2DBoundaryOperator(Dielectric2DBoundaryOperatorBase): - def _structured_unknown(self, unknown, with_l2_weights): - """ - :arg with_l2_weights: If True, return the 'bare' unknowns - that do not have the :math:`L^2` weights divided out. - Note: Those unknowns should *not* be interpreted as - point values of a density. - :returns: an array of unknowns, with the following index axes: - ``[side, field_kind, i_interface]``, where - ``side`` is o for the outside part and i for the interior part, - ``field_kind`` is 0 for the E-field and 1 for the H-field part, - ``i_interface`` is the number of the enclosed domain, starting from 0. - """ - result = np.zeros((2, 2, len(self.interfaces)), dtype=np.object) - sides = { - self.side_out: "o", - self.side_in: "i" - } - fields = { - self.field_kind_e: "E", - self.field_kind_h: "H" - } - - i_unknown = 0 - for side in self.sides: - for field_kind in self.field_kinds: - for i_interface in range(len(self.interfaces)): - - if self.is_field_present(field_kind): - dens = unknown[i_unknown] - i_unknown += 1 - else: - dens = 0 - - _, _, interface_id = self.interfaces[i_interface] - - if not with_l2_weights: - dens = sym.cse( - dens/self.get_sqrt_weight(interface_id), - "dens_{side}_{field}_{dom}".format( - side=sides[side], - field=fields[field_kind], - dom=i_interface)) - - result[side, field_kind, i_interface] = dens - - assert i_unknown == len(unknown) - return result - - def representation(self, unknown, i_domain, qbx_forced_limit=None): - """ - :return: a symbolic expression for the representation of the PDE solution - in domain number *i_domain*. - """ - unk = self._structured_unknown(unknown, with_l2_weights=False) - - result = [] - - for field_kind in self.field_kinds: - if not self.is_field_present(field_kind): - continue - - field_result = 0 - for i_interface, (i_domain_outer, i_domain_inner, interface_id) in ( - enumerate(self.interfaces)): - if i_domain_outer == i_domain: - side = self.side_out - elif i_domain_inner == i_domain: - side = self.side_in - else: - continue - - my_unk = unk[side, field_kind, i_interface] - if my_unk: - field_result += sym.S( - self.kernel, - my_unk, - source=interface_id, - k=self.domain_K_exprs[i_domain], - qbx_forced_limit=qbx_forced_limit) - - result.append(field_result) - - from pytools.obj_array import make_obj_array - return make_obj_array(result) - - def operator(self, unknown): - density_unk = self._structured_unknown(unknown, with_l2_weights=False) - discrete_unk = self._structured_unknown(unknown, with_l2_weights=True) - - result = [] - for bc in self.bcs: - op = 0 - - for side in self.sides: - for term in bc: - unk_index = (side, term.field_kind, term.i_interface) - density = density_unk[unk_index] - discrete = discrete_unk[unk_index] - - op += self.bc_term_to_operator_contrib( - term, side, sym.S, density, discrete) - - result.append(op) - - return np.array(result, dtype=np.object) - -# }}} - - -# {{{ single + double layer representation - -class DielectricSDRep2DBoundaryOperator(Dielectric2DBoundaryOperatorBase): - pot_kind_S = 0 # noqa: N815 - pot_kind_D = 1 # noqa: N815 - pot_kinds = [pot_kind_S, pot_kind_D] - potential_ops = { - pot_kind_S: sym.S, - pot_kind_D: sym.D, - } - - def __init__(self, mode, k_vacuum, domain_k_exprs, beta, - interfaces, use_l2_weighting=None): - - super(DielectricSDRep2DBoundaryOperator, self).__init__( - mode, k_vacuum, domain_k_exprs, beta, - interfaces, use_l2_weighting=use_l2_weighting) - - side_in = self.side_in - side_out = self.side_out - - def find_normal_derivative_bc_coeff(field_kind, i_interface, side): - result = 0 - for bc in self.bcs: - for term in bc: - if term.field_kind != field_kind: - continue - if term.i_interface != i_interface: - continue - if term.direction != self.dir_normal: - continue - - if side == side_in: - result += term.coeff_inner - elif side == side_out: - result += term.coeff_outer - else: - raise ValueError("invalid side") - - return result - - self.density_coeffs = np.zeros( - (len(self.pot_kinds), len(self.field_kinds), - len(self.interfaces), len(self.sides)), - dtype=np.object) - for field_kind in self.field_kinds: - for i_interface in range(len(self.interfaces)): - self.density_coeffs[ - self.pot_kind_S, field_kind, i_interface, side_in] = 1 - self.density_coeffs[ - self.pot_kind_S, field_kind, i_interface, side_out] = 1 - - # These need to satisfy - # - # [dens_coeff_D * bc_coeff * dn D] - # = dens_coeff_D_out * bc_coeff_out * (dn D) - # + dens_coeff_D_in * bc_coeff_in * dn D - # = 0 - # - # (because dn D is hypersingular, which we'd like to cancel out) - # - # NB: bc_coeff_{in,out} already contain the signs to realize - # the subtraction for the jump. (So the "+" above is as it - # should be.) - - dens_coeff_D_in = find_normal_derivative_bc_coeff( # noqa - field_kind, i_interface, side_out) - dens_coeff_D_out = - find_normal_derivative_bc_coeff( # noqa - field_kind, i_interface, side_in) - - self.density_coeffs[ - self.pot_kind_D, field_kind, i_interface, side_in] \ - = dens_coeff_D_in - self.density_coeffs[ - self.pot_kind_D, field_kind, i_interface, side_out] \ - = dens_coeff_D_out - - def _structured_unknown(self, unknown, with_l2_weights): - """ - :arg with_l2_weights: If True, return the 'bare' unknowns - that do not have the :math:`L^2` weights divided out. - Note: Those unknowns should *not* be interpreted as - point values of a density. - :returns: an array of unknowns, with the following index axes: - ``[pot_kind, field_kind, i_interface]``, where - ``pot_kind`` is 0 for the single-layer part and 1 for the double-layer - part, - ``field_kind`` is 0 for the E-field and 1 for the H-field part, - ``i_interface`` is the number of the enclosed domain, starting from 0. - """ - result = np.zeros((2, 2, len(self.interfaces)), dtype=np.object) - - i_unknown = 0 - for pot_kind in self.pot_kinds: - for field_kind in self.field_kinds: - for i_interface in range(len(self.interfaces)): - - if self.is_field_present(field_kind): - dens = unknown[i_unknown] - i_unknown += 1 - else: - dens = 0 - - _, _, interface_id = self.interfaces[i_interface] - - if not with_l2_weights: - dens = sym.cse( - dens/self.get_sqrt_weight(interface_id), - "dens_{pot}_{field}_{intf}".format( - pot={0: "S", 1: "D"}[pot_kind], - field={ - self.field_kind_e: "E", - self.field_kind_h: "H" - } - [field_kind], - intf=i_interface)) - - result[pot_kind, field_kind, i_interface] = dens - - assert i_unknown == len(unknown) - return result - - def representation(self, unknown, i_domain): - """ - :return: a symbolic expression for the representation of the PDE solution - in domain number *i_domain*. - """ - unk = self._structured_unknown(unknown, with_l2_weights=False) - - result = [] - - for field_kind in self.field_kinds: - if not self.is_field_present(field_kind): - continue - - field_result = 0 - for pot_kind in self.pot_kinds: - for i_interface, (i_domain_outer, i_domain_inner, interface_id) in ( - enumerate(self.interfaces)): - if i_domain_outer == i_domain: - side = self.side_out - elif i_domain_inner == i_domain: - side = self.side_in - else: - continue - - my_unk = unk[pot_kind, field_kind, i_interface] - if my_unk: - field_result += ( - self.density_coeffs[ - pot_kind, field_kind, i_interface, side] - * self.potential_ops[pot_kind]( - self.kernel, - my_unk, - source=interface_id, - k=self.domain_K_exprs[i_domain] - )) - - result.append(field_result) - - from pytools.obj_array import make_obj_array - return make_obj_array(result) - - def operator(self, unknown): - density_unk = self._structured_unknown(unknown, with_l2_weights=False) - discrete_unk = self._structured_unknown(unknown, with_l2_weights=True) - - result = [] - for bc in self.bcs: - op = 0 - - for pot_kind in self.pot_kinds: - for term in bc: - - for side in self.sides: - raw_potential_op = \ - self.potential_ops[pot_kind] - - unk_index = (pot_kind, term.field_kind, term.i_interface) - density = density_unk[unk_index] - discrete = discrete_unk[unk_index] - - op += ( - self.density_coeffs[ - pot_kind, term.field_kind, term.i_interface, - side] - * self.bc_term_to_operator_contrib( - term, side, raw_potential_op, density, discrete) - ) - - result.append(op) - - return np.array(result, dtype=np.object) - -# }}} - -# }}} - # vim: foldmethod=marker diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 1799e09b4..65c18bbe8 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -36,7 +36,7 @@ from pymbolic.geometric_algebra.primitives import ( # noqa: F401 NablaComponent, DerivativeSource, Derivative as DerivativeBase) from pymbolic.primitives import make_sym_vector # noqa: F401 -from pytools.obj_array import make_obj_array, join_fields # noqa: F401 +from pytools.obj_array import make_obj_array, flat_obj_array # noqa: F401 from functools import partial @@ -53,28 +53,33 @@ Based on the mathematical quantity being represented, the following types of objects occur as part of a symbolic operator representation: -* If a quantity is a scalar, it is just a symbolic expression--that is, a nested - combination of placeholders (see below), arithmetic on them (see - :mod:`pymbolic.primitives`. These objects are created simply by doing - arithmetic on placeholders. +* If a quantity is a scalar, it is just a symbolic expression--that is, an + element of the set of formal expressions recursively generated by the + placeholders (see :ref:`placeholders`), constants, and arithmetic operations + on them (see :mod:`pymbolic.primitives`). Objects of this type are created + simply by doing arithmetic on placeholders and scalar constants. * If the quantity is "just a bunch of scalars" (like, say, rows in a system - of integral equations), the symbolic representation an object array. Each + of integral equations), the symbolic representation is an object array. Each element of the object array contains a symbolic expression. :func:`pytools.obj_array.make_obj_array` and - :func:`pytools.obj_array.join_fields` + :func:`pytools.obj_array.flat_obj_array` can help create those. * If it is a geometric quantity (that makes sense without explicit reference to coordinates), it is a :class:`pymbolic.geometric_algebra.MultiVector`. - This can be converted to an object array by calling : + This can be converted to an object array by calling: :meth:`pymbolic.geometric_algebra.MultiVector.as_vector`. -:mod:`pyopencl.array.Array` instances do not occur on the symbolic of -:mod:`pymbolic` at all. Those hold per-node degrees of freedom (and only -those), which is not visible as an array axis in symbolic code. (They're -visible only once evaluated.) +:class:`pyopencl.array.Array` and :class:`meshmode.dof_array.DOFArray` instances +hold per-node degrees of freedom (and only those). Such instances do *not* occur +on the symbolic side of :mod:`pytential` at all. They're only visible either as +bound inputs (see :func:`pytential.bind`) or outputs of evaluation. Which one is +used depends on the meaning of the data being represented. If the data is +associated with a :class:`~meshmode.discretization.Discretization`, then +:class:`~meshmode.dof_array.DOFArray` is used and otherwise +:class:`~pyopencl.array.Array` is used. DOF Description ^^^^^^^^^^^^^^^ @@ -93,6 +98,8 @@ .. autoclass:: DOFDescriptor .. autofunction:: as_dofdesc +.. _placeholders: + Placeholders ^^^^^^^^^^^^ @@ -472,12 +479,12 @@ def __call__(self, operand, *args, **kwargs): # return an object array of the operator applied to each of the # operands. - from pytools.obj_array import is_obj_array, with_object_array_or_scalar - if is_obj_array(operand): + from pytools.obj_array import obj_array_vectorize + if isinstance(operand, np.ndarray) and operand.dtype.char == "O": def make_op(operand_i): return self(operand_i, *args, **kwargs) - return with_object_array_or_scalar(make_op, operand) + return obj_array_vectorize(make_op, operand) else: return var.__call__(self, operand, *args, **kwargs) @@ -759,7 +766,7 @@ def first_fundamental_form(ambient_dim, dim=None, dofdesc=None): if dim is None: dim = ambient_dim - 1 - if ambient_dim != 3 and dim != 2: + if not (ambient_dim == 3 and dim == 2): raise NotImplementedError("only available for surfaces in 3D") pd_mat = parametrization_derivative_matrix(ambient_dim, dim, dofdesc) @@ -782,7 +789,7 @@ def second_fundamental_form(ambient_dim, dim=None, dofdesc=None): if dim is None: dim = ambient_dim - 1 - if ambient_dim != 3 and dim != 2: + if not (ambient_dim == 3 and dim == 2): raise NotImplementedError("only available for surfaces in 3D") r = nodes(ambient_dim, dofdesc=dofdesc).as_vector() @@ -814,7 +821,7 @@ def shape_operator(ambient_dim, dim=None, dofdesc=None): if dim is None: dim = ambient_dim - 1 - if ambient_dim != 3 and dim != 2: + if not (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 @@ -1333,12 +1340,12 @@ 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): + from pytools.obj_array import obj_array_vectorize + if isinstance(operand, np.ndarray) and operand.dtype.char == "O": def dd_axis_comp(operand_i): return dd_axis(axis, ambient_dim, operand_i) - return with_object_array_or_scalar(dd_axis_comp, operand) + return obj_array_vectorize(dd_axis_comp, operand) d = Derivative() diff --git a/pytential/target.py b/pytential/target.py index e677bdb7a..98f876b51 100644 --- a/pytential/target.py +++ b/pytential/target.py @@ -41,8 +41,8 @@ class TargetBase(object): .. attribute:: ambient_dim .. method:: nodes - Shape: ``[ambient_dim, nnodes]`` - .. attribute:: nnodes + Shape: ``[ambient_dim, ndofs]`` + .. attribute:: ndofs """ @@ -63,13 +63,13 @@ def preprocess_optemplate(self, name, discretizations, expr): return expr def nodes(self): - """Shape: ``[ambient_dim, nnodes]`` + """Shape: ``[ambient_dim, ndofs]`` """ return self._nodes @property - def nnodes(self): + def ndofs(self): for coord_ary in self._nodes: return coord_ary.shape[0] diff --git a/pytential/unregularized.py b/pytential/unregularized.py index 6f0125cd5..b7cae228a 100644 --- a/pytential/unregularized.py +++ b/pytential/unregularized.py @@ -27,6 +27,7 @@ import six +from meshmode.array_context import PyOpenCLArrayContext import numpy as np import loopy as lp @@ -99,8 +100,8 @@ def copy( density_discr=density_discr or self.density_discr, debug=debug if debug is not None else self.debug) - def exec_compute_potential_insn(self, queue, insn, bound_expr, evaluate, - return_timing_data): + def exec_compute_potential_insn(self, actx: PyOpenCLArrayContext, + insn, bound_expr, evaluate, return_timing_data): if return_timing_data: from warnings import warn from pytential.source import UnableToCollectTimingData @@ -108,18 +109,18 @@ def exec_compute_potential_insn(self, queue, insn, bound_expr, evaluate, "Timing data collection not supported.", category=UnableToCollectTimingData) - from pytools.obj_array import with_object_array_or_scalar + from pytools.obj_array import obj_array_vectorize def evaluate_wrapper(expr): value = evaluate(expr) - return with_object_array_or_scalar(lambda x: x, value) + return obj_array_vectorize(lambda x: x, value) if self.fmm_level_to_order is False: func = self.exec_compute_potential_insn_direct else: func = self.exec_compute_potential_insn_fmm - return func(queue, insn, bound_expr, evaluate_wrapper) + return func(actx, insn, bound_expr, evaluate_wrapper) def op_group_features(self, expr): from sumpy.kernel import AxisTargetDerivativeRemover @@ -138,18 +139,23 @@ def preprocess_optemplate(self, name, discretizations, expr): from pytential.symbolic.mappers import UnregularizedPreprocessor return UnregularizedPreprocessor(name, discretizations)(expr) - def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate): + def exec_compute_potential_insn_direct(self, actx: PyOpenCLArrayContext, + insn, bound_expr, evaluate): kernel_args = {} + from pytential.utils import flatten_if_needed + from meshmode.dof_array import flatten, thaw, unflatten + for arg_name, arg_expr in six.iteritems(insn.kernel_arguments): - kernel_args[arg_name] = evaluate(arg_expr) + kernel_args[arg_name] = flatten_if_needed(actx, evaluate(arg_expr)) from pytential import bind, sym waa = bind(bound_expr.places, sym.weights_and_area_elements( - self.ambient_dim, dofdesc=insn.source))(queue) - strengths = waa * evaluate(insn.density).with_queue(queue) + self.ambient_dim, dofdesc=insn.source))(actx) + strengths = waa * evaluate(insn.density) + flat_strengths = flatten(strengths) - result = [] + results = [] p2p = None for o in insn.outputs: @@ -157,17 +163,22 @@ def exec_compute_potential_insn_direct(self, queue, insn, bound_expr, evaluate): o.target_name.geometry, o.target_name.discr_stage) if p2p is None: - p2p = self.get_p2p(insn.kernels) + p2p = self.get_p2p(actx, insn.kernels) + + evt, output_for_each_kernel = p2p(actx.queue, + flatten_if_needed(actx, target_discr.nodes()), + flatten(thaw(actx, self.density_discr.nodes())), + [flat_strengths], **kernel_args) - evt, output_for_each_kernel = p2p(queue, - target_discr.nodes(), - self.density_discr.nodes(), - [strengths], **kernel_args) + from meshmode.discretization import Discretization + result = output_for_each_kernel[o.kernel_index] + if isinstance(target_discr, Discretization): + result = unflatten(actx, target_discr, result) - result.append((o.name, output_for_each_kernel[o.kernel_index])) + results.append((o.name, result)) timing_data = {} - return result, timing_data + return results, timing_data # {{{ fmm-based execution @@ -190,10 +201,9 @@ def expansion_wrangler_code_container(self, fmm_kernel, out_kernels): out_kernels) @property - @memoize_method def fmm_geometry_code_container(self): - return _FMMGeometryCodeContainer( - self.cl_context, self.ambient_dim, self.debug) + return _FMMGeometryDataCodeContainer( + self._setup_actx, self.ambient_dim, self.debug) def fmm_geometry_data(self, targets): return _FMMGeometryData( @@ -202,7 +212,8 @@ def fmm_geometry_data(self, targets): targets, self.debug) - def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): + def exec_compute_potential_insn_fmm(self, actx: PyOpenCLArrayContext, + insn, bound_expr, evaluate): # {{{ gather unique target discretizations used target_name_to_index = {} @@ -227,8 +238,11 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): from pytential import bind, sym waa = bind(bound_expr.places, sym.weights_and_area_elements( - self.ambient_dim, dofdesc=insn.source))(queue) - strengths = waa * evaluate(insn.density).with_queue(queue) + self.ambient_dim, dofdesc=insn.source))(actx) + strengths = waa * evaluate(insn.density) + + from meshmode.dof_array import flatten + flat_strengths = flatten(strengths) out_kernels = tuple(knl for knl in insn.kernels) fmm_kernel = self.get_fmm_kernel(out_kernels) @@ -236,12 +250,12 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): self.get_fmm_output_and_expansion_dtype(fmm_kernel, strengths)) kernel_extra_kwargs, source_extra_kwargs = ( self.get_fmm_expansion_wrangler_extra_kwargs( - queue, out_kernels, geo_data.tree().user_source_ids, + actx, out_kernels, geo_data.tree().user_source_ids, insn.kernel_arguments, evaluate)) wrangler = self.expansion_wrangler_code_container( fmm_kernel, out_kernels).get_wrangler( - queue, + actx.queue, geo_data.tree(), output_and_expansion_dtype, self.fmm_level_to_order, @@ -252,25 +266,32 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): from boxtree.fmm import drive_fmm all_potentials_on_every_tgt = drive_fmm( - geo_data.traversal(), wrangler, strengths, timing_data=None) + geo_data.traversal(), wrangler, flat_strengths, + timing_data=None) # {{{ postprocess fmm - result = [] + results = [] for o in insn.outputs: target_index = target_name_to_index[o.target_name] target_slice = slice(*geo_data.target_info().target_discr_starts[ target_index:target_index+2]) + target_discr = targets[target_index] + + result = all_potentials_on_every_tgt[o.kernel_index][target_slice] - result.append( - (o.name, - all_potentials_on_every_tgt[o.kernel_index][target_slice])) + from meshmode.discretization import Discretization + if isinstance(target_discr, Discretization): + from meshmode.dof_array import unflatten + result = unflatten(actx, target_discr, result) + + results.append((o.name, result)) # }}} timing_data = {} - return result, timing_data + return results, timing_data # }}} @@ -279,13 +300,17 @@ def exec_compute_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): # {{{ fmm tools -class _FMMGeometryCodeContainer(object): +class _FMMGeometryDataCodeContainer(object): - def __init__(self, cl_context, ambient_dim, debug): - self.cl_context = cl_context + def __init__(self, actx, ambient_dim, debug): + self.array_context = actx self.ambient_dim = ambient_dim self.debug = debug + @property + def cl_context(self): + return self.array_context.context + @memoize_method def copy_targets_kernel(self): knl = lp.make_kernel( @@ -343,11 +368,15 @@ def __init__(self, lpot_source, code_getter, target_discrs, debug=True): @property def cl_context(self): - return self.lpot_source.cl_context + return self.code_getter.cl_context + + @property + def array_context(self): + return self.code_getter.array_context @property def coord_dtype(self): - return self.lpot_source.density_discr.nodes().dtype + return self.lpot_source.density_discr.real_dtype @property def ambient_dim(self): @@ -373,25 +402,29 @@ def tree(self): lpot_src = self.lpot_source target_info = self.target_info() - with cl.CommandQueue(self.cl_context) as queue: - nsources = lpot_src.density_discr.nnodes - nparticles = nsources + target_info.ntargets + queue = self.array_context.queue + + nsources = lpot_src.density_discr.ndofs + nparticles = nsources + target_info.ntargets - refine_weights = cl.array.zeros(queue, nparticles, dtype=np.int32) - refine_weights[:nsources] = 1 - refine_weights.finish() + refine_weights = cl.array.zeros(queue, nparticles, dtype=np.int32) + refine_weights[:nsources] = 1 + refine_weights.finish() - MAX_LEAF_REFINE_WEIGHT = 32 # noqa + MAX_LEAF_REFINE_WEIGHT = 32 # noqa - tree, _ = code_getter.build_tree(queue, - particles=lpot_src.density_discr.nodes(), - targets=target_info.targets, - max_leaf_refine_weight=MAX_LEAF_REFINE_WEIGHT, - refine_weights=refine_weights, - debug=self.debug, - kind="adaptive") + from meshmode.dof_array import thaw, flatten - return tree + tree, _ = code_getter.build_tree(queue, + particles=flatten( + thaw(self.array_context, lpot_src.density_discr.nodes())), + targets=target_info.targets, + max_leaf_refine_weight=MAX_LEAF_REFINE_WEIGHT, + refine_weights=refine_weights, + debug=self.debug, + kind="adaptive") + + return tree @memoize_method def target_info(self): @@ -399,31 +432,31 @@ def target_info(self): lpot_src = self.lpot_source target_discrs = self.target_discrs - with cl.CommandQueue(self.cl_context) as queue: - ntargets = 0 - target_discr_starts = [] - - for target_discr in target_discrs: - target_discr_starts.append(ntargets) - ntargets += target_discr.nnodes + ntargets = 0 + target_discr_starts = [] + for target_discr in target_discrs: target_discr_starts.append(ntargets) - - targets = cl.array.empty( - self.cl_context, - (lpot_src.ambient_dim, ntargets), - self.coord_dtype) - - for start, target_discr in zip(target_discr_starts, target_discrs): - code_getter.copy_targets_kernel()( - queue, - targets=targets[:, start:start+target_discr.nnodes], - points=target_discr.nodes()) - - return _TargetInfo( - targets=targets, - target_discr_starts=target_discr_starts, - ntargets=ntargets).with_queue(None) + ntargets += target_discr.ndofs + + target_discr_starts.append(ntargets) + + targets = self.array_context.empty( + (lpot_src.ambient_dim, ntargets), + self.coord_dtype) + + from pytential.utils import flatten_if_needed + for start, target_discr in zip(target_discr_starts, target_discrs): + code_getter.copy_targets_kernel()( + self.array_context.queue, + targets=targets[:, start:start+target_discr.ndofs], + points=flatten_if_needed( + self.array_context, target_discr.nodes())) + + return _TargetInfo( + targets=targets, + target_discr_starts=target_discr_starts, + ntargets=ntargets).with_queue(None) # }}} diff --git a/pytential/utils.py b/pytential/utils.py new file mode 100644 index 000000000..f1e9f0d10 --- /dev/null +++ b/pytential/utils.py @@ -0,0 +1,64 @@ +__copyright__ = """ +Copyright (C) 2020 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 +from meshmode.array_context import PyOpenCLArrayContext + + +def flatten_if_needed(actx: PyOpenCLArrayContext, ary: np.ndarray): + from pytools.obj_array import obj_array_vectorize_n_args + from meshmode.dof_array import DOFArray, thaw, flatten + + if (isinstance(ary, np.ndarray) + and ary.dtype.char == "O" + and not isinstance(ary, DOFArray)): + return obj_array_vectorize_n_args(flatten_if_needed, actx, ary) + + if not isinstance(ary, DOFArray): + return ary + + if ary.array_context is None: + ary = thaw(actx, ary) + + return flatten(ary) + + +def unflatten_from_numpy(actx, discr, ary): + from pytools.obj_array import obj_array_vectorize + from meshmode.dof_array import unflatten + + ary = obj_array_vectorize(actx.from_numpy, ary) + if discr is None: + return ary + else: + return unflatten(actx, discr, ary) + + +def flatten_to_numpy(actx, ary): + result = flatten_if_needed(actx, ary) + + from pytools.obj_array import obj_array_vectorize + return obj_array_vectorize(actx.to_numpy, result) + +# vim: foldmethod=marker diff --git a/pytential/version.py b/pytential/version.py index 0849c083e..aafdf685f 100644 --- a/pytential/version.py +++ b/pytential/version.py @@ -43,7 +43,7 @@ # }}} -VERSION = (2020, 1) +VERSION = (2020, 2) VERSION_TEXT = ".".join(str(i) for i in VERSION) PYTENTIAL_KERNEL_VERSION = (VERSION, _git_rev, 0) diff --git a/requirements.txt b/requirements.txt index 625deb28d..6ce07e8a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,6 @@ 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://github.com/inducer/meshmode +git+https://gitlab.tiker.net/inducer/meshmode@array-context git+https://gitlab.tiker.net/inducer/sumpy git+https://gitlab.tiker.net/inducer/pyfmmlib diff --git a/setup.cfg b/setup.cfg index a353f3f72..444d02d0d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,3 @@ - [flake8] ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503,N803,N806,N802,D102,D103 max-line-length=85 diff --git a/setup.py b/setup.py index 6c4efcf85..8a3bd5f87 100644 --- a/setup.py +++ b/setup.py @@ -120,7 +120,7 @@ def write_git_revision(package_name): "boxtree>=2019.1", "pymbolic>=2013.2", "loo.py>=2017.2", - "sumpy>=2013.1", + "sumpy>=2020.2beta1", "cgen>=2013.1.2", "pyfmmlib>=2019.1.1", diff --git a/test/test_cost_model.py b/test/test_cost_model.py index 2ebac61c3..42e03b2c4 100644 --- a/test/test_cost_model.py +++ b/test/test_cost_model.py @@ -26,6 +26,7 @@ import numpy.linalg as la # noqa from boxtree.tools import ConstantOneExpansionWrangler +from meshmode.array_context import PyOpenCLArrayContext import pyopencl as cl import pyopencl.clmath # noqa import pytest @@ -55,7 +56,7 @@ } -def get_lpot_source(queue, dim): +def get_lpot_source(actx: PyOpenCLArrayContext, dim): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) @@ -72,7 +73,7 @@ def get_lpot_source(queue, dim): raise ValueError("unsupported dimension: %d" % dim) pre_density_discr = Discretization( - queue.context, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) lpot_kwargs = DEFAULT_LPOT_KWARGS.copy() @@ -91,9 +92,10 @@ def get_lpot_source(queue, dim): return lpot_source -def get_density(queue, discr): - nodes = discr.nodes().with_queue(queue) - return cl.clmath.sin(10 * nodes[0]) +def get_density(actx, discr): + from meshmode.dof_array import thaw + nodes = thaw(actx, discr.nodes()) + return actx.np.sin(10 * nodes[0]) # }}} @@ -108,13 +110,14 @@ def test_timing_data_gathering(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) + actx = PyOpenCLArrayContext(queue) - lpot_source = get_lpot_source(queue, 2) + lpot_source = get_lpot_source(actx, 2) places = GeometryCollection(lpot_source) dofdesc = places.auto_source.to_stage1() density_discr = places.get_discretization(dofdesc.geometry) - sigma = get_density(queue, density_discr) + sigma = get_density(actx, density_discr) sigma_sym = sym.var("sigma") k_sym = LaplaceKernel(lpot_source.ambient_dim) @@ -123,7 +126,7 @@ def test_timing_data_gathering(ctx_factory): op_S = bind(places, sym_op_S) timing_data = {} - op_S.eval(queue, dict(sigma=sigma), timing_data=timing_data) + op_S.eval(dict(sigma=sigma), timing_data=timing_data, array_context=actx) assert timing_data print(timing_data) @@ -140,28 +143,29 @@ def test_cost_model(ctx_factory, dim, use_target_specific_qbx): """Test that cost model gathering can execute successfully.""" cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) - lpot_source = get_lpot_source(queue, dim).copy( + lpot_source = get_lpot_source(actx, dim).copy( _use_target_specific_qbx=use_target_specific_qbx, cost_model=CostModel()) places = GeometryCollection(lpot_source) density_discr = places.get_discretization(places.auto_source.geometry) - sigma = get_density(queue, density_discr) + sigma = get_density(actx, density_discr) sigma_sym = sym.var("sigma") k_sym = LaplaceKernel(lpot_source.ambient_dim) sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) op_S = bind(places, sym_op_S) - cost_S = op_S.get_modeled_cost(queue, sigma=sigma) + cost_S = op_S.get_modeled_cost(actx, sigma=sigma) assert len(cost_S) == 1 sym_op_S_plus_D = ( sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) + sym.D(k_sym, sigma_sym, qbx_forced_limit="avg")) op_S_plus_D = bind(places, sym_op_S_plus_D) - cost_S_plus_D = op_S_plus_D.get_modeled_cost(queue, sigma=sigma) + cost_S_plus_D = op_S_plus_D.get_modeled_cost(actx, sigma=sigma) assert len(cost_S_plus_D) == 2 # }}} @@ -173,17 +177,18 @@ def test_cost_model_metadata_gathering(ctx_factory): """Test that the cost model correctly gathers metadata.""" cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder fmm_level_to_order = SimpleExpansionOrderFinder(tol=1e-5) - lpot_source = get_lpot_source(queue, 2).copy( + lpot_source = get_lpot_source(actx, 2).copy( fmm_level_to_order=fmm_level_to_order) places = GeometryCollection(lpot_source) density_discr = places.get_discretization(places.auto_source.geometry) - sigma = get_density(queue, density_discr) + sigma = get_density(actx, density_discr) sigma_sym = sym.var("sigma") k_sym = HelmholtzKernel(2, "k") @@ -192,7 +197,7 @@ def test_cost_model_metadata_gathering(ctx_factory): sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1, k=sym.var("k")) op_S = bind(places, sym_op_S) - cost_S = one(op_S.get_modeled_cost(queue, sigma=sigma, k=k).values()) + cost_S = one(op_S.get_modeled_cost(actx, sigma=sigma, k=k).values()) geo_data = lpot_source.qbx_fmm_geometry_data( places, @@ -437,12 +442,13 @@ def test_cost_model_correctness(ctx_factory, dim, off_surface, """Check that computed cost matches that of a constant-one FMM.""" cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) cost_model = ( CostModel( translation_cost_model_factory=OpCountingTranslationCostModel)) - lpot_source = get_lpot_source(queue, dim).copy( + lpot_source = get_lpot_source(actx, dim).copy( cost_model=cost_model, _use_target_specific_qbx=use_target_specific_qbx) @@ -470,10 +476,10 @@ def test_cost_model_correctness(ctx_factory, dim, off_surface, sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=qbx_forced_limit) op_S = bind(places, sym_op_S) - sigma = get_density(queue, density_discr) + sigma = get_density(actx, density_discr) from pytools import one - cost_S = one(op_S.get_modeled_cost(queue, sigma=sigma).values()) + cost_S = one(op_S.get_modeled_cost(actx, sigma=sigma).values()) # Run FMM with ConstantOneWrangler. This can't be done with pytential's # high-level interface, so call the FMM driver directly. @@ -487,15 +493,15 @@ def test_cost_model_correctness(ctx_factory, dim, off_surface, quad_stage2_density_discr = places.get_discretization( source_dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) - nnodes = quad_stage2_density_discr.nnodes - src_weights = np.ones(nnodes) + ndofs = quad_stage2_density_discr.ndofs + src_weights = np.ones(ndofs) timing_data = {} potential = drive_fmm(wrangler, src_weights, timing_data, traversal=wrangler.trav)[0][geo_data.ncenters:] # Check constant one wrangler for correctness. - assert (potential == nnodes).all() + assert (potential == ndofs).all() modeled_time = cost_S.get_predicted_times(merge_close_lists=True) @@ -538,13 +544,14 @@ def test_cost_model_order_varying_by_level(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # {{{ constant level to order def level_to_order_constant(kernel, kernel_args, tree, level): return 1 - lpot_source = get_lpot_source(queue, 2).copy( + lpot_source = get_lpot_source(actx, 2).copy( cost_model=CostModel( calibration_params=CONSTANT_ONE_PARAMS), fmm_level_to_order=level_to_order_constant) @@ -556,11 +563,11 @@ def level_to_order_constant(kernel, kernel_args, tree, level): k_sym = LaplaceKernel(2) sym_op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) - sigma = get_density(queue, density_discr) + sigma = get_density(actx, density_discr) cost_constant = one( bind(places, sym_op) - .get_modeled_cost(queue, sigma=sigma).values()) + .get_modeled_cost(actx, sigma=sigma).values()) # }}} diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 9ab5b6926..6b8047b36 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -37,6 +37,7 @@ from pytential.qbx import QBXLayerPotentialSource from functools import partial +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, make_curve_mesh, generate_icosphere, generate_torus) @@ -55,6 +56,22 @@ FAR_TARGET_DIST_FROM_SOURCE = 10 +# {{{ utils + +def dof_array_to_numpy(actx, ary): + """Converts DOFArrays (or object arrays of DOFArrays) to NumPy arrays. + Object arrays get turned into multidimensional arrays. + """ + from pytools.obj_array import obj_array_vectorize + from meshmode.dof_array import flatten + arr = obj_array_vectorize(actx.to_numpy, flatten(ary)) + if arr.dtype.char == "O": + arr = np.array(list(arr)) + return arr + +# }}} + + # {{{ source refinement checker class ElementInfo(RecordWithoutPickling): @@ -76,22 +93,23 @@ def iter_elements(discr): yield ElementInfo( element_nr=element_nr, discr_slice=slice(discr_nodes_idx, - discr_nodes_idx + discr_group.nunit_nodes)) + discr_nodes_idx + discr_group.nunit_dofs)) - discr_nodes_idx += discr_group.nunit_nodes + discr_nodes_idx += discr_group.nunit_dofs def run_source_refinement_test(ctx_factory, mesh, order, helmholtz_k=None, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # {{{ initial geometry from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) - discr = Discretization(cl_ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) lpot_source = QBXLayerPotentialSource(discr, @@ -107,7 +125,7 @@ def run_source_refinement_test(ctx_factory, mesh, order, expansion_disturbance_tolerance = 0.025 from pytential.qbx.refinement import refine_geometry_collection - places = refine_geometry_collection(queue, places, + places = refine_geometry_collection(places, kernel_length_scale=kernel_length_scale, expansion_disturbance_tolerance=expansion_disturbance_tolerance, visualize=visualize) @@ -116,27 +134,34 @@ def run_source_refinement_test(ctx_factory, mesh, order, dd = places.auto_source stage1_density_discr = places.get_discretization(dd.geometry) - stage1_density_nodes = stage1_density_discr.nodes().get(queue) + from meshmode.dof_array import thaw + + stage1_density_nodes = dof_array_to_numpy(actx, + thaw(actx, stage1_density_discr.nodes())) quad_stage2_density_discr = places.get_discretization( dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) - quad_stage2_density_nodes = quad_stage2_density_discr.nodes().get(queue) - - int_centers = bind(places, - sym.expansion_centers(lpot_source.ambient_dim, -1))(queue) - int_centers = np.array([axis.get(queue) for axis in int_centers]) - ext_centers = bind(places, - sym.expansion_centers(lpot_source.ambient_dim, +1))(queue) - ext_centers = np.array([axis.get(queue) for axis in ext_centers]) - - expansion_radii = bind(places, - sym.expansion_radii(lpot_source.ambient_dim))(queue).get() + quad_stage2_density_nodes = dof_array_to_numpy(actx, + thaw(actx, quad_stage2_density_discr.nodes())) + + int_centers = dof_array_to_numpy(actx, + bind(places, + sym.expansion_centers(lpot_source.ambient_dim, -1))(actx)) + ext_centers = dof_array_to_numpy(actx, + bind(places, + sym.expansion_centers(lpot_source.ambient_dim, +1))(actx)) + expansion_radii = dof_array_to_numpy(actx, + bind(places, sym.expansion_radii(lpot_source.ambient_dim))(actx)) dd = dd.copy(granularity=sym.GRANULARITY_ELEMENT) - source_danger_zone_radii = bind(places, sym._source_danger_zone_radii( - lpot_source.ambient_dim, dofdesc=dd.to_stage2()))(queue).get() - quad_res = bind(places, sym._quad_resolution( - lpot_source.ambient_dim, dofdesc=dd))(queue) + source_danger_zone_radii = dof_array_to_numpy(actx, + bind(places, + sym._source_danger_zone_radii( + lpot_source.ambient_dim, dofdesc=dd.to_stage2()))(actx)) + quad_res = dof_array_to_numpy(actx, + bind(places, + sym._quad_resolution( + lpot_source.ambient_dim, dofdesc=dd))(actx)) # {{{ check if satisfying criteria @@ -236,6 +261,7 @@ def test_target_association(ctx_factory, curve_name, curve_f, nelements, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # {{{ generate lpot source @@ -248,7 +274,7 @@ def test_target_association(ctx_factory, curve_name, curve_f, nelements, from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory factory = InterpolatoryQuadratureSimplexGroupFactory(order) - discr = Discretization(cl_ctx, mesh, factory) + discr = Discretization(actx, mesh, factory) lpot_source = QBXLayerPotentialSource(discr, qbx_order=order, # not used in target association @@ -263,20 +289,25 @@ def test_target_association(ctx_factory, curve_name, curve_f, nelements, rng = PhiloxGenerator(cl_ctx, seed=RNG_SEED) dd = places.auto_source.to_stage1() - centers = bind(places, sym.interleaved_expansion_centers( - lpot_source.ambient_dim, dofdesc=dd))(queue) - centers = np.array([ax.get(queue) for ax in centers]) - - tunnel_radius = bind(places, sym._close_target_tunnel_radii( - lpot_source.ambient_dim, dofdesc=dd))(queue) + centers = dof_array_to_numpy(actx, + bind(places, sym.interleaved_expansion_centers( + lpot_source.ambient_dim, dofdesc=dd))(actx)) density_discr = places.get_discretization(dd.geometry) - noise = rng.uniform(queue, density_discr.nnodes, dtype=np.float, a=0.01, b=1.0) + + noise = actx.to_numpy( + rng.uniform(queue, density_discr.ndofs, dtype=np.float, a=0.01, b=1.0)) + + tunnel_radius = dof_array_to_numpy(actx, + bind(places, sym._close_target_tunnel_radii( + lpot_source.ambient_dim, dofdesc=dd))(actx)) def targets_from_sources(sign, dist, dim=2): - nodes = bind(places, sym.nodes(dim, dofdesc=dd))(queue) - normals = bind(places, sym.normal(dim, dofdesc=dd))(queue) - return (nodes + normals * sign * dist).as_vector(np.object) + nodes = dof_array_to_numpy(actx, + bind(places, sym.nodes(dim, dofdesc=dd))(actx).as_vector(np.object)) + normals = dof_array_to_numpy(actx, + bind(places, sym.normal(dim, dofdesc=dd))(actx).as_vector(np.object)) + return actx.from_numpy(nodes + normals * sign * dist) from pytential.target import PointsTarget int_targets = PointsTarget(targets_from_sources(-1, noise * tunnel_radius)) @@ -297,7 +328,7 @@ def targets_from_sources(sign, dist, dim=2): (far_targets, 0), ) - sizes = np.cumsum([discr.nnodes for discr, _ in target_discrs]) + sizes = np.cumsum([discr.ndofs for discr, _ in target_discrs]) (surf_int_slice, surf_ext_slice, @@ -315,23 +346,24 @@ def targets_from_sources(sign, dist, dim=2): from pytential.qbx.utils import TreeCodeContainer code_container = TargetAssociationCodeContainer( - cl_ctx, TreeCodeContainer(cl_ctx)) + actx, TreeCodeContainer(actx)) target_assoc = (associate_targets_to_qbx_centers( places, places.auto_source, - code_container.get_wrangler(queue), + code_container.get_wrangler(actx), target_discrs, target_association_tolerance=1e-10) .get(queue=queue)) - expansion_radii = bind(places, sym.expansion_radii( - lpot_source.ambient_dim, - granularity=sym.GRANULARITY_CENTER))(queue).get() - surf_targets = np.array( - [axis.get(queue) for axis in 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()]) + expansion_radii = dof_array_to_numpy(actx, + bind(places, sym.expansion_radii( + lpot_source.ambient_dim, + granularity=sym.GRANULARITY_CENTER))(actx)) + from meshmode.dof_array import thaw + surf_targets = dof_array_to_numpy(actx, thaw(actx, density_discr.nodes())) + int_targets = actx.to_numpy(int_targets.nodes()) + ext_targets = actx.to_numpy(ext_targets.nodes()) def visualize_curve_and_assoc(): import matplotlib.pyplot as plt @@ -416,6 +448,7 @@ def check_close_targets(centers, targets, true_side, def test_target_association_failure(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # {{{ generate circle @@ -430,7 +463,7 @@ def test_target_association_failure(ctx_factory): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory factory = InterpolatoryQuadratureSimplexGroupFactory(order) - discr = Discretization(cl_ctx, mesh, factory) + discr = Discretization(actx, mesh, factory) lpot_source = QBXLayerPotentialSource(discr, qbx_order=order, # not used in target association fine_order=order) @@ -444,8 +477,9 @@ def test_target_association_failure(ctx_factory): 2j * np.pi * np.linspace(0, 1, 500, endpoint=False)) from pytential.target import PointsTarget close_circle_target = ( - PointsTarget(cl.array.to_device( - queue, np.array([close_circle.real, close_circle.imag])))) + PointsTarget( + actx.from_numpy( + np.array([close_circle.real, close_circle.imag])))) targets = ( (close_circle_target, 0), @@ -458,13 +492,13 @@ def test_target_association_failure(ctx_factory): from pytential.qbx.utils import TreeCodeContainer code_container = TargetAssociationCodeContainer( - cl_ctx, TreeCodeContainer(cl_ctx)) + actx, TreeCodeContainer(actx)) with pytest.raises(QBXTargetAssociationFailedException): associate_targets_to_qbx_centers( places, places.auto_source, - code_container.get_wrangler(queue), + code_container.get_wrangler(actx), targets, target_association_tolerance=1e-10) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index c7eeb200c..9fa844355 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -26,12 +26,12 @@ 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 functools import partial +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh, NArmedStarfish) @@ -51,6 +51,7 @@ def test_geometry(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) nelements = 30 order = 5 @@ -63,13 +64,13 @@ def test_geometry(ctx_factory): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - discr = Discretization(cl_ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) import pytential.symbolic.primitives as prim area_sym = prim.integral(2, 1, 1) - area = bind(discr, area_sym)(queue) + area = bind(discr, area_sym)(actx) err = abs(area-2*np.pi) print(err) @@ -86,6 +87,7 @@ def test_off_surface_eval(ctx_factory, use_fmm, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache 'splosion from sympy.core.cache import clear_cache @@ -109,7 +111,7 @@ def test_off_surface_eval(ctx_factory, use_fmm, visualize=False): InterpolatoryQuadratureSimplexGroupFactory pre_density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource( pre_density_discr, 4*target_order, @@ -127,16 +129,16 @@ def test_off_surface_eval(ctx_factory, use_fmm, visualize=False): from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=-2) - sigma = density_discr.zeros(queue) + 1 - fld_in_vol = bind(places, op)(queue, sigma=sigma) + sigma = density_discr.zeros(actx) + 1 + fld_in_vol = bind(places, op)(actx, sigma=sigma) fld_in_vol_exact = -1 - err = cl.clmath.fabs(fld_in_vol - fld_in_vol_exact) - linf_err = cl.array.max(err).get() + err = actx.np.fabs(fld_in_vol - fld_in_vol_exact) + linf_err = actx.to_numpy(err).max() print("l_inf error:", linf_err) if visualize: - fplot.show_scalar_in_matplotlib(fld_in_vol.get()) + fplot.show_scalar_in_matplotlib(actx.to_numpy(fld_in_vol)) import matplotlib.pyplot as pt pt.colorbar() pt.show() @@ -153,6 +155,7 @@ def test_off_surface_eval_vs_direct(ctx_factory, do_plot=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache 'splosion from sympy.core.cache import clear_cache @@ -172,7 +175,7 @@ def test_off_surface_eval_vs_direct(ctx_factory, do_plot=False): InterpolatoryQuadratureSimplexGroupFactory pre_density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) direct_qbx = QBXLayerPotentialSource( pre_density_discr, 4*target_order, qbx_order, fmm_order=False, @@ -201,31 +204,31 @@ def test_off_surface_eval_vs_direct(ctx_factory, do_plot=False): from pytential.qbx import QBXTargetAssociationFailedException op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None) try: - direct_sigma = direct_density_discr.zeros(queue) + 1 + direct_sigma = direct_density_discr.zeros(actx) + 1 direct_fld_in_vol = bind(places, op, auto_where=("direct_qbx", "target"))( - queue, sigma=direct_sigma) + actx, sigma=direct_sigma) except QBXTargetAssociationFailedException as e: - fplot.show_scalar_in_matplotlib(e.failed_target_flags.get(queue)) + fplot.show_scalar_in_matplotlib( + actx.to_numpy(actx.thaw(e.failed_target_flags))) import matplotlib.pyplot as pt pt.show() raise - fmm_sigma = fmm_density_discr.zeros(queue) + 1 + fmm_sigma = fmm_density_discr.zeros(actx) + 1 fmm_fld_in_vol = bind(places, op, auto_where=("fmm_qbx", "target"))( - queue, sigma=fmm_sigma) + actx, sigma=fmm_sigma) - err = cl.clmath.fabs(fmm_fld_in_vol - direct_fld_in_vol) - - linf_err = cl.array.max(err).get() + err = actx.np.fabs(fmm_fld_in_vol - direct_fld_in_vol) + linf_err = actx.to_numpy(err).max() print("l_inf error:", linf_err) if do_plot: #fplot.show_scalar_in_mayavi(0.1*.get(queue)) fplot.write_vtk_file("potential.vts", [ - ("fmm_fld_in_vol", fmm_fld_in_vol.get(queue)), - ("direct_fld_in_vol", direct_fld_in_vol.get(queue)) + ("fmm_fld_in_vol", actx.to_numpy(fmm_fld_in_vol)), + ("direct_fld_in_vol", actx.to_numpy(direct_fld_in_vol)) ]) assert linf_err < 1e-3 @@ -235,10 +238,10 @@ def test_off_surface_eval_vs_direct(ctx_factory, do_plot=False): # {{{ unregularized tests - def test_unregularized_with_ones_kernel(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) nelements = 10 order = 8 @@ -251,7 +254,7 @@ def test_unregularized_with_ones_kernel(ctx_factory): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - discr = Discretization(cl_ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) from pytential.unregularized import UnregularizedLayerPotentialSource @@ -268,24 +271,24 @@ def test_unregularized_with_ones_kernel(ctx_factory): sigma_sym = sym.var("sigma") op = sym.IntG(one_kernel_2d, sigma_sym, qbx_forced_limit=None) - sigma = cl.array.zeros(queue, discr.nnodes, dtype=float) - sigma.fill(1) - sigma.finish() + sigma = discr.zeros(actx) + 1 result_self = bind(places, op, auto_where=places.auto_where)( - queue, sigma=sigma) + actx, sigma=sigma) result_nonself = bind(places, op, auto_where=(places.auto_source, "target_non_self"))( - queue, sigma=sigma) + actx, sigma=sigma) - assert np.allclose(result_self.get(), 2 * np.pi) - assert np.allclose(result_nonself.get(), 2 * np.pi) + from meshmode.dof_array import flatten + assert np.allclose(actx.to_numpy(flatten(result_self)), 2 * np.pi) + assert np.allclose(actx.to_numpy(result_nonself), 2 * np.pi) def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) nelements = 300 target_order = 8 @@ -303,7 +306,7 @@ def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) direct = UnregularizedLayerPotentialSource( density_discr, fmm_order=False, @@ -311,7 +314,7 @@ def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): fmm = direct.copy( fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order) - sigma = density_discr.zeros(queue) + 1 + sigma = density_discr.zeros(actx) + 1 fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100) from pytential.target import PointsTarget @@ -332,13 +335,12 @@ def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): direct_fld_in_vol = bind(places, op, auto_where=("unregularized_direct", "targets"))( - queue, sigma=sigma) + actx, sigma=sigma) fmm_fld_in_vol = bind(places, op, - auto_where=("unregularized_fmm", "targets"))(queue, sigma=sigma) - - err = cl.clmath.fabs(fmm_fld_in_vol - direct_fld_in_vol) + auto_where=("unregularized_fmm", "targets"))(actx, sigma=sigma) - linf_err = cl.array.max(err).get() + err = actx.np.fabs(fmm_fld_in_vol - direct_fld_in_vol) + linf_err = actx.to_numpy(err).max() print("l_inf error:", linf_err) assert linf_err < 5e-3 @@ -356,6 +358,7 @@ def test_3d_jump_relations(ctx_factory, relation, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) if relation == "div_s": target_order = 3 @@ -377,7 +380,7 @@ def test_3d_jump_relations(ctx_factory, relation, visualize=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory pre_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(3)) from pytential.qbx import QBXLayerPotentialSource @@ -401,8 +404,9 @@ def nxcurlS(qbx_forced_limit): sym.cse(sym.tangential_to_xyz(density_sym), "jxyz"), qbx_forced_limit=qbx_forced_limit))) - x, y, z = density_discr.nodes().with_queue(queue) - m = cl.clmath + from meshmode.dof_array import thaw + x, y, z = thaw(actx, density_discr.nodes()) + m = actx.np if relation == "nxcurls": density_sym = sym.make_sym_vector("density", 2) @@ -417,7 +421,7 @@ def nxcurlS(qbx_forced_limit): # an XYZ function and project it. density = bind(places, sym.xyz_to_tangential(sym.make_sym_vector("jxyz", 3)))( - queue, + actx, jxyz=sym.make_obj_array([ m.cos(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z), m.sin(0.5*x) * m.cos(0.5*y) * m.sin(0.5*z), @@ -448,12 +452,12 @@ def nxcurlS(qbx_forced_limit): raise ValueError("unexpected value of 'relation': %s" % relation) bound_jump_identity = bind(places, jump_identity_sym) - jump_identity = bound_jump_identity(queue, density=density) + jump_identity = bound_jump_identity(actx, density=density) - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) err = ( - norm(density_discr, queue, jump_identity, np.inf) - / norm(density_discr, queue, density, np.inf)) + norm(density_discr, jump_identity, np.inf) + / norm(density_discr, density, np.inf)) print("ERROR", h_max, err) eoc_rec.add_data_point(h_max, err) @@ -461,15 +465,15 @@ def nxcurlS(qbx_forced_limit): # {{{ visualization if visualize and relation == "nxcurls": - nxcurlS_ext = bind(places, nxcurlS(+1))(queue, density=density) - nxcurlS_avg = bind(places, nxcurlS("avg"))(queue, density=density) + nxcurlS_ext = bind(places, nxcurlS(+1))(actx, density=density) + nxcurlS_avg = bind(places, nxcurlS("avg"))(actx, density=density) jtxyz = bind(places, sym.tangential_to_xyz(density_sym))( - queue, density=density) + actx, density=density) from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, qbx.density_discr, target_order+3) + bdry_vis = make_visualizer(actx, qbx.density_discr, target_order+3) - bdry_normals = bind(places, sym.normal(3))(queue)\ + bdry_normals = bind(places, sym.normal(3))(actx)\ .as_vector(dtype=object) bdry_vis.write_vtk_file("source-%s.vtu" % nel_factor, [ @@ -481,15 +485,15 @@ def nxcurlS(qbx_forced_limit): if visualize and relation == "sp": op = sym.Sp(knl, density_sym, qbx_forced_limit=+1) - sp_ext = bind(places, op)(queue, density=density) + sp_ext = bind(places, op)(actx, density=density) op = sym.Sp(knl, density_sym, qbx_forced_limit="avg") - sp_avg = bind(places, op)(queue, density=density) + sp_avg = bind(places, op)(actx, density=density) from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, qbx.density_discr, target_order+3) + bdry_vis = make_visualizer(actx, qbx.density_discr, target_order+3) bdry_normals = bind(places, - sym.normal(3))(queue).as_vector(dtype=object) + sym.normal(3))(actx).as_vector(dtype=object) bdry_vis.write_vtk_file("source-%s.vtu" % nel_factor, [ ("density", density), diff --git a/test/test_layer_pot_eigenvalues.py b/test/test_layer_pot_eigenvalues.py index af2278ebe..b57979130 100644 --- a/test/test_layer_pot_eigenvalues.py +++ b/test/test_layer_pot_eigenvalues.py @@ -26,12 +26,12 @@ 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 functools import partial +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh, NArmedStarfish) @@ -72,6 +72,7 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) target_order = 8 @@ -107,7 +108,7 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, fmm_order = False pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource( pre_density_discr, 4*target_order, @@ -117,18 +118,19 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, places = GeometryCollection(qbx) density_discr = places.get_discretization(places.auto_source.geometry) - nodes = density_discr.nodes().with_queue(queue) + from meshmode.dof_array import thaw, flatten + nodes = thaw(actx, density_discr.nodes()) if visualize: # plot geometry, centers, normals centers = bind(places, - sym.expansion_centers(qbx.ambient_dim, +1))(queue) - normal = bind(places, - sym.normal(qbx.ambient_dim))(queue).as_vector(np.object) + sym.expansion_centers(qbx.ambient_dim, +1))(actx) + normals = bind(places, + sym.normal(qbx.ambient_dim))(actx).as_vector(np.object) - nodes_h = nodes.get() - centers_h = [centers[0].get(), centers[1].get()] - normals_h = [normal[0].get(), normal[1].get()] + nodes_h = np.array([actx.to_numpy(axis) for axis in flatten(nodes)]) + centers_h = np.array([actx.to_numpy(axis) for axis in flatten(centers)]) + normals_h = np.array([actx.to_numpy(axis) for axis in flatten(normals)]) pt.plot(nodes_h[0], nodes_h[1], "x-") pt.plot(centers_h[0], centers_h[1], "o") @@ -136,14 +138,14 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, pt.gca().set_aspect("equal") pt.show() - angle = cl.clmath.atan2(nodes[1]*ellipse_aspect, nodes[0]) + angle = actx.np.atan2(nodes[1]*ellipse_aspect, nodes[0]) ellipse_fraction = ((1-ellipse_aspect)/(1+ellipse_aspect))**mode_nr # (2.6) in [1] - J = cl.clmath.sqrt( # noqa - cl.clmath.sin(angle)**2 - + (1/ellipse_aspect)**2 * cl.clmath.cos(angle)**2) + J = actx.np.sqrt( # noqa + actx.np.sin(angle)**2 + + (1/ellipse_aspect)**2 * actx.np.cos(angle)**2) from sumpy.kernel import LaplaceKernel lap_knl = LaplaceKernel(2) @@ -153,8 +155,8 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, sigma_sym = sym.var("sigma") s_sigma_op = sym.S(lap_knl, sigma_sym, qbx_forced_limit=+1) - sigma = cl.clmath.cos(mode_nr*angle)/J - s_sigma = bind(places, s_sigma_op)(queue=queue, sigma=sigma) + sigma = actx.np.cos(mode_nr*angle)/J + s_sigma = bind(places, s_sigma_op)(actx, sigma=sigma) # SIGN BINGO! :) s_eigval = 1/(2*mode_nr) * (1 + (-1)**mode_nr * ellipse_fraction) @@ -165,14 +167,14 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, if 0: #pt.plot(s_sigma.get(), label="result") #pt.plot(s_sigma_ref.get(), label="ref") - pt.plot((s_sigma_ref - s_sigma).get(), label="err") + pt.plot(actx.to_numpy(flatten(s_sigma_ref - s_sigma)), label="err") pt.legend() pt.show() - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) s_err = ( - norm(density_discr, queue, s_sigma - s_sigma_ref) - / norm(density_discr, queue, s_sigma_ref)) + norm(density_discr, s_sigma - s_sigma_ref) + / norm(density_discr, s_sigma_ref)) s_eoc_rec.add_data_point(h_max, s_err) # }}} @@ -181,8 +183,8 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, d_sigma_op = sym.D(lap_knl, sigma_sym, qbx_forced_limit="avg") - sigma = cl.clmath.cos(mode_nr*angle) - d_sigma = bind(places, d_sigma_op)(queue=queue, sigma=sigma) + sigma = actx.np.cos(mode_nr*angle) + d_sigma = bind(places, d_sigma_op)(actx, sigma=sigma) # SIGN BINGO! :) d_eigval = -(-1)**mode_nr * 1/2*ellipse_fraction @@ -190,18 +192,18 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, d_sigma_ref = d_eigval*sigma if 0: - pt.plot(d_sigma.get(), label="result") - pt.plot(d_sigma_ref.get(), label="ref") + pt.plot(actx.to_numpy(flatten(d_sigma)), label="result") + pt.plot(actx.to_numpy(flatten(d_sigma_ref)), label="ref") pt.legend() pt.show() if ellipse_aspect == 1: - d_ref_norm = norm(density_discr, queue, sigma) + d_ref_norm = norm(density_discr, sigma) else: - d_ref_norm = norm(density_discr, queue, d_sigma_ref) + d_ref_norm = norm(density_discr, d_sigma_ref) d_err = ( - norm(density_discr, queue, d_sigma - d_sigma_ref) + norm(density_discr, d_sigma - d_sigma_ref) / d_ref_norm) d_eoc_rec.add_data_point(h_max, d_err) @@ -212,15 +214,15 @@ def test_ellipse_eigenvalues(ctx_factory, ellipse_aspect, mode_nr, qbx_order, sp_sigma_op = sym.Sp(lap_knl, sym.var("sigma"), qbx_forced_limit="avg") - sigma = cl.clmath.cos(mode_nr*angle) - sp_sigma = bind(places, sp_sigma_op)(queue=queue, sigma=sigma) + sigma = actx.np.cos(mode_nr*angle) + sp_sigma = bind(places, sp_sigma_op)(actx, sigma=sigma) sp_eigval = 0 sp_sigma_ref = sp_eigval*sigma sp_err = ( - norm(density_discr, queue, sp_sigma - sp_sigma_ref) - / norm(density_discr, queue, sigma)) + norm(density_discr, sp_sigma - sp_sigma_ref) + / norm(density_discr, sigma)) sp_eoc_rec.add_data_point(h_max, sp_err) # }}} @@ -261,6 +263,7 @@ def test_sphere_eigenvalues(ctx_factory, mode_m, mode_n, qbx_order, cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) target_order = 8 @@ -277,8 +280,8 @@ def test_sphere_eigenvalues(ctx_factory, 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, comp - ref) + / norm(density_discr, ref)) for nrefinements in [0, 1]: from meshmode.mesh.generation import generate_icosphere @@ -292,7 +295,7 @@ def rel_err(comp, ref): mesh = refiner.get_current_mesh() pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource( pre_density_discr, 4*target_order, @@ -301,14 +304,20 @@ def rel_err(comp, ref): ) places = GeometryCollection(qbx) - density_discr = places.get_discretization(places.auto_source.geometry) - nodes = density_discr.nodes().with_queue(queue) - r = cl.clmath.sqrt(nodes[0]**2 + nodes[1]**2 + nodes[2]**2) - phi = cl.clmath.acos(nodes[2]/r) - theta = cl.clmath.atan2(nodes[0], nodes[1]) + from meshmode.dof_array import flatten, unflatten, thaw - ymn = cl.array.to_device(queue, - special.sph_harm(mode_m, mode_n, theta.get(), phi.get())) + density_discr = places.get_discretization(places.auto_source.geometry) + nodes = thaw(actx, density_discr.nodes()) + r = actx.np.sqrt(nodes[0]*nodes[0] + nodes[1]*nodes[1] + nodes[2]*nodes[2]) + phi = actx.np.acos(nodes[2]/r) + theta = actx.np.atan2(nodes[0], nodes[1]) + + ymn = unflatten(actx, density_discr, + actx.from_numpy( + special.sph_harm( + mode_m, mode_n, + actx.to_numpy(flatten(theta)), + actx.to_numpy(flatten(phi))))) from sumpy.kernel import LaplaceKernel lap_knl = LaplaceKernel(3) @@ -317,10 +326,10 @@ def rel_err(comp, ref): s_sigma_op = bind(places, sym.S(lap_knl, sym.var("sigma"), qbx_forced_limit=+1)) - s_sigma = s_sigma_op(queue=queue, sigma=ymn) + s_sigma = s_sigma_op(actx, sigma=ymn) s_eigval = 1/(2*mode_n + 1) - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) s_eoc_rec.add_data_point(h_max, rel_err(s_sigma, s_eigval*ymn)) # }}} @@ -329,7 +338,7 @@ def rel_err(comp, ref): d_sigma_op = bind(places, sym.D(lap_knl, sym.var("sigma"), qbx_forced_limit="avg")) - d_sigma = d_sigma_op(queue=queue, sigma=ymn) + d_sigma = d_sigma_op(actx, sigma=ymn) d_eigval = -1/(2*(2*mode_n + 1)) d_eoc_rec.add_data_point(h_max, rel_err(d_sigma, d_eigval*ymn)) @@ -339,7 +348,7 @@ def rel_err(comp, ref): sp_sigma_op = bind(places, sym.Sp(lap_knl, sym.var("sigma"), qbx_forced_limit="avg")) - sp_sigma = sp_sigma_op(queue=queue, sigma=ymn) + sp_sigma = sp_sigma_op(actx, sigma=ymn) sp_eigval = -1/(2*(2*mode_n + 1)) sp_eoc_rec.add_data_point(h_max, rel_err(sp_sigma, sp_eigval*ymn)) @@ -350,7 +359,7 @@ def rel_err(comp, ref): dp_sigma_op = bind(places, sym.Dp(lap_knl, sym.var("sigma"), qbx_forced_limit="avg")) - dp_sigma = dp_sigma_op(queue=queue, sigma=ymn) + dp_sigma = dp_sigma_op(actx, sigma=ymn) dp_eigval = -(mode_n*(mode_n+1))/(2*mode_n + 1) dp_eoc_rec.add_data_point(h_max, rel_err(dp_sigma, dp_eigval*ymn)) diff --git a/test/test_layer_pot_identity.py b/test/test_layer_pot_identity.py index 7b0cbc65c..b206e78de 100644 --- a/test/test_layer_pot_identity.py +++ b/test/test_layer_pot_identity.py @@ -32,6 +32,7 @@ pytest_generate_tests_for_pyopencl as pytest_generate_tests) from functools import partial +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, NArmedStarfish, @@ -280,6 +281,7 @@ def test_identity_convergence(ctx_factory, case, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache 'splosion from sympy.core.cache import clear_cache @@ -314,7 +316,7 @@ def test_identity_convergence(ctx_factory, case, visualize=False): InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource( @@ -331,16 +333,18 @@ def test_identity_convergence(ctx_factory, case, visualize=False): from pytential.qbx.refinement import refine_geometry_collection kernel_length_scale = 5 / case.k if case.k else None - places = refine_geometry_collection(queue, places, + places = refine_geometry_collection(places, kernel_length_scale=kernel_length_scale) # {{{ compute values of a solution to the PDE density_discr = places.get_discretization(places.auto_source.geometry) - nodes_host = density_discr.nodes().get(queue) - normal = bind(places, sym.normal(d))(queue).as_vector(np.object) - normal_host = [normal[j].get() for j in range(d)] + from meshmode.dof_array import thaw, flatten, unflatten + nodes_host = [actx.to_numpy(axis) + for axis in flatten(thaw(actx, density_discr.nodes()))] + normal = bind(places, sym.normal(d))(actx).as_vector(np.object) + normal_host = [actx.to_numpy(axis)for axis in flatten(normal)] if k != 0: if d == 2: @@ -376,31 +380,33 @@ def test_identity_convergence(ctx_factory, case, visualize=False): # }}} - u_dev = cl.array.to_device(queue, u) - dn_u_dev = cl.array.to_device(queue, dn_u) - grad_u_dev = cl.array.to_device(queue, grad_u) + u_dev = unflatten(actx, density_discr, actx.from_numpy(u)) + dn_u_dev = unflatten(actx, density_discr, actx.from_numpy(dn_u)) + from pytools.obj_array import make_obj_array, obj_array_vectorize + grad_u_dev = unflatten(actx, density_discr, + obj_array_vectorize(actx.from_numpy, make_obj_array(grad_u))) key = (case.qbx_order, case.geometry.mesh_name, resolution, case.expr.zero_op_name) bound_op = bind(places, case.expr.get_zero_op(k_sym, **knl_kwargs)) error = bound_op( - queue, u=u_dev, dn_u=dn_u_dev, grad_u=grad_u_dev, k=case.k) + actx, u=u_dev, dn_u=dn_u_dev, grad_u=grad_u_dev, k=case.k) if 0: pt.plot(error) pt.show() - linf_error_norm = norm(density_discr, queue, error, p=np.inf) + linf_error_norm = norm(density_discr, error, p=np.inf) print("--->", key, linf_error_norm) - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) eoc_rec.add_data_point(h_max, linf_error_norm) if visualize: from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, density_discr, target_order) + bdry_vis = make_visualizer(actx, density_discr, target_order) - bdry_normals = bind(places, sym.normal(mesh.ambient_dim))(queue)\ + bdry_normals = bind(places, sym.normal(mesh.ambient_dim))(actx)\ .as_vector(dtype=np.object) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ diff --git a/test/test_linalg_proxy.py b/test/test_linalg_proxy.py index a44872906..8e485d6e9 100644 --- a/test/test_linalg_proxy.py +++ b/test/test_linalg_proxy.py @@ -29,6 +29,8 @@ import pyopencl.array # noqa from pytential import bind, sym + +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, NArmedStarfish, generate_torus, make_curve_mesh) @@ -41,9 +43,9 @@ from test_matrix import _build_geometry, _build_block_index -def _plot_partition_indices(queue, discr, indices, **kwargs): +def _plot_partition_indices(actx, discr, indices, **kwargs): import matplotlib.pyplot as pt - indices = indices.get(queue) + indices = indices.get(actx.queue) args = [ kwargs.get("method", "unknown"), @@ -57,12 +59,13 @@ def _plot_partition_indices(queue, discr, indices, **kwargs): pt.savefig("test_partition_{0}_{1}_{3}d_ranges_{2}.png".format(*args)) pt.clf() + from pytential.utils import flatten_to_numpy if discr.ambient_dim == 2: - sources = discr.nodes().get(queue) + sources = flatten_to_numpy(actx, discr.nodes()) pt.figure(figsize=(10, 8), dpi=300) - if indices.indices.shape[0] != discr.nnodes: + if indices.indices.shape[0] != discr.ndofs: pt.plot(sources[0], sources[1], 'ko', alpha=0.5) for i in range(indices.nblocks): isrc = indices.block_indices(i) @@ -80,17 +83,20 @@ def _plot_partition_indices(queue, discr, indices, **kwargs): return from meshmode.discretization.visualization import make_visualizer - marker = -42.0 * np.ones(discr.nnodes) + marker = -42.0 * np.ones(discr.ndofs) for i in range(indices.nblocks): isrc = indices.block_indices(i) marker[isrc] = 10.0 * (i + 1.0) - vis = make_visualizer(queue, discr, 10) + from meshmode.dof_array import unflatten + marker = unflatten(actx, discr, actx.from_numpy(marker)) + + vis = make_visualizer(actx, discr, 10) - filename = "test_partition_{0}_{1}_{3}d_{2}.png".format(*args) + filename = "test_partition_{0}_{1}_{3}d_{2}.vtu".format(*args) vis.write_vtk_file(filename, [ - ("marker", cl.array.to_device(queue, marker)) + ("marker", marker) ]) @@ -99,12 +105,14 @@ def _plot_partition_indices(queue, discr, indices, **kwargs): def test_partition_points(ctx_factory, use_tree, ambient_dim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - places, dofdesc = _build_geometry(queue, ambient_dim=ambient_dim) - _build_block_index(queue, - places.get_discretization(dofdesc.geometry, dofdesc.discr_stage), - use_tree=use_tree, - factor=0.6) + places, dofdesc = _build_geometry(actx, ambient_dim=ambient_dim) + discr = places.get_discretization(dofdesc.geometry, dofdesc.discr_stage) + indices = _build_block_index(actx, discr, use_tree=use_tree, factor=0.6) + + if visualize: + _plot_partition_indices(actx, discr, indices, use_tree=use_tree) @pytest.mark.parametrize("ambient_dim", [2, 3]) @@ -112,24 +120,23 @@ def test_partition_points(ctx_factory, use_tree, ambient_dim, visualize=False): def test_proxy_generator(ctx_factory, ambient_dim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - places, dofdesc = _build_geometry(queue, ambient_dim=ambient_dim) + places, dofdesc = _build_geometry(actx, ambient_dim=ambient_dim) dofdesc = dofdesc.to_stage1() density_discr = places.get_discretization(dofdesc.geometry, dofdesc.discr_stage) - srcindices = _build_block_index(queue, - density_discr, - factor=factor) + srcindices = _build_block_index(actx, density_discr, factor=factor) from pytential.linalg.proxy import ProxyGenerator generator = ProxyGenerator(places) proxies, pxyranges, pxycenters, pxyradii = \ - generator(queue, dofdesc, srcindices) + generator(actx, dofdesc, 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() + proxies = np.vstack([actx.to_numpy(p) for p in proxies]) + pxyranges = actx.to_numpy(pxyranges) + pxycenters = np.vstack([actx.to_numpy(c) for c in pxycenters]) + pxyradii = actx.to_numpy(pxyradii) for i in range(srcindices.nblocks): ipxy = np.s_[pxyranges[i]:pxyranges[i + 1]] @@ -142,12 +149,14 @@ def test_proxy_generator(ctx_factory, ambient_dim, factor, visualize=False): if ambient_dim == 2: import matplotlib.pyplot as pt - density_nodes = density_discr.nodes().get(queue) - ci = bind(places, sym.expansion_centers(ambient_dim, -1))(queue) - ci = np.vstack([c.get(queue) for c in ci]) - ce = bind(places, sym.expansion_centers(ambient_dim, +1))(queue) - ce = np.vstack([c.get(queue) for c in ce]) - r = bind(places, sym.expansion_radii(ambient_dim))(queue).get() + from pytential.utils import flatten_to_numpy + density_nodes = np.vstack(flatten_to_numpy(actx, density_discr.nodes())) + ci = bind(places, sym.expansion_centers(ambient_dim, -1))(actx) + ci = np.vstack(flatten_to_numpy(actx, ci)) + ce = bind(places, sym.expansion_centers(ambient_dim, +1))(actx) + ce = np.vstack(flatten_to_numpy(actx, ce)) + r = bind(places, sym.expansion_radii(ambient_dim))(actx) + r = flatten_to_numpy(actx, r) for i in range(srcindices.nblocks): isrc = srcindices.block_indices(i) @@ -195,10 +204,10 @@ def test_proxy_generator(ctx_factory, ambient_dim, factor, visualize=False): b=pxycenters[:, i].reshape(-1)) mesh = merge_disjoint_meshes([mesh, density_discr.mesh]) - discr = Discretization(ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(10)) - vis = make_visualizer(queue, discr, 10) + vis = make_visualizer(actx, discr, 10) filename = "test_proxy_generator_{}d_{:04}.vtu".format( ambient_dim, i) vis.write_vtk_file(filename, []) @@ -209,26 +218,25 @@ def test_proxy_generator(ctx_factory, ambient_dim, factor, visualize=False): def test_interaction_points(ctx_factory, ambient_dim, factor, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - places, dofdesc = _build_geometry(queue, ambient_dim=ambient_dim) + places, dofdesc = _build_geometry(actx, ambient_dim=ambient_dim) dofdesc = dofdesc.to_stage1() density_discr = places.get_discretization(dofdesc.geometry, dofdesc.discr_stage) - srcindices = _build_block_index(queue, - density_discr, - factor=factor) + srcindices = _build_block_index(actx, density_discr, factor=factor) # generate proxy points from pytential.linalg.proxy import ProxyGenerator generator = ProxyGenerator(places) - _, _, pxycenters, pxyradii = generator(queue, dofdesc, srcindices) + _, _, pxycenters, pxyradii = generator(actx, dofdesc, srcindices) from pytential.linalg.proxy import ( # noqa gather_block_neighbor_points, gather_block_interaction_points) - nbrindices = gather_block_neighbor_points(density_discr, + nbrindices = gather_block_neighbor_points(actx, density_discr, srcindices, pxycenters, pxyradii) - nodes, ranges = gather_block_interaction_points( + nodes, ranges = gather_block_interaction_points(actx, places, dofdesc, srcindices) srcindices = srcindices.get(queue) @@ -240,12 +248,13 @@ def test_interaction_points(ctx_factory, ambient_dim, factor, visualize=False): assert not np.any(np.isin(inbr, isrc)) + from pytential.utils import flatten_to_numpy if visualize: if ambient_dim == 2: import matplotlib.pyplot as pt - density_nodes = density_discr.nodes().get(queue) - nodes = nodes.get(queue) - ranges = ranges.get(queue) + density_nodes = flatten_to_numpy(actx, density_discr.nodes()) + nodes = flatten_to_numpy(actx, nodes) + ranges = actx.to_numpy(ranges) for i in range(srcindices.nblocks): isrc = srcindices.block_indices(i) @@ -255,14 +264,14 @@ def test_interaction_points(ctx_factory, ambient_dim, factor, visualize=False): 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.indices], - density_nodes[1, srcindices.indices], + 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], + pt.plot(density_nodes[0][isrc], density_nodes[1][isrc], 'o', ms=2.0) - pt.plot(density_nodes[0, inbr], density_nodes[1, inbr], + pt.plot(density_nodes[0][inbr], density_nodes[1][inbr], 'o', ms=2.0) - pt.plot(nodes[0, iall], nodes[1, iall], + pt.plot(nodes[0][iall], nodes[1][iall], 'x', ms=2.0) pt.xlim([-1.5, 1.5]) pt.ylim([-1.5, 1.5]) @@ -272,7 +281,7 @@ def test_interaction_points(ctx_factory, ambient_dim, factor, visualize=False): pt.clf() elif ambient_dim == 3: from meshmode.discretization.visualization import make_visualizer - marker = np.empty(density_discr.nnodes) + marker = np.empty(density_discr.ndofs) for i in range(srcindices.nblocks): isrc = srcindices.block_indices(i) @@ -282,9 +291,11 @@ def test_interaction_points(ctx_factory, ambient_dim, factor, visualize=False): marker[srcindices.indices] = 0.0 marker[isrc] = -42.0 marker[inbr] = +42.0 - marker_dev = cl.array.to_device(queue, marker) - vis = make_visualizer(queue, density_discr, 10) + from meshmode.dof_array import unflatten + marker_dev = unflatten(actx, density_discr, actx.from_numpy(marker)) + + vis = make_visualizer(actx, density_discr, 10) filename = "test_area_query_{}d_{:04}.vtu".format(ambient_dim, i) vis.write_vtk_file(filename, [ ("marker", marker_dev), diff --git a/test/test_matrix.py b/test/test_matrix.py index 12be496c5..ce429642d 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -31,16 +31,17 @@ import numpy.linalg as la import pyopencl as cl -import pyopencl.array # noqa +import pyopencl.array from pytools.obj_array import make_obj_array, is_obj_array from sumpy.tools import BlockIndexRanges, MatrixBlockIndexRanges from sumpy.symbolic import USE_SYMENGINE -from pytential import sym +from pytential import bind, sym from pytential import GeometryCollection +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, NArmedStarfish, make_curve_mesh, generate_torus) @@ -55,7 +56,7 @@ pass -def _build_geometry(queue, +def _build_geometry(actx, ambient_dim=2, nelements=30, target_order=7, @@ -79,8 +80,7 @@ def _build_geometry(queue, from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource - density_discr = Discretization( - queue.context, mesh, + density_discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource(density_discr, @@ -92,24 +92,24 @@ def _build_geometry(queue, return places, places.auto_source -def _build_block_index(queue, - discr, +def _build_block_index(actx, discr, nblks=10, factor=1.0, use_tree=True): - nnodes = discr.nnodes - max_particles_in_box = nnodes // nblks + max_particles_in_box = discr.ndofs // nblks # create index ranges from pytential.linalg.proxy import partition_by_nodes - indices = partition_by_nodes(discr, - use_tree=use_tree, max_nodes_in_box=max_particles_in_box) + indices = partition_by_nodes(actx, discr, + use_tree=use_tree, + max_nodes_in_box=max_particles_in_box) if abs(factor - 1.0) < 1.0e-14: return indices # randomly pick a subset of points - indices = indices.get(queue) + # FIXME: this needs porting in sumpy.tools.BlockIndexRanges + indices = indices.get(actx.queue) indices_ = np.empty(indices.nblocks, dtype=np.object) for i in range(indices.nblocks): @@ -120,13 +120,11 @@ def _build_block_index(queue, 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_)) + ranges_ = actx.from_numpy(np.cumsum([0] + [r.shape[0] for r in indices_])) + indices_ = actx.from_numpy(np.hstack(indices_)) - indices = BlockIndexRanges(discr.cl_context, - indices_.with_queue(None), - ranges_.with_queue(None)) + indices = BlockIndexRanges(actx.context, + actx.freeze(indices_), actx.freeze(ranges_)) return indices @@ -137,8 +135,8 @@ def _build_op(lpot_id, source=sym.DEFAULT_SOURCE, target=sym.DEFAULT_TARGET, qbx_forced_limit="avg"): - from sumpy.kernel import LaplaceKernel, HelmholtzKernel + if k: knl = HelmholtzKernel(ambient_dim) knl_kwargs = {"k": k} @@ -200,6 +198,7 @@ def _max_block_error(mat, blk, index_set): def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache 'splosion from sympy.core.cache import clear_cache @@ -215,8 +214,7 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - pre_density_discr = Discretization( - cl_ctx, mesh, + pre_density_discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource @@ -228,7 +226,7 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): from pytential.qbx.refinement import refine_geometry_collection places = GeometryCollection(qbx) - places = refine_geometry_collection(queue, places, + places = refine_geometry_collection(places, kernel_length_scale=(5 / k if k else None)) source = places.auto_source.to_stage1() @@ -237,15 +235,14 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): op, u_sym, knl_kwargs = _build_op(lpot_id, k=k, source=places.auto_source, target=places.auto_target) - from pytential import bind bound_op = bind(places, op) from pytential.symbolic.execution import build_matrix - mat = build_matrix(queue, places, op, u_sym).get() + mat = build_matrix(actx, places, op, u_sym).get() if visualize: from sumpy.tools import build_matrix as build_matrix_via_matvec - mat2 = bound_op.scipy_op(queue, "u", dtype=mat.dtype, **knl_kwargs) + mat2 = bound_op.scipy_op(actx, "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")) @@ -267,23 +264,22 @@ def test_matrix_build(ctx_factory, k, curve_f, lpot_id, visualize=False): pt.colorbar() pt.show() - from sumpy.tools import vector_to_device, vector_from_device + from pytential.utils import unflatten_from_numpy, flatten_to_numpy np.random.seed(12) for i in range(5): if is_obj_array(u_sym): u = make_obj_array([ - np.random.randn(density_discr.nnodes) + np.random.randn(density_discr.ndofs) for _ in range(len(u_sym)) ]) else: - u = np.random.randn(density_discr.nnodes) + u = np.random.randn(density_discr.ndofs) + u_dev = unflatten_from_numpy(actx, density_discr, u) - u_dev = vector_to_device(queue, u) res_matvec = np.hstack( - list(vector_from_device( - queue, bound_op(queue, u=u_dev)))) - - res_mat = mat.dot(np.hstack(list(u))) + flatten_to_numpy(actx, bound_op(actx, u=u_dev)) + ) + res_mat = mat.dot(np.hstack(u)) abs_err = la.norm(res_mat - res_matvec, np.inf) rel_err = abs_err / la.norm(res_matvec, np.inf) @@ -299,6 +295,7 @@ def test_p2p_block_builder(ctx_factory, factor, ambient_dim, lpot_id, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache explosion from sympy.core.cache import clear_cache @@ -312,7 +309,7 @@ def test_p2p_block_builder(ctx_factory, factor, ambient_dim, lpot_id, ) target_order = 2 if ambient_dim == 3 else 7 - places, dofdesc = _build_geometry(queue, + places, dofdesc = _build_geometry(actx, target_order=target_order, ambient_dim=ambient_dim, auto_where=place_ids) @@ -323,14 +320,14 @@ def test_p2p_block_builder(ctx_factory, factor, ambient_dim, lpot_id, dd = places.auto_source density_discr = places.get_discretization(dd.geometry, dd.discr_stage) - index_set = _build_block_index(queue, density_discr, factor=factor) + index_set = _build_block_index(actx, density_discr, factor=factor) index_set = MatrixBlockIndexRanges(ctx, index_set, index_set) from pytential.symbolic.execution import _prepare_expr expr = _prepare_expr(places, op) from pytential.symbolic.matrix import P2PMatrixBuilder - mbuilder = P2PMatrixBuilder(queue, + mbuilder = P2PMatrixBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -341,7 +338,7 @@ def test_p2p_block_builder(ctx_factory, factor, ambient_dim, lpot_id, mat = mbuilder(expr) from pytential.symbolic.matrix import FarFieldBlockBuilder - mbuilder = FarFieldBlockBuilder(queue, + mbuilder = FarFieldBlockBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -352,7 +349,7 @@ def test_p2p_block_builder(ctx_factory, factor, ambient_dim, lpot_id, exclude_self=True) blk = mbuilder(expr) - index_set = index_set.get(queue) + index_set = index_set.get(actx.queue) if visualize and ambient_dim == 2: blk_full = np.zeros_like(mat) mat_full = np.zeros_like(mat) @@ -381,6 +378,7 @@ def test_qbx_block_builder(ctx_factory, factor, ambient_dim, lpot_id, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache explosion from sympy.core.cache import clear_cache @@ -394,7 +392,7 @@ def test_qbx_block_builder(ctx_factory, factor, ambient_dim, lpot_id, ) target_order = 2 if ambient_dim == 3 else 7 - places, _ = _build_geometry(queue, + places, _ = _build_geometry(actx, target_order=target_order, ambient_dim=ambient_dim, auto_where=place_ids) @@ -409,11 +407,11 @@ def test_qbx_block_builder(ctx_factory, factor, ambient_dim, lpot_id, dd = places.auto_source density_discr = places.get_discretization(dd.geometry, dd.discr_stage) - index_set = _build_block_index(queue, density_discr, factor=factor) + index_set = _build_block_index(actx, density_discr, factor=factor) index_set = MatrixBlockIndexRanges(ctx, index_set, index_set) from pytential.symbolic.matrix import NearFieldBlockBuilder - mbuilder = NearFieldBlockBuilder(queue, + mbuilder = NearFieldBlockBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -424,7 +422,7 @@ def test_qbx_block_builder(ctx_factory, factor, ambient_dim, lpot_id, blk = mbuilder(expr) from pytential.symbolic.matrix import MatrixBuilder - mbuilder = MatrixBuilder(queue, + mbuilder = MatrixBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -462,6 +460,7 @@ def test_build_matrix_places(ctx_factory, source_discr_stage, target_discr_stage, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) # prevent cache explosion from sympy.core.cache import clear_cache @@ -476,7 +475,7 @@ def test_build_matrix_places(ctx_factory, ) # build test operators - places, _ = _build_geometry(queue, + places, _ = _build_geometry(actx, nelements=8, target_order=2, ambient_dim=2, @@ -493,7 +492,7 @@ def test_build_matrix_places(ctx_factory, dd = places.auto_source source_discr = places.get_discretization(dd.geometry, dd.discr_stage) - index_set = _build_block_index(queue, source_discr, factor=0.6) + index_set = _build_block_index(actx, source_discr, factor=0.6) index_set = MatrixBlockIndexRanges(ctx, index_set, index_set) from pytential.symbolic.execution import _prepare_expr @@ -501,7 +500,7 @@ def test_build_matrix_places(ctx_factory, # build full QBX matrix from pytential.symbolic.matrix import MatrixBuilder - mbuilder = MatrixBuilder(queue, + mbuilder = MatrixBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -512,7 +511,7 @@ def test_build_matrix_places(ctx_factory, # build full p2p matrix from pytential.symbolic.matrix import P2PMatrixBuilder - mbuilder = P2PMatrixBuilder(queue, + mbuilder = P2PMatrixBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -521,11 +520,11 @@ def test_build_matrix_places(ctx_factory, context={}) p2p_mat = mbuilder(op) - assert p2p_mat.shape == (target_discr.nnodes, source_discr.nnodes) + assert p2p_mat.shape == (target_discr.ndofs, source_discr.ndofs) # build block qbx and p2p matrices from pytential.symbolic.matrix import NearFieldBlockBuilder - mbuilder = NearFieldBlockBuilder(queue, + mbuilder = NearFieldBlockBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), @@ -538,7 +537,7 @@ def test_build_matrix_places(ctx_factory, assert _max_block_error(qbx_mat, mat, index_set.get(queue)) < 1.0e-14 from pytential.symbolic.matrix import FarFieldBlockBuilder - mbuilder = FarFieldBlockBuilder(queue, + mbuilder = FarFieldBlockBuilder(actx, dep_expr=u_sym, other_dep_exprs=[], dep_source=places.get_geometry(dd.geometry), diff --git a/test/test_maxwell.py b/test/test_maxwell.py index 2e950193e..0fc67b0bf 100644 --- a/test/test_maxwell.py +++ b/test/test_maxwell.py @@ -31,6 +31,7 @@ from pytential import bind, sym, norm +from meshmode.array_context import PyOpenCLArrayContext from sumpy.visualization import make_field_plotter_from_bbox # noqa from sumpy.point_calculus import CalculusPatch, frequency_domain_maxwell from sumpy.tools import vector_from_device @@ -77,7 +78,7 @@ def get_observation_mesh(self, target_order): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -87,9 +88,7 @@ def get_source(self, queue): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) class RoundedCubeTestCase(MaxwellTestCase): @@ -121,7 +120,7 @@ def get_observation_mesh(self, target_order): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -131,9 +130,7 @@ def get_source(self, queue): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) class ElliptiPlaneTestCase(MaxwellTestCase): @@ -168,7 +165,7 @@ def get_observation_mesh(self, target_order): else: return generate_icosphere(0.5, target_order) - def get_source(self, queue): + def get_source(self, actx): if self.is_interior: source_ctr = np.array([[0.35, 0.1, 0.15]]).T else: @@ -178,9 +175,7 @@ def get_source(self, queue): sources = source_ctr + source_rad*2*(np.random.rand(3, 10)-0.5) from pytential.source import PointPotentialSource - return PointPotentialSource( - queue.context, - cl.array.to_device(queue, sources)) + return PointPotentialSource(actx.from_numpy(sources)) # }}} @@ -228,6 +223,7 @@ def test_pec_mfie_extinction(ctx_factory, case, cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) np.random.seed(12) @@ -245,13 +241,16 @@ def test_pec_mfie_extinction(ctx_factory, case, get_sym_maxwell_plane_wave) mfie = PECChargeCurrentMFIEOperator() - test_source = case.get_source(queue) + test_source = case.get_source(actx) calc_patch = CalculusPatch(np.array([-3, 0, 0]), h=0.01) - calc_patch_tgt = PointsTarget(cl.array.to_device(queue, calc_patch.points)) + calc_patch_tgt = PointsTarget(actx.from_numpy(calc_patch.points)) rng = cl.clrandom.PhiloxGenerator(cl_ctx, seed=12) - src_j = rng.normal(queue, (3, test_source.nnodes), dtype=np.float64) + from pytools.obj_array import make_obj_array + src_j = make_obj_array([ + rng.normal(actx.queue, (test_source.ndofs), dtype=np.float64) + for _ in range(3)]) def eval_inc_field_at(places, source=None, target=None): if source is None: @@ -264,12 +263,12 @@ def eval_inc_field_at(places, source=None, target=None): amplitude_vec=np.array([1, 1, 1]), v=np.array([1, 0, 0]), omega=case.k), - auto_where=target)(queue) + auto_where=target)(actx) else: # point source return bind(places, get_sym_maxwell_point_source(mfie.kernel, j_sym, mfie.k), - auto_where=(source, target))(queue, j=src_j, k=case.k) + auto_where=(source, target))(actx, j=src_j, k=case.k) # }}} @@ -294,7 +293,7 @@ def eval_inc_field_at(places, source=None, target=None): observation_mesh = case.get_observation_mesh(case.target_order) pre_scat_discr = Discretization( - cl_ctx, scat_mesh, + actx, scat_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) qbx = QBXLayerPotentialSource( pre_scat_discr, fine_order=4*case.target_order, @@ -306,7 +305,7 @@ def eval_inc_field_at(places, source=None, target=None): scat_discr = qbx.density_discr obs_discr = Discretization( - cl_ctx, observation_mesh, + actx, observation_mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) places.update({ @@ -324,7 +323,7 @@ def eval_inc_field_at(places, source=None, target=None): fplot = make_field_plotter_from_bbox( find_bounding_box(scat_discr.mesh), h=(0.05, 0.05, 0.3), extend_factor=0.3) - fplot_tgt = PointsTarget(cl.array.to_device(queue, fplot.points)) + fplot_tgt = PointsTarget(actx.from_numpy(fplot.points)) places.update({ "qbx_target_tol": qbx_tgt_tol, @@ -337,9 +336,9 @@ def eval_inc_field_at(places, source=None, target=None): # {{{ system solve - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) - pde_test_inc = EHField(vector_from_device(queue, + pde_test_inc = EHField(vector_from_device(actx.queue, eval_inc_field_at(places, target="patch_target"))) source_maxwell_resids = [ @@ -356,7 +355,7 @@ def eval_inc_field_at(places, source=None, target=None): bound_j_op = bind(places, mfie.j_operator(loc_sign, jt_sym)) j_rhs = bind(places, mfie.j_rhs(inc_xyz_sym.h))( - queue, inc_fld=inc_field_scat.field, **knl_kwargs) + actx, inc_fld=inc_field_scat.field, **knl_kwargs) gmres_settings = dict( tol=case.gmres_tol, @@ -365,24 +364,24 @@ def eval_inc_field_at(places, source=None, target=None): stall_iterations=50, no_progress_factor=1.05) from pytential.solve import gmres gmres_result = gmres( - bound_j_op.scipy_op(queue, "jt", np.complex128, **knl_kwargs), + bound_j_op.scipy_op(actx, "jt", np.complex128, **knl_kwargs), j_rhs, **gmres_settings) jt = gmres_result.solution bound_rho_op = bind(places, mfie.rho_operator(loc_sign, rho_sym)) rho_rhs = bind(places, mfie.rho_rhs(jt_sym, inc_xyz_sym.e))( - queue, jt=jt, inc_fld=inc_field_scat.field, **knl_kwargs) + actx, jt=jt, inc_fld=inc_field_scat.field, **knl_kwargs) gmres_result = gmres( - bound_rho_op.scipy_op(queue, "rho", np.complex128, **knl_kwargs), + bound_rho_op.scipy_op(actx, "rho", np.complex128, **knl_kwargs), rho_rhs, **gmres_settings) rho = gmres_result.solution # }}} - jxyz = bind(places, sym.tangential_to_xyz(jt_sym))(queue, jt=jt) + jxyz = bind(places, sym.tangential_to_xyz(jt_sym))(actx, jt=jt) # {{{ volume eval @@ -393,9 +392,9 @@ def eval_repr_at(tgt, source=None, target=None): source = sym.DEFAULT_SOURCE return bind(places, sym_repr, auto_where=(source, target))( - queue, jt=jt, rho=rho, **knl_kwargs) + actx, jt=jt, rho=rho, **knl_kwargs) - pde_test_repr = EHField(vector_from_device(queue, + pde_test_repr = EHField(vector_from_device(actx.queue, eval_repr_at(places, target="patch_target"))) maxwell_residuals = [ @@ -415,12 +414,12 @@ def eval_repr_at(tgt, source=None, target=None): pec_bc_e = sym.n_cross(bc_repr.e + inc_xyz_sym.e) pec_bc_h = sym.normal(3).as_vector().dot(bc_repr.h + inc_xyz_sym.h) - eh_bc_values = bind(places, sym.join_fields(pec_bc_e, pec_bc_h))( - queue, jt=jt, rho=rho, inc_fld=inc_field_scat.field, + eh_bc_values = bind(places, sym.flat_obj_array(pec_bc_e, pec_bc_h))( + actx, jt=jt, rho=rho, inc_fld=inc_field_scat.field, **knl_kwargs) def scat_norm(f): - return norm(density_discr, queue, f, p=np.inf) + return norm(density_discr, 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) @@ -435,11 +434,11 @@ def scat_norm(f): if visualize: from meshmode.discretization.visualization import make_visualizer - bdry_vis = make_visualizer(queue, scat_discr, case.target_order+3) + bdry_vis = make_visualizer(actx, scat_discr, case.target_order+3) bdry_normals = bind(places, sym.normal(3, dofdesc="scat_discr") - )(queue).as_vector(dtype=object) + )(actx).as_vector(dtype=object) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("j", jxyz), @@ -459,12 +458,13 @@ def scat_norm(f): fplot.write_vtk_file( "failed-targets.vts", [ - ("failed_targets", e.failed_target_flags.get(queue)) + ("failed_targets", actx.to_numpy( + actx.thaw(e.failed_target_flags))), ]) raise - fplot_repr = EHField(vector_from_device(queue, fplot_repr)) - fplot_inc = EHField(vector_from_device(queue, + fplot_repr = EHField(vector_from_device(actx.queue, fplot_repr)) + fplot_inc = EHField(vector_from_device(actx.queue, eval_inc_field_at(places, target="plot_targets"))) fplot.write_vtk_file( @@ -484,7 +484,7 @@ def scat_norm(f): obs_repr = EHField(eval_repr_at(places, target="obs_discr")) def obs_norm(f): - return norm(obs_discr, queue, f, p=np.inf) + return norm(obs_discr, f, p=np.inf) rel_err_e = (obs_norm(inc_field_obs.e + obs_repr.e) / obs_norm(inc_field_obs.e)) diff --git a/test/test_scalar_int_eq.py b/test/test_scalar_int_eq.py index bc1973957..82fca9ae1 100644 --- a/test/test_scalar_int_eq.py +++ b/test/test_scalar_int_eq.py @@ -32,6 +32,7 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh) @@ -436,7 +437,8 @@ def get_mesh(self, resolution, target_order): # {{{ test backend -def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): +def run_int_eq_test(actx: PyOpenCLArrayContext, + case, resolution, visualize=False): mesh = case.get_mesh(resolution, case.target_order) print("%d elements" % mesh.nelements) @@ -445,7 +447,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(case.target_order)) source_order = 4*case.target_order @@ -495,7 +497,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): point_sources = make_circular_point_group( mesh.ambient_dim, 10, test_src_geo_radius, func=lambda x: x**1.5) - point_source = PointPotentialSource(cl_ctx, point_sources) + point_source = PointPotentialSource(point_sources) from pytential.target import PointsTarget test_targets = make_circular_point_group( @@ -554,7 +556,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): places = GeometryCollection(places) if case.use_refinement: from pytential.qbx.refinement import refine_geometry_collection - places = refine_geometry_collection(queue, places, + places = refine_geometry_collection(places, **refiner_extra_kwargs) dd = sym.as_dofdesc(sym.DEFAULT_SOURCE).to_stage1() @@ -573,15 +575,15 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): discr = places.get_discretization(dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) print("quad stage-2 elements have %d nodes" - % discr.groups[0].nunit_nodes) + % discr.groups[0].nunit_dofs) # }}} if hasattr(case, "visualize_geometry") and case.visualize_geometry: bdry_normals = bind(places, sym.normal(mesh.ambient_dim))( - queue).as_vector(dtype=np.object) + actx).as_vector(dtype=np.object) - bdry_vis = make_visualizer(queue, density_discr, case.target_order) + bdry_vis = make_visualizer(actx, density_discr, case.target_order) bdry_vis.write_vtk_file("geometry.vtu", [ ("normals", bdry_normals) ]) @@ -591,19 +593,23 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): if visualize: if mesh.ambient_dim == 2: # show geometry, centers, normals - nodes_h = density_discr.nodes().get(queue=queue) - normal = bind(places, sym.normal(2))(queue).as_vector(np.object) + from meshmode.dof_array import thaw, flatten + nodes_h = [actx.to_numpy(axis) for axis in + flatten(thaw(actx, density_discr.nodes()))] + normal_h = [actx.to_numpy(axis) for axis in + flatten( + bind(places, sym.normal(2))(actx) + .as_vector(np.object))] pt.plot(nodes_h[0], nodes_h[1], "x-") - pt.quiver(nodes_h[0], nodes_h[1], - normal[0].get(queue), normal[1].get(queue)) + pt.quiver(nodes_h[0], nodes_h[1], normal_h[0], normal_h[1]) pt.gca().set_aspect("equal") pt.show() elif mesh.ambient_dim == 3: bdry_normals = bind(places, sym.normal(3))( - queue).as_vector(dtype=object) + actx).as_vector(dtype=object) - bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) + bdry_vis = make_visualizer(actx, density_discr, case.target_order+3) bdry_vis.write_vtk_file("pre-solve-source-%s.vtu" % resolution, [ ("bdry_normals", bdry_normals), ]) @@ -654,7 +660,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): source_charges = source_charges.astype(dtype) assert np.sum(source_charges) < 1e-15 - source_charges_dev = cl.array.to_device(queue, source_charges) + source_charges_dev = actx.from_numpy(source_charges) # }}} @@ -666,27 +672,27 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): test_direct = bind(places, pot_src, auto_where=("point_source", "point_target"))( - queue, charges=source_charges_dev, **concrete_knl_kwargs) + actx, charges=source_charges_dev, **concrete_knl_kwargs) if case.bc_type == "dirichlet": bc = bind(places, pot_src, auto_where=("point_source", sym.DEFAULT_TARGET))( - queue, charges=source_charges_dev, **concrete_knl_kwargs) + actx, charges=source_charges_dev, **concrete_knl_kwargs) elif case.bc_type == "neumann": bc = bind(places, sym.normal_derivative( qbx.ambient_dim, pot_src, dofdesc=sym.DEFAULT_TARGET), auto_where=("point_source", sym.DEFAULT_TARGET))( - queue, charges=source_charges_dev, **concrete_knl_kwargs) + actx, charges=source_charges_dev, **concrete_knl_kwargs) elif case.bc_type == "clamped_plate": bc_u = bind((point_source, density_discr), pot_src)( - queue, charges=source_charges_dev, **concrete_knl_kwargs) + actx, charges=source_charges_dev, **concrete_knl_kwargs) bc_du = bind( (point_source, density_discr), sym.normal_derivative( qbx.ambient_dim, pot_src, dofdesc=sym.DEFAULT_TARGET) - )(queue, charges=source_charges_dev, **concrete_knl_kwargs) + )(actx, charges=source_charges_dev, **concrete_knl_kwargs) bc = [bc_u, bc_du] # }}} @@ -694,22 +700,22 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): # {{{ solve bound_op = bind(places, op_u) - rhs = bind(places, op.prepare_rhs(op.get_density_var("bc")))(queue, bc=bc) + rhs = bind(places, op.prepare_rhs(op.get_density_var("bc")))(actx, bc=bc) try: from pytential.solve import gmres gmres_result = gmres( - bound_op.scipy_op(queue, "u", dtype, **concrete_knl_kwargs), + bound_op.scipy_op(actx, "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 = make_visualizer(actx, density_discr, case.target_order+3) bdry_vis.write_vtk_file("failed-targets-%s.vtu" % resolution, [ - ("failed_targets", e.failed_target_flags), + ("failed_targets", actx.thaw(e.failed_target_flags)), ]) raise @@ -724,7 +730,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): from sumpy.tools import build_matrix mat = build_matrix( bound_op.scipy_op( - queue, arg_name="u", dtype=dtype, **concrete_knl_kwargs)) + actx, arg_name="u", dtype=dtype, **concrete_knl_kwargs)) w, v = la.eig(mat) if visualize: pt.imshow(np.log10(1e-20+np.abs(mat))) @@ -740,7 +746,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): op.representation(op.get_density_var("u")), auto_where=(sym.DEFAULT_SOURCE, "point_target")) - test_via_bdry = bound_tgt_op(queue, u=weighted_u, **concrete_knl_kwargs) + test_via_bdry = bound_tgt_op(actx, u=weighted_u, **concrete_knl_kwargs) err = test_via_bdry - test_direct @@ -784,11 +790,11 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): #print(bound_t_deriv_op.code) grad_from_src = bound_grad_op( - queue, u=weighted_u, **concrete_knl_kwargs) + actx, u=weighted_u, **concrete_knl_kwargs) grad_ref = bind(places, sym.grad(mesh.ambient_dim, pot_src), - auto_where=("point_source", "point_target"))(queue, + auto_where=("point_source", "point_target"))(actx, charges=source_charges_dev, **concrete_knl_kwargs) @@ -812,14 +818,16 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): sym.tangential_derivative(qbx.ambient_dim, pot), qbx_forced_limit=loc_sign)) - tang_deriv_from_src = bound_t_deriv_op( - queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get() + from meshmode.dof_array import flatten + tang_deriv_from_src = actx.to_numpy( + flatten(bound_t_deriv_op( + actx, u=weighted_u, **concrete_knl_kwargs).as_scalar())) - tang_deriv_ref = bind(places, + tang_deriv_ref = actx.to_numpy(flatten(bind(places, sym.tangential_derivative(qbx.ambient_dim, pot_src), - auto_where=("point_source", sym.DEFAULT_TARGET))(queue, + auto_where=("point_source", sym.DEFAULT_TARGET))(actx, charges=source_charges_dev, - **concrete_knl_kwargs).as_scalar().get() + **concrete_knl_kwargs).as_scalar())) if visualize: pt.plot(tang_deriv_ref.real) @@ -841,12 +849,12 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): if visualize: bdry_normals = bind(places, sym.normal(qbx.ambient_dim))( - queue).as_vector(dtype=np.object) + actx).as_vector(dtype=np.object) sym_sqrt_j = sym.sqrt_jac_q_weight(density_discr.ambient_dim) - u = bind(places, op.get_density_var("u") / sym_sqrt_j)(queue, u=weighted_u) + u = bind(places, op.get_density_var("u") / sym_sqrt_j)(actx, u=weighted_u) - bdry_vis = make_visualizer(queue, density_discr, case.target_order+3) + bdry_vis = make_visualizer(actx, density_discr, case.target_order+3) bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [ ("u", u), ("bc", bc), @@ -857,32 +865,34 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): solved_pot = bind(places, op.representation(op.get_density_var("u")), auto_where=("qbx_target_tol", "plot_targets"))( - queue, u=weighted_u, k=case.k) + actx, u=weighted_u, k=getattr(case, "k", None)) except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( "failed-targets.vts", [ - ("failed_targets", e.failed_target_flags.get(queue)) + ("failed_targets", actx.to_numpy( + actx.thaw(e.failed_target_flags))) ]) raise - ones_density = density_discr.zeros(queue) - ones_density.fill(1) + ones_density = density_discr.zeros(actx) + 1 indicator = -sym.D(LaplaceKernel(qbx.ambient_dim), op.get_density_var("sigma"), qbx_forced_limit=None) indicator = bind(places, indicator, auto_where=("qbx_target_tol", "plot_targets"))( - queue, sigma=ones_density).get() - - solved_pot = solved_pot.get() + actx, sigma=ones_density) true_pot = bind(places, pot_src, auto_where=("point_source", "plot_targets"))( - queue, + actx, charges=source_charges_dev, - **concrete_knl_kwargs).get() + **concrete_knl_kwargs) + + solved_pot = actx.to_numpy(solved_pot) + true_pot = actx.to_numpy(true_pot) + indicator = actx.to_numpy(indicator) #fplot.show_scalar_in_mayavi(solved_pot.real, max_val=5) if case.prob_side == "scat": @@ -906,7 +916,7 @@ def run_int_eq_test(cl_ctx, queue, case, resolution, visualize=False): # }}} - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) return dict( h_max=h_max, rel_err_2=rel_err_2, @@ -943,6 +953,7 @@ def test_integral_equation(ctx_factory, case, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) if USE_SYMENGINE and case.fmm_backend is None: pytest.skip("https://gitlab.tiker.net/inducer/sumpy/issues/25") @@ -959,8 +970,7 @@ def test_integral_equation(ctx_factory, case, visualize=False): have_error_data = False for resolution in case.resolutions: - result = run_int_eq_test(cl_ctx, queue, case, resolution, - visualize=visualize) + result = run_int_eq_test(actx, case, resolution, visualize=visualize) if result["rel_err_2"] is not None: have_error_data = True diff --git a/test/test_stokes.py b/test/test_stokes.py index 19167efe1..5ac2ab47a 100644 --- a/test/test_stokes.py +++ b/test/test_stokes.py @@ -25,9 +25,9 @@ import numpy as np import pyopencl as cl -import pyopencl.clmath # noqa import pytest +from meshmode.array_context import PyOpenCLArrayContext from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory @@ -46,7 +46,8 @@ def run_exterior_stokes_2d(ctx_factory, nelements, mesh_order=4, target_order=4, qbx_order=4, - fmm_order=10, mu=1, circle_rad=1.5, visualize=False): + fmm_order=False, # FIXME: FMM is slower than direct eval + mu=1, circle_rad=1.5, visualize=False): # This program tests an exterior Stokes flow in 2D using the # compound representation given in Hsiao & Kress, @@ -57,6 +58,7 @@ def run_exterior_stokes_2d(ctx_factory, nelements, cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) ovsmp_target_order = 4*target_order @@ -68,8 +70,7 @@ def run_exterior_stokes_2d(ctx_factory, nelements, lambda t: circle_rad * ellipse(1, t), np.linspace(0, 1, nelements+1), target_order) - coarse_density_discr = Discretization( - cl_ctx, mesh, + coarse_density_discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource @@ -111,8 +112,8 @@ def outside_circle(test_points, radius): density_discr = places.get_discretization(sym.DEFAULT_SOURCE) - normal = bind(places, sym.normal(2).as_vector())(queue) - path_length = bind(places, sym.integral(2, 1, 1))(queue) + normal = bind(places, sym.normal(2).as_vector())(actx) + path_length = bind(places, sym.integral(2, 1, 1))(actx) # }}} @@ -150,47 +151,52 @@ def outside_circle(test_points, radius): def fund_soln(x, y, loc, strength): #with direction (1,0) for point source - r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) + r = actx.np.sqrt((x - loc[0])**2 + (y - loc[1])**2) scaling = strength/(4*np.pi*mu) - xcomp = (-cl.clmath.log(r) + (x - loc[0])**2/r**2) * scaling + xcomp = (-actx.np.log(r) + (x - loc[0])**2/r**2) * scaling ycomp = ((x - loc[0])*(y - loc[1])/r**2) * scaling return [xcomp, ycomp] def rotlet_soln(x, y, loc): - r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) + r = actx.np.sqrt((x - loc[0])**2 + (y - loc[1])**2) xcomp = -(y - loc[1])/r**2 ycomp = (x - loc[0])/r**2 return [xcomp, ycomp] def fund_and_rot_soln(x, y, loc, strength): #with direction (1,0) for point source - r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2) + r = actx.np.sqrt((x - loc[0])**2 + (y - loc[1])**2) scaling = strength/(4*np.pi*mu) xcomp = ( - (-cl.clmath.log(r) + (x - loc[0])**2/r**2) * scaling + (-actx.np.log(r) + (x - loc[0])**2/r**2) * scaling - (y - loc[1])*strength*0.125/r**2 + 3.3) ycomp = ( ((x - loc[0])*(y - loc[1])/r**2) * scaling + (x - loc[0])*strength*0.125/r**2 + 1.5) - return [xcomp, ycomp] + return make_obj_array([xcomp, ycomp]) - nodes = density_discr.nodes().with_queue(queue) + from meshmode.dof_array import unflatten, flatten, thaw + nodes = flatten(thaw(actx, density_discr.nodes())) fund_soln_loc = np.array([0.5, -0.2]) strength = 100. - bc = fund_and_rot_soln(nodes[0], nodes[1], fund_soln_loc, strength) + bc = unflatten(actx, density_discr, + fund_and_rot_soln(nodes[0], nodes[1], fund_soln_loc, strength)) omega_sym = sym.make_sym_vector("omega", dim) u_A_sym_bdry = stokeslet_obj.apply( # noqa: N806 omega_sym, mu_sym, qbx_forced_limit=1) - omega = [ - cl.array.to_device(queue, (strength/path_length)*np.ones(len(nodes[0]))), - cl.array.to_device(queue, np.zeros(len(nodes[0])))] + from pytential.utils import unflatten_from_numpy + omega = unflatten_from_numpy(actx, density_discr, make_obj_array([ + (strength/path_length)*np.ones(len(nodes[0])), + np.zeros(len(nodes[0])) + ])) + bvp_rhs = bind(places, - sym.make_sym_vector("bc", dim) + u_A_sym_bdry)(queue, + sym.make_sym_vector("bc", dim) + u_A_sym_bdry)(actx, bc=bc, mu=mu, omega=omega) gmres_result = gmres( - bound_op.scipy_op(queue, "sigma", np.float64, mu=mu, normal=normal), + bound_op.scipy_op(actx, "sigma", np.float64, mu=mu, normal=normal), bvp_rhs, x0=bvp_rhs, tol=1e-9, progress=True, @@ -203,7 +209,7 @@ def fund_and_rot_soln(x, y, loc, strength): sigma = gmres_result.solution sigma_int_val_sym = sym.make_sym_vector("sigma_int_val", 2) - int_val = bind(places, sym.integral(2, 1, sigma_sym))(queue, sigma=sigma) + int_val = bind(places, sym.integral(2, 1, sigma_sym))(actx, sigma=sigma) int_val = -int_val/(2 * np.pi) print("int_val = ", int_val) @@ -217,7 +223,7 @@ def fund_and_rot_soln(x, y, loc, strength): - u_A_sym_vol + sigma_int_val_sym) where = (sym.DEFAULT_SOURCE, "point_target") - vel = bind(places, representation_sym, auto_where=where)(queue, + vel = bind(places, representation_sym, auto_where=where)(actx, sigma=sigma, mu=mu, normal=normal, @@ -226,7 +232,7 @@ def fund_and_rot_soln(x, y, loc, strength): print("@@@@@@@@") plot_vel = bind(places, representation_sym, - auto_where=(sym.DEFAULT_SOURCE, "plot_target"))(queue, + auto_where=(sym.DEFAULT_SOURCE, "plot_target"))(actx, sigma=sigma, mu=mu, normal=normal, @@ -240,8 +246,10 @@ def get_obj_array(obj_array): ]) exact_soln = fund_and_rot_soln( - cl.array.to_device(queue, eval_points[0]), cl.array.to_device( - queue, eval_points[1]), fund_soln_loc, strength) + actx.from_numpy(eval_points[0]), + actx.from_numpy(eval_points[1]), + fund_soln_loc, + strength) vel = get_obj_array(vel) err = vel-get_obj_array(exact_soln) @@ -289,7 +297,7 @@ def get_obj_array(obj_array): # }}} - h_max = bind(places, sym.h_max(qbx.ambient_dim))(queue) + h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx) return h_max, l2_err diff --git a/test/test_symbolic.py b/test/test_symbolic.py index 37b86c12a..a510596cd 100644 --- a/test/test_symbolic.py +++ b/test/test_symbolic.py @@ -39,6 +39,7 @@ from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.array_context import PyOpenCLArrayContext from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, WobblyCircle, make_curve_mesh) @@ -51,27 +52,27 @@ # {{{ discretization getters -def get_ellipse_with_ref_mean_curvature(cl_ctx, nelements, aspect=1): +def get_ellipse_with_ref_mean_curvature(actx, nelements, aspect=1): order = 4 mesh = make_curve_mesh( partial(ellipse, aspect), np.linspace(0, 1, nelements+1), order) - discr = Discretization(cl_ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) - with cl.CommandQueue(cl_ctx) as queue: - nodes = discr.nodes().get(queue=queue) + from meshmode.dof_array import thaw + nodes = thaw(actx, discr.nodes()) a = 1 b = 1/aspect - t = np.arctan2(nodes[1] * aspect, nodes[0]) + t = actx.np.atan2(nodes[1] * aspect, nodes[0]) - return discr, a*b / ((a*np.sin(t))**2 + (b*np.cos(t))**2)**(3/2) + return discr, a*b / ((a*actx.np.sin(t))**2 + (b*actx.np.cos(t))**2)**(3/2) -def get_torus_with_ref_mean_curvature(cl_ctx, h): +def get_torus_with_ref_mean_curvature(actx, h): order = 4 r_minor = 1.0 r_major = 3.0 @@ -79,20 +80,21 @@ def get_torus_with_ref_mean_curvature(cl_ctx, h): from meshmode.mesh.generation import generate_torus mesh = generate_torus(r_major, r_minor, n_major=h, n_minor=h, order=order) - discr = Discretization(cl_ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) - with cl.CommandQueue(cl_ctx) as queue: - nodes = discr.nodes().get(queue=queue) + from meshmode.dof_array import thaw + nodes = thaw(actx, discr.nodes()) # copied from meshmode.mesh.generation.generate_torus a = r_major b = r_minor - u = np.arctan2(nodes[1], nodes[0]) - rvec = np.array([np.cos(u), np.sin(u), np.zeros_like(u)]) - rvec = np.sum(nodes * rvec, axis=0) - a - cosv = np.cos(np.arctan2(nodes[2], rvec)) + u = actx.np.atan2(nodes[1], nodes[0]) + from pytools.obj_array import flat_obj_array + rvec = flat_obj_array(actx.np.cos(u), actx.np.sin(u), 0*u) + rvec = sum(nodes * rvec) - a + cosv = actx.np.cos(actx.np.atan2(nodes[2], rvec)) return discr, (a + 2.0 * b * cosv) / (2 * b * (a + b * cosv)) @@ -115,19 +117,20 @@ def test_mean_curvature(ctx_factory, discr_name, resolutions, discr_and_ref_mean_curvature_getter, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) from pytools.convergence import EOCRecorder eoc = EOCRecorder() for r in resolutions: discr, ref_mean_curvature = \ - discr_and_ref_mean_curvature_getter(ctx, r) + discr_and_ref_mean_curvature_getter(actx, r) mean_curvature = bind( - discr, - sym.mean_curvature(discr.ambient_dim))(queue).get(queue) + discr, sym.mean_curvature(discr.ambient_dim))(actx) h = 1.0 / r - h_error = la.norm(mean_curvature - ref_mean_curvature, np.inf) + from meshmode.dof_array import flat_norm + h_error = flat_norm(mean_curvature - ref_mean_curvature, np.inf) eoc.add_data_point(h, h_error) print(eoc) @@ -142,13 +145,13 @@ def test_mean_curvature(ctx_factory, discr_name, resolutions, def test_tangential_onb(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.mesh.generation import generate_torus mesh = generate_torus(5, 2, order=3) discr = Discretization( - cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(3)) + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(3)) tob = sym.tangential_onb(mesh.ambient_dim) nvecs = tob.shape[1] @@ -157,8 +160,10 @@ def test_tangential_onb(ctx_factory): orth_check = bind(discr, sym.make_obj_array([ np.dot(tob[:, i], tob[:, j]) - (1 if i == j else 0) for i in range(nvecs) for j in range(nvecs)]) - )(queue) + )(actx) + from meshmode.dof_array import flatten + orth_check = flatten(orth_check) for i, orth_i in enumerate(orth_check): assert (cl.clmath.fabs(orth_i) < 1e-13).get().all() @@ -166,8 +171,9 @@ def test_tangential_onb(ctx_factory): orth_check = bind(discr, sym.make_obj_array([ np.dot(tob[:, i], sym.normal(mesh.ambient_dim).as_vector()) for i in range(nvecs)]) - )(queue) + )(actx) + orth_check = flatten(orth_check) for i, orth_i in enumerate(orth_check): assert (cl.clmath.fabs(orth_i) < 1e-13).get().all() @@ -227,6 +233,7 @@ def test_layer_potential_construction(lpot_class, ambient_dim=2): def test_interpolation(ctx_factory, name, source_discr_stage, target_granularity): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) nelements = 32 target_order = 7 @@ -245,7 +252,7 @@ def test_interpolation(ctx_factory, name, source_discr_stage, target_granularity mesh = make_curve_mesh(starfish, np.linspace(0.0, 1.0, nelements + 1), target_order) - discr = Discretization(ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource @@ -261,16 +268,22 @@ def test_interpolation(ctx_factory, name, source_discr_stage, target_granularity op_sym = sym.sin(sym.interp(from_dd, to_dd, sigma_sym)) bound_op = bind(places, op_sym, auto_where=where) - def nodes(stage): + from meshmode.dof_array import thaw, flatten, unflatten + + def discr_and_nodes(stage): density_discr = places.get_discretization(where.geometry, stage) - return density_discr.nodes().get(queue) + return density_discr, np.array([ + actx.to_numpy(flatten(axis)) + for axis in thaw(actx, density_discr.nodes())]) - target_nodes = nodes(sym.QBX_SOURCE_QUAD_STAGE2) - source_nodes = nodes(source_discr_stage) + _, target_nodes = discr_and_nodes(sym.QBX_SOURCE_QUAD_STAGE2) + source_discr, source_nodes = discr_and_nodes(source_discr_stage) - sigma_dev = cl.array.to_device(queue, la.norm(source_nodes, axis=0)) sigma_target = np.sin(la.norm(target_nodes, axis=0)) - sigma_target_interp = bound_op(queue, sigma=sigma_dev).get(queue) + sigma_dev = unflatten( + actx, source_discr, + actx.from_numpy(la.norm(source_nodes, axis=0))) + sigma_target_interp = actx.to_numpy(flatten(bound_op(actx, sigma=sigma_dev))) if name in ("default", "default_explicit", "stage2", "quad"): error = la.norm(sigma_target_interp - sigma_target) / la.norm(sigma_target) diff --git a/test/test_target_specific_qbx.py b/test/test_target_specific_qbx.py index 551bb38c5..440e74862 100644 --- a/test/test_target_specific_qbx.py +++ b/test/test_target_specific_qbx.py @@ -23,10 +23,10 @@ """ +from meshmode.array_context import PyOpenCLArrayContext import numpy as np import numpy.linalg as la # noqa import pyopencl as cl -import pyopencl.clmath as clmath import pytest from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) @@ -140,6 +140,7 @@ def test_target_specific_qbx(ctx_factory, op, helmholtz_k, qbx_order): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) target_order = 4 fmm_tol = 1e-3 @@ -152,7 +153,7 @@ def test_target_specific_qbx(ctx_factory, op, helmholtz_k, qbx_order): InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource pre_density_discr = Discretization( - cl_ctx, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder @@ -174,12 +175,13 @@ def test_target_specific_qbx(ctx_factory, op, helmholtz_k, qbx_order): from pytential.qbx.refinement import refine_geometry_collection places = GeometryCollection(places, auto_where="qbx") - places = refine_geometry_collection(queue, places, + places = refine_geometry_collection(places, kernel_length_scale=kernel_length_scale) density_discr = places.get_discretization("qbx") - nodes = density_discr.nodes().with_queue(queue) - u_dev = clmath.sin(nodes[0]) + from meshmode.dof_array import thaw + nodes = thaw(actx, density_discr.nodes()) + u_dev = actx.np.sin(nodes[0]) if helmholtz_k == 0: kernel = LaplaceKernel(3) @@ -201,11 +203,12 @@ def test_target_specific_qbx(ctx_factory, op, helmholtz_k, qbx_order): expr = op(kernel, u_sym, qbx_forced_limit=-1, **kernel_kwargs) + from meshmode.dof_array import flatten bound_op = bind(places, expr) - pot_ref = bound_op(queue, u=u_dev, k=helmholtz_k).get() + pot_ref = actx.to_numpy(flatten(bound_op(actx, u=u_dev, k=helmholtz_k))) bound_op = bind(places, expr, auto_where="qbx_target_specific") - pot_tsqbx = bound_op(queue, u=u_dev, k=helmholtz_k).get() + pot_tsqbx = actx.to_numpy(flatten(bound_op(actx, u=u_dev, k=helmholtz_k))) assert np.allclose(pot_tsqbx, pot_ref, atol=1e-13, rtol=1e-13) diff --git a/test/test_tools.py b/test/test_tools.py index d2f107848..89bfcd785 100644 --- a/test/test_tools.py +++ b/test/test_tools.py @@ -24,6 +24,7 @@ from functools import partial +from meshmode.array_context import PyOpenCLArrayContext import pytest import numpy as np @@ -63,6 +64,7 @@ def test_interpolatory_error_reporting(ctx_factory): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) h = 0.2 from meshmode.mesh.io import generate_gmsh, FileSource @@ -80,20 +82,19 @@ def test_interpolatory_error_reporting(ctx_factory): from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory - vol_discr = Discretization(ctx, mesh, + vol_discr = Discretization(actx, mesh, QuadratureSimplexGroupFactory(5)) - vol_x = vol_discr.nodes().with_queue(queue) + from meshmode.dof_array import thaw + vol_x = thaw(actx, vol_discr.nodes()) # }}} from pytential import integral - rhs = 1 + 0*vol_x[0] - - one = rhs.copy() - one.fill(1) - with pytest.raises(TypeError): - print("AREA", integral(vol_discr, queue, one), 0.25**2*np.pi) + one = 1 + 0*vol_x[0] + from meshmode.discretization import NoninterpolatoryElementGroupError + with pytest.raises(NoninterpolatoryElementGroupError): + print("AREA", integral(vol_discr, one), 0.25**2*np.pi) def test_geometry_collection_caching(ctx_factory): @@ -103,6 +104,7 @@ def test_geometry_collection_caching(ctx_factory): # the `nodes` on each `discr_stage`. ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) ndim = 2 nelements = 1024 @@ -128,7 +130,7 @@ def test_geometry_collection_caching(ctx_factory): mesh = affine_map(discrs[0].mesh, b=np.array([3 * k * radius, 0])) - discr = Discretization(ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) discrs.append(discr) @@ -161,7 +163,7 @@ def test_geometry_collection_caching(ctx_factory): discr = places._get_discr_from_cache(sources[k], discr_stage) dofdesc = sym.DOFDescriptor(sources[k], discr_stage=discr_stage) - bind(places, sym.nodes(ndim, dofdesc=dofdesc))(queue) + bind(places, sym.nodes(ndim, dofdesc=dofdesc))(actx) discr = places._get_discr_from_cache(sources[k], discr_stage) assert discr is not None diff --git a/test/too_slow_test_helmholtz.py b/test/too_slow_test_helmholtz.py deleted file mode 100644 index 9e9df21aa..000000000 --- a/test/too_slow_test_helmholtz.py +++ /dev/null @@ -1,455 +0,0 @@ -from __future__ import division, absolute_import, print_function - -__copyright__ = "Copyright (C) 2014 Shidong Jiang, Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -import numpy as np -import numpy.linalg as la -import pyopencl as cl -import pyopencl.array # noqa -import pyopencl.clmath # noqa - -import pytest - -from pytools.obj_array import make_obj_array - -from meshmode.discretization import Discretization -from meshmode.discretization.poly_element import \ - InterpolatoryQuadratureSimplexGroupFactory - -from six.moves import range - -from pytential import bind, sym, norm # noqa -from pytential.symbolic.pde.maxwell.waveguide import ( # noqa - DielectricSRep2DBoundaryOperator as SRep, - DielectricSDRep2DBoundaryOperator as SDRep) - - -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) - -import logging -logger = logging.getLogger(__name__) - - -def run_dielectric_test(cl_ctx, queue, nelements, qbx_order, - op_class, mode, - k0=3, k1=2.9, mesh_order=10, - bdry_quad_order=None, bdry_ovsmp_quad_order=None, - use_l2_weighting=False, - fmm_order=None, visualize=False): - - if fmm_order is None: - fmm_order = qbx_order * 2 - if bdry_quad_order is None: - bdry_quad_order = mesh_order - if bdry_ovsmp_quad_order is None: - bdry_ovsmp_quad_order = 4*bdry_quad_order - - # {{{ geometries - - from meshmode.mesh.generation import ellipse, make_curve_mesh - from functools import partial - mesh = make_curve_mesh( - partial(ellipse, 3), - np.linspace(0, 1, nelements+1), - mesh_order) - - density_discr = Discretization( - cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) - - logger.info("%d elements" % mesh.nelements) - - from pytential.qbx import QBXLayerPotentialSource - qbx = QBXLayerPotentialSource( - density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, - fmm_order=fmm_order - ) - - from pytential.target import PointsTarget - targets_0 = PointsTarget(make_obj_array(list(np.array([ - [3.2 + t, -4] - for t in [0, 0.5, 1] - ]).T.copy()))) - targets_1 = PointsTarget(make_obj_array(list(np.array([ - [-0.3 * t, -0.2 * t] - for t in [0, 0.5, 1] - ]).T.copy()))) - - if visualize: - low_order_qbx, _ = QBXLayerPotentialSource( - density_discr, - fine_order=bdry_ovsmp_quad_order, qbx_order=2, - fmm_order=3, - ) - - from sumpy.visualization import FieldPlotter - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=300) - targets_plot = PointsTarget(fplot.points) - - places = { - sym.DEFAULT_SOURCE: qbx, - sym.DEFAULT_TARGET: qbx.density_discr, - "targets0": targets_0, - "targets1": targets_1 - } - if visualize: - places.update({ - "qbx-low-order": low_order_qbx, - "targets-plot": targets_plot - }) - - from pytential import GeometryCollection - places = GeometryCollection(places) - - # }}} - - # from meshmode.discretization.visualization import make_visualizer - # bdry_vis = make_visualizer(queue, density_discr, 20) - - # {{{ solve bvp - - from sumpy.kernel import HelmholtzKernel, AxisTargetDerivative - kernel = HelmholtzKernel(2) - - beta = 2.5 - K0 = np.sqrt(k0**2-beta**2) # noqa - K1 = np.sqrt(k1**2-beta**2) # noqa - - pde_op = op_class( - mode, - k_vacuum=1, - domain_k_exprs=(k0, k1), - beta=beta, - interfaces=((0, 1, sym.DEFAULT_SOURCE),), - use_l2_weighting=use_l2_weighting) - - op_unknown_sym = pde_op.make_unknown("unknown") - - representation0_sym = pde_op.representation(op_unknown_sym, 0) - representation1_sym = pde_op.representation(op_unknown_sym, 1) - bound_pde_op = bind(places, pde_op.operator(op_unknown_sym)) - - e_factor = float(pde_op.ez_enabled) - h_factor = float(pde_op.hz_enabled) - - e_sources_0 = make_obj_array(list(np.array([ - [0.1, 0.2] - ]).T.copy())) - e_strengths_0 = np.array([1*e_factor]) - e_sources_1 = make_obj_array(list(np.array([ - [4, 4] - ]).T.copy())) - e_strengths_1 = np.array([1*e_factor]) - - h_sources_0 = make_obj_array(list(np.array([ - [0.2, 0.1] - ]).T.copy())) - h_strengths_0 = np.array([1*h_factor]) - h_sources_1 = make_obj_array(list(np.array([ - [4, 5] - ]).T.copy())) - h_strengths_1 = np.array([1*h_factor]) - - kernel_grad = [ - AxisTargetDerivative(i, kernel) for i in range(density_discr.ambient_dim)] - - from sumpy.p2p import P2P - pot_p2p = P2P(cl_ctx, [kernel], exclude_self=False) - pot_p2p_grad = P2P(cl_ctx, kernel_grad, exclude_self=False) - - normal = bind(places, sym.normal(qbx.ambient_dim))( - queue).as_vector(np.object) - tangent = bind(places, - sym.pseudoscalar(qbx.ambient_dim)/sym.area_element(qbx.ambient_dim))( - queue).as_vector(np.object) - - _, (E0,) = pot_p2p(queue, density_discr.nodes(), e_sources_0, [e_strengths_0], - out_host=False, k=K0) - _, (E1,) = pot_p2p(queue, density_discr.nodes(), e_sources_1, [e_strengths_1], - out_host=False, k=K1) - _, (grad0_E0, grad1_E0) = pot_p2p_grad( - queue, density_discr.nodes(), e_sources_0, [e_strengths_0], - out_host=False, k=K0) - _, (grad0_E1, grad1_E1) = pot_p2p_grad( - queue, density_discr.nodes(), e_sources_1, [e_strengths_1], - out_host=False, k=K1) - - _, (H0,) = pot_p2p(queue, density_discr.nodes(), h_sources_0, [h_strengths_0], - out_host=False, k=K0) - _, (H1,) = pot_p2p(queue, density_discr.nodes(), h_sources_1, [h_strengths_1], - out_host=False, k=K1) - _, (grad0_H0, grad1_H0) = pot_p2p_grad( - queue, density_discr.nodes(), h_sources_0, [h_strengths_0], - out_host=False, k=K0) - _, (grad0_H1, grad1_H1) = pot_p2p_grad( - queue, density_discr.nodes(), h_sources_1, [h_strengths_1], - out_host=False, k=K1) - - E0_dntarget = (grad0_E0*normal[0] + grad1_E0*normal[1]) # noqa - E1_dntarget = (grad0_E1*normal[0] + grad1_E1*normal[1]) # noqa - - H0_dntarget = (grad0_H0*normal[0] + grad1_H0*normal[1]) # noqa - H1_dntarget = (grad0_H1*normal[0] + grad1_H1*normal[1]) # noqa - - E0_dttarget = (grad0_E0*tangent[0] + grad1_E0*tangent[1]) # noqa - E1_dttarget = (grad0_E1*tangent[0] + grad1_E1*tangent[1]) # noqa - - H0_dttarget = (grad0_H0*tangent[0] + grad1_H0*tangent[1]) # noqa - H1_dttarget = (grad0_H1*tangent[0] + grad1_H1*tangent[1]) # noqa - - sqrt_w = bind(places, sym.sqrt_jac_q_weight(qbx.ambient_dim))(queue) - - bvp_rhs = np.zeros(len(pde_op.bcs), dtype=np.object) - for i_bc, terms in enumerate(pde_op.bcs): - for term in terms: - assert term.i_interface == 0 - if term.field_kind == pde_op.field_kind_e: - - if term.direction == pde_op.dir_none: - bvp_rhs[i_bc] += ( - term.coeff_outer * E0 - + term.coeff_inner * E1) - elif term.direction == pde_op.dir_normal: - bvp_rhs[i_bc] += ( - term.coeff_outer * E0_dntarget - + term.coeff_inner * E1_dntarget) - elif term.direction == pde_op.dir_tangential: - bvp_rhs[i_bc] += ( - term.coeff_outer * E0_dttarget - + term.coeff_inner * E1_dttarget) - else: - raise NotImplementedError("direction spec in RHS") - - elif term.field_kind == pde_op.field_kind_h: - if term.direction == pde_op.dir_none: - bvp_rhs[i_bc] += ( - term.coeff_outer * H0 - + term.coeff_inner * H1) - elif term.direction == pde_op.dir_normal: - bvp_rhs[i_bc] += ( - term.coeff_outer * H0_dntarget - + term.coeff_inner * H1_dntarget) - elif term.direction == pde_op.dir_tangential: - bvp_rhs[i_bc] += ( - term.coeff_outer * H0_dttarget - + term.coeff_inner * H1_dttarget) - else: - raise NotImplementedError("direction spec in RHS") - - if use_l2_weighting: - bvp_rhs[i_bc] *= sqrt_w - - scipy_op = bound_pde_op.scipy_op(queue, "unknown", - domains=[sym.DEFAULT_TARGET]*len(pde_op.bcs), K0=K0, K1=K1, - dtype=np.complex128) - - if mode == "tem" or op_class is SRep: - from sumpy.tools import vector_from_device, vector_to_device - from pytential.solve import lu - unknown = lu(scipy_op, vector_from_device(queue, bvp_rhs)) - unknown = vector_to_device(queue, unknown) - - else: - from pytential.solve import gmres - gmres_result = gmres(scipy_op, - bvp_rhs, tol=1e-14, progress=True, - hard_failure=True, stall_iterations=0) - - unknown = gmres_result.solution - - # }}} - - from sumpy.tools import vector_from_device - F0_tgt = bind(places, representation0_sym, - auto_where=(sym.DEFAULT_SOURCE, "targets0"))( - queue, unknown=unknown, K0=K0, K1=K1) - F0_tgt = vector_from_device(queue, F0_tgt) - - F1_tgt = bind(places, representation1_sym, - auto_where=(sym.DEFAULT_SOURCE, "targets1"))( - queue, unknown=unknown, K0=K0, K1=K1) - F1_tgt = vector_from_device(queue, F1_tgt) - - _, (E0_tgt_true,) = pot_p2p(queue, - targets_0.nodes(), e_sources_0, [e_strengths_0], - out_host=True, k=K0) - _, (E1_tgt_true,) = pot_p2p(queue, - targets_1.nodes(), e_sources_1, [e_strengths_1], - out_host=True, k=K1) - - _, (H0_tgt_true,) = pot_p2p(queue, - targets_0.nodes(), h_sources_0, [h_strengths_0], - out_host=True, k=K0) - _, (H1_tgt_true,) = pot_p2p(queue, - targets_1.nodes(), h_sources_1, [h_strengths_1], - out_host=True, k=K1) - - err_F0_total = 0 # noqa - err_F1_total = 0 # noqa - - i_field = 0 - - def vec_norm(ary): - return la.norm(ary.reshape(-1)) - - def field_kind_to_string(field_kind): - return {pde_op.field_kind_e: "E", pde_op.field_kind_h: "H"}[field_kind] - - for field_kind in pde_op.field_kinds: - if not pde_op.is_field_present(field_kind): - continue - - if field_kind == pde_op.field_kind_e: - F0_tgt_true = E0_tgt_true # noqa - F1_tgt_true = E1_tgt_true # noqa - elif field_kind == pde_op.field_kind_h: - F0_tgt_true = H0_tgt_true # noqa - F1_tgt_true = H1_tgt_true # noqa - else: - assert False - - abs_err_F0 = vec_norm(F0_tgt[i_field] - F0_tgt_true) # noqa - abs_err_F1 = vec_norm(F1_tgt[i_field] - F1_tgt_true) # noqa - - rel_err_F0 = abs_err_F0/vec_norm(F0_tgt_true) # noqa - rel_err_F1 = abs_err_F1/vec_norm(F1_tgt_true) # noqa - - err_F0_total = max(rel_err_F0, err_F0_total) # noqa - err_F1_total = max(rel_err_F1, err_F1_total) # noqa - - print("Abs Err %s0" % field_kind_to_string(field_kind), abs_err_F0) - print("Abs Err %s1" % field_kind_to_string(field_kind), abs_err_F1) - - print("Rel Err %s0" % field_kind_to_string(field_kind), rel_err_F0) - print("Rel Err %s1" % field_kind_to_string(field_kind), rel_err_F1) - - i_field += 1 - - if visualize: - fld0 = bind(places, representation0_sym, - auto_where=(sym.DEFAULT_SOURCE, "targets-plot"))( - queue, unknown=unknown, K0=K0) - fld1 = bind(places, representation1_sym, - auto_where=(sym.DEFAULT_SOURCE, "targets-plot"))( - queue, unknown=unknown, K1=K1) - - comp_fields = [] - i_field = 0 - for field_kind in pde_op.field_kinds: - if not pde_op.is_field_present(field_kind): - continue - - fld_str = field_kind_to_string(field_kind) - comp_fields.extend([ - ("%s_fld0" % fld_str, fld0[i_field].get()), - ("%s_fld1" % fld_str, fld1[i_field].get()), - ]) - - i_field += 0 - - from sumpy.kernel import LaplaceKernel - ones = (cl.array.empty(queue, (density_discr.nnodes,), dtype=np.float64) - .fill(1)) - ind_func = - bind(places, sym.D(LaplaceKernel(2), sym.var("u")), - auto_where=("qbx-low-order", "targets-plot"))( - queue, u=ones).get() - - _, (e_fld0_true,) = pot_p2p( - queue, fplot.points, e_sources_0, [e_strengths_0], - out_host=True, k=K0) - _, (e_fld1_true,) = pot_p2p( - queue, fplot.points, e_sources_1, [e_strengths_1], - out_host=True, k=K1) - _, (h_fld0_true,) = pot_p2p( - queue, fplot.points, h_sources_0, [h_strengths_0], - out_host=True, k=K0) - _, (h_fld1_true,) = pot_p2p( - queue, fplot.points, h_sources_1, [h_strengths_1], - out_host=True, k=K1) - - #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) - fplot.write_vtk_file( - "potential-n%d.vts" % nelements, - [ - ("e_fld0_true", e_fld0_true), - ("e_fld1_true", e_fld1_true), - ("h_fld0_true", h_fld0_true), - ("h_fld1_true", h_fld1_true), - ("ind", ind_func), - ] + comp_fields - ) - - return err_F0_total, err_F1_total - - -@pytest.mark.parametrize("qbx_order", [4]) -@pytest.mark.parametrize("op_class", [ - SRep, - SDRep, - ]) -@pytest.mark.parametrize("mode", [ - "te", - "tm", - "tem", - ]) -def test_dielectric(ctx_factory, qbx_order, op_class, mode, visualize=False): - cl_ctx = ctx_factory() - queue = cl.CommandQueue(cl_ctx) - - import logging - logging.basicConfig(level=logging.INFO) - - from pytools.convergence import EOCRecorder - eoc_rec = EOCRecorder() - - for nelements in [30, 50, 70]: - # prevent sympy cache 'splosion - from sympy.core.cache import clear_cache - clear_cache() - - errs = run_dielectric_test( - cl_ctx, queue, - nelements=nelements, qbx_order=qbx_order, - op_class=op_class, mode=mode, - visualize=visualize) - - eoc_rec.add_data_point(1/nelements, la.norm(list(errs), np.inf)) - - print(eoc_rec) - assert eoc_rec.order_estimate() > qbx_order - 0.5 - - -# 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: fdm=marker