diff --git a/doc/array.rst b/doc/array.rst new file mode 100644 index 0000000000000000000000000000000000000000..a722c4799ace3d3d6f4d10b38333b68404bbbcc0 --- /dev/null +++ b/doc/array.rst @@ -0,0 +1,9 @@ +DOF (Degree-of-Freedom) Arrays +============================== + +.. automodule:: meshmode.dof_array + +Array Contexts +============== + +.. automodule:: meshmode.array_context diff --git a/doc/index.rst b/doc/index.rst index 0beee875eddae0e9b6d2b4d81ebb2d864bd8798f..9caf6446ed33cfb3dcc7fa6d6d0f4782426efbbe 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -7,6 +7,7 @@ Contents: :maxdepth: 2 mesh + array discretization connection interop diff --git a/examples/plot-connectivity.py b/examples/plot-connectivity.py index 068983ca81672295af65cdf3c82112478edac1a5..48e6be76b7033c4a3f04f55c6958c7efd856f0e0 100644 --- a/examples/plot-connectivity.py +++ b/examples/plot-connectivity.py @@ -2,7 +2,8 @@ from __future__ import division import numpy as np # noqa import pyopencl as cl - +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw order = 4 @@ -10,6 +11,7 @@ order = 4 def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.mesh.generation import ( # noqa generate_icosphere, generate_icosahedron, @@ -23,13 +25,13 @@ def main(): PolynomialWarpAndBlendGroupFactory discr = Discretization( - cl_ctx, mesh, PolynomialWarpAndBlendGroupFactory(order)) + actx, mesh, PolynomialWarpAndBlendGroupFactory(order)) from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr, order) + vis = make_visualizer(actx, discr, order) vis.write_vtk_file("geometry.vtu", [ - ("f", discr.nodes()[0]), + ("f", thaw(actx, discr.nodes()[0])), ]) from meshmode.discretization.visualization import \ diff --git a/examples/simple-dg.py b/examples/simple-dg.py index 8fe6f70600c3230bca4240b3c0808bf853ea650b..b945687758f172194d859560bc1723064d56971b 100644 --- a/examples/simple-dg.py +++ b/examples/simple-dg.py @@ -27,14 +27,13 @@ import numpy as np import numpy.linalg as la # noqa import pyopencl as cl import pyopencl.array as cla # noqa -import pyopencl.clmath as clmath from pytools import memoize_method, memoize_in from pytools.obj_array import ( - join_fields, make_obj_array, - with_object_array_or_scalar, - is_obj_array) -import loopy as lp + flat_obj_array, make_obj_array, + obj_array_vectorize) from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa +from meshmode.dof_array import DOFArray, freeze, thaw +from meshmode.array_context import PyOpenCLArrayContext, make_loopy_program # Features lost vs. https://github.com/inducer/grudge: @@ -44,42 +43,39 @@ from meshmode.mesh import BTAG_ALL, BTAG_NONE # noqa # - distributed-memory -def with_queue(queue, ary): - return with_object_array_or_scalar( - lambda x: x.with_queue(queue), ary) - - -def without_queue(ary): - return with_queue(None, ary) - - # {{{ discretization -def parametrization_derivative(queue, discr): +def parametrization_derivative(actx, discr): + thawed_nodes = thaw(actx, discr.nodes()) + result = np.zeros((discr.ambient_dim, discr.dim), dtype=object) for iambient in range(discr.ambient_dim): for idim in range(discr.dim): result[iambient, idim] = discr.num_reference_derivative( - queue, (idim,), discr.nodes()[iambient]) + (idim,), thawed_nodes[iambient]) return result class DGDiscretization: - def __init__(self, cl_ctx, mesh, order): + def __init__(self, actx, mesh, order): self.order = order from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory self.group_factory = PolynomialWarpAndBlendGroupFactory(order=order) - self.volume_discr = Discretization(cl_ctx, mesh, self.group_factory) + self.volume_discr = Discretization(actx, mesh, self.group_factory) assert self.volume_discr.dim == 2 @property - def cl_context(self): - return self.volume_discr.cl_context + def _setup_actx(self): + return self.volume_discr._setup_actx + + @property + def array_context(self): + return self.volume_discr.array_context @property def dim(self): @@ -91,6 +87,7 @@ class DGDiscretization: def boundary_connection(self, boundary_tag): from meshmode.discretization.connection import make_face_restriction return make_face_restriction( + self.volume_discr._setup_actx, self.volume_discr, self.group_factory, boundary_tag=boundary_tag) @@ -100,6 +97,7 @@ class DGDiscretization: from meshmode.discretization.connection import ( make_face_restriction, FACE_RESTR_INTERIOR) return make_face_restriction( + self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_INTERIOR, @@ -110,13 +108,15 @@ class DGDiscretization: from meshmode.discretization.connection import \ make_opposite_face_connection - return make_opposite_face_connection(self.interior_faces_connection()) + return make_opposite_face_connection( + self._setup_actx, self.interior_faces_connection()) @memoize_method def all_faces_connection(self): from meshmode.discretization.connection import ( make_face_restriction, FACE_RESTR_ALL) return make_face_restriction( + self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_ALL, @@ -129,7 +129,7 @@ class DGDiscretization: faces_conn = self.get_connection("vol", where) return make_face_to_all_faces_embedding( - faces_conn, self.get_discr("all_faces")) + self._setup_actx, faces_conn, self.get_discr("all_faces")) def get_connection(self, src, tgt): src_tgt = (src, tgt) @@ -148,11 +148,13 @@ class DGDiscretization: raise ValueError(f"locations '{src}'->'{tgt}' not understood") def interp(self, src, tgt, vec): - if is_obj_array(vec): - return with_object_array_or_scalar( + if (isinstance(vec, np.ndarray) + and vec.dtype.char == "O" + and not isinstance(vec, DOFArray)): + return obj_array_vectorize( lambda el: self.interp(src, tgt, el), vec) - return self.get_connection(src, tgt)(vec.queue, vec) + return self.get_connection(src, tgt)(vec) def get_discr(self, where): if where == "vol": @@ -170,40 +172,35 @@ class DGDiscretization: @memoize_method def parametrization_derivative(self): - with cl.CommandQueue(self.cl_context) as queue: - return without_queue( - parametrization_derivative(queue, self.volume_discr)) + return freeze( + parametrization_derivative(self._setup_actx, self.volume_discr)) @memoize_method def vol_jacobian(self): - with cl.CommandQueue(self.cl_context) as queue: - [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) - return (a*d-b*c).with_queue(None) + [a, b], [c, d] = thaw(self._setup_actx, self.parametrization_derivative()) + return freeze(a*d-b*c) @memoize_method def inverse_parametrization_derivative(self): - with cl.CommandQueue(self.cl_context) as queue: - [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) + [a, b], [c, d] = thaw(self._setup_actx, self.parametrization_derivative()) - result = np.zeros((2, 2), dtype=object) - det = a*d-b*c - result[0, 0] = d/det - result[0, 1] = -b/det - result[1, 0] = -c/det - result[1, 1] = a/det + result = np.zeros((2, 2), dtype=object) + det = a*d-b*c + result[0, 0] = d/det + result[0, 1] = -b/det + result[1, 0] = -c/det + result[1, 1] = a/det - return without_queue(result) + return freeze(result) - def zeros(self, queue): - return self.volume_discr.zeros(queue) + def zeros(self, actx): + return self.volume_discr.zeros(actx) def grad(self, vec): ipder = self.inverse_parametrization_derivative() - queue = vec.queue dref = [ - self.volume_discr.num_reference_derivative( - queue, (idim,), vec).with_queue(queue) + self.volume_discr.num_reference_derivative((idim,), vec) for idim in range(self.volume_discr.dim)] return make_obj_array([ @@ -218,22 +215,18 @@ class DGDiscretization: def normal(self, where): bdry_discr = self.get_discr(where) - with cl.CommandQueue(self.cl_context) as queue: - ((a,), (b,)) = with_queue( - queue, parametrization_derivative(queue, bdry_discr)) + ((a,), (b,)) = parametrization_derivative(self._setup_actx, bdry_discr) - nrm = 1/(a**2+b**2)**0.5 - return without_queue(join_fields(b*nrm, -a*nrm)) + nrm = 1/(a**2+b**2)**0.5 + return freeze(flat_obj_array(b*nrm, -a*nrm)) @memoize_method def face_jacobian(self, where): bdry_discr = self.get_discr(where) - with cl.CommandQueue(self.cl_context) as queue: - ((a,), (b,)) = with_queue(queue, - parametrization_derivative(queue, bdry_discr)) + ((a,), (b,)) = parametrization_derivative(self._setup_actx, bdry_discr) - return ((a**2 + b**2)**0.5).with_queue(None) + return freeze((a**2 + b**2)**0.5) @memoize_method def get_inverse_mass_matrix(self, grp, dtype): @@ -242,37 +235,36 @@ class DGDiscretization: grp.basis(), grp.unit_nodes) - with cl.CommandQueue(self.cl_context) as queue: - return (cla.to_device(queue, matrix) - .with_queue(None)) + actx = self._setup_actx + return actx.freeze(actx.from_numpy(matrix)) def inverse_mass(self, vec): - if is_obj_array(vec): - return with_object_array_or_scalar( + if (isinstance(vec, np.ndarray) + and vec.dtype.char == "O" + and not isinstance(vec, DOFArray)): + return obj_array_vectorize( lambda el: self.inverse_mass(el), vec) @memoize_in(self, "elwise_linear_knl") def knl(): - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i<ndiscr_nodes_out and + return make_loopy_program( + """{[iel,idof,j]: + 0<=iel<nelements and + 0<=idof<ndiscr_nodes_out and 0<=j<ndiscr_nodes_in}""", - "result[k,i] = sum(j, mat[i, j] * vec[k, j])", - default_offset=lp.auto, name="diff") - - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + "result[iel,idof] = sum(j, mat[idof, j] * vec[iel, j])", + name="diff") discr = self.volume_discr - result = discr.empty(queue=vec.queue, dtype=vec.dtype) + result = discr.empty_like(vec) for grp in discr.groups: - matrix = self.get_inverse_mass_matrix(grp, vec.dtype) + matrix = self.get_inverse_mass_matrix(grp, vec.entry_dtype) - knl()(vec.queue, mat=matrix, result=grp.view(result), - vec=grp.view(vec)) + vec.array_context.call_loopy( + knl(), + mat=matrix, result=result[grp.index], vec=vec[grp.index]) return result/self.vol_jacobian() @@ -282,9 +274,9 @@ class DGDiscretization: assert afgrp.nelements == nfaces * volgrp.nelements matrix = np.empty( - (volgrp.nunit_nodes, + (volgrp.nunit_dofs, nfaces, - afgrp.nunit_nodes), + afgrp.nunit_dofs), dtype=dtype) from modepy.tools import UNIT_VERTICES @@ -297,34 +289,33 @@ class DGDiscretization: volgrp.order, face_vertices) - with cl.CommandQueue(self.cl_context) as queue: - return (cla.to_device(queue, matrix) - .with_queue(None)) + actx = self._setup_actx + return actx.freeze(actx.from_numpy(matrix)) def face_mass(self, vec): - if is_obj_array(vec): - return with_object_array_or_scalar( + if (isinstance(vec, np.ndarray) + and vec.dtype.char == "O" + and not isinstance(vec, DOFArray)): + return obj_array_vectorize( lambda el: self.face_mass(el), vec) @memoize_in(self, "face_mass_knl") def knl(): - knl = lp.make_kernel( - """{[k,i,f,j]: - 0<=k<nelements and + return make_loopy_program( + """{[iel,idof,f,j]: + 0<=iel<nelements and 0<=f<nfaces and - 0<=i<nvol_nodes and + 0<=idof<nvol_nodes and 0<=j<nface_nodes}""", - "result[k,i] = sum(f, sum(j, mat[i, f, j] * vec[f, k, j]))", - default_offset=lp.auto, name="face_mass") - - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + "result[iel,idof] = " + "sum(f, sum(j, mat[idof, f, j] * vec[f, iel, j]))", + name="face_mass") all_faces_conn = self.get_connection("vol", "all_faces") all_faces_discr = all_faces_conn.to_discr vol_discr = all_faces_conn.from_discr - result = vol_discr.empty(queue=vec.queue, dtype=vec.dtype) + result = vol_discr.empty_like(vec) fj = self.face_jacobian("all_faces") vec = vec*fj @@ -334,12 +325,12 @@ class DGDiscretization: for afgrp, volgrp in zip(all_faces_discr.groups, vol_discr.groups): nfaces = volgrp.mesh_el_group.nfaces - matrix = self.get_local_face_mass_matrix(afgrp, volgrp, vec.dtype) + matrix = self.get_local_face_mass_matrix(afgrp, volgrp, vec.entry_dtype) - input_view = afgrp.view(vec).reshape( - nfaces, volgrp.nelements, afgrp.nunit_nodes) - knl()(vec.queue, mat=matrix, result=volgrp.view(result), - vec=input_view) + vec.array_context.call_loopy(knl(), + mat=matrix, result=result[volgrp.index], + vec=vec[afgrp.index].reshape( + nfaces, volgrp.nelements, afgrp.nunit_dofs)) return result @@ -379,8 +370,8 @@ class TracePair: def interior_trace_pair(discr, vec): i = discr.interp("vol", "int_faces", vec) - e = with_object_array_or_scalar( - lambda el: discr.opposite_face_connection()(el.queue, el), + e = obj_array_vectorize( + lambda el: discr.opposite_face_connection()(el), i) return TracePair("int_faces", i, e) @@ -389,26 +380,26 @@ def interior_trace_pair(discr, vec): # {{{ wave equation bits -def wave_flux(discr, c, w_tpair): +def wave_flux(actx, discr, c, w_tpair): u = w_tpair[0] v = w_tpair[1:] - normal = with_queue(u.int.queue, discr.normal(w_tpair.where)) + normal = thaw(actx, discr.normal(w_tpair.where)) - flux_weak = join_fields( + flux_weak = flat_obj_array( np.dot(v.avg, normal), normal[0] * u.avg, normal[1] * u.avg) # upwind v_jump = np.dot(normal, v.int-v.ext) - flux_weak -= join_fields( + flux_weak -= flat_obj_array( 0.5*(u.int-u.ext), 0.5*normal[0]*v_jump, 0.5*normal[1]*v_jump, ) - flux_strong = join_fields( + flux_strong = flat_obj_array( np.dot(v.int, normal), u.int * normal[0], u.int * normal[1], @@ -417,26 +408,27 @@ def wave_flux(discr, c, w_tpair): return discr.interp(w_tpair.where, "all_faces", c*flux_strong) -def wave_operator(discr, c, w): +def wave_operator(actx, discr, c, w): u = w[0] v = w[1:] dir_u = discr.interp("vol", BTAG_ALL, u) dir_v = discr.interp("vol", BTAG_ALL, v) - dir_bval = join_fields(dir_u, dir_v) - dir_bc = join_fields(-dir_u, dir_v) + dir_bval = flat_obj_array(dir_u, dir_v) + dir_bc = flat_obj_array(-dir_u, dir_v) return ( - - join_fields( + - flat_obj_array( -c*discr.div(v), -c*discr.grad(u) ) + # noqa: W504 discr.inverse_mass( discr.face_mass( - wave_flux(discr, c=c, w_tpair=interior_trace_pair(discr, w)) - + wave_flux(discr, c=c, w_tpair=TracePair( - BTAG_ALL, dir_bval, dir_bc)) + wave_flux(actx, discr, c=c, + w_tpair=interior_trace_pair(discr, w)) + + wave_flux(actx, discr, c=c, + w_tpair=TracePair(BTAG_ALL, dir_bval, dir_bc)) )) ) @@ -452,20 +444,20 @@ def rk4_step(y, t, h, f): return y + h/6*(k1 + 2*k2 + 2*k3 + k4) -def bump(discr, queue, t=0): +def bump(actx, discr, t=0): source_center = np.array([0.0, 0.05]) source_width = 0.05 source_omega = 3 - nodes = discr.volume_discr.nodes().with_queue(queue) - center_dist = join_fields([ + nodes = thaw(actx, discr.volume_discr.nodes()) + center_dist = flat_obj_array([ nodes[0] - source_center[0], nodes[1] - source_center[1], ]) return ( np.cos(source_omega*t) - * clmath.exp( + * actx.np.exp( -np.dot(center_dist, center_dist) / source_width**2)) @@ -474,6 +466,8 @@ def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( @@ -488,18 +482,18 @@ def main(): print("%d elements" % mesh.nelements) - discr = DGDiscretization(cl_ctx, mesh, order=order) + discr = DGDiscretization(actx, mesh, order=order) - fields = join_fields( - bump(discr, queue), - [discr.zeros(queue) for i in range(discr.dim)] + fields = flat_obj_array( + bump(actx, discr), + [discr.zeros(actx) for i in range(discr.dim)] ) from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr.volume_discr, discr.order+3) + vis = make_visualizer(actx, discr.volume_discr, discr.order+3) def rhs(t, w): - return wave_operator(discr, c=1, w=w) + return wave_operator(actx, discr, c=1, w=w) t = 0 t_final = 3 @@ -508,7 +502,10 @@ def main(): fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: - print(istep, t, la.norm(fields[0].get())) + # FIXME: Maybe an integral function to go with the + # DOFArray would be nice? + assert len(fields[0]) == 1 + print(istep, t, la.norm(actx.to_numpy(fields[0][0]))) vis.write_vtk_file("fld-wave-min-%04d.vtu" % istep, [ ("u", fields[0]), diff --git a/meshmode/array_context.py b/meshmode/array_context.py new file mode 100644 index 0000000000000000000000000000000000000000..88d3ecf412f033833272d696de2e70c9b35266f6 --- /dev/null +++ b/meshmode/array_context.py @@ -0,0 +1,319 @@ +from __future__ import division + +__copyright__ = "Copyright (C) 2020 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 loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION +from pytools import memoize_method +from pytools.obj_array import obj_array_vectorized_n_args + +__doc__ = """ +.. autofunction:: make_loopy_program +.. autoclass:: ArrayContext +.. autoclass:: PyOpenCLArrayContext +""" + + +def make_loopy_program(domains, statements, kernel_data=["..."], + name="mm_actx_kernel"): + """Return a :class:`loopy.Program` suitable for use with + :meth:`ArrayContext.call_loopy`. + """ + return lp.make_kernel( + domains, + statements, + kernel_data=kernel_data, + options=lp.Options( + no_numpy=True, + return_dict=True), + default_offset=lp.auto, + name=name, + lang_version=MOST_RECENT_LANGUAGE_VERSION) + + +# {{{ ArrayContext + +class _BaseFakeNumpyNamespace: + def __init__(self, array_context): + self._array_context = array_context + + def __getattr__(self, name): + def f(*args): + actx = self._array_context + # FIXME: Maybe involve loopy type inference? + result = actx.empty(args[0].shape, args[0].dtype) + prg = actx._get_scalar_func_loopy_program( + name, nargs=len(args), naxes=len(args[0].shape)) + actx.call_loopy(prg, out=result, + **{"inp%d" % i: arg for i, arg in enumerate(args)}) + return result + + return obj_array_vectorized_n_args(f) + + +class ArrayContext: + """An interface that allows a :class:`Discretization` to create and interact with + arrays of degrees of freedom without fully specifying their types. + + .. automethod:: empty + .. automethod:: zeros + .. automethod:: empty_like + .. automethod:: zeros_like + .. automethod:: from_numpy + .. automethod:: to_numpy + .. automethod:: call_loopy + .. attribute:: np + + Provides access to a namespace that serves as a work-alike to + :mod:`numpy`. The actual level of functionality provided is up to the + individual array context implementation, however the functions and + objects available under this namespace must not behave differently + from :mod:`numpy`. + + As a baseline, special functions available through :mod:`loopy` + (e.g. ``sin``, ``exp``) are accessible through this interface. + + Callables accessible through this namespace vectorize over object + arrays, including :class:`meshmode.dof_array.DOFArray`. + + .. automethod:: freeze + .. automethod:: thaw + + .. versionadded:: 2020.2 + """ + + def __init__(self): + self.np = self._get_fake_numpy_namespace() + + def _get_fake_numpy_namespace(self): + return _BaseFakeNumpyNamespace(self) + + def empty(self, shape, dtype): + raise NotImplementedError + + def zeros(self, shape, dtype): + raise NotImplementedError + + def empty_like(self, ary): + return self.empty(shape=ary.shape, dtype=ary.dtype) + + def zeros_like(self, ary): + return self.zeros(shape=ary.shape, dtype=ary.dtype) + + def from_numpy(self, array: np.ndarray): + r""" + :returns: the :class:`numpy.ndarray` *array* converted to the + array context's array type. The returned array will be + :meth:`thaw`\ ed. + """ + raise NotImplementedError + + def to_numpy(self, array): + r""" + :returns: *array*, an array recognized by the context, converted + to a :class:`numpy.ndarray`. *array* must be + :meth:`thaw`\ ed. + """ + raise NotImplementedError + + def call_loopy(self, program, **kwargs): + """Execute the :mod:`loopy` program *program* on the arguments + *kwargs*. + + *program* is a :class:`loopy.LoopKernel` or :class:`loopy.Program`. + It is expected to not yet be transformed for execution speed. + It must have :class:`loopy.Options.return_dict` set. + + :return: a :class:`dict` of outputs from the program, each an + array understood by the context. + """ + raise NotImplementedError + + @memoize_method + def _get_scalar_func_loopy_program(self, name, nargs, naxes): + if name == "arctan2": + name = "atan2" + elif name == "atan2": + from warnings import warn + warn("'atan2' in ArrayContext.np is deprecated. Use 'arctan2', " + "as in numpy2. This will be disallowed in 2021.", + DeprecationWarning, stacklevel=3) + + from pymbolic import var + + var_names = ["i%d" % i for i in range(naxes)] + size_names = ["n%d" % i for i in range(naxes)] + subscript = tuple(var(vname) for vname in var_names) + from islpy import make_zero_and_vars + v = make_zero_and_vars(var_names, params=size_names) + domain = v[0].domain() + for vname, sname in zip(var_names, size_names): + domain = domain & v[0].le_set(v[vname]) & v[vname].lt_set(v[sname]) + + domain_bset, = domain.get_basic_sets() + + return make_loopy_program( + [domain_bset], + [ + lp.Assignment( + var("out")[subscript], + var(name)(*[ + var("inp%d" % i)[subscript] for i in range(nargs)])) + ], + name="actx_special_%s" % name) + + def freeze(self, array): + """Return a version of the context-defined array *array* that is + 'frozen', i.e. suitable for long-term storage and reuse. Frozen arrays + do not support arithmetic. For example, in the context of + :class:`~pyopencl.array.Array`, this might mean stripping the array + of an associated command queue, whereas in a lazily-evaluated context, + it might mean that the array is evaluated and stored. + + Freezing makes the array independent of this :class:`ArrayContext`; + it is permitted to :meth:`thaw` it in a different one, as long as that + context understands the array format. + """ + raise NotImplementedError + + def thaw(self, array): + """Take a 'frozen' array and return a new array representing the data in + *array* that is able to perform arithmetic and other operations, using + the execution resources of this context. In the context of + :class:`~pyopencl.array.Array`, this might mean that the array is + equipped with a command queue, whereas in a lazily-evaluated context, + it might mean that the returned array is a symbol bound to + the data in *array*. + + The returned array may not be used with other contexts while thawed. + """ + raise NotImplementedError + +# }}} + + +# {{{ PyOpenCLArrayContext + +class _PyOpenCLFakeNumpyNamespace(_BaseFakeNumpyNamespace): + def __getattr__(self, name): + if name in ["minimum", "maximum"]: + import pyopencl.array as cl_array + return obj_array_vectorized_n_args(getattr(cl_array, name)) + + return super().__getattr__(name) + + @obj_array_vectorized_n_args + def where(self, criterion, then, else_): + import pyopencl.array as cl_array + return cl_array.if_positive(criterion != 0, then, else_) + + +class PyOpenCLArrayContext(ArrayContext): + """ + A :class:`ArrayContext` that uses :class:`pyopencl.array.Array` instances + for DOF arrays. + + .. attribute:: context + + A :class:`pyopencl.Context`. + + .. attribute:: queue + + A :class:`pyopencl.CommandQueue`. + + .. attribute:: allocator + """ + + def __init__(self, queue, allocator=None): + super().__init__() + self.context = queue.context + self.queue = queue + self.allocator = allocator + + def _get_fake_numpy_namespace(self): + return _PyOpenCLFakeNumpyNamespace(self) + + # {{{ ArrayContext interface + + def empty(self, shape, dtype): + import pyopencl.array as cla + return cla.empty(self.queue, shape=shape, dtype=dtype, + allocator=self.allocator) + + def zeros(self, shape, dtype): + import pyopencl.array as cla + return cla.zeros(self.queue, shape=shape, dtype=dtype, + allocator=self.allocator) + + def from_numpy(self, np_array: np.ndarray): + import pyopencl.array as cla + return cla.to_device(self.queue, np_array, allocator=self.allocator) + + def to_numpy(self, array): + return array.get(queue=self.queue) + + def call_loopy(self, program, **kwargs): + program = self.transform_loopy_program(program) + assert program.options.return_dict + assert program.options.no_numpy + + evt, result = program(self.queue, **kwargs, allocator=self.allocator) + return result + + def freeze(self, array): + array.finish() + return array.with_queue(None) + + def thaw(self, array): + return array.with_queue(self.queue) + + # }}} + + @memoize_method + def transform_loopy_program(self, program): + # FIXME: This could be much smarter. + import loopy as lp + all_inames = program.all_inames() + + inner_iname = None + if "iel" not in all_inames and "i0" in all_inames: + outer_iname = "i0" + + if "i1" in all_inames: + inner_iname = "i1" + else: + outer_iname = "iel" + + if "idof" in all_inames: + inner_iname = "idof" + + if inner_iname is not None: + program = lp.split_iname(program, inner_iname, 16, inner_tag="l.0") + return lp.tag_inames(program, {outer_iname: "g.0"}) + +# }}} + + +# vim: foldmethod=marker diff --git a/meshmode/discretization/__init__.py b/meshmode/discretization/__init__.py index a3847f2c3ab6c262c390f3c805619d74c85ab401..d19e5b92e1f58bf21416e09e8e23b734f24db206 100644 --- a/meshmode/discretization/__init__.py +++ b/meshmode/discretization/__init__.py @@ -1,6 +1,4 @@ -from __future__ import division - -__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" +__copyright__ = "Copyright (C) 2013-2020 Andreas Kloeckner" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,12 +21,13 @@ THE SOFTWARE. """ import numpy as np -import loopy as lp -import pyopencl as cl -import pyopencl.array # noqa -from loopy.version import MOST_RECENT_LANGUAGE_VERSION -from pytools import memoize_method, memoize_in +from pytools import memoize_in, memoize_method +from pytools.obj_array import make_obj_array +from meshmode.array_context import ArrayContext, make_loopy_program + +# underscored because it shouldn't be imported from here. +from meshmode.dof_array import DOFArray as _DOFArray __doc__ = """ .. autoclass:: ElementGroupBase @@ -50,22 +49,21 @@ class ElementGroupBase(object): .. attribute :: mesh_el_group .. attribute :: order - .. attribute :: node_nr_base + .. attribute :: index .. autoattribute:: nelements - .. autoattribute:: nunit_nodes - .. autoattribute:: nnodes + .. autoattribute:: nunit_dofs + .. autoattribute:: ndofs .. autoattribute:: dim - .. automethod:: view .. method:: unit_nodes() - Returns a :class:`numpy.ndarray` of shape ``(dim, nunit_nodes)`` + Returns a :class:`numpy.ndarray` of shape ``(dim, nunit_dofs)`` of reference coordinates of interpolation nodes. .. method:: weights() - Returns an array of length :attr:`nunit_nodes` containing + Returns an array of length :attr:`nunit_dofs` containing quadrature weights. .. attribute:: is_affine @@ -75,14 +73,14 @@ class ElementGroupBase(object): :attr:`meshmode.mesh.MeshElementGroup.is_affine`. """ - def __init__(self, mesh_el_group, order, node_nr_base): + def __init__(self, mesh_el_group, order, index): """ :arg mesh_el_group: an instance of :class:`meshmode.mesh.MeshElementGroup` """ self.mesh_el_group = mesh_el_group self.order = order - self.node_nr_base = node_nr_base + self.index = index @property def is_affine(self): @@ -93,12 +91,18 @@ class ElementGroupBase(object): return self.mesh_el_group.nelements @property - def nunit_nodes(self): + def nunit_dofs(self): + """The number of (for now: nodal) degrees of freedom ("DOFs") associated with a + single element. + """ return self.unit_nodes.shape[-1] @property - def nnodes(self): - return self.nunit_nodes * self.nelements + def ndofs(self): + """The total number of (for now: nodal) degrees of freedom ("DOFs") associated with + the element group. + """ + return self.nunit_dofs * self.nelements @property def dim(self): @@ -113,27 +117,6 @@ class ElementGroupBase(object): grad_basis = basis diff_matrices = basis - def _nodes(self): - # Not cached, because the global nodes array is what counts. - # This is just used to build that. - - return np.tensordot( - self.mesh_el_group.nodes, - self._from_mesh_interp_matrix(), - (-1, -1)) - - def view(self, global_array): - """Return a view of *global_array* of shape ``(..., nelements, - nunit_nodes)`` where *global_array* is of shape ``(..., nnodes)``, - where *nnodes* is the global (per-discretization) node count. - """ - - return global_array[ - ..., self.node_nr_base:self.node_nr_base + self.nnodes] \ - .reshape( - global_array.shape[:-1] - + (self.nelements, self.nunit_nodes)) - # }}} @@ -195,13 +178,6 @@ class OrderBasedGroupFactory(ElementGroupFactory): class Discretization(object): """An unstructured composite discretization. - :param cl_ctx: A :class:`pyopencl.Context` - :param mesh: A :class:`meshmode.mesh.Mesh` over which the discretization is - built - :param group_factory: An :class:`ElementGroupFactory` - :param real_dtype: The :mod:`numpy` data type used for representing real - data, either :class:`numpy.float32` or :class:`numpy.float64` - .. attribute:: real_dtype .. attribute:: complex_dtype @@ -212,35 +188,44 @@ class Discretization(object): .. attribute:: ambient_dim - .. attribute :: nnodes + .. attribute:: ndofs .. attribute :: groups .. automethod:: empty - .. automethod:: zeros + .. automethod:: empty_like + .. automethod:: zeros_like - .. method:: nodes() - - shape: ``(ambient_dim, nnodes)`` + .. automethod:: nodes() - .. method:: num_reference_derivative(queue, ref_axes, vec) + .. automethod:: num_reference_derivative - .. method:: quad_weights(queue) - - shape: ``(nnodes)`` + .. automethod:: quad_weights """ - def __init__(self, cl_ctx, mesh, group_factory, real_dtype=np.float64): - self.cl_context = cl_ctx + def __init__(self, actx: ArrayContext, mesh, group_factory, + real_dtype=np.float64): + """ + :param actx: A :class:`ArrayContext` used to perform computation needed + during initial set-up of the mesh. + :param mesh: A :class:`meshmode.mesh.Mesh` over which the discretization is + built. + :param group_factory: An :class:`ElementGroupFactory`. + :param real_dtype: The :mod:`numpy` data type used for representing real + data, either :class:`numpy.float32` or :class:`numpy.float64`. + """ + + if not isinstance(actx, ArrayContext): + raise TypeError("'actx' must be an ArrayContext") self.mesh = mesh - self.nnodes = 0 - self.groups = [] + groups = [] for mg in mesh.groups: - ng = group_factory(mg, self.nnodes) - self.groups.append(ng) - self.nnodes += ng.nnodes + ng = group_factory(mg, len(groups)) + groups.append(ng) + + self.groups = groups self.real_dtype = np.dtype(real_dtype) self.complex_dtype = np.dtype({ @@ -248,6 +233,8 @@ class Discretization(object): np.float64: np.complex128 }[self.real_dtype.type]) + self._setup_actx = actx + @property def dim(self): return self.mesh.dim @@ -256,13 +243,11 @@ class Discretization(object): def ambient_dim(self): return self.mesh.ambient_dim - def empty(self, queue=None, dtype=None, extra_dims=None, allocator=None): - """Return an empty DOF vector. + @property + def ndofs(self): + return sum(grp.ndofs for grp in self.groups) - :arg dtype: type special value 'c' will result in a - vector of dtype :attr:`self.complex_dtype`. If - *None* (the default), a real vector will be returned. - """ + def _new_array(self, actx, creation_func, dtype=None): if dtype is None: dtype = self.real_dtype elif dtype == "c": @@ -270,45 +255,55 @@ class Discretization(object): else: dtype = np.dtype(dtype) - if queue is None: - first_arg = self.cl_context - else: - first_arg = queue + return _DOFArray.from_list(actx, [ + creation_func(shape=(grp.nelements, grp.nunit_dofs), dtype=dtype) + for grp in self.groups]) - shape = (self.nnodes,) - if extra_dims is not None: - shape = extra_dims + shape + def empty(self, actx: ArrayContext, dtype=None): + """Return an empty :class:`~meshmode.dof_array.DOFArray`. - return cl.array.empty(first_arg, shape, dtype=dtype, allocator=allocator) + :arg dtype: type special value 'c' will result in a + vector of dtype :attr:`self.complex_dtype`. If + *None* (the default), a real vector will be returned. + """ + if not isinstance(actx, ArrayContext): + raise TypeError("'actx' must be an ArrayContext, not '%s'" + % type(actx).__name__) + + return self._new_array(actx, actx.empty, dtype=dtype) + + def zeros(self, actx: ArrayContext, dtype=None): + """Return a zero-initialized :class:`~meshmode.dof_array.DOFArray`. - def zeros(self, queue, dtype=None, extra_dims=None, allocator=None): - return self.empty(queue, dtype=dtype, extra_dims=extra_dims, - allocator=allocator).fill(0) + :arg dtype: type special value 'c' will result in a + vector of dtype :attr:`self.complex_dtype`. If + *None* (the default), a real vector will be returned. + """ + if not isinstance(actx, ArrayContext): + raise TypeError("'actx' must be an ArrayContext, not '%s'" + % type(actx).__name__) - def num_reference_derivative(self, queue, ref_axes, vec): - @memoize_in(self, "reference_derivative_knl") - def knl(): - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i,j<ndiscr_nodes}""", - "result[k,i] = sum(j, diff_mat[i, j] * vec[k, j])", - default_offset=lp.auto, name="diff", - lang_version=MOST_RECENT_LANGUAGE_VERSION) + return self._new_array(actx, actx.zeros, dtype=dtype) - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + def empty_like(self, array: _DOFArray): + return self.empty(array.array_context, dtype=array.entry_dtype) - result = self.empty(dtype=vec.dtype) + def zeros_like(self, array: _DOFArray): + return self.zeros(array.array_context, dtype=array.entry_dtype) - # Check that we don't differentiate vectors belonging to other - # discretizations. - assert vec.shape == (self.nnodes,) + def num_reference_derivative(self, ref_axes, vec): + actx = vec.array_context - for grp in self.groups: - if grp.nelements == 0: - continue + @memoize_in(actx, (Discretization, "reference_derivative_prg")) + def prg(): + return make_loopy_program( + """{[iel,idof,j]: + 0<=iel<nelements and + 0<=idof,j<nunit_dofs}""", + "result[iel,idof] = sum(j, diff_mat[idof, j] * vec[iel, j])", + name="diff") + def get_mat(grp): mat = None for ref_axis in ref_axes: next_mat = grp.diff_matrices()[ref_axis] @@ -317,68 +312,68 @@ class Discretization(object): else: mat = np.dot(next_mat, mat) - knl()(queue, diff_mat=mat, result=grp.view(result), vec=grp.view(vec)) - - return result - - def quad_weights(self, queue): - @memoize_in(self, "quad_weights_knl") - def knl(): - knl = lp.make_kernel( - "{[k,i]: 0<=k<nelements and 0<=i<ndiscr_nodes}", - "result[k,i] = weights[i]", - name="quad_weights", - default_offset=lp.auto, - lang_version=MOST_RECENT_LANGUAGE_VERSION) + return mat - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + return _DOFArray.from_list(actx, [ + actx.call_loopy( + prg(), diff_mat=actx.from_numpy(get_mat(grp)), vec=vec[grp.index] + )["result"] + for grp in self.groups]) - result = self.empty(dtype=self.real_dtype) - for grp in self.groups: - if grp.nelements == 0: - continue - - knl()(queue, result=grp.view(result), weights=grp.weights) - return result + @memoize_method + def quad_weights(self): + """:returns: A :class:`~meshmode.dof_array.DOFArray` with quadrature weights. + """ + actx = self._setup_actx + + @memoize_in(actx, (Discretization, "quad_weights_prg")) + def prg(): + return make_loopy_program( + "{[iel,idof]: 0<=iel<nelements and 0<=idof<nunit_dofs}", + "result[iel,idof] = weights[idof]", + name="quad_weights") + + return _DOFArray.from_list(None, [ + actx.freeze( + actx.call_loopy( + prg(), + weights=actx.from_numpy(grp.weights), + nelements=grp.nelements, + )["result"]) + for grp in self.groups]) @memoize_method def nodes(self): - @memoize_in(self, "nodes_knl") - def knl(): - knl = lp.make_kernel( - """{[d,k,i,j]: - 0<=d<dims and - 0<=k<nelements and - 0<=i<ndiscr_nodes and + r""" + :returns: object array of shape ``(ambient_dim,)`` containing + :class:`~meshmode.dof_array.DOFArray`\ s of node coordinates. + """ + + actx = self._setup_actx + + @memoize_in(actx, (Discretization, "nodes_prg")) + def prg(): + return make_loopy_program( + """{[iel,idof,j]: + 0<=iel<nelements and + 0<=idof<ndiscr_nodes and 0<=j<nmesh_nodes}""", """ - result[d, k, i] = \ - sum(j, resampling_mat[i, j] * nodes[d, k, j]) + result[iel, idof] = \ + sum(j, resampling_mat[idof, j] * nodes[iel, j]) """, - name="nodes", - default_offset=lp.auto, - lang_version=MOST_RECENT_LANGUAGE_VERSION) - - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - knl = lp.tag_inames(knl, dict(k="g.0")) - knl = lp.tag_array_axes(knl, "result", - "stride:auto,stride:auto,stride:auto") - return knl - - result = self.empty(dtype=self.real_dtype, extra_dims=(self.ambient_dim,)) - - with cl.CommandQueue(self.cl_context) as queue: - for grp in self.groups: - if grp.nelements == 0: - continue - - meg = grp.mesh_el_group - knl()(queue, - resampling_mat=grp.from_mesh_interp_matrix(), - result=grp.view(result), nodes=meg.nodes) - - return result - + name="nodes") + + return make_obj_array([ + _DOFArray.from_list(None, [ + actx.freeze( + actx.call_loopy( + prg(), + resampling_mat=actx.from_numpy( + grp.from_mesh_interp_matrix()), + nodes=actx.from_numpy(grp.mesh_el_group.nodes[iaxis]) + )["result"]) + for grp in self.groups]) + for iaxis in range(self.ambient_dim)]) # vim: fdm=marker diff --git a/meshmode/discretization/connection/__init__.py b/meshmode/discretization/connection/__init__.py index 5942c7e2e1c7fc04dacd0a5309faac621bc27e9e..cf812738bbce21fa7c508399d9a4cdc9cc4e806b 100644 --- a/meshmode/discretization/connection/__init__.py +++ b/meshmode/discretization/connection/__init__.py @@ -25,9 +25,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import pyopencl as cl -import pyopencl.array # noqa - from meshmode.discretization.connection.direct import ( InterpolationBatch, DiscretizationConnectionElementGroup, @@ -38,6 +35,7 @@ from meshmode.discretization.connection.chained import \ from meshmode.discretization.connection.projection import \ L2ProjectionInverseDiscretizationConnection +from meshmode.array_context import ArrayContext from meshmode.discretization.connection.same_mesh import \ make_same_mesh_connection from meshmode.discretization.connection.face import ( @@ -120,27 +118,26 @@ Implementation details # {{{ check connection -def check_connection(connection): +def check_connection(actx: ArrayContext, connection: DirectDiscretizationConnection): from_discr = connection.from_discr to_discr = connection.to_discr assert len(connection.groups) == len(to_discr.groups) - with cl.CommandQueue(to_discr.cl_context) as queue: - for cgrp, tgrp in zip(connection.groups, to_discr.groups): - for batch in cgrp.batches: - fgrp = from_discr.groups[batch.from_group_index] - - from_element_indices = batch.from_element_indices.get(queue) - to_element_indices = batch.to_element_indices.get(queue) + for cgrp, tgrp in zip(connection.groups, to_discr.groups): + for batch in cgrp.batches: + fgrp = from_discr.groups[batch.from_group_index] - assert (0 <= from_element_indices).all() - assert (0 <= to_element_indices).all() - assert (from_element_indices < fgrp.nelements).all() - assert (to_element_indices < tgrp.nelements).all() - if batch.to_element_face is not None: - assert 0 <= batch.to_element_face < fgrp.mesh_el_group.nfaces + from_element_indices = actx.to_numpy( + actx.thaw(batch.from_element_indices)) + to_element_indices = actx.to_numpy(actx.thaw(batch.to_element_indices)) + assert (0 <= from_element_indices).all() + assert (0 <= to_element_indices).all() + assert (from_element_indices < fgrp.nelements).all() + assert (to_element_indices < tgrp.nelements).all() + if batch.to_element_face is not None: + assert 0 <= batch.to_element_face < fgrp.mesh_el_group.nfaces # }}} diff --git a/meshmode/discretization/connection/chained.py b/meshmode/discretization/connection/chained.py index bd1da1f4221f079ad081bba4d86c10742969f2bf..53e57e2ea02fbc9395244f10cd07f23752bbbeca 100644 --- a/meshmode/discretization/connection/chained.py +++ b/meshmode/discretization/connection/chained.py @@ -23,8 +23,6 @@ THE SOFTWARE. """ import numpy as np -import pyopencl as cl -import pyopencl.array # noqa from pytools import Record @@ -65,9 +63,9 @@ class ChainedDiscretizationConnection(DiscretizationConnection): self.connections = connections - def __call__(self, queue, vec): + def __call__(self, vec): for cnx in self.connections: - vec = cnx(queue, vec) + vec = cnx(vec) return vec @@ -86,13 +84,13 @@ def _iterbatches(groups): yield (igrp, ibatch), (grp, batch) -def _build_element_lookup_table(queue, conn): +def _build_element_lookup_table(actx, conn): el_table = [np.full(g.nelements, -1, dtype=np.int) for g in conn.to_discr.groups] for (igrp, _), (_, batch) in _iterbatches(conn.groups): - el_table[igrp][batch.to_element_indices.get(queue)] = \ - batch.from_element_indices.get(queue) + el_table[igrp][actx.to_numpy(batch.to_element_indices)] = \ + actx.to_numpy(batch.from_element_indices) return el_table @@ -146,12 +144,12 @@ def _build_new_group_table(from_conn, to_conn): return grp_to_grp, batch_info -def _build_batches(queue, from_bins, to_bins, batch): +def _build_batches(actx, from_bins, to_bins, batch): from meshmode.discretization.connection.direct import \ InterpolationBatch def to_device(x): - return cl.array.to_device(queue, np.asarray(x)) + return actx.freeze(actx.from_numpy(np.asarray(x))) for ibatch, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins)): yield InterpolationBatch( @@ -162,7 +160,7 @@ def _build_batches(queue, from_bins, to_bins, batch): to_element_face=batch[ibatch].to_element_face) -def flatten_chained_connection(queue, connection): +def flatten_chained_connection(actx, connection): """Collapse a connection into a direct connection. If the given connection is already a @@ -189,7 +187,7 @@ def flatten_chained_connection(queue, connection): If a large number of connections is chained, the number of groups and batches can become very large. - :arg queue: An instance of :class:`pyopencl.CommandQueue`. + :arg actx: An instance of :class:`meshmode.array_contex.ArrayContext`. :arg connection: An instance of :class:`~meshmode.discretization.connection.DiscretizationConnection`. :return: An instance of @@ -204,19 +202,19 @@ def flatten_chained_connection(queue, connection): return connection if not connection.connections: - return make_same_mesh_connection(connection.to_discr, + return make_same_mesh_connection(actx, connection.to_discr, connection.from_discr) # recursively build direct connections connections = connection.connections direct_connections = [] for conn in connections: - direct_connections.append(flatten_chained_connection(queue, conn)) + direct_connections.append(flatten_chained_connection(actx, conn)) # merge all the direct connections from_conn = direct_connections[0] for to_conn in direct_connections[1:]: - el_table = _build_element_lookup_table(queue, from_conn) + el_table = _build_element_lookup_table(actx, from_conn) grp_to_grp, batch_info = _build_new_group_table(from_conn, to_conn) # distribute the indices to new groups and batches @@ -224,13 +222,13 @@ def flatten_chained_connection(queue, connection): to_bins = [[np.empty(0, dtype=np.int) for _ in g] for g in batch_info] for (igrp, ibatch), (_, from_batch) in _iterbatches(from_conn.groups): - from_to_element_indices = from_batch.to_element_indices.get(queue) + from_to_element_indices = actx.to_numpy(from_batch.to_element_indices) for (jgrp, jbatch), (_, to_batch) in _iterbatches(to_conn.groups): igrp_new, ibatch_new = grp_to_grp[igrp, ibatch, jgrp, jbatch] - jfrom = to_batch.from_element_indices.get(queue) - jto = to_batch.to_element_indices.get(queue) + jfrom = actx.to_numpy(to_batch.from_element_indices) + jto = actx.to_numpy(to_batch.to_element_indices) mask = np.isin(jfrom, from_to_element_indices) from_bins[igrp_new][ibatch_new] = \ @@ -244,7 +242,7 @@ def flatten_chained_connection(queue, connection): groups = [] for igrp, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins)): groups.append(DiscretizationConnectionElementGroup( - list(_build_batches(queue, from_bin, to_bin, + list(_build_batches(actx, from_bin, to_bin, batch_info[igrp])))) from_conn = DirectDiscretizationConnection( @@ -260,7 +258,7 @@ def flatten_chained_connection(queue, connection): # {{{ build chained resample matrix -def make_full_resample_matrix(queue, connection): +def make_full_resample_matrix(actx, connection): """Build a dense matrix representing the discretization connection. This is based on @@ -273,7 +271,7 @@ def make_full_resample_matrix(queue, connection): This method will be very slow, both in terms of speed and memory usage, and should only be used for testing or if absolutely necessary. - :arg queue: a :class:`pyopencl.CommandQueue`. + :arg actx: a :class:`meshmode.array_context.ArrayContext`. :arg connection: a :class:`~meshmode.discretization.connection.DiscretizationConnection`. :return: a :class:`pyopencl.array.Array` of shape @@ -281,21 +279,22 @@ def make_full_resample_matrix(queue, connection): """ if hasattr(connection, "full_resample_matrix"): - return connection.full_resample_matrix(queue) + return connection.full_resample_matrix(actx) if not hasattr(connection, 'connections'): raise TypeError('connection is not chained') if not connection.connections: result = np.eye(connection.to_discr.nnodes) - return cl.array.to_device(queue, result) + return actx.from_numpy(result) - acc = make_full_resample_matrix(queue, connection.connections[0]).get(queue) + acc = actx.to_numpy( + make_full_resample_matrix(actx, connection.connections[0])) for conn in connection.connections[1:]: - resampler = make_full_resample_matrix(queue, conn).get(queue) - acc = resampler.dot(acc) + resampler = actx.to_numpy(make_full_resample_matrix(actx, conn)) + acc = resampler @ acc - return cl.array.to_device(queue, acc) + return actx.from_numpy(acc) # }}} diff --git a/meshmode/discretization/connection/direct.py b/meshmode/discretization/connection/direct.py index 20f5cdb94023b15e42d713dffd538986d3bc823c..0fb957b8e92584519a665b277da0d14730a8d81f 100644 --- a/meshmode/discretization/connection/direct.py +++ b/meshmode/discretization/connection/direct.py @@ -25,11 +25,10 @@ THE SOFTWARE. from six.moves import range, zip import numpy as np -import pyopencl as cl -import pyopencl.array # noqa -from loopy.version import MOST_RECENT_LANGUAGE_VERSION -from pytools import memoize_method, memoize_in +import loopy as lp +from pytools import memoize_in, keyed_memoize_method +from meshmode.array_context import ArrayContext, make_loopy_program # {{{ interpolation batch @@ -134,12 +133,6 @@ class DiscretizationConnection(object): .. automethod:: __call__ """ def __init__(self, from_discr, to_discr, is_surjective): - if from_discr.cl_context != to_discr.cl_context: - raise ValueError("from_discr and to_discr must live in the " - "same OpenCL context") - - self.cl_context = from_discr.cl_context - if from_discr.mesh.vertex_id_dtype != to_discr.mesh.vertex_id_dtype: raise ValueError("from_discr and to_discr must agree on the " "vertex_id_dtype") @@ -153,7 +146,7 @@ class DiscretizationConnection(object): self.is_surjective = is_surjective - def __call__(self, queue, vec): + def __call__(self, ary): raise NotImplementedError() @@ -188,8 +181,9 @@ class DirectDiscretizationConnection(DiscretizationConnection): self.groups = groups - @memoize_method - def _resample_matrix(self, to_group_index, ibatch_index): + @keyed_memoize_method(key=lambda actx, to_group_index, ibatch_index: + (to_group_index, ibatch_index)) + def _resample_matrix(self, actx: ArrayContext, to_group_index, ibatch_index): import modepy as mp ibatch = self.groups[to_group_index].batches[ibatch_index] from_grp = self.from_discr.groups[ibatch.from_group_index] @@ -214,11 +208,12 @@ class DirectDiscretizationConnection(DiscretizationConnection): from_grp.basis(), ibatch.result_unit_nodes, from_grp.unit_nodes) - with cl.CommandQueue(self.cl_context) as queue: - return cl.array.to_device(queue, result).with_queue(None) + return actx.freeze(actx.from_numpy(result)) - @memoize_method - def _resample_point_pick_indices(self, to_group_index, ibatch_index, + @keyed_memoize_method(lambda actx, to_group_index, ibatch_index, + tol_multiplier=None: (to_group_index, ibatch_index, tol_multiplier)) + def _resample_point_pick_indices(self, actx: ArrayContext, + to_group_index, ibatch_index, tol_multiplier=None): """If :meth:`_resample_matrix` *R* is a row subset of a permutation matrix *P*, return the index subset I so that, loosely, ``x[I] == R @ x``. @@ -227,9 +222,8 @@ class DirectDiscretizationConnection(DiscretizationConnection): :class:`pyopencl.array.Array` containing the index subset. """ - with cl.CommandQueue(self.cl_context) as queue: - mat = self._resample_matrix(to_group_index, ibatch_index).get( - queue=queue) + mat = actx.to_numpy(actx.thaw( + self._resample_matrix(actx, to_group_index, ibatch_index))) nrows, ncols = mat.shape result = np.zeros(nrows, dtype=self.to_discr.mesh.element_id_dtype) @@ -251,30 +245,34 @@ class DirectDiscretizationConnection(DiscretizationConnection): one_index, = one_indices result[irow] = one_index - with cl.CommandQueue(self.cl_context) as queue: - return cl.array.to_device(queue, result).with_queue(None) + return actx.freeze(actx.from_numpy(result)) - def full_resample_matrix(self, queue): + def full_resample_matrix(self, actx): """Build a dense matrix representing this discretization connection. .. warning:: On average, this will be exceedingly expensive (:math:`O(N^2)` in the number *N* of discretization points) in terms of memory usage - and thus not what you'd typically want. + and thus not what you'd typically want, other than maybe for + testing. + + .. note:: + + This function assumes a flattened DOF array, as produced by + :class:`~meshmode.dof_array.flatten`. """ - @memoize_in(self, "oversample_mat_knl") + @memoize_in(actx, (DirectDiscretizationConnection, "oversample_mat_knl")) def knl(): - import loopy as lp - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i<n_to_nodes and + return make_loopy_program( + """{[iel, idof, j]: + 0<=iel<nelements and + 0<=idof<n_to_nodes and 0<=j<n_from_nodes}""", - "result[itgt_base + to_element_indices[k]*n_to_nodes + i, \ - isrc_base + from_element_indices[k]*n_from_nodes + j] \ - = resample_mat[i, j]", + "result[itgt_base + to_element_indices[iel]*n_to_nodes + idof, \ + isrc_base + from_element_indices[iel]*n_from_nodes + j] \ + = resample_mat[idof, j]", [ lp.GlobalArg("result", None, shape="nnodes_tgt, nnodes_src", @@ -283,78 +281,87 @@ class DirectDiscretizationConnection(DiscretizationConnection): lp.ValueArg("nnodes_tgt,nnodes_src", np.int32), "...", ], - name="oversample_mat", - lang_version=MOST_RECENT_LANGUAGE_VERSION) + name="oversample_mat") - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + to_discr_ndofs = sum(grp.nelements*grp.nunit_dofs + for grp in self.to_discr.groups) + from_discr_ndofs = sum(grp.nelements*grp.nunit_dofs + for grp in self.from_discr.groups) - result = cl.array.zeros( - queue, - (self.to_discr.nnodes, self.from_discr.nnodes), + result = actx.zeros( + (to_discr_ndofs, from_discr_ndofs), dtype=self.to_discr.real_dtype) + from_group_sizes = [ + grp.nelements*grp.nunit_dofs + for grp in self.from_discr.groups] + from_group_starts = np.cumsum([0] + from_group_sizes) + + tgt_node_nr_base = 0 for i_tgrp, (tgrp, cgrp) in enumerate( zip(self.to_discr.groups, self.groups)): for i_batch, batch in enumerate(cgrp.batches): if not len(batch.from_element_indices): continue - sgrp = self.from_discr.groups[batch.from_group_index] + actx.call_loopy(knl(), + resample_mat=self._resample_matrix(actx, i_tgrp, i_batch), + result=result, + itgt_base=tgt_node_nr_base, + isrc_base=from_group_starts[batch.from_group_index], + from_element_indices=batch.from_element_indices, + to_element_indices=batch.to_element_indices) - knl()(queue, - resample_mat=self._resample_matrix(i_tgrp, i_batch), - result=result, - itgt_base=tgrp.node_nr_base, - isrc_base=sgrp.node_nr_base, - from_element_indices=batch.from_element_indices, - to_element_indices=batch.to_element_indices) + tgt_node_nr_base += tgrp.nelements*tgrp.nunit_dofs return result - def __call__(self, queue, vec): - @memoize_in(self, "resample_by_mat_knl") + def __call__(self, ary): + from meshmode.dof_array import DOFArray + if not isinstance(ary, DOFArray): + raise TypeError("non-array passed to discretization connection") + + actx = ary.array_context + + @memoize_in(actx, (DirectDiscretizationConnection, "resample_by_mat_knl")) def mat_knl(): - import loopy as lp - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i<n_to_nodes and + knl = make_loopy_program( + """{[iel, idof, j]: + 0<=iel<nelements and + 0<=idof<n_to_nodes and 0<=j<n_from_nodes}""", - "result[to_element_indices[k], i] \ - = sum(j, resample_mat[i, j] \ - * vec[from_element_indices[k], j])", + "result[to_element_indices[iel], idof] \ + = sum(j, resample_mat[idof, j] \ + * ary[from_element_indices[iel], j])", [ lp.GlobalArg("result", None, shape="nelements_result, n_to_nodes", offset=lp.auto), - lp.GlobalArg("vec", None, + lp.GlobalArg("ary", None, shape="nelements_vec, n_from_nodes", offset=lp.auto), lp.ValueArg("nelements_result", np.int32), lp.ValueArg("nelements_vec", np.int32), "...", ], - name="resample_by_mat", - lang_version=MOST_RECENT_LANGUAGE_VERSION) + name="resample_by_mat") - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) + return knl - @memoize_in(self, "resample_by_picking_knl") + @memoize_in(actx, + (DirectDiscretizationConnection, "resample_by_picking_knl")) def pick_knl(): - import loopy as lp - knl = lp.make_kernel( - """{[k,i,j]: - 0<=k<nelements and - 0<=i<n_to_nodes}""", - "result[to_element_indices[k], i] \ - = vec[from_element_indices[k], pick_list[i]]", + knl = make_loopy_program( + """{[iel, idof, j]: + 0<=iel<nelements and + 0<=idof<n_to_nodes}""", + "result[to_element_indices[iel], idof] \ + = ary[from_element_indices[iel], pick_list[idof]]", [ lp.GlobalArg("result", None, shape="nelements_result, n_to_nodes", offset=lp.auto), - lp.GlobalArg("vec", None, + lp.GlobalArg("ary", None, shape="nelements_vec, n_from_nodes", offset=lp.auto), lp.ValueArg("nelements_result", np.int32), @@ -362,45 +369,41 @@ class DirectDiscretizationConnection(DiscretizationConnection): lp.ValueArg("n_from_nodes", np.int32), "...", ], - name="resample_by_picking", - lang_version=MOST_RECENT_LANGUAGE_VERSION) + name="resample_by_picking") - knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") - return lp.tag_inames(knl, dict(k="g.0")) - - if not isinstance(vec, cl.array.Array): - raise TypeError("non-array passed to discretization connection") + return knl if self.is_surjective: - result = self.to_discr.empty(queue, dtype=vec.dtype) + result = self.to_discr.empty(actx, dtype=ary.entry_dtype) else: - result = self.to_discr.zeros(queue, dtype=vec.dtype) + result = self.to_discr.zeros(actx, dtype=ary.entry_dtype) - if vec.shape != (self.from_discr.nnodes,): + if ary.shape != (len(self.from_discr.groups),): raise ValueError("invalid shape of incoming resampling data") for i_tgrp, (tgrp, cgrp) in enumerate( zip(self.to_discr.groups, self.groups)): for i_batch, batch in enumerate(cgrp.batches): - sgrp = self.from_discr.groups[batch.from_group_index] - if not len(batch.from_element_indices): continue point_pick_indices = self._resample_point_pick_indices( - i_tgrp, i_batch) + actx, i_tgrp, i_batch) if point_pick_indices is None: - mat_knl()(queue, - resample_mat=self._resample_matrix(i_tgrp, i_batch), - result=tgrp.view(result), vec=sgrp.view(vec), + actx.call_loopy(mat_knl(), + resample_mat=self._resample_matrix( + actx, i_tgrp, i_batch), + result=result[i_tgrp], + ary=ary[batch.from_group_index], from_element_indices=batch.from_element_indices, to_element_indices=batch.to_element_indices) else: - pick_knl()(queue, + actx.call_loopy(pick_knl(), pick_list=point_pick_indices, - result=tgrp.view(result), vec=sgrp.view(vec), + result=result[i_tgrp], + ary=ary[batch.from_group_index], from_element_indices=batch.from_element_indices, to_element_indices=batch.to_element_indices) diff --git a/meshmode/discretization/connection/face.py b/meshmode/discretization/connection/face.py index f45a95c1b72fe742505c90535aa783c6ae5fb971..bac1ccc9e59ff82716fbd4e87213db1d48798e2d 100644 --- a/meshmode/discretization/connection/face.py +++ b/meshmode/discretization/connection/face.py @@ -28,8 +28,6 @@ from six.moves import range, zip from pytools import Record import numpy as np -import pyopencl as cl -import pyopencl.array # noqa import modepy as mp import logging @@ -63,7 +61,7 @@ class _ConnectionBatchData(Record): pass -def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data, +def _build_boundary_connection(actx, vol_discr, bdry_discr, connection_data, per_face_groups): from meshmode.discretization.connection.direct import ( InterpolationBatch, @@ -87,14 +85,10 @@ def _build_boundary_connection(queue, vol_discr, bdry_discr, connection_data, batches.append( InterpolationBatch( from_group_index=igrp, - from_element_indices=cl.array.to_device( - queue, - data.group_source_element_indices) - .with_queue(None), - to_element_indices=cl.array.to_device( - queue, - data.group_target_element_indices) - .with_queue(None), + from_element_indices=actx.freeze(actx.from_numpy( + data.group_source_element_indices)), + to_element_indices=actx.freeze(actx.from_numpy( + data.group_target_element_indices)), result_unit_nodes=result_unit_nodes, to_element_face=face_id )) @@ -160,7 +154,7 @@ def _get_face_vertices(mesh, boundary_tag): # }}} -def make_face_restriction(discr, group_factory, boundary_tag, +def make_face_restriction(actx, discr, group_factory, boundary_tag, per_face_groups=False): """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces @@ -373,12 +367,11 @@ def make_face_restriction(discr, group_factory, boundary_tag, from meshmode.discretization import Discretization bdry_discr = Discretization( - discr.cl_context, bdry_mesh, group_factory) + actx, bdry_mesh, group_factory) - with cl.CommandQueue(discr.cl_context) as queue: - connection = _build_boundary_connection( - queue, discr, bdry_discr, connection_data, - per_face_groups) + connection = _build_boundary_connection( + actx, discr, bdry_discr, connection_data, + per_face_groups) logger.info("building face restriction: done") @@ -389,7 +382,7 @@ def make_face_restriction(discr, group_factory, boundary_tag, # {{{ face -> all_faces connection -def make_face_to_all_faces_embedding(faces_connection, all_faces_discr, +def make_face_to_all_faces_embedding(actx, faces_connection, all_faces_discr, from_discr=None): """Return a :class:`meshmode.discretization.connection.DiscretizationConnection` @@ -437,61 +430,59 @@ def make_face_to_all_faces_embedding(faces_connection, all_faces_discr, i_faces_grp = 0 - with cl.CommandQueue(vol_discr.cl_context) as queue: - groups = [] - for ivol_grp, vol_grp in enumerate(vol_discr.groups): - batches = [] + groups = [] + for ivol_grp, vol_grp in enumerate(vol_discr.groups): + batches = [] - nfaces = vol_grp.mesh_el_group.nfaces - for iface in range(nfaces): - all_faces_grp = all_faces_discr.groups[i_faces_grp] + nfaces = vol_grp.mesh_el_group.nfaces + for iface in range(nfaces): + all_faces_grp = all_faces_discr.groups[i_faces_grp] - if per_face_groups: - assert len(faces_connection.groups[i_faces_grp].batches) == 1 - else: - assert (len(faces_connection.groups[i_faces_grp].batches) - == nfaces) + if per_face_groups: + assert len(faces_connection.groups[i_faces_grp].batches) == 1 + else: + assert (len(faces_connection.groups[i_faces_grp].batches) + == nfaces) - assert np.array_equal( - from_discr.groups[i_faces_grp].unit_nodes, - all_faces_grp.unit_nodes) + assert np.array_equal( + from_discr.groups[i_faces_grp].unit_nodes, + all_faces_grp.unit_nodes) - # {{{ find src_batch + # {{{ find src_batch - src_batches = faces_connection.groups[i_faces_grp].batches - if per_face_groups: - src_batch, = src_batches - else: - src_batch = src_batches[iface] - del src_batches + src_batches = faces_connection.groups[i_faces_grp].batches + if per_face_groups: + src_batch, = src_batches + else: + src_batch = src_batches[iface] + del src_batches - # }}} + # }}} - if per_face_groups: - to_element_indices = src_batch.from_element_indices - else: - assert all_faces_grp.nelements == nfaces * vol_grp.nelements - - to_element_indices = ( - vol_grp.nelements*iface - + src_batch.from_element_indices.with_queue(queue) - ).with_queue(None) - - batches.append( - InterpolationBatch( - from_group_index=i_faces_grp, - from_element_indices=src_batch.to_element_indices, - to_element_indices=to_element_indices, - result_unit_nodes=all_faces_grp.unit_nodes, - to_element_face=None)) - - is_last_face = iface + 1 == nfaces - if per_face_groups or is_last_face: - groups.append( - DiscretizationConnectionElementGroup(batches=batches)) - batches = [] - - i_faces_grp += 1 + if per_face_groups: + to_element_indices = src_batch.from_element_indices + else: + assert all_faces_grp.nelements == nfaces * vol_grp.nelements + + to_element_indices = actx.freeze( + vol_grp.nelements*iface + + actx.thaw(src_batch.from_element_indices)) + + batches.append( + InterpolationBatch( + from_group_index=i_faces_grp, + from_element_indices=src_batch.to_element_indices, + to_element_indices=to_element_indices, + result_unit_nodes=all_faces_grp.unit_nodes, + to_element_face=None)) + + is_last_face = iface + 1 == nfaces + if per_face_groups or is_last_face: + groups.append( + DiscretizationConnectionElementGroup(batches=batches)) + batches = [] + + i_faces_grp += 1 return DirectDiscretizationConnection( from_discr, diff --git a/meshmode/discretization/connection/opposite_face.py b/meshmode/discretization/connection/opposite_face.py index e084525ba9a6eb5001548f432c25a0a8f861173e..8f83cd4e7e1031a7e319132397899e0cc38ff5cc 100644 --- a/meshmode/discretization/connection/opposite_face.py +++ b/meshmode/discretization/connection/opposite_face.py @@ -26,43 +26,45 @@ from six.moves import range import numpy as np import numpy.linalg as la -import pyopencl as cl -import pyopencl.array # noqa import logging logger = logging.getLogger(__name__) +def freeze_from_numpy(actx, array): + return actx.freeze(actx.from_numpy(array)) + + +def thaw_to_numpy(actx, array): + return actx.to_numpy(actx.thaw(array)) + + # {{{ _make_cross_face_batches -def _make_cross_face_batches(queue, +def _make_cross_face_batches(actx, tgt_bdry_discr, src_bdry_discr, i_tgt_grp, i_src_grp, tgt_bdry_element_indices, src_bdry_element_indices): - def to_dev(ary): - return cl.array.to_device(queue, ary, array_queue=None) from meshmode.discretization.connection.direct import InterpolationBatch if tgt_bdry_discr.dim == 0: yield InterpolationBatch( from_group_index=i_src_grp, - from_element_indices=to_dev(src_bdry_element_indices), - to_element_indices=to_dev(tgt_bdry_element_indices), + from_element_indices=freeze_from_numpy(actx, src_bdry_element_indices), + to_element_indices=freeze_from_numpy(actx, tgt_bdry_element_indices), result_unit_nodes=src_bdry_discr.groups[i_src_grp].unit_nodes, to_element_face=None) return - # FIXME: This should view-then-transfer - # (but PyOpenCL doesn't do non-contiguous transfers for now). - tgt_bdry_nodes = (tgt_bdry_discr.groups[i_tgt_grp] - .view(tgt_bdry_discr.nodes().get(queue=queue)) - [:, tgt_bdry_element_indices]) + tgt_bdry_nodes = np.array([ + thaw_to_numpy(actx, ary[i_tgt_grp])[tgt_bdry_element_indices] + for ary in tgt_bdry_discr.nodes() + ]) - # FIXME: This should view-then-transfer - # (but PyOpenCL doesn't do non-contiguous transfers for now). - src_bdry_nodes = (src_bdry_discr.groups[i_src_grp] - .view(src_bdry_discr.nodes().get(queue=queue)) - [:, src_bdry_element_indices]) + src_bdry_nodes = np.array([ + thaw_to_numpy(actx, ary[i_src_grp])[src_bdry_element_indices] + for ary in src_bdry_discr.nodes() + ]) tol = 1e4 * np.finfo(tgt_bdry_nodes.dtype).eps @@ -255,8 +257,10 @@ def _make_cross_face_batches(queue, from meshmode.discretization.connection.direct import InterpolationBatch yield InterpolationBatch( from_group_index=i_src_grp, - from_element_indices=to_dev(src_bdry_element_indices[close_els]), - to_element_indices=to_dev(tgt_bdry_element_indices[close_els]), + from_element_indices=freeze_from_numpy( + actx, src_bdry_element_indices[close_els]), + to_element_indices=freeze_from_numpy( + actx, tgt_bdry_element_indices[close_els]), result_unit_nodes=template_unit_nodes, to_element_face=None) @@ -276,7 +280,7 @@ def _find_ibatch_for_face(vbc_tgt_grp_batches, iface): return vbc_tgt_grp_face_batch -def _make_bdry_el_lookup_table(queue, connection, igrp): +def _make_bdry_el_lookup_table(actx, connection, igrp): """Given a volume-to-boundary connection as *connection*, return a table of shape ``(from_nelements, nfaces)`` to look up the element number of the boundary element for that face. @@ -289,9 +293,9 @@ def _make_bdry_el_lookup_table(queue, connection, igrp): iel_lookup.fill(-1) for ibatch, batch in enumerate(connection.groups[igrp].batches): - from_element_indices = batch.from_element_indices.get(queue=queue) + from_element_indices = thaw_to_numpy(actx, batch.from_element_indices) iel_lookup[from_element_indices, batch.to_element_face] = \ - batch.to_element_indices.get(queue=queue) + thaw_to_numpy(actx, batch.to_element_indices) return iel_lookup @@ -300,7 +304,7 @@ def _make_bdry_el_lookup_table(queue, connection, igrp): # {{{ make_opposite_face_connection -def make_opposite_face_connection(volume_to_bdry_conn): +def make_opposite_face_connection(actx, volume_to_bdry_conn): """Given a boundary restriction connection *volume_to_bdry_conn*, return a :class:`DirectDiscretizationConnection` that performs data exchange across opposite faces. @@ -323,103 +327,104 @@ def make_opposite_face_connection(volume_to_bdry_conn): # One interpolation batch in this connection corresponds # to a key (i_tgt_grp,) (i_src_grp, i_face_tgt,) - with cl.CommandQueue(vol_discr.cl_context) as queue: - # a list of batches for each group - groups = [[] for i_tgt_grp in range(ngrps)] + # a list of batches for each group + groups = [[] for i_tgt_grp in range(ngrps)] - for i_src_grp in range(ngrps): - src_grp_el_lookup = _make_bdry_el_lookup_table( - queue, volume_to_bdry_conn, i_src_grp) + for i_src_grp in range(ngrps): + src_grp_el_lookup = _make_bdry_el_lookup_table( + actx, volume_to_bdry_conn, i_src_grp) - for i_tgt_grp in range(ngrps): - vbc_tgt_grp_batches = volume_to_bdry_conn.groups[i_tgt_grp].batches + for i_tgt_grp in range(ngrps): + vbc_tgt_grp_batches = volume_to_bdry_conn.groups[i_tgt_grp].batches - adj = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] + adj = vol_mesh.facial_adjacency_groups[i_tgt_grp][i_src_grp] - for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): - vbc_tgt_grp_face_batch = _find_ibatch_for_face( - vbc_tgt_grp_batches, i_face_tgt) + for i_face_tgt in range(vol_mesh.groups[i_tgt_grp].nfaces): + vbc_tgt_grp_face_batch = _find_ibatch_for_face( + vbc_tgt_grp_batches, i_face_tgt) - # {{{ index wrangling + # {{{ index wrangling - # The elements in the adjacency group will be a subset of - # the elements in the restriction interpolation batch: - # Imagine an inter-group boundary. The volume-to-boundary - # connection will include all faces as targets, whereas - # there will be separate adjacency groups for intra- and - # inter-group connections. + # The elements in the adjacency group will be a subset of + # the elements in the restriction interpolation batch: + # Imagine an inter-group boundary. The volume-to-boundary + # connection will include all faces as targets, whereas + # there will be separate adjacency groups for intra- and + # inter-group connections. - adj_tgt_flags = adj.element_faces == i_face_tgt - adj_els = adj.elements[adj_tgt_flags] - if adj_els.size == 0: - # NOTE: this case can happen for inter-group boundaries - # when all elements are adjacent on the same face - # index, so all other ones will be empty - continue + adj_tgt_flags = adj.element_faces == i_face_tgt + adj_els = adj.elements[adj_tgt_flags] + if adj_els.size == 0: + # NOTE: this case can happen for inter-group boundaries + # when all elements are adjacent on the same face + # index, so all other ones will be empty + continue - vbc_els = vbc_tgt_grp_face_batch.from_element_indices.get(queue) + vbc_els = thaw_to_numpy(actx, + vbc_tgt_grp_face_batch.from_element_indices) - if len(adj_els) == len(vbc_els): - # Same length: assert (below) that the two use the same - # ordering. - vbc_used_els = slice(None) + if len(adj_els) == len(vbc_els): + # Same length: assert (below) that the two use the same + # ordering. + vbc_used_els = slice(None) - else: - # Genuine subset: figure out an index mapping. - vbc_els_sort_idx = np.argsort(vbc_els) - vbc_used_els = vbc_els_sort_idx[np.searchsorted( - vbc_els, adj_els, sorter=vbc_els_sort_idx - )] + else: + # Genuine subset: figure out an index mapping. + vbc_els_sort_idx = np.argsort(vbc_els) + vbc_used_els = vbc_els_sort_idx[np.searchsorted( + vbc_els, adj_els, sorter=vbc_els_sort_idx + )] - assert np.array_equal(vbc_els[vbc_used_els], adj_els) + assert np.array_equal(vbc_els[vbc_used_els], adj_els) - # find to_element_indices + # find to_element_indices - tgt_bdry_element_indices = ( - vbc_tgt_grp_face_batch.to_element_indices - .get(queue=queue)[vbc_used_els]) + tgt_bdry_element_indices = thaw_to_numpy( + actx, + vbc_tgt_grp_face_batch.to_element_indices + )[vbc_used_els] - # find from_element_indices + # find from_element_indices - src_vol_element_indices = adj.neighbors[adj_tgt_flags] - src_element_faces = adj.neighbor_faces[adj_tgt_flags] + src_vol_element_indices = adj.neighbors[adj_tgt_flags] + src_element_faces = adj.neighbor_faces[adj_tgt_flags] - src_bdry_element_indices = src_grp_el_lookup[ - src_vol_element_indices, src_element_faces] + src_bdry_element_indices = src_grp_el_lookup[ + src_vol_element_indices, src_element_faces] - # }}} + # }}} - # {{{ visualization (for debugging) + # {{{ visualization (for debugging) - if 0: - print("TVE", adj.elements[adj_tgt_flags]) - print("TBE", tgt_bdry_element_indices) - print("FVE", src_vol_element_indices) - from meshmode.mesh.visualization import draw_2d_mesh - import matplotlib.pyplot as pt - draw_2d_mesh(vol_discr.mesh, draw_element_numbers=True, - set_bounding_box=True, - draw_vertex_numbers=False, - draw_face_numbers=True, - fill=None) - pt.figure() + if 0: + print("TVE", adj.elements[adj_tgt_flags]) + print("TBE", tgt_bdry_element_indices) + print("FVE", src_vol_element_indices) + from meshmode.mesh.visualization import draw_2d_mesh + import matplotlib.pyplot as pt + draw_2d_mesh(vol_discr.mesh, draw_element_numbers=True, + set_bounding_box=True, + draw_vertex_numbers=False, + draw_face_numbers=True, + fill=None) + pt.figure() - draw_2d_mesh(bdry_discr.mesh, draw_element_numbers=True, - set_bounding_box=True, - draw_vertex_numbers=False, - draw_face_numbers=True, - fill=None) + draw_2d_mesh(bdry_discr.mesh, draw_element_numbers=True, + set_bounding_box=True, + draw_vertex_numbers=False, + draw_face_numbers=True, + fill=None) - pt.show() + pt.show() - # }}} + # }}} - batches = _make_cross_face_batches(queue, - bdry_discr, bdry_discr, - i_tgt_grp, i_src_grp, - tgt_bdry_element_indices, - src_bdry_element_indices) - groups[i_tgt_grp].extend(batches) + batches = _make_cross_face_batches(actx, + bdry_discr, bdry_discr, + i_tgt_grp, i_src_grp, + tgt_bdry_element_indices, + src_bdry_element_indices) + groups[i_tgt_grp].extend(batches) from meshmode.discretization.connection import ( DirectDiscretizationConnection, DiscretizationConnectionElementGroup) @@ -436,7 +441,7 @@ def make_opposite_face_connection(volume_to_bdry_conn): # {{{ make_partition_connection -def make_partition_connection(local_bdry_conn, i_local_part, +def make_partition_connection(actx, local_bdry_conn, i_local_part, remote_bdry, remote_adj_groups, remote_from_elem_faces, remote_from_elem_indices): """ @@ -472,52 +477,50 @@ def make_partition_connection(local_bdry_conn, i_local_part, part_batches = [[] for _ in local_groups] - with cl.CommandQueue(local_bdry_conn.cl_context) as queue: - - for i_remote_grp, adj in enumerate(remote_adj_groups): - indices = (i_local_part == adj.neighbor_partitions) - if not np.any(indices): - # Skip because i_remote_grp is not connected to i_local_part. - continue - i_remote_faces = adj.element_faces[indices] - i_local_meshwide_elems = adj.global_neighbors[indices] - i_local_faces = adj.neighbor_faces[indices] - - i_local_grps = find_group_indices(local_groups, i_local_meshwide_elems) - - for i_local_grp in np.unique(i_local_grps): - - elem_base = local_groups[i_local_grp].element_nr_base - local_el_lookup = _make_bdry_el_lookup_table(queue, - local_bdry_conn, - i_local_grp) - - for i_remote_face in i_remote_faces: - - index_flags = np.logical_and(i_local_grps == i_local_grp, - i_remote_faces == i_remote_face) - if not np.any(index_flags): - continue - - remote_bdry_indices = None - for idxs, face in zip(remote_from_elem_indices[i_remote_grp], - remote_from_elem_faces[i_remote_grp]): - if face == i_remote_face: - remote_bdry_indices = idxs - break - assert remote_bdry_indices is not None - - elems = i_local_meshwide_elems[index_flags] - elem_base - faces = i_local_faces[index_flags] - local_bdry_indices = local_el_lookup[elems, faces] - - batches = _make_cross_face_batches(queue, - local_bdry, remote_bdry, - i_local_grp, i_remote_grp, - local_bdry_indices, - remote_bdry_indices) - - part_batches[i_local_grp].extend(batches) + for i_remote_grp, adj in enumerate(remote_adj_groups): + indices = (i_local_part == adj.neighbor_partitions) + if not np.any(indices): + # Skip because i_remote_grp is not connected to i_local_part. + continue + i_remote_faces = adj.element_faces[indices] + i_local_meshwide_elems = adj.global_neighbors[indices] + i_local_faces = adj.neighbor_faces[indices] + + i_local_grps = find_group_indices(local_groups, i_local_meshwide_elems) + + for i_local_grp in np.unique(i_local_grps): + + elem_base = local_groups[i_local_grp].element_nr_base + local_el_lookup = _make_bdry_el_lookup_table(actx, + local_bdry_conn, + i_local_grp) + + for i_remote_face in i_remote_faces: + + index_flags = np.logical_and(i_local_grps == i_local_grp, + i_remote_faces == i_remote_face) + if not np.any(index_flags): + continue + + remote_bdry_indices = None + for idxs, face in zip(remote_from_elem_indices[i_remote_grp], + remote_from_elem_faces[i_remote_grp]): + if face == i_remote_face: + remote_bdry_indices = idxs + break + assert remote_bdry_indices is not None + + elems = i_local_meshwide_elems[index_flags] - elem_base + faces = i_local_faces[index_flags] + local_bdry_indices = local_el_lookup[elems, faces] + + batches = _make_cross_face_batches(actx, + local_bdry, remote_bdry, + i_local_grp, i_remote_grp, + local_bdry_indices, + remote_bdry_indices) + + part_batches[i_local_grp].extend(batches) return DirectDiscretizationConnection( from_discr=remote_bdry, diff --git a/meshmode/discretization/connection/projection.py b/meshmode/discretization/connection/projection.py index fae3f9b7435b526cd7342090bd31012885945ff9..575b07aa5a945ee346d7f67af03da001455a029a 100644 --- a/meshmode/discretization/connection/projection.py +++ b/meshmode/discretization/connection/projection.py @@ -23,12 +23,13 @@ THE SOFTWARE. """ import numpy as np -import pyopencl as cl -import pyopencl.array # noqa -from loopy.version import MOST_RECENT_LANGUAGE_VERSION -from pytools import memoize_method, memoize_in +from pytools import keyed_memoize_method, memoize_in +import loopy as lp + +from meshmode.array_context import make_loopy_program +from meshmode.dof_array import DOFArray from meshmode.discretization.connection.direct import ( DiscretizationConnection, DirectDiscretizationConnection) @@ -78,8 +79,8 @@ class L2ProjectionInverseDiscretizationConnection(DiscretizationConnection): to_discr=self.conn.from_discr, is_surjective=is_surjective) - @memoize_method - def _batch_weights(self): + @keyed_memoize_method(key=lambda actx: ()) + def _batch_weights(self, actx): """Computes scaled quadrature weights for each interpolation batch in :attr:`conn`. The quadrature weights can be used to integrate over child elements in the domain of the parent element, by a change of @@ -111,26 +112,32 @@ class L2ProjectionInverseDiscretizationConnection(DiscretizationConnection): mat = grp.diff_matrices()[iaxis] jac[iaxis] = mat.dot(batch.result_unit_nodes.T) - weights[igrp, ibatch] = det(jac) * grp.weights + weights[igrp, ibatch] = actx.freeze(actx.from_numpy( + det(jac) * grp.weights)) return weights - def __call__(self, queue, vec): - @memoize_in(self, "conn_projection_knl") + def __call__(self, vec): + if not isinstance(vec, DOFArray): + raise TypeError("non-array passed to discretization connection") + + actx = vec.array_context + + @memoize_in(actx, (L2ProjectionInverseDiscretizationConnection, + "conn_projection_knl")) def kproj(): - import loopy as lp - knl = lp.make_kernel([ - "{[k]: 0 <= k < nelements}", - "{[j]: 0 <= j < n_from_nodes}" + return make_loopy_program([ + "{[iel]: 0 <= iel < nelements}", + "{[idof_quad]: 0 <= idof_quad < n_from_nodes}" ], """ - for k - <> element_dot = \ - sum(j, vec[from_element_indices[k], j] * \ - basis[j] * weights[j]) + for iel + <> element_dot = sum(idof_quad, + vec[from_element_indices[iel], idof_quad] + * basis[idof_quad] * weights[idof_quad]) - result[to_element_indices[k], ibasis] = \ - result[to_element_indices[k], ibasis] + element_dot + result[to_element_indices[iel], ibasis] = \ + result[to_element_indices[iel], ibasis] + element_dot end """, [ @@ -148,21 +155,18 @@ class L2ProjectionInverseDiscretizationConnection(DiscretizationConnection): lp.ValueArg("ibasis", np.int32), '...' ], - name="conn_projection_knl", - lang_version=MOST_RECENT_LANGUAGE_VERSION) - - return knl + name="conn_projection_knl") - @memoize_in(self, "conn_evaluation_knl") + @memoize_in(actx, (L2ProjectionInverseDiscretizationConnection, + "conn_evaluation_knl")) def keval(): - import loopy as lp - knl = lp.make_kernel([ - "{[k]: 0 <= k < nelements}", - "{[j]: 0 <= j < n_to_nodes}" + return make_loopy_program([ + "{[iel]: 0 <= iel < nelements}", + "{[idof]: 0 <= idof < n_to_nodes}" ], """ - result[k, j] = result[k, j] + \ - coefficients[k, ibasis] * basis[j] + result[iel, idof] = result[iel, idof] + \ + coefficients[iel, ibasis] * basis[idof] """, [ lp.GlobalArg("coefficients", None, @@ -170,55 +174,49 @@ class L2ProjectionInverseDiscretizationConnection(DiscretizationConnection): lp.ValueArg("ibasis", np.int32), '...' ], - name="conn_evaluate_knl", - default_offset=lp.auto, - lang_version=MOST_RECENT_LANGUAGE_VERSION) - - return knl - - if not isinstance(vec, cl.array.Array): - raise TypeError("non-array passed to discretization connection") - - if vec.shape != (self.from_discr.nnodes,): - raise ValueError("invalid shape of incoming resampling data") + name="conn_evaluate_knl") # compute weights on each refinement of the reference element - weights = self._batch_weights() + weights = self._batch_weights(actx) # perform dot product (on reference element) to get basis coefficients - c = self.to_discr.zeros(queue, dtype=vec.dtype) + c = self.to_discr.zeros(actx, dtype=vec.entry_dtype) + for igrp, (tgrp, cgrp) in enumerate( zip(self.to_discr.groups, self.conn.groups)): for ibatch, batch in enumerate(cgrp.batches): sgrp = self.from_discr.groups[batch.from_group_index] for ibasis, basis_fn in enumerate(sgrp.basis()): - basis = basis_fn(batch.result_unit_nodes).flatten() + basis = actx.from_numpy( + basis_fn(batch.result_unit_nodes).flatten()) # NOTE: batch.*_element_indices are reversed here because # they are from the original forward connection, but # we are going in reverse here. a bit confusing, but # saves on recreating the connection groups and batches. - kproj()(queue, + actx.call_loopy(kproj(), ibasis=ibasis, - vec=sgrp.view(vec), + vec=vec[sgrp.index], basis=basis, weights=weights[igrp, ibatch], - result=tgrp.view(c), + result=c[igrp], from_element_indices=batch.to_element_indices, to_element_indices=batch.from_element_indices) # evaluate at unit_nodes to get the vector on to_discr - result = self.to_discr.zeros(queue, dtype=vec.dtype) + result = self.to_discr.zeros(actx, dtype=vec.entry_dtype) for igrp, grp in enumerate(self.to_discr.groups): for ibasis, basis_fn in enumerate(grp.basis()): - basis = basis_fn(grp.unit_nodes).flatten() + basis = actx.from_numpy( + basis_fn(grp.unit_nodes).flatten()) - keval()(queue, + actx.call_loopy( + keval(), ibasis=ibasis, - result=grp.view(result), + result=result[grp.index], basis=basis, - coefficients=grp.view(c)) + coefficients=c[grp.index]) return result diff --git a/meshmode/discretization/connection/refinement.py b/meshmode/discretization/connection/refinement.py index 9a2d60d890f6b436e72151d66171d819cdce6a98..55026e59a453c5a6fd1738c381ac313863242d15 100644 --- a/meshmode/discretization/connection/refinement.py +++ b/meshmode/discretization/connection/refinement.py @@ -24,8 +24,6 @@ THE SOFTWARE. """ import numpy as np -import pyopencl as cl -import pyopencl.array # noqa import logging logger = logging.getLogger(__name__) @@ -36,7 +34,7 @@ from pytools import log_process # {{{ Build interpolation batches for group def _build_interpolation_batches_for_group( - queue, group_idx, coarse_discr_group, fine_discr_group, record): + actx, group_idx, coarse_discr_group, fine_discr_group, record): r""" To map between discretizations, we sort each of the fine mesh elements into an interpolation batch. Which batch they go @@ -104,8 +102,8 @@ def _build_interpolation_batches_for_group( continue yield InterpolationBatch( from_group_index=group_idx, - from_element_indices=cl.array.to_device(queue, np.asarray(from_bin)), - to_element_indices=cl.array.to_device(queue, np.asarray(to_bin)), + from_element_indices=actx.from_numpy(np.asarray(from_bin)), + to_element_indices=actx.from_numpy(np.asarray(to_bin)), result_unit_nodes=unit_nodes, to_element_face=None) @@ -113,7 +111,7 @@ def _build_interpolation_batches_for_group( @log_process(logger) -def make_refinement_connection(refiner, coarse_discr, group_factory): +def make_refinement_connection(actx, refiner, coarse_discr, group_factory): """Return a :class:`meshmode.discretization.connection.DiscretizationConnection` connecting `coarse_discr` to a discretization on the fine mesh. @@ -142,21 +140,20 @@ def make_refinement_connection(refiner, coarse_discr, group_factory): from meshmode.discretization import Discretization fine_discr = Discretization( - coarse_discr.cl_context, + actx, fine_mesh, group_factory, real_dtype=coarse_discr.real_dtype) groups = [] - with cl.CommandQueue(fine_discr.cl_context) as queue: - for group_idx, (coarse_discr_group, fine_discr_group, record) in \ - enumerate(zip(coarse_discr.groups, fine_discr.groups, - refiner.group_refinement_records)): - groups.append( - DiscretizationConnectionElementGroup( - list(_build_interpolation_batches_for_group( - queue, group_idx, coarse_discr_group, - fine_discr_group, record)))) + for group_idx, (coarse_discr_group, fine_discr_group, record) in \ + enumerate(zip(coarse_discr.groups, fine_discr.groups, + refiner.group_refinement_records)): + groups.append( + DiscretizationConnectionElementGroup( + list(_build_interpolation_batches_for_group( + actx, group_idx, coarse_discr_group, + fine_discr_group, record)))) return DirectDiscretizationConnection( from_discr=coarse_discr, diff --git a/meshmode/discretization/connection/same_mesh.py b/meshmode/discretization/connection/same_mesh.py index e7b781cd8e2908e1ef9b5af7da202a009931a256..355d534944e905974ca61ec210f64bb48e91bfcd 100644 --- a/meshmode/discretization/connection/same_mesh.py +++ b/meshmode/discretization/connection/same_mesh.py @@ -23,13 +23,11 @@ THE SOFTWARE. """ import numpy as np -import pyopencl as cl -import pyopencl.array # noqa # {{{ same-mesh constructor -def make_same_mesh_connection(to_discr, from_discr): +def make_same_mesh_connection(actx, to_discr, from_discr): from meshmode.discretization.connection.direct import ( InterpolationBatch, DiscretizationConnectionElementGroup, @@ -39,23 +37,22 @@ def make_same_mesh_connection(to_discr, from_discr): raise ValueError("from_discr and to_discr must be based on " "the same mesh") - assert to_discr.cl_context == from_discr.cl_context - - with cl.CommandQueue(to_discr.cl_context) as queue: - groups = [] - for igrp, (fgrp, tgrp) in enumerate(zip(from_discr.groups, to_discr.groups)): - all_elements = cl.array.arange(queue, - fgrp.nelements, - dtype=np.intp).with_queue(None) - ibatch = InterpolationBatch( - from_group_index=igrp, - from_element_indices=all_elements, - to_element_indices=all_elements, - result_unit_nodes=tgrp.unit_nodes, - to_element_face=None) - - groups.append( - DiscretizationConnectionElementGroup([ibatch])) + groups = [] + for igrp, (fgrp, tgrp) in enumerate(zip(from_discr.groups, to_discr.groups)): + all_elements = actx.freeze( + actx.from_numpy( + np.arange( + fgrp.nelements, + dtype=np.intp))) + ibatch = InterpolationBatch( + from_group_index=igrp, + from_element_indices=all_elements, + to_element_indices=all_elements, + result_unit_nodes=tgrp.unit_nodes, + to_element_face=None) + + groups.append( + DiscretizationConnectionElementGroup([ibatch])) return DirectDiscretizationConnection( from_discr, to_discr, groups, diff --git a/meshmode/discretization/poly_element.py b/meshmode/discretization/poly_element.py index 436b1fb1229e2dc11bc56faa280cf0185763735c..d0f3823c0b9bb368fa215a7187f39594bb190def 100644 --- a/meshmode/discretization/poly_element.py +++ b/meshmode/discretization/poly_element.py @@ -53,15 +53,6 @@ Group factories .. autoclass:: LegendreGaussLobattoTensorProductGroupFactory """ -# FIXME Most of the loopy kernels will break as soon as we start using multiple -# element groups. That's because then the dimension-to-dimension stride will no -# longer just be the long axis of the array, but something entirely -# independent. The machinery for this on the loopy end is there, in the form -# of the "stride:auto" dim tag, it just needs to be pushed through all the -# kernels. Fortunately, this will fail in an obvious and noisy way, because -# loopy sees strides that it doesn't expect and complains. - - from meshmode.discretization import ElementGroupBase, InterpolatoryElementGroupBase @@ -221,7 +212,11 @@ class PolynomialWarpAndBlendElementGroup(_MassMatrixQuadratureElementGroup): @memoize_method def unit_nodes(self): dim = self.mesh_el_group.dim - result = mp.warp_and_blend_nodes(dim, self.order) + if self.order == 0: + result = mp.warp_and_blend_nodes(dim, 1) + result = np.mean(result, axis=1).reshape(-1, 1) + else: + result = mp.warp_and_blend_nodes(dim, self.order) dim2, nunit_nodes = result.shape assert dim2 == dim @@ -300,12 +295,12 @@ class HomogeneousOrderBasedGroupFactory(ElementGroupFactory): def __init__(self, order): self.order = order - def __call__(self, mesh_el_group, node_nr_base): + def __call__(self, mesh_el_group, index): if not isinstance(mesh_el_group, self.mesh_group_class): raise TypeError("only mesh element groups of type '%s' " "are supported" % self.mesh_group_class.__name__) - return self.group_class(mesh_el_group, self.order, node_nr_base) + return self.group_class(mesh_el_group, self.order, index) class OrderAndTypeBasedGroupFactory(ElementGroupFactory): @@ -314,7 +309,7 @@ class OrderAndTypeBasedGroupFactory(ElementGroupFactory): self.simplex_group_class = simplex_group_class self.tensor_product_group_class = tensor_product_group_class - def __call__(self, mesh_el_group, node_nr_base): + def __call__(self, mesh_el_group, index): if isinstance(mesh_el_group, _MeshSimplexElementGroup): group_class = self.simplex_group_class elif isinstance(mesh_el_group, _MeshTensorProductElementGroup): @@ -325,7 +320,7 @@ class OrderAndTypeBasedGroupFactory(ElementGroupFactory): _MeshSimplexElementGroup.__name__, _MeshTensorProductElementGroup.__name__)) - return group_class(mesh_el_group, self.order, node_nr_base) + return group_class(mesh_el_group, self.order, index) # {{{ group factories for simplices diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py index e985f3ffcd9f077a2c6dcb97b6e94e4c8147c49c..24fe92f675293ef6ab048553778ac50a394d4173 100644 --- a/meshmode/discretization/visualization.py +++ b/meshmode/discretization/visualization.py @@ -1,5 +1,3 @@ -from __future__ import division, absolute_import - __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" __license__ = """ @@ -22,10 +20,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from six.moves import range import numpy as np from pytools import memoize_method, Record -import pyopencl as cl +from meshmode.dof_array import DOFArray, flatten, thaw + __doc__ = """ @@ -40,29 +38,28 @@ __doc__ = """ # {{{ visualizer def separate_by_real_and_imag(data, real_only): - for name, field in data: - from pytools.obj_array import log_shape, is_obj_array - ls = log_shape(field) + # This function is called on numpy data that has already been + # merged into a single vector. - if is_obj_array(field): - assert len(ls) == 1 + for name, field in data: + if isinstance(field, np.ndarray) and field.dtype.char == "O": + assert len(field.shape) == 1 from pytools.obj_array import ( - oarray_real_copy, oarray_imag_copy, - with_object_array_or_scalar) + obj_array_real_copy, obj_array_imag_copy, + obj_array_vectorize) if field[0].dtype.kind == "c": if real_only: yield (name, - with_object_array_or_scalar(oarray_real_copy, field)) + obj_array_vectorize(obj_array_real_copy, field)) else: yield (name+"_r", - with_object_array_or_scalar(oarray_real_copy, field)) + obj_array_vectorize(obj_array_real_copy, field)) yield (name+"_i", - with_object_array_or_scalar(oarray_imag_copy, field)) + obj_array_vectorize(obj_array_imag_copy, field)) else: yield (name, field) else: - # ls == () if field.dtype.kind == "c": yield (name+"_r", field.real.copy()) yield (name+"_i", field.imag.copy()) @@ -115,17 +112,29 @@ class Visualizer(object): self.element_shrink_factor = element_shrink_factor - def _resample_and_get(self, queue, vec): - from pytools.obj_array import with_object_array_or_scalar - - def resample_and_get_one(fld): - from numbers import Number - if isinstance(fld, Number): - return np.ones(self.connection.to_discr.nnodes) * fld - else: - return self.connection(queue, fld).get(queue=queue) + def _resample_to_numpy(self, vec): + if (isinstance(vec, np.ndarray) + and vec.dtype.char == "O" + and not isinstance(vec, DOFArray)): + from pytools.obj_array import obj_array_vectorize + return obj_array_vectorize(self._resample_to_numpy, vec) + + from numbers import Number + if isinstance(vec, Number): + nnodes = sum(grp.ndofs for grp in self.connection.to_discr.groups) + return np.ones(nnodes) * vec + else: + resampled = self.connection(vec) + actx = resampled.array_context + return actx.to_numpy(flatten(resampled)) - return with_object_array_or_scalar(resample_and_get_one, vec) + @memoize_method + def _vis_nodes(self): + actx = self.vis_discr._setup_actx + return np.array([ + actx.to_numpy(flatten(thaw(actx, ary))) + for ary in self.vis_discr.nodes() + ]) # {{{ vis sub-element connectivity @@ -148,14 +157,15 @@ class Visualizer(object): VTK_QUAD, VTK_HEXAHEDRON) subel_nr_base = 0 + node_nr_base = 0 for group in self.vis_discr.groups: if isinstance(group.mesh_el_group, SimplexElementGroup): node_tuples = list(gnitstam(group.order, group.dim)) - from modepy.tools import submesh + from modepy.tools import simplex_submesh el_connectivity = np.array( - submesh(node_tuples), + simplex_submesh(node_tuples), dtype=np.intp) vtk_cell_type = { 1: VTK_LINE, @@ -202,10 +212,10 @@ class Visualizer(object): raise NotImplementedError("visualization for element groups " "of type '%s'" % type(group.mesh_el_group).__name__) - assert len(node_tuples) == group.nunit_nodes + assert len(node_tuples) == group.nunit_dofs vis_connectivity = ( - group.node_nr_base + np.arange( - 0, group.nelements*group.nunit_nodes, group.nunit_nodes + node_nr_base + np.arange( + 0, group.nelements*group.nunit_dofs, group.nunit_dofs )[:, np.newaxis, np.newaxis] + el_connectivity).astype(np.intp) @@ -216,6 +226,7 @@ class Visualizer(object): result.append(vgrp) subel_nr_base += vgrp.nsubelements + node_nr_base += group.ndofs return result @@ -228,10 +239,8 @@ class Visualizer(object): do_show = kwargs.pop("do_show", True) - with cl.CommandQueue(self.vis_discr.cl_context) as queue: - nodes = self.vis_discr.nodes().with_queue(queue).get() - - field = self._resample_and_get(queue, field) + nodes = self._vis_nodes() + field = self._resample_to_numpy(field) assert nodes.shape[0] == self.vis_discr.ambient_dim #mlab.points3d(nodes[0], nodes[1], 0*nodes[0]) @@ -286,12 +295,10 @@ class Visualizer(object): AppendedDataXMLGenerator, VF_LIST_OF_COMPONENTS) - with cl.CommandQueue(self.vis_discr.cl_context) as queue: - nodes = self.vis_discr.nodes().with_queue(queue).get() - - names_and_fields = [ - (name, self._resample_and_get(queue, fld)) - for name, fld in names_and_fields] + nodes = self._vis_nodes() + names_and_fields = [ + (name, self._resample_to_numpy(fld)) + for name, fld in names_and_fields] vc_groups = self._vis_connectivity() @@ -318,8 +325,12 @@ class Visualizer(object): + (1-self.element_shrink_factor) * el_centers[:, :, np.newaxis]) + if len(self.vis_discr.groups) != 1: + raise NotImplementedError("visualization with multiple " + "element groups") + grid = UnstructuredGrid( - (self.vis_discr.nnodes, + (sum(grp.ndofs for grp in self.vis_discr.groups), DataArray("points", nodes.reshape(self.vis_discr.ambient_dim, -1), vector_format=VF_LIST_OF_COMPONENTS)), @@ -362,10 +373,8 @@ class Visualizer(object): vmax = kwargs.pop("vmax", None) norm = kwargs.pop("norm", None) - with cl.CommandQueue(self.vis_discr.cl_context) as queue: - nodes = self.vis_discr.nodes().with_queue(queue).get() - - field = self._resample_and_get(queue, field) + nodes = self._vis_nodes() + field = self._resample_to_numpy(field) assert nodes.shape[0] == self.vis_discr.ambient_dim @@ -420,14 +429,14 @@ class Visualizer(object): # }}} -def make_visualizer(queue, discr, vis_order, element_shrink_factor=None): +def make_visualizer(actx, discr, vis_order, element_shrink_factor=None): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import ( PolynomialWarpAndBlendElementGroup, LegendreGaussLobattoTensorProductElementGroup, OrderAndTypeBasedGroupFactory) vis_discr = Discretization( - discr.cl_context, discr.mesh, + actx, discr.mesh, OrderAndTypeBasedGroupFactory( vis_order, simplex_group_class=PolynomialWarpAndBlendElementGroup, @@ -438,7 +447,7 @@ def make_visualizer(queue, discr, vis_order, element_shrink_factor=None): make_same_mesh_connection return Visualizer( - make_same_mesh_connection(vis_discr, discr), + make_same_mesh_connection(actx, vis_discr, discr), element_shrink_factor=element_shrink_factor) # }}} @@ -453,16 +462,18 @@ def draw_curve(discr): plt.plot(mesh.vertices[0], mesh.vertices[1], "o") color = plt.cm.rainbow(np.linspace(0, 1, len(discr.groups))) - with cl.CommandQueue(discr.cl_context) as queue: - for i, group in enumerate(discr.groups): - group_nodes = group.view(discr.nodes()).get(queue=queue) - artist_handles = plt.plot( - group_nodes[0].T, - group_nodes[1].T, "-x", - color=color[i]) - - if artist_handles: - artist_handles[0].set_label("Group %d" % i) + for igrp, group in enumerate(discr.groups): + group_nodes = np.array([ + discr._setup_actx.to_numpy(discr.nodes()[iaxis][igrp]) + for iaxis in range(discr.ambient_dim) + ]) + artist_handles = plt.plot( + group_nodes[0].T, + group_nodes[1].T, "-x", + color=color[igrp]) + + if artist_handles: + artist_handles[0].set_label("Group %d" % igrp) # }}} diff --git a/meshmode/distributed.py b/meshmode/distributed.py index 54135fb061097ed8064b696b1b4b778e70dd02c4..bbe24bb0511b072a89192f2703c2e232f7dfcf7c 100644 --- a/meshmode/distributed.py +++ b/meshmode/distributed.py @@ -128,15 +128,14 @@ class MPIBoundaryCommSetupHelper(object): .. automethod:: __call__ .. automethod:: is_ready """ - def __init__(self, mpi_comm, queue, local_bdry_conn, i_remote_part, + def __init__(self, mpi_comm, actx, local_bdry_conn, i_remote_part, bdry_grp_factory): """ :arg mpi_comm: A :class:`MPI.Intracomm` - :arg queue: :arg i_remote_part: The part number of the remote partition """ self.mpi_comm = mpi_comm - self.queue = queue + self.array_context = actx self.i_local_part = mpi_comm.Get_rank() self.i_remote_part = i_remote_part self.local_bdry_conn = local_bdry_conn @@ -151,9 +150,11 @@ class MPIBoundaryCommSetupHelper(object): for i in range(len(local_mesh.groups))] local_to_elem_faces = [[batch.to_element_face for batch in grp_batches] for grp_batches in local_batches] - local_to_elem_indices = [[batch.to_element_indices.get(queue=self.queue) - for batch in grp_batches] - for grp_batches in local_batches] + local_to_elem_indices = [ + [ + self.array_context.to_numpy(batch.to_element_indices) + for batch in grp_batches] + for grp_batches in local_batches] local_data = {'bdry_mesh': local_bdry.mesh, 'adj': local_adj_groups, @@ -190,7 +191,7 @@ class MPIBoundaryCommSetupHelper(object): from meshmode.discretization import Discretization remote_bdry_mesh = remote_data['bdry_mesh'] - remote_bdry = Discretization(self.queue.context, remote_bdry_mesh, + remote_bdry = Discretization(self.array_context, remote_bdry_mesh, self.bdry_grp_factory) remote_adj_groups = remote_data['adj'] remote_to_elem_faces = remote_data['to_elem_faces'] @@ -198,12 +199,11 @@ class MPIBoundaryCommSetupHelper(object): # Connect local_mesh to remote_mesh from meshmode.discretization.connection import make_partition_connection - remote_to_local_bdry_conn = make_partition_connection(self.local_bdry_conn, - self.i_local_part, - remote_bdry, - remote_adj_groups, - remote_to_elem_faces, - remote_to_elem_indices) + remote_to_local_bdry_conn = make_partition_connection( + self.array_context, self.local_bdry_conn, self.i_local_part, + remote_bdry, remote_adj_groups, remote_to_elem_faces, + remote_to_elem_indices) + self.send_req.wait() return remote_to_local_bdry_conn diff --git a/meshmode/dof_array.py b/meshmode/dof_array.py new file mode 100644 index 0000000000000000000000000000000000000000..f74f2090672c4f94a13426f05cd71a2cfe583297 --- /dev/null +++ b/meshmode/dof_array.py @@ -0,0 +1,266 @@ +__copyright__ = "Copyright (C) 2020 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 +from typing import Optional, Iterable, TYPE_CHECKING, Any +from functools import partial + +from pytools import single_valued, memoize_in +from pytools.obj_array import obj_array_vectorize + +from meshmode.array_context import ArrayContext, make_loopy_program + +if TYPE_CHECKING: + from meshmode.discretization import Discretization as _Discretization + + +__doc__ = """ +.. autoclass:: DOFArray +.. autofunction:: thaw +.. autofunction:: freeze +.. autofunction:: flatten +.. autofunction:: unflatten +.. autofunction:: flat_norm +""" + + +# {{{ DOFArray + +class DOFArray(np.ndarray): + """This array type is a subclass of :class:`numpy.ndarray` intended to hold + degree-of-freedom arrays for use with + :class:`~meshmode.discretization.Discretization`, + with one entry in the :class:`DOFArray` for each + :class:`~meshmode.discretization.ElementGroupBase`. + The arrays contained within a :class:`DOFArray` + are expected to be logically two-dimensional, with shape + ``(nelements, ndofs_per_element)``, where ``nelements`` is the same as + :attr:`~meshmode.discretization.ElementGroupBase.nelements` + of the associated group. + ``ndofs_per_element`` is typically, but not necessarily, the same as + :attr:`~meshmode.discretization.ElementGroupBase.nunit_dofs` + of the associated group. + This array is derived from :class:`numpy.ndarray` with dtype object ("an + object array"). The entries in this array are further arrays managed by + :attr:`array_context`. + + One main purpose of this class is to describe the data structure, + i.e. when a :class:`DOFArray` occurs inside of further numpy object array, + the level representing the array of element groups can be recognized (by + people and programs). + + .. attribute:: array_context + + An :class:`meshmode.array_context.ArrayContext`. + + .. attribute:: entry_dtype + + The (assumed uniform) :class:`numpy.dtype` of the group arrays + contained in this instance. + + .. automethod:: from_list + """ + + # Follows https://numpy.org/devdocs/user/basics.subclassing.html + + def __new__(cls, actx: Optional[ArrayContext], input_array): + if not (actx is None or isinstance(actx, ArrayContext)): + raise TypeError("actx must be of type ArrayContext") + + result = np.asarray(input_array).view(cls) + if len(result.shape) != 1: + raise ValueError("DOFArray instances must have one-dimensional " + "shape, with one entry per element group") + + result.array_context = actx + return result + + def __array_finalize__(self, obj): + if obj is None: + return + self.array_context = getattr(obj, "array_context", None) + + @property + def entry_dtype(self): + return single_valued(subary.dtype for subary in self.flat) + + @classmethod + def from_list(cls, actx: Optional[ArrayContext], res_list) -> "DOFArray": + r"""Create a :class:`DOFArray` from a list of arrays + (one per :class:`~meshmode.discretization.ElementGroupBase`). + + :arg actx: If *None*, the arrays in *res_list* must be + :meth:`~meshmode.array_context.ArrayContext.thaw`\ ed. + """ + if not (actx is None or isinstance(actx, ArrayContext)): + raise TypeError("actx must be of type ArrayContext") + + ngroups = len(res_list) + + result = np.empty(ngroups, dtype=object).view(cls) + result.array_context = actx + + # 'result[:] = res_list' may look tempting, however: + # https://github.com/numpy/numpy/issues/16564 + for igrp in range(ngroups): + result[igrp] = res_list[igrp] + + return result + +# }}} + + +def thaw(actx: ArrayContext, ary: np.ndarray) -> np.ndarray: + r"""Call :meth:`~meshmode.array_context.ArrayContext.thaw` on the element + group arrays making up the :class:`DOFArray`, using *actx*. + + Vectorizes over object arrays of :class:`DOFArray`\ s. + """ + if (isinstance(ary, np.ndarray) + and ary.dtype.char == "O" + and not isinstance(ary, DOFArray)): + return obj_array_vectorize(partial(thaw, actx), ary) + + if ary.array_context is not None: + raise ValueError("DOFArray passed to thaw is not frozen") + + return DOFArray.from_list(actx, [ + actx.thaw(subary) + for subary in ary + ]) + + +def freeze(ary: np.ndarray) -> np.ndarray: + r"""Call :meth:`~meshmode.array_context.ArrayContext.freeze` on the element + group arrays making up the :class:`DOFArray`, using the + :class:`~meshmode.array_context.ArrayContext` in *ary*. + + Vectorizes over object arrays of :class:`DOFArray`\ s. + """ + if (isinstance(ary, np.ndarray) + and ary.dtype.char == "O" + and not isinstance(ary, DOFArray)): + return obj_array_vectorize(freeze, ary) + + if ary.array_context is None: + raise ValueError("DOFArray passed to freeze is already frozen") + + return DOFArray.from_list(None, [ + ary.array_context.freeze(subary) + for subary in ary + ]) + + +def flatten(ary: np.ndarray) -> Any: + r"""Convert a :class:`DOFArray` into a "flat" array of degrees of freedom, + where the resulting type of the array is given by the + :attr:`DOFArray.array_context`. + + Array elements are laid out contiguously, with the element group + index varying slowest, element index next, and intra-element DOF + index fastest. + + Vectorizes over object arrays of :class:`DOFArray`\ s. + """ + if (isinstance(ary, np.ndarray) + and ary.dtype.char == "O" + and not isinstance(ary, DOFArray)): + return obj_array_vectorize(flatten, ary) + + group_sizes = [grp_ary.shape[0] * grp_ary.shape[1] for grp_ary in ary] + group_starts = np.cumsum([0] + group_sizes) + + actx = ary.array_context + + @memoize_in(actx, (flatten, "flatten_prg")) + def prg(): + return make_loopy_program( + "{[iel,idof]: 0<=iel<nelements and 0<=idof<ndofs_per_element}", + """result[grp_start + iel*ndofs_per_element + idof] \ + = grp_ary[iel, idof]""", + name="flatten") + + result = actx.empty(group_starts[-1], dtype=ary.entry_dtype) + + for grp_start, grp_ary in zip(group_starts, ary): + actx.call_loopy(prg(), grp_ary=grp_ary, result=result, grp_start=grp_start) + + return result + + +def unflatten(actx: ArrayContext, discr: "_Discretization", ary, + ndofs_per_element_per_group: Optional[Iterable[int]] = None) -> np.ndarray: + r"""Convert a 'flat' array returned by :func:`flatten` back to a :class:`DOFArray`. + + Vectorizes over object arrays of :class:`DOFArray`\ s. + """ + if (isinstance(ary, np.ndarray) + and ary.dtype.char == "O" + and not isinstance(ary, DOFArray)): + return obj_array_vectorize( + lambda subary: unflatten( + actx, discr, subary, ndofs_per_element_per_group), + ary) + + @memoize_in(actx, (unflatten, "unflatten_prg")) + def prg(): + return make_loopy_program( + "{[iel,idof]: 0<=iel<nelements and 0<=idof<ndofs_per_element}", + "result[iel, idof] = ary[grp_start + iel*ndofs_per_element + idof]", + name="unflatten") + + if ndofs_per_element_per_group is None: + ndofs_per_element_per_group = [ + grp.nunit_dofs for grp in discr.groups] + + group_sizes = [ + grp.nelements * ndofs_per_element + for grp, ndofs_per_element + in zip(discr.groups, ndofs_per_element_per_group)] + + if ary.size != sum(group_sizes): + raise ValueError("array has size %d, expected %d" + % (ary.size, sum(group_sizes))) + + group_starts = np.cumsum([0] + group_sizes) + + return DOFArray.from_list(actx, [ + actx.call_loopy( + prg(), + grp_start=grp_start, ary=ary, + nelements=grp.nelements, + ndofs_per_element=ndofs_per_element, + )["result"] + for grp_start, grp, ndofs_per_element in zip( + group_starts, + discr.groups, + ndofs_per_element_per_group)]) + + +def flat_norm(ary: DOFArray, ord=2): + # FIXME This could be done without flattening and copying + actx = ary.array_context + import numpy.linalg as la + return la.norm(actx.to_numpy(flatten(ary)), ord) + + +# vim: foldmethod=marker diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py index 4fa520bc4d7a62a4dfb27f55025ff9d906b52a0f..c524292c1fffd741951b5838729014ac38db64b5 100644 --- a/meshmode/mesh/generation.py +++ b/meshmode/mesh/generation.py @@ -321,6 +321,9 @@ def make_group_from_vertices(vertices, vertex_indices, order, group_factory = SimplexElementGroup if issubclass(group_factory, SimplexElementGroup): + if order < 1: + raise ValueError("can't represent simplices with mesh order < 1") + el_origins = el_vertices[:, :, 0][:, :, np.newaxis] # ambient_dim, nelements, nspan_vectors spanning_vectors = ( @@ -811,10 +814,9 @@ def generate_box_mesh(axis_coords, order=1, coord_dtype=np.float64, axes = ["x", "y", "z", "w"] if boundary_tags: - from pytools import indices_in_shape vert_index_to_tuple = { vertex_indices[itup]: itup - for itup in indices_in_shape(shape)} + for itup in np.ndindex(shape)} for tag_idx, tag in enumerate(boundary_tags): # Need to map the correct face vertices to the boundary tags diff --git a/meshmode/version.py b/meshmode/version.py index 5cea8857d9550183c2caabd0948032faac48e0ae..82ea6fb128de4adc8cddd1928ee3eb063bd70aa0 100644 --- a/meshmode/version.py +++ b/meshmode/version.py @@ -1,2 +1,2 @@ -VERSION = (2020, 1) +VERSION = (2020, 2) VERSION_TEXT = ".".join(str(i) for i in VERSION) diff --git a/requirements.txt b/requirements.txt index 62920342df6e2e29d0772be0b2e7cc705f87a214..4c3ff3392cea23471a0778e46ae7625bd6b7094e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,21 @@ numpy -git+https://gitlab.tiker.net/inducer/gmsh_interop.git -git+https://gitlab.tiker.net/inducer/modepy.git -git+https://gitlab.tiker.net/inducer/pyopencl.git -git+https://gitlab.tiker.net/inducer/islpy.git +git+https://gitlab.tiker.net/inducer/pytools.git#egg=pytools +git+https://gitlab.tiker.net/inducer/gmsh_interop.git#egg=gmsh_interop +git+https://gitlab.tiker.net/inducer/modepy.git#egg=modepy +git+https://gitlab.tiker.net/inducer/pyopencl.git#egg=pyopencl +git+https://gitlab.tiker.net/inducer/islpy.git#egg=islpy # required by pytential, which is in turn needed for some tests -git+https://gitlab.tiker.net/inducer/pymbolic.git +git+https://gitlab.tiker.net/inducer/pymbolic.git#egg=pymbolic # also depends on pymbolic, so should come after it -git+https://gitlab.tiker.net/inducer/loopy.git +git+https://gitlab.tiker.net/inducer/loopy.git#egg=loo.py # more pytential dependencies -git+https://gitlab.tiker.net/inducer/boxtree.git -git+https://gitlab.tiker.net/inducer/sumpy.git -git+https://gitlab.tiker.net/inducer/pytential.git +git+https://github.com/inducer/boxtree.git#egg=boxtree +git+https://github.com/inducer/sumpy.git#egg=sumpy +git+https://github.com/inducer/pytential.git#pytential # requires pymetis for tests for partition_mesh -git+https://gitlab.tiker.net/inducer/pymetis.git +git+https://gitlab.tiker.net/inducer/pymetis.git#egg=pymetis diff --git a/setup.py b/setup.py index 55a523ca5cf961abd0c8bfb9b70c35f0898b0b5e..810d2560b674251f4b6b72d617e4fba74f38c692 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,9 @@ def main(): 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Information Analysis', 'Topic :: Scientific/Engineering :: Mathematics', @@ -42,7 +45,7 @@ def main(): "modepy", "gmsh_interop", "six", - "pytools>=2018.4", + "pytools>=2020.3", "pytest>=2.3", "loo.py>=2014.1", ], diff --git a/test/test_chained.py b/test/test_chained.py index 21ebbda4f202b5175f3d04e5c4eae2c95027b759..51c1e12c494f32af90e8a7fa98724188a04bc2ca 100644 --- a/test/test_chained.py +++ b/test/test_chained.py @@ -23,27 +23,25 @@ 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 pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw, flat_norm, flatten + import logging logger = logging.getLogger(__name__) -def create_discretization(queue, ndim, +def create_discretization(actx, ndim, nelements=42, mesh_name=None, order=4): - ctx = queue.context - # construct mesh if ndim == 2: from functools import partial @@ -80,13 +78,13 @@ def create_discretization(queue, ndim, from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - discr = Discretization(ctx, mesh, + discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) return discr -def create_refined_connection(queue, discr, threshold=0.3): +def create_refined_connection(actx, discr, threshold=0.3): from meshmode.mesh.refinement import RefinerWithoutAdjacency from meshmode.discretization.connection import make_refinement_connection from meshmode.discretization.poly_element import \ @@ -97,20 +95,20 @@ def create_refined_connection(queue, discr, threshold=0.3): refiner.refine(flags) discr_order = discr.groups[0].order - connection = make_refinement_connection(refiner, discr, + connection = make_refinement_connection(actx, refiner, discr, InterpolatoryQuadratureSimplexGroupFactory(discr_order)) return connection -def create_face_connection(queue, discr): +def create_face_connection(actx, discr): from meshmode.discretization.connection import FACE_RESTR_ALL from meshmode.discretization.connection import make_face_restriction from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory discr_order = discr.groups[0].order - connection = make_face_restriction(discr, + connection = make_face_restriction(actx, discr, InterpolatoryQuadratureSimplexGroupFactory(discr_order), FACE_RESTR_ALL, per_face_groups=True) @@ -126,19 +124,20 @@ def test_chained_batch_table(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - discr = create_discretization(queue, ndim) + discr = create_discretization(actx, ndim) connections = [] - conn = create_refined_connection(queue, discr) + conn = create_refined_connection(actx, discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) from meshmode.discretization.connection import ChainedDiscretizationConnection chained = ChainedDiscretizationConnection(connections) conn = chained.connections[0] - el_table = _build_element_lookup_table(queue, conn) + el_table = _build_element_lookup_table(actx, conn) for igrp, grp in enumerate(conn.groups): for ibatch, batch in enumerate(grp.batches): ifrom = batch.from_element_indices.get(queue) @@ -156,15 +155,15 @@ def test_chained_new_group_table(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - discr = create_discretization(queue, ndim, + discr = create_discretization(actx, ndim, nelements=8, - mesh_order=2, - discr_order=2) + order=2) connections = [] - conn = create_refined_connection(queue, discr) + conn = create_refined_connection(actx, discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) grp_to_grp, grp_info = _build_new_group_table(connections[0], @@ -202,13 +201,13 @@ def test_chained_new_group_table(ctx_factory, ndim, visualize=False): def test_chained_connection(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - discr = create_discretization(queue, ndim, - nelements=10) + discr = create_discretization(actx, ndim, nelements=10) connections = [] - conn = create_refined_connection(queue, discr, threshold=np.inf) + conn = create_refined_connection(actx, discr, threshold=np.inf) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr, threshold=np.inf) + conn = create_refined_connection(actx, conn.to_discr, threshold=np.inf) connections.append(conn) from meshmode.discretization.connection import \ @@ -216,15 +215,15 @@ def test_chained_connection(ctx_factory, ndim, visualize=False): chained = ChainedDiscretizationConnection(connections) def f(x): - from six.moves import reduce - return 0.1 * reduce(lambda x, y: x * cl.clmath.sin(5 * y), x) + from functools import reduce + return 0.1 * reduce(lambda x, y: x * actx.np.sin(5 * y), x) - x = connections[0].from_discr.nodes().with_queue(queue) + x = thaw(actx, connections[0].from_discr.nodes()) fx = f(x) - f1 = chained(queue, fx).get(queue) - f2 = connections[1](queue, connections[0](queue, fx)).get(queue) + f1 = chained(fx) + f2 = connections[1](connections[0](fx)) - assert np.allclose(f1, f2) + assert flat_norm(f1-f2, np.inf) / flat_norm(f2) < 1e-11 @pytest.mark.skip(reason='slow test') @@ -235,12 +234,13 @@ def test_chained_full_resample_matrix(ctx_factory, ndim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - discr = create_discretization(queue, ndim) + discr = create_discretization(actx, ndim) connections = [] - conn = create_refined_connection(queue, discr) + conn = create_refined_connection(actx, discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) from meshmode.discretization.connection import \ @@ -249,15 +249,15 @@ def test_chained_full_resample_matrix(ctx_factory, ndim, visualize=False): def f(x): from six.moves import reduce - return 0.1 * reduce(lambda x, y: x * cl.clmath.sin(5 * y), x) + return 0.1 * reduce(lambda x, y: x * actx.np.sin(5 * y), x) - resample_mat = make_full_resample_matrix(queue, chained).get(queue) + resample_mat = actx.to_numpy(make_full_resample_matrix(actx, chained)) - x = connections[0].from_discr.nodes().with_queue(queue) + x = thaw(actx, connections[0].from_discr.nodes()) fx = f(x) - f1 = np.dot(resample_mat, fx.get(queue)) - f2 = chained(queue, fx).get(queue) - f3 = connections[1](queue, connections[0](queue, fx)).get(queue) + f1 = resample_mat @ actx.to_numpy(flatten(fx)) + f2 = actx.to_numpy(flatten(chained(fx))) + f3 = actx.to_numpy(flatten(connections[1](connections[0](fx)))) assert np.allclose(f1, f2) assert np.allclose(f2, f3) @@ -273,25 +273,26 @@ def test_chained_to_direct(ctx_factory, ndim, chain_type, ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) - discr = create_discretization(queue, ndim, nelements=nelements) + discr = create_discretization(actx, ndim, nelements=nelements) connections = [] if chain_type == 1: - conn = create_refined_connection(queue, discr) + conn = create_refined_connection(actx, discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) elif chain_type == 2: - conn = create_refined_connection(queue, discr) + conn = create_refined_connection(actx, discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) - conn = create_refined_connection(queue, conn.to_discr) + conn = create_refined_connection(actx, conn.to_discr) connections.append(conn) elif chain_type == 3 and ndim == 3: - conn = create_refined_connection(queue, discr, threshold=np.inf) + conn = create_refined_connection(actx, discr, threshold=np.inf) connections.append(conn) - conn = create_face_connection(queue, conn.to_discr) + conn = create_face_connection(actx, conn.to_discr) connections.append(conn) else: raise ValueError('unknown test case') @@ -301,7 +302,7 @@ def test_chained_to_direct(ctx_factory, ndim, chain_type, chained = ChainedDiscretizationConnection(connections) t_start = time.time() - direct = flatten_chained_connection(queue, chained) + direct = flatten_chained_connection(actx, chained) t_end = time.time() if visualize: print('[TIME] Flatten: {:.5e}'.format(t_end - t_start)) @@ -317,19 +318,19 @@ def test_chained_to_direct(ctx_factory, ndim, chain_type, def f(x): from six.moves import reduce - return 0.1 * reduce(lambda x, y: x * cl.clmath.sin(5 * y), x) + return 0.1 * reduce(lambda x, y: x * actx.np.sin(5 * y), x) - x = connections[0].from_discr.nodes().with_queue(queue) + x = thaw(actx, connections[0].from_discr.nodes()) fx = f(x) t_start = time.time() - f1 = direct(queue, fx).get(queue) + f1 = actx.to_numpy(flatten(direct(fx))) t_end = time.time() if visualize: print('[TIME] Direct: {:.5e}'.format(t_end - t_start)) t_start = time.time() - f2 = chained(queue, fx).get(queue) + f2 = actx.to_numpy(flatten(chained(fx))) t_end = time.time() if visualize: print('[TIME] Chained: {:.5e}'.format(t_end - t_start)) @@ -351,27 +352,28 @@ def test_chained_to_direct(ctx_factory, ndim, chain_type, @pytest.mark.parametrize(("ndim", "mesh_name"), [ (2, "starfish"), (3, "torus")]) -def test_reversed_chained_connection(ctx_factory, ndim, mesh_name, visualize=False): +def test_reversed_chained_connection(ctx_factory, ndim, mesh_name): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) def run(nelements, order): - discr = create_discretization(queue, ndim, + discr = create_discretization(actx, ndim, nelements=nelements, order=order, mesh_name=mesh_name) threshold = 1.0 connections = [] - conn = create_refined_connection(queue, + conn = create_refined_connection(actx, discr, threshold=threshold) connections.append(conn) if ndim == 2: # NOTE: additional refinement makes the 3D meshes explode in size - conn = create_refined_connection(queue, + conn = create_refined_connection(actx, conn.to_discr, threshold=threshold) connections.append(conn) - conn = create_refined_connection(queue, + conn = create_refined_connection(actx, conn.to_discr, threshold=threshold) connections.append(conn) @@ -383,21 +385,19 @@ def test_reversed_chained_connection(ctx_factory, ndim, mesh_name, visualize=Fal reverse = L2ProjectionInverseDiscretizationConnection(chained) # create test vector - from_nodes = chained.from_discr.nodes().with_queue(queue) - to_nodes = chained.to_discr.nodes().with_queue(queue) + from_nodes = thaw(actx, chained.from_discr.nodes()) + to_nodes = thaw(actx, chained.to_discr.nodes()) from_x = 0 to_x = 0 for d in range(ndim): - from_x += cl.clmath.cos(from_nodes[d]) ** (d + 1) - to_x += cl.clmath.cos(to_nodes[d]) ** (d + 1) - - from_interp = reverse(queue, to_x) + from_x += actx.np.cos(from_nodes[d]) ** (d + 1) + to_x += actx.np.cos(to_nodes[d]) ** (d + 1) - from_interp = from_interp.get(queue) - from_x = from_x.get(queue) + from_interp = reverse(to_x) - return 1.0 / nelements, la.norm(from_interp - from_x) / la.norm(from_x) + return (1.0 / nelements, + flat_norm(from_interp - from_x, np.inf) / flat_norm(from_x, np.inf)) from pytools.convergence import EOCRecorder eoc = EOCRecorder() diff --git a/test/test_meshmode.py b/test/test_meshmode.py index 047362d20c1b297e22eed758fbe633bee01a82cc..17773c2086e0419705fac109175d77ebb34c0220 100644 --- a/test/test_meshmode.py +++ b/test/test_meshmode.py @@ -26,8 +26,6 @@ from six.moves import range import numpy as np import numpy.linalg as la import pyopencl as cl -import pyopencl.array # noqa -import pyopencl.clmath # noqa from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl @@ -40,6 +38,8 @@ from meshmode.discretization.poly_element import ( PolynomialEquidistantSimplexGroupFactory, ) from meshmode.mesh import Mesh, BTAG_ALL +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw, flat_norm, flatten, unflatten from meshmode.discretization.connection import \ FACE_RESTR_ALL, FACE_RESTR_INTERIOR import meshmode.mesh.generation as mgen @@ -59,7 +59,8 @@ def test_circle_mesh(do_plot=False): FileSource("circle.step"), 2, order=2, force_ambient_dim=2, other_options=[ - "-string", "Mesh.CharacteristicLengthMax = 0.05;"] + "-string", "Mesh.CharacteristicLengthMax = 0.05;"], + target_unit="MM", ) print("END GEN") print(mesh.nelements) @@ -223,6 +224,7 @@ def test_boundary_interpolation(ctx_factory, group_factory, boundary_tag, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( @@ -234,7 +236,7 @@ def test_boundary_interpolation(ctx_factory, group_factory, boundary_tag, order = 4 def f(x): - return 0.1*cl.clmath.sin(30*x) + return 0.1*actx.np.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh @@ -267,32 +269,31 @@ def test_boundary_interpolation(ctx_factory, group_factory, boundary_tag, # }}} - vol_discr = Discretization(cl_ctx, mesh, - group_factory(order)) + vol_discr = Discretization(actx, mesh, group_factory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) - x = vol_discr.nodes()[0].with_queue(queue) + x = thaw(actx, vol_discr.nodes()[0]) vol_f = f(x) bdry_connection = make_face_restriction( - vol_discr, group_factory(order), + actx, vol_discr, group_factory(order), boundary_tag, per_face_groups=per_face_groups) - check_connection(bdry_connection) + check_connection(actx, bdry_connection) bdry_discr = bdry_connection.to_discr - bdry_x = bdry_discr.nodes()[0].with_queue(queue) + bdry_x = thaw(actx, bdry_discr.nodes()[0]) bdry_f = f(bdry_x) - bdry_f_2 = bdry_connection(queue, vol_f) + bdry_f_2 = bdry_connection(vol_f) if mesh_name == "blob" and dim == 2 and mesh.nelements < 500: - mat = bdry_connection.full_resample_matrix(queue).get(queue) - bdry_f_2_by_mat = mat.dot(vol_f.get()) + mat = actx.to_numpy(bdry_connection.full_resample_matrix(actx)) + bdry_f_2_by_mat = mat.dot(actx.to_numpy(flatten(vol_f))) - mat_error = la.norm(bdry_f_2.get(queue=queue) - bdry_f_2_by_mat) + mat_error = la.norm(actx.to_numpy(flatten(bdry_f_2)) - bdry_f_2_by_mat) assert mat_error < 1e-14, mat_error - err = la.norm((bdry_f-bdry_f_2).get(), np.inf) + err = flat_norm(bdry_f-bdry_f_2, np.inf) eoc_rec.add_data_point(h, err) order_slack = 0.75 if mesh_name == "blob" else 0.5 @@ -316,6 +317,7 @@ def test_all_faces_interpolation(ctx_factory, mesh_name, dim, mesh_pars, per_face_groups): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( @@ -328,7 +330,7 @@ def test_all_faces_interpolation(ctx_factory, mesh_name, dim, mesh_pars, order = 4 def f(x): - return 0.1*cl.clmath.sin(30*x) + return 0.1*actx.np.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh @@ -344,7 +346,8 @@ def test_all_faces_interpolation(ctx_factory, mesh_name, dim, mesh_pars, FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ - "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + "-string", "Mesh.CharacteristicLengthMax = %s;" % h], + target_unit="MM", ) print("END GEN") elif mesh_name == "warp": @@ -357,20 +360,20 @@ def test_all_faces_interpolation(ctx_factory, mesh_name, dim, mesh_pars, # }}} - vol_discr = Discretization(cl_ctx, mesh, + vol_discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) all_face_bdry_connection = make_face_restriction( - vol_discr, PolynomialWarpAndBlendGroupFactory(order), + actx, vol_discr, PolynomialWarpAndBlendGroupFactory(order), FACE_RESTR_ALL, per_face_groups=per_face_groups) all_face_bdry_discr = all_face_bdry_connection.to_discr for ito_grp, ceg in enumerate(all_face_bdry_connection.groups): for ibatch, batch in enumerate(ceg.batches): assert np.array_equal( - batch.from_element_indices.get(queue), + actx.to_numpy(actx.thaw(batch.from_element_indices)), np.arange(vol_discr.mesh.nelements)) if per_face_groups: @@ -378,31 +381,31 @@ def test_all_faces_interpolation(ctx_factory, mesh_name, dim, mesh_pars, else: assert ibatch == batch.to_element_face - all_face_x = all_face_bdry_discr.nodes()[0].with_queue(queue) + all_face_x = thaw(actx, all_face_bdry_discr.nodes()[0]) all_face_f = f(all_face_x) - all_face_f_2 = all_face_bdry_discr.zeros(queue) + all_face_f_2 = all_face_bdry_discr.zeros(actx) for boundary_tag in [ BTAG_ALL, FACE_RESTR_INTERIOR, ]: bdry_connection = make_face_restriction( - vol_discr, PolynomialWarpAndBlendGroupFactory(order), + actx, vol_discr, PolynomialWarpAndBlendGroupFactory(order), boundary_tag, per_face_groups=per_face_groups) bdry_discr = bdry_connection.to_discr - bdry_x = bdry_discr.nodes()[0].with_queue(queue) + bdry_x = thaw(actx, bdry_discr.nodes()[0]) bdry_f = f(bdry_x) all_face_embedding = make_face_to_all_faces_embedding( - bdry_connection, all_face_bdry_discr) + actx, bdry_connection, all_face_bdry_discr) - check_connection(all_face_embedding) + check_connection(actx, all_face_embedding) - all_face_f_2 += all_face_embedding(queue, bdry_f) + all_face_f_2 = all_face_f_2 + all_face_embedding(bdry_f) - err = la.norm((all_face_f-all_face_f_2).get(), np.inf) + err = flat_norm(all_face_f-all_face_f_2, np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) @@ -431,6 +434,7 @@ def test_opposite_face_interpolation(ctx_factory, group_factory, cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( @@ -443,7 +447,7 @@ def test_opposite_face_interpolation(ctx_factory, group_factory, order = 5 def f(x): - return 0.1*cl.clmath.sin(30*x) + return 0.1*actx.np.sin(30*x) for mesh_par in mesh_pars: # {{{ get mesh @@ -467,7 +471,8 @@ def test_opposite_face_interpolation(ctx_factory, group_factory, FileSource("blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ - "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + "-string", "Mesh.CharacteristicLengthMax = %s;" % h], + target_unit="MM", ) print("END GEN") elif mesh_name == "warp": @@ -480,24 +485,24 @@ def test_opposite_face_interpolation(ctx_factory, group_factory, # }}} - vol_discr = Discretization(cl_ctx, mesh, + vol_discr = Discretization(actx, mesh, group_factory(order)) print("h=%s -> %d elements" % ( h, sum(mgrp.nelements for mgrp in mesh.groups))) bdry_connection = make_face_restriction( - vol_discr, group_factory(order), + actx, vol_discr, group_factory(order), FACE_RESTR_INTERIOR) bdry_discr = bdry_connection.to_discr - opp_face = make_opposite_face_connection(bdry_connection) - check_connection(opp_face) + opp_face = make_opposite_face_connection(actx, bdry_connection) + check_connection(actx, opp_face) - bdry_x = bdry_discr.nodes()[0].with_queue(queue) + bdry_x = thaw(actx, bdry_discr.nodes()[0]) bdry_f = f(bdry_x) - bdry_f_2 = opp_face(queue, bdry_f) + bdry_f_2 = opp_face(bdry_f) - err = la.norm((bdry_f-bdry_f_2).get(), np.inf) + err = flat_norm(bdry_f-bdry_f_2, np.inf) eoc_rec.add_data_point(h, err) print(eoc_rec) @@ -518,7 +523,8 @@ def test_element_orientation(): mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=mesh_order, force_ambient_dim=2, - other_options=["-string", "Mesh.CharacteristicLengthMax = 0.02;"] + other_options=["-string", "Mesh.CharacteristicLengthMax = 0.02;"], + target_unit="MM", ) from meshmode.mesh.processing import (perform_flips, @@ -555,13 +561,14 @@ def test_3d_orientation(ctx_factory, what, mesh_gen_func, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) mesh = mesh_gen_func() logger.info("%d elements" % mesh.nelements) from meshmode.discretization import Discretization - discr = Discretization(ctx, mesh, + discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(1)) from pytential import bind, sym @@ -582,17 +589,18 @@ def test_3d_orientation(ctx_factory, what, mesh_gen_func, visualize=False): normal_outward_expr = ( sym.normal(mesh.ambient_dim) | sym.nodes(mesh.ambient_dim)) - normal_outward_check = bind(discr, normal_outward_expr)(queue).as_scalar() > 0 + normal_outward_check = actx.to_numpy( + flatten(bind(discr, normal_outward_expr)(actx).as_scalar())) > 0 - assert normal_outward_check.get().all(), normal_outward_check.get() + assert normal_outward_check.all(), normal_outward_check # }}} - normals = bind(discr, sym.normal(mesh.ambient_dim).xproject(1))(queue) + normals = bind(discr, sym.normal(mesh.ambient_dim).xproject(1))(actx) if visualize: from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr, 1) + vis = make_visualizer(actx, discr, 1) vis.write_vtk_file("normals.vtu", [ ("normals", normals), @@ -616,7 +624,8 @@ def test_merge_and_map(ctx_factory, visualize=False): mesh = generate_gmsh( FileSource("blob-2d.step"), 2, order=mesh_order, force_ambient_dim=2, - other_options=["-string", "Mesh.CharacteristicLengthMax = 0.02;"] + other_options=["-string", "Mesh.CharacteristicLengthMax = 0.02;"], + target_unit="MM", ) discr_grp_factory = PolynomialWarpAndBlendGroupFactory(3) @@ -664,6 +673,7 @@ def test_sanity_single_element(ctx_factory, dim, order, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from modepy.tools import unit_vertices vertices = unit_vertices(dim).T.copy() @@ -684,21 +694,20 @@ def test_sanity_single_element(ctx_factory, dim, order, visualize=False): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory - vol_discr = Discretization(cl_ctx, mesh, + vol_discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(order+3)) # {{{ volume calculation check - vol_x = vol_discr.nodes().with_queue(queue) + vol_x = thaw(actx, vol_discr.nodes()) - vol_one = vol_x[0].copy() - vol_one.fill(1) + vol_one = vol_x[0] * 0 + 1 from pytential import norm, integral # noqa from pytools import factorial true_vol = 1/factorial(dim) * 2**dim - comp_vol = integral(vol_discr, queue, vol_one) + comp_vol = integral(vol_discr, vol_one) rel_vol_err = abs(true_vol - comp_vol) / true_vol assert rel_vol_err < 1e-12 @@ -709,7 +718,7 @@ def test_sanity_single_element(ctx_factory, dim, order, visualize=False): from meshmode.discretization.connection import make_face_restriction bdry_connection = make_face_restriction( - vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3), + actx, vol_discr, PolynomialWarpAndBlendGroupFactory(order + 3), BTAG_ALL) bdry_discr = bdry_connection.to_discr @@ -719,12 +728,12 @@ def test_sanity_single_element(ctx_factory, dim, order, visualize=False): from meshmode.discretization.visualization import make_visualizer #vol_vis = make_visualizer(queue, vol_discr, 4) - bdry_vis = make_visualizer(queue, bdry_discr, 4) + bdry_vis = make_visualizer(actx, bdry_discr, 4) # }}} from pytential import bind, sym - bdry_normals = bind(bdry_discr, sym.normal(dim))(queue).as_vector(dtype=object) + bdry_normals = bind(bdry_discr, sym.normal(dim))(actx).as_vector(dtype=object) if visualize: bdry_vis.write_vtk_file("boundary.vtu", [ @@ -734,9 +743,10 @@ def test_sanity_single_element(ctx_factory, dim, order, visualize=False): normal_outward_check = bind(bdry_discr, sym.normal(dim) | (sym.nodes(dim) + 0.5*sym.ones_vec(dim)), - )(queue).as_scalar() > 0 + )(actx).as_scalar() - assert normal_outward_check.get().all(), normal_outward_check.get() + normal_outward_check = actx.to_numpy(flatten(normal_outward_check) > 0) + assert normal_outward_check.all(), normal_outward_check # }}} @@ -752,6 +762,7 @@ def test_sanity_qhull_nd(ctx_factory, dim, order): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) from scipy.spatial import Delaunay verts = np.random.rand(1000, dim) @@ -762,28 +773,28 @@ def test_sanity_qhull_nd(ctx_factory, dim, order): fix_orientation=True) from meshmode.discretization import Discretization - low_discr = Discretization(ctx, mesh, + low_discr = Discretization(actx, mesh, PolynomialEquidistantSimplexGroupFactory(order)) - high_discr = Discretization(ctx, mesh, + high_discr = Discretization(actx, mesh, PolynomialEquidistantSimplexGroupFactory(order+1)) from meshmode.discretization.connection import make_same_mesh_connection - cnx = make_same_mesh_connection(high_discr, low_discr) + cnx = make_same_mesh_connection(actx, high_discr, low_discr) def f(x): - return 0.1*cl.clmath.sin(x) + return 0.1*actx.np.sin(x) - x_low = low_discr.nodes()[0].with_queue(queue) + x_low = thaw(actx, low_discr.nodes()[0]) f_low = f(x_low) - x_high = high_discr.nodes()[0].with_queue(queue) + x_high = thaw(actx, high_discr.nodes()[0]) f_high_ref = f(x_high) - f_high_num = cnx(queue, f_low) - - err = (f_high_ref-f_high_num).get() + f_high_num = cnx(f_low) - err = la.norm(err, np.inf)/la.norm(f_high_ref.get(), np.inf) + err = ( + flat_norm(f_high_ref-f_high_num, np.inf) + / flat_norm(f_high_ref, np.inf)) print(err) assert err < 1e-2 @@ -799,14 +810,14 @@ def test_sanity_qhull_nd(ctx_factory, dim, order): ("ball-radius-1.step", 3), ]) @pytest.mark.parametrize("mesh_order", [1, 2]) -def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, - visualize=False): +def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, visualize=False): pytest.importorskip("pytential") logging.basicConfig(level=logging.INFO) ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) from pytools.convergence import EOCRecorder vol_eoc_rec = EOCRecorder() @@ -822,18 +833,20 @@ def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, mesh = generate_gmsh( FileSource(src_file), dim, order=mesh_order, other_options=["-string", "Mesh.CharacteristicLengthMax = %g;" % h], - force_ambient_dim=dim) + force_ambient_dim=dim, + target_unit="MM") logger.info("%d elements" % mesh.nelements) # {{{ discretizations and connections from meshmode.discretization import Discretization - vol_discr = Discretization(ctx, mesh, + vol_discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(quad_order)) from meshmode.discretization.connection import make_face_restriction bdry_connection = make_face_restriction( + actx, vol_discr, InterpolatoryQuadratureSimplexGroupFactory(quad_order), BTAG_ALL) @@ -844,8 +857,8 @@ def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, # {{{ visualizers from meshmode.discretization.visualization import make_visualizer - vol_vis = make_visualizer(queue, vol_discr, 20) - bdry_vis = make_visualizer(queue, bdry_discr, 20) + vol_vis = make_visualizer(actx, vol_discr, 20) + bdry_vis = make_visualizer(actx, bdry_discr, 20) # }}} @@ -853,27 +866,25 @@ def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, true_surf = 2*np.pi**(dim/2)/gamma(dim/2) true_vol = true_surf/dim - vol_x = vol_discr.nodes().with_queue(queue) + vol_x = thaw(actx, vol_discr.nodes()) - vol_one = vol_x[0].copy() - vol_one.fill(1) + vol_one = vol_x[0]*0 + 1 from pytential import norm, integral # noqa - comp_vol = integral(vol_discr, queue, vol_one) + comp_vol = integral(vol_discr, vol_one) rel_vol_err = abs(true_vol - comp_vol) / true_vol vol_eoc_rec.add_data_point(h, rel_vol_err) print("VOL", true_vol, comp_vol) - bdry_x = bdry_discr.nodes().with_queue(queue) + bdry_x = thaw(actx, bdry_discr.nodes()) - bdry_one_exact = bdry_x[0].copy() - bdry_one_exact.fill(1) + bdry_one_exact = bdry_x[0] * 0 + 1 - bdry_one = bdry_connection(queue, vol_one).with_queue(queue) - intp_err = norm(bdry_discr, queue, bdry_one-bdry_one_exact) + bdry_one = bdry_connection(vol_one) + intp_err = norm(bdry_discr, bdry_one-bdry_one_exact) assert intp_err < 1e-14 - comp_surf = integral(bdry_discr, queue, bdry_one) + comp_surf = integral(bdry_discr, bdry_one) rel_surf_err = abs(true_surf - comp_surf) / true_surf surf_eoc_rec.add_data_point(h, rel_surf_err) print("SURF", true_surf, comp_surf) @@ -884,7 +895,7 @@ def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, ("area_el", bind( vol_discr, sym.area_element(mesh.ambient_dim, mesh.ambient_dim)) - (queue)), + (actx)), ]) bdry_vis.write_vtk_file("boundary-h=%g.vtu" % h, [("f", bdry_one)]) @@ -892,9 +903,10 @@ def test_sanity_balls(ctx_factory, src_file, dim, mesh_order, normal_outward_check = bind(bdry_discr, sym.normal(mesh.ambient_dim) | sym.nodes(mesh.ambient_dim), - )(queue).as_scalar() > 0 + )(actx).as_scalar() - assert normal_outward_check.get().all(), normal_outward_check.get() + normal_outward_check = actx.to_numpy(flatten(normal_outward_check) > 0) + assert normal_outward_check.all(), normal_outward_check # }}} @@ -1044,6 +1056,7 @@ def test_quad_mesh_2d(): """, ["blob-2d.step"]), force_ambient_dim=2, + target_unit="MM", ) print("END GEN") print(mesh.nelements) @@ -1135,7 +1148,7 @@ def test_quad_multi_element(): # {{{ test_vtk_overwrite -def test_vtk_overwrite(ctx_getter): +def test_vtk_overwrite(ctx_factory): pytest.importorskip("pyvisfile") def _try_write_vtk(writer, obj): @@ -1154,8 +1167,9 @@ def test_vtk_overwrite(ctx_getter): if os.path.exists(filename): os.remove(filename) - ctx = ctx_getter() + ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) target_order = 7 @@ -1166,7 +1180,7 @@ def test_vtk_overwrite(ctx_getter): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory discr = Discretization( - queue.context, mesh, + actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from meshmode.discretization.visualization import make_visualizer @@ -1174,7 +1188,7 @@ def test_vtk_overwrite(ctx_getter): write_nodal_adjacency_vtk_file from meshmode.mesh.visualization import write_vertex_vtk_file - vis = make_visualizer(queue, discr, 1) + vis = make_visualizer(actx, discr, 1) _try_write_vtk(vis.write_vtk_file, discr) _try_write_vtk(lambda x, y, **kwargs: @@ -1197,7 +1211,8 @@ def test_mesh_to_tikz(): FileSource("../test/blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ - "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + "-string", "Mesh.CharacteristicLengthMax = %s;" % h], + target_unit="MM", ) from meshmode.mesh.visualization import mesh_to_tikz @@ -1227,6 +1242,7 @@ def test_affine_map(): def test_mesh_without_vertices(ctx_factory): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) # create a mesh from meshmode.mesh.generation import generate_icosphere @@ -1246,11 +1262,11 @@ def test_mesh_without_vertices(ctx_factory): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory as GroupFactory - discr = Discretization(ctx, mesh, GroupFactory(4)) - discr.nodes().with_queue(queue) + discr = Discretization(actx, mesh, GroupFactory(4)) + thaw(actx, discr.nodes()) from meshmode.discretization.visualization import make_visualizer - make_visualizer(queue, discr, 4) + make_visualizer(actx, discr, 4) @pytest.mark.parametrize("curve_name", ["ellipse", "arc"]) @@ -1352,6 +1368,7 @@ def test_is_affine_group_check(mesh_name): def test_mesh_multiple_groups(ctx_factory, ambient_dim, visualize=False): ctx = ctx_factory() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) order = 4 @@ -1385,16 +1402,16 @@ def test_mesh_multiple_groups(ctx_factory, ambient_dim, visualize=False): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory as GroupFactory - discr = Discretization(ctx, mesh, GroupFactory(order)) + discr = Discretization(actx, mesh, GroupFactory(order)) if visualize: - group_id = discr.empty(queue, dtype=np.int) + group_id = discr.empty(actx, dtype=np.int) for igrp, grp in enumerate(discr.groups): group_id_view = grp.view(group_id) group_id_view.fill(igrp) from meshmode.discretization.visualization import make_visualizer - vis = make_visualizer(queue, discr, vis_order=order) + vis = make_visualizer(actx, discr, vis_order=order) vis.write_vtk_file("test_mesh_multiple_groups.vtu", [ ("group_id", group_id) ], overwrite=True) @@ -1406,31 +1423,63 @@ def test_mesh_multiple_groups(ctx_factory, ambient_dim, visualize=False): make_opposite_face_connection, check_connection) for boundary_tag in [BTAG_ALL, FACE_RESTR_INTERIOR, FACE_RESTR_ALL]: - conn = make_face_restriction(discr, GroupFactory(order), + conn = make_face_restriction(actx, discr, GroupFactory(order), boundary_tag=boundary_tag, per_face_groups=False) - check_connection(conn) + check_connection(actx, conn) - bdry_f = conn.to_discr.empty(queue) - bdry_f.fill(1.0) + bdry_f = conn.to_discr.zeros(actx) + 1 if boundary_tag == FACE_RESTR_INTERIOR: - opposite = make_opposite_face_connection(conn) - check_connection(opposite) + opposite = make_opposite_face_connection(actx, conn) + check_connection(actx, opposite) - op_bdry_f = opposite(queue, bdry_f) - error = abs(cl.array.sum(bdry_f - op_bdry_f).get(queue)) + op_bdry_f = opposite(bdry_f) + error = flat_norm(bdry_f - op_bdry_f, np.inf) assert error < 1.0e-11, error if boundary_tag == FACE_RESTR_ALL: - embedding = make_face_to_all_faces_embedding(conn, conn.to_discr) - check_connection(embedding) + embedding = make_face_to_all_faces_embedding(actx, conn, conn.to_discr) + check_connection(actx, embedding) - em_bdry_f = embedding(queue, bdry_f) - error = abs(cl.array.sum(bdry_f - em_bdry_f).get(queue)) + em_bdry_f = embedding(bdry_f) + error = flat_norm(bdry_f - em_bdry_f) assert error < 1.0e-11, error +def test_array_context_np_workalike(ctx_factory): + ctx = ctx_factory() + queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) + + from meshmode.mesh.generation import generate_regular_rect_mesh + mesh = generate_regular_rect_mesh( + a=(-0.5,)*2, b=(0.5,)*2, n=(8,)*2, order=3) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + PolynomialWarpAndBlendGroupFactory as GroupFactory + discr = Discretization(actx, mesh, GroupFactory(3)) + + for sym_name, n_args in [ + ("sin", 1), + ("exp", 1), + ("arctan2", 2), + ("minimum", 2), + ("maximum", 2), + ("where", 3), + ]: + args = [np.random.randn(discr.ndofs) for i in range(n_args)] + ref_result = getattr(np, sym_name)(*args) + + actx_args = [unflatten(actx, discr, actx.from_numpy(arg)) for arg in args] + + actx_result = actx.to_numpy( + flatten(getattr(actx.np, sym_name)(*actx_args))) + + assert np.allclose(actx_result, ref_result) + + if __name__ == "__main__": import sys if len(sys.argv) > 1: diff --git a/test/test_partition.py b/test/test_partition.py index 54b9d88923249b553c334a278744e2f51ab14c44..1e8e6c7fb5d556807375dc3e132b4e1b1c3f3a09 100644 --- a/test/test_partition.py +++ b/test/test_partition.py @@ -27,10 +27,10 @@ THE SOFTWARE. from six.moves import range import numpy as np -import numpy.linalg as la import pyopencl as cl -import pyopencl.array # noqa -import pyopencl.clmath # noqa + +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw, flatten, unflatten, flat_norm from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl @@ -69,6 +69,8 @@ def test_partition_interpolation(ctx_factory, dim, mesh_pars, group_factory = PolynomialWarpAndBlendGroupFactory cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) + order = 4 from pytools.convergence import EOCRecorder @@ -80,7 +82,7 @@ def test_partition_interpolation(ctx_factory, dim, mesh_pars, eoc_rec[i, j] = EOCRecorder() def f(x): - return 10.*cl.clmath.sin(50.*x) + return 10.*actx.np.sin(50.*x) for n in mesh_pars: from meshmode.mesh.generation import generate_warped_rect_mesh @@ -107,7 +109,7 @@ def test_partition_interpolation(ctx_factory, dim, mesh_pars, partition_mesh(mesh, part_per_element, i)[0] for i in range(num_parts)] from meshmode.discretization import Discretization - vol_discrs = [Discretization(cl_ctx, part_meshes[i], group_factory(order)) + vol_discrs = [Discretization(actx, part_meshes[i], group_factory(order)) for i in range(num_parts)] from meshmode.mesh import BTAG_PARTITION @@ -120,23 +122,26 @@ def test_partition_interpolation(ctx_factory, dim, mesh_pars, continue # Mark faces within local_mesh that are connected to remote_mesh - local_bdry_conn = make_face_restriction(vol_discrs[i_local_part], + local_bdry_conn = make_face_restriction(actx, vol_discrs[i_local_part], group_factory(order), BTAG_PARTITION(i_remote_part)) # If these parts are not connected, don't bother checking the error - bdry_nodes = local_bdry_conn.to_discr.nodes() - if bdry_nodes.size == 0: + bdry_nelements = sum( + grp.nelements for grp in local_bdry_conn.to_discr.groups) + if bdry_nelements == 0: eoc_rec[i_local_part, i_remote_part] = None continue # Mark faces within remote_mesh that are connected to local_mesh - remote_bdry_conn = make_face_restriction(vol_discrs[i_remote_part], + remote_bdry_conn = make_face_restriction(actx, vol_discrs[i_remote_part], group_factory(order), BTAG_PARTITION(i_local_part)) - assert bdry_nodes.size == remote_bdry_conn.to_discr.nodes().size, \ - "partitions do not have the same number of connected nodes" + remote_bdry_nelements = sum( + grp.nelements for grp in remote_bdry_conn.to_discr.groups) + assert bdry_nelements == remote_bdry_nelements, \ + "partitions do not have the same number of connected elements" # Gather just enough information for the connection local_bdry = local_bdry_conn.to_discr @@ -166,27 +171,25 @@ def test_partition_interpolation(ctx_factory, dim, mesh_pars, for grp_batches in remote_batches] # Connect from remote_mesh to local_mesh - remote_to_local_conn = make_partition_connection(local_bdry_conn, - i_local_part, - remote_bdry, - remote_adj_groups, - remote_from_elem_faces, - remote_from_elem_indices) + remote_to_local_conn = make_partition_connection( + actx, local_bdry_conn, i_local_part, remote_bdry, + remote_adj_groups, remote_from_elem_faces, + remote_from_elem_indices) + # Connect from local mesh to remote mesh - local_to_remote_conn = make_partition_connection(remote_bdry_conn, - i_remote_part, - local_bdry, - local_adj_groups, - local_from_elem_faces, - local_from_elem_indices) - check_connection(remote_to_local_conn) - check_connection(local_to_remote_conn) - - true_local_points = f(local_bdry.nodes()[0].with_queue(queue)) - remote_points = local_to_remote_conn(queue, true_local_points) - local_points = remote_to_local_conn(queue, remote_points) - - err = la.norm((true_local_points - local_points).get(), np.inf) + local_to_remote_conn = make_partition_connection( + actx, remote_bdry_conn, i_remote_part, local_bdry, + local_adj_groups, local_from_elem_faces, + local_from_elem_indices) + + check_connection(actx, remote_to_local_conn) + check_connection(actx, local_to_remote_conn) + + true_local_points = f(thaw(actx, local_bdry.nodes()[0])) + remote_points = local_to_remote_conn(true_local_points) + local_points = remote_to_local_conn(remote_points) + + err = flat_norm(true_local_points - local_points, np.inf) eoc_rec[i_local_part, i_remote_part].add_data_point(1./n, err) for (i, j), e in eoc_rec.items(): @@ -359,9 +362,10 @@ def _test_mpi_boundary_swap(dim, order, num_groups): group_factory = PolynomialWarpAndBlendGroupFactory(order) cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.discretization import Discretization - vol_discr = Discretization(cl_ctx, local_mesh, group_factory) + vol_discr = Discretization(actx, local_mesh, group_factory) from meshmode.distributed import get_connected_partitions connected_parts = get_connected_partitions(local_mesh) @@ -373,11 +377,11 @@ def _test_mpi_boundary_swap(dim, order, num_groups): from meshmode.mesh import BTAG_PARTITION for i_remote_part in connected_parts: local_bdry_conns[i_remote_part] = make_face_restriction( - vol_discr, group_factory, BTAG_PARTITION(i_remote_part)) + actx, vol_discr, group_factory, BTAG_PARTITION(i_remote_part)) setup_helper = bdry_setup_helpers[i_remote_part] = \ MPIBoundaryCommSetupHelper( - mpi_comm, queue, local_bdry_conns[i_remote_part], + mpi_comm, actx, local_bdry_conns[i_remote_part], i_remote_part, bdry_grp_factory=group_factory) setup_helper.post_sends() @@ -389,14 +393,14 @@ def _test_mpi_boundary_swap(dim, order, num_groups): if setup_helper.is_setup_ready(): assert bdry_setup_helpers.pop(i_remote_part) is setup_helper conn = setup_helper.complete_setup() - check_connection(conn) + check_connection(actx, conn) remote_to_local_bdry_conns[i_remote_part] = conn break # FIXME: Not ideal, busy-waits _test_data_transfer(mpi_comm, - queue, + actx, local_bdry_conns, remote_to_local_bdry_conns, connected_parts) @@ -405,12 +409,12 @@ def _test_mpi_boundary_swap(dim, order, num_groups): # TODO -def _test_data_transfer(mpi_comm, queue, local_bdry_conns, +def _test_data_transfer(mpi_comm, actx, local_bdry_conns, remote_to_local_bdry_conns, connected_parts): from mpi4py import MPI def f(x): - return 10*cl.clmath.sin(20.*x) + return 10*actx.np.sin(20.*x) ''' Here is a simplified example of what happens from @@ -435,13 +439,13 @@ def _test_data_transfer(mpi_comm, queue, local_bdry_conns, for i_remote_part in connected_parts: conn = remote_to_local_bdry_conns[i_remote_part] bdry_discr = local_bdry_conns[i_remote_part].to_discr - bdry_x = bdry_discr.nodes()[0].with_queue(queue=queue) + bdry_x = thaw(actx, bdry_discr.nodes()[0]) true_local_f = f(bdry_x) - remote_f = conn(queue, true_local_f) + remote_f = conn(true_local_f) # 2. - send_reqs.append(mpi_comm.isend(remote_f.get(queue=queue), + send_reqs.append(mpi_comm.isend(actx.to_numpy(flatten(remote_f)), dest=i_remote_part, tag=TAG_SEND_REMOTE_NODES)) @@ -471,12 +475,9 @@ def _test_data_transfer(mpi_comm, queue, local_bdry_conns, send_reqs = [] for i_remote_part in connected_parts: conn = remote_to_local_bdry_conns[i_remote_part] - local_f_np = remote_to_local_f_data[i_remote_part] - local_f_cl = cl.array.Array(queue, - shape=local_f_np.shape, - dtype=local_f_np.dtype) - local_f_cl.set(local_f_np) - remote_f = conn(queue, local_f_cl).get(queue=queue) + local_f = unflatten(actx, conn.from_discr, + actx.from_numpy(remote_to_local_f_data[i_remote_part])) + remote_f = actx.to_numpy(flatten(conn(local_f))) # 5. send_reqs.append(mpi_comm.isend(remote_f, @@ -508,9 +509,9 @@ def _test_data_transfer(mpi_comm, queue, local_bdry_conns, # 7. for i_remote_part in connected_parts: bdry_discr = local_bdry_conns[i_remote_part].to_discr - bdry_x = bdry_discr.nodes()[0].with_queue(queue=queue) + bdry_x = thaw(actx, bdry_discr.nodes()[0]) - true_local_f = f(bdry_x).get(queue=queue) + true_local_f = actx.to_numpy(flatten(f(bdry_x))) local_f = local_f_data[i_remote_part] from numpy.linalg import norm @@ -554,7 +555,7 @@ if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from py.test.cmdline import main + from pytest import main main([__file__]) # vim: fdm=marker diff --git a/test/test_refinement.py b/test/test_refinement.py index 2bc29e89266f4e7a5ae824335d719cb259412ea6..94a4fe0879533163953ae831cb4541b68ca2a951 100644 --- a/test/test_refinement.py +++ b/test/test_refinement.py @@ -27,12 +27,13 @@ from functools import partial import pytest import pyopencl as cl -import pyopencl.clmath # noqa import numpy as np from pyopencl.tools import ( # noqa pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from meshmode.array_context import PyOpenCLArrayContext +from meshmode.dof_array import thaw, flat_norm from meshmode.mesh.generation import ( # noqa generate_icosahedron, generate_box_mesh, make_curve_mesh, ellipse) from meshmode.mesh.refinement.utils import check_nodal_adj_against_geometry @@ -191,6 +192,7 @@ def test_refinement_connection( cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) + actx = PyOpenCLArrayContext(queue) from meshmode.discretization import Discretization from meshmode.discretization.connection import ( @@ -235,27 +237,27 @@ def test_refinement_connection( factor = 9 for iaxis in range(len(x)): - result = result * cl.clmath.sin(factor * (x[iaxis]/mesh_ext[iaxis])) + result = result * actx.np.sin(factor * (x[iaxis]/mesh_ext[iaxis])) return result - discr = Discretization(cl_ctx, mesh, group_factory(order)) + discr = Discretization(actx, mesh, group_factory(order)) refiner = refiner_cls(mesh) flags = refine_flags(mesh) refiner.refine(flags) connection = make_refinement_connection( - refiner, discr, group_factory(order)) - check_connection(connection) + actx, refiner, discr, group_factory(order)) + check_connection(actx, connection) fine_discr = connection.to_discr - x = discr.nodes().with_queue(queue) - x_fine = fine_discr.nodes().with_queue(queue) + x = thaw(actx, discr.nodes()) + x_fine = thaw(actx, fine_discr.nodes()) f_coarse = f(x) - f_interp = connection(queue, f_coarse).with_queue(queue) - f_true = f(x_fine).with_queue(queue) + f_interp = connection(f_coarse) + f_true = f(x_fine) if visualize == "dots": import matplotlib.pyplot as plt @@ -279,8 +281,7 @@ def test_refinement_connection( ("f_true", f_true), ]) - import numpy.linalg as la - err = la.norm((f_interp - f_true).get(queue), np.inf) + err = flat_norm(f_interp - f_true, np.inf) eoc_rec.add_data_point(h, err) order_slack = 0.5