From e3e667ac23801507b1da9584d68507ad1b518891 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 20 Sep 2016 15:04:18 -0500 Subject: [PATCH 01/62] [wip] Add refinement. --- pytential/qbx/refinement.py | 1047 +++++++++++++++++++++++++++++++++++ test/extra_curve_data.py | 132 +++++ test/test_geometry.py | 179 ++++++ 3 files changed, 1358 insertions(+) create mode 100644 pytential/qbx/refinement.py create mode 100644 test/extra_curve_data.py create mode 100644 test/test_geometry.py diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py new file mode 100644 index 00000000..e20b5764 --- /dev/null +++ b/pytential/qbx/refinement.py @@ -0,0 +1,1047 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function +from six.moves import range, zip + +__copyright__ = """ +Copyright (C) 2013 Andreas Kloeckner +Copyright (C) 2016 Matt Wala +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import loopy as lp +import numpy as np +from pytools import memoize_method +from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory + +import pyopencl as cl + +import logging +logger = logging.getLogger(__name__) + + +# {{{ layer potential source + +class NewQBXLayerPotentialSource(object): + """A source discretization for a QBX layer potential. + + .. attribute :: density_discr + .. attribute :: qbx_order + .. attribute :: fmm_order + .. attribute :: cl_context + .. automethod :: centers + .. automethod :: panel_sizes + .. automethod :: weights_and_area_elements + + See :ref:`qbxguts` for some information on the inner workings of this. + """ + def __init__(self, density_discr, fine_order, + qbx_order=None, fmm_order=None, + qbx_level_to_order=None, fmm_level_to_order=None, + # FIXME set debug=False once everything works + real_dtype=np.float64, debug=True, + performance_data_file=None): + """ + :arg fine_order: The total degree to which the (upsampled) + underlying quadrature is exact. + :arg fmm_order: `False` for direct calculation. ``None`` will set + a reasonable(-ish?) default. + """ + + self.fine_density_discr = Discretization( + density_discr.cl_context, density_discr.mesh, + QuadratureSimplexGroupFactory(fine_order), real_dtype) + + from meshmode.discretization.connection import make_same_mesh_connection + self.resampler = make_same_mesh_connection( + self.fine_density_discr, density_discr) + + if fmm_level_to_order is None: + if fmm_order is None and qbx_order is not None: + fmm_order = qbx_order + 1 + + if qbx_order is not None and qbx_level_to_order is not None: + raise TypeError("may not specify both qbx_order an qbx_level_to_order") + if fmm_order is not None and fmm_level_to_order is not None: + raise TypeError("may not specify both fmm_order an fmm_level_to_order") + + if fmm_level_to_order is None: + if fmm_order is False: + fmm_level_to_order = False + else: + def fmm_level_to_order(level): + return fmm_order + + if qbx_level_to_order is None: + def qbx_level_to_order(level): + return qbx_order + + self.fine_order = fine_order + self.qbx_level_to_order = qbx_level_to_order + self.density_discr = density_discr + self.fmm_level_to_order = fmm_level_to_order + self.debug = debug + self.performance_data_file = performance_data_file + + def el_view(self, discr, group_nr, global_array): + """Return a view of *global_array* of shape + ``(..., discr.groups[group_nr].nelements)`` + where *global_array* is of shape ``(..., nelements)``, + where *nelements* is the global (per-discretization) node count. + """ + + group = discr.groups[group_nr] + el_nr_base = sum(group.nelements for group in discr.groups[:group_nr]) + + return global_array[ + ..., el_nr_base:el_nr_base + group.nelements] \ + .reshape( + global_array.shape[:-1] + + (group.nelements,)) + + @property + def ambient_dim(self): + return self.density_discr.ambient_dim + + @property + def cl_context(self): + return self.density_discr.cl_context + + @property + def real_dtype(self): + return self.density_discr.real_dtype + + @property + def complex_dtype(self): + return self.density_discr.complex_dtype + + @memoize_method + def panel_centers_of_mass(self): + knl = lp.make_kernel( + """{[dim,k,i]: + 0<=dim +typedef ${dtype_to_ctype(vec_types_dict[coord_dtype, dimensions])} coord_vec_t; +""" + + +REFINER_MAKO_DEFS = r"""//CL:mako// +<%def name="load_particle(particle, coords)"> + <% zerovect = ["0"] * 2 ** (dimensions - 1).bit_length() %> + /* Zero initialize, to allow for use in distance computations. */ + ${coords} = (coord_vec_t) (${", ".join(zerovect)}); + + %for ax in AXIS_NAMES[:dimensions]: + ${coords}.${ax} = sources_${ax}[${particle}]; + %endfor + +""" + + +TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( + arguments=r"""//CL:mako// + /* input */ + particle_id_t source_offset, + particle_id_t panel_offset, + int npanels, + particle_id_t *panel_to_source_starts, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + + /* output */ + float *tunnel_query_dists, + + /* input, dim-dependent size */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *sources_${ax}, + %endfor + """, + operation=REFINER_MAKO_DEFS + REFINER_C_MACROS + r"""//CL:mako// + /* Find my panel. */ + particle_id_t panel = bsearch(panel_to_source_starts, npanels + 1, i); + + /* Compute dist(tunnel region, panel center) */ + + coord_vec_t center_of_mass; + ${load_particle("INDEX_FOR_PANEL_PARTICLE(panel)", "center_of_mass")} + + coord_vec_t center; + ${load_particle("INDEX_FOR_SOURCE_PARTICLE(i)", "center")} + + coord_t panel_size = panel_sizes[panel]; + + coord_t max_dist = 0; + + %for ax in AXIS_NAMES[:dimensions]: + { + max_dist = fmax(max_dist, + distance(center_of_mass.${ax}, center.${ax} + panel_size / 2)); + max_dist = fmax(max_dist, + distance(center_of_mass.${ax}, center.${ax} - panel_size / 2)); + } + %endfor + + // The atomic max operation supports only integer types. + // However, max_dist is of a floating point type. + // For comparison purposes we reinterpret the bits of max_dist + // as an integer. The comparison result is the same as for positive + // IEEE floating point numbers, so long as the float/int endianness + // matches (fingers crossed). + atomic_max( + (volatile __global int *) + &tunnel_query_dists[panel], + as_int((float) max_dist)); + """, + name="find_tunnel_query_distance", + preamble=str(InlineBinarySearch("particle_id_t"))) + + +# Implements "Algorithm for triggering refinement based on Condition 1" +CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( + extra_args=r""" + /* input */ + particle_id_t *box_to_panel_starts, + particle_id_t *box_to_panel_lists, + /* XXX: starts dtype */ + particle_id_t *panel_to_source_starts, + particle_id_t *panel_to_center_starts, + particle_id_t source_offset, + particle_id_t center_offset, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + int npanels, + coord_t r_max, + + /* output */ + int *panel_refine_flags, + int *found_panel_to_refine, + + /* input, dim-dependent length */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *sources_${ax}, + %endfor + """, + ball_center_and_radius_expr=REFINER_MAKO_DEFS + REFINER_C_MACROS + r""" + particle_id_t my_panel = bsearch(panel_to_center_starts, npanels + 1, i); + + ${load_particle("INDEX_FOR_CENTER_PARTICLE(i)", ball_center)} + ${ball_radius} = r_max + panel_sizes[my_panel] / 2; + """, + leaf_found_op=REFINER_MAKO_DEFS + r""" + for (particle_id_t panel_idx = box_to_panel_starts[${leaf_box_id}]; + panel_idx < box_to_panel_starts[${leaf_box_id} + 1]; + ++panel_idx) + { + particle_id_t panel = box_to_panel_lists[panel_idx]; + + // Skip self. + if (my_panel == panel) + { + continue; + } + + bool is_close = false; + + for (particle_id_t source = panel_to_source_starts[panel]; + source < panel_to_source_starts[panel + 1]; + ++source) + { + coord_vec_t source_coords; + + ${load_particle( + "INDEX_FOR_SOURCE_PARTICLE(source)", "source_coords")} + + is_close |= ( + distance(${ball_center}, source_coords) + <= panel_sizes[my_panel] / 2); + } + + if (is_close) + { + panel_refine_flags[my_panel] = 1; + *found_panel_to_refine = 1; + break; + } + } + """, + name="refine_center_to_own_panel", + preamble=str(InlineBinarySearch("particle_id_t"))) + + +# Implements "Algorithm for triggering refinement based on Condition 2" +CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( + extra_args=r""" + /* input */ + particle_id_t *box_to_panel_starts, + particle_id_t *box_to_panel_lists, + particle_id_t *panel_to_source_starts, + particle_id_t *panel_to_center_starts, + particle_id_t source_offset, + particle_id_t center_offset, + particle_id_t panel_offset, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + /* XXX: data type... */ + particle_id_t *panel_adjacency_starts, + particle_id_t *panel_adjacency_lists, + int npanels, + coord_t *tunnel_query_dists, + + /* output */ + int *panel_refine_flags, + int *found_panel_to_refine, + + /* input, dim-dependent length */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *sources_${ax}, + %endfor + """, + ball_center_and_radius_expr=REFINER_MAKO_DEFS + REFINER_C_MACROS + r""" + particle_id_t my_panel = bsearch(panel_to_center_starts, npanels + 1, i); + coord_vec_t my_center_coords; + + ${load_particle("INDEX_FOR_CENTER_PARTICLE(i)", "my_center_coords")} + ${load_particle("INDEX_FOR_PANEL_PARTICLE(my_panel)", ball_center)} + ${ball_radius} = tunnel_query_dists[my_panel]; + """, + leaf_found_op=REFINER_MAKO_DEFS + r""" + for (particle_id_t panel_idx = box_to_panel_starts[${leaf_box_id}]; + panel_idx < box_to_panel_starts[${leaf_box_id} + 1]; + ++panel_idx) + { + particle_id_t panel = box_to_panel_lists[panel_idx]; + + bool is_self_or_adjacent = (my_panel == panel); + + for (particle_id_t adj_panel_idx = panel_adjacency_starts[my_panel]; + adj_panel_idx < panel_adjacency_starts[my_panel + 1]; + ++adj_panel_idx) + { + is_self_or_adjacent |= ( + panel_adjacency_lists[adj_panel_idx] == panel); + } + + // Skip self and adjacent panels. + if (is_self_or_adjacent) + { + continue; + } + + bool is_close = false; + + for (particle_id_t source = panel_to_source_starts[panel]; + source < panel_to_source_starts[panel + 1]; + ++source) + { + coord_vec_t source_coords; + + ${load_particle( + "INDEX_FOR_SOURCE_PARTICLE(source)", "source_coords")} + + is_close |= ( + distance(my_center_coords, source_coords) + <= panel_sizes[panel] / 2); + } + + if (is_close) + { + panel_refine_flags[my_panel] = 1; + *found_panel_to_refine = 1; + break; + } + } + """, + name="refine_center_to_other_panel", + preamble=str(InlineBinarySearch("particle_id_t"))) + + +from boxtree.tree import Tree + + +class TreeWithQBXMetadata(Tree): + """ + .. attribute:: box_to_qbx_panel_starts + .. attribute:: box_to_qbx_panel_lists + .. attribute:: qbx_panel_to_source_starts + .. attribute:: qbx_panel_to_center_starts + .. attribute:: qbx_user_source_range + .. attribute:: qbx_user_center_range + .. attribute:: qbx_user_panel_range + XXX + """ + pass + + +class QBXLayerPotentialSourceRefiner(object): + + def __init__(self, context): + self.context = context + from boxtree.tree_build import TreeBuilder + self.tree_builder = TreeBuilder(self.context) + from boxtree.area_query import PeerListFinder + self.peer_list_finder = PeerListFinder(self.context) + + # {{{ tree creation + + def create_tree(self, queue, lpot_source): + # The ordering of particles is as follows: + # - sources go first + # - then centers + # - then panels (=centers of mass) + + sources = lpot_source.density_discr.nodes() + centers = self.get_interleaved_centers(queue, lpot_source) + centers_of_mass = lpot_source.panel_centers_of_mass() + + particles = tuple( + cl.array.concatenate(dim_coords, queue=queue) + for dim_coords in zip(sources, centers, centers_of_mass)) + + nparticles = len(particles[0]) + npanels = len(centers_of_mass[0]) + nsources = len(sources[0]) + ncenters = len(centers[0]) + assert 2 * nsources == ncenters + + qbx_user_source_range = range(0, nsources) + nsourcescenters = 3 * nsources + qbx_user_center_range = range(nsources, nsourcescenters) + qbx_user_panel_range = range(nsourcescenters, nsourcescenters + npanels) + + # Build tree with sources, centers, and centers of mass. Split boxes + # only because of sources. + refine_weights = cl.array.zeros(queue, nparticles, np.int32) + refine_weights[:nsources].fill(1) + MAX_REFINE_WEIGHT = 30 + + refine_weights.finish() + + tree, evt = self.tree_builder(queue, particles, + max_leaf_refine_weight=MAX_REFINE_WEIGHT, + refine_weights=refine_weights) + + # Compute box => panel relation + qbx_panel_flags = refine_weights + qbx_panel_flags.fill(0) + qbx_panel_flags[3 * nsources:].fill(1) + qbx_panel_flags.finish() + + from boxtree.tree import filter_target_lists_in_user_order + box_to_qbx_panel = \ + filter_target_lists_in_user_order(queue, tree, qbx_panel_flags).\ + with_queue(queue) + # Fix up offset. + box_to_qbx_panel.target_lists -= 3 * nsources + + qbx_panel_to_source_starts = cl.array.empty( + queue, npanels + 1, dtype=tree.particle_id_dtype) + + # Compute panel => source relation + el_offset = 0 + for group in lpot_source.density_discr.groups: + qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ + cl.array.arange(queue, group.node_nr_base, + group.node_nr_base + group.nnodes, + group.nunit_nodes, + dtype=tree.particle_id_dtype) + el_offset += group.nelements + qbx_panel_to_source_starts[-1] = nsources + + # Compute panel => center relation + qbx_panel_to_center_starts = 2 * qbx_panel_to_source_starts + + # Transfer all tree attributes. + tree_attrs = {} + for attr_name in tree.__class__.fields: + try: + tree_attrs[attr_name] = getattr(tree, attr_name) + except AttributeError: + pass + + logger.info("refiner: done building tree") + + # XXX: evt management + return TreeWithQBXMetadata( + box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, + box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, + qbx_panel_to_source_starts=qbx_panel_to_source_starts, + qbx_panel_to_center_starts=qbx_panel_to_center_starts, + qbx_user_source_range=qbx_user_source_range, + qbx_user_panel_range=qbx_user_panel_range, + qbx_user_center_range=qbx_user_center_range, + nqbxpanels=npanels, + nqbxsources=nsources, + nqbxcenters=ncenters, + **tree_attrs).with_queue(None) + + # }}} + + # {{{ kernels + + @memoize_method + def get_tunnel_query_distance_finder(self, dimensions, coord_dtype, + particle_id_dtype): + from pyopencl.tools import dtype_to_ctype + from boxtree.tools import AXIS_NAMES + logger.info("refiner: building tunnel query distance finder kernel") + + knl = TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE.build( + self.context, + type_aliases=( + ("particle_id_t", particle_id_dtype), + ("coord_t", coord_dtype), + ), + var_values=( + ("dimensions", dimensions), + ("AXIS_NAMES", AXIS_NAMES), + ("coord_dtype", coord_dtype), + ("dtype_to_ctype", dtype_to_ctype), + ("vec_types", tuple(cl.array.vec.types.items())), + )) + + logger.info("refiner: done building tunnel query distance finder kernel") + return knl + + @memoize_method + def get_center_is_closest_to_orig_panel_refiner(self, dimensions, + coord_dtype, box_id_dtype, + peer_list_idx_dtype, + particle_id_dtype, + max_levels): + return CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER.generate(self.context, + dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, + max_levels, + extra_type_aliases=(("particle_id_t", particle_id_dtype),)) + + @memoize_method + def get_center_is_far_from_nonneighbor_panel_refiner(self, dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + particle_id_dtype, + max_levels): + return CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER.generate(self.context, + dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, + max_levels, + extra_type_aliases=(("particle_id_t", particle_id_dtype),)) + + @memoize_method + def get_2_to_1_panel_ratio_refiner(self): + knl = lp.make_kernel([ + "{[panel]: 0<=panel neighbor_start = panel_adjacency_starts[panel] + <> neighbor_stop = panel_adjacency_starts[panel + 1] + for ineighbor + <> neighbor = panel_adjacency_lists[ineighbor] + <> oversize = (refine_flags_prev[panel] == 0 + and ( + (panel_sizes[panel] > 2 * panel_sizes[neighbor]) or + (panel_sizes[panel] > panel_sizes[neighbor] and + refine_flags_prev[neighbor] == 1))) + refine_flags[panel] = 1 {if=oversize} + refine_flags_updated = 1 {if=oversize} + end + end + """, [ + lp.GlobalArg("panel_adjacency_lists", shape=None), + "..." + ], + options="return_dict") + knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") + return knl + + @memoize_method + def get_helmholtz_k_to_panel_ratio_refiner(self): + knl = lp.make_kernel( + "{[panel]: 0<=panel oversize = panel_sizes[panel] > 5 * omega + refine_flags[panel] = 1 {if=oversize} + refine_flags_updated = 1 {if=oversize} + end + """, + options="return_dict") + knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") + return knl + + @memoize_method + def get_interleaver_kernel(self): + knl = lp.make_kernel( + "{[i]: 0<=i maxiter: + logger.warning( + "Max iteration count reached in QBX layer potential source" + " refiner.") + break + + # Build tree and auxiliary data. + tree = self.create_tree(queue, lpot_source) + wait_for = [] + + peer_lists, evt = self.peer_list_finder(queue, tree, wait_for) + wait_for = [evt] + + refine_flags, evt = self.get_refine_flags(queue, lpot_source) + wait_for.append(evt) + + tq_dists, evt = self.get_tunnel_query_dists(queue, tree, lpot_source) + wait_for.append(evt) + + # Run refinement checkers. + must_refine = False + + must_refine |= \ + self.refinement_check_center_is_closest_to_orig_panel( + queue, tree, lpot_source, peer_lists, tq_dists, + refine_flags, wait_for) + + must_refine |= \ + self.refinement_check_center_is_far_from_nonneighbor_panels( + queue, tree, lpot_source, peer_lists, tq_dists, + refine_flags, wait_for) + + must_refine |= \ + self.refinement_check_2_to_1_panel_size_ratio( + queue, lpot_source, refine_flags, wait_for) + + if helmholtz_k: + must_refine |= \ + self.refinement_check_helmholtz_k_to_panel_size_ratio( + queue, lpot_source, helmholtz_k, refine_flags, + wait_for) + + if must_refine: + lpot_source, conn = self.refine( + queue, lpot_source, refine_flags, refiner, order) + connections.append(conn) + + del tree + del peer_lists + del tq_dists + del refine_flags + done_refining = not must_refine + + return lpot_source, connections + +# vim: foldmethod=marker:filetype=pyopencl diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py new file mode 100644 index 00000000..799d3ad7 --- /dev/null +++ b/test/extra_curve_data.py @@ -0,0 +1,132 @@ +import matplotlib.pyplot as plt +import numpy as np +import numpy.linalg as la + + +class Curve(object): + + def plot(self, npoints=100): + x, y = self(np.linspace(0, 1, npoints)) + plt.plot(x, y) + plt.axis("equal") + plt.show() + + def __add__(self, other): + return CompositeCurve(self, other) + + +class CompositeCurve(Curve): + """ + Parametrization of two or more curves combined. + """ + + def __init__(self, *objs): + curves = [] + for obj in objs: + if isinstance(obj, CompositeCurve): + curves.extend(obj.curves) + else: + curves.append(obj) + self.curves = curves + + def __call__(self, ts): + ranges = np.linspace(0, 1, len(self.curves) + 1) + ts_argsort = np.argsort(ts) + ts_sorted = ts[ts_argsort] + ts_split_points = np.searchsorted(ts_sorted, ranges) + # FIXME: This isn't exactly right. + ts_split_points[-1] = len(ts) + result = [] + subranges = [slice(*ts_split_points[i:i+2]) + for i in range(len(ts_split_points))] + for curve, subrange, (start, end) in zip( + self.curves, subranges, zip(ranges, ranges[1:])): + ts_mapped = (ts_sorted[subrange] - start) / (end - start) + c = curve(ts_mapped) + result.append(c) + final = np.concatenate(result, axis=-1) + return final + + +class Segment(Curve): + """ + Represents a line segment. + """ + + def __init__(self, start, end): + self.start = np.array(start) + self.end = np.array(end) + + def __call__(self, ts): + return ( + self.start[:, np.newaxis] + + ts * (self.end - self.start)[:, np.newaxis]) + + +class Arc(Curve): + """ + Represents an arc of a circle. + """ + + def __init__(self, start, mid, end): + """ + :arg start: starting point of the arc + :arg mid: any point along the arc + :arg end: ending point of the arc + """ + xs, ys = np.stack((start, mid, end), axis=1) + + # Get center and radius of circle containing the arc. + # http://math.stackexchange.com/a/1460096 + C = np.array([xs**2 + ys**2, xs, ys, [1, 1, 1]]) + x0 = la.det(np.delete(C, 1, 0)) / (2 * la.det(np.delete(C, 0, 0))) + y0 = -la.det(np.delete(C, 2, 0)) / (2 * la.det(np.delete(C, 0, 0))) + + self.r = la.norm([start[0] - x0, start[1] - y0]) + self.center = x0 + 1j * y0 + + theta_start = np.arctan2(start[1] - y0, start[0] - x0) + theta_mid = np.arctan2(mid[1] - y0, mid[0] - x0) + theta_end = np.arctan2(end[1] - y0, end[0] - x0) + + if theta_start <= theta_end: + crosses_branch = not (theta_start <= theta_mid <= theta_end) + else: + crosses_branch = not (theta_start >= theta_mid >= theta_end) + + if crosses_branch: + # Shift the angles so that branch crossing is not involved. + if theta_start < 0: + theta_start += 2 * np.pi + if theta_mid < 0: + theta_mid += 2 * np.pi + if theta_end < 0: + theta_end += 2 * np.pi + + self.theta_range = np.array(sorted([theta_start, theta_end])) + self.theta_increasing = theta_start <= theta_end + + def __call__(self, t): + if self.theta_increasing: + thetas = ( + self.theta_range[0] + + t * (self.theta_range[1] - self.theta_range[0])) + else: + thetas = ( + self.theta_range[1] - + t * (self.theta_range[1] - self.theta_range[0])) + val = (self.r * np.exp(1j * thetas)) + self.center + return np.array([val.real, val.imag]) + + +# horseshoe curve +horseshoe = ( + Segment((0, 0), (-5, 0)) + + Arc((-5, 0), (-5.5, -0.5), (-5, -1)) + + Segment((-5, -1), (0, -1)) + + Arc((0, -1), (1.5, 0), (0, 2)) + + Segment((0, 2), (-5, 2)) + + Arc((-5, 2), (-5.5, 1.5), (-5, 1)) + + Segment((-5, 1), (0, 1)) + + Arc((0, 1), (0.5, 0.5), (0, 0)) + ) diff --git a/test/test_geometry.py b/test/test_geometry.py new file mode 100644 index 00000000..6a000053 --- /dev/null +++ b/test/test_geometry.py @@ -0,0 +1,179 @@ +from __future__ import division, absolute_import, print_function + +__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +import numpy.linalg as la +import pyopencl as cl +import pyopencl.clmath # noqa +import pytest +from pytools import RecordWithoutPickling +from pyopencl.tools import pytest_generate_tests_for_pyopencl \ + as pytest_generate_tests + +from functools import partial +from meshmode.mesh.generation import ( # noqa + ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, + make_curve_mesh) + +import logging +logger = logging.getLogger(__name__) + +__all__ = ["pytest_generate_tests"] + + +class ElementInfo(RecordWithoutPickling): + """ + .. attribute:: element_nr + .. attribute:: neighbors + .. attribute:: discr_slice + .. attribute:: mesh_slice + .. attribute:: element_group + .. attribute:: mesh_element_group + """ + __slots__ = ["element_nr", + "neighbors", + "discr_slice"] + + +def iter_elements(discr): + discr_nodes_idx = 0 + element_nr = 0 + adjacency = discr.mesh.nodal_adjacency + + for discr_group in discr.groups: + start = element_nr + for element_nr in range(start, start + discr_group.nelements): + yield ElementInfo( + element_nr=element_nr, + neighbors=list(adjacency.neighbors[ + slice(*adjacency.neighbors_starts[ + element_nr:element_nr+2])]), + discr_slice=slice(discr_nodes_idx, + discr_nodes_idx + discr_group.nunit_nodes)) + + discr_nodes_idx += discr_group.nunit_nodes + + +from extra_curve_data import horseshoe + + +@pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ + ("20-to-1 ellipse", partial(ellipse, 20), 100), + ("horseshoe", horseshoe, 50), + ]) +def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + order = 5 + + mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), 5) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + + discr = Discretization(cl_ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(order)) + + from pytential.qbx.refinement import ( + NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + + lpot_source = NewQBXLayerPotentialSource(discr, order) + del discr + refiner = QBXLayerPotentialSourceRefiner(cl_ctx) + + lpot_source, conn = refiner(lpot_source, order) + + discr_nodes = lpot_source.density_discr.nodes().get(queue) + int_centers = lpot_source.centers(-1) + int_centers = np.array([axis.get(queue) for axis in int_centers]) + ext_centers = lpot_source.centers(+1) + ext_centers = np.array([axis.get(queue) for axis in ext_centers]) + panel_sizes = lpot_source.panel_sizes("nelements").get(queue) + + def check_panel(panel): + # Check 2-to-1 panel to neighbor size ratio. + for neighbor in panel.neighbors: + assert panel_sizes[panel.element_nr] / panel_sizes[neighbor] <= 2, \ + (panel_sizes[panel.element_nr], panel_sizes[neighbor]) + + # Check wavenumber to panel size ratio. + # XXX: Make this a parameter, pass to refiner. + omega = 1 + assert panel_sizes[panel.element_nr] * omega <= 5 + + def check_panel_pair(panel_1, panel_2): + h_1 = panel_sizes[panel_1.element_nr] + h_2 = panel_sizes[panel_2.element_nr] + + if panel_1.element_nr == panel_2.element_nr: + # Same panel + return + + panel_1_centers = int_centers[:, panel_1.discr_slice] + panel_2_nodes = discr_nodes[:, panel_2.discr_slice] + + # =distance(centers of panel 1, panel 2) + dist = ( + la.norm(( + panel_1_centers[..., np.newaxis] - + panel_2_nodes[:, np.newaxis, ...]).T, + axis=-1) + .min()) + + # Criterion 1: + # A center cannot be closer to another panel than to its originating + # panel. + + assert dist >= h_1 / 2, (dist, h_1, panel_1.element_nr, panel_2.element_nr) + + # Criterion 2: + # A center cannot be closer to another panel than that panel's + # centers - unless the panels are adjacent, to allow for refinement. + + if panel_2.element_nr in panel_1.neighbors: + return + + assert dist >= h_2 / 2, (dist, h_2, panel_1.element_nr, panel_2.element_nr) + + for panel_1 in iter_elements(lpot_source.density_discr): + check_panel(panel_1) + for panel_2 in iter_elements(lpot_source.density_discr): + check_panel_pair(panel_1, panel_2) + + +# You can test individual routines by typing +# $ python test_layer_pot.py 'test_routine()' + +if __name__ == "__main__": + import sys + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + from py.test.cmdline import main + main([__file__]) + +# vim: fdm=marker -- GitLab From 82605f08e92543a3aa4434c1e503b3a2900f9a1b Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 29 Sep 2016 12:08:32 -0500 Subject: [PATCH 02/62] range=range(...) => range=slice(...) --- pytential/qbx/refinement.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index e20b5764..d9b07c72 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -757,7 +757,7 @@ class QBXLayerPotentialSourceRefiner(object): refine_flags, found_panel_to_refine, *tree.sources), - range=range(tree.nqbxcenters), + range=slice(tree.nqbxcenters), queue=queue) cl.wait_for_events([evt]) @@ -805,7 +805,7 @@ class QBXLayerPotentialSourceRefiner(object): refine_flags, found_panel_to_refine, *tree.sources), - range=range(tree.nqbxcenters), + range=slice(tree.nqbxcenters), queue=queue) cl.wait_for_events([evt]) -- GitLab From d5cff4392cae8d405858adc8a8cb9d84bcc5e1f2 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 1 Oct 2016 18:57:41 -0500 Subject: [PATCH 03/62] Refiner: add more logger output. --- pytential/qbx/refinement.py | 89 +++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index d9b07c72..bb0255f8 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -545,7 +545,7 @@ class QBXLayerPotentialSourceRefiner(object): # only because of sources. refine_weights = cl.array.zeros(queue, nparticles, np.int32) refine_weights[:nsources].fill(1) - MAX_REFINE_WEIGHT = 30 + MAX_REFINE_WEIGHT = 100 refine_weights.finish() @@ -721,7 +721,7 @@ class QBXLayerPotentialSourceRefiner(object): # {{{ refinement triggering def refinement_check_center_is_closest_to_orig_panel(self, queue, tree, - lpot_source, peer_lists, tq_dists, refine_flags, wait_for=None): + lpot_source, peer_lists, tq_dists, refine_flags, debug, wait_for=None): # Avoid generating too many kernels. from pytools import div_ceil max_levels = 10 * div_ceil(tree.nlevels, 10) @@ -733,6 +733,11 @@ class QBXLayerPotentialSourceRefiner(object): tree.particle_id_dtype, max_levels) + logger.info("refiner: checking center is closest to orig panel") + + if debug: + npanels_to_refine_prev = cl.array.sum(refine_flags).get() + found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) found_panel_to_refine.finish() @@ -762,10 +767,18 @@ class QBXLayerPotentialSourceRefiner(object): cl.wait_for_events([evt]) + if debug: + npanels_to_refine = cl.array.sum(refine_flags).get() + if npanels_to_refine > npanels_to_refine_prev: + logger.debug("refiner: found {} panel(s) to refine".format( + npanels_to_refine - npanels_to_refine_prev)) + + logger.info("refiner: done checking center is closest to orig panel") + return found_panel_to_refine.get()[0] == 1 def refinement_check_center_is_far_from_nonneighbor_panels(self, queue, - tree, lpot_source, peer_lists, tq_dists, refine_flags, + tree, lpot_source, peer_lists, tq_dists, refine_flags, debug, wait_for=None): # Avoid generating too many kernels. from pytools import div_ceil @@ -778,6 +791,11 @@ class QBXLayerPotentialSourceRefiner(object): tree.particle_id_dtype, max_levels) + logger.info("refiner: checking center is far from nonneighbor panels") + + if debug: + npanels_to_refine_prev = cl.array.sum(refine_flags).get() + found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) found_panel_to_refine.finish() @@ -810,12 +828,23 @@ class QBXLayerPotentialSourceRefiner(object): cl.wait_for_events([evt]) + if debug: + npanels_to_refine = cl.array.sum(refine_flags).get() + if npanels_to_refine > npanels_to_refine_prev: + logger.debug("refiner: found {} panel(s) to refine".format( + npanels_to_refine - npanels_to_refine_prev)) + + logger.info("refiner: done checking center is far from nonneighbor panels") + return found_panel_to_refine.get()[0] == 1 def refinement_check_helmholtz_k_panel_ratio(self, queue, lpot_source, - helmholtz_k, refine_flags): + helmholtz_k, refine_flags, debug, wait_for=None): knl = self.get_helmholtz_k_to_panel_ratio_refiner() + if debug: + npanels_to_refine_prev = cl.array.sum(refine_flags).get() + evt, out = knl(queue, panel_sizes=lpot_source.panel_sizes("nelements"), refine_flags=refine_flags, @@ -823,17 +852,27 @@ class QBXLayerPotentialSourceRefiner(object): cl.wait_for_events([evt]) + if debug: + npanels_to_refine = cl.array.sum(refine_flags).get() + if npanels_to_refine > npanels_to_refine_prev: + logger.debug("refiner: found {} panel(s) to refine".format( + npanels_to_refine - npanels_to_refine_prev)) + return (out["refine_flags_updated"].get() == 1).all() def refinement_check_2_to_1_panel_size_ratio(self, queue, lpot_source, - refine_flags, wait_for): + refine_flags, debug, wait_for=None): knl = self.get_2_to_1_panel_ratio_refiner() adjacency = self.get_adjacency_on_device(queue, lpot_source) - done_checking = False refine_flags_updated = False - while not done_checking: + logger.info("refiner: checking 2-to-1 panel size ratio") + + if debug: + npanels_to_refine_prev = cl.array.sum(refine_flags).get() + + while True: evt, out = knl(queue, npanels=lpot_source.density_discr.mesh.nelements, panel_sizes=lpot_source.panel_sizes("nelements"), @@ -851,9 +890,16 @@ class QBXLayerPotentialSourceRefiner(object): if (out["refine_flags_updated"].get() == 1).all(): refine_flags_updated = True - done_checking = False else: - done_checking = True + break + + if debug: + npanels_to_refine = cl.array.sum(refine_flags).get() + if npanels_to_refine > npanels_to_refine_prev: + logger.debug("refiner: found {} panel(s) to refine".format( + npanels_to_refine - npanels_to_refine_prev)) + + logger.info("refiner: done checking 2-to-1 panel size ratio") return refine_flags_updated @@ -931,7 +977,10 @@ class QBXLayerPotentialSourceRefiner(object): # }}} - def refine(self, queue, lpot_source, refine_flags, refiner, order): + def refine(self, queue, lpot_source, refine_flags, refiner, factory, debug): + """ + Refine the underlying mesh and discretization. + """ if isinstance(refine_flags, cl.array.Array): refine_flags = refine_flags.get(queue) refine_flags = refine_flags.astype(np.bool) @@ -943,7 +992,7 @@ class QBXLayerPotentialSourceRefiner(object): conn = make_refinement_connection( refiner, lpot_source.density_discr, - QuadratureSimplexGroupFactory(order)) + factory) logger.info("refiner: done calling meshmode") @@ -953,8 +1002,7 @@ class QBXLayerPotentialSourceRefiner(object): new_density_discr, lpot_source.fine_order, qbx_level_to_order=lpot_source.qbx_level_to_order, fmm_level_to_order=lpot_source.fmm_level_to_order, - # FIXME set debug=False once everything works - real_dtype=lpot_source.real_dtype, debug=True) + real_dtype=lpot_source.real_dtype, debug=debug) return new_lpot_source, conn @@ -977,7 +1025,9 @@ class QBXLayerPotentialSourceRefiner(object): plt.legend() plt.show() - def __call__(self, lpot_source, order, helmholtz_k=None, maxiter=50): + def __call__(self, lpot_source, discr_factory, helmholtz_k=None, + # FIXME: Set debug=False once everything works. + debug=True, maxiter=50): from meshmode.mesh.refinement import Refiner refiner = Refiner(lpot_source.density_discr.mesh) connections = [] @@ -1014,26 +1064,27 @@ class QBXLayerPotentialSourceRefiner(object): must_refine |= \ self.refinement_check_center_is_closest_to_orig_panel( queue, tree, lpot_source, peer_lists, tq_dists, - refine_flags, wait_for) + refine_flags, debug, wait_for) must_refine |= \ self.refinement_check_center_is_far_from_nonneighbor_panels( queue, tree, lpot_source, peer_lists, tq_dists, - refine_flags, wait_for) + refine_flags, debug, wait_for) must_refine |= \ self.refinement_check_2_to_1_panel_size_ratio( - queue, lpot_source, refine_flags, wait_for) + queue, lpot_source, refine_flags, debug, wait_for) if helmholtz_k: must_refine |= \ self.refinement_check_helmholtz_k_to_panel_size_ratio( - queue, lpot_source, helmholtz_k, refine_flags, + queue, lpot_source, helmholtz_k, refine_flags, debug, wait_for) if must_refine: lpot_source, conn = self.refine( - queue, lpot_source, refine_flags, refiner, order) + queue, lpot_source, refine_flags, refiner, discr_factory, + debug) connections.append(conn) del tree -- GitLab From a50edb8d93baf2faaefd8c4f8c2d1c11a042ed97 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 1 Oct 2016 18:59:23 -0500 Subject: [PATCH 04/62] Refiner tests: update order, pass factory as argument. --- test/test_geometry.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_geometry.py b/test/test_geometry.py index 6a000053..fe8d54e7 100644 --- a/test/test_geometry.py +++ b/test/test_geometry.py @@ -81,22 +81,22 @@ from extra_curve_data import horseshoe @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ ("20-to-1 ellipse", partial(ellipse, 20), 100), - ("horseshoe", horseshoe, 50), + ("horseshoe", horseshoe, 80), ]) def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - order = 5 + order = 8 - mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), 5) + mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory + factory = InterpolatoryQuadratureSimplexGroupFactory(order) - discr = Discretization(cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(order)) + discr = Discretization(cl_ctx, mesh, factory) from pytential.qbx.refinement import ( NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) @@ -105,7 +105,7 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement del discr refiner = QBXLayerPotentialSourceRefiner(cl_ctx) - lpot_source, conn = refiner(lpot_source, order) + lpot_source, conn = refiner(lpot_source, factory) discr_nodes = lpot_source.density_discr.nodes().get(queue) int_centers = lpot_source.centers(-1) -- GitLab From a638114478d8b9a1d95f29a70c915b293b5d1f60 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 3 Oct 2016 14:08:51 -0500 Subject: [PATCH 05/62] A few fixes. 1) Document refiner. 2) Get helmholtz parameter based refinement working. 3) Fix curve smoothness (needs scipy dependency). --- pytential/qbx/refinement.py | 143 ++++++++++++++++++++++++++++-------- requirements.txt | 1 + test/extra_curve_data.py | 10 ++- test/test_geometry.py | 11 ++- 4 files changed, 126 insertions(+), 39 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index bb0255f8..3bf21fcd 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -39,9 +39,18 @@ import pyopencl as cl import logging logger = logging.getLogger(__name__) +__doc__ = """ +Refinement +^^^^^^^^^^ + +.. autoclass:: QBXLayerPotentialSourceRefiner +""" + # {{{ layer potential source +# FIXME: Move to own file, replace existing QBXLayerPotentialSource when +# finished. class NewQBXLayerPotentialSource(object): """A source discretization for a QBX layer potential. @@ -242,7 +251,10 @@ class NewQBXLayerPotentialSource(object): from pyopencl.elementwise import ElementwiseTemplate from boxtree.area_query import AreaQueryElementwiseTemplate from boxtree.tools import InlineBinarySearch +from boxtree.tree import Tree + +# {{{ kernels REFINER_C_MACROS = r"""//CL:mako// // A note on node numberings: sources, centers, and panels each @@ -330,12 +342,15 @@ TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( # Implements "Algorithm for triggering refinement based on Condition 1" +# +# FIXME: There is probably a better way to do this. For instance, since +# we are not using Newton to compute center-panel distances, we can just +# do an area query of size h_k / 2 around each center. CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( extra_args=r""" /* input */ particle_id_t *box_to_panel_starts, particle_id_t *box_to_panel_lists, - /* XXX: starts dtype */ particle_id_t *panel_to_source_starts, particle_id_t *panel_to_center_starts, particle_id_t source_offset, @@ -397,7 +412,7 @@ CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( } } """, - name="refine_center_to_own_panel", + name="refine_center_closest_to_orig_panel", preamble=str(InlineBinarySearch("particle_id_t"))) @@ -414,7 +429,6 @@ CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( particle_id_t panel_offset, particle_id_t *sorted_target_ids, coord_t *panel_sizes, - /* XXX: data type... */ particle_id_t *panel_adjacency_starts, particle_id_t *panel_adjacency_lists, int npanels, @@ -484,28 +498,24 @@ CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( } } """, - name="refine_center_to_other_panel", + name="refine_center_far_from_nonneighbor_panels", preamble=str(InlineBinarySearch("particle_id_t"))) +# }}} -from boxtree.tree import Tree +# {{{ lpot source refiner -class TreeWithQBXMetadata(Tree): - """ - .. attribute:: box_to_qbx_panel_starts - .. attribute:: box_to_qbx_panel_lists - .. attribute:: qbx_panel_to_source_starts - .. attribute:: qbx_panel_to_center_starts - .. attribute:: qbx_user_source_range - .. attribute:: qbx_user_center_range - .. attribute:: qbx_user_panel_range - XXX +class QBXLayerPotentialSourceRefiner(object): """ - pass + Driver for refining the QBX source grid. Follows [1]_. + .. [1] Rachh, Manas, Andreas Klöckner, and Michael O'Neil. "Fast + algorithms for Quadrature by Expansion I: Globally valid expansions." -class QBXLayerPotentialSourceRefiner(object): + .. automethod:: get_refine_flags + .. automethod:: __call__ + """ def __init__(self, context): self.context = context @@ -516,6 +526,24 @@ class QBXLayerPotentialSourceRefiner(object): # {{{ tree creation + class TreeWithQBXMetadata(Tree): + """ + .. attribute:: nqbxpanels + .. attribuet:: nsources + .. attribute:: ncenters + + .. attribute:: box_to_qbx_panel_starts + .. attribute:: box_to_qbx_panel_lists + + .. attribute:: qbx_panel_to_source_starts + .. attribute:: qbx_panel_to_center_starts + + .. attribute:: qbx_user_source_range + .. attribute:: qbx_user_center_range + .. attribute:: qbx_user_panel_range + """ + pass + def create_tree(self, queue, lpot_source): # The ordering of particles is as follows: # - sources go first @@ -534,6 +562,7 @@ class QBXLayerPotentialSourceRefiner(object): npanels = len(centers_of_mass[0]) nsources = len(sources[0]) ncenters = len(centers[0]) + # Each source gets an interior / exterior center. assert 2 * nsources == ncenters qbx_user_source_range = range(0, nsources) @@ -545,7 +574,7 @@ class QBXLayerPotentialSourceRefiner(object): # only because of sources. refine_weights = cl.array.zeros(queue, nparticles, np.int32) refine_weights[:nsources].fill(1) - MAX_REFINE_WEIGHT = 100 + MAX_REFINE_WEIGHT = 128 refine_weights.finish() @@ -560,9 +589,9 @@ class QBXLayerPotentialSourceRefiner(object): qbx_panel_flags.finish() from boxtree.tree import filter_target_lists_in_user_order - box_to_qbx_panel = \ - filter_target_lists_in_user_order(queue, tree, qbx_panel_flags).\ - with_queue(queue) + box_to_qbx_panel = ( + filter_target_lists_in_user_order(queue, tree, qbx_panel_flags) + .with_queue(queue)) # Fix up offset. box_to_qbx_panel.target_lists -= 3 * nsources @@ -593,8 +622,7 @@ class QBXLayerPotentialSourceRefiner(object): logger.info("refiner: done building tree") - # XXX: evt management - return TreeWithQBXMetadata( + return self.TreeWithQBXMetadata( box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, qbx_panel_to_source_starts=qbx_panel_to_source_starts, @@ -676,14 +704,16 @@ class QBXLayerPotentialSourceRefiner(object): (panel_sizes[panel] > panel_sizes[neighbor] and refine_flags_prev[neighbor] == 1))) refine_flags[panel] = 1 {if=oversize} - refine_flags_updated = 1 {if=oversize} + refine_flags_updated = 1 { + id=write_refine_flags_updated,if=oversize} end end """, [ lp.GlobalArg("panel_adjacency_lists", shape=None), "..." ], - options="return_dict") + options="return_dict", + silenced_warnings="write_race(write_refine_flags_updated)") knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl @@ -693,12 +723,13 @@ class QBXLayerPotentialSourceRefiner(object): "{[panel]: 0<=panel oversize = panel_sizes[panel] > 5 * omega + <> oversize = panel_sizes[panel] * helmholtz_k > 5 refine_flags[panel] = 1 {if=oversize} - refine_flags_updated = 1 {if=oversize} + refine_flags_updated = 1 {id=write_refine_flags_updated,if=oversize} end """, - options="return_dict") + options="return_dict", + silenced_warnings="write_race(write_refine_flags_updated)") knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl @@ -838,17 +869,21 @@ class QBXLayerPotentialSourceRefiner(object): return found_panel_to_refine.get()[0] == 1 - def refinement_check_helmholtz_k_panel_ratio(self, queue, lpot_source, + def refinement_check_helmholtz_k_to_panel_size_ratio(self, queue, lpot_source, helmholtz_k, refine_flags, debug, wait_for=None): knl = self.get_helmholtz_k_to_panel_ratio_refiner() + logger.info("refiner: checking helmholtz k to panel size ratio") + if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() evt, out = knl(queue, panel_sizes=lpot_source.panel_sizes("nelements"), refine_flags=refine_flags, - refine_flags_updated=np.array(0)) + refine_flags_updated=np.array(0), + helmholtz_k=np.array(helmholtz_k), + wait_for=wait_for) cl.wait_for_events([evt]) @@ -858,6 +893,8 @@ class QBXLayerPotentialSourceRefiner(object): logger.debug("refiner: found {} panel(s) to refine".format( npanels_to_refine - npanels_to_refine_prev)) + logger.info("refiner: done checking helmholtz k to panel size ratio") + return (out["refine_flags_updated"].get() == 1).all() def refinement_check_2_to_1_panel_size_ratio(self, queue, lpot_source, @@ -872,6 +909,7 @@ class QBXLayerPotentialSourceRefiner(object): if debug: npanels_to_refine_prev = cl.array.sum(refine_flags).get() + # Iterative refinement until no more panels can be marked while True: evt, out = knl(queue, npanels=lpot_source.density_discr.mesh.nelements, @@ -908,6 +946,9 @@ class QBXLayerPotentialSourceRefiner(object): # {{{ other utilities def get_tunnel_query_dists(self, queue, tree, lpot_source): + """ + Compute radii for the tubular neighborhood around each panel center of mass. + """ nqbxpanels = lpot_source.density_discr.mesh.nelements # atomic_max only works on float32 tq_dists = cl.array.zeros(queue, nqbxpanels, np.float32) @@ -925,7 +966,9 @@ class QBXLayerPotentialSourceRefiner(object): tq_dists, *tree.sources, queue=queue, - range=tree.qbx_user_source_range) + range=slice(tree.nqbxsources)) + + cl.wait_for_events([evt]) if tree.coord_dtype != tq_dists.dtype: tq_dists = tq_dists.astype(tree.coord_dtype) @@ -948,6 +991,12 @@ class QBXLayerPotentialSourceRefiner(object): def get_refine_flags(self, queue, lpot_source): """ Return an array on the device suitable for use as element refine flags. + + :arg queue: An instance of :class:`pyopencl.CommandQueue`. + :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource`. + + :returns: An instance of :class:`pyopencl.array.Array` suitable for + use as refine flags, initialized to zero. """ result = cl.array.zeros( queue, lpot_source.density_discr.mesh.nelements, np.int32) @@ -1027,12 +1076,39 @@ class QBXLayerPotentialSourceRefiner(object): def __call__(self, lpot_source, discr_factory, helmholtz_k=None, # FIXME: Set debug=False once everything works. - debug=True, maxiter=50): + refine_flags=None, debug=True, maxiter=50): + """ + Entry point for calling the refiner. + + :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource`. + + :arg group_factory: An instance of + :class:`meshmode.mesh.discretization.ElementGroupFactory`. Used for + discretizing the refined mesh. + + :arg helmholtz_k: The Helmholtz parameter, or `None` if not applicable. + + :arg refine_flags: A :class:`pyopencl.array.Array` indicating which + panels should get refined initially, or `None` if no initial + refinement should be done. Should have size equal to the number of + panels. See also :meth:`get_refine_flags()`. + + :returns: A tuple ``(lpot_source, conns)`` where ``lpot_source`` is the + refined layer potential source, and ``conns`` is a list of +` :class:`meshmode.discretization.connection.DiscretizationConnection` + objects going from the original mesh to the refined mesh. + """ from meshmode.mesh.refinement import Refiner refiner = Refiner(lpot_source.density_discr.mesh) connections = [] with cl.CommandQueue(self.context) as queue: + if refine_flags: + lpot_source, conn = self.refine( + queue, lpot_source, refine_flags, refiner, discr_factory, + debug) + connections.append(conn) + done_refining = False niter = 0 @@ -1046,6 +1122,7 @@ class QBXLayerPotentialSourceRefiner(object): break # Build tree and auxiliary data. + # FIXME: The tree should not have to be rebuilt at each iteration. tree = self.create_tree(queue, lpot_source) wait_for = [] @@ -1095,4 +1172,6 @@ class QBXLayerPotentialSourceRefiner(object): return lpot_source, connections +# }}} + # vim: foldmethod=marker:filetype=pyopencl diff --git a/requirements.txt b/requirements.txt index 3ff086fc..8ee3e6bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ numpy +scipy git+git://github.com/inducer/pymbolic git+git://github.com/sympy/sympy git+git://github.com/inducer/modepy diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index 799d3ad7..6c5d5ac1 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -120,7 +120,7 @@ class Arc(Curve): # horseshoe curve -horseshoe = ( +_horseshoe = ( Segment((0, 0), (-5, 0)) + Arc((-5, 0), (-5.5, -0.5), (-5, -1)) + Segment((-5, -1), (0, -1)) + @@ -130,3 +130,11 @@ horseshoe = ( Segment((-5, 1), (0, 1)) + Arc((0, 1), (0.5, 0.5), (0, 0)) ) + + +def horseshoe(ts): + # The horseshoe curve as defined above is not smooth enough for the refiner + # to work well, so we smooth it out with a spline. + from scipy.interpolate import splprep, splev + tck, u = splprep(_horseshoe(np.linspace(0, 1, 50)), s=0, per=True) + return np.array(splev(ts, tck)) diff --git a/test/test_geometry.py b/test/test_geometry.py index fe8d54e7..13f3e8b4 100644 --- a/test/test_geometry.py +++ b/test/test_geometry.py @@ -81,13 +81,14 @@ from extra_curve_data import horseshoe @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ ("20-to-1 ellipse", partial(ellipse, 20), 100), - ("horseshoe", horseshoe, 80), + ("horseshoe", horseshoe, 50), ]) def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - order = 8 + order = 16 + helmholtz_k = 10 mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) @@ -105,7 +106,7 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement del discr refiner = QBXLayerPotentialSourceRefiner(cl_ctx) - lpot_source, conn = refiner(lpot_source, factory) + lpot_source, conn = refiner(lpot_source, factory, helmholtz_k) discr_nodes = lpot_source.density_discr.nodes().get(queue) int_centers = lpot_source.centers(-1) @@ -121,9 +122,7 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement (panel_sizes[panel.element_nr], panel_sizes[neighbor]) # Check wavenumber to panel size ratio. - # XXX: Make this a parameter, pass to refiner. - omega = 1 - assert panel_sizes[panel.element_nr] * omega <= 5 + assert panel_sizes[panel.element_nr] * helmholtz_k <= 5 def check_panel_pair(panel_1, panel_2): h_1 = panel_sizes[panel_1.element_nr] -- GitLab From b8d4547bb7fb0286ef8b18495ba7f0a70a16f7b2 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 3 Oct 2016 17:20:01 -0500 Subject: [PATCH 06/62] Fix curve parametrization, get rid of global matplotlib import and scipy spline stuff. --- requirements.txt | 1 - test/extra_curve_data.py | 33 +++++++++++++++------------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8ee3e6bd..3ff086fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ numpy -scipy git+git://github.com/inducer/pymbolic git+git://github.com/sympy/sympy git+git://github.com/inducer/modepy diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index 6c5d5ac1..bb86d4fe 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -1,13 +1,13 @@ -import matplotlib.pyplot as plt import numpy as np import numpy.linalg as la class Curve(object): - def plot(self, npoints=100): + def plot(self, npoints=50): + import matplotlib.pyplot as plt x, y = self(np.linspace(0, 1, npoints)) - plt.plot(x, y) + plt.plot(x, y, marker=".", lw=0) plt.axis("equal") plt.show() @@ -34,17 +34,18 @@ class CompositeCurve(Curve): ts_argsort = np.argsort(ts) ts_sorted = ts[ts_argsort] ts_split_points = np.searchsorted(ts_sorted, ranges) - # FIXME: This isn't exactly right. + # Make sure the last entry = len(ts), otherwise if ts finishes with a + # trail of 1s, then they won't be forwarded to the last curve. ts_split_points[-1] = len(ts) result = [] - subranges = [slice(*ts_split_points[i:i+2]) - for i in range(len(ts_split_points))] + subranges = [ + slice(*pair) for pair in zip(ts_split_points, ts_split_points[1:])] for curve, subrange, (start, end) in zip( self.curves, subranges, zip(ranges, ranges[1:])): ts_mapped = (ts_sorted[subrange] - start) / (end - start) - c = curve(ts_mapped) - result.append(c) + result.append(curve(ts_mapped)) final = np.concatenate(result, axis=-1) + assert len(final[0]) == len(ts) return final @@ -84,6 +85,7 @@ class Arc(Curve): self.r = la.norm([start[0] - x0, start[1] - y0]) self.center = x0 + 1j * y0 + print("r, center", self.r, self.center, start, mid, end) theta_start = np.arctan2(start[1] - y0, start[0] - x0) theta_mid = np.arctan2(mid[1] - y0, mid[0] - x0) @@ -120,21 +122,16 @@ class Arc(Curve): # horseshoe curve -_horseshoe = ( +# +# To avoid issues with crossing non-smooth regions, make sure the number of +# panels given to this function (for make_curve_mesh) is a multiple of 8. +horseshoe = ( Segment((0, 0), (-5, 0)) + Arc((-5, 0), (-5.5, -0.5), (-5, -1)) + Segment((-5, -1), (0, -1)) + - Arc((0, -1), (1.5, 0), (0, 2)) + + Arc((0, -1), (1.5, 0.5), (0, 2)) + Segment((0, 2), (-5, 2)) + Arc((-5, 2), (-5.5, 1.5), (-5, 1)) + Segment((-5, 1), (0, 1)) + Arc((0, 1), (0.5, 0.5), (0, 0)) ) - - -def horseshoe(ts): - # The horseshoe curve as defined above is not smooth enough for the refiner - # to work well, so we smooth it out with a spline. - from scipy.interpolate import splprep, splev - tck, u = splprep(_horseshoe(np.linspace(0, 1, 50)), s=0, per=True) - return np.array(splev(ts, tck)) -- GitLab From 8b63057864388151e7ea6a5116a83b4c23b9b524 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 3 Oct 2016 17:20:31 -0500 Subject: [PATCH 07/62] (Horseshoe) Use nelements = multiple of 8. --- test/test_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_geometry.py b/test/test_geometry.py index 13f3e8b4..a76c2751 100644 --- a/test/test_geometry.py +++ b/test/test_geometry.py @@ -81,7 +81,7 @@ from extra_curve_data import horseshoe @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ ("20-to-1 ellipse", partial(ellipse, 20), 100), - ("horseshoe", horseshoe, 50), + ("horseshoe", horseshoe, 64), ]) def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() -- GitLab From 26e42f4b91dac145fb6196ad8b2bf0558001aab9 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 3 Oct 2016 17:36:34 -0500 Subject: [PATCH 08/62] Remove print statement. --- test/extra_curve_data.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index bb86d4fe..f81a08a9 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -85,7 +85,6 @@ class Arc(Curve): self.r = la.norm([start[0] - x0, start[1] - y0]) self.center = x0 + 1j * y0 - print("r, center", self.r, self.center, start, mid, end) theta_start = np.arctan2(start[1] - y0, start[0] - x0) theta_mid = np.arctan2(mid[1] - y0, mid[0] - x0) -- GitLab From dcba1f53df62ea1659bf47219379c31ef94653c0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 4 Oct 2016 15:48:15 -0500 Subject: [PATCH 09/62] Extract TreeWithQBXMetadataBuilder class and put into its own file. --- pytential/qbx/refinement.py | 160 +--------------------------- pytential/qbx/utils.py | 206 ++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 155 deletions(-) create mode 100644 pytential/qbx/utils.py diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 3bf21fcd..730b4429 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function -from six.moves import range, zip +from six.moves import range __copyright__ = """ Copyright (C) 2013 Andreas Kloeckner @@ -251,7 +251,6 @@ class NewQBXLayerPotentialSource(object): from pyopencl.elementwise import ElementwiseTemplate from boxtree.area_query import AreaQueryElementwiseTemplate from boxtree.tools import InlineBinarySearch -from boxtree.tree import Tree # {{{ kernels @@ -519,124 +518,11 @@ class QBXLayerPotentialSourceRefiner(object): def __init__(self, context): self.context = context - from boxtree.tree_build import TreeBuilder - self.tree_builder = TreeBuilder(self.context) + from pytential.qbx.utils import TreeWithQBXMetadataBuilder + self.tree_builder = TreeWithQBXMetadataBuilder(self.context) from boxtree.area_query import PeerListFinder self.peer_list_finder = PeerListFinder(self.context) - # {{{ tree creation - - class TreeWithQBXMetadata(Tree): - """ - .. attribute:: nqbxpanels - .. attribuet:: nsources - .. attribute:: ncenters - - .. attribute:: box_to_qbx_panel_starts - .. attribute:: box_to_qbx_panel_lists - - .. attribute:: qbx_panel_to_source_starts - .. attribute:: qbx_panel_to_center_starts - - .. attribute:: qbx_user_source_range - .. attribute:: qbx_user_center_range - .. attribute:: qbx_user_panel_range - """ - pass - - def create_tree(self, queue, lpot_source): - # The ordering of particles is as follows: - # - sources go first - # - then centers - # - then panels (=centers of mass) - - sources = lpot_source.density_discr.nodes() - centers = self.get_interleaved_centers(queue, lpot_source) - centers_of_mass = lpot_source.panel_centers_of_mass() - - particles = tuple( - cl.array.concatenate(dim_coords, queue=queue) - for dim_coords in zip(sources, centers, centers_of_mass)) - - nparticles = len(particles[0]) - npanels = len(centers_of_mass[0]) - nsources = len(sources[0]) - ncenters = len(centers[0]) - # Each source gets an interior / exterior center. - assert 2 * nsources == ncenters - - qbx_user_source_range = range(0, nsources) - nsourcescenters = 3 * nsources - qbx_user_center_range = range(nsources, nsourcescenters) - qbx_user_panel_range = range(nsourcescenters, nsourcescenters + npanels) - - # Build tree with sources, centers, and centers of mass. Split boxes - # only because of sources. - refine_weights = cl.array.zeros(queue, nparticles, np.int32) - refine_weights[:nsources].fill(1) - MAX_REFINE_WEIGHT = 128 - - refine_weights.finish() - - tree, evt = self.tree_builder(queue, particles, - max_leaf_refine_weight=MAX_REFINE_WEIGHT, - refine_weights=refine_weights) - - # Compute box => panel relation - qbx_panel_flags = refine_weights - qbx_panel_flags.fill(0) - qbx_panel_flags[3 * nsources:].fill(1) - qbx_panel_flags.finish() - - from boxtree.tree import filter_target_lists_in_user_order - box_to_qbx_panel = ( - filter_target_lists_in_user_order(queue, tree, qbx_panel_flags) - .with_queue(queue)) - # Fix up offset. - box_to_qbx_panel.target_lists -= 3 * nsources - - qbx_panel_to_source_starts = cl.array.empty( - queue, npanels + 1, dtype=tree.particle_id_dtype) - - # Compute panel => source relation - el_offset = 0 - for group in lpot_source.density_discr.groups: - qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ - cl.array.arange(queue, group.node_nr_base, - group.node_nr_base + group.nnodes, - group.nunit_nodes, - dtype=tree.particle_id_dtype) - el_offset += group.nelements - qbx_panel_to_source_starts[-1] = nsources - - # Compute panel => center relation - qbx_panel_to_center_starts = 2 * qbx_panel_to_source_starts - - # Transfer all tree attributes. - tree_attrs = {} - for attr_name in tree.__class__.fields: - try: - tree_attrs[attr_name] = getattr(tree, attr_name) - except AttributeError: - pass - - logger.info("refiner: done building tree") - - return self.TreeWithQBXMetadata( - box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, - box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, - qbx_panel_to_source_starts=qbx_panel_to_source_starts, - qbx_panel_to_center_starts=qbx_panel_to_center_starts, - qbx_user_source_range=qbx_user_source_range, - qbx_user_panel_range=qbx_user_panel_range, - qbx_user_center_range=qbx_user_center_range, - nqbxpanels=npanels, - nqbxsources=nsources, - nqbxcenters=ncenters, - **tree_attrs).with_queue(None) - - # }}} - # {{{ kernels @memoize_method @@ -733,20 +619,6 @@ class QBXLayerPotentialSourceRefiner(object): knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl - @memoize_method - def get_interleaver_kernel(self): - knl = lp.make_kernel( - "{[i]: 0<=i panel relation + qbx_panel_flags = refine_weights + qbx_panel_flags.fill(0) + qbx_panel_flags[3 * nsources:].fill(1) + qbx_panel_flags.finish() + + from boxtree.tree import filter_target_lists_in_user_order + box_to_qbx_panel = ( + filter_target_lists_in_user_order(queue, tree, qbx_panel_flags) + .with_queue(queue)) + # Fix up offset. + box_to_qbx_panel.target_lists -= 3 * nsources + + qbx_panel_to_source_starts = cl.array.empty( + queue, npanels + 1, dtype=tree.particle_id_dtype) + + # Compute panel => source relation + el_offset = 0 + for group in lpot_source.density_discr.groups: + qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ + cl.array.arange(queue, group.node_nr_base, + group.node_nr_base + group.nnodes, + group.nunit_nodes, + dtype=tree.particle_id_dtype) + el_offset += group.nelements + qbx_panel_to_source_starts[-1] = nsources + + # Compute panel => center relation + qbx_panel_to_center_starts = 2 * qbx_panel_to_source_starts + + # Transfer all tree attributes. + tree_attrs = {} + for attr_name in tree.__class__.fields: + try: + tree_attrs[attr_name] = getattr(tree, attr_name) + except AttributeError: + pass + + logger.info("refiner: done building tree") + + return self.TreeWithQBXMetadata( + box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, + box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, + qbx_panel_to_source_starts=qbx_panel_to_source_starts, + qbx_panel_to_center_starts=qbx_panel_to_center_starts, + qbx_user_source_range=qbx_user_source_range, + qbx_user_panel_range=qbx_user_panel_range, + qbx_user_center_range=qbx_user_center_range, + nqbxpanels=npanels, + nqbxsources=nsources, + nqbxcenters=ncenters, + **tree_attrs).with_queue(None) + +# }}} + +# vim: foldmethod=marker:filetype=pyopencl -- GitLab From 61d795a3aaa87806442c3f313579c45610495cee Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 6 Oct 2016 17:00:18 -0500 Subject: [PATCH 10/62] Add support for building particle tree with targets. --- pytential/qbx/refinement.py | 23 ++++++------ pytential/qbx/utils.py | 70 +++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 30 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 730b4429..fc38369a 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function -from six.moves import range __copyright__ = """ Copyright (C) 2013 Andreas Kloeckner @@ -656,8 +655,8 @@ class QBXLayerPotentialSourceRefiner(object): tree.box_to_qbx_panel_lists, tree.qbx_panel_to_source_starts, tree.qbx_panel_to_center_starts, - tree.qbx_user_source_range.start, - tree.qbx_user_center_range.start, + tree.qbx_user_source_slice.start, + tree.qbx_user_center_slice.start, tree.sorted_target_ids, lpot_source.panel_sizes("nelements"), tree.nqbxpanels, @@ -714,9 +713,9 @@ class QBXLayerPotentialSourceRefiner(object): tree.box_to_qbx_panel_lists, tree.qbx_panel_to_source_starts, tree.qbx_panel_to_center_starts, - tree.qbx_user_source_range.start, - tree.qbx_user_center_range.start, - tree.qbx_user_panel_range.start, + tree.qbx_user_source_slice.start, + tree.qbx_user_center_slice.start, + tree.qbx_user_panel_slice.start, tree.sorted_target_ids, lpot_source.panel_sizes("nelements"), adjacency.adjacency_starts, @@ -829,8 +828,8 @@ class QBXLayerPotentialSourceRefiner(object): knl = self.get_tunnel_query_distance_finder(tree.dimensions, tree.coord_dtype, tree.particle_id_dtype) - evt = knl(tree.qbx_user_source_range.start, - tree.qbx_user_panel_range.start, + evt = knl(tree.qbx_user_source_slice.start, + tree.qbx_user_panel_slice.start, nqbxpanels, tree.qbx_panel_to_source_starts, tree.sorted_target_ids, @@ -914,11 +913,11 @@ class QBXLayerPotentialSourceRefiner(object): tp.draw_tree() sources = (tree.sources[0], tree.sources[1]) sti = tree.sorted_target_ids - plt.plot(sources[0][sti[tree.qbx_user_source_range]], - sources[1][sti[tree.qbx_user_source_range]], + plt.plot(sources[0][sti[tree.qbx_user_source_slice]], + sources[1][sti[tree.qbx_user_source_slice]], lw=0, marker=".", label="sources") - plt.plot(sources[0][sti[tree.qbx_user_center_range]], - sources[1][sti[tree.qbx_user_center_range]], + plt.plot(sources[0][sti[tree.qbx_user_center_slice]], + sources[1][sti[tree.qbx_user_center_slice]], lw=0, marker=".", label="centers") plt.axis("equal") plt.legend() diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index b685a800..3c1c9961 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function -from six.moves import range, zip __copyright__ = """ Copyright (C) 2016 Matt Wala @@ -45,18 +44,23 @@ class TreeWithQBXMetadataBuilder(object): class TreeWithQBXMetadata(Tree): """ .. attribute:: nqbxpanels - .. attribuet:: nsources - .. attribute:: ncenters + .. attribuet:: nqbxsources + .. attribute:: nqbxcenters + .. attribute:: nqbxtargets .. attribute:: box_to_qbx_panel_starts .. attribute:: box_to_qbx_panel_lists + .. attribute:: box_to_qbx_target_starts + .. attribute:: box_to_qbx_target_lists + .. attribute:: qbx_panel_to_source_starts .. attribute:: qbx_panel_to_center_starts - .. attribute:: qbx_user_source_range - .. attribute:: qbx_user_center_range - .. attribute:: qbx_user_panel_range + .. attribute:: qbx_user_source_slice + .. attribute:: qbx_user_center_slice + .. attribute:: qbx_user_panel_slice + .. attribute:: qbx_user_target_slice """ pass @@ -101,7 +105,7 @@ class TreeWithQBXMetadataBuilder(object): return result - def __call__(self, queue, lpot_source): + def __call__(self, queue, lpot_source, targets_list=()): """ Return a :class:`TreeWithQBXMetadata` built from the given layer potential source. @@ -110,31 +114,43 @@ class TreeWithQBXMetadataBuilder(object): :arg lpot_source: An instance of :class:`pytential.qbx.NewQBXLayerPotentialSource`. + + :arg targets_list: A list of :class:`pytential.target.TargetBase` """ # The ordering of particles is as follows: # - sources go first # - then centers # - then panels (=centers of mass) + # - then targets + + logger.info("start building tree with qbx metadata") sources = lpot_source.density_discr.nodes() centers = self.get_interleaved_centers(queue, lpot_source) centers_of_mass = lpot_source.panel_centers_of_mass() + targets = (tgt.nodes for tgt in targets_list) particles = tuple( cl.array.concatenate(dim_coords, queue=queue) - for dim_coords in zip(sources, centers, centers_of_mass)) + for dim_coords in zip(sources, centers, centers_of_mass, *targets)) + # Counts nparticles = len(particles[0]) npanels = len(centers_of_mass[0]) nsources = len(sources[0]) ncenters = len(centers[0]) # Each source gets an interior / exterior center. assert 2 * nsources == ncenters + ntargets = sum(tgt.nnodes for tgt in targets_list) - qbx_user_source_range = range(0, nsources) - nsourcescenters = 3 * nsources - qbx_user_center_range = range(nsources, nsourcescenters) - qbx_user_panel_range = range(nsourcescenters, nsourcescenters + npanels) + # Slices + qbx_user_source_slice = slice(0, nsources) + panel_slice_start = 3 * nsources + qbx_user_center_slice = slice(nsources, panel_slice_start) + target_slice_start = panel_slice_start + npanels + qbx_user_panel_slice = slice(panel_slice_start, panel_slice_start + npanels) + qbx_user_target_slice = slice(target_slice_start, + target_slice_start + ntargets) # Build tree with sources, centers, and centers of mass. Split boxes # only because of sources. @@ -150,8 +166,9 @@ class TreeWithQBXMetadataBuilder(object): # Compute box => panel relation qbx_panel_flags = refine_weights + del refine_weights qbx_panel_flags.fill(0) - qbx_panel_flags[3 * nsources:].fill(1) + qbx_panel_flags[qbx_user_panel_slice].fill(1) qbx_panel_flags.finish() from boxtree.tree import filter_target_lists_in_user_order @@ -159,7 +176,20 @@ class TreeWithQBXMetadataBuilder(object): filter_target_lists_in_user_order(queue, tree, qbx_panel_flags) .with_queue(queue)) # Fix up offset. - box_to_qbx_panel.target_lists -= 3 * nsources + box_to_qbx_panel.target_lists -= panel_slice_start + + # Compute box => target relation + qbx_target_flags = qbx_panel_flags + del qbx_panel_flags + qbx_target_flags.fill(0) + qbx_target_flags[qbx_user_target_slice].fill(1) + qbx_target_flags.finish() + + box_to_qbx_target = ( + filter_target_lists_in_user_order(queue, tree, qbx_target_flags) + .with_queue(queue)) + # Fix up offset. + box_to_qbx_target.target_lists -= target_slice_start qbx_panel_to_source_starts = cl.array.empty( queue, npanels + 1, dtype=tree.particle_id_dtype) @@ -186,19 +216,23 @@ class TreeWithQBXMetadataBuilder(object): except AttributeError: pass - logger.info("refiner: done building tree") + logger.info("done building tree with qbx metadata") return self.TreeWithQBXMetadata( box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, + box_to_qbx_target_starts=box_to_qbx_target.target_starts, + box_to_qbx_target_lists=box_to_qbx_target.target_lists, qbx_panel_to_source_starts=qbx_panel_to_source_starts, qbx_panel_to_center_starts=qbx_panel_to_center_starts, - qbx_user_source_range=qbx_user_source_range, - qbx_user_panel_range=qbx_user_panel_range, - qbx_user_center_range=qbx_user_center_range, + qbx_user_source_slice=qbx_user_source_slice, + qbx_user_panel_slice=qbx_user_panel_slice, + qbx_user_center_slice=qbx_user_center_slice, + qbx_user_target_slice=qbx_user_target_slice, nqbxpanels=npanels, nqbxsources=nsources, nqbxcenters=ncenters, + nqbxtargets=ntargets, **tree_attrs).with_queue(None) # }}} -- GitLab From 88ac564f2879ca58638b9048ec93e56f5c3eb718 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 7 Oct 2016 15:58:57 -0500 Subject: [PATCH 11/62] Move common macros to utils. --- pytential/qbx/refinement.py | 46 ++++++++----------------------------- pytential/qbx/utils.py | 34 ++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index fc38369a..b6121a8d 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -247,40 +247,14 @@ class NewQBXLayerPotentialSource(object): # }}} -from pyopencl.elementwise import ElementwiseTemplate from boxtree.area_query import AreaQueryElementwiseTemplate +from pyopencl.elementwise import ElementwiseTemplate from boxtree.tools import InlineBinarySearch +from pytential.qbx.utils import QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS # {{{ kernels -REFINER_C_MACROS = r"""//CL:mako// -// A note on node numberings: sources, centers, and panels each -// have their own numbering starting at 0. These macros convert -// the per-class numbering into the internal tree particle number. -#define INDEX_FOR_CENTER_PARTICLE(i) (sorted_target_ids[center_offset + i]) -#define INDEX_FOR_PANEL_PARTICLE(i) (sorted_target_ids[panel_offset + i]) -#define INDEX_FOR_SOURCE_PARTICLE(i) (sorted_target_ids[source_offset + i]) - -## Convert to dict first, as this may be passed as a tuple-of-tuples. -<% vec_types_dict = dict(vec_types) %> -typedef ${dtype_to_ctype(vec_types_dict[coord_dtype, dimensions])} coord_vec_t; -""" - - -REFINER_MAKO_DEFS = r"""//CL:mako// -<%def name="load_particle(particle, coords)"> - <% zerovect = ["0"] * 2 ** (dimensions - 1).bit_length() %> - /* Zero initialize, to allow for use in distance computations. */ - ${coords} = (coord_vec_t) (${", ".join(zerovect)}); - - %for ax in AXIS_NAMES[:dimensions]: - ${coords}.${ax} = sources_${ax}[${particle}]; - %endfor - -""" - - TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( arguments=r"""//CL:mako// /* input */ @@ -296,10 +270,10 @@ TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( /* input, dim-dependent size */ %for ax in AXIS_NAMES[:dimensions]: - coord_t *sources_${ax}, + coord_t *particles_${ax}, %endfor """, - operation=REFINER_MAKO_DEFS + REFINER_C_MACROS + r"""//CL:mako// + operation=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r"""//CL:mako// /* Find my panel. */ particle_id_t panel = bsearch(panel_to_source_starts, npanels + 1, i); @@ -364,16 +338,16 @@ CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( /* input, dim-dependent length */ %for ax in AXIS_NAMES[:dimensions]: - coord_t *sources_${ax}, + coord_t *particles_${ax}, %endfor """, - ball_center_and_radius_expr=REFINER_MAKO_DEFS + REFINER_C_MACROS + r""" + ball_center_and_radius_expr=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r""" particle_id_t my_panel = bsearch(panel_to_center_starts, npanels + 1, i); ${load_particle("INDEX_FOR_CENTER_PARTICLE(i)", ball_center)} ${ball_radius} = r_max + panel_sizes[my_panel] / 2; """, - leaf_found_op=REFINER_MAKO_DEFS + r""" + leaf_found_op=QBX_TREE_MAKO_DEFS + r""" for (particle_id_t panel_idx = box_to_panel_starts[${leaf_box_id}]; panel_idx < box_to_panel_starts[${leaf_box_id} + 1]; ++panel_idx) @@ -438,10 +412,10 @@ CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( /* input, dim-dependent length */ %for ax in AXIS_NAMES[:dimensions]: - coord_t *sources_${ax}, + coord_t *particles_${ax}, %endfor """, - ball_center_and_radius_expr=REFINER_MAKO_DEFS + REFINER_C_MACROS + r""" + ball_center_and_radius_expr=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r""" particle_id_t my_panel = bsearch(panel_to_center_starts, npanels + 1, i); coord_vec_t my_center_coords; @@ -449,7 +423,7 @@ CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( ${load_particle("INDEX_FOR_PANEL_PARTICLE(my_panel)", ball_center)} ${ball_radius} = tunnel_query_dists[my_panel]; """, - leaf_found_op=REFINER_MAKO_DEFS + r""" + leaf_found_op=QBX_TREE_MAKO_DEFS + r""" for (particle_id_t panel_idx = box_to_panel_starts[${leaf_box_id}]; panel_idx < box_to_panel_starts[${leaf_box_id} + 1]; ++panel_idx) diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index 3c1c9961..2633ee1b 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -37,6 +37,38 @@ import logging logger = logging.getLogger(__name__) +# {{{ c and mako snippets + +QBX_TREE_C_PREAMBLE = r"""//CL:mako// +// A note on node numberings: sources, centers, and panels each +// have their own numbering starting at 0. These macros convert +// the per-class numbering into the internal tree particle number. +#define INDEX_FOR_CENTER_PARTICLE(i) (sorted_target_ids[center_offset + i]) +#define INDEX_FOR_PANEL_PARTICLE(i) (sorted_target_ids[panel_offset + i]) +#define INDEX_FOR_SOURCE_PARTICLE(i) (sorted_target_ids[source_offset + i]) +#define INDEX_FOR_TARGET_PARTICLE(i) (sorted_target_ids[target_offset + i]) + +## Convert to dict first, as this may be passed as a tuple-of-tuples. +<% vec_types_dict = dict(vec_types) %> +typedef ${dtype_to_ctype(vec_types_dict[coord_dtype, dimensions])} coord_vec_t; +""" + + +QBX_TREE_MAKO_DEFS = r"""//CL:mako// +<%def name="load_particle(particle, coords)"> + <% zerovect = ["0"] * 2 ** (dimensions - 1).bit_length() %> + /* Zero initialize, to allow for use in distance computations. */ + ${coords} = (coord_vec_t) (${", ".join(zerovect)}); + + %for ax in AXIS_NAMES[:dimensions]: + ${coords}.${ax} = particles_${ax}[${particle}]; + %endfor + +""" + +# }}} + + # {{{ tree creation class TreeWithQBXMetadataBuilder(object): @@ -128,7 +160,7 @@ class TreeWithQBXMetadataBuilder(object): sources = lpot_source.density_discr.nodes() centers = self.get_interleaved_centers(queue, lpot_source) centers_of_mass = lpot_source.panel_centers_of_mass() - targets = (tgt.nodes for tgt in targets_list) + targets = (tgt.nodes() for tgt in targets_list) particles = tuple( cl.array.concatenate(dim_coords, queue=queue) -- GitLab From 9a5fed6e10fc816add36040834ffa0674409c4b8 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 7 Oct 2016 15:59:37 -0500 Subject: [PATCH 12/62] [wip] Implement target association. --- pytential/qbx/target_assoc.py | 569 ++++++++++++++++++++++++++++++++++ test/test_geometry.py | 64 ++++ 2 files changed, 633 insertions(+) create mode 100644 pytential/qbx/target_assoc.py diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py new file mode 100644 index 00000000..e89f488c --- /dev/null +++ b/pytential/qbx/target_assoc.py @@ -0,0 +1,569 @@ +# -*- coding: utf-8 -*- +from __future__ import division, absolute_import, print_function + +__copyright__ = """ +Copyright (C) 2016 Matt Wala +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +from pytools import memoize_method + +import pyopencl as cl +import pyopencl.array # noqa + +import logging +logger = logging.getLogger(__name__) + +# HOW DOES TARGET ASSOCIATION WORK? +# +# The goal of the target association is to: +# a) decide which targets require QBX, and +# b) decide which centers to use for targets that require QBX, and +# c) if no good centers are available for a target, decide which panels to +# refine. +# +# The flow chart of what happens to target t is shown below. Pass names are in +# parentheses: +# +# START HERE +# | +# v +# +-----------------------+ No +-----------------------------------+ +# |(QBXTargetMarker) |--->|Mark t as not requiring QBX. | +# |Is t close to a source?| +-----------------------------------+ +# +-----------------------+ +# | Yes +# v +# +-----------------------+ No +-----------------------------------+ +# |(QBXCenterFinder) |--->|(QBXFailedTargetAssociationRefiner)| +# |Is there a valid center| |Mark panels close to t for | +# |close to t? | |refinement. | +# +-----------------------+ +-----------------------------------+ +# | Yes +# v +# +-----------------------+ +# |Associate t with the | +# |best available center. | +# +-----------------------+ + + +# {{{ kernels + +TARGET_ASSOC_DEFINES = r""" +#define EPSILON .05 + +enum TargetStatus +{ + UNMARKED, + MARKED_QBX_CENTER_PENDING, + MARKED_QBX_CENTER_FOUND +}; + +enum TargetFlag +{ + INTERIOR_OR_EXTERIOR_VOLUME_TARGET = 0, + INTERIOR_SURFACE_TARGET = -1, + EXTERIOR_SURFACE_TARGET = +1, + INTERIOR_VOLUME_TARGET = -2, + EXTERIOR_VOLUME_TARGET = +2 +}; +""" + +MARKED_QBX_CENTER_PENDING = 1 + + +from boxtree.area_query import AreaQueryElementwiseTemplate +from boxtree.tools import InlineBinarySearch +from pytential.qbx.utils import QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS + + +QBX_TARGET_MARKER = AreaQueryElementwiseTemplate( + extra_args=r""" + /* input */ + particle_id_t *box_to_target_starts, + particle_id_t *box_to_target_lists, + particle_id_t *panel_to_source_starts, + particle_id_t source_offset, + particle_id_t target_offset, + int npanels, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + + /* output */ + int *target_status, + int *found_target_close_to_panel, + + /* input, dim-dependent size */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *particles_${ax}, + %endfor + """, + ball_center_and_radius_expr=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r""" + ${load_particle("INDEX_FOR_SOURCE_PARTICLE(i)", ball_center)} + particle_id_t my_panel = bsearch(panel_to_source_starts, npanels + 1, i); + ${ball_radius} = panel_sizes[my_panel] / 2; + """, + leaf_found_op=QBX_TREE_MAKO_DEFS + r""" + for (particle_id_t target_idx = box_to_target_starts[${leaf_box_id}]; + target_idx < box_to_target_starts[${leaf_box_id} + 1]; + ++target_idx) + { + particle_id_t target = box_to_target_lists[target_idx]; + coord_vec_t target_coords; + ${load_particle("INDEX_FOR_TARGET_PARTICLE(target)", "target_coords")} + + if (distance(target_coords, ${ball_center}) <= ${ball_radius}) + { + target_status[target] = MARKED_QBX_CENTER_PENDING; + *found_target_close_to_panel = 1; + } + } + """, + name="mark_targets", + preamble=TARGET_ASSOC_DEFINES + str(InlineBinarySearch("particle_id_t"))) + + +QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( + extra_args=r""" + /* input */ + particle_id_t *box_to_target_starts, + particle_id_t *box_to_target_lists, + particle_id_t *panel_to_source_starts, + particle_id_t source_offset, + particle_id_t center_offset, + particle_id_t target_offset, + int npanels, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + int *target_flags, + + /* input/output */ + int *target_status, + + /* output */ + int *target_to_center, + + /* input, dim-dependent size */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *particles_${ax}, + %endfor + """, + ball_center_and_radius_expr=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r""" + ${load_particle("INDEX_FOR_CENTER_PARTICLE(i)", ball_center)} + particle_id_t my_panel = bsearch( + panel_to_source_starts, npanels + 1, SOURCE_FOR_CENTER_PARTICLE(i)); + ${ball_radius} = panel_sizes[my_panel] / 2; + """, + leaf_found_op=QBX_TREE_MAKO_DEFS + r""" + int my_side = SIDE_FOR_CENTER_PARTICLE(i); + + for (particle_id_t target_idx = box_to_target_starts[${leaf_box_id}]; + target_idx < box_to_target_starts[${leaf_box_id} + 1]; + ++target_idx) + { + particle_id_t target = box_to_target_lists[target_idx]; + coord_vec_t target_coords; + ${load_particle("INDEX_FOR_TARGET_PARTICLE(target)", "target_coords")} + + coord_t my_dist_to_target = distance(target_coords, ${ball_center}); + + if (/* Sign of side should match requested target sign. */ + my_side * target_flags[target] < 0 + /* Target should be covered by center. */ + || my_dist_to_target <= ${ball_radius} * (1 + EPSILON)) + { + continue; + } + + target_status[target] = MARKED_QBX_CENTER_FOUND; + + int curr_center, curr_center_updated; + do + { + /* Read closest center. */ + curr_center = target_to_center[target]; + + /* Check if I am closer than recorded closest center. */ + if (curr_center != -1) + { + coord_vec_t curr_center_coords; + ${load_particle( + "INDEX_FOR_CENTER_PARTICLE(curr_center)", + "curr_center_coords")} + + if (distance(target_coords, curr_center_coords) + <= my_dist_to_target) + { + /* The current center is closer, don't update. */ + break; + } + } + + /* Try to update the memory location. */ + curr_center_updated = atomic_cmpxchg( + (volatile __global int *) &target_to_center[target], + curr_center, i); + } while (curr_center != curr_center_updated); + } + """, + name="find_centers", + preamble=TARGET_ASSOC_DEFINES + str(InlineBinarySearch("particle_id_t"))) + + +QBX_FAILED_TARGET_ASSOCIATION_REFINER = AreaQueryElementwiseTemplate( + extra_args=r""" + /* input */ + particle_id_t *box_to_target_starts, + particle_id_t *box_to_target_lists, + particle_id_t *panel_to_source_starts, + particle_id_t source_offset, + particle_id_t target_offset, + int npanels, + particle_id_t *sorted_target_ids, + coord_t *panel_sizes, + int *target_status, + + /* output */ + int *refine_flags, + int *found_panel_to_refine, + + /* input, dim-dependent size */ + %for ax in AXIS_NAMES[:dimensions]: + coord_t *particles_${ax}, + %endfor + """, + ball_center_and_radius_expr=QBX_TREE_C_PREAMBLE + QBX_TREE_MAKO_DEFS + r""" + ${load_particle("INDEX_FOR_SOURCE_PARTICLE(i)", ball_center)} + particle_id_t my_panel = bsearch(panel_to_source_starts, npanels + 1, i); + ${ball_radius} = panel_sizes[my_panel] / 2; + """, + leaf_found_op=QBX_TREE_MAKO_DEFS + r""" + for (particle_id_t target_idx = box_to_target_starts[${leaf_box_id}]; + target_idx < box_to_target_starts[${leaf_box_id} + 1]; + ++target_idx) + { + particle_id_t target = box_to_target_lists[target_idx]; + coord_vec_t target_coords; + ${load_particle("INDEX_FOR_TARGET_PARTICLE(target)", "target_coords")} + + bool is_close = distance(target_coords, ${ball_center}) <= ${ball_radius}; + + if (is_close && target_status[target] == MARKED_QBX_CENTER_PENDING) + { + refine_flags[my_panel] = 1; + *found_panel_to_refine = 1; + } + } + """, + name="refine_panels", + preamble=TARGET_ASSOC_DEFINES + str(InlineBinarySearch("particle_id_t"))) + +# }}} + + +# {{{ target associator + +class TargetAssociationFailedException(Exception): + """ + .. attribute:: refine_flags + """ + pass + + +class QBXTargetAssociator(object): + + def __init__(self, cl_context): + from pytential.qbx.utils import TreeWithQBXMetadataBuilder + self.tree_builder = TreeWithQBXMetadataBuilder(cl_context) + self.cl_context = cl_context + from boxtree.area_query import PeerListFinder + self.peer_list_finder = PeerListFinder(cl_context) + + # {{{ kernel generation + + @memoize_method + def get_qbx_target_marker(self, + dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + particle_id_dtype, + max_levels): + return QBX_TARGET_MARKER.generate( + self.cl_context, + dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + max_levels, + extra_type_aliases=(("particle_id_t", particle_id_dtype),)) + + @memoize_method + def get_qbx_center_finder(self, + dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + particle_id_dtype, + max_levels): + return QBX_CENTER_FINDER.generate( + self.cl_context, + dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + max_levels, + extra_type_aliases=(("particle_id_t", particle_id_dtype),)) + + @memoize_method + def get_qbx_failed_target_association_refiner(self, dimensions, coord_dtype, + box_id_dtype, peer_list_idx_dtype, + particle_id_dtype, max_levels): + return QBX_FAILED_TARGET_ASSOCIATION_REFINER.generate( + self.cl_context, + dimensions, + coord_dtype, + box_id_dtype, + peer_list_idx_dtype, + max_levels, + extra_type_aliases=(("particle_id_t", particle_id_dtype),)) + + # }}} + + def mark_targets(self, queue, tree, peer_lists, lpot_source, target_status, debug, + wait_for=None): + # Avoid generating too many kernels. + from pytools import div_ceil + max_levels = 10 * div_ceil(tree.nlevels, 10) + + knl = self.get_qbx_target_marker( + tree.dimensions, + tree.coord_dtype, tree.box_id_dtype, + peer_lists.peer_list_starts.dtype, + tree.particle_id_dtype, + max_levels) + + from boxtree.area_query import AreaQueryElementwiseTemplate + unwrap_args = AreaQueryElementwiseTemplate.unwrap_args + + found_target_close_to_panel = cl.array.zeros(queue, 1, np.int32) + found_target_close_to_panel.finish() + + logger.info("target association: marking targets close to panels") + + evt = knl( + *unwrap_args( + tree, peer_lists, + tree.box_to_qbx_target_starts, + tree.box_to_qbx_target_lists, + tree.qbx_panel_to_source_starts, + tree.qbx_user_source_slice.start, + tree.qbx_user_target_slice.start, + tree.nqbxpanels, + tree.sorted_target_ids, + lpot_source.panel_sizes("nelements"), + target_status, + found_target_close_to_panel, + *tree.sources), + range=slice(tree.nqbxsources), + queue=queue, + wait_for=wait_for) + + if debug: + target_status.finish() + # Marked target = 1, 0 otherwise + marked_target_count = cl.array.sum(target_status).get() + logger.debug("target association: {} targets marked close to panels" + .format(marked_target_count)) + + cl.wait_for_events([evt]) + + logger.info("target association: done marking targets close to panels") + + return (found_target_close_to_panel.get() == 1).all() + + def try_find_centers(self, queue, tree, peer_lists, lpot_source, + target_status, target_flags, target_to_center, debug, + wait_for=None): + # Avoid generating too many kernels. + from pytools import div_ceil + max_levels = 10 * div_ceil(tree.nlevels, 10) + + knl = self.get_qbx_center_finder( + tree.dimensions, + tree.coord_dtype, tree.box_id_dtype, + peer_lists.peer_list_starts.dtype, + tree.particle_id_dtype, + max_levels) + + from boxtree.area_query import AreaQueryElementwiseTemplate + unwrap_args = AreaQueryElementwiseTemplate.unwrap_args + + if debug: + target_status.finish() + marked_target_count = int(cl.array.sum(target_status).get()) + + logger.info("target association: finding centers for targets") + + evt = knl( + *unwrap_args( + tree, peer_lists, + tree.box_to_qbx_target_starts, + tree.box_to_qbx_target_lists, + tree.qbx_panel_to_source_starts, + tree.qbx_user_source_slice.start, + tree.qbx_user_center_slice.start, + tree.qbx_user_target_slice.start, + tree.nqbxpanels, + tree.sorted_target_ids, + lpot_source.panel_sizes("nelements"), + target_flags, + target_status, + target_to_center, + *tree.sources), + range=slice(tree.nqbxcenters), + queue=queue, + wait_for=wait_for) + + if debug: + target_status.finish() + ntargets_associated = ( + int(cl.array.sum(target_status).get()) - marked_target_count) + assert ntargets_associated >= 0 + logger.debug("target association: {} targets were assigned centers" + .format(ntargets_associated)) + + cl.wait_for_events([evt]) + logger.info("target association: done finding centers for targets") + return + + def mark_panels_for_refinement(self, queue, tree, peer_lists, lpot_source, + target_status, refine_flags, debug, + wait_for=None): + # Avoid generating too many kernels. + from pytools import div_ceil + max_levels = 10 * div_ceil(tree.nlevels, 10) + + knl = self.get_qbx_failed_target_association_refiner( + tree.dimensions, + tree.coord_dtype, tree.box_id_dtype, + peer_lists.peer_list_starts.dtype, + tree.particle_id_dtype, + max_levels) + + from boxtree.area_query import AreaQueryElementwiseTemplate + unwrap_args = AreaQueryElementwiseTemplate.unwrap_args + + found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) + found_panel_to_refine.finish() + + logger.info("target association: marking panels for refinement") + + evt = knl( + *unwrap_args( + tree, peer_lists, + tree.box_to_qbx_target_starts, + tree.box_to_qbx_target_lists, + tree.qbx_panel_to_source_starts, + tree.qbx_user_source_slice.start, + tree.qbx_user_target_slice.start, + tree.nqbxpanels, + tree.sorted_target_ids, + lpot_source.panel_sizes("nelements"), + target_status, + refine_flags, + found_panel_to_refine, + *tree.sources), + range=slice(tree.nqbxsources), + queue=queue, + wait_for=wait_for) + + if debug: + refine_flags.finish() + # Marked panel = 1, 0 otherwise + marked_panel_count = cl.array.sum(refine_flags).get() + logger.debug("target association: {} panels flagged for refinement" + .format(marked_panel_count)) + + cl.wait_for_events([evt]) + + logger.info("target association: done marking panels for refinement") + + return (found_panel_to_refine.get() == 1).all() + + def make_target_flags(self, queue, target_discrs): + ntargets = sum(discr.nnodes for discr, _ in target_discrs) + target_flags = cl.array.empty(queue, ntargets, dtype=np.int32) + offset = 0 + for discr, flags, in target_discrs: + if np.isscalar(flags): + target_flags[offset:offset + discr.nnodes].fill(flags) + else: + assert len(flags) == discr.nnodes + target_flags[offset:offset + discr.nnodes] = flags + offset += discr.nnodes + target_flags.finish() + return target_flags + + def __call__(self, lpot_source, target_discrs, debug=True, wait_for=None): + with cl.CommandQueue(self.cl_context) as queue: + tree = self.tree_builder(queue, lpot_source, [discr for discr, _ in target_discrs]) + peer_lists, evt = self.peer_list_finder(queue, tree, wait_for) + wait_for = [evt] + + # Get target flags array. + target_status = cl.array.zeros(queue, tree.nqbxtargets, dtype=np.int32) + target_status.finish() + + have_close_targets = self.mark_targets(queue, tree, + peer_lists, + lpot_source, + target_status, debug) + + target_to_center = cl.array.empty(queue, tree.ntargets, dtype=np.int32) + target_to_center.fill(-1) + + if not have_close_targets: + return target_to_center.with_queue(None) + + target_flags = self.make_target_flags(queue, target_discrs) + + self.try_find_centers(queue, tree, peer_lists, lpot_source, + target_status, target_flags, target_to_center, + debug) + + if True: #(target_status == MARKED_QBX_CENTER_PENDING).any().get(): + refine_flags = cl.array.zeros(queue, tree.nqbxpanels, dtype=np.int32) + have_panel_to_refine = self.mark_panels_for_refinement(queue, + tree, peer_lists, + lpot_source, target_status, + refine_flags, debug) + assert have_panel_to_refine + raise TargetAssociationFailedException(refine_flags.with_queue(None)) + + return target_to_center.with_queue(None) + +# }}} + +# vim: foldmethod=marker:filetype=pyopencl diff --git a/test/test_geometry.py b/test/test_geometry.py index a76c2751..b639455f 100644 --- a/test/test_geometry.py +++ b/test/test_geometry.py @@ -164,6 +164,70 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement check_panel_pair(panel_1, panel_2) +def test_ellipse_target_association(ctx_getter): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + order = 16 + + # Make the curve mesh. + nelements = 100 + mesh = make_curve_mesh(partial(ellipse, 3), np.linspace(0, 1, nelements+1), order) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + factory = InterpolatoryQuadratureSimplexGroupFactory(order) + + discr = Discretization(cl_ctx, mesh, factory) + + from pytential.qbx.refinement import ( + NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + + lpot_source = NewQBXLayerPotentialSource(discr, order) + del discr + refiner = QBXLayerPotentialSourceRefiner(cl_ctx) + + lpot_source, conn = refiner(lpot_source, factory) + + print("lpot_source", lpot_source) + + discr_nodes = lpot_source.density_discr.nodes().get(queue) + int_centers = lpot_source.centers(-1) + int_centers = np.array([axis.get(queue) for axis in int_centers]) + ext_centers = lpot_source.centers(+1) + ext_centers = np.array([axis.get(queue) for axis in ext_centers]) + panel_sizes = lpot_source.panel_sizes("nelements").get(queue) + + # Create target discretizations. + target_discrs = [ + # On-surface target, interior + (lpot_source.density_discr, -1), + # On-surface target, exterior + (lpot_source.density_discr, +1), + # Interior targets + #(), + # Exterior targets + #(), + # Far targets, should not need centers + #() + ] + + from pytential.qbx.target_assoc import QBXTargetAssociator + target_assoc = QBXTargetAssociator(cl_ctx) + + target_assoc_result = target_assoc(lpot_source, target_discrs) + + def check_surface_targets(): + pass + + def check_close_targets(): + pass + + def check_far_targets(): + pass + + # You can test individual routines by typing # $ python test_layer_pot.py 'test_routine()' -- GitLab From 314c8de6ee91de3085d8ff6d133494b8883daa15 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 12 Oct 2016 14:33:29 -0500 Subject: [PATCH 13/62] Pacify flake8, run refiner on debug. --- pytential/qbx/target_assoc.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index e89f488c..e79b5f6a 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -71,6 +71,7 @@ logger = logging.getLogger(__name__) # {{{ kernels TARGET_ASSOC_DEFINES = r""" +// Stick out factor for target association #define EPSILON .05 enum TargetStatus @@ -90,7 +91,12 @@ enum TargetFlag }; """ -MARKED_QBX_CENTER_PENDING = 1 + +class TargetStatus(object): + # NOTE: Must match "enum TargetStatus" above + UNMARKED = 0 + MARKED_QBX_CENTER_PENDING = 1 + MARKED_QBX_CENTER_PENDING = 2 from boxtree.area_query import AreaQueryElementwiseTemplate @@ -267,7 +273,9 @@ QBX_FAILED_TARGET_ASSOCIATION_REFINER = AreaQueryElementwiseTemplate( coord_vec_t target_coords; ${load_particle("INDEX_FOR_TARGET_PARTICLE(target)", "target_coords")} - bool is_close = distance(target_coords, ${ball_center}) <= ${ball_radius}; + bool is_close = + distance(target_coords, ${ball_center}) + <= ${ball_radius}; if (is_close && target_status[target] == MARKED_QBX_CENTER_PENDING) { @@ -351,8 +359,8 @@ class QBXTargetAssociator(object): # }}} - def mark_targets(self, queue, tree, peer_lists, lpot_source, target_status, debug, - wait_for=None): + def mark_targets(self, queue, tree, peer_lists, lpot_source, target_status, + debug, wait_for=None): # Avoid generating too many kernels. from pytools import div_ceil max_levels = 10 * div_ceil(tree.nlevels, 10) @@ -527,8 +535,10 @@ class QBXTargetAssociator(object): return target_flags def __call__(self, lpot_source, target_discrs, debug=True, wait_for=None): + with cl.CommandQueue(self.cl_context) as queue: - tree = self.tree_builder(queue, lpot_source, [discr for discr, _ in target_discrs]) + tree = self.tree_builder(queue, lpot_source, + [discr for discr, _ in target_discrs]) peer_lists, evt = self.peer_list_finder(queue, tree, wait_for) wait_for = [evt] @@ -553,14 +563,18 @@ class QBXTargetAssociator(object): target_status, target_flags, target_to_center, debug) - if True: #(target_status == MARKED_QBX_CENTER_PENDING).any().get(): + if debug or ( + target_status == TargetStatus.MARKED_QBX_CENTER_PENDING)\ + .any().get(): refine_flags = cl.array.zeros(queue, tree.nqbxpanels, dtype=np.int32) have_panel_to_refine = self.mark_panels_for_refinement(queue, tree, peer_lists, lpot_source, target_status, refine_flags, debug) - assert have_panel_to_refine - raise TargetAssociationFailedException(refine_flags.with_queue(None)) + assert debug or have_panel_to_refine + if have_panel_to_refine: + raise TargetAssociationFailedException( + refine_flags.with_queue(None)) return target_to_center.with_queue(None) -- GitLab From f835287e3b60b79b48d120732db3f006aecc3f6e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 12 Oct 2016 15:19:13 -0500 Subject: [PATCH 14/62] [wip] Use space invader queries. --- pytential/qbx/refinement.py | 41 +- pytential/qbx/target_assoc.py | 372 ++++++++++++------ pytential/qbx/utils.py | 114 +++--- test/{test_geometry.py => test_global_qbx.py} | 128 ++++-- 4 files changed, 446 insertions(+), 209 deletions(-) rename test/{test_geometry.py => test_global_qbx.py} (65%) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index b6121a8d..8bcdbe15 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -175,14 +175,14 @@ class NewQBXLayerPotentialSource(object): return tuple(panels[d, :] for d in range(mesh.ambient_dim)) @memoize_method - def panel_sizes(self, last_dim_length="nnodes"): - assert last_dim_length in ["nnodes", "nelements"] + def panel_sizes(self, last_dim_length="nsources"): + assert last_dim_length in ["nsources", "ncenters", "npanels"] # To get the panel size this does the equivalent of ∫ 1 ds. # FIXME: Kernel optimizations discr = self.density_discr - if last_dim_length == "nnodes": + if last_dim_length == "nsources" or last_dim_length == "ncenters": knl = lp.make_kernel( "{[i,j,k]: 0<=i typedef ${dtype_to_ctype(vec_types_dict[coord_dtype, dimensions])} coord_vec_t; @@ -64,11 +67,39 @@ QBX_TREE_MAKO_DEFS = r"""//CL:mako// ${coords}.${ax} = particles_${ax}[${particle}]; %endfor + +<%def name="find_leaf_for_particle(particle, box_to_particle)"> + box_id_t box = bsearch_boxes(${box_to_particle}, nboxes + 1, ${particle}); + """ # }}} +# {{{ interleaver kernel + +@memoize +def get_interleaver_kernel(dtype): + # NOTE: Returned kernel needs dstlen or dst parameter + from pymbolic import var + knl = lp.make_kernel( + "[srclen,dstlen] -> {[i]: 0<=i panel relation - qbx_panel_flags = refine_weights + # Compute box => particle class relations + flags = refine_weights del refine_weights - qbx_panel_flags.fill(0) - qbx_panel_flags[qbx_user_panel_slice].fill(1) - qbx_panel_flags.finish() - - from boxtree.tree import filter_target_lists_in_user_order - box_to_qbx_panel = ( - filter_target_lists_in_user_order(queue, tree, qbx_panel_flags) + particle_classes = {} + + for class_name, particle_slice, fixup in ( + ("box_to_qbx_source", qbx_user_source_slice, 0), + ("box_to_qbx_target", qbx_user_target_slice, -target_slice_start), + ("box_to_qbx_center", qbx_user_center_slice, -nsources), + ("box_to_qbx_panel", qbx_user_panel_slice, -panel_slice_start)): + flags.fill(0) + flags[particle_slice].fill(1) + flags.finish() + + from boxtree.tree import filter_target_lists_in_user_order + box_to_class = ( + filter_target_lists_in_user_order(queue, tree, flags) .with_queue(queue)) - # Fix up offset. - box_to_qbx_panel.target_lists -= panel_slice_start - - # Compute box => target relation - qbx_target_flags = qbx_panel_flags - del qbx_panel_flags - qbx_target_flags.fill(0) - qbx_target_flags[qbx_user_target_slice].fill(1) - qbx_target_flags.finish() - - box_to_qbx_target = ( - filter_target_lists_in_user_order(queue, tree, qbx_target_flags) - .with_queue(queue)) - # Fix up offset. - box_to_qbx_target.target_lists -= target_slice_start - qbx_panel_to_source_starts = cl.array.empty( - queue, npanels + 1, dtype=tree.particle_id_dtype) + if fixup: + box_to_class.target_lists += fixup + particle_classes[class_name + "_starts"] = box_to_class.target_starts + particle_classes[class_name + "_lists"] = box_to_class.target_lists + + del flags + del box_to_class # Compute panel => source relation + qbx_panel_to_source_starts = cl.array.empty( + queue, npanels + 1, dtype=tree.particle_id_dtype) el_offset = 0 for group in lpot_source.density_discr.groups: qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ @@ -248,13 +270,11 @@ class TreeWithQBXMetadataBuilder(object): except AttributeError: pass + tree_attrs.update(particle_classes) + logger.info("done building tree with qbx metadata") return self.TreeWithQBXMetadata( - box_to_qbx_panel_starts=box_to_qbx_panel.target_starts, - box_to_qbx_panel_lists=box_to_qbx_panel.target_lists, - box_to_qbx_target_starts=box_to_qbx_target.target_starts, - box_to_qbx_target_lists=box_to_qbx_target.target_lists, qbx_panel_to_source_starts=qbx_panel_to_source_starts, qbx_panel_to_center_starts=qbx_panel_to_center_starts, qbx_user_source_slice=qbx_user_source_slice, diff --git a/test/test_geometry.py b/test/test_global_qbx.py similarity index 65% rename from test/test_geometry.py rename to test/test_global_qbx.py index b639455f..10ae9eb7 100644 --- a/test/test_geometry.py +++ b/test/test_global_qbx.py @@ -36,6 +36,8 @@ from functools import partial from meshmode.mesh.generation import ( # noqa ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut, make_curve_mesh) +from extra_curve_data import horseshoe + import logging logger = logging.getLogger(__name__) @@ -43,6 +45,8 @@ logger = logging.getLogger(__name__) __all__ = ["pytest_generate_tests"] +# {{{ utilities for iterating over panels + class ElementInfo(RecordWithoutPickling): """ .. attribute:: element_nr @@ -75,19 +79,20 @@ def iter_elements(discr): discr_nodes_idx += discr_group.nunit_nodes - -from extra_curve_data import horseshoe +# }}} @pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ ("20-to-1 ellipse", partial(ellipse, 20), 100), ("horseshoe", horseshoe, 64), ]) -def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): +def test_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - order = 16 + # {{{ generate lpot source, run refiner + + order = 5 helmholtz_k = 10 mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) @@ -113,7 +118,11 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement int_centers = np.array([axis.get(queue) for axis in int_centers]) ext_centers = lpot_source.centers(+1) ext_centers = np.array([axis.get(queue) for axis in ext_centers]) - panel_sizes = lpot_source.panel_sizes("nelements").get(queue) + panel_sizes = lpot_source.panel_sizes("npanels").get(queue) + + # }}} + + # {{{ check if satisfying criteria def check_panel(panel): # Check 2-to-1 panel to neighbor size ratio. @@ -163,6 +172,8 @@ def test_global_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelement for panel_2 in iter_elements(lpot_source.density_discr): check_panel_pair(panel_1, panel_2) + # }}} + def test_ellipse_target_association(ctx_getter): cl_ctx = ctx_getter() @@ -190,42 +201,103 @@ def test_ellipse_target_association(ctx_getter): lpot_source, conn = refiner(lpot_source, factory) - print("lpot_source", lpot_source) - discr_nodes = lpot_source.density_discr.nodes().get(queue) int_centers = lpot_source.centers(-1) - int_centers = np.array([axis.get(queue) for axis in int_centers]) ext_centers = lpot_source.centers(+1) - ext_centers = np.array([axis.get(queue) for axis in ext_centers]) - panel_sizes = lpot_source.panel_sizes("nelements").get(queue) + + RNG_SEED = 10 + from pyopencl.clrandom import PhiloxGenerator + rng = PhiloxGenerator(cl_ctx, seed=RNG_SEED) + nsources = lpot_source.density_discr.nnodes + noise = rng.uniform(queue, nsources, dtype=np.float, a=0.01, b=1.0) + + def close_targets(sign): + from pytential import sym, bind + nodes = bind(lpot_source.density_discr, sym.Nodes())(queue) + normals = bind(lpot_source.density_discr, sym.normal())(queue) + panel_sizes = lpot_source.panel_sizes().with_queue(queue) + return (nodes + normals * sign * noise * panel_sizes / 2).as_vector(np.object) + + from pytential.target import PointsTarget + + int_targets = PointsTarget(close_targets(-1)) + ext_targets = PointsTarget(close_targets(+1)) + NFARTARGETS = 10 + far_targets = PointsTarget( + (rng.normal(queue, NFARTARGETS, np.float, sigma=5e-3), + rng.normal(queue, NFARTARGETS, np.float, sigma=5e-3))) # Create target discretizations. target_discrs = [ - # On-surface target, interior + # On-surface targets, interior (lpot_source.density_discr, -1), - # On-surface target, exterior + # On-surface targets, exterior (lpot_source.density_discr, +1), - # Interior targets - #(), - # Exterior targets - #(), - # Far targets, should not need centers - #() + # Interior close targets + (int_targets, -2), + # Exterior close targets + (ext_targets, +2), + ## Far targets, should not need centers + (far_targets, 0), ] - from pytential.qbx.target_assoc import QBXTargetAssociator - target_assoc = QBXTargetAssociator(cl_ctx) - - target_assoc_result = target_assoc(lpot_source, target_discrs) + sizes = np.cumsum([discr.nnodes for discr, _ in target_discrs]) + from itertools import chain + slices = [slice(start, end) for start, end in zip(chain([0], sizes), sizes)] - def check_surface_targets(): - pass + from pytential.qbx.target_assoc import QBXTargetAssociator + target_assoc = QBXTargetAssociator(cl_ctx)(lpot_source, target_discrs) + target_assoc = target_assoc.get(queue=queue) - def check_close_targets(): - pass + panel_sizes = lpot_source.panel_sizes("nsources").get(queue) - def check_far_targets(): - pass + int_centers = np.array([axis.get(queue) for axis in int_centers]) + ext_centers = np.array([axis.get(queue) for axis in ext_centers]) + int_targets = np.array([axis.get(queue) for axis in int_targets.nodes()]) + ext_targets = np.array([axis.get(queue) for axis in ext_targets.nodes()]) + + # Checks that the sources match with their own centers. + def check_on_surface_targets(nsources, true_side, target_to_source_result, + target_to_side_result): + sources = np.arange(0, nsources) + assert (target_to_source_result == sources).all() + assert (target_to_side_result == true_side).all() + + # Checks that the targets match with centers on the appropriate side and + # within the allowable distance. + def check_close_targets(centers, targets, true_side, + target_to_source_result, target_to_side_result): + assert (target_to_side_result == true_side).all() + dists = la.norm((targets.T - centers.T[target_to_source_result]), axis=1) + assert (dists <= panel_sizes[target_to_source_result] / 2).all() + + def check_far_targets(target_to_source_result, target_to_side_result): + assert (target_to_source_result == -1).all() + assert (target_to_side_result == 0).all() + + check_on_surface_targets( + nsources, -1, + target_assoc.target_to_source[slices[0]], + target_assoc.target_to_center_side[slices[0]]) + + check_on_surface_targets( + nsources, +1, + target_assoc.target_to_source[slices[1]], + target_assoc.target_to_center_side[slices[1]]) + + check_close_targets( + int_centers, int_targets, -1, + target_assoc.target_to_source[slices[2]], + target_assoc.target_to_center_side[slices[2]]) + + check_close_targets( + ext_centers, ext_targets, +1, + target_assoc.target_to_source[slices[3]], + target_assoc.target_to_center_side[slices[3]]) + + check_far_targets( + target_assoc.target_to_source[slices[4]], + target_assoc.target_to_center_side[slices[4]]) # You can test individual routines by typing -- GitLab From d12513b707b5998e6be602eef954a0d658b2f453 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 5 Nov 2016 18:34:14 -0500 Subject: [PATCH 15/62] more work on refiner and target associator --- pytential/qbx/refinement.py | 44 +++------- pytential/qbx/target_assoc.py | 148 ++++++++++++++++++---------------- pytential/qbx/utils.py | 41 +++++++--- test/extra_curve_data.py | 24 ++++++ test/test_global_qbx.py | 138 +++++++++++++++++++++++-------- 5 files changed, 250 insertions(+), 145 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 8bcdbe15..4b83c2e4 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -32,6 +32,7 @@ import numpy as np from pytools import memoize_method from meshmode.discretization import Discretization from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory +from pytential.qbx.utils import DiscrPlotterMixin import pyopencl as cl @@ -261,6 +262,9 @@ from boxtree.tools import InlineBinarySearch from pytential.qbx.utils import QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS +unwrap_args = AreaQueryElementwiseTemplate.unwrap_args + + # {{{ kernels TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( @@ -486,7 +490,7 @@ CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER = AreaQueryElementwiseTemplate( # {{{ lpot source refiner -class QBXLayerPotentialSourceRefiner(object): +class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): """ Driver for refining the QBX source grid. Follows [1]_. @@ -565,11 +569,13 @@ class QBXLayerPotentialSourceRefiner(object): <> neighbor_stop = panel_adjacency_starts[panel + 1] for ineighbor <> neighbor = panel_adjacency_lists[ineighbor] + <> oversize = (refine_flags_prev[panel] == 0 and ( (panel_sizes[panel] > 2 * panel_sizes[neighbor]) or (panel_sizes[panel] > panel_sizes[neighbor] and refine_flags_prev[neighbor] == 1))) + refine_flags[panel] = 1 {if=oversize} refine_flags_updated = 1 { id=write_refine_flags_updated,if=oversize} @@ -580,7 +586,8 @@ class QBXLayerPotentialSourceRefiner(object): "..." ], options="return_dict", - silenced_warnings="write_race(write_refine_flags_updated)") + silenced_warnings="write_race(write_refine_flags_updated)", + name="refine_2_to_1_adj_panel_size_ratio") knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl @@ -596,7 +603,8 @@ class QBXLayerPotentialSourceRefiner(object): end """, options="return_dict", - silenced_warnings="write_race(write_refine_flags_updated)") + silenced_warnings="write_race(write_refine_flags_updated)", + name="refine_helmholtz_k_to_panel_size_ratio") knl = lp.split_iname(knl, "panel", 128, inner_tag="l.0", outer_tag="g.0") return knl @@ -627,9 +635,6 @@ class QBXLayerPotentialSourceRefiner(object): r_max = cl.array.max(tq_dists).get() - from boxtree.area_query import AreaQueryElementwiseTemplate - unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - evt = knl( *unwrap_args( tree, peer_lists, @@ -683,9 +688,6 @@ class QBXLayerPotentialSourceRefiner(object): found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) found_panel_to_refine.finish() - from boxtree.area_query import AreaQueryElementwiseTemplate - unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - adjacency = self.get_adjacency_on_device(queue, lpot_source) evt = knl( @@ -886,30 +888,6 @@ class QBXLayerPotentialSourceRefiner(object): return new_lpot_source, conn - def plot_discr(self, lpot_source): - with cl.CommandQueue(self.context) as queue: - tree = self.tree_builder(queue, lpot_source).get(queue=queue) - from boxtree.visualization import TreePlotter - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot as plt - tp = TreePlotter(tree) - tp.draw_tree() - sources = (tree.sources[0], tree.sources[1]) - sti = tree.sorted_target_ids - plt.plot(sources[0][sti[tree.qbx_user_source_slice]], - sources[1][sti[tree.qbx_user_source_slice]], - lw=0, marker=".", markersize=1, label="sources") - plt.plot(sources[0][sti[tree.qbx_user_center_slice]], - sources[1][sti[tree.qbx_user_center_slice]], - lw=0, marker=".", markersize=1, label="centers") - plt.plot(sources[0][sti[tree.qbx_user_target_slice]], - sources[1][sti[tree.qbx_user_target_slice]], - lw=0, marker=".", markersize=1, label="targets") - plt.axis("equal") - plt.legend() - plt.savefig("discr.pdf") - def __call__(self, lpot_source, discr_factory, helmholtz_k=None, # FIXME: Set debug=False once everything works. refine_flags=None, debug=True, maxiter=50): diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 36bea2f8..95b04706 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -35,7 +35,11 @@ import pyopencl.array # noqa from boxtree.tools import DeviceDataRecord from boxtree.area_query import AreaQueryElementwiseTemplate from boxtree.tools import InlineBinarySearch -from pytential.qbx.utils import QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS +from pytential.qbx.utils import ( + QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS, DiscrPlotterMixin) + + +unwrap_args = AreaQueryElementwiseTemplate.unwrap_args import logging @@ -44,7 +48,7 @@ logger = logging.getLogger(__name__) # # TODO: # - Remove spurious arguments -# - Remove max_levels garbage +# - Documentation # #================== # HOW DOES TARGET ASSOCIATION WORK? @@ -52,8 +56,8 @@ logger = logging.getLogger(__name__) # The goal of the target association is to: # a) decide which targets require QBX, and # b) decide which centers to use for targets that require QBX, and -# c) if no good centers are available for a target, decide which panels to -# refine. +# c) if no good centers are available for a target that requires QBX, +# flag the appropriate panels for refinement. # # The flow chart of what happens to target t is shown below. Pass names are in # parentheses: @@ -113,8 +117,8 @@ class TargetFlag(object): INTERIOR_OR_EXTERIOR_VOLUME_TARGET = 0 INTERIOR_SURFACE_TARGET = -1 EXTERIOR_SURFACE_TARGET = +1 - INTERIOR_VOLUME_TARGET = -2 - EXTERIOR_VOLUME_TARGET = +2 + INTERIOR_VOLUME_TARGET = -2 + EXTERIOR_VOLUME_TARGET = +2 QBX_TARGET_MARKER = AreaQueryElementwiseTemplate( @@ -122,11 +126,8 @@ QBX_TARGET_MARKER = AreaQueryElementwiseTemplate( /* input */ particle_id_t *box_to_source_starts, particle_id_t *box_to_source_lists, - particle_id_t *panel_to_source_starts, particle_id_t source_offset, particle_id_t target_offset, - int npanels, - int nboxes, particle_id_t *sorted_target_ids, coord_t *panel_sizes, coord_t *box_to_search_dist, @@ -144,7 +145,7 @@ QBX_TARGET_MARKER = AreaQueryElementwiseTemplate( coord_vec_t tgt_coords; ${load_particle("INDEX_FOR_TARGET_PARTICLE(i)", "tgt_coords")} { - ${find_guiding_box("tgt_coords", 0, "my_box", particle="i")} + ${find_guiding_box("tgt_coords", 0, "my_box")} ${load_center("ball_center", "my_box", declare=False)} ${ball_radius} = box_to_search_dist[my_box]; } @@ -304,14 +305,15 @@ QBX_FAILED_TARGET_ASSOCIATION_REFINER = AreaQueryElementwiseTemplate( # {{{ target associator -class TargetAssociationFailedException(Exception): +class QBXTargetAssociationFailedException(Exception): """ .. attribute:: refine_flags """ - pass + def __init__(self, refine_flags): + self.refine_flags = refine_flags -class TargetAssociation(DeviceDataRecord): +class QBXTargetAssociation(DeviceDataRecord): """ .. attribute:: target_to_source .. attribute:: target_to_side @@ -319,7 +321,7 @@ class TargetAssociation(DeviceDataRecord): pass -class QBXTargetAssociator(object): +class QBXTargetAssociator(DiscrPlotterMixin): def __init__(self, cl_context): from pytential.qbx.utils import TreeWithQBXMetadataBuilder @@ -329,31 +331,6 @@ class QBXTargetAssociator(object): self.peer_list_finder = PeerListFinder(cl_context) self.space_invader_query = SpaceInvaderQueryBuilder(cl_context) - def plot_tree(self, queue, tree): - # FIXME: Move to utils... - tree = tree.get(queue=queue) - from boxtree.visualization import TreePlotter - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot as plt - tp = TreePlotter(tree) - tp.draw_tree() - sources = (tree.sources[0], tree.sources[1]) - sti = tree.sorted_target_ids - plt.plot(sources[0][sti[tree.qbx_user_source_slice]], - sources[1][sti[tree.qbx_user_source_slice]], - lw=0, marker=".", markersize=1, label="sources") - plt.plot(sources[0][sti[tree.qbx_user_center_slice]], - sources[1][sti[tree.qbx_user_center_slice]], - lw=0, marker=".", markersize=1, label="centers") - print(tree.qbx_user_target_slice) - plt.plot(sources[0][sti[tree.qbx_user_target_slice]], - sources[1][sti[tree.qbx_user_target_slice]], - lw=0, marker=".", markersize=1, label="targets") - plt.axis("equal") - plt.legend() - plt.savefig("discr.pdf") - # {{{ kernel generation @memoize_method @@ -418,9 +395,6 @@ class QBXTargetAssociator(object): tree.particle_id_dtype, max_levels) - from boxtree.area_query import AreaQueryElementwiseTemplate - unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - found_target_close_to_panel = cl.array.zeros(queue, 1, np.int32) found_target_close_to_panel.finish() @@ -445,11 +419,8 @@ class QBXTargetAssociator(object): tree, peer_lists, tree.box_to_qbx_source_starts, tree.box_to_qbx_source_lists, - tree.qbx_panel_to_source_starts, tree.qbx_user_source_slice.start, tree.qbx_user_target_slice.start, - tree.nqbxpanels, - tree.nboxes, tree.sorted_target_ids, panel_sizes, box_to_search_dist, @@ -487,15 +458,13 @@ class QBXTargetAssociator(object): tree.particle_id_dtype, max_levels) - from boxtree.area_query import AreaQueryElementwiseTemplate - unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - if debug: target_status.finish() marked_target_count = int(cl.array.sum(target_status).get()) # Perform a space invader query over the centers. - center_slice = tree.user_source_ids[tree.qbx_user_center_slice] + center_slice = \ + tree.sorted_target_ids[tree.qbx_user_center_slice].with_queue(queue) centers = [axis.with_queue(queue)[center_slice] for axis in tree.sources] panel_sizes = lpot_source.panel_sizes("ncenters").with_queue(queue) @@ -503,7 +472,7 @@ class QBXTargetAssociator(object): queue, tree, centers, - panel_sizes / 2, + panel_sizes * ((1 + stick_out_factor) / 2), peer_lists, wait_for=wait_for) wait_for = [evt] @@ -543,6 +512,7 @@ class QBXTargetAssociator(object): if debug: target_status.finish() + # Associated target = 2, marked target = 1 ntargets_associated = ( int(cl.array.sum(target_status).get()) - marked_target_count) assert ntargets_associated >= 0 @@ -567,9 +537,6 @@ class QBXTargetAssociator(object): tree.particle_id_dtype, max_levels) - from boxtree.area_query import AreaQueryElementwiseTemplate - unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) found_panel_to_refine.finish() @@ -622,21 +589,23 @@ class QBXTargetAssociator(object): return (found_panel_to_refine.get() == 1).all() - def make_target_flags(self, queue, target_discrs): - ntargets = sum(discr.nnodes for discr, _ in target_discrs) + def make_target_flags(self, queue, target_discrs_and_qbx_sides): + ntargets = sum(discr.nnodes for discr, _ in target_discrs_and_qbx_sides) target_flags = cl.array.empty(queue, ntargets, dtype=np.int32) offset = 0 - for discr, flags, in target_discrs: + + for discr, flags in target_discrs_and_qbx_sides: if np.isscalar(flags): target_flags[offset:offset + discr.nnodes].fill(flags) else: assert len(flags) == discr.nnodes target_flags[offset:offset + discr.nnodes] = flags offset += discr.nnodes + target_flags.finish() return target_flags - def make_target_association(self, queue, ntargets): + def make_default_target_association(self, queue, ntargets): target_to_source = cl.array.empty(queue, ntargets, dtype=np.int32) target_to_center_side = cl.array.empty_like(target_to_source) @@ -646,25 +615,62 @@ class QBXTargetAssociator(object): target_to_source.finish() target_to_center_side.finish() - return TargetAssociation( + return QBXTargetAssociation( target_to_source=target_to_source, target_to_center_side=target_to_center_side) - def __call__(self, lpot_source, target_discrs, stick_out_factor=1e-10, - debug=True, wait_for=None): + def __call__(self, lpot_source, target_discrs_and_qbx_sides, + stick_out_factor=1e-10, debug=True, wait_for=None): """ Entry point for calling the target associator. - :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource`. + :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource` + + :arg target_discrs_and_qbx_sides: + + a list of tuples ``(discr, sides)``, where + *discr* is a + :class:`pytential.discretization.Discretization` + or a + :class:`pytential.discretization.target.TargetBase` instance, and + *sides* is either a :class:`int` or + an array of (:class:`numpy.int8`) side requests for each + target. + + The side request can take the following values for each target: + + ===== ============================================== + Value Meaning + ===== ============================================== + 0 Volume target. If near a QBX center, + the value from the QBX expansion is returned, + otherwise the volume potential is returned. + + -1 Surface target. Return interior limit from + interior-side QBX expansion. + + +1 Surface target. Return exterior limit from + exterior-side QBX expansion. + + -2 Volume target. If within an *interior* QBX disk, + the value from the QBX expansion is returned, + otherwise the volume potential is returned. + + +2 Volume target. If within an *exterior* QBX disk, + the value from the QBX expansion is returned, + otherwise the volume potential is returned. + ===== ============================================== - :arg target_discrs: + :raises QBXTargetAssociationFailedException: + when target association failed to find a center for a target. + The returned exception object contains suggested refine flags. :returns: """ with cl.CommandQueue(self.cl_context) as queue: tree = self.tree_builder(queue, lpot_source, - [discr for discr, _ in target_discrs]) + [discr for discr, _ in target_discrs_and_qbx_sides]) peer_lists, evt = self.peer_list_finder(queue, tree, wait_for) wait_for = [evt] @@ -675,26 +681,28 @@ class QBXTargetAssociator(object): lpot_source, target_status, debug) - target_assoc = self.make_target_association(queue, tree.nqbxtargets) + target_assoc = self.make_default_target_association( + queue, tree.nqbxtargets) if not have_close_targets: return target_assoc.with_queue(None) - target_flags = self.make_target_flags(queue, target_discrs) + target_flags = self.make_target_flags(queue, target_discrs_and_qbx_sides) self.try_find_centers(queue, tree, peer_lists, lpot_source, target_status, target_flags, target_assoc, stick_out_factor, debug) - center_not_found = (target_status == TargetStatus.MARKED_QBX_CENTER_PENDING) + center_not_found = ( + target_status == TargetStatus.MARKED_QBX_CENTER_PENDING) if center_not_found.any().get(): surface_target = ( (target_flags == TargetFlag.INTERIOR_SURFACE_TARGET) - or (target_flags == TargetFlag.EXTERIOR_SURFACE_TARGET)) + | (target_flags == TargetFlag.EXTERIOR_SURFACE_TARGET)) - if (center_not_found and surface_target).any().get(): - logger.warning("WARNING: An on-surface target was not " + if (center_not_found & surface_target).any().get(): + logger.warning("An on-surface target was not " "assigned a center. As a remedy you can try increasing " "the \"stick_out_factor\" parameter, but this could " "also cause an invalid center assignment.") @@ -705,7 +713,7 @@ class QBXTargetAssociator(object): lpot_source, target_status, refine_flags, debug) assert have_panel_to_refine - raise TargetAssociationFailedException( + raise QBXTargetAssociationFailedException( refine_flags.with_queue(None)) return target_assoc.with_queue(None) diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index 94fc7ba2..bbaf2171 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -56,21 +56,12 @@ QBX_TREE_C_PREAMBLE = r"""//CL:mako// typedef ${dtype_to_ctype(vec_types_dict[coord_dtype, dimensions])} coord_vec_t; """ - QBX_TREE_MAKO_DEFS = r"""//CL:mako// <%def name="load_particle(particle, coords)"> - <% zerovect = ["0"] * 2 ** (dimensions - 1).bit_length() %> - /* Zero initialize, to allow for use in distance computations. */ - ${coords} = (coord_vec_t) (${", ".join(zerovect)}); - %for ax in AXIS_NAMES[:dimensions]: ${coords}.${ax} = particles_${ax}[${particle}]; %endfor - -<%def name="find_leaf_for_particle(particle, box_to_particle)"> - box_id_t box = bsearch_boxes(${box_to_particle}, nboxes + 1, ${particle}); - """ # }}} @@ -100,6 +91,38 @@ def get_interleaver_kernel(dtype): # }}} +# {{{ discr plotter mixin + +class DiscrPlotterMixin(object): + + def plot_discr(self, lpot_source, outfilename="discr.pdf"): + with cl.CommandQueue(self.cl_context) as queue: + tree = self.tree_builder(queue, lpot_source).get(queue=queue) + from boxtree.visualization import TreePlotter + + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + tp = TreePlotter(tree) + tp.draw_tree() + sources = (tree.sources[0], tree.sources[1]) + sti = tree.sorted_target_ids + plt.plot(sources[0][sti[tree.qbx_user_source_slice]], + sources[1][sti[tree.qbx_user_source_slice]], + lw=0, marker=".", markersize=1, label="sources") + plt.plot(sources[0][sti[tree.qbx_user_center_slice]], + sources[1][sti[tree.qbx_user_center_slice]], + lw=0, marker=".", markersize=1, label="centers") + plt.plot(sources[0][sti[tree.qbx_user_target_slice]], + sources[1][sti[tree.qbx_user_target_slice]], + lw=0, marker=".", markersize=1, label="targets") + plt.axis("equal") + plt.legend() + plt.savefig(outfilename) + +# }}} + + # {{{ tree creation class TreeWithQBXMetadataBuilder(object): diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index f81a08a9..86caa82e 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -1,3 +1,27 @@ +from __future__ import division, absolute_import, print_function + +__copyright__ = """Copyright (C) 2016 Matt Wala""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + import numpy as np import numpy.linalg as la diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 10ae9eb7..2340bd50 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -1,6 +1,9 @@ from __future__ import division, absolute_import, print_function -__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" +__copyright__ = """ +Copyright (C) 2013 Andreas Kloeckner +Copyright (C) 2016 Matt Wala +""" __license__ = """ Permission is hereby granted, free of charge, to any person obtaining a copy @@ -86,13 +89,13 @@ def iter_elements(discr): ("20-to-1 ellipse", partial(ellipse, 20), 100), ("horseshoe", horseshoe, 64), ]) -def test_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): +def test_source_refinement(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) # {{{ generate lpot source, run refiner - order = 5 + order = 16 helmholtz_k = 10 mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) @@ -175,15 +178,20 @@ def test_lpot_source_refinement(ctx_getter, curve_name, curve_f, nelements): # }}} -def test_ellipse_target_association(ctx_getter): +@pytest.mark.parametrize(("curve_name", "curve_f", "nelements"), [ + ("20-to-1 ellipse", partial(ellipse, 20), 100), + ("horseshoe", horseshoe, 64), + ]) +def test_target_association(ctx_getter, curve_name, curve_f, nelements): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) + # {{{ generate lpot source + order = 16 # Make the curve mesh. - nelements = 100 - mesh = make_curve_mesh(partial(ellipse, 3), np.linspace(0, 1, nelements+1), order) + mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -201,34 +209,36 @@ def test_ellipse_target_association(ctx_getter): lpot_source, conn = refiner(lpot_source, factory) - discr_nodes = lpot_source.density_discr.nodes().get(queue) int_centers = lpot_source.centers(-1) ext_centers = lpot_source.centers(+1) + # }}} + + # {{{ generate targets + RNG_SEED = 10 + from pyopencl.clrandom import PhiloxGenerator rng = PhiloxGenerator(cl_ctx, seed=RNG_SEED) nsources = lpot_source.density_discr.nnodes noise = rng.uniform(queue, nsources, dtype=np.float, a=0.01, b=1.0) + panel_sizes = lpot_source.panel_sizes("nsources").with_queue(queue) - def close_targets(sign): + def targets_from_sources(sign, dist): from pytential import sym, bind nodes = bind(lpot_source.density_discr, sym.Nodes())(queue) normals = bind(lpot_source.density_discr, sym.normal())(queue) - panel_sizes = lpot_source.panel_sizes().with_queue(queue) - return (nodes + normals * sign * noise * panel_sizes / 2).as_vector(np.object) + return (nodes + normals * sign * dist).as_vector(np.object) from pytential.target import PointsTarget - int_targets = PointsTarget(close_targets(-1)) - ext_targets = PointsTarget(close_targets(+1)) - NFARTARGETS = 10 - far_targets = PointsTarget( - (rng.normal(queue, NFARTARGETS, np.float, sigma=5e-3), - rng.normal(queue, NFARTARGETS, np.float, sigma=5e-3))) + int_targets = PointsTarget(targets_from_sources(-1, noise * panel_sizes / 2)) + ext_targets = PointsTarget(targets_from_sources(+1, noise * panel_sizes / 2)) + FAR_TARGET_DIST = 10 + far_targets = PointsTarget(targets_from_sources(+1, FAR_TARGET_DIST)) # Create target discretizations. - target_discrs = [ + target_discrs = ( # On-surface targets, interior (lpot_source.density_discr, -1), # On-surface targets, exterior @@ -237,17 +247,27 @@ def test_ellipse_target_association(ctx_getter): (int_targets, -2), # Exterior close targets (ext_targets, +2), - ## Far targets, should not need centers + # Far targets, should not need centers (far_targets, 0), - ] + ) sizes = np.cumsum([discr.nnodes for discr, _ in target_discrs]) - from itertools import chain - slices = [slice(start, end) for start, end in zip(chain([0], sizes), sizes)] + + (surf_int_slice, + surf_ext_slice, + vol_int_slice, + vol_ext_slice, + far_slice, + ) = [slice(start, end) for start, end in zip(np.r_[0, sizes], sizes)] + + # }}} + + # {{{ run target associator and check from pytential.qbx.target_assoc import QBXTargetAssociator - target_assoc = QBXTargetAssociator(cl_ctx)(lpot_source, target_discrs) - target_assoc = target_assoc.get(queue=queue) + target_assoc = ( + QBXTargetAssociator(cl_ctx)(lpot_source, target_discrs) + .get(queue=queue)) panel_sizes = lpot_source.panel_sizes("nsources").get(queue) @@ -271,33 +291,85 @@ def test_ellipse_target_association(ctx_getter): dists = la.norm((targets.T - centers.T[target_to_source_result]), axis=1) assert (dists <= panel_sizes[target_to_source_result] / 2).all() + # Checks that far targets are not assigned a center. def check_far_targets(target_to_source_result, target_to_side_result): assert (target_to_source_result == -1).all() assert (target_to_side_result == 0).all() check_on_surface_targets( nsources, -1, - target_assoc.target_to_source[slices[0]], - target_assoc.target_to_center_side[slices[0]]) + target_assoc.target_to_source[surf_int_slice], + target_assoc.target_to_center_side[surf_int_slice]) check_on_surface_targets( nsources, +1, - target_assoc.target_to_source[slices[1]], - target_assoc.target_to_center_side[slices[1]]) + target_assoc.target_to_source[surf_ext_slice], + target_assoc.target_to_center_side[surf_ext_slice]) check_close_targets( int_centers, int_targets, -1, - target_assoc.target_to_source[slices[2]], - target_assoc.target_to_center_side[slices[2]]) + target_assoc.target_to_source[vol_int_slice], + target_assoc.target_to_center_side[vol_int_slice]) check_close_targets( ext_centers, ext_targets, +1, - target_assoc.target_to_source[slices[3]], - target_assoc.target_to_center_side[slices[3]]) + target_assoc.target_to_source[vol_ext_slice], + target_assoc.target_to_center_side[vol_ext_slice]) check_far_targets( - target_assoc.target_to_source[slices[4]], - target_assoc.target_to_center_side[slices[4]]) + target_assoc.target_to_source[far_slice], + target_assoc.target_to_center_side[far_slice]) + + # }}} + + +def test_target_association_failure(ctx_getter): + cl_ctx = ctx_getter() + queue = cl.CommandQueue(cl_ctx) + + # {{{ generate circle + + order = 5 + nelements = 40 + + # Make the curve mesh. + curve_f = partial(ellipse, 1) + mesh = make_curve_mesh(curve_f, np.linspace(0, 1, nelements+1), order) + + from meshmode.discretization import Discretization + from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + factory = InterpolatoryQuadratureSimplexGroupFactory(order) + + discr = Discretization(cl_ctx, mesh, factory) + + from pytential.qbx.refinement import NewQBXLayerPotentialSource + + lpot_source = NewQBXLayerPotentialSource(discr, order) + del discr + + # }}} + + # {{{ generate targets + + close_circle = 0.999 * np.exp( + 2j * np.pi * np.linspace(0, 1, 500, endpoint=False)) + from pytential.target import PointsTarget + close_circle_target = ( + PointsTarget(cl.array.to_device( + queue, np.array([close_circle.real, close_circle.imag])))) + + targets = ( + (close_circle_target, 0), + ) + + from pytential.qbx.target_assoc import ( + QBXTargetAssociator, QBXTargetAssociationFailedException) + + with pytest.raises(QBXTargetAssociationFailedException): + QBXTargetAssociator(cl_ctx)(lpot_source, targets) + + # }}} # You can test individual routines by typing -- GitLab From 45a1708503a3a8bdffe88ac0a6b4c650a35503f5 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sun, 6 Nov 2016 16:12:03 -0600 Subject: [PATCH 16/62] Target association: don't pull data onto host when we don't have to. --- pytential/qbx/target_assoc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 95b04706..24a287ab 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -442,7 +442,7 @@ class QBXTargetAssociator(DiscrPlotterMixin): logger.info("target association: done marking targets close to panels") - return (found_target_close_to_panel.get() == 1).all() + return (found_target_close_to_panel == 1).all().get() def try_find_centers(self, queue, tree, peer_lists, lpot_source, target_status, target_flags, target_assoc, @@ -587,7 +587,7 @@ class QBXTargetAssociator(DiscrPlotterMixin): logger.info("target association: done marking panels for refinement") - return (found_panel_to_refine.get() == 1).all() + return (found_panel_to_refine == 1).all().get() def make_target_flags(self, queue, target_discrs_and_qbx_sides): ntargets = sum(discr.nnodes for discr, _ in target_discrs_and_qbx_sides) -- GitLab From f804a606a6cb58dcc6f4a22d786132da32ca1571 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 17 Nov 2016 14:29:28 -0600 Subject: [PATCH 17/62] Target associator: Remove unused values in kernels. --- pytential/qbx/target_assoc.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index 24a287ab..d32b391d 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -47,7 +47,6 @@ logger = logging.getLogger(__name__) # # TODO: -# - Remove spurious arguments # - Documentation # #================== @@ -167,7 +166,7 @@ QBX_TARGET_MARKER = AreaQueryElementwiseTemplate( } """, name="mark_targets", - preamble=TARGET_ASSOC_DEFINES + str(InlineBinarySearch("particle_id_t"))) + preamble=TARGET_ASSOC_DEFINES) QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( @@ -175,12 +174,8 @@ QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( /* input */ particle_id_t *box_to_center_starts, particle_id_t *box_to_center_lists, - particle_id_t *panel_to_source_starts, - particle_id_t source_offset, particle_id_t center_offset, particle_id_t target_offset, - int npanels, - int nboxes, particle_id_t *sorted_target_ids, coord_t *panel_sizes, coord_t *box_to_search_dist, @@ -239,7 +234,7 @@ QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( } """, name="find_centers", - preamble=TARGET_ASSOC_DEFINES + str(InlineBinarySearch("particle_id_t"))) + preamble=TARGET_ASSOC_DEFINES) QBX_FAILED_TARGET_ASSOCIATION_REFINER = AreaQueryElementwiseTemplate( @@ -490,12 +485,8 @@ class QBXTargetAssociator(DiscrPlotterMixin): tree, peer_lists, tree.box_to_qbx_center_starts, tree.box_to_qbx_center_lists, - tree.qbx_panel_to_source_starts, - tree.qbx_user_source_slice.start, tree.qbx_user_center_slice.start, tree.qbx_user_target_slice.start, - tree.nqbxpanels, - tree.nboxes, tree.sorted_target_ids, panel_sizes, box_to_search_dist, -- GitLab From 2f2d9b563eb58dd2cb9f90ab1a1397e6e72da1e6 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 17 Nov 2016 14:56:29 -0600 Subject: [PATCH 18/62] 2-to-1 panel ratio refiner: Use if block syntax. --- pytential/qbx/refinement.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 4b83c2e4..ec46a7bb 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -576,9 +576,10 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): (panel_sizes[panel] > panel_sizes[neighbor] and refine_flags_prev[neighbor] == 1))) - refine_flags[panel] = 1 {if=oversize} - refine_flags_updated = 1 { - id=write_refine_flags_updated,if=oversize} + if oversize + refine_flags[panel] = 1 + refine_flags_updated = 1 {id=write_refine_flags_updated} + end end end """, [ -- GitLab From 65aee8aadaad305de4fcc2cbc715ba33d1b61182 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 26 Nov 2016 20:33:54 -0600 Subject: [PATCH 19/62] Rename enums. --- pytential/qbx/target_assoc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index d32b391d..bceb2067 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -104,14 +104,14 @@ enum TargetFlag """ -class TargetStatus(object): +class target_status_enum(object): # NOTE: Must match "enum TargetStatus" above UNMARKED = 0 MARKED_QBX_CENTER_PENDING = 1 MARKED_QBX_CENTER_FOUND = 2 -class TargetFlag(object): +class target_flag_enum(object): # NOTE: Must match "enum TargetFlag" above INTERIOR_OR_EXTERIOR_VOLUME_TARGET = 0 INTERIOR_SURFACE_TARGET = -1 @@ -685,12 +685,12 @@ class QBXTargetAssociator(DiscrPlotterMixin): stick_out_factor, debug) center_not_found = ( - target_status == TargetStatus.MARKED_QBX_CENTER_PENDING) + target_status == target_status_enum.MARKED_QBX_CENTER_PENDING) if center_not_found.any().get(): surface_target = ( - (target_flags == TargetFlag.INTERIOR_SURFACE_TARGET) - | (target_flags == TargetFlag.EXTERIOR_SURFACE_TARGET)) + (target_flags == target_flag_enum.INTERIOR_SURFACE_TARGET) + | (target_flags == target_flag_enum.EXTERIOR_SURFACE_TARGET)) if (center_not_found & surface_target).any().get(): logger.warning("An on-surface target was not " -- GitLab From 72fe042e3ecfe2d65278e69c665ebb8fac637a13 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 26 Nov 2016 21:09:12 -0600 Subject: [PATCH 20/62] [WIP] FMM sort of works. --- pytential/qbx/fmm.py | 14 +- pytential/qbx/geometry.py | 224 ++++---------------------------- pytential/qbx/refinement.py | 233 ++++++++++++++++++++++++++++++++-- pytential/qbx/target_assoc.py | 27 ++-- pytential/qbx/utils.py | 51 ++++---- test/test_layer_pot.py | 23 +++- 6 files changed, 307 insertions(+), 265 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index 539d3b6e..ce605589 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -160,7 +160,7 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), def reorder_sources(self, source_array): return (source_array .with_queue(self.queue) - [self.tree.user_point_source_ids] + [self.tree.user_source_ids] .with_queue(None)) def reorder_potentials(self, potentials): @@ -177,10 +177,10 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), def box_source_list_kwargs(self): return dict( - box_source_starts=self.tree.box_point_source_starts, + box_source_starts=self.tree.box_source_starts, box_source_counts_nonchild=( - self.tree.box_point_source_counts_nonchild), - sources=self.tree.point_sources) + self.tree.box_source_counts_nonchild), + sources=self.tree.sources) def box_target_list_kwargs(self): # This only covers the non-QBX targets. @@ -574,7 +574,7 @@ def write_performance_model(outf, geo_data): start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_point_source_counts_nonchild[src_ibox] + nsources = tree.box_source_counts_nonchild[src_ibox] npart_direct += ntargets * nsources @@ -620,7 +620,7 @@ def write_performance_model(outf, geo_data): start, end = traversal.sep_bigger_starts[itgt_box:itgt_box+2] for src_ibox in traversal.sep_bigger_lists[start:end]: - nsources = tree.box_point_source_counts_nonchild[src_ibox] + nsources = tree.box_source_counts_nonchild[src_ibox] nform_local += nsources @@ -667,7 +667,7 @@ def write_performance_model(outf, geo_data): start, end = traversal.neighbor_source_boxes_starts[itgt_box:itgt_box+2] for src_ibox in traversal.neighbor_source_boxes_lists[start:end]: - nsources = tree.box_point_source_counts_nonchild[src_ibox] + nsources = tree.box_source_counts_nonchild[src_ibox] nqbxl_direct += nsources diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 08a363ae..2294caef 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -754,75 +754,17 @@ class QBXFMMGeometryData(object): @memoize_method def center_info(self): - """Return a :class:`CenterInfo`. |cached| - + """ Return a :class:`CenterInfo`. |cached| """ - self_discr = self.lpot_source.density_discr - ncenters = 0 - for el_group in self_discr.groups: - kept_indices = self.kept_center_indices(el_group) - # two: one for positive side, one for negative side - ncenters += 2 * len(kept_indices) * el_group.nelements + lpot_source = self.lpot_source - from pytential import sym, bind - from pytools.obj_array import make_obj_array with cl.CommandQueue(self.cl_context) as queue: - radii_sym = sym.cse(2*sym.area_element(), "radii") - all_radii, all_pos_centers, all_neg_centers = bind(self_discr, - make_obj_array([ - radii_sym, - sym.Nodes() + radii_sym*sym.normal(), - sym.Nodes() - radii_sym*sym.normal() - ]))(queue) - - # The centers are returned from the above as multivectors. - all_pos_centers = all_pos_centers.as_vector(np.object) - all_neg_centers = all_neg_centers.as_vector(np.object) - - # -1 for inside, +1 for outside - sides = cl.array.empty( - self.cl_context, ncenters, np.int8) - radii = cl.array.empty( - self.cl_context, ncenters, self.coord_dtype) - centers = make_obj_array([ - cl.array.empty(self.cl_context, ncenters, - self.coord_dtype) - for i in range(self_discr.ambient_dim)]) - - ibase = 0 - for el_group in self_discr.groups: - kept_center_indices = self.kept_center_indices(el_group) - group_len = len(kept_indices) * el_group.nelements - - for side, all_centers in [ - (+1, all_pos_centers), - (-1, all_neg_centers), - ]: - - sides[ibase:ibase + group_len].fill(side, queue=queue) - - radii_view = radii[ibase:ibase + group_len] \ - .reshape(el_group.nelements, len(kept_indices)) - centers_view = make_obj_array([ - centers_i[ibase:ibase + group_len] - .reshape((el_group.nelements, len(kept_indices))) - for centers_i in centers - ]) - all_centers_view = make_obj_array([ - el_group.view(pos_centers_i) - for pos_centers_i in all_centers - ]) - self.code_getter.pick_expansion_centers(queue, - centers=centers_view, - all_centers=all_centers_view, - radii=radii_view, - all_radii=el_group.view(all_radii), - kept_center_indices=kept_center_indices) - - ibase += group_len - - assert ibase == ncenters + from pytential.qbx.utils import get_interleaved_centers + centers = get_interleaved_centers(queue, lpot_source) + sides = cl.array.arange(queue, len(centers[0]), dtype=np.int32) + sides = 2 * (sides & 1) - 1 + radii = lpot_source.panel_sizes("ncenters").with_queue(queue) / 2 return CenterInfo( sides=sides, @@ -838,7 +780,6 @@ class QBXFMMGeometryData(object): """Return a :class:`TargetInfo`. |cached|""" code_getter = self.code_getter - lpot_src = self.lpot_source with cl.CommandQueue(self.cl_context) as queue: @@ -925,74 +866,19 @@ class QBXFMMGeometryData(object): """ code_getter = self.code_getter - lpot_src = self.lpot_source + target_info = self.target_info() with cl.CommandQueue(self.cl_context) as queue: - nelements = sum(grp.nelements - for grp in lpot_src.fine_density_discr.groups) - - el_centers = cl.array.empty( - self.cl_context, (lpot_src.ambient_dim, nelements), - self.coord_dtype) - el_radii = cl.array.empty(self.cl_context, nelements, self.coord_dtype) - # {{{ find sources and radii (=element 'centroids') - - # FIXME: Should probably use quad weights to find 'centroids' to deal - # with non-symmetric elements. - - i_el_base = 0 - for grp in lpot_src.fine_density_discr.groups: - el_centers_view = el_centers[:, i_el_base:i_el_base+grp.nelements] - el_radii_view = el_radii[i_el_base:i_el_base+grp.nelements] - nodes_view = grp.view(lpot_src.fine_density_discr.nodes()) - - code_getter.find_element_centers( - queue, el_centers=el_centers_view, nodes=nodes_view) - code_getter.find_element_radii( - queue, el_centers=el_centers_view, nodes=nodes_view, - el_radii=el_radii_view) - - i_el_base += grp.nelements - - # }}} - - target_info = self.target_info() + # TODO: build refine weights. tree, _ = code_getter.build_tree(queue, - particles=el_centers, source_radii=el_radii, - max_particles_in_box=30, + particles=lpot_src.fine_density_discr.nodes(), targets=target_info.targets, - target_radii=self.target_radii(), - debug=self.debug) - - # {{{ link point sources - - point_source_starts = cl.array.empty(self.cl_context, - nelements+1, tree.particle_id_dtype) - - i_el_base = 0 - for grp in lpot_src.fine_density_discr.groups: - point_source_starts.setitem( - slice(i_el_base, i_el_base+grp.nelements), - cl.array.arange(queue, - grp.node_nr_base, grp.node_nr_base + grp.nnodes, - grp.nunit_nodes, - dtype=point_source_starts.dtype), - queue=queue) - - i_el_base += grp.nelements - - point_source_starts.setitem( - -1, self.lpot_source.fine_density_discr.nnodes, queue=queue) - - from boxtree.tree import link_point_sources - tree = link_point_sources(queue, tree, - point_source_starts, - self.lpot_source.fine_density_discr.nodes()) - - # }}} + max_particles_in_box=30, + debug=self.debug, + kind="adaptive-level-restricted") return tree @@ -1010,7 +896,6 @@ class QBXFMMGeometryData(object): trav, _ = self.code_getter.build_traversal(queue, self.tree(), debug=self.debug) - trav = trav.merge_close_lists(queue) return trav def leaf_to_center_lookup(self): @@ -1087,45 +972,13 @@ class QBXFMMGeometryData(object): |cached| """ - tree = self.tree() - trav = self.traversal() center_info = self.center_info() - qbx_center_for_global_tester = \ - self.code_getter.qbx_center_for_global_tester( - # coord_dtype: - tree.coord_dtype, - # box_id_dtype: - tree.box_id_dtype, - # particle_id_dtype: - tree.particle_id_dtype) - with cl.CommandQueue(self.cl_context) as queue: result = cl.array.empty(queue, center_info.ncenters, np.int8) + result.fill(1) - logger.info("find global qbx flags: start") - - qbx_center_for_global_tester(*( - tuple(center_info.centers) - + ( - center_info.radii, - self.qbx_center_to_target_box(), - - trav.neighbor_source_boxes_starts, - trav.neighbor_source_boxes_lists, - tree.box_point_source_starts, - tree.box_point_source_counts_cumul, - ) + tuple(tree.point_sources) + ( - result, - )), - **dict( - queue=queue, - range=slice(center_info.ncenters) - )) - - logger.info("find global qbx flags: done") - - return result.with_queue(None) + return result.with_queue(None) @memoize_method def global_qbx_centers(self): @@ -1163,52 +1016,25 @@ class QBXFMMGeometryData(object): Shape: ``[ntargets]`` of :attr:`boxtree.Tree.particle_id_dtype`, with extra values from :class:`target_state` allowed. Targets occur in user order. """ + from pytential.qbx.target_assoc import QBXTargetAssociator - ltc = self.leaf_to_center_lookup() + # FIXME: kernel ownership... + tgt_assoc = QBXTargetAssociator(self.cl_context) + + # FIXME: try block... + tgt_assoc_result = tgt_assoc(self.lpot_source, + self.target_discrs_and_qbx_sides) - tree = self.tree() tgt_info = self.target_info() center_info = self.center_info() + tree = self.tree() - assert ltc.balls_near_box_starts.dtype == ltc.balls_near_box_lists.dtype - assert ltc.balls_near_box_starts.dtype == tree.particle_id_dtype - - centers_for_target_finder = self.code_getter.centers_for_target_finder( - tree.coord_dtype, - tree.box_id_dtype, - tree.particle_id_dtype) - - logger.info("find center for each target: start") with cl.CommandQueue(self.cl_context) as queue: result = cl.array.empty(queue, tgt_info.ntargets, tree.particle_id_dtype) result[:center_info.ncenters].fill(target_state.NO_QBX_NEEDED) + result[center_info.ncenters:] = tgt_assoc_result.target_to_center - centers_for_target_finder( - *(( - tree.aligned_nboxes, - tree.box_child_ids, - tgt_info.ntargets, - tgt_info.targets, - self.target_side_preferences(), - tree.root_extent, - ) + tuple(tree.bounding_box[0]) + ( - ltc.balls_near_box_starts, - ltc.balls_near_box_lists, - ) + tuple(center_info.centers) + ( - center_info.radii, - center_info.sides, - self.global_qbx_flags(), - QBX_CENTER_MATCH_THRESHOLD**2, - - result - )), - **dict( - queue=queue, - range=slice(center_info.ncenters, tgt_info.ntargets))) - - logger.info("find center for each target: done") - - return result.with_queue(None) + return result @memoize_method def center_to_tree_targets(self): diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index ec46a7bb..d6f32293 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -47,11 +47,14 @@ Refinement """ +from pytential.qbx import LayerPotentialSource, get_multipole_expansion_class, get_local_expansion_class + + # {{{ layer potential source # FIXME: Move to own file, replace existing QBXLayerPotentialSource when # finished. -class NewQBXLayerPotentialSource(object): +class NewQBXLayerPotentialSource(LayerPotentialSource): """A source discretization for a QBX layer potential. .. attribute :: density_discr @@ -66,7 +69,7 @@ class NewQBXLayerPotentialSource(object): """ def __init__(self, density_discr, fine_order, qbx_order=None, fmm_order=None, - qbx_level_to_order=None, fmm_level_to_order=None, + fmm_level_to_order=None, # FIXME set debug=False once everything works real_dtype=np.float64, debug=True, performance_data_file=None): @@ -89,8 +92,6 @@ class NewQBXLayerPotentialSource(object): if fmm_order is None and qbx_order is not None: fmm_order = qbx_order + 1 - if qbx_order is not None and qbx_level_to_order is not None: - raise TypeError("may not specify both qbx_order an qbx_level_to_order") if fmm_order is not None and fmm_level_to_order is not None: raise TypeError("may not specify both fmm_order an fmm_level_to_order") @@ -101,12 +102,8 @@ class NewQBXLayerPotentialSource(object): def fmm_level_to_order(level): return fmm_order - if qbx_level_to_order is None: - def qbx_level_to_order(level): - return qbx_order - self.fine_order = fine_order - self.qbx_level_to_order = qbx_level_to_order + self.qbx_order = qbx_order self.density_discr = density_discr self.fmm_level_to_order = fmm_level_to_order self.debug = debug @@ -254,6 +251,222 @@ class NewQBXLayerPotentialSource(object): return (area_element.with_queue(queue)*qweight).with_queue(None) + def preprocess_optemplate(self, name, discretizations, expr): + """ + :arg name: The symbolic name for *self*, which the preprocessor + should use to find which expressions it is allowed to modify. + """ + from pytential.symbolic.mappers import QBXPreprocessor + return QBXPreprocessor(name, discretizations)(expr) + + def op_group_features(self, expr): + from pytential.symbolic.primitives import IntGdSource + assert not isinstance(expr, IntGdSource) + + from sumpy.kernel import AxisTargetDerivativeRemover + result = ( + expr.source, expr.density, + AxisTargetDerivativeRemover()(expr.kernel), + ) + + return result + + def exec_layer_potential_insn(self, queue, insn, bound_expr, evaluate): + from pytools.obj_array import with_object_array_or_scalar + from functools import partial + oversample = partial(self.resampler, queue) + + def evaluate_wrapper(expr): + value = evaluate(expr) + return with_object_array_or_scalar(oversample, value) + + if self.fmm_level_to_order is False: + func = self.exec_layer_potential_insn_direct + else: + func = self.exec_layer_potential_insn_fmm + + return func(queue, insn, bound_expr, evaluate_wrapper) + + @property + @memoize_method + def qbx_fmm_code_getter(self): + from pytential.qbx.geometry import QBXFMMGeometryCodeGetter + return QBXFMMGeometryCodeGetter(self.cl_context, self.ambient_dim, + debug=self.debug) + + @memoize_method + def qbx_fmm_geometry_data(self, target_discrs_and_qbx_sides): + """ + :arg target_discrs_and_qbx_sides: + a tuple of *(discr, qbx_forced_limit)* + tuples, where *discr* is a + :class:`meshmode.discretization.Discretization` + or + :class:`pytential.target.TargetBase` + instance + """ + from pytential.qbx.geometry import QBXFMMGeometryData + + return QBXFMMGeometryData(self.qbx_fmm_code_getter, + self, target_discrs_and_qbx_sides, debug=self.debug) + + @memoize_method + def expansion_wrangler_code_container(self, base_kernel, out_kernels): + mpole_expn_class = get_multipole_expansion_class(base_kernel) + local_expn_class = get_local_expansion_class(base_kernel) + + from functools import partial + fmm_mpole_factory = partial(mpole_expn_class, base_kernel) + fmm_local_factory = partial(local_expn_class, base_kernel) + qbx_local_factory = partial(local_expn_class, base_kernel) + + from pytential.qbx.fmm import \ + QBXExpansionWranglerCodeContainer + return QBXExpansionWranglerCodeContainer( + self.cl_context, + fmm_mpole_factory, fmm_local_factory, qbx_local_factory, + out_kernels) + + def exec_layer_potential_insn_fmm(self, queue, insn, bound_expr, evaluate): + # {{{ build list of unique target discretizations used + + # map (name, qbx_side) to number in list + tgt_name_and_side_to_number = {} + # list of tuples (discr, qbx_side) + target_discrs_and_qbx_sides = [] + + for o in insn.outputs: + key = (o.target_name, o.qbx_forced_limit) + if key not in tgt_name_and_side_to_number: + tgt_name_and_side_to_number[key] = \ + len(target_discrs_and_qbx_sides) + + target_discr = bound_expr.places[o.target_name] + if isinstance(target_discr, LayerPotentialSource): + target_discr = target_discr.density_discr + + qbx_forced_limit = o.qbx_forced_limit + if qbx_forced_limit is None: + qbx_forced_limit = 0 + + target_discrs_and_qbx_sides.append( + (target_discr, qbx_forced_limit)) + + target_discrs_and_qbx_sides = tuple(target_discrs_and_qbx_sides) + + # }}} + + geo_data = self.qbx_fmm_geometry_data(target_discrs_and_qbx_sides) + + # FIXME Exert more positive control over geo_data attribute lifetimes using + # geo_data..clear_cache(geo_data). + + # FIXME Synthesize "bad centers" around corners and edges that have + # inadequate QBX coverage. + + # FIXME don't compute *all* output kernels on all targets--respect that + # some target discretizations may only be asking for derivatives (e.g.) + + strengths = (evaluate(insn.density).with_queue(queue) + * self.weights_and_area_elements()) + + # {{{ get expansion wrangler + + base_kernel = None + out_kernels = [] + + from sumpy.kernel import AxisTargetDerivativeRemover + for knl in insn.kernels: + candidate_base_kernel = AxisTargetDerivativeRemover()(knl) + + if base_kernel is None: + base_kernel = candidate_base_kernel + else: + assert base_kernel == candidate_base_kernel + + out_kernels = tuple(knl for knl in insn.kernels) + + if base_kernel.is_complex_valued or strengths.dtype.kind == "c": + value_dtype = self.complex_dtype + else: + value_dtype = self.real_dtype + + # {{{ build extra_kwargs dictionaries + + # This contains things like the Helmholtz parameter k or + # the normal directions for double layers. + + def reorder_sources(source_array): + if isinstance(source_array, cl.array.Array): + return (source_array + .with_queue(queue) + [geo_data.tree().user_source_ids] + .with_queue(None)) + else: + return source_array + + kernel_extra_kwargs = {} + source_extra_kwargs = {} + + from sumpy.tools import gather_arguments, gather_source_arguments + from pytools.obj_array import with_object_array_or_scalar + for func, var_dict in [ + (gather_arguments, kernel_extra_kwargs), + (gather_source_arguments, source_extra_kwargs), + ]: + for arg in func(out_kernels): + var_dict[arg.name] = with_object_array_or_scalar( + reorder_sources, + evaluate(insn.kernel_arguments[arg.name])) + + # }}} + + wrangler = self.expansion_wrangler_code_container( + base_kernel, out_kernels).get_wrangler( + queue, geo_data, value_dtype, + self.qbx_order, + self.fmm_level_to_order, + source_extra_kwargs=source_extra_kwargs, + kernel_extra_kwargs=kernel_extra_kwargs) + + # }}} + + #geo_data.plot() + + if len(geo_data.global_qbx_centers()) != geo_data.center_info().ncenters: + raise NotImplementedError("geometry has centers requiring local QBX") + + from pytential.qbx.geometry import target_state + if (geo_data.user_target_to_center().with_queue(queue) + == target_state.FAILED).any().get(): + raise RuntimeError("geometry has failed targets") + + if self.performance_data_file is not None: + from pytential.qbx.fmm import write_performance_model + with open(self.performance_data_file, "w") as outf: + write_performance_model(outf, geo_data) + + # {{{ execute global QBX + + from pytential.qbx.fmm import drive_fmm + all_potentials_on_every_tgt = drive_fmm(wrangler, strengths) + + # }}} + + result = [] + + for o in insn.outputs: + tgt_side_number = tgt_name_and_side_to_number[ + o.target_name, o.qbx_forced_limit] + tgt_slice = slice(*geo_data.target_info().target_discr_starts[ + tgt_side_number:tgt_side_number+2]) + + result.append( + (o.name, + all_potentials_on_every_tgt[o.kernel_index][tgt_slice])) + + return result, [] + # }}} from boxtree.area_query import AreaQueryElementwiseTemplate @@ -883,7 +1096,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): new_lpot_source = NewQBXLayerPotentialSource( new_density_discr, lpot_source.fine_order, - qbx_level_to_order=lpot_source.qbx_level_to_order, + qbx_order=lpot_source.qbx_order, fmm_level_to_order=lpot_source.fmm_level_to_order, real_dtype=lpot_source.real_dtype, debug=debug) diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index bceb2067..fab60629 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -186,8 +186,7 @@ QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( int *target_status, /* output */ - int *target_to_source, - int *target_to_center_side, + int *target_to_center, coord_t *min_dist_to_center, /* input, dim-dependent size */ @@ -228,8 +227,7 @@ QBX_CENTER_FINDER = AreaQueryElementwiseTemplate( { target_status[i] = MARKED_QBX_CENTER_FOUND; min_dist_to_center[i] = my_dist_to_center; - target_to_source[i] = SOURCE_FOR_CENTER_PARTICLE(center); - target_to_center_side[i] = center_side; + target_to_center[i] = center; } } """, @@ -310,8 +308,7 @@ class QBXTargetAssociationFailedException(Exception): class QBXTargetAssociation(DeviceDataRecord): """ - .. attribute:: target_to_source - .. attribute:: target_to_side + .. attribute:: target_to_center """ pass @@ -493,8 +490,7 @@ class QBXTargetAssociator(DiscrPlotterMixin): stick_out_factor, target_flags, target_status, - target_assoc.target_to_source, - target_assoc.target_to_center_side, + target_assoc.target_to_center, min_dist_to_center, *tree.sources), range=slice(tree.nqbxtargets), @@ -597,18 +593,11 @@ class QBXTargetAssociator(DiscrPlotterMixin): return target_flags def make_default_target_association(self, queue, ntargets): - target_to_source = cl.array.empty(queue, ntargets, dtype=np.int32) - target_to_center_side = cl.array.empty_like(target_to_source) + target_to_center = cl.array.empty(queue, ntargets, dtype=np.int32) + target_to_center.fill(-1) + target_to_center.finish() - target_to_source.fill(-1) - target_to_center_side.fill(0) - - target_to_source.finish() - target_to_center_side.finish() - - return QBXTargetAssociation( - target_to_source=target_to_source, - target_to_center_side=target_to_center_side) + return QBXTargetAssociation(target_to_center=target_to_center) def __call__(self, lpot_source, target_discrs_and_qbx_sides, stick_out_factor=1e-10, debug=True, wait_for=None): diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index bbaf2171..2fdac2c3 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -91,6 +91,33 @@ def get_interleaver_kernel(dtype): # }}} +# {{{ make interleaved centers + +def get_interleaved_centers(queue, lpot_source): + """ + Return an array of shape (dim, ncenters) in which interior centers are placed + next to corresponding exterior centers. + """ + knl = get_interleaver_kernel(lpot_source.density_discr.real_dtype) + int_centers = lpot_source.centers(-1) + ext_centers = lpot_source.centers(+1) + + result = [] + wait_for = [] + + for int_axis, ext_axis in zip(int_centers, ext_centers): + axis = cl.array.empty(queue, len(int_axis) * 2, int_axis.dtype) + evt, _ = knl(queue, src1=int_axis, src2=ext_axis, dst=axis) + result.append(axis) + wait_for.append(evt) + + cl.wait_for_events(wait_for) + + return result + +# }}} + + # {{{ discr plotter mixin class DiscrPlotterMixin(object): @@ -161,28 +188,6 @@ class TreeWithQBXMetadataBuilder(object): from boxtree.tree_build import TreeBuilder self.tree_builder = TreeBuilder(self.context) - def get_interleaved_centers(self, queue, lpot_source): - """ - Return an array of shape (dim, ncenters) in which interior centers are placed - next to corresponding exterior centers. - """ - knl = get_interleaver_kernel(lpot_source.density_discr.real_dtype) - int_centers = lpot_source.centers(-1) - ext_centers = lpot_source.centers(+1) - - result = [] - wait_for = [] - - for int_axis, ext_axis in zip(int_centers, ext_centers): - axis = cl.array.empty(queue, len(int_axis) * 2, int_axis.dtype) - evt, _ = knl(queue, src1=int_axis, src2=ext_axis, dst=axis) - result.append(axis) - wait_for.append(evt) - - cl.wait_for_events(wait_for) - - return result - def __call__(self, queue, lpot_source, targets_list=()): """ Return a :class:`TreeWithQBXMetadata` built from the given layer @@ -204,7 +209,7 @@ class TreeWithQBXMetadataBuilder(object): logger.info("start building tree with qbx metadata") sources = lpot_source.density_discr.nodes() - centers = self.get_interleaved_centers(queue, lpot_source) + centers = get_interleaved_centers(queue, lpot_source) centers_of_mass = lpot_source.panel_centers_of_mass() targets = (tgt.nodes() for tgt in targets_list) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 2503835c..d7813edd 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -44,6 +44,10 @@ logger = logging.getLogger(__name__) circle = partial(ellipse, 1) +import faulthandler + +faulthandler.enable() + __all__ = [ "pytest_generate_tests", @@ -127,7 +131,8 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - from pytential.qbx import QBXLayerPotentialSource + from pytential.qbx.refinement import ( + NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) from pytools.convergence import EOCRecorder s_eoc_rec = EOCRecorder() @@ -151,16 +156,18 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): np.linspace(0, 1, nelements+1), target_order) - fmm_order = qbx_order - if fmm_order > 3: - # FIXME: for now + fmm_order = qbx_order + 3 + if fmm_order > 4: fmm_order = False density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, fmm_order=fmm_order) + refiner = QBXLayerPotentialSourceRefiner(cl_ctx) + qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + density_discr = qbx.density_discr nodes = density_discr.nodes().with_queue(queue) @@ -186,6 +193,8 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): cl.clmath.sin(angle)**2 + (1/ellipse_aspect)**2 * cl.clmath.cos(angle)**2) + h = cl.array.max(qbx.panel_sizes("npanels").with_queue(queue)).get() + # {{{ single layer sigma = cl.clmath.cos(mode_nr*angle)/J @@ -210,7 +219,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): norm(density_discr, queue, s_sigma - s_sigma_ref) / norm(density_discr, queue, s_sigma_ref)) - s_eoc_rec.add_data_point(1/nelements, s_err) + s_eoc_rec.add_data_point(h, s_err) # }}} @@ -241,7 +250,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): norm(density_discr, queue, d_sigma - d_sigma_ref) / d_ref_norm) - d_eoc_rec.add_data_point(1/nelements, d_err) + d_eoc_rec.add_data_point(h, d_err) # }}} -- GitLab From c1e8d8b68a450005bf7a7c99c5c5517b02889af7 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 2 Dec 2016 17:38:16 -0600 Subject: [PATCH 21/62] Fix l2qbxl for order varying by level. --- pytential/qbx/fmm.py | 39 ++++++++++++++++++----------------- pytential/qbx/interactions.py | 33 +++++++++++++++++------------ 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/pytential/qbx/fmm.py b/pytential/qbx/fmm.py index ce605589..e3508b70 100644 --- a/pytential/qbx/fmm.py +++ b/pytential/qbx/fmm.py @@ -277,33 +277,34 @@ QBXFMMGeometryData.non_qbx_box_target_lists`), return qbx_expansions trav = geo_data.traversal() - from pytools import single_valued + wait_for = local_exps.events - # AARGH FIXME order may vary by level - # TODO: This needs to be split up by source order - src_order = single_valued(self.level_orders) - l2qbxl = self.code.l2qbxl( - src_order, - self.qbx_order) + for isrc_level in range(geo_data.tree().nlevels): + l2qbxl = self.code.l2qbxl( + self.level_orders[isrc_level], + self.qbx_order) - l_expn_length = len(self.code.local_expansion_factory(src_order)) + target_level_start_ibox, target_locals_view = \ + self.local_expansions_view(local_exps, isrc_level) - local_exps_shaped = local_exps.reshape(-1, l_expn_length) + evt, (qbx_expansions_res,) = l2qbxl( + self.queue, + qbx_center_to_target_box=geo_data.qbx_center_to_target_box(), + target_boxes=trav.target_boxes, + target_base_ibox=target_level_start_ibox, - evt, (qbx_expansions_res,) = l2qbxl( - self.queue, - qbx_center_to_target_box=geo_data.qbx_center_to_target_box(), - target_boxes=trav.target_boxes, + centers=self.tree.box_centers, + qbx_centers=geo_data.center_info().centers, - centers=self.tree.box_centers, - qbx_centers=geo_data.center_info().centers, + expansions=target_locals_view, + qbx_expansions=qbx_expansions, - expansions=local_exps_shaped, - qbx_expansions=qbx_expansions, + wait_for=wait_for, - wait_for=local_exps.events, + **self.kernel_extra_kwargs) - **self.kernel_extra_kwargs) + wait_for = [evt] + assert qbx_expansions_res is qbx_expansions qbx_expansions.add_event(evt) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 956fee29..2deffe69 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -242,18 +242,25 @@ class L2QBXL(E2EBase): <> src_ibox = target_boxes[isrc_box] \ {id=read_src_ibox} - <> tgt_center[idim] = qbx_centers[idim, icenter] - <> src_center[idim] = centers[idim, src_ibox] {dup=idim} - <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} + <> in_range = (target_base_ibox <= src_ibox + and src_ibox < target_base_ibox + nboxes) - """] + [""" - <> src_coeff{i} = expansions[src_ibox, {i}] {{dep=read_src_ibox}} - """.format(i=i) for i in range(ncoeff_src)] + [ - ] + self.get_translation_loopy_insns() + [""" - qbx_expansions[icenter, {i}] = \ - qbx_expansions[icenter, {i}] + coeff{i} \ - {{id_prefix=write_expn}} - """.format(i=i) for i in range(ncoeff_tgt)] + [""" + if in_range + <> tgt_center[idim] = qbx_centers[idim, icenter] + <> src_center[idim] = centers[idim, src_ibox] {dup=idim} + <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} + + """] + [""" + <> src_coeff{i} = \ + expansions[src_ibox - target_base_ibox, {i}] \ + {{dep=read_src_ibox}} + """.format(i=i) for i in range(ncoeff_src)] + [ + ] + self.get_translation_loopy_insns() + [""" + qbx_expansions[icenter, {i}] = \ + qbx_expansions[icenter, {i}] + coeff{i} \ + {{id_prefix=write_expn}} + """.format(i=i) for i in range(ncoeff_tgt)] + [""" + end end """], [ @@ -262,9 +269,9 @@ class L2QBXL(E2EBase): lp.GlobalArg("centers", None, shape="dim, naligned_boxes"), lp.GlobalArg("qbx_centers", None, shape="dim, ncenters", dim_tags="sep,c"), - lp.ValueArg("naligned_boxes,nboxes", np.int32), + lp.ValueArg("naligned_boxes,target_base_ibox,nboxes", np.int32), lp.GlobalArg("expansions", None, - shape=("nboxes", ncoeff_src)), + shape=("nboxes", ncoeff_src), offset=lp.auto), "..." ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), name=self.name, -- GitLab From bfa5041eece9bbbdf63a65bd2ed16c41d9c5c542 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 5 Dec 2016 14:08:21 -0600 Subject: [PATCH 22/62] Add direct execution back (haven't tried running it yet). --- pytential/qbx/refinement.py | 168 ++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index d6f32293..51196eb2 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -467,6 +467,174 @@ class NewQBXLayerPotentialSource(LayerPotentialSource): return result, [] + # {{{ direct execution + + @memoize_method + def get_lpot_applier(self, kernels): + # needs to be separate method for caching + + from pytools import any + if any(knl.is_complex_valued for knl in kernels): + value_dtype = self.density_discr.complex_dtype + else: + value_dtype = self.density_discr.real_dtype + + from sumpy.qbx import LayerPotential + from sumpy.expansion.local import LineTaylorLocalExpansion + return LayerPotential(self.cl_context, + [LineTaylorLocalExpansion(knl, self.qbx_order) + for knl in kernels], + value_dtypes=value_dtype) + + @memoize_method + def get_lpot_applier_on_tgt_subset(self, kernels): + # needs to be separate method for caching + + from pytools import any + if any(knl.is_complex_valued for knl in kernels): + value_dtype = self.density_discr.complex_dtype + else: + value_dtype = self.density_discr.real_dtype + + from pytential.qbx.direct import LayerPotentialOnTargetAndCenterSubset + from sumpy.expansion.local import VolumeTaylorLocalExpansion + return LayerPotentialOnTargetAndCenterSubset( + self.cl_context, + [VolumeTaylorLocalExpansion(knl, self.qbx_order) + for knl in kernels], + value_dtypes=value_dtype) + + @memoize_method + def get_p2p(self, kernels): + # needs to be separate method for caching + + from pytools import any + if any(knl.is_complex_valued for knl in kernels): + value_dtype = self.density_discr.complex_dtype + else: + value_dtype = self.density_discr.real_dtype + + from sumpy.p2p import P2P + p2p = P2P(self.cl_context, + kernels, exclude_self=False, value_dtypes=value_dtype) + + return p2p + + @memoize_method + def get_qbx_target_numberer(self, dtype): + assert dtype == np.int32 + from pyopencl.scan import GenericScanKernel + return GenericScanKernel( + self.cl_context, np.int32, + arguments="int *tgt_to_qbx_center, int *qbx_tgt_number, int *count", + input_expr="tgt_to_qbx_center[i] >= 0 ? 1 : 0", + scan_expr="a+b", neutral="0", + output_statement=""" + if (item != prev_item) + qbx_tgt_number[item-1] = i; + + if (i+1 == N) + *count = item; + """) + + def exec_layer_potential_insn_direct(self, queue, insn, bound_expr, evaluate): + lpot_applier = self.get_lpot_applier(insn.kernels) + p2p = None + lpot_applier_on_tgt_subset = None + + kernel_args = {} + for arg_name, arg_expr in six.iteritems(insn.kernel_arguments): + kernel_args[arg_name] = evaluate(arg_expr) + + strengths = (evaluate(insn.density).with_queue(queue) + * self.weights_and_area_elements()) + + # FIXME: Do this all at once + result = [] + for o in insn.outputs: + target_discr = bound_expr.get_discretization(o.target_name) + + is_self = self.density_discr is target_discr + + if is_self: + # QBXPreprocessor is supposed to have taken care of this + assert o.qbx_forced_limit is not None + assert abs(o.qbx_forced_limit) > 0 + + evt, output_for_each_kernel = lpot_applier( + queue, target_discr.nodes(), + self.fine_density_discr.nodes(), + self.centers(target_discr, o.qbx_forced_limit), + [strengths], **kernel_args) + result.append((o.name, output_for_each_kernel[o.kernel_index])) + else: + # no on-disk kernel caching + if p2p is None: + p2p = self.get_p2p(insn.kernels) + if lpot_applier_on_tgt_subset is None: + lpot_applier_on_tgt_subset = self.get_lpot_applier_on_tgt_subset( + insn.kernels) + + evt, output_for_each_kernel = p2p(queue, + target_discr.nodes(), self.fine_density_discr.nodes(), + [strengths], **kernel_args) + + geo_data = self.qbx_fmm_geometry_data( + target_discrs_and_qbx_sides=[ + (target_discr, qbx_forced_limit) + ]) + + # center_info is independent of targets + center_info = geo_data.center_info() + + qbx_forced_limit = o.qbx_forced_limit + if qbx_forced_limit is None: + qbx_forced_limit = 0 + + tgt_to_qbx_center = ( + geo_data.user_target_to_center()[center_info.ncenters:]) + + qbx_tgt_numberer = self.get_qbx_target_numberer( + tgt_to_qbx_center.dtype) + qbx_tgt_count = cl.array.empty(queue, (), np.int32) + qbx_tgt_numbers = cl.array.empty_like(tgt_to_qbx_center) + + qbx_tgt_numberer( + tgt_to_qbx_center, qbx_tgt_numbers, qbx_tgt_count, + queue=queue) + + qbx_tgt_count = int(qbx_tgt_count.get()) + + if (o.qbx_forced_limit is not None + and abs(o.qbx_forced_limit) == 1 + and qbx_tgt_count < target_discr.nnodes): + raise RuntimeError("Did not find a matching QBX center " + "for some targets") + + qbx_tgt_numbers = qbx_tgt_numbers[:qbx_tgt_count] + qbx_center_numbers = tgt_to_qbx_center[qbx_tgt_numbers] + + tgt_subset_kwargs = kernel_args.copy() + for i, res_i in enumerate(output_for_each_kernel): + tgt_subset_kwargs["result_%d" % i] = res_i + + if qbx_tgt_count: + lpot_applier_on_tgt_subset( + queue, + targets=target_discr.nodes(), + sources=self.fine_density_discr.nodes(), + centers=center_info.centers, + strengths=[strengths], + qbx_tgt_numbers=qbx_tgt_numbers, + qbx_center_numbers=qbx_center_numbers, + **tgt_subset_kwargs) + + result.append((o.name, output_for_each_kernel[o.kernel_index])) + + return result, [] + + # }}} + # }}} from boxtree.area_query import AreaQueryElementwiseTemplate -- GitLab From f9c8418ad0d46f5ba2a9fe400027cab588de2cfb Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 6 Dec 2016 16:51:23 -0600 Subject: [PATCH 23/62] Start moving lpot source over, fix flake8 issues. --- pytential/qbx/__init__.py | 199 +++++++--- pytential/qbx/geometry.py | 176 --------- pytential/qbx/refinement.py | 626 +------------------------------- pytential/qbx/target_assoc.py | 4 +- pytential/qbx/utils.py | 5 +- test/extra_curve_data.py | 6 +- test/test_global_qbx.py | 56 ++- test/test_layer_pot.py | 28 +- test/too_slow_test_helmholtz.py | 4 +- 9 files changed, 230 insertions(+), 874 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index a2af8377..a2f44584 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -24,14 +24,17 @@ THE SOFTWARE. import six +import loopy as lp import numpy as np -#import numpy.linalg as la from pytools import memoize_method from meshmode.discretization import Discretization from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory import pyopencl as cl +import logging +logger = logging.getLogger(__name__) + __doc__ = """ .. autoclass:: QBXLayerPotentialSource """ @@ -124,6 +127,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): .. attribute :: fmm_order .. attribute :: cl_context .. automethod :: centers + .. automethod :: panel_sizes .. automethod :: weights_and_area_elements See :ref:`qbxguts` for some information on the inner workings of this. @@ -133,6 +137,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): fmm_level_to_order=None, # FIXME set debug=False once everything works real_dtype=np.float64, debug=True, + refined_for_global_qbx=False, performance_data_file=None): """ :arg fine_order: The total degree to which the (upsampled) @@ -141,14 +146,6 @@ class QBXLayerPotentialSource(LayerPotentialSource): a reasonable(-ish?) default. """ - self.fine_density_discr = Discretization( - density_discr.cl_context, density_discr.mesh, - QuadratureSimplexGroupFactory(fine_order), real_dtype) - - from meshmode.discretization.connection import make_same_mesh_connection - self.resampler = make_same_mesh_connection( - self.fine_density_discr, density_discr) - if fmm_level_to_order is None: if fmm_order is None and qbx_order is not None: fmm_order = qbx_order + 1 @@ -163,12 +160,56 @@ class QBXLayerPotentialSource(LayerPotentialSource): def fmm_level_to_order(level): return fmm_order + self.refined_for_global_qbx = refined_for_global_qbx + self.fine_order = fine_order self.qbx_order = qbx_order self.density_discr = density_discr self.fmm_level_to_order = fmm_level_to_order self.debug = debug self.performance_data_file = performance_data_file + @property + @memoize_method + def fine_density_discr(self): + return Discretization( + self.density_discr.cl_context, self.density_discr.mesh, + QuadratureSimplexGroupFactory(self.fine_order), self.real_dtype) + + @property + @memoize_method + def resampler(self): + from meshmode.discretization.connection import make_same_mesh_connection + return make_same_mesh_connection( + self.fine_density_discr, self.density_discr) + + def el_view(self, discr, group_nr, global_array): + """Return a view of *global_array* of shape + ``(..., discr.groups[group_nr].nelements)`` + where *global_array* is of shape ``(..., nelements)``, + where *nelements* is the global (per-discretization) node count. + """ + + group = discr.groups[group_nr] + el_nr_base = sum(group.nelements for group in discr.groups[:group_nr]) + + return global_array[ + ..., el_nr_base:el_nr_base + group.nelements] \ + .reshape( + global_array.shape[:-1] + + (group.nelements,)) + + @memoize_method + def with_refinement(self, target_order=None): + from pytential.qbx.refinement import QBXLayerPotentialSourceRefiner + refiner = QBXLayerPotentialSourceRefiner(self.cl_context) + from meshmode.discretization.poly_element import ( + InterpolatoryQuadratureSimplexGroupFactory) + if target_order is None: + target_order = self.density_discr.groups[0].order + lpot, _ = refiner(self, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + return lpot + @property def ambient_dim(self): return self.density_discr.ambient_dim @@ -186,12 +227,96 @@ class QBXLayerPotentialSource(LayerPotentialSource): return self.density_discr.complex_dtype @memoize_method - def centers(self, target_discr, sign): + def panel_centers_of_mass(self): + knl = lp.make_kernel( + """{[dim,k,i]: + 0<=dim.clear_cache(geo_data). - - # FIXME Synthesize "bad centers" around corners and edges that have - # inadequate QBX coverage. - - # FIXME don't compute *all* output kernels on all targets--respect that - # some target discretizations may only be asking for derivatives (e.g.) - - strengths = (evaluate(insn.density).with_queue(queue) - * self.weights_and_area_elements()) - - # {{{ get expansion wrangler - - base_kernel = None - out_kernels = [] - - from sumpy.kernel import AxisTargetDerivativeRemover - for knl in insn.kernels: - candidate_base_kernel = AxisTargetDerivativeRemover()(knl) - - if base_kernel is None: - base_kernel = candidate_base_kernel - else: - assert base_kernel == candidate_base_kernel - - out_kernels = tuple(knl for knl in insn.kernels) - - if base_kernel.is_complex_valued or strengths.dtype.kind == "c": - value_dtype = self.complex_dtype - else: - value_dtype = self.real_dtype - - # {{{ build extra_kwargs dictionaries - - # This contains things like the Helmholtz parameter k or - # the normal directions for double layers. - - def reorder_sources(source_array): - if isinstance(source_array, cl.array.Array): - return (source_array - .with_queue(queue) - [geo_data.tree().user_source_ids] - .with_queue(None)) - else: - return source_array - - kernel_extra_kwargs = {} - source_extra_kwargs = {} - - from sumpy.tools import gather_arguments, gather_source_arguments - from pytools.obj_array import with_object_array_or_scalar - for func, var_dict in [ - (gather_arguments, kernel_extra_kwargs), - (gather_source_arguments, source_extra_kwargs), - ]: - for arg in func(out_kernels): - var_dict[arg.name] = with_object_array_or_scalar( - reorder_sources, - evaluate(insn.kernel_arguments[arg.name])) - - # }}} - - wrangler = self.expansion_wrangler_code_container( - base_kernel, out_kernels).get_wrangler( - queue, geo_data, value_dtype, - self.qbx_order, - self.fmm_level_to_order, - source_extra_kwargs=source_extra_kwargs, - kernel_extra_kwargs=kernel_extra_kwargs) - - # }}} - - #geo_data.plot() - - if len(geo_data.global_qbx_centers()) != geo_data.center_info().ncenters: - raise NotImplementedError("geometry has centers requiring local QBX") - - from pytential.qbx.geometry import target_state - if (geo_data.user_target_to_center().with_queue(queue) - == target_state.FAILED).any().get(): - raise RuntimeError("geometry has failed targets") - - if self.performance_data_file is not None: - from pytential.qbx.fmm import write_performance_model - with open(self.performance_data_file, "w") as outf: - write_performance_model(outf, geo_data) - - # {{{ execute global QBX - - from pytential.qbx.fmm import drive_fmm - all_potentials_on_every_tgt = drive_fmm(wrangler, strengths) - - # }}} - - result = [] - - for o in insn.outputs: - tgt_side_number = tgt_name_and_side_to_number[ - o.target_name, o.qbx_forced_limit] - tgt_slice = slice(*geo_data.target_info().target_discr_starts[ - tgt_side_number:tgt_side_number+2]) - - result.append( - (o.name, - all_potentials_on_every_tgt[o.kernel_index][tgt_slice])) - - return result, [] - - # {{{ direct execution - - @memoize_method - def get_lpot_applier(self, kernels): - # needs to be separate method for caching - - from pytools import any - if any(knl.is_complex_valued for knl in kernels): - value_dtype = self.density_discr.complex_dtype - else: - value_dtype = self.density_discr.real_dtype - - from sumpy.qbx import LayerPotential - from sumpy.expansion.local import LineTaylorLocalExpansion - return LayerPotential(self.cl_context, - [LineTaylorLocalExpansion(knl, self.qbx_order) - for knl in kernels], - value_dtypes=value_dtype) - - @memoize_method - def get_lpot_applier_on_tgt_subset(self, kernels): - # needs to be separate method for caching - - from pytools import any - if any(knl.is_complex_valued for knl in kernels): - value_dtype = self.density_discr.complex_dtype - else: - value_dtype = self.density_discr.real_dtype - - from pytential.qbx.direct import LayerPotentialOnTargetAndCenterSubset - from sumpy.expansion.local import VolumeTaylorLocalExpansion - return LayerPotentialOnTargetAndCenterSubset( - self.cl_context, - [VolumeTaylorLocalExpansion(knl, self.qbx_order) - for knl in kernels], - value_dtypes=value_dtype) - - @memoize_method - def get_p2p(self, kernels): - # needs to be separate method for caching - - from pytools import any - if any(knl.is_complex_valued for knl in kernels): - value_dtype = self.density_discr.complex_dtype - else: - value_dtype = self.density_discr.real_dtype - - from sumpy.p2p import P2P - p2p = P2P(self.cl_context, - kernels, exclude_self=False, value_dtypes=value_dtype) - - return p2p - - @memoize_method - def get_qbx_target_numberer(self, dtype): - assert dtype == np.int32 - from pyopencl.scan import GenericScanKernel - return GenericScanKernel( - self.cl_context, np.int32, - arguments="int *tgt_to_qbx_center, int *qbx_tgt_number, int *count", - input_expr="tgt_to_qbx_center[i] >= 0 ? 1 : 0", - scan_expr="a+b", neutral="0", - output_statement=""" - if (item != prev_item) - qbx_tgt_number[item-1] = i; - - if (i+1 == N) - *count = item; - """) - - def exec_layer_potential_insn_direct(self, queue, insn, bound_expr, evaluate): - lpot_applier = self.get_lpot_applier(insn.kernels) - p2p = None - lpot_applier_on_tgt_subset = None - - kernel_args = {} - for arg_name, arg_expr in six.iteritems(insn.kernel_arguments): - kernel_args[arg_name] = evaluate(arg_expr) - - strengths = (evaluate(insn.density).with_queue(queue) - * self.weights_and_area_elements()) - - # FIXME: Do this all at once - result = [] - for o in insn.outputs: - target_discr = bound_expr.get_discretization(o.target_name) - - is_self = self.density_discr is target_discr - - if is_self: - # QBXPreprocessor is supposed to have taken care of this - assert o.qbx_forced_limit is not None - assert abs(o.qbx_forced_limit) > 0 - - evt, output_for_each_kernel = lpot_applier( - queue, target_discr.nodes(), - self.fine_density_discr.nodes(), - self.centers(target_discr, o.qbx_forced_limit), - [strengths], **kernel_args) - result.append((o.name, output_for_each_kernel[o.kernel_index])) - else: - # no on-disk kernel caching - if p2p is None: - p2p = self.get_p2p(insn.kernels) - if lpot_applier_on_tgt_subset is None: - lpot_applier_on_tgt_subset = self.get_lpot_applier_on_tgt_subset( - insn.kernels) - - evt, output_for_each_kernel = p2p(queue, - target_discr.nodes(), self.fine_density_discr.nodes(), - [strengths], **kernel_args) - - geo_data = self.qbx_fmm_geometry_data( - target_discrs_and_qbx_sides=[ - (target_discr, qbx_forced_limit) - ]) - - # center_info is independent of targets - center_info = geo_data.center_info() - - qbx_forced_limit = o.qbx_forced_limit - if qbx_forced_limit is None: - qbx_forced_limit = 0 - - tgt_to_qbx_center = ( - geo_data.user_target_to_center()[center_info.ncenters:]) - - qbx_tgt_numberer = self.get_qbx_target_numberer( - tgt_to_qbx_center.dtype) - qbx_tgt_count = cl.array.empty(queue, (), np.int32) - qbx_tgt_numbers = cl.array.empty_like(tgt_to_qbx_center) - - qbx_tgt_numberer( - tgt_to_qbx_center, qbx_tgt_numbers, qbx_tgt_count, - queue=queue) - - qbx_tgt_count = int(qbx_tgt_count.get()) - - if (o.qbx_forced_limit is not None - and abs(o.qbx_forced_limit) == 1 - and qbx_tgt_count < target_discr.nnodes): - raise RuntimeError("Did not find a matching QBX center " - "for some targets") - - qbx_tgt_numbers = qbx_tgt_numbers[:qbx_tgt_count] - qbx_center_numbers = tgt_to_qbx_center[qbx_tgt_numbers] - - tgt_subset_kwargs = kernel_args.copy() - for i, res_i in enumerate(output_for_each_kernel): - tgt_subset_kwargs["result_%d" % i] = res_i - - if qbx_tgt_count: - lpot_applier_on_tgt_subset( - queue, - targets=target_discr.nodes(), - sources=self.fine_density_discr.nodes(), - centers=center_info.centers, - strengths=[strengths], - qbx_tgt_numbers=qbx_tgt_numbers, - qbx_center_numbers=qbx_center_numbers, - **tgt_subset_kwargs) - - result.append((o.name, output_for_each_kernel[o.kernel_index])) - - return result, [] - - # }}} - -# }}} - -from boxtree.area_query import AreaQueryElementwiseTemplate -from pyopencl.elementwise import ElementwiseTemplate -from boxtree.tools import InlineBinarySearch -from pytential.qbx.utils import QBX_TREE_C_PREAMBLE, QBX_TREE_MAKO_DEFS - - -unwrap_args = AreaQueryElementwiseTemplate.unwrap_args - - # {{{ kernels TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( @@ -980,8 +384,10 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): """ for panel <> oversize = panel_sizes[panel] * helmholtz_k > 5 - refine_flags[panel] = 1 {if=oversize} - refine_flags_updated = 1 {id=write_refine_flags_updated,if=oversize} + if oversize + refine_flags[panel] = 1 + refine_flags_updated = 1 {id=write_refine_flags_updated} + end end """, options="return_dict", @@ -1230,7 +636,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): Return an array on the device suitable for use as element refine flags. :arg queue: An instance of :class:`pyopencl.CommandQueue`. - :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource`. + :arg lpot_source: An instance of :class:`QBXLayerPotentialSource`. :returns: An instance of :class:`pyopencl.array.Array` suitable for use as refine flags, initialized to zero. @@ -1262,11 +668,13 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): new_density_discr = conn.to_discr - new_lpot_source = NewQBXLayerPotentialSource( + from pytential.qbx import QBXLayerPotentialSource + new_lpot_source = QBXLayerPotentialSource( new_density_discr, lpot_source.fine_order, qbx_order=lpot_source.qbx_order, fmm_level_to_order=lpot_source.fmm_level_to_order, - real_dtype=lpot_source.real_dtype, debug=debug) + real_dtype=lpot_source.real_dtype, debug=debug, + refined_for_global_qbx=True) return new_lpot_source, conn @@ -1276,7 +684,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): """ Entry point for calling the refiner. - :arg lpot_source: An instance of :class:`NewQBXLayerPotentialSource`. + :arg lpot_source: An instance of :class:`QBXLayerPotentialSource`. :arg group_factory: An instance of :class:`meshmode.mesh.discretization.ElementGroupFactory`. Used for diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index fab60629..deca3b7d 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -104,14 +104,14 @@ enum TargetFlag """ -class target_status_enum(object): +class target_status_enum(object): # noqa # NOTE: Must match "enum TargetStatus" above UNMARKED = 0 MARKED_QBX_CENTER_PENDING = 1 MARKED_QBX_CENTER_FOUND = 2 -class target_flag_enum(object): +class target_flag_enum(object): # noqa # NOTE: Must match "enum TargetFlag" above INTERIOR_OR_EXTERIOR_VOLUME_TARGET = 0 INTERIOR_SURFACE_TARGET = -1 diff --git a/pytential/qbx/utils.py b/pytential/qbx/utils.py index 2fdac2c3..60da4b5b 100644 --- a/pytential/qbx/utils.py +++ b/pytential/qbx/utils.py @@ -154,6 +154,8 @@ class DiscrPlotterMixin(object): class TreeWithQBXMetadataBuilder(object): + MAX_REFINE_WEIGHT = 64 + class TreeWithQBXMetadata(Tree): """ .. attribute:: nqbxpanels @@ -239,12 +241,11 @@ class TreeWithQBXMetadataBuilder(object): # only because of sources. refine_weights = cl.array.zeros(queue, nparticles, np.int32) refine_weights[:nsources].fill(1) - MAX_REFINE_WEIGHT = 64 refine_weights.finish() tree, evt = self.tree_builder(queue, particles, - max_leaf_refine_weight=MAX_REFINE_WEIGHT, + max_leaf_refine_weight=self.MAX_REFINE_WEIGHT, refine_weights=refine_weights) # Compute box => particle class relations diff --git a/test/extra_curve_data.py b/test/extra_curve_data.py index 86caa82e..e6d667e4 100644 --- a/test/extra_curve_data.py +++ b/test/extra_curve_data.py @@ -103,9 +103,9 @@ class Arc(Curve): # Get center and radius of circle containing the arc. # http://math.stackexchange.com/a/1460096 - C = np.array([xs**2 + ys**2, xs, ys, [1, 1, 1]]) - x0 = la.det(np.delete(C, 1, 0)) / (2 * la.det(np.delete(C, 0, 0))) - y0 = -la.det(np.delete(C, 2, 0)) / (2 * la.det(np.delete(C, 0, 0))) + c = np.array([xs**2 + ys**2, xs, ys, [1, 1, 1]]) + x0 = la.det(np.delete(c, 1, 0)) / (2 * la.det(np.delete(c, 0, 0))) + y0 = -la.det(np.delete(c, 2, 0)) / (2 * la.det(np.delete(c, 0, 0))) self.r = la.norm([start[0] - x0, start[1] - y0]) self.center = x0 + 1j * y0 diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 2340bd50..1665a2ec 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -34,6 +34,7 @@ import pytest from pytools import RecordWithoutPickling from pyopencl.tools import pytest_generate_tests_for_pyopencl \ as pytest_generate_tests +from pytential.qbx import QBXLayerPotentialSource from functools import partial from meshmode.mesh.generation import ( # noqa @@ -48,6 +49,10 @@ logger = logging.getLogger(__name__) __all__ = ["pytest_generate_tests"] +RNG_SEED = 10 +FAR_TARGET_DIST_FROM_SOURCE = 10 + + # {{{ utilities for iterating over panels class ElementInfo(RecordWithoutPickling): @@ -107,10 +112,9 @@ def test_source_refinement(ctx_getter, curve_name, curve_f, nelements): discr = Discretization(cl_ctx, mesh, factory) - from pytential.qbx.refinement import ( - NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + from pytential.qbx.refinement import QBXLayerPotentialSourceRefiner - lpot_source = NewQBXLayerPotentialSource(discr, order) + lpot_source = QBXLayerPotentialSource(discr, order) del discr refiner = QBXLayerPotentialSourceRefiner(cl_ctx) @@ -200,10 +204,9 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): discr = Discretization(cl_ctx, mesh, factory) - from pytential.qbx.refinement import ( - NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + from pytential.qbx.refinement import QBXLayerPotentialSourceRefiner - lpot_source = NewQBXLayerPotentialSource(discr, order) + lpot_source = QBXLayerPotentialSource(discr, order) del discr refiner = QBXLayerPotentialSourceRefiner(cl_ctx) @@ -216,8 +219,6 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): # {{{ generate targets - RNG_SEED = 10 - from pyopencl.clrandom import PhiloxGenerator rng = PhiloxGenerator(cl_ctx, seed=RNG_SEED) nsources = lpot_source.density_discr.nnodes @@ -234,8 +235,7 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): int_targets = PointsTarget(targets_from_sources(-1, noise * panel_sizes / 2)) ext_targets = PointsTarget(targets_from_sources(+1, noise * panel_sizes / 2)) - FAR_TARGET_DIST = 10 - far_targets = PointsTarget(targets_from_sources(+1, FAR_TARGET_DIST)) + far_targets = PointsTarget(targets_from_sources(+1, FAR_TARGET_DIST_FROM_SOURCE)) # Create target discretizations. target_discrs = ( @@ -292,33 +292,36 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): assert (dists <= panel_sizes[target_to_source_result] / 2).all() # Checks that far targets are not assigned a center. - def check_far_targets(target_to_source_result, target_to_side_result): + def check_far_targets(target_to_source_result): assert (target_to_source_result == -1).all() - assert (target_to_side_result == 0).all() + + # Centers for source i are located at indices 2 * i, 2 * i + 1 + target_to_source = target_assoc.target_to_center // 2 + # Center side order = -1, 1, -1, 1, ... + target_to_center_side = 2 * (target_assoc.target_to_center % 2) - 1 check_on_surface_targets( nsources, -1, - target_assoc.target_to_source[surf_int_slice], - target_assoc.target_to_center_side[surf_int_slice]) + target_to_source[surf_int_slice], + target_to_center_side[surf_int_slice]) check_on_surface_targets( nsources, +1, - target_assoc.target_to_source[surf_ext_slice], - target_assoc.target_to_center_side[surf_ext_slice]) + target_to_source[surf_ext_slice], + target_to_center_side[surf_ext_slice]) check_close_targets( int_centers, int_targets, -1, - target_assoc.target_to_source[vol_int_slice], - target_assoc.target_to_center_side[vol_int_slice]) + target_to_source[vol_int_slice], + target_to_center_side[vol_int_slice]) check_close_targets( ext_centers, ext_targets, +1, - target_assoc.target_to_source[vol_ext_slice], - target_assoc.target_to_center_side[vol_ext_slice]) + target_to_source[vol_ext_slice], + target_to_center_side[vol_ext_slice]) check_far_targets( - target_assoc.target_to_source[far_slice], - target_assoc.target_to_center_side[far_slice]) + target_to_source[far_slice]) # }}} @@ -340,17 +343,12 @@ def test_target_association_failure(ctx_getter): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory factory = InterpolatoryQuadratureSimplexGroupFactory(order) - discr = Discretization(cl_ctx, mesh, factory) - - from pytential.qbx.refinement import NewQBXLayerPotentialSource - - lpot_source = NewQBXLayerPotentialSource(discr, order) - del discr + lpot_source = QBXLayerPotentialSource(discr, order) # }}} - # {{{ generate targets + # {{{ generate targets and check close_circle = 0.999 * np.exp( 2j * np.pi * np.linspace(0, 1, 500, endpoint=False)) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index d7813edd..792c62aa 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -761,15 +761,17 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - from pytential.qbx import QBXLayerPotentialSource + from pytential.qbx.refinement import ( + NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, - qbx_order, - # Don't use FMM for now - fmm_order=False) + qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, + qbx_order, fmm_order=qbx_order + 15) + refiner = QBXLayerPotentialSourceRefiner(cl_ctx) + qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE @@ -810,7 +812,9 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) l2_error_norm = norm(density_discr, queue, error) print(key, l2_error_norm) - eoc_rec.add_data_point(1/nelements, l2_error_norm) + h = cl.array.max(qbx.panel_sizes("npanels").with_queue(queue)).get() + + eoc_rec.add_data_point(h, l2_error_norm) print(eoc_rec) tgt_order = order_table[zero_op_name] @@ -850,9 +854,13 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( - cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, - fmm_order=fmm_order) + cl_ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(target_order)) + qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, + qbx_order, fmm_order=fmm_order) + refiner = QBXLayerPotentialSourceRefiner(cl_ctx) + qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + density_discr = qbx.density_discr from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(), sym.var("sigma"), qbx_forced_limit=-2) diff --git a/test/too_slow_test_helmholtz.py b/test/too_slow_test_helmholtz.py index ef89b615..c225d3bf 100644 --- a/test/too_slow_test_helmholtz.py +++ b/test/too_slow_test_helmholtz.py @@ -108,7 +108,7 @@ def run_dielectric_test(cl_ctx, queue, nelements, qbx_order, qbx = QBXLayerPotentialSource( density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order - ) + ).with_refinement() #print(sym.pretty(pde_op.operator(op_unknown_sym))) #1/0 @@ -338,7 +338,7 @@ def run_dielectric_test(cl_ctx, queue, nelements, qbx_order, low_order_qbx = QBXLayerPotentialSource( density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=2, - fmm_order=3) + fmm_order=3).with_refinement() from sumpy.kernel import LaplaceKernel from pytential.target import PointsTarget ones = (cl.array.empty(queue, (density_discr.nnodes,), dtype=np.float64) -- GitLab From 650bee8e3e7ef9a60c25a92646f4934628f769d8 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Wed, 7 Dec 2016 00:43:05 -0600 Subject: [PATCH 24/62] Add what I have. --- pytential/qbx/__init__.py | 18 +++++++++---- pytential/qbx/refinement.py | 9 ++++++- test/test_layer_pot.py | 53 +++++++++++++------------------------ 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index a2f44584..72632500 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -101,8 +101,8 @@ def get_local_expansion_class(base_kernel): from sumpy.expansion.local import H2DLocalExpansion return H2DLocalExpansion else: - from sumpy.expansion.local import VolumeTaylorLocalExpansion - return VolumeTaylorLocalExpansion + from sumpy.expansion.local import LaplaceConformingVolumeTaylorLocalExpansion + return LaplaceConformingVolumeTaylorLocalExpansion def get_multipole_expansion_class(base_kernel): @@ -113,8 +113,9 @@ def get_multipole_expansion_class(base_kernel): from sumpy.expansion.multipole import H2DMultipoleExpansion return H2DMultipoleExpansion else: - from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion - return VolumeTaylorMultipoleExpansion + from sumpy.expansion.multipole import ( + LaplaceConformingVolumeTaylorMultipoleExpansion) + return LaplaceConformingVolumeTaylorMultipoleExpansion # {{{ QBX layer potential source @@ -210,6 +211,13 @@ class QBXLayerPotentialSource(LayerPotentialSource): InterpolatoryQuadratureSimplexGroupFactory(target_order)) return lpot + @property + @memoize_method + def h_max(self): + with cl.CommandQueue(self.cl_context) as queue: + panel_sizes = self.panel_sizes("npanels").with_queue(queue) + return np.asscalar(cl.array.max(panel_sizes).get()) + @property def ambient_dim(self): return self.density_discr.ambient_dim @@ -655,7 +663,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): evt, output_for_each_kernel = lpot_applier( queue, target_discr.nodes(), self.fine_density_discr.nodes(), - self.centers(target_discr, o.qbx_forced_limit), + self.centers(o.qbx_forced_limit), [strengths], **kernel_args) result.append((o.name, output_for_each_kernel[o.kernel_index])) else: diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 073e4ef9..021347ca 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -32,6 +32,7 @@ import numpy as np import pyopencl as cl from pytools import memoize_method +from pytential.qbx import QBXLayerPotentialSource from pytential.qbx.utils import DiscrPlotterMixin from boxtree.area_query import AreaQueryElementwiseTemplate from pyopencl.elementwise import ElementwiseTemplate @@ -668,7 +669,6 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): new_density_discr = conn.to_discr - from pytential.qbx import QBXLayerPotentialSource new_lpot_source = QBXLayerPotentialSource( new_density_discr, lpot_source.fine_order, qbx_order=lpot_source.qbx_order, @@ -706,6 +706,13 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): refiner = Refiner(lpot_source.density_discr.mesh) connections = [] + lpot_source = QBXLayerPotentialSource( + lpot_source.density_discr, lpot_source.fine_order, + qbx_order=lpot_source.qbx_order, + fmm_level_to_order=lpot_source.fmm_level_to_order, + real_dtype=lpot_source.real_dtype, debug=debug, + refined_for_global_qbx=True) + with cl.CommandQueue(self.context) as queue: if refine_flags: lpot_source, conn = self.refine( diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 792c62aa..177072c7 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -44,10 +44,6 @@ logger = logging.getLogger(__name__) circle = partial(ellipse, 1) -import faulthandler - -faulthandler.enable() - __all__ = [ "pytest_generate_tests", @@ -131,8 +127,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - from pytential.qbx.refinement import ( - NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) + from pytential.qbx import QBXLayerPotentialSource from pytools.convergence import EOCRecorder s_eoc_rec = EOCRecorder() @@ -157,17 +152,15 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): target_order) fmm_order = qbx_order + 3 - if fmm_order > 4: + if fmm_order > 6: + # FIXME: for now fmm_order = False density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, - qbx_order, fmm_order=fmm_order) - refiner = QBXLayerPotentialSourceRefiner(cl_ctx) - qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - density_discr = qbx.density_discr + qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx_order, fmm_order=fmm_order).with_refinement() nodes = density_discr.nodes().with_queue(queue) @@ -193,8 +186,6 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): cl.clmath.sin(angle)**2 + (1/ellipse_aspect)**2 * cl.clmath.cos(angle)**2) - h = cl.array.max(qbx.panel_sizes("npanels").with_queue(queue)).get() - # {{{ single layer sigma = cl.clmath.cos(mode_nr*angle)/J @@ -219,7 +210,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): norm(density_discr, queue, s_sigma - s_sigma_ref) / norm(density_discr, queue, s_sigma_ref)) - s_eoc_rec.add_data_point(h, s_err) + s_eoc_rec.add_data_point(qbx.h_max, s_err) # }}} @@ -250,7 +241,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): norm(density_discr, queue, d_sigma - d_sigma_ref) / d_ref_norm) - d_eoc_rec.add_data_point(h, d_err) + d_eoc_rec.add_data_point(qbx.h_max, d_err) # }}} @@ -322,7 +313,7 @@ def run_int_eq_test( qbx = QBXLayerPotentialSource( density_discr, fine_order=source_order, qbx_order=qbx_order, # Don't use FMM for now - fmm_order=False) + fmm_order=False).with_refinement() # {{{ set up operator @@ -761,17 +752,15 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - from pytential.qbx.refinement import ( - NewQBXLayerPotentialSource, QBXLayerPotentialSourceRefiner) - + from pytential.qbx import QBXLayerPotentialSource density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, - qbx_order, fmm_order=qbx_order + 15) - refiner = QBXLayerPotentialSourceRefiner(cl_ctx) - qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - density_discr = qbx.density_discr + + qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx_order, + # Don't use FMM for now + fmm_order=False) # {{{ compute values of a solution to the PDE @@ -812,9 +801,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) l2_error_norm = norm(density_discr, queue, error) print(key, l2_error_norm) - h = cl.array.max(qbx.panel_sizes("npanels").with_queue(queue)).get() - - eoc_rec.add_data_point(h, l2_error_norm) + eoc_rec.add_data_point(qbx.h_max, l2_error_norm) print(eoc_rec) tgt_order = order_table[zero_op_name] @@ -854,13 +841,9 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( - cl_ctx, mesh, - InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = NewQBXLayerPotentialSource(density_discr, 4*target_order, - qbx_order, fmm_order=fmm_order) - refiner = QBXLayerPotentialSourceRefiner(cl_ctx) - qbx, conn = refiner(qbx, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - density_discr = qbx.density_discr + cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) + qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, + fmm_order=fmm_order).with_refinement() from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(), sym.var("sigma"), qbx_forced_limit=-2) -- GitLab From b36fbfe9b27acaa121b52bc1a370fd55a214ae6b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 8 Dec 2016 17:46:45 -0600 Subject: [PATCH 25/62] Document, fix interface of with_refinement to return connection --- pytential/qbx/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 72632500..6d9a9866 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -130,6 +130,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): .. automethod :: centers .. automethod :: panel_sizes .. automethod :: weights_and_area_elements + .. automethod :: with_refinement See :ref:`qbxguts` for some information on the inner workings of this. """ @@ -201,15 +202,21 @@ class QBXLayerPotentialSource(LayerPotentialSource): @memoize_method def with_refinement(self, target_order=None): + """ + :returns: a tuple ``(lpot_src, cnx)``, where ``lpot_src`` is a + :class:`QBXLayerPotentialSource` and ``cnx`` is a + :class:`meshmode.discretization.connection.DiscretizationConnection` + from the originally given to the refined geometry. + """ from pytential.qbx.refinement import QBXLayerPotentialSourceRefiner refiner = QBXLayerPotentialSourceRefiner(self.cl_context) from meshmode.discretization.poly_element import ( InterpolatoryQuadratureSimplexGroupFactory) if target_order is None: target_order = self.density_discr.groups[0].order - lpot, _ = refiner(self, + lpot, connection = refiner(self, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - return lpot + return lpot, connection @property @memoize_method @@ -369,7 +376,8 @@ class QBXLayerPotentialSource(LayerPotentialSource): oversample = partial(self.resampler, queue) if not self.refined_for_global_qbx: - logger.warning( + from warnings import warn + warn( "Executing global QBX without refinement. " "This is unlikely to work.") -- GitLab From 0778c06a57d3498e0375f44da79639417f0345c2 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 8 Dec 2016 23:10:17 -0600 Subject: [PATCH 26/62] Adapt test_identities to use refinement --- test/test_layer_pot.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 177072c7..eb56526d 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -701,6 +701,8 @@ d2 = sym.Derivative() # sample invocation to copy and paste: # 'test_identities(cl._csc, "green", "circ", partial(ellipse, 1), 4, 0)' def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k): + logging.basicConfig(level=logging.INFO) + cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) @@ -753,14 +755,14 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, - # Don't use FMM for now - fmm_order=False) + fmm_order=qbx_order + 20).with_refinement() + density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE -- GitLab From 8d0a8f2a60e13ac2141c9c3b6fcfd82a35dd9393 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Sat, 10 Dec 2016 18:53:22 -0600 Subject: [PATCH 27/62] Refinement: Don't use r_max for checking that the center is closest to its originating panel. Since we don't use Newton to check panel-center distances, the result of the check is the same regardless of whether we use r_max or not. This decreases the size of the area query. --- pytential/qbx/refinement.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 021347ca..a7f0638d 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -113,9 +113,7 @@ TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE = ElementwiseTemplate( # Implements "Algorithm for triggering refinement based on Condition 1" # -# FIXME: There is probably a better way to do this. For instance, since -# we are not using Newton to compute center-panel distances, we can just -# do an area query of size h_k / 2 around each center. +# Does not use r_max as we do not use Newton for checking center-panel closeness. CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( extra_args=r""" /* input */ @@ -128,7 +126,6 @@ CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( particle_id_t *sorted_target_ids, coord_t *panel_sizes, int npanels, - coord_t r_max, /* output */ int *panel_refine_flags, @@ -143,7 +140,7 @@ CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER = AreaQueryElementwiseTemplate( particle_id_t my_panel = bsearch(panel_to_center_starts, npanels + 1, i); ${load_particle("INDEX_FOR_CENTER_PARTICLE(i)", ball_center)} - ${ball_radius} = r_max + panel_sizes[my_panel] / 2; + ${ball_radius} = panel_sizes[my_panel] / 2; """, leaf_found_op=QBX_TREE_MAKO_DEFS + r""" for (particle_id_t panel_idx = box_to_panel_starts[${leaf_box_id}]; @@ -402,7 +399,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): # {{{ refinement triggering def refinement_check_center_is_closest_to_orig_panel(self, queue, tree, - lpot_source, peer_lists, tq_dists, refine_flags, debug, wait_for=None): + lpot_source, peer_lists, refine_flags, debug, wait_for=None): # Avoid generating too many kernels. from pytools import div_ceil max_levels = 10 * div_ceil(tree.nlevels, 10) @@ -422,8 +419,6 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): found_panel_to_refine = cl.array.zeros(queue, 1, np.int32) found_panel_to_refine.finish() - r_max = cl.array.max(tq_dists).get() - evt = knl( *unwrap_args( tree, peer_lists, @@ -436,7 +431,6 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): tree.sorted_target_ids, lpot_source.panel_sizes("npanels"), tree.nqbxpanels, - r_max, refine_flags, found_panel_to_refine, *tree.sources), @@ -751,8 +745,8 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): must_refine |= \ self.refinement_check_center_is_closest_to_orig_panel( - queue, tree, lpot_source, peer_lists, tq_dists, - refine_flags, debug, wait_for) + queue, tree, lpot_source, peer_lists, refine_flags, + debug, wait_for) must_refine |= \ self.refinement_check_center_is_far_from_nonneighbor_panels( -- GitLab From 9270ced193db9737cff726952da938572836c41b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 11 Dec 2016 18:35:57 -0600 Subject: [PATCH 28/62] Fix test_geometry --- test/test_layer_pot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index d014afb1..b746d7dd 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -86,7 +86,7 @@ def test_geometry(ctx_getter): InterpolatoryQuadratureSimplexGroupFactory(order)) import pytential.symbolic.primitives as prim - area_sym = prim.integral(1) + area_sym = prim.integral(2, 1, 1) area = bind(discr, area_sym)(queue) -- GitLab From 3c551cd0a85819b7410a4146f7a61ca9d01b9c3c Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 11 Dec 2016 18:36:23 -0600 Subject: [PATCH 29/62] Fix test_identities: grad(Green) --- test/test_layer_pot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index b746d7dd..b9de6af6 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -743,10 +743,10 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) - 0.5*u_sym, "green_grad": - d1.nabla * d1(sym.S(k_sym, dn_u_sym, - qbx_forced_limit="avg", **knl_kwargs)) - - d2.nabla * d2(sym.D(k_sym, u_sym, - qbx_forced_limit="avg", **knl_kwargs)) + d1.resolve(d1.nabla * d1(sym.S(k_sym, dn_u_sym, + qbx_forced_limit="avg", **knl_kwargs))) + - d2.resolve(d2.nabla * d2(sym.D(k_sym, u_sym, + qbx_forced_limit="avg", **knl_kwargs))) - 0.5*grad_u_sym, # only for k==0: -- GitLab From 3a82d5582a39ebedfc5a9fe3cd9655bcbad298d0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 11 Dec 2016 18:37:47 -0600 Subject: [PATCH 30/62] Add make_sym_surface_mv --- pytential/symbolic/primitives.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 1eb79475..330a4dff 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -44,6 +44,7 @@ __doc__ = """ .. autoclass:: Variable .. autoclass:: make_sym_vector .. autoclass:: make_sym_mv +.. autoclass:: make_sym_surface_mv Functions ^^^^^^^^^ @@ -60,6 +61,7 @@ Discretization properties .. autoclass:: QWeight .. autofunction:: nodes .. autofunction:: parametrization_derivative +.. autofunction:: parametrization_derivative_matrix .. autofunction:: pseudoscalar .. autofunction:: area_element .. autofunction:: sqrt_jac_q_weight @@ -125,6 +127,16 @@ def make_sym_mv(name, num_components): return MultiVector(make_sym_vector(name, num_components)) +def make_sym_surface_mv(name, ambient_dim, dim, where=None): + par_grad = parametrization_derivative_matrix(ambient_dim, dim, where) + + return sum( + var("%s%d" % (name, i)) + * + cse(MultiVector(vec), "tangent%d" % i, cse_scope.DISCRETIZATION) + for i, vec in enumerate(par_grad.T)) + + class Function(var): def __call__(self, operand, *args, **kwargs): # If the call is handed an object array full of operands, @@ -225,7 +237,7 @@ class NumReferenceDerivative(DiscretizationProperty): mapper_method = intern("map_num_reference_derivative") -def parametrization_derivative(ambient_dim, dim, where=None): +def parametrization_derivative_matrix(ambient_dim, dim, where=None): """Return a :class:`pymbolic.geometric_algebra.MultiVector` representing the derivative of the reference-to-global parametrization. """ @@ -238,6 +250,16 @@ def parametrization_derivative(ambient_dim, dim, where=None): NodeCoordinateComponent(i, where), where) + return par_grad + + +def parametrization_derivative(ambient_dim, dim, where=None): + """Return a :class:`pymbolic.geometric_algebra.MultiVector` representing + the derivative of the reference-to-global parametrization. + """ + + par_grad = parametrization_derivative_matrix(ambient_dim, dim, where) + from pytools import product return product(MultiVector(vec) for vec in par_grad.T) -- GitLab From c902cac0c130b97f03ecf147a5353324331aa551 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 11 Dec 2016 18:38:04 -0600 Subject: [PATCH 31/62] Fix sym.{area,mean} --- pytential/symbolic/primitives.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 330a4dff..add36193 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -362,13 +362,16 @@ def ones_vec(dim, where=None): make_obj_array(dim*[Ones(where)])) -def area(where=None): - return cse(integral(Ones(where), where), "area", +def area(ambient_dim, dim, where=None): + return cse(integral(ambient_dim, dim, Ones(where), where), "area", cse_scope.DISCRETIZATION) -def mean(operand, where=None): - return integral(operand, where) / area(where) +def mean(ambient_dim, dim, operand, where=None): + return ( + integral(ambient_dim, dim, operand, where) + / + area(ambient_dim, dim, where)) class IterativeInverse(Expression): -- GitLab From c661fff91dff8366a2558c0718af23873c25072f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 12 Dec 2016 01:53:33 -0600 Subject: [PATCH 32/62] Fix sym.mean() invocations in scalar PDE --- pytential/symbolic/pde/scalar.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pytential/symbolic/pde/scalar.py b/pytential/symbolic/pde/scalar.py index 92295c5d..57231af2 100644 --- a/pytential/symbolic/pde/scalar.py +++ b/pytential/symbolic/pde/scalar.py @@ -39,7 +39,6 @@ __doc__ = """ from pytential import sym from pytential.symbolic.primitives import ( cse, - Ones, mean, sqrt_jac_q_weight, QWeight, area_element) import numpy as np from collections import namedtuple @@ -153,7 +152,8 @@ class DirichletOperator(L2WeightedPDEOperator): # See Hackbusch, http://books.google.com/books?id=Ssnf7SZB0ZMC # Theorem 8.2.18b - ones_contribution = Ones() * mean(inv_sqrt_w_u) + amb_dim = self.kernel.dim + ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) else: ones_contribution = 0 @@ -272,7 +272,8 @@ class NeumannOperator(L2WeightedPDEOperator): # to the desired solution separately. As is, this operator # returns a mean that is not well-specified. - ones_contribution = Ones() * mean(inv_sqrt_w_u) + amb_dim = self.kernel.dim + ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) else: ones_contribution = 0 -- GitLab From 40c63987e9581c02f8a6726a0f90f0962ba9d99e Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 12 Dec 2016 13:28:35 -0600 Subject: [PATCH 33/62] Revert "Merge branch 'master' into gqbx-and-immed" This reverts commit f3db476b02955c93d10799c915e32056a538cf11, reversing changes made to 81ccb4a705a4fa4ce86a975b65951f8ed9919059. --- pytential/symbolic/pde/scalar.py | 6 ++---- pytential/symbolic/primitives.py | 5 ++--- test/test_layer_pot.py | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pytential/symbolic/pde/scalar.py b/pytential/symbolic/pde/scalar.py index ff5a22e2..57231af2 100644 --- a/pytential/symbolic/pde/scalar.py +++ b/pytential/symbolic/pde/scalar.py @@ -153,8 +153,7 @@ class DirichletOperator(L2WeightedPDEOperator): # Theorem 8.2.18b amb_dim = self.kernel.dim - ones_contribution = ( - sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u)) + ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) else: ones_contribution = 0 @@ -274,8 +273,7 @@ class NeumannOperator(L2WeightedPDEOperator): # returns a mean that is not well-specified. amb_dim = self.kernel.dim - ones_contribution = ( - sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u)) + ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) else: ones_contribution = 0 diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index 5e8ff991..add36193 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -396,10 +396,9 @@ class IterativeInverse(Expression): class Derivative(DerivativeBase): - @staticmethod - def resolve(expr): + def resolve(self, expr): from pytential.symbolic.mappers import DerivativeBinder - return DerivativeBinder()(expr) + return DerivativeBinder(self.my_id)(expr) # {{{ potentials diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index a6f39db2..b9de6af6 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -743,9 +743,9 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) - 0.5*u_sym, "green_grad": - d1.resolve(d1.dnabla(2) * d1(sym.S(k_sym, dn_u_sym, + d1.resolve(d1.nabla * d1(sym.S(k_sym, dn_u_sym, qbx_forced_limit="avg", **knl_kwargs))) - - d2.resolve(d2.dnabla(2) * d2(sym.D(k_sym, u_sym, + - d2.resolve(d2.nabla * d2(sym.D(k_sym, u_sym, qbx_forced_limit="avg", **knl_kwargs))) - 0.5*grad_u_sym, -- GitLab From ae017ba6f8990986c62cfc95358a80d0fb1faa98 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 12 Dec 2016 13:29:34 -0600 Subject: [PATCH 34/62] Revert "Revert "Merge branch 'master' into gqbx-and-immed"" This reverts commit 40c63987e9581c02f8a6726a0f90f0962ba9d99e. --- pytential/symbolic/pde/scalar.py | 6 ++++-- pytential/symbolic/primitives.py | 5 +++-- test/test_layer_pot.py | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pytential/symbolic/pde/scalar.py b/pytential/symbolic/pde/scalar.py index 57231af2..ff5a22e2 100644 --- a/pytential/symbolic/pde/scalar.py +++ b/pytential/symbolic/pde/scalar.py @@ -153,7 +153,8 @@ class DirichletOperator(L2WeightedPDEOperator): # Theorem 8.2.18b amb_dim = self.kernel.dim - ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) + ones_contribution = ( + sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u)) else: ones_contribution = 0 @@ -273,7 +274,8 @@ class NeumannOperator(L2WeightedPDEOperator): # returns a mean that is not well-specified. amb_dim = self.kernel.dim - ones_contribution = sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u) + ones_contribution = ( + sym.Ones() * sym.mean(amb_dim, amb_dim-1, inv_sqrt_w_u)) else: ones_contribution = 0 diff --git a/pytential/symbolic/primitives.py b/pytential/symbolic/primitives.py index add36193..5e8ff991 100644 --- a/pytential/symbolic/primitives.py +++ b/pytential/symbolic/primitives.py @@ -396,9 +396,10 @@ class IterativeInverse(Expression): class Derivative(DerivativeBase): - def resolve(self, expr): + @staticmethod + def resolve(expr): from pytential.symbolic.mappers import DerivativeBinder - return DerivativeBinder(self.my_id)(expr) + return DerivativeBinder()(expr) # {{{ potentials diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index b9de6af6..a6f39db2 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -743,9 +743,9 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) - 0.5*u_sym, "green_grad": - d1.resolve(d1.nabla * d1(sym.S(k_sym, dn_u_sym, + d1.resolve(d1.dnabla(2) * d1(sym.S(k_sym, dn_u_sym, qbx_forced_limit="avg", **knl_kwargs))) - - d2.resolve(d2.nabla * d2(sym.D(k_sym, u_sym, + - d2.resolve(d2.dnabla(2) * d2(sym.D(k_sym, u_sym, qbx_forced_limit="avg", **knl_kwargs))) - 0.5*grad_u_sym, -- GitLab From 411d9cd73dd0292186b1350700a9aeb80df4f9cc Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 12 Dec 2016 14:13:40 -0600 Subject: [PATCH 35/62] Add comment section title --- pytential/qbx/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index e3f7255f..26754fe1 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -43,7 +43,7 @@ logger = logging.getLogger(__name__) QBX_CENTER_MATCH_THRESHOLD = 1 + 0.05 -# {{{ +# {{{ docs __doc__ = """ -- GitLab From b1046255bc4ad48f9f36985e947085897c36e982 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 12 Dec 2016 14:14:16 -0600 Subject: [PATCH 36/62] Fix primitive use in center/panel size computation --- pytential/qbx/__init__.py | 10 +++++++--- test/test_layer_pot.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index dfa755eb..c20607f7 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -308,7 +308,11 @@ class QBXLayerPotentialSource(LayerPotentialSource): with cl.CommandQueue(self.cl_context) as queue: from pytential import bind, sym - ds = bind(discr, sym.area_element() * sym.QWeight())(queue) + ds = bind( + discr, + sym.area_element(ambient_dim=discr.ambient_dim, dim=discr.dim) + * sym.QWeight() + )(queue) panel_sizes = cl.array.empty( queue, discr.nnodes if last_dim_length in ("nsources", "ncenters") @@ -335,8 +339,8 @@ class QBXLayerPotentialSource(LayerPotentialSource): from pytential import sym, bind with cl.CommandQueue(self.cl_context) as queue: - nodes = bind(self.density_discr, sym.nodes(adim, dim))(queue) - normals = bind(self.density_discr, sym.normal(adim, dim))(queue) + nodes = bind(self.density_discr, sym.nodes(adim))(queue) + normals = bind(self.density_discr, sym.normal(adim, dim=dim))(queue) panel_sizes = self.panel_sizes().with_queue(queue) return (nodes + normals * sign * panel_sizes / 2).as_vector(np.object) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index a6f39db2..1eebe256 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -780,7 +780,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, - fmm_order=qbx_order + 20).with_refinement() + fmm_order=qbx_order + 5).with_refinement() density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE -- GitLab From 0730042663d23169385a6719ac9499e64cd3f223 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 12 Dec 2016 15:46:09 -0600 Subject: [PATCH 37/62] Add an encoding declaration (thanks to Natalie for pointing this out). --- pytential/qbx/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index c20607f7..dda53a9c 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import division, absolute_import __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" -- GitLab From 00700bfdd5591df21385680f3fdc6ad8010fd895 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 13 Dec 2016 01:18:17 -0600 Subject: [PATCH 38/62] Add QBXLayerPotentialSource.copy(), allow setting target stick-out factor --- pytential/qbx/__init__.py | 56 +++++++++++++++++++++++++++++++------ pytential/qbx/geometry.py | 17 +++++------ pytential/qbx/refinement.py | 9 ++---- 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index c20607f7..9767f2fd 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -135,10 +135,14 @@ class QBXLayerPotentialSource(LayerPotentialSource): See :ref:`qbxguts` for some information on the inner workings of this. """ def __init__(self, density_discr, fine_order, - qbx_order=None, fmm_order=None, + qbx_order=None, + fmm_order=None, fmm_level_to_order=None, - # FIXME set debug=False once everything works - real_dtype=np.float64, debug=True, + target_stick_out_factor=1e-10, + + # begin undocumented arguments + # FIXME default debug=False once everything works + debug=True, refined_for_global_qbx=False, performance_data_file=None): """ @@ -153,7 +157,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): fmm_order = qbx_order + 1 if fmm_order is not None and fmm_level_to_order is not None: - raise TypeError("may not specify both fmm_order an fmm_level_to_order") + raise TypeError("may not specify both fmm_order and fmm_level_to_order") if fmm_level_to_order is None: if fmm_order is False: @@ -162,14 +166,46 @@ class QBXLayerPotentialSource(LayerPotentialSource): def fmm_level_to_order(level): return fmm_order - self.refined_for_global_qbx = refined_for_global_qbx self.fine_order = fine_order self.qbx_order = qbx_order self.density_discr = density_discr self.fmm_level_to_order = fmm_level_to_order + self.target_stick_out_factor = target_stick_out_factor + self.debug = debug + self.refined_for_global_qbx = refined_for_global_qbx self.performance_data_file = performance_data_file + def copy( + self, + density_discr=None, + fine_order=None, + qbx_order=None, + fmm_level_to_order=None, + target_stick_out_factor=None, + + debug=None, + refined_for_global_qbx=None, + ): + # FIXME Could/should share wrangler and geometry kernels + # if no relevant changes have been made. + return QBXLayerPotentialSource( + density_discr=density_discr or self.density_discr, + fine_order=( + fine_order if fine_order is not None else self.fine_order), + qbx_order=qbx_order if qbx_order is not None else self.qbx_order, + fmm_level_to_order=( + fmm_level_to_order or self.fmm_level_to_order), + target_stick_out_factor=( + target_stick_out_factor or self.target_stick_out_factor), + + debug=( + debug if debug is not None else self.debug), + refined_for_global_qbx=( + refined_for_global_qbx if refined_for_global_qbx is not None + else self.refined_for_global_qbx), + performance_data_file=self.performance_data_file) + @property @memoize_method def fine_density_discr(self): @@ -423,7 +459,9 @@ class QBXLayerPotentialSource(LayerPotentialSource): from pytential.qbx.geometry import QBXFMMGeometryData return QBXFMMGeometryData(self.qbx_fmm_code_getter, - self, target_discrs_and_qbx_sides, debug=self.debug) + self, target_discrs_and_qbx_sides, + target_stick_out_factor=self.target_stick_out_factor, + debug=self.debug) # {{{ fmm-based execution @@ -701,9 +739,9 @@ class QBXLayerPotentialSource(LayerPotentialSource): qbx_forced_limit = 0 geo_data = self.qbx_fmm_geometry_data( - target_discrs_and_qbx_sides=[ - (target_discr, qbx_forced_limit) - ]) + target_discrs_and_qbx_sides=( + (target_discr, qbx_forced_limit), + )) # center_info is independent of targets center_info = geo_data.center_info() diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 26754fe1..945c5d61 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -1,7 +1,4 @@ -from __future__ import division -from __future__ import absolute_import -from __future__ import print_function -from six.moves import zip +from __future__ import division, absolute_import, print_function __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" @@ -25,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from six.moves import zip import numpy as np import pyopencl as cl @@ -38,10 +36,6 @@ from cgen import Enum import logging logger = logging.getLogger(__name__) -# Targets match centers if they are within the center's circle, which, for matching -# purposes, is enlarged by this fraction. -QBX_CENTER_MATCH_THRESHOLD = 1 + 0.05 - # {{{ docs @@ -540,7 +534,8 @@ class QBXFMMGeometryData(object): """ def __init__(self, code_getter, lpot_source, - target_discrs_and_qbx_sides, debug): + target_discrs_and_qbx_sides, + target_stick_out_factor, debug): """ .. rubric:: Constructor arguments @@ -555,6 +550,7 @@ class QBXFMMGeometryData(object): self.lpot_source = lpot_source self.target_discrs_and_qbx_sides = \ target_discrs_and_qbx_sides + self.target_stick_out_factor = target_stick_out_factor self.debug = debug @property @@ -847,7 +843,8 @@ class QBXFMMGeometryData(object): # FIXME: try block... tgt_assoc_result = tgt_assoc(self.lpot_source, - self.target_discrs_and_qbx_sides) + self.target_discrs_and_qbx_sides, + stick_out_factor=self.target_stick_out_factor) tgt_info = self.target_info() center_info = self.center_info() diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index a7f0638d..bc24e7f4 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -663,12 +663,9 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): new_density_discr = conn.to_discr - new_lpot_source = QBXLayerPotentialSource( - new_density_discr, lpot_source.fine_order, - qbx_order=lpot_source.qbx_order, - fmm_level_to_order=lpot_source.fmm_level_to_order, - real_dtype=lpot_source.real_dtype, debug=debug, - refined_for_global_qbx=True) + new_lpot_source = lpot_source.copy( + density_discr=new_density_discr, + refined_for_global_qbx=True) return new_lpot_source, conn -- GitLab From 0967b85ca4d886e33ec251136c914e3ed04b09ed Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Tue, 13 Dec 2016 01:21:22 -0600 Subject: [PATCH 39/62] Adapt 2d layer potential example to new global QBX FMM --- examples/layerpot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/layerpot.py b/examples/layerpot.py index 11c7a23e..ef1c95ed 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -32,7 +32,7 @@ if k: kernel = HelmholtzKernel(2) kernel_kwargs = {"k": sym.var("k")} else: - kernel = LaplaceKernel() + kernel = LaplaceKernel(2) kernel_kwargs = {} #kernel = OneKernel() @@ -53,7 +53,8 @@ density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, - fmm_order=False) + fmm_order=qbx_order+3, + target_stick_out_factor=0.005) nodes = density_discr.nodes().with_queue(queue) @@ -77,10 +78,12 @@ if isinstance(kernel, HelmholtzKernel): bound_bdry_op = bind(qbx, op) #mlab.figure(bgcolor=(1, 1, 1)) if 1: - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=400) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) from pytential.target import PointsTarget + + targets_dev = cl.array.to_device(queue, fplot.points) fld_in_vol = bind( - (qbx, PointsTarget(fplot.points)), + (qbx, PointsTarget(targets_dev)), op)(queue, sigma=sigma, k=k).get() if enable_mayavi: -- GitLab From 6af34de69f3b7439de454149b6ae4788813f2e05 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 09:59:25 -0600 Subject: [PATCH 40/62] Refinement: Switch from explicit constructor to .copy() --- pytential/qbx/refinement.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index bc24e7f4..47e6f54d 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -32,7 +32,6 @@ import numpy as np import pyopencl as cl from pytools import memoize_method -from pytential.qbx import QBXLayerPotentialSource from pytential.qbx.utils import DiscrPlotterMixin from boxtree.area_query import AreaQueryElementwiseTemplate from pyopencl.elementwise import ElementwiseTemplate @@ -697,11 +696,8 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): refiner = Refiner(lpot_source.density_discr.mesh) connections = [] - lpot_source = QBXLayerPotentialSource( - lpot_source.density_discr, lpot_source.fine_order, - qbx_order=lpot_source.qbx_order, - fmm_level_to_order=lpot_source.fmm_level_to_order, - real_dtype=lpot_source.real_dtype, debug=debug, + lpot_source = lpot_source.copy( + debug=debug, refined_for_global_qbx=True) with cl.CommandQueue(self.context) as queue: -- GitLab From d4d382e38e73e69ac08259236fb103f046659458 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 10:02:54 -0600 Subject: [PATCH 41/62] Adapt Helmholtz-Dirichlet example to new state of affairs --- examples/helmholtz-dirichlet.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index 4c0cd80d..309c5fc8 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -32,19 +32,20 @@ def main(): from functools import partial mesh = make_curve_mesh( - partial(ellipse, 3), + partial(ellipse, 1), np.linspace(0, 1, nelements+1), mesh_order) - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import QBXLayerPotentialSource - qbx = QBXLayerPotentialSource( - density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, + qbx, _ = QBXLayerPotentialSource( + pre_density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order - ) + ).with_refinement() + density_discr = qbx. density_discr # {{{ describe bvp @@ -54,7 +55,7 @@ def main(): cse = sym.cse sigma_sym = sym.var("sigma") - sqrt_w = sym.sqrt_jac_q_weight() + sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = cse(sigma_sym/sqrt_w) # Brakhage-Werner parameter @@ -102,10 +103,15 @@ def main(): - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k"))) from sumpy.visualization import FieldPlotter - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=400) + + targets = cl.array.to_device(queue, fplot.points) + + qbx_stick_out = qbx.copy(target_stick_out_factor=0.05) + from pytential.target import PointsTarget fld_in_vol = bind( - (qbx, PointsTarget(fplot.points)), + (qbx_stick_out, PointsTarget(targets)), representation_sym)(queue, sigma=sigma, k=k).get() #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) -- GitLab From 3e2b989001618d550ab630d15d9e150e255dc756 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 18:20:25 -0600 Subject: [PATCH 42/62] Minor doc fix --- pytential/target.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/target.py b/pytential/target.py index a4f199f0..e677bdb7 100644 --- a/pytential/target.py +++ b/pytential/target.py @@ -48,7 +48,7 @@ class TargetBase(object): class PointsTarget(TargetBase): """The point of this class is to act as a container for some target points - while presenting enough of the :class:`pytential.discretization.Discretization` + while presenting enough of the :class:`meshmode.discretization.Discretization` interface to not necessitate a lot of special cases in that code path. """ -- GitLab From 265eff846f218859d94adb0b07a6a48efdc4aeb0 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 18:23:50 -0600 Subject: [PATCH 43/62] Target association: In case of failed targets, return flag array in exception --- pytential/qbx/__init__.py | 9 +++++++++ pytential/qbx/target_assoc.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index d4498ba1..7a5cb276 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -30,14 +30,18 @@ import numpy as np from pytools import memoize_method from meshmode.discretization import Discretization from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory +from pytential.qbx.target_assoc import QBXTargetAssociationFailedException import pyopencl as cl import logging logger = logging.getLogger(__name__) + __doc__ = """ .. autoclass:: QBXLayerPotentialSource + +.. autoclass:: QBXTargetAssociationFailedException """ @@ -795,4 +799,9 @@ class QBXLayerPotentialSource(LayerPotentialSource): # }}} +__all__ = ( + QBXLayerPotentialSource, + QBXTargetAssociationFailedException, + ) + # vim: fdm=marker diff --git a/pytential/qbx/target_assoc.py b/pytential/qbx/target_assoc.py index deca3b7d..8890d992 100644 --- a/pytential/qbx/target_assoc.py +++ b/pytential/qbx/target_assoc.py @@ -301,9 +301,14 @@ QBX_FAILED_TARGET_ASSOCIATION_REFINER = AreaQueryElementwiseTemplate( class QBXTargetAssociationFailedException(Exception): """ .. attribute:: refine_flags + .. attribute:: failed_target_flags """ - def __init__(self, refine_flags): + def __init__(self, refine_flags, failed_target_flags): self.refine_flags = refine_flags + self.failed_target_flags = failed_target_flags + + def __repr__(self): + return "<%s>" % type(self).__name__ class QBXTargetAssociation(DeviceDataRecord): @@ -694,7 +699,8 @@ class QBXTargetAssociator(DiscrPlotterMixin): refine_flags, debug) assert have_panel_to_refine raise QBXTargetAssociationFailedException( - refine_flags.with_queue(None)) + refine_flags=refine_flags.with_queue(None), + failed_target_flags=center_not_found.with_queue(None)) return target_assoc.with_queue(None) -- GitLab From b1b231662362071f054913047fff73742c257bf5 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 18:33:04 -0600 Subject: [PATCH 44/62] Tweak Helmholtz-Dirichlet example --- examples/helmholtz-dirichlet.py | 88 +++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index 309c5fc8..bb06c259 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -10,13 +10,13 @@ from pytential import bind, sym, norm # noqa # {{{ set some constants for use below -nelements = 50 -mesh_order = 3 -bdry_quad_order = 10 +nelements = 20 +bdry_quad_order = 4 +mesh_order = bdry_quad_order +qbx_order = bdry_quad_order bdry_ovsmp_quad_order = 4*bdry_quad_order -qbx_order = 4 -fmm_order = 8 -k = 15 +fmm_order = 25 +k = 25 # }}} @@ -31,21 +31,48 @@ def main(): from meshmode.mesh.generation import ellipse, make_curve_mesh from functools import partial - mesh = make_curve_mesh( - partial(ellipse, 1), - np.linspace(0, 1, nelements+1), - mesh_order) + if 0: + mesh = make_curve_mesh( + partial(ellipse, 1), + np.linspace(0, 1, nelements+1), + mesh_order) + else: + base_mesh = make_curve_mesh( + partial(ellipse, 1), + np.linspace(0, 1, nelements+1), + mesh_order) + + from meshmode.mesh.processing import affine_map, merge_disjoint_meshes + nx = 5 + ny = 5 + dx = 2 / nx + meshes = [ + affine_map( + base_mesh, + A=np.diag([dx*0.25, dx*0.25]), + b=np.array([dx*(ix-nx/2), dx*(iy-ny/2)])) + for ix in range(nx) + for iy in range(ny)] + + mesh = merge_disjoint_meshes(meshes, single_group=True) + + if 0: + from meshmode.mesh.visualization import draw_curve + draw_curve(mesh) + import matplotlib.pyplot as plt + plt.show() pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) - from pytential.qbx import QBXLayerPotentialSource + from pytential.qbx import ( + QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx, _ = QBXLayerPotentialSource( pre_density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order ).with_refinement() - density_discr = qbx. density_discr + density_discr = qbx.density_discr # {{{ describe bvp @@ -63,7 +90,7 @@ def main(): # -1 for interior Dirichlet # +1 for exterior Dirichlet - loc_sign = -1 + loc_sign = +1 bdry_op_sym = (-loc_sign*0.5*sigma_sym + sqrt_w*( @@ -86,33 +113,44 @@ def main(): bvp_rhs = bind(qbx, sqrt_w*sym.var("bc"))(queue, bc=bc) from pytential.solve import gmres - gmres_result = gmres( - bound_op.scipy_op(queue, "sigma", dtype=np.complex128, k=k), - bvp_rhs, tol=1e-14, progress=True, - stall_iterations=0, - hard_failure=True) + # gmres_result = gmres( + # bound_op.scipy_op(queue, "sigma", dtype=np.complex128, k=k), + # bvp_rhs, tol=1e-8, progress=True, + # stall_iterations=0, + # hard_failure=True) # }}} # {{{ postprocess/visualize - sigma = gmres_result.solution + #sigma = gmres_result.solution + sigma = bc + repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=None) representation_sym = ( - alpha*sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k")) - - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k"))) + alpha*sym.S(kernel, inv_sqrt_w_sigma, **repr_kwargs) + - sym.D(kernel, inv_sqrt_w_sigma, **repr_kwargs)) from sumpy.visualization import FieldPlotter - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=400) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) targets = cl.array.to_device(queue, fplot.points) qbx_stick_out = qbx.copy(target_stick_out_factor=0.05) from pytential.target import PointsTarget - fld_in_vol = bind( - (qbx_stick_out, PointsTarget(targets)), - representation_sym)(queue, sigma=sigma, k=k).get() + try: + fld_in_vol = bind( + (qbx_stick_out, PointsTarget(targets)), + representation_sym)(queue, sigma=sigma, k=k).get() + except QBXTargetAssociationFailedException as e: + fplot.write_vtk_file( + "failed-targets.vts", + [ + ("failed", e.failed_target_flags.get(queue)) + ] + ) + raise #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( -- GitLab From 756a66107481310b0530c8ec488b3b9070164013 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Wed, 14 Dec 2016 18:35:09 -0600 Subject: [PATCH 45/62] Helmholtz-Dirichlet example: Reenable the solve --- examples/helmholtz-dirichlet.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index bb06c259..c1fcd373 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -113,18 +113,17 @@ def main(): bvp_rhs = bind(qbx, sqrt_w*sym.var("bc"))(queue, bc=bc) from pytential.solve import gmres - # gmres_result = gmres( - # bound_op.scipy_op(queue, "sigma", dtype=np.complex128, k=k), - # bvp_rhs, tol=1e-8, progress=True, - # stall_iterations=0, - # hard_failure=True) + gmres_result = gmres( + bound_op.scipy_op(queue, "sigma", dtype=np.complex128, k=k), + bvp_rhs, tol=1e-8, progress=True, + stall_iterations=0, + hard_failure=True) # }}} # {{{ postprocess/visualize - #sigma = gmres_result.solution - sigma = bc + sigma = gmres_result.solution repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=None) representation_sym = ( -- GitLab From 850ca000083bef31b518e0db5e5e2ce4d7199334 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 15 Dec 2016 11:19:33 -0600 Subject: [PATCH 46/62] Add script for scaling study --- examples/scaling-study.py | 205 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 examples/scaling-study.py diff --git a/examples/scaling-study.py b/examples/scaling-study.py new file mode 100644 index 00000000..cf8cf166 --- /dev/null +++ b/examples/scaling-study.py @@ -0,0 +1,205 @@ +import numpy as np +import pyopencl as cl +import pyopencl.clmath # noqa + +from meshmode.discretization import Discretization +from meshmode.discretization.poly_element import \ + InterpolatoryQuadratureSimplexGroupFactory + +from pytential import bind, sym, norm # noqa +from pytential.target import PointsTarget + +# {{{ set some constants for use below + +nelements = 20 +bdry_quad_order = 4 +mesh_order = bdry_quad_order +qbx_order = bdry_quad_order +bdry_ovsmp_quad_order = 4*bdry_quad_order +fmm_order = 25 +k = 25 + +# }}} + + +def make_mesh(nx, ny): + from meshmode.mesh.generation import ellipse, make_curve_mesh + from functools import partial + + base_mesh = make_curve_mesh( + partial(ellipse, 1), + np.linspace(0, 1, nelements+1), + mesh_order) + + from meshmode.mesh.processing import affine_map, merge_disjoint_meshes + dx = 2 / nx + meshes = [ + affine_map( + base_mesh, + A=np.diag([dx*0.25, dx*0.25]), + b=np.array([dx*(ix-nx/2), dx*(iy-ny/2)])) + for ix in range(nx) + for iy in range(ny)] + + mesh = merge_disjoint_meshes(meshes, single_group=True) + + if 0: + from meshmode.mesh.visualization import draw_curve + draw_curve(mesh) + import matplotlib.pyplot as plt + plt.show() + + return mesh + + +def timing_run(nx, ny): + import logging + logging.basicConfig(level=logging.INFO) + + cl_ctx = cl.create_some_context() + queue = cl.CommandQueue(cl_ctx) + + mesh = make_mesh(nx=nx, ny=ny) + + density_discr = Discretization( + cl_ctx, mesh, + InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) + + from pytential.qbx import ( + QBXLayerPotentialSource, QBXTargetAssociationFailedException) + qbx = QBXLayerPotentialSource( + density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, + fmm_order=fmm_order + ) + + # {{{ describe bvp + + from sumpy.kernel import HelmholtzKernel + kernel = HelmholtzKernel(2) + + cse = sym.cse + + sigma_sym = sym.var("sigma") + sqrt_w = sym.sqrt_jac_q_weight(2) + inv_sqrt_w_sigma = cse(sigma_sym/sqrt_w) + + # Brakhage-Werner parameter + alpha = 1j + + # -1 for interior Dirichlet + # +1 for exterior Dirichlet + loc_sign = +1 + + bdry_op_sym = (-loc_sign*0.5*sigma_sym + + sqrt_w*( + alpha*sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k")) + - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k")) + )) + + # }}} + + bound_op = bind(qbx, bdry_op_sym) + + # {{{ fix rhs and solve + + mode_nr = 3 + nodes = density_discr.nodes().with_queue(queue) + angle = cl.clmath.atan2(nodes[1], nodes[0]) + + sigma = cl.clmath.cos(mode_nr*angle) + + # }}} + + # {{{ postprocess/visualize + + repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=+1) + + sym_op = sym.S(kernel, sym.var("sigma"), **repr_kwargs) + bound_op = bind(qbx, sym_op) + + print("FMM WARM-UP RUN 1: %d elements" % mesh.nelements) + bound_op(queue, sigma=sigma, k=k) + print("FMM WARM-UP RUN 2: %d elements" % mesh.nelements) + bound_op(queue, sigma=sigma, k=k) + queue.finish() + print("FMM TIMING RUN: %d elements" % mesh.nelements) + + from time import time + t_start = time() + + bound_op(queue, sigma=sigma, k=k) + queue.finish() + elapsed = time()-t_start + + print("FMM TIMING RUN DONE: %d elements -> %g s" + % (mesh.nelements, elapsed)) + + return (mesh.nelements, elapsed) + + if 0: + from sumpy.visualization import FieldPlotter + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) + + targets = cl.array.to_device(queue, fplot.points) + + qbx_stick_out = qbx.copy(target_stick_out_factor=0.05) + + indicator_qbx = qbx_stick_out.copy( + fmm_level_to_order=lambda lev: 7, qbx_order=2) + + ones_density = density_discr.zeros(queue) + ones_density.fill(1) + indicator = bind( + (indicator_qbx, PointsTarget(targets)), + sym_op)( + queue, sigma=ones_density).get() + + try: + fld_in_vol = bind( + (qbx_stick_out, PointsTarget(targets)), + sym_op)(queue, sigma=sigma, k=k).get() + except QBXTargetAssociationFailedException as e: + fplot.write_vtk_file( + "failed-targets.vts", + [ + ("failed", e.failed_target_flags.get(queue)) + ] + ) + raise + + #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) + fplot.write_vtk_file( + "potential.vts", + [ + ("potential", fld_in_vol), + ("indicator", indicator) + ] + ) + + # }}} + + +if __name__ == "__main__": + results = [] + for nx, ny in [ + (3, 3), + (3, 4), + (4, 4), + (4, 5), + (5, 5), + (5, 6), + (6, 6), + (6, 7), + (7, 7), + (7, 8), + (8, 8), + (8, 9), + (9, 9), + (9, 10), + (10, 10), + ]: + + results.append(timing_run(nx, ny)) + + for r in results: + print(r) -- GitLab From db67f9276e080eab353621dbd29dc3f513348c2b Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 15 Dec 2016 11:20:08 -0600 Subject: [PATCH 47/62] Switch Helmholtz-Dirichlet example to plane wave scattering --- examples/helmholtz-dirichlet.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/examples/helmholtz-dirichlet.py b/examples/helmholtz-dirichlet.py index c1fcd373..f385215b 100644 --- a/examples/helmholtz-dirichlet.py +++ b/examples/helmholtz-dirichlet.py @@ -1,4 +1,5 @@ import numpy as np +import numpy.linalg as la import pyopencl as cl import pyopencl.clmath # noqa @@ -7,6 +8,7 @@ from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from pytential import bind, sym, norm # noqa +from pytential.target import PointsTarget # {{{ set some constants for use below @@ -76,7 +78,7 @@ def main(): # {{{ describe bvp - from sumpy.kernel import HelmholtzKernel + from sumpy.kernel import LaplaceKernel, HelmholtzKernel kernel = HelmholtzKernel(2) cse = sym.cse @@ -104,11 +106,15 @@ def main(): # {{{ fix rhs and solve - mode_nr = 3 nodes = density_discr.nodes().with_queue(queue) - angle = cl.clmath.atan2(nodes[1], nodes[0]) + k_vec = np.array([2, 1]) + k_vec = k * k_vec / la.norm(k_vec, 2) - bc = cl.clmath.cos(mode_nr*angle) + def u_incoming_func(x): + return cl.clmath.exp( + 1j * (x[0] * k_vec[0] + x[1] * k_vec[1])) + + bc = -u_incoming_func(nodes) bvp_rhs = bind(qbx, sqrt_w*sym.var("bc"))(queue, bc=bc) @@ -131,13 +137,24 @@ def main(): - sym.D(kernel, inv_sqrt_w_sigma, **repr_kwargs)) from sumpy.visualization import FieldPlotter - fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1000) + fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) targets = cl.array.to_device(queue, fplot.points) + u_incoming = u_incoming_func(targets) + qbx_stick_out = qbx.copy(target_stick_out_factor=0.05) - from pytential.target import PointsTarget + indicator_qbx = qbx_stick_out.copy( + fmm_level_to_order=lambda lev: 7, qbx_order=2) + + ones_density = density_discr.zeros(queue) + ones_density.fill(1) + indicator = bind( + (indicator_qbx, PointsTarget(targets)), + sym.D(LaplaceKernel(2), sym.var("sigma")))( + queue, sigma=ones_density).get() + try: fld_in_vol = bind( (qbx_stick_out, PointsTarget(targets)), @@ -155,7 +172,9 @@ def main(): fplot.write_vtk_file( "potential.vts", [ - ("potential", fld_in_vol) + ("potential", fld_in_vol), + ("indicator", indicator), + ("u_incoming", u_incoming.get()), ] ) -- GitLab From c4ff2970ce582616328dcec99e64e7d5b8d12450 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 27 Dec 2016 05:02:07 -0600 Subject: [PATCH 48/62] FMM tree build: refine only on sources (#3). --- pytential/qbx/geometry.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 945c5d61..60cff3f2 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -690,13 +690,27 @@ class QBXFMMGeometryData(object): target_info = self.target_info() with cl.CommandQueue(self.cl_context) as queue: - - # TODO: build refine weights. - + nsources = lpot_src.fine_density_discr.nnodes + nparticles = nsources + target_info.ntargets + + refine_weights = cl.array.zeros(queue, nparticles, dtype=np.int32) + refine_weights[:nsources] = 1 + refine_weights.finish() + + # NOTE: max_leaf_refine_weight has an impact on accuracy. + # For instance, if a panel contains 64*4 = 256 nodes, then + # a box will contain at most half a panel, meaning that + # its width will be on the order h/2, which means many + # QBX disks (diameter h) will be forced to cross boxes. + # So we set max_leaf_refine weight comfortably large + # to avoid having too many disks overlap more than one box. + # + # FIXME: Should investigate this further. tree, _ = code_getter.build_tree(queue, particles=lpot_src.fine_density_discr.nodes(), targets=target_info.targets, - max_particles_in_box=30, + max_leaf_refine_weight=384, + refine_weights=refine_weights, debug=self.debug, kind="adaptive-level-restricted") -- GitLab From 78573e8b08ef9c976c44898fae3db811c180f1fc Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 27 Dec 2016 06:01:41 -0600 Subject: [PATCH 49/62] Layerpot example: refine for global QBX. --- examples/layerpot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/layerpot.py b/examples/layerpot.py index ef1c95ed..d08659a5 100644 --- a/examples/layerpot.py +++ b/examples/layerpot.py @@ -49,12 +49,14 @@ from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory -density_discr = Discretization( +pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) -qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, +qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, fmm_order=qbx_order+3, - target_stick_out_factor=0.005) + target_stick_out_factor=0.005).with_refinement() + +density_discr = qbx.density_discr nodes = density_discr.nodes().with_queue(queue) -- GitLab From 753e5209eeb14a3011cf59f51dbf25b63dc47469 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Tue, 27 Dec 2016 06:05:05 -0600 Subject: [PATCH 50/62] Fix comment, change refine weight to 256. --- pytential/qbx/geometry.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 60cff3f2..9db08a56 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -699,9 +699,10 @@ class QBXFMMGeometryData(object): # NOTE: max_leaf_refine_weight has an impact on accuracy. # For instance, if a panel contains 64*4 = 256 nodes, then - # a box will contain at most half a panel, meaning that - # its width will be on the order h/2, which means many - # QBX disks (diameter h) will be forced to cross boxes. + # a box with 128 sources will contain at most half a + # panel, meaning that its width will be on the order h/2, + # which means many QBX disks (diameter h) will be forced + # to cross boxes. # So we set max_leaf_refine weight comfortably large # to avoid having too many disks overlap more than one box. # @@ -709,7 +710,7 @@ class QBXFMMGeometryData(object): tree, _ = code_getter.build_tree(queue, particles=lpot_src.fine_density_discr.nodes(), targets=target_info.targets, - max_leaf_refine_weight=384, + max_leaf_refine_weight=256, refine_weights=refine_weights, debug=self.debug, kind="adaptive-level-restricted") -- GitLab From 5721132f7697df55356cf5320d869159e0ef45d7 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 29 Dec 2016 22:41:12 -0600 Subject: [PATCH 51/62] Fix pyopencl warning/error. --- pytential/qbx/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 9db08a56..d91c6ff2 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -898,7 +898,7 @@ class QBXFMMGeometryData(object): tree_ttc, filtered_tree_ttc, filtered_target_ids, count, queue=queue, size=len(tree_ttc)) - count = count.get() + count = np.asscalar(count.get()) filtered_tree_ttc = filtered_tree_ttc[:count] filtered_target_ids = filtered_target_ids[:count].copy() -- GitLab From e664f900c4b8daa4d9740d58ed75253dd469f012 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 29 Dec 2016 22:58:31 -0600 Subject: [PATCH 52/62] Un-bitrot test_global_qbx. --- test/test_global_qbx.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test_global_qbx.py b/test/test_global_qbx.py index 1665a2ec..d7e51451 100644 --- a/test/test_global_qbx.py +++ b/test/test_global_qbx.py @@ -227,8 +227,9 @@ def test_target_association(ctx_getter, curve_name, curve_f, nelements): def targets_from_sources(sign, dist): from pytential import sym, bind - nodes = bind(lpot_source.density_discr, sym.Nodes())(queue) - normals = bind(lpot_source.density_discr, sym.normal())(queue) + dim = 2 + nodes = bind(lpot_source.density_discr, sym.nodes(dim))(queue) + normals = bind(lpot_source.density_discr, sym.normal(dim))(queue) return (nodes + normals * sign * dist).as_vector(np.object) from pytential.target import PointsTarget -- GitLab From afdb4b958af6c24ee453498d1e69acff265343d4 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 29 Dec 2016 23:11:32 -0600 Subject: [PATCH 53/62] test_layer_pot: Update to use with_refinement() everywhere properly. --- test/test_layer_pot.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 1eebe256..7c96e47b 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -156,12 +156,13 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): # FIXME: for now fmm_order = False - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, fmm_order=fmm_order).with_refinement() + density_discr = qbx.density_discr nodes = density_discr.nodes().with_queue(queue) if 0: @@ -309,17 +310,19 @@ def run_int_eq_test( from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) if source_order is None: source_order = 4*target_order - qbx = QBXLayerPotentialSource( - density_discr, fine_order=source_order, qbx_order=qbx_order, + qbx, _ = QBXLayerPotentialSource( + pre_density_discr, fine_order=source_order, qbx_order=qbx_order, # Don't use FMM for now fmm_order=False).with_refinement() + density_discr = qbx.density_discr + # {{{ set up operator from pytential.symbolic.pde.scalar import ( @@ -778,9 +781,9 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, - qbx_order, - fmm_order=qbx_order + 5).with_refinement() + qbx, _ = QBXLayerPotentialSource( + pre_density_discr, 4*target_order, + qbx_order, fmm_order=qbx_order + 5).with_refinement() density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE @@ -861,11 +864,13 @@ def test_off_surface_eval(ctx_getter, use_fmm, do_plot=False): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order, + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, fmm_order=fmm_order).with_refinement() + density_discr = qbx.density_discr + from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=-2) -- GitLab From 8916bc3cc852b1cef30222d86433accb03b97ab0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Thu, 29 Dec 2016 23:44:26 -0600 Subject: [PATCH 54/62] Update test_matrix --- pytential/symbolic/matrix.py | 2 +- test/test_matrix.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pytential/symbolic/matrix.py b/pytential/symbolic/matrix.py index 6d204372..9b4622fd 100644 --- a/pytential/symbolic/matrix.py +++ b/pytential/symbolic/matrix.py @@ -187,7 +187,7 @@ class MatrixBuilder(EvaluationMapperBase): _, (mat,) = mat_gen(self.queue, target_discr.nodes(), source.fine_density_discr.nodes(), - source.centers(target_discr, expr.qbx_forced_limit), + source.centers(expr.qbx_forced_limit), **kernel_args) mat = mat.get() diff --git a/test/test_matrix.py b/test/test_matrix.py index 24c10d6b..cd70c0e3 100644 --- a/test/test_matrix.py +++ b/test/test_matrix.py @@ -102,14 +102,16 @@ def test_matrix_build(ctx_factory): from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory from pytential.qbx import QBXLayerPotentialSource - density_discr = Discretization( + pre_density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) - qbx = QBXLayerPotentialSource(density_discr, 4*target_order, + qbx, _ = QBXLayerPotentialSource(pre_density_discr, 4*target_order, qbx_order, # Don't use FMM for now - fmm_order=False) + fmm_order=False).with_refinement() + + density_discr = qbx.density_discr bound_op = bind(qbx, op) -- GitLab From 342e5c8b0dd044d7ec0298f00a74cf548b5ecc1c Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 30 Dec 2016 01:09:37 -0600 Subject: [PATCH 55/62] More tests passing. --- pytential/qbx/__init__.py | 3 ++- pytential/qbx/geometry.py | 17 ++++++++++++++--- test/test_layer_pot.py | 11 ++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 7a5cb276..a8277317 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -753,7 +753,8 @@ class QBXLayerPotentialSource(LayerPotentialSource): # First ncenters targets are the centers tgt_to_qbx_center = ( - geo_data.user_target_to_center()[center_info.ncenters:]) + geo_data.user_target_to_center()[center_info.ncenters:] + .copy(queue=queue)) qbx_tgt_numberer = self.get_qbx_target_numberer( tgt_to_qbx_center.dtype) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index d91c6ff2..325bf3ee 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -856,13 +856,24 @@ class QBXFMMGeometryData(object): # FIXME: kernel ownership... tgt_assoc = QBXTargetAssociator(self.cl_context) + tgt_info = self.target_info() + center_info = self.center_info() + + from pytential.target import PointsTarget + + with cl.CommandQueue(self.cl_context) as queue: + target_side_prefs = (self + .target_side_preferences()[center_info.ncenters:].get(queue=queue)) + + target_discrs_and_qbx_sides = [( + PointsTarget(tgt_info.targets[:,center_info.ncenters:]), + target_side_prefs.astype(np.int32))] + # FIXME: try block... tgt_assoc_result = tgt_assoc(self.lpot_source, - self.target_discrs_and_qbx_sides, + target_discrs_and_qbx_sides, stick_out_factor=self.target_stick_out_factor) - tgt_info = self.target_info() - center_info = self.center_info() tree = self.tree() with cl.CommandQueue(self.cl_context) as queue: diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 7c96e47b..dcdf887f 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -122,7 +122,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) - target_order = 7 + target_order = 8 from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ @@ -266,7 +266,7 @@ def test_ellipse_eigenvalues(ctx_getter, ellipse_aspect, mode_nr, qbx_order): norm(density_discr, queue, sp_sigma - sp_sigma_ref) / norm(density_discr, queue, sigma)) - sp_eoc_rec.add_data_point(1/nelements, sp_err) + sp_eoc_rec.add_data_point(qbx.h_max, sp_err) # }}} @@ -621,6 +621,7 @@ def run_int_eq_test( pass return Result( + h_max=qbx.h_max, rel_err_2=rel_err_2, rel_err_inf=rel_err_inf, rel_td_err_inf=rel_td_err_inf, @@ -672,8 +673,8 @@ def test_integral_equation( bc_type, loc_sign, k, target_order=target_order, source_order=source_order) - eoc_rec_target.add_data_point(1/nelements, result.rel_err_2) - eoc_rec_td.add_data_point(1/nelements, result.rel_td_err_inf) + eoc_rec_target.add_data_point(result.h_max, result.rel_err_2) + eoc_rec_td.add_data_point(result.h_max, result.rel_td_err_inf) if bc_type == "dirichlet": tgt_order = qbx_order @@ -783,7 +784,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) qbx, _ = QBXLayerPotentialSource( pre_density_discr, 4*target_order, - qbx_order, fmm_order=qbx_order + 5).with_refinement() + qbx_order, fmm_order=qbx_order + 15).with_refinement() density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE -- GitLab From efa64366b0fb8789f6fb1a5aa5a485b98aff863a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 30 Dec 2016 01:26:48 -0600 Subject: [PATCH 56/62] Bump the FMM order for test_identities. --- test/test_layer_pot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index dcdf887f..22a1d573 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -725,7 +725,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) from sympy.core.cache import clear_cache clear_cache() - target_order = 7 + target_order = 8 u_sym = sym.var("u") grad_u_sym = sym.make_sym_mv("grad_u", 2) @@ -784,7 +784,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) qbx, _ = QBXLayerPotentialSource( pre_density_discr, 4*target_order, - qbx_order, fmm_order=qbx_order + 15).with_refinement() + qbx_order, fmm_order=qbx_order + 20).with_refinement() density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE -- GitLab From f9bfbac570f6fb1458f544a7df020857dd61184a Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 30 Dec 2016 01:33:35 -0600 Subject: [PATCH 57/62] Flake8 fix. --- pytential/qbx/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytential/qbx/geometry.py b/pytential/qbx/geometry.py index 325bf3ee..ce93e851 100644 --- a/pytential/qbx/geometry.py +++ b/pytential/qbx/geometry.py @@ -866,7 +866,7 @@ class QBXFMMGeometryData(object): .target_side_preferences()[center_info.ncenters:].get(queue=queue)) target_discrs_and_qbx_sides = [( - PointsTarget(tgt_info.targets[:,center_info.ncenters:]), + PointsTarget(tgt_info.targets[:, center_info.ncenters:]), target_side_prefs.astype(np.int32))] # FIXME: try block... -- GitLab From 7125a178d3601f8f85c3b6d212aaa3d05ed57c83 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Fri, 30 Dec 2016 01:48:53 -0600 Subject: [PATCH 58/62] Fix off surface eval test. --- pytential/qbx/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index a8277317..33aa82b5 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -775,6 +775,7 @@ class QBXLayerPotentialSource(LayerPotentialSource): qbx_tgt_numbers = qbx_tgt_numbers[:qbx_tgt_count] qbx_center_numbers = tgt_to_qbx_center[qbx_tgt_numbers] + qbx_center_numbers.finish() tgt_subset_kwargs = kernel_args.copy() for i, res_i in enumerate(output_for_each_kernel): -- GitLab From dbc9cd6fa80526128e12c214c7edfbf1fc97cee2 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 2 Jan 2017 04:24:56 -0600 Subject: [PATCH 59/62] get_*_expansion_class: Don't default to Laplace. --- pytential/qbx/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pytential/qbx/__init__.py b/pytential/qbx/__init__.py index 33aa82b5..baa5a6ba 100644 --- a/pytential/qbx/__init__.py +++ b/pytential/qbx/__init__.py @@ -100,27 +100,33 @@ class LayerPotentialSource(object): def get_local_expansion_class(base_kernel): # FIXME: Don't hard-code expansion types - from sumpy.kernel import HelmholtzKernel + from sumpy.kernel import HelmholtzKernel, LaplaceKernel if (isinstance(base_kernel.get_base_kernel(), HelmholtzKernel) and base_kernel.dim == 2): from sumpy.expansion.local import H2DLocalExpansion return H2DLocalExpansion - else: + elif isinstance(base_kernel.get_base_kernel(), LaplaceKernel): from sumpy.expansion.local import LaplaceConformingVolumeTaylorLocalExpansion return LaplaceConformingVolumeTaylorLocalExpansion + else: + from sumpy.expansion.local import VolumeTaylorLocalExpansion + return VolumeTaylorLocalExpansion def get_multipole_expansion_class(base_kernel): # FIXME: Don't hard-code expansion types - from sumpy.kernel import HelmholtzKernel + from sumpy.kernel import HelmholtzKernel, LaplaceKernel if (isinstance(base_kernel.get_base_kernel(), HelmholtzKernel) and base_kernel.dim == 2): from sumpy.expansion.multipole import H2DMultipoleExpansion return H2DMultipoleExpansion - else: + elif isinstance(base_kernel.get_base_kernel(), LaplaceKernel): from sumpy.expansion.multipole import ( LaplaceConformingVolumeTaylorMultipoleExpansion) return LaplaceConformingVolumeTaylorMultipoleExpansion + else: + from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion + return VolumeTaylorMultipoleExpansion # {{{ QBX layer potential source -- GitLab From 41ddd3b4dd5d53aae2e9e03685a200301f73c1c0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 2 Jan 2017 18:07:17 -0600 Subject: [PATCH 60/62] QBXL2P: Follow-up to inducer/sumpy!16. --- pytential/qbx/interactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytential/qbx/interactions.py b/pytential/qbx/interactions.py index 2deffe69..cc4d494e 100644 --- a/pytential/qbx/interactions.py +++ b/pytential/qbx/interactions.py @@ -319,7 +319,8 @@ class QBXL2P(E2PBase): icenter_tgt_start<=icenter_tgt src_icenter = global_qbx_centers[iglobal_center] -- GitLab From 63f59bbfe91cf4022a7d4681726d20566af0a0e0 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 2 Jan 2017 18:11:30 -0600 Subject: [PATCH 61/62] Refiner: Rename self.context to self.cl_context, in order to be compatible with DiscrPlotterMixin. --- pytential/qbx/refinement.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pytential/qbx/refinement.py b/pytential/qbx/refinement.py index 47e6f54d..c942b45b 100644 --- a/pytential/qbx/refinement.py +++ b/pytential/qbx/refinement.py @@ -284,11 +284,11 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): """ def __init__(self, context): - self.context = context + self.cl_context = context from pytential.qbx.utils import TreeWithQBXMetadataBuilder - self.tree_builder = TreeWithQBXMetadataBuilder(self.context) + self.tree_builder = TreeWithQBXMetadataBuilder(self.cl_context) from boxtree.area_query import PeerListFinder - self.peer_list_finder = PeerListFinder(self.context) + self.peer_list_finder = PeerListFinder(self.cl_context) # {{{ kernels @@ -300,7 +300,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): logger.info("refiner: building tunnel query distance finder kernel") knl = TUNNEL_QUERY_DISTANCE_FINDER_TEMPLATE.build( - self.context, + self.cl_context, type_aliases=( ("particle_id_t", particle_id_dtype), ("coord_t", coord_dtype), @@ -322,7 +322,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): peer_list_idx_dtype, particle_id_dtype, max_levels): - return CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER.generate(self.context, + return CENTER_IS_CLOSEST_TO_ORIG_PANEL_REFINER.generate(self.cl_context, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, max_levels, extra_type_aliases=(("particle_id_t", particle_id_dtype),)) @@ -334,7 +334,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): peer_list_idx_dtype, particle_id_dtype, max_levels): - return CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER.generate(self.context, + return CENTER_IS_FAR_FROM_NONNEIGHBOR_PANEL_REFINER.generate(self.cl_context, dimensions, coord_dtype, box_id_dtype, peer_list_idx_dtype, max_levels, extra_type_aliases=(("particle_id_t", particle_id_dtype),)) @@ -700,7 +700,7 @@ class QBXLayerPotentialSourceRefiner(DiscrPlotterMixin): debug=debug, refined_for_global_qbx=True) - with cl.CommandQueue(self.context) as queue: + with cl.CommandQueue(self.cl_context) as queue: if refine_flags: lpot_source, conn = self.refine( queue, lpot_source, refine_flags, refiner, discr_factory, -- GitLab From e54bcb693820f6a9d4a9f065eec998b9f69ffe37 Mon Sep 17 00:00:00 2001 From: Matt Wala Date: Mon, 2 Jan 2017 18:19:56 -0600 Subject: [PATCH 62/62] test_identities: Use starfish instead of 3-to-1 ellipse. This permits us to use a lower FMM order. My guess is QBX finds the starfish more difficult than the ellipse, so that in the starfish the QBX error dominates compared to FMM error for the values of h we are using. This change should also make the build green. :fireworks: --- test/test_layer_pot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_layer_pot.py b/test/test_layer_pot.py index 22a1d573..2a2dce38 100644 --- a/test/test_layer_pot.py +++ b/test/test_layer_pot.py @@ -702,8 +702,8 @@ d2 = sym.Derivative() @pytest.mark.parametrize(("curve_name", "curve_f"), [ #("circle", partial(ellipse, 1)), - ("3-to-1 ellipse", partial(ellipse, 3)), - #("starfish", starfish), + #("3-to-1 ellipse", partial(ellipse, 3)), + ("starfish", starfish), ]) @pytest.mark.parametrize("qbx_order", [5]) @pytest.mark.parametrize(("zero_op_name", "k"), [ @@ -784,7 +784,7 @@ def test_identities(ctx_getter, zero_op_name, curve_name, curve_f, qbx_order, k) qbx, _ = QBXLayerPotentialSource( pre_density_discr, 4*target_order, - qbx_order, fmm_order=qbx_order + 20).with_refinement() + qbx_order, fmm_order=qbx_order + 15).with_refinement() density_discr = qbx.density_discr # {{{ compute values of a solution to the PDE -- GitLab