Merge remote-tracking branch 'mine/Spatial_searching-Parallelize_kd_tree_build-GF' into Spatial_searching-Parallelize_kd_tree_build-GF

This commit is contained in:
Simon Giraudot 2020-04-16 17:03:44 +02:00
commit 95b9f05a28
9 changed files with 352 additions and 140 deletions

View File

@ -161,9 +161,9 @@ private:
CGAL::Real_timer t;
t.start();
if (lower_grid == nullptr)
neighborhood = new Neighborhood (input, point_map);
neighborhood = new Neighborhood (input, point_map, ConcurrencyTag());
else
neighborhood = new Neighborhood (input, point_map, voxel_size);
neighborhood = new Neighborhood (input, point_map, voxel_size, ConcurrencyTag());
t.stop();
if (lower_grid == nullptr)

View File

@ -173,12 +173,32 @@ public:
/*!
\brief Constructs a neighborhood object based on the input range.
\tparam ConcurrencyTag enables sequential versus parallel
algorithm. Possible values are `Sequential_tag`, `Parallel_tag`,
and `Parallel_if_available_tag`. If no tag is provided,
`Parallel_if_available_tag` is used.
\param input point range.
\param point_map property map to access the input points.
*/
template <typename ConcurrencyTag>
Point_set_neighborhood (const PointRange& input,
PointMap point_map)
PointMap point_map,
const ConcurrencyTag&)
: m_tree (nullptr)
{
init<ConcurrencyTag> (input, point_map);
}
/// \cond SKIP_IN_MANUAL
Point_set_neighborhood (const PointRange& input, PointMap point_map)
: m_tree (nullptr)
{
init<Parallel_if_available_tag> (input, point_map);
}
template <typename ConcurrencyTag>
void init (const PointRange& input, PointMap point_map)
{
My_point_property_map pmap (&input, point_map);
m_tree = new Tree (boost::counting_iterator<boost::uint32_t> (0),
@ -186,8 +206,9 @@ public:
Splitter(),
Search_traits (pmap));
m_distance = Distance (pmap);
m_tree->build();
m_tree->template build<ConcurrencyTag>();
}
/// \endcond
/*!
\brief Constructs a simplified neighborhood object based on the input range.
@ -197,14 +218,36 @@ public:
present in one cell, only the point closest to the centroid of
this subset is used.
\tparam ConcurrencyTag enables sequential versus parallel
algorithm. Possible values are `Sequential_tag`, `Parallel_tag`,
and `Parallel_if_available_tag`. If no tag is provided,
`Parallel_if_available_tag` is used.
\param input input range.
\param point_map property map to access the input points.
\param voxel_size size of the cells of the 3D grid used for simplification.
*/
template <typename ConcurrencyTag>
Point_set_neighborhood (const PointRange& input,
PointMap point_map,
float voxel_size,
const ConcurrencyTag&)
: m_tree (nullptr)
{
init<ConcurrencyTag> (input, point_map, voxel_size);
}
/// \cond SKIP_IN_MANUAL
Point_set_neighborhood (const PointRange& input,
PointMap point_map,
float voxel_size)
: m_tree (nullptr)
{
init<Parallel_if_available_tag> (input, point_map, voxel_size);
}
template <typename ConcurrencyTag>
void init (const PointRange& input, PointMap point_map, float voxel_size)
{
// First, simplify
std::vector<boost::uint32_t> indices;
@ -215,8 +258,9 @@ public:
Splitter(),
Search_traits (pmap));
m_distance = Distance (pmap);
m_tree->build();
m_tree->template build<ConcurrencyTag>();
}
/// \endcond
/// @}

View File

@ -23,7 +23,7 @@ in a dynamically allocated array (e.g., `Epick_d` with dynamic
dimension) &mdash; we says "to a lesser extent" because the points
are re-created by the kd-tree in a cache-friendly order after its construction,
so the coordinates are more likely to be stored in a near-optimal order on the
heap. When EnablePointsCache` is set to `Tag_true`, the points
heap. When `EnablePointsCache` is set to `Tag_true`, the points
coordinates will be cached in an optimal way. This will
increase memory consumption but provide better search performance.
See also the `GeneralDistance` and `FuzzyQueryItem` concepts for
@ -115,7 +115,17 @@ at the first call to a query or removal member function. You can call
`build()` explicitly to ensure that the next call to
query functions will not trigger the reconstruction of the
data structure.
\tparam ConcurrencyTag enables sequential versus parallel
algorithm. Possible values are `Sequential_tag`, `Parallel_tag`, and
`Parallel_if_available_tag`. This template parameter is optional:
calling `build()` without specifying the concurrency tag will result
in `Sequential_tag` being used. If `build()` is not called by the user
but called implicitly at the first call to a query or removal member
function, `Sequential_tag` is also used.
*/
template <typename ConcurrencyTag>
void build();
/*!
@ -147,14 +157,14 @@ template <class InputIterator> void insert(InputIterator first, InputIterator be
/*!
Removes the point `p` from the `k-d` tree. It uses `equal_to_p` to identify
the point after locating it, which can matter in particular when 2 points are
in the same place. `Identify_point` is a unary functor that takes a `Point_d`
in the same place. `IdentifyPoint` is a unary functor that takes a `Point_d`
and returns a `bool`. This is a limited and naive implementation that does not
rebalance the tree. On the other hand, the tree remains valid and ready for
queries. If the internal data structure is not already built, for instance
because the last operation was an insertion, it first calls `build()`.
*/
template<class Identify_point>
void remove(Point_d p, Identify_point equal_to_p);
template<class IdentifyPoint>
void remove(Point_d p, IdentifyPoint identify_point);
/*!
Removes point `p`, calling the 2-argument function `remove()` with a functor

View File

@ -407,6 +407,23 @@ higher dimensions.
\cgalExample{Spatial_searching/splitter_worst_cases.cpp}
\subsection Spatial_searchingExampleParallel Example for Parallel Neighbor Search
In order to speed-up the construction of the `kd` tree, the child
branches of each internal node can be computed in parallel, by calling
`Kd_tree::build<CGAL::Parallel_tag>()`. On a quad-core processor, the
parallel construction is experimentally 2 to 3 times faster than the
sequential version, depending on the point cloud. The parallel version
requires the executable to be linked against the <a href="https://www.threadingbuildingblocks.org">Intel TBB library</a>.
One query on the `kd` tree is purely sequential, but several queries
can be done in parallel.
The following example shows how to build the `kd` tree in parallel and
how to perform parallel queries:
\cgalExample{Spatial_searching/parallel_kdtree.cpp}
\section Performance Performance
\subsection OrthogonalPerfomance Performance of the Orthogonal Search
@ -415,21 +432,33 @@ We took the gargoyle data set (Surface) from aim\@shape, and generated the same
We then consider three scenarios as data/queries.
The data set contains 800K points. For each query point we compute the K=10,20,30 closest points, with the default splitter and for the bucket size 10 (default) and 20.
The results were produced with the release 4.6 of \cgal, on an Intel i7 2.7 Ghz
laptop with 16 GB RAM, compiled with Visual C++ 2012 with the /O2 option.
The values are the average of ten tests each.
The results were produced with the release 5.0 of \cgal, on an Intel i7 2.3 Ghz
laptop with 16 GB RAM, compiled with CLang++ 6 with the O3 option.
The values are the average of ten tests each. We show timings in seconds for both the building of the tree and the queries.
<center>
k | bucket size | Surface/Surface | Surface/Random | Random/Random
--|------------:|-----------------:|---------------:|----------------:
10| 10 | 0.89 | 11.48 | 2.63
10| 20 | 0.89 | 9.80 | 2.25
20| 10 | 1.60 | 13.41 | 4.06
20| 20 | 1.59 | 11.62 | 3.46
30| 10 | 2.35 | 15.52 | 5.42
30| 20 | 2.33 | 13.50 | 4.61
k | bucket size | Surface Build | Random Build | Surface/Surface | Surface/Random | Random/Random
--|------------:|--------------:|-------------:|-----------------:|---------------:|----------------:
10| 10 | 0.17 | 0.31 | 1.13 | 15.35 | 3.40
10| 20 | 0.14 | 0.28 | 1.09 | 12.28 | 3.00
20| 10 | (see above) | (see above) | 1.88 | 18.25 | 5.39
20| 20 | (see above) | (see above) | 1.81 | 14.99 | 4.51
30| 10 | (see above) | (see above) | 2.87 | 22.62 | 7.07
30| 20 | (see above) | (see above) | 2.66 | 18.39 | 5.68
</center>
The same experiment is done using the parallel version of the tree building algorithm, and performing the queries in parallel too:
<center>
k | bucket size | Surface Build | Random Build | Surface/Surface | Surface/Random | Random/Random
--|------------:|--------------:|-------------:|-----------------:|---------------:|----------------:
10| 10 | 0.07 | 0.12 | 0.24 | 3.52 | 0.66
10| 20 | 0.06 | 0.12 | 0.22 | 2.87 | 0.57
20| 10 | (see above) | (see above) | 0.41 | 4.28 | 1.02
20| 20 | (see above) | (see above) | 0.38 | 3.43 | 0.88
30| 10 | (see above) | (see above) | 0.58 | 4.90 | 1.44
30| 20 | (see above) | (see above) | 0.60 | 4.28 | 1.28
</center>
\cgalFigureBegin{Spatial_searchingfigbenchmark,gargoyle.png}
@ -520,9 +549,12 @@ additional requirements when using such a cache.
\section Spatial_searchingImplementationHistory Implementation History
The initial implementation of this package was done by Hans Tangelder and
Andreas Fabri. It was optimized in speed and memory consumption by Markus
Overtheil during an internship at GeometryFactory in 2014.
The initial implementation of this package was done by Hans Tangelder
and Andreas Fabri. It was optimized in speed and memory consumption by
Markus Overtheil during an internship at GeometryFactory in 2014. The
`EnablePointsCache` feature was introduced by Clément Jamin in 2019.
The parallel `kd` tree build function was introduced by Simon Giraudot
in 2020.
*/
} /* namespace CGAL */

View File

@ -18,4 +18,5 @@
\example Spatial_searching/weighted_Minkowski_distance.cpp
\example Spatial_searching/splitter_worst_cases.cpp
\example Spatial_searching/searching_sphere_orthogonally.cpp
\example Spatial_searching/parallel_kdtree.cpp
*/

View File

@ -76,3 +76,12 @@ else()
message(STATUS "will not be compiled as they use CGAL::Epick_d which requires the Eigen library.")
endif()
find_package( TBB QUIET )
if(TBB_FOUND)
create_single_source_cgal_program( "parallel_kdtree.cpp" )
cgal_target_use_TBB(parallel_kdtree)
else()
message(STATUS "parallel_kdtree.cpp requires TBB and will not be compiled")
endif()

View File

@ -0,0 +1,56 @@
#include <CGAL/Simple_cartesian.h>
#include <CGAL/point_generators_3.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>
#include <CGAL/Search_traits_3.h>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
using Kernel = CGAL::Simple_cartesian<double>;
using Point_3 = Kernel::Point_3;
using Traits = CGAL::Search_traits_3<Kernel>;
using Neighbor_search = CGAL::Orthogonal_k_neighbor_search<Traits>;
using Tree = Neighbor_search::Tree;
using Point_with_distance = Neighbor_search::Point_with_transformed_distance;
using Generator = CGAL::Random_points_in_sphere_3<Point_3>;
int main()
{
const unsigned int N = 1000;
const unsigned int k = 6;
// Generate N points in a sphere
std::vector<Point_3> points;
points.reserve (N);
Generator generator;
for (unsigned int i = 0; i < N; ++ i)
points.push_back (*(generator++));
// Build tree in parallel
Tree tree(points.begin(), points.end());
tree.build<CGAL::Parallel_tag>();
// Query tree in parallel
std::vector<std::vector<Point_3> > neighbors (points.size());
tbb::parallel_for (tbb::blocked_range<std::size_t> (0, points.size()),
[&](const tbb::blocked_range<std::size_t>& r)
{
for (std::size_t s = r.begin(); s != r.end(); ++ s)
{
// Neighbor search can be instantiated from
// several threads at the same time
Neighbor_search search (tree, points[s], k);
neighbors[s].reserve(k);
// neighbor search returns a set of pair of
// point and distance <Point_3,FT>, here we
// keep the points only
for (const Point_with_distance& pwd : search)
neighbors[s].push_back (pwd.first);
}
});
return 0;
}

View File

@ -27,7 +27,6 @@
#include <CGAL/Splitters.h>
#include <CGAL/internal/Get_dimension_tag.h>
#include <deque>
#include <boost/container/deque.hpp>
#include <boost/optional.hpp>
@ -35,6 +34,30 @@
#include <CGAL/mutex.h>
#endif
/*
For building the KD Tree in parallel, TBB is needed. If TBB is
linked, the internal structures `deque` will be replaced by
`tbb::concurrent_vector`, even if the KD Tree is built in sequential
mode (this is to avoid changing the type of the KD Tree when
changing the concurrency mode of `build()`).
Experimentally, using the `tbb::concurrent_vector` in sequential
mode does not trigger any loss of performance, so from a user's
point of view, it should be transparent.
However, in case one wants to compile the KD Tree *without using TBB
structure even though CGAL is linked with TBB*, the macro
`CGAL_DISABLE_TBB_STRUCTURE_IN_KD_TREE` can be defined. In that
case, even if TBB is linked, the standard `deque` will be used
internally. Note that of course, in that case, parallel build will
be disabled.
*/
#if defined(CGAL_LINKED_WITH_TBB) && !defined(CGAL_DISABLE_TBB_STRUCTURE_IN_KD_TREE)
# include <tbb/parallel_invoke.h>
# include <tbb/concurrent_vector.h>
# define CGAL_TBB_STRUCTURE_IN_KD_TREE
#endif
namespace CGAL {
//template <class SearchTraits, class Splitter_=Median_of_rectangle<SearchTraits>, class UseExtendedNode = Tag_true >
@ -77,14 +100,13 @@ public:
typedef EnablePointsCache Enable_points_cache;
private:
SearchTraits traits_;
Splitter split;
// wokaround for https://svn.boost.org/trac/boost/ticket/9332
#if (_MSC_VER == 1800) && (BOOST_VERSION == 105500)
std::deque<Internal_node> internal_nodes;
std::deque<Leaf_node> leaf_nodes;
#if defined(CGAL_TBB_STRUCTURE_IN_KD_TREE)
tbb::concurrent_vector<Internal_node> internal_nodes;
tbb::concurrent_vector<Leaf_node> leaf_nodes;
#else
boost::container::deque<Internal_node> internal_nodes;
boost::container::deque<Leaf_node> leaf_nodes;
@ -119,7 +141,6 @@ private:
: traits_(tree.traits_),built_(tree.built_),dim_(-1)
{};
// Instead of the recursive construction of the tree in the class Kd_tree_node
// we do this in the tree class. The advantage is that we then can optimize
// the allocation of the nodes.
@ -128,50 +149,69 @@ private:
Node_handle
create_leaf_node(Point_container& c)
{
Leaf_node node(true , static_cast<unsigned int>(c.size()));
Leaf_node node(static_cast<unsigned int>(c.size()));
std::ptrdiff_t tmp = c.begin() - data.begin();
node.data = pts.begin() + tmp;
leaf_nodes.push_back(node);
Leaf_node_handle nh = &leaf_nodes.back();
return nh;
#ifdef CGAL_TBB_STRUCTURE_IN_KD_TREE
return &*(leaf_nodes.push_back(node));
#else
leaf_nodes.emplace_back (node);
return &(leaf_nodes.back());
#endif
}
// The internal node
Node_handle
create_internal_node(Point_container& c, const Tag_true&)
Node_handle new_internal_node()
{
return create_internal_node_use_extension(c);
#ifdef CGAL_TBB_STRUCTURE_IN_KD_TREE
return &*(internal_nodes.push_back(Internal_node()));
#else
internal_nodes.emplace_back ();
return &(internal_nodes.back());
#endif
}
Node_handle
create_internal_node(Point_container& c, const Tag_false&)
{
return create_internal_node(c);
}
// TODO: Similiar to the leaf_init function above, a part of the code should be
// moved to a the class Kd_tree_node.
// It is not proper yet, but the goal was to see if there is
// a potential performance gain through the Compact_container
Node_handle
create_internal_node_use_extension(Point_container& c)
template <typename ConcurrencyTag>
void
create_internal_node(Node_handle n, Point_container& c, const ConcurrencyTag& tag)
{
Internal_node node(false);
internal_nodes.push_back(node);
Internal_node_handle nh = &internal_nodes.back();
Internal_node_handle nh = static_cast<Internal_node_handle>(n);
CGAL_assertion (nh != nullptr);
Separator sep;
Point_container c_low(c.dimension(),traits_);
split(sep, c, c_low);
nh->set_separator(sep);
handle_extended_node (nh, c, c_low, UseExtendedNode());
if (try_parallel_internal_node_creation (nh, c, c_low, tag))
return;
if (c_low.size() > split.bucket_size())
{
nh->lower_ch = new_internal_node();
create_internal_node (nh->lower_ch, c_low, tag);
}
else
nh->lower_ch = create_leaf_node(c_low);
if (c.size() > split.bucket_size())
{
nh->upper_ch = new_internal_node();
create_internal_node (nh->upper_ch, c, tag);
}
else
nh->upper_ch = create_leaf_node(c);
}
void handle_extended_node (Internal_node_handle nh, Point_container& c, Point_container& c_low, const Tag_true&)
{
int cd = nh->cutting_dimension();
if(!c_low.empty()){
nh->lower_low_val = c_low.tight_bounding_box().min_coord(cd);
@ -192,56 +232,45 @@ private:
CGAL_assertion(nh->cutting_value() >= nh->lower_low_val);
CGAL_assertion(nh->cutting_value() <= nh->upper_high_val);
if (c_low.size() > split.bucket_size()){
nh->lower_ch = create_internal_node_use_extension(c_low);
}else{
nh->lower_ch = create_leaf_node(c_low);
}
if (c.size() > split.bucket_size()){
nh->upper_ch = create_internal_node_use_extension(c);
}else{
nh->upper_ch = create_leaf_node(c);
}
return nh;
}
inline void handle_extended_node (Internal_node_handle, Point_container&, Point_container&, const Tag_false&) { }
// Note also that I duplicated the code to get rid if the if's for
// the boolean use_extension which was constant over the construction
Node_handle
create_internal_node(Point_container& c)
inline bool try_parallel_internal_node_creation (Internal_node_handle, Point_container&,
Point_container&, const Sequential_tag&)
{
Internal_node node(false);
internal_nodes.push_back(node);
Internal_node_handle nh = &internal_nodes.back();
Separator sep;
Point_container c_low(c.dimension(),traits_);
split(sep, c, c_low);
nh->set_separator(sep);
if (c_low.size() > split.bucket_size()){
nh->lower_ch = create_internal_node(c_low);
}else{
nh->lower_ch = create_leaf_node(c_low);
}
if (c.size() > split.bucket_size()){
nh->upper_ch = create_internal_node(c);
}else{
nh->upper_ch = create_leaf_node(c);
}
return nh;
return false;
}
#ifdef CGAL_TBB_STRUCTURE_IN_KD_TREE
inline bool try_parallel_internal_node_creation (Internal_node_handle nh, Point_container& c,
Point_container& c_low, const Parallel_tag& tag)
{
/*
The two child branches are computed in parallel if and only if:
* both branches lead to internal nodes (if at least one branch
is a leaf, it's useless)
* the current number of points is sufficiently high to be worth
the cost of launching new threads. Experimentally, using 10
times the bucket size as a limit gives the best timings.
*/
if (c_low.size() > split.bucket_size() && c.size() > split.bucket_size()
&& (c_low.size() + c.size() > 10 * split.bucket_size()))
{
nh->lower_ch = new_internal_node();
nh->upper_ch = new_internal_node();
tbb::parallel_invoke (std::bind (&Self::create_internal_node<Parallel_tag>, this, nh->lower_ch, std::ref(c_low), std::cref(tag)),
std::bind (&Self::create_internal_node<Parallel_tag>, this, nh->upper_ch, std::ref(c), std::cref(tag)));
return true;
}
return false;
}
#endif
public:
@ -261,6 +290,32 @@ public:
return pts.empty();
}
void build()
{
build<Sequential_tag>();
}
/*
Note about parallel `build()`. Several different strategies have
been tried, among which:
* keeping the `deque` and using mutex structures to secure the
insertions in them
* using free stand-alone pointers generated with `new` instead of
pushing elements in a container
* using a global `tbb::task_group` to handle the internal node
computations
* using one `tbb::task_group` per internal node to handle the
internal node computations
Experimentally, the options giving the best timings is the one
kept, namely:
* nodes are stored in `tbb::concurrent_vector` structures
* the parallel computations are launched using
`tbb::parallel_invoke`
*/
template <typename ConcurrencyTag>
void
build()
{
@ -277,12 +332,19 @@ public:
for(unsigned int i = 0; i < pts.size(); i++){
data.push_back(&pts[i]);
}
#ifndef CGAL_TBB_STRUCTURE_IN_KD_TREE
CGAL_static_assertion_msg (!(boost::is_convertible<ConcurrencyTag, Parallel_tag>::value),
"Parallel_tag is enabled but TBB is unavailable.");
#endif
Point_container c(dim_, data.begin(), data.end(),traits_);
bbox = new Kd_tree_rectangle<FT,D>(c.bounding_box());
if (c.size() <= split.bucket_size()){
tree_root = create_leaf_node(c);
}else {
tree_root = create_internal_node(c, UseExtendedNode());
tree_root = new_internal_node();
create_internal_node (tree_root, c, ConcurrencyTag());
}
//Reorder vector for spatial locality

View File

@ -51,15 +51,12 @@ namespace CGAL {
typedef typename Kdt::iterator iterator;
typedef typename Kdt::D D;
bool leaf;
public :
Kd_tree_node(bool leaf_)
:leaf(leaf_){}
Kd_tree_node() { }
bool is_leaf() const{
return leaf;
}
virtual ~Kd_tree_node() { }
virtual bool is_leaf() const = 0;
std::size_t
num_items() const
@ -97,8 +94,8 @@ namespace CGAL {
Internal_node_const_handle node =
static_cast<Internal_node_const_handle>(this);
return
(std::max)( node->lower()->depth(current_max_depth + 1),
node->upper()->depth(current_max_depth + 1));
(std::max)( node->lower()->depth(current_max_depth + 1),
node->upper()->depth(current_max_depth + 1));
}
}
@ -112,17 +109,17 @@ namespace CGAL {
OutputIterator
tree_items(OutputIterator it) const {
if (is_leaf()) {
Leaf_node_const_handle node =
Leaf_node_const_handle node =
static_cast<Leaf_node_const_handle>(this);
if (node->size()>0)
for (iterator i=node->begin(); i != node->end(); i++)
{*it=*i; ++it;}
}
if (node->size()>0)
for (iterator i=node->begin(); i != node->end(); i++)
{*it=*i; ++it;}
}
else {
Internal_node_const_handle node =
Internal_node_const_handle node =
static_cast<Internal_node_const_handle>(this);
it=node->lower()->tree_items(it);
it=node->upper()->tree_items(it);
it=node->lower()->tree_items(it);
it=node->upper()->tree_items(it);
}
return it;
}
@ -206,20 +203,20 @@ namespace CGAL {
else {
Internal_node_const_handle node =
static_cast<Internal_node_const_handle>(this);
// after splitting b denotes the lower part of b
Kd_tree_rectangle<FT,D> b_upper(b);
node->split_bbox(b, b_upper);
// after splitting b denotes the lower part of b
Kd_tree_rectangle<FT,D> b_upper(b);
node->split_bbox(b, b_upper);
if (q.outer_range_contains(b))
it=node->lower()->tree_items(it);
else
if (q.inner_range_intersects(b))
it=node->lower()->search(it,q,b,tree_points_begin,cache_begin,dim);
if (q.outer_range_contains(b_upper))
it=node->upper()->tree_items(it);
else
if (q.inner_range_intersects(b_upper))
it=node->upper()->search(it,q,b_upper,tree_points_begin,cache_begin,dim);
if (q.outer_range_contains(b))
it=node->lower()->tree_items(it);
else
if (q.inner_range_intersects(b))
it=node->lower()->search(it,q,b,tree_points_begin,cache_begin,dim);
if (q.outer_range_contains(b_upper))
it=node->upper()->tree_items(it);
else
if (q.inner_range_intersects(b_upper))
it=node->upper()->search(it,q,b_upper,tree_points_begin,cache_begin,dim);
};
return it;
}
@ -398,13 +395,13 @@ namespace CGAL {
Kd_tree_leaf_node()
{}
Kd_tree_leaf_node(bool leaf_ )
: Base(leaf_)
Kd_tree_leaf_node(unsigned int n_ )
: n(n_)
{}
Kd_tree_leaf_node(bool leaf_,unsigned int n_ )
: Base(leaf_), n(n_)
{}
virtual ~Kd_tree_leaf_node() { }
virtual bool is_leaf() const { return true; }
// members for all nodes
@ -475,12 +472,15 @@ namespace CGAL {
// default constructor
Kd_tree_internal_node()
: cut_dim(-1), cut_val(0)
, lower_ch (nullptr), upper_ch (nullptr)
, upper_low_val(0), upper_high_val(0)
, lower_low_val(0), lower_high_val(0)
{}
Kd_tree_internal_node(bool leaf_)
: Base(leaf_)
{}
virtual ~Kd_tree_internal_node() { }
virtual bool is_leaf() const { return false; }
// members for internal node and extended internal node
@ -621,9 +621,7 @@ namespace CGAL {
Kd_tree_internal_node()
{}
Kd_tree_internal_node(bool leaf_)
: Base(leaf_)
{}
virtual bool is_leaf() const { return false; }
// members for internal node and extended internal node