def mark_panels_for_refinement(self, places, dofdesc, tree, peer_lists, target_status, refine_flags, debug, wait_for=None): from pytential import bind, sym ambient_dim = places.ambient_dim # Round up level count--this gets included in the kernel as # a stack bound. Rounding avoids too many kernel versions. from pytools import div_ceil max_levels = 10 * div_ceil(tree.nlevels, 10) knl = self.code_container.refiner_for_failed_target_association( tree.dimensions, tree.coord_dtype, tree.box_id_dtype, peer_lists.peer_list_starts.dtype, tree.particle_id_dtype, max_levels) found_panel_to_refine = cl.array.zeros(self.queue, 1, np.int32) found_panel_to_refine.finish() # Perform a space invader query over the sources. source_slice = tree.user_source_ids[tree.qbx_user_source_slice] sources = [ axis.with_queue(self.queue)[source_slice] for axis in tree.sources ] tunnel_radius_by_source = flatten( bind(places, sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc))( self.array_context)) # see (TGTMARK) above for algorithm. box_to_search_dist, evt = self.code_container.space_invader_query()( self.queue, tree, sources, tunnel_radius_by_source, peer_lists, wait_for=wait_for) wait_for = [evt] evt = knl(*unwrap_args( 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.sorted_target_ids, tunnel_radius_by_source, target_status, box_to_search_dist, refine_flags, found_panel_to_refine, *tree.sources), range=slice(tree.nqbxtargets), queue=self.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]) return (found_panel_to_refine == 1).all().get()
def test_target_association(ctx_factory, curve_name, curve_f, nelements, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) # {{{ generate lpot source order = 16 # Make the curve mesh. 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(actx, mesh, factory) lpot_source = QBXLayerPotentialSource( discr, qbx_order=order, # not used in target association fine_order=order) places = GeometryCollection(lpot_source) # }}} # {{{ generate targets from pyopencl.clrandom import PhiloxGenerator rng = PhiloxGenerator(cl_ctx, seed=RNG_SEED) dd = places.auto_source.to_stage1() centers = dof_array_to_numpy( actx, bind( places, sym.interleaved_expansion_centers(lpot_source.ambient_dim, dofdesc=dd))(actx)) density_discr = places.get_discretization(dd.geometry) noise = actx.to_numpy( rng.uniform(queue, density_discr.ndofs, dtype=np.float, a=0.01, b=1.0)) tunnel_radius = dof_array_to_numpy( actx, bind( places, sym._close_target_tunnel_radii(lpot_source.ambient_dim, dofdesc=dd))(actx)) def targets_from_sources(sign, dist, dim=2): nodes = dof_array_to_numpy( actx, bind(places, sym.nodes(dim, dofdesc=dd))(actx).as_vector(np.object)) normals = dof_array_to_numpy( actx, bind(places, sym.normal(dim, dofdesc=dd))(actx).as_vector(np.object)) return actx.from_numpy(nodes + normals * sign * dist) from pytential.target import PointsTarget int_targets = PointsTarget(targets_from_sources(-1, noise * tunnel_radius)) ext_targets = PointsTarget(targets_from_sources(+1, noise * tunnel_radius)) far_targets = PointsTarget( targets_from_sources(+1, FAR_TARGET_DIST_FROM_SOURCE)) # Create target discretizations. target_discrs = ( # On-surface targets, interior (density_discr, -1), # On-surface targets, exterior (density_discr, +1), # Interior close targets (int_targets, -2), # Exterior close targets (ext_targets, +2), # Far targets, should not need centers (far_targets, 0), ) sizes = np.cumsum([discr.ndofs for discr, _ in target_discrs]) ( 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 (TargetAssociationCodeContainer, associate_targets_to_qbx_centers) from pytential.qbx.utils import TreeCodeContainer code_container = TargetAssociationCodeContainer(actx, TreeCodeContainer(actx)) target_assoc = (associate_targets_to_qbx_centers( places, places.auto_source, code_container.get_wrangler(actx), target_discrs, target_association_tolerance=1e-10).get(queue=queue)) expansion_radii = dof_array_to_numpy( actx, bind( places, sym.expansion_radii(lpot_source.ambient_dim, granularity=sym.GRANULARITY_CENTER))(actx)) from meshmode.dof_array import thaw surf_targets = dof_array_to_numpy(actx, thaw(actx, density_discr.nodes())) int_targets = actx.to_numpy(int_targets.nodes()) ext_targets = actx.to_numpy(ext_targets.nodes()) def visualize_curve_and_assoc(): import matplotlib.pyplot as plt from meshmode.mesh.visualization import draw_curve draw_curve(density_discr.mesh) targets = int_targets tgt_slice = surf_int_slice plt.plot(centers[0], centers[1], "+", color="orange") ax = plt.gca() for tx, ty, tcenter in zip(targets[0, tgt_slice], targets[1, tgt_slice], target_assoc.target_to_center[tgt_slice]): if tcenter >= 0: ax.add_artist( plt.Line2D( (tx, centers[0, tcenter]), (ty, centers[1, tcenter]), )) ax.set_aspect("equal") plt.show() if visualize: visualize_curve_and_assoc() # Checks that the targets match with centers on the appropriate side and # within the allowable distance. def check_close_targets(centers, targets, true_side, target_to_center, target_to_side_result, tgt_slice): targets_have_centers = (target_to_center >= 0).all() assert targets_have_centers assert (target_to_side_result == true_side).all() TOL = 1e-3 dists = la.norm((targets.T - centers.T[target_to_center]), axis=1) assert (dists <= (1 + TOL) * expansion_radii[target_to_center]).all() # Center side order = -1, 1, -1, 1, ... target_to_center_side = 2 * (target_assoc.target_to_center % 2) - 1 # interior surface check_close_targets(centers, surf_targets, -1, target_assoc.target_to_center[surf_int_slice], target_to_center_side[surf_int_slice], surf_int_slice) # exterior surface check_close_targets(centers, surf_targets, +1, target_assoc.target_to_center[surf_ext_slice], target_to_center_side[surf_ext_slice], surf_ext_slice) # interior volume check_close_targets(centers, int_targets, -1, target_assoc.target_to_center[vol_int_slice], target_to_center_side[vol_int_slice], vol_int_slice) # exterior volume check_close_targets(centers, ext_targets, +1, target_assoc.target_to_center[vol_ext_slice], target_to_center_side[vol_ext_slice], vol_ext_slice) # Checks that far targets are not assigned a center. assert (target_assoc.target_to_center[far_slice] == -1).all()
def mark_targets(self, places, dofdesc, tree, peer_lists, target_status, debug, wait_for=None): from pytential import bind, sym ambient_dim = places.ambient_dim # Round up level count--this gets included in the kernel as # a stack bound. Rounding avoids too many kernel versions. from pytools import div_ceil max_levels = 10 * div_ceil(tree.nlevels, 10) knl = self.code_container.target_marker( tree.dimensions, tree.coord_dtype, tree.box_id_dtype, peer_lists.peer_list_starts.dtype, tree.particle_id_dtype, max_levels) found_target_close_to_panel = cl.array.zeros(self.queue, 1, np.int32) found_target_close_to_panel.finish() # Perform a space invader query over the sources. source_slice = tree.sorted_target_ids[tree.qbx_user_source_slice] sources = [ axis.with_queue(self.queue)[source_slice] for axis in tree.sources ] tunnel_radius_by_source = flatten( bind(places, sym._close_target_tunnel_radii(ambient_dim, dofdesc=dofdesc))( self.array_context)) # Target-marking algorithm (TGTMARK): # # (1) Use a space invader query to tag each leaf box that intersects with the # "near-source-detection tunnel" with the distance to the closest source. # # (2) Do an area query around all targets with the radius resulting # from the space invader query, enumerate sources in that vicinity. # If a source is found whose distance to the target is less than the # source's tunnel radius, mark that target as pending. # (or below: mark the source for refinement) # Note that this comment is referred to below by "TGTMARK". If you # remove this comment or change the algorithm here, make sure that # the reference below is still accurate. # Trade off for space-invaders vs directly tagging targets in # endangered boxes: # # (-) More complicated # (-) More actual work # (+) Taking the point of view of the targets could potentially lead to # more parallelism, if you think of the targets as unbounded while the # sources are fixed (which sort of makes sense, given that the number # of targets per box is not bounded). box_to_search_dist, evt = self.code_container.space_invader_query()( self.queue, tree, sources, tunnel_radius_by_source, peer_lists, wait_for=wait_for) wait_for = [evt] evt = knl(*unwrap_args(tree, peer_lists, tree.box_to_qbx_source_starts, tree.box_to_qbx_source_lists, tree.qbx_user_source_slice.start, tree.qbx_user_target_slice.start, tree.sorted_target_ids, tunnel_radius_by_source, box_to_search_dist, target_status, found_target_close_to_panel, *tree.sources), range=slice(tree.nqbxtargets), queue=self.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, tree.nqbxtargets)) cl.wait_for_events([evt]) return (found_target_close_to_panel == 1).all().get()