DGtal 2.1.0
Loading...
Searching...
No Matches
testQuickHull.cpp
Go to the documentation of this file.
1
31#include <iostream>
32#include <vector>
33#include <algorithm>
34#include "DGtal/base/Common.h"
35#include "DGtal/kernel/SpaceND.h"
36#include "DGtal/geometry/tools/QuickHull.h"
37#include "DGtal/geometry/tools/AffineGeometry.h"
38#include "DGtalCatch.h"
40
41using namespace std;
42using namespace DGtal;
43
44std::random_device rd;
45std::mt19937 g(rd());
46
47template <typename Point>
48std::vector< Point >
49randomPointsInBall( int nb, int R )
50{
51 std::vector< Point > V;
52 Point c = Point::diagonal( R );
53 double R2 = (double) R * (double) R;
54 for ( int i = 0; i < nb; ) {
55 Point p;
56 for ( DGtal::Dimension k = 0; k < Point::dimension; ++k )
57 p[ k ] = rand() % (2*R);
58 if ( ( p - c ).squaredNorm() < R2 ) { V.push_back( p ); i++; }
59 }
60 return V;
61}
62
63template < typename Point >
64std::vector< Point >
65makeRandomLatticePointsFromDirVectors( int nb, const vector< Point>& V )
66{
67 std::uniform_int_distribution<int> U(-10, 10);
68 vector< Point > P;
69 int n = V[0].size();
70 int m = V.size();
71 Point A;
72 for ( auto i = 0; i < n; i++ )
73 A[ i ] = U( g );
74 P.push_back( A );
75 for ( auto k = 0; k < nb; k++ )
76 {
77 Point B = A;
78 for ( auto i = 0; i < m; i++ )
79 {
80 int l = U( g );
81 B += l * V[ i ];
82 }
83 P.push_back( B );
84 }
85 std::shuffle( P.begin(), P.end(), g );
86 return P;
87}
88
89// ///////////////////////////////////////////////////////////////////////////////
90// // Functions for testing class QuickHull in 2D.
91// ///////////////////////////////////////////////////////////////////////////////
92
93SCENARIO( "QuickHull< ConvexHullIntegralKernel< 2 > > unit tests", "[quickhull][integral_kernel][2d]" )
94{
95 typedef ConvexHullIntegralKernel< 2 > QHKernel;
96 typedef QuickHull< QHKernel > QHull;
97 typedef SpaceND< 2, int > Space;
98 typedef Space::Point Point;
99 typedef QHull::Index Index;
100 typedef QHull::IndexRange IndexRange;
101
102
103 GIVEN( "Given a star { (0,0), (-4,-1), (-3,5), (7,3), (5, -2) } " ) {
104 std::vector<Point> V
105 = { Point(0,0), Point(-4,-1), Point(-3,5), Point(7,3), Point(5, -2) };
106 QHull hull;
107 hull.setInput( V, false );
108 hull.computeConvexHull();
109 THEN( "The convex hull is valid and contains every point" ) {
110 REQUIRE( hull.check() );
111 }
112 THEN( "Its convex hull has 4 vertices" ) {
113 REQUIRE( hull.nbVertices() == 4 );
114 }
115 THEN( "Its convex hull has 4 facets" ) {
116 REQUIRE( hull.nbFacets() == 4 );
117 }
118 THEN( "Its facets form a linked list" ) {
119 std::vector< IndexRange > facets;
120 hull.getFacetVertices( facets );
121 std::vector< Index > next( hull.nbVertices(), (Index) -1 );
122 Index nb_zero_next = hull.nbVertices();
123 Index nb_two_next = 0;
124 for ( auto f : facets ) {
125 if ( next[ f[ 0 ] ] != (Index) -1 ) nb_two_next += 1;
126 else {
127 next[ f[ 0 ] ] = f[ 1 ];
128 nb_zero_next -= 1;
129 }
130 }
131 REQUIRE( nb_zero_next == 0 );
132 REQUIRE( nb_two_next == 0 );
133 }
134 }
135 GIVEN( "Given 100 random point in a ball of radius 50 " ) {
136 std::vector<Point> V = randomPointsInBall< Point >( 100, 50 );
137 QHull hull;
138 hull.setInput( V, false );
139 hull.computeConvexHull();
140 THEN( "The convex hull is valid and contains every point" ) {
141 REQUIRE( hull.check() );
142 }
143 THEN( "Its convex hull has the same number of vertices and facets" ) {
144 REQUIRE( hull.nbVertices() == hull.nbFacets() );
145 }
146 THEN( "Its convex hull has much fewer vertices than input points" ) {
147 REQUIRE( 2*hull.nbVertices() <= hull.nbPoints() );
148 }
149 THEN( "Its facets form a linked list" ) {
150 std::vector< IndexRange > facets;
151 hull.getFacetVertices( facets );
152 std::vector< Index > next( hull.nbVertices(), (Index) -1 );
153 Index nb_zero_next = hull.nbVertices();
154 Index nb_two_next = 0;
155 for ( auto f : facets ) {
156 if ( next[ f[ 0 ] ] != (Index) -1 ) nb_two_next += 1;
157 else {
158 next[ f[ 0 ] ] = f[ 1 ];
159 nb_zero_next -= 1;
160 }
161 }
162 REQUIRE( nb_zero_next == 0 );
163 REQUIRE( nb_two_next == 0 );
164 }
165 }
166}
167
168SCENARIO( "QuickHull< ConvexHullIntegralKernel< 2 > > dimensionality tests", "[quickhull][integral_kernel][2d][not_full_dimensional]" )
169{
170 typedef ConvexHullIntegralKernel< 2 > QHKernel;
171 typedef QuickHull< QHKernel > QHull;
172 typedef SpaceND< 2, int > Space;
173 typedef Space::Point Point;
174 typedef AffineGeometry< Point > Affine;
175
176 std::vector< Point > V = { Point{ 3, 1 } };
177
178 GIVEN( "Given 100 aligned points" ) {
179 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
180 auto I = Affine::affineSubset( X );
181 auto d = Affine::affineDimension( X );
182 QHull hull;
183 hull.setInput( X, false );
184 bool ok = hull.computeConvexHull();
185 auto status = hull.status();
186 THEN( "AffineSubset should detect not full dimensionality" ) {
187 CAPTURE( d );
188 REQUIRE( d == 1 );
189 }
190 THEN( "QuickHull should detect not full dimensionality" ) {
191 REQUIRE( ok == false );
192 REQUIRE( status == QHull::Status::NotFullDimensional );
193 }
194 }
195
196 GIVEN( "Given 100 aligned points + another not aligned" ) {
197 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
198 X.push_back( X[ 0 ] + Point(-1,1) );
199 std::shuffle( X.begin(), X.end(), g );
200 auto I = Affine::affineSubset( X );
201 auto d = Affine::affineDimension( X );
202 QHull hull;
203 hull.setInput( X, false );
204 bool ok = hull.computeConvexHull();
205 auto status = hull.status();
206 THEN( "AffineSubset should detect full dimensionality" ) {
207 CAPTURE( d );
208 REQUIRE( d == 2 );
209 }
210 THEN( "QuickHull should detect full dimensionality" ) {
211 REQUIRE( ok == true );
212 REQUIRE( status != QHull::Status::NotFullDimensional );
213 }
214 THEN( "QuickHull should find 3 vertices" ) {
215 REQUIRE( hull.nbVertices() == 3 );
216 }
217 }
218}
219
221// Functions for testing class QuickHull in 3D.
223
224SCENARIO( "QuickHull< ConvexHullIntegralKernel< 3 > > unit tests", "[quickhull][integral_kernel][3d]" )
225{
226 typedef ConvexHullIntegralKernel< 3 > QHKernel;
227 typedef QuickHull< QHKernel > QHull;
228 typedef SpaceND< 3, int > Space;
229 typedef Space::Point Point;
230 typedef QHull::IndexRange IndexRange;
231
232
233 GIVEN( "Given an octahedron" ) {
234 const int R = 5;
235 std::vector<Point> V = { Point( 0,0,0 ) };
236 for ( Dimension k = 0; k < 3; ++k ) {
237 V.push_back( Point::base( k, R ) );
238 V.push_back( Point::base( k, -R ) );
239 }
240 QHull hull;
241 hull.setInput( V, false );
242 hull.setInitialSimplex( IndexRange { 0, 1, 3, 5 } );
243 hull.computeConvexHull();
244 THEN( "The convex hull is valid and contains every point" ) {
245 REQUIRE( hull.check() );
246 }
247 THEN( "Its convex hull has 6 vertices" ) {
248 REQUIRE( hull.nbVertices() == 6 );
249 }
250 THEN( "Its convex hull has 8 facets" ) {
251 REQUIRE( hull.nbFacets() == 8 );
252 }
253 }
254 GIVEN( "Given 100 random point in a ball of radius 50 " ) {
255 std::vector<Point> V = randomPointsInBall< Point >( 100, 50 );
256 QHull hull;
257 hull.setInput( V, false );
258 hull.computeConvexHull();
259 THEN( "The convex hull is valid and contains every point" ) {
260 REQUIRE( hull.check() );
261 }
262 THEN( "Its convex hull has more facets than vertices" ) {
263 REQUIRE( hull.nbVertices() < hull.nbFacets() );
264 }
265 THEN( "Its convex hull has fewer vertices than input points" ) {
266 REQUIRE( hull.nbVertices() < hull.nbPoints() );
267 }
268 }
269}
270
271SCENARIO( "QuickHull< ConvexHullIntegralKernel< 3 > > dimensionality tests", "[quickhull][integral_kernel][3d][not_full_dimensional]" )
272{
273 typedef ConvexHullIntegralKernel< 3 > QHKernel;
274 typedef QuickHull< QHKernel > QHull;
275 typedef SpaceND< 3, int > Space;
276 typedef Space::Point Point;
277 typedef AffineGeometry< Point > Affine;
278
279 GIVEN( "Given 100 aligned points" ) {
280 std::vector< Point > V = { Point{ 3, 1, -1 } };
281 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
282 auto I = Affine::affineSubset( X );
283 auto d = Affine::affineDimension( X );
284 QHull hull;
285 hull.setInput( X, false );
286 bool ok = hull.computeConvexHull();
287 auto status = hull.status();
288 THEN( "AffineSubset should detect not full dimensionality" ) {
289 CAPTURE( d );
290 REQUIRE( d == 1 );
291 }
292 THEN( "QuickHull should detect not full dimensionality" ) {
293 REQUIRE( ok == false );
294 REQUIRE( status == QHull::Status::NotFullDimensional );
295 }
296 }
297
298 GIVEN( "Given 100 aligned points + another not aligned" ) {
299 std::vector< Point > V = { Point{ 3, 2, -1 } };
300 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
301 X.push_back( X[ 0 ] + Point(-1,1,-1) );
302 std::shuffle( X.begin(), X.end(), g );
303 auto I = Affine::affineSubset( X );
304 auto d = Affine::affineDimension( X );
305 QHull hull;
306 hull.setInput( X, false );
307 bool ok = hull.computeConvexHull();
308 auto status = hull.status();
309 THEN( "AffineSubset should detect not full dimensionality (dimension 2)" ) {
310 CAPTURE( d );
311 REQUIRE( d == 2 );
312 }
313 THEN( "QuickHull should detect not full dimensionality" ) {
314 REQUIRE( ok == false );
315 REQUIRE( status == QHull::Status::NotFullDimensional );
316 }
317 }
318
319 GIVEN( "Given 100 points on a lattice plane" ) {
320 std::vector< Point > V = { Point{ 3, 1, -1 }, Point{ -2, 2, 1 } };
321 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
322 auto I = Affine::affineSubset( X );
323 auto d = Affine::affineDimension( X );
324 QHull hull;
325 hull.setInput( X, false );
326 bool ok = hull.computeConvexHull();
327 auto status = hull.status();
328 THEN( "AffineSubset should detect not full dimensionality (dimension 2)" ) {
329 CAPTURE( d );
330 REQUIRE( d == 2 );
331 }
332 THEN( "QuickHull should detect not full dimensionality" ) {
333 REQUIRE( ok == false );
334 REQUIRE( status == QHull::Status::NotFullDimensional );
335 }
336 }
337
338 GIVEN( "Given 100 points on a lattice plane + a point just outside" ) {
339 std::vector< Point > V = { Point{ 3, 1, -1 }, Point{ -2, 2, 1 } };
340 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
341 X.push_back( X[ 0 ] + Point(-1,1,-1) );
342 std::shuffle( X.begin(), X.end(), g );
343 auto I = Affine::affineSubset( X );
344 auto d = Affine::affineDimension( X );
345 QHull hull;
346 hull.setInput( X, false );
347 bool ok = hull.computeConvexHull();
348 auto status = hull.status();
349 THEN( "AffineSubset should detect full dimensionality" ) {
350 CAPTURE( d );
351 REQUIRE( d == 3 );
352 }
353 THEN( "QuickHull should detect full dimensionality" ) {
354 REQUIRE( ok == true );
355 REQUIRE( status != QHull::Status::NotFullDimensional );
356 }
357 THEN( "QuickHull should have more than 3 vertices" ) {
358 REQUIRE( hull.nbVertices() > 3 );
359 }
360 }
361
362}
363
364
366// Functions for testing class QuickHull in 4D.
368
369SCENARIO( "QuickHull< ConvexHullIntegralKernel< 4 > > unit tests", "[quickhull][integral_kernel][4d]" )
370{
371 typedef ConvexHullIntegralKernel< 4 > QHKernel;
372 typedef QuickHull< QHKernel > QHull;
373 typedef SpaceND< 4, int > Space;
374 typedef Space::Point Point;
375
376
377 GIVEN( "Given an octahedron" ) {
378 const int R = 5;
379 std::vector<Point> V = { Point( 0,0,0,0 ) };
380 for ( Dimension k = 0; k < 4; ++k ) {
381 V.push_back( Point::base( k, R ) );
382 V.push_back( Point::base( k, -R ) );
383 }
384 QHull hull;
385 hull.setInput( V, false );
386 hull.computeConvexHull();
387 THEN( "The convex hull is valid and contains every point" ) {
388 REQUIRE( hull.check() );
389 }
390 THEN( "Its convex hull has 8 vertices" ) {
391 REQUIRE( hull.nbVertices() == 8 );
392 }
393 THEN( "Its convex hull has 16 facets" ) {
394 REQUIRE( hull.nbFacets() == 16 );
395 }
396 }
397 GIVEN( "Given 100 random point in a ball of radius 50 " ) {
398 std::vector<Point> V = randomPointsInBall< Point >( 100, 50 );
399 QHull hull;
400 hull.setInput( V, false );
401 hull.computeConvexHull();
402 THEN( "The convex hull is valid and contains every point" ) {
403 REQUIRE( hull.check() );
404 }
405 THEN( "Its convex hull has fewer vertices than input points" ) {
406 REQUIRE( hull.nbVertices() < hull.nbPoints() );
407 }
408 }
409}
410
411SCENARIO( "QuickHull< ConvexHullIntegralKernel< 4 > > dimensionality tests", "[quickhull][integral_kernel][4d][not_full_dimensional]" )
412{
413 typedef ConvexHullIntegralKernel< 4 > QHKernel;
414 typedef QuickHull< QHKernel > QHull;
415 typedef SpaceND< 4, int > Space;
416 typedef Space::Point Point;
417 typedef AffineGeometry< Point > Affine;
418
419 GIVEN( "Given 100 aligned points" ) {
420 std::vector< Point > V = { Point{ 3, 1, -1, 2 } };
421 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
422 auto I = Affine::affineSubset( X );
423 auto d = Affine::affineDimension( X );
424 QHull hull;
425 hull.setInput( X, false );
426 bool ok = hull.computeConvexHull();
427 auto status = hull.status();
428 THEN( "AffineSubset should detect not full dimensionality" ) {
429 CAPTURE( d );
430 REQUIRE( d == 1 );
431 }
432 THEN( "QuickHull should detect not full dimensionality" ) {
433 REQUIRE( ok == false );
434 REQUIRE( status == QHull::Status::NotFullDimensional );
435 }
436 }
437
438 GIVEN( "Given 100 aligned points + another not aligned" ) {
439 std::vector< Point > V = { Point{ 3, 2, -1, 2 } };
440 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
441 X.push_back( X[ 0 ] + Point(-1,1,-1,0) );
442 std::shuffle( X.begin(), X.end(), g );
443 auto I = Affine::affineSubset( X );
444 auto d = Affine::affineDimension( X );
445 QHull hull;
446 hull.setInput( X, false );
447 bool ok = hull.computeConvexHull();
448 auto status = hull.status();
449 THEN( "AffineSubset should detect not full dimensionality (dimension 2)" ) {
450 CAPTURE( d );
451 REQUIRE( d == 2 );
452 }
453 THEN( "QuickHull should detect not full dimensionality" ) {
454 REQUIRE( ok == false );
455 REQUIRE( status == QHull::Status::NotFullDimensional );
456 }
457 }
458
459 GIVEN( "Given 100 points on a lattice 2d plane" ) {
460 std::vector< Point > V = { Point{ 3, 1, -1, 2 }, Point{ -2, 2, 1, -1 } };
461 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
462 auto I = Affine::affineSubset( X );
463 auto d = Affine::affineDimension( X );
464 QHull hull;
465 hull.setInput( X, false );
466 bool ok = hull.computeConvexHull();
467 auto status = hull.status();
468 THEN( "AffineSubset should detect not full dimensionality (dimension 2)" ) {
469 CAPTURE( d );
470 REQUIRE( d == 2 );
471 }
472 THEN( "QuickHull should detect not full dimensionality" ) {
473 REQUIRE( ok == false );
474 REQUIRE( status == QHull::Status::NotFullDimensional );
475 }
476 }
477
478 GIVEN( "Given 100 points on a lattice plane + a point just outside" ) {
479 std::vector< Point > V = { Point{ 3, 1, -1, 2 }, Point{ -2, 2, 1, -1 } };
480 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
481 X.push_back( X[ 0 ] + Point(-1,1,-1,0) );
482 std::shuffle( X.begin(), X.end(), g );
483 auto I = Affine::affineSubset( X );
484 auto d = Affine::affineDimension( X );
485 QHull hull;
486 hull.setInput( X, false );
487 bool ok = hull.computeConvexHull();
488 auto status = hull.status();
489 THEN( "AffineSubset should detect not full dimensionality (dimension 3)" ) {
490 CAPTURE( d );
491 REQUIRE( d == 3 );
492 }
493 THEN( "QuickHull should detect not full dimensionality" ) {
494 REQUIRE( ok == false );
495 REQUIRE( status == QHull::Status::NotFullDimensional );
496 }
497 }
498
499 GIVEN( "Given 100 points on a lattice 3d plane" ) {
500 std::vector< Point > V = { Point{ 3, 1, -1, 2 }, Point{ -2, 2, 1, -1 }, Point{ 0, -3, -2, 2 } };
501 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
502 auto I = Affine::affineSubset( X );
503 auto d = Affine::affineDimension( X );
504 QHull hull;
505 hull.setInput( X, false );
506 bool ok = hull.computeConvexHull();
507 auto status = hull.status();
508 THEN( "AffineSubset should detect not full dimensionality (dimension 3)" ) {
509 CAPTURE( d );
510 REQUIRE( d == 3 );
511 }
512 THEN( "QuickHull should detect not full dimensionality" ) {
513 REQUIRE( ok == false );
514 REQUIRE( status == QHull::Status::NotFullDimensional );
515 }
516 }
517
518 GIVEN( "Given 100 points on a lattice 3d plane + one exterior point" ) {
519 std::vector< Point > V = { Point{ 3, 1, -1, 2 }, Point{ -2, 2, 1, -1 }, Point{ 0, -3, -2, 2 } };
520 auto X = makeRandomLatticePointsFromDirVectors( 100, V );
521 X.push_back( X[ 0 ] + Point(-1,1,-1,0) );
522 std::shuffle( X.begin(), X.end(), g );
523 auto I = Affine::affineSubset( X );
524 auto d = Affine::affineDimension( X );
525 QHull hull;
526 hull.setInput( X, false );
527 bool ok = hull.computeConvexHull();
528 auto status = hull.status();
529 THEN( "AffineSubset should detect full dimensionality" ) {
530 CAPTURE( d );
531 REQUIRE( d == 4 );
532 }
533 THEN( "QuickHull should detect full dimensionality" ) {
534 REQUIRE( ok == true );
535 REQUIRE( status != QHull::Status::NotFullDimensional );
536 }
537 THEN( "QuickHull should have more than 4 vertices" ) {
538 REQUIRE( hull.nbVertices() > 4 );
539 }
540 THEN( "QuickHull should have more than 4 facets" ) {
541 REQUIRE( hull.nbFacets() > 4 );
542 }
543 }
544
545}
546
547
SMesh::Index Index
DGtal is the top-level namespace which contains all DGtal functions and types.
DGtal::uint32_t Dimension
Definition Common.h:119
STL namespace.
Aim: Utility class to determine the affine geometry of an input set of points. It provides exact resu...
Aim: a geometric kernel to compute the convex hull of digital points with integer-only arithmetic.
Aim: Implements the quickhull algorithm by Barber et al. , a famous arbitrary dimensional convex hull...
Definition QuickHull.h:141
MyPointD Point
CAPTURE(thicknessHV)
GIVEN("A cubical complex with random 3-cells")
std::vector< Point > randomPointsInBall(int nb, int R)
std::mt19937 g(rd())
std::random_device rd
std::vector< Point > makeRandomLatticePointsFromDirVectors(int nb, const vector< Point > &V)
REQUIRE(domain.isInside(aPoint))
SCENARIO("UnorderedSetByBlock< PointVector< 2, int > unit tests with 32 bits blocks", "[unorderedsetbyblock][2d]")