diff --git a/ChangeLog.md b/ChangeLog.md index 6258eee55d..d5db097696 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,11 +5,18 @@ ## New features / critical changes +- *Geometry* + - Add P-convexity, another characterization of full convexity, + which is faster to compute (Jacques-Olivier Lachaud, + [#1736](https://github.com/DGtal-team/DGtal/pull/1736)) + ## Changes + - *General* - Removing DGtal installation with `homebrew` on mac (the formula being deprecated) (David Coeurjolly, [#1738](https://github.com/DGtal-team/DGtal/pull/1738)) + ## Bug fixes - *General* - Fixing typos int the cmake script (David Coeurjolly, [#1739](https://github.com/DGtal-team/DGtal/pull/1739)) @@ -17,6 +24,8 @@ - *DEC* - Minor update of the DEC package documentation (David Coeurjolly, [#1734](https://github.com/DGtal-team/DGtal/pull/1734)) + + # DGtal 1.4 ## New features / critical changes diff --git a/examples/geometry/volumes/CMakeLists.txt b/examples/geometry/volumes/CMakeLists.txt index 732b91dc28..994b589c79 100644 --- a/examples/geometry/volumes/CMakeLists.txt +++ b/examples/geometry/volumes/CMakeLists.txt @@ -8,6 +8,7 @@ set( DGTAL_EXAMPLES_SRC exampleBoundedLatticePolytopeCount2D exampleBoundedLatticePolytopeCount3D exampleBoundedLatticePolytopeCount4D + pConvexity-benchmark ) foreach(FILE ${DGTAL_EXAMPLES_SRC}) diff --git a/examples/geometry/volumes/pConvexity-benchmark.cpp b/examples/geometry/volumes/pConvexity-benchmark.cpp new file mode 100644 index 0000000000..1044934b3b --- /dev/null +++ b/examples/geometry/volumes/pConvexity-benchmark.cpp @@ -0,0 +1,614 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +/** + * @file geometry/volumes/pConvexity-benchmark.cpp + * @ingroup Examples + * @author Jacques-Olivier Lachaud (\c jacques-olivier.lachaud@univ-savoie.fr ) + * Laboratory of Mathematics (CNRS, UMR 5127), University of Savoie, France + * + * @date 2024/06/26 + * + * An example file named pConvexity-benchmark + * + * This file is part of the DGtal library. + */ + +#include +#include +#include +#include +#include "DGtal/base/Common.h" +#include "DGtal/kernel/SpaceND.h" +#include "DGtal/kernel/domains/HyperRectDomain.h" +#include "DGtal/kernel/sets/DigitalSetBySTLSet.h" +#include "DGtal/topology/KhalimskySpaceND.h" +#include "DGtal/shapes/Shapes.h" +#include "DGtal/geometry/volumes/PConvexity.h" +#include "DGtal/geometry/volumes/DigitalConvexity.h" + +/** + This example compares the speed of computation of P-convexity wrt + to the computation of full convexity. Both definitions are + equivalent but P-convexity is faster to compute, especially in higher dimensions. + +\verbatim +pConvexity-benchmark +\endverbatim + + Simply run the benchmark (it will take more than 1 hour on a M2 pro chip). It + produces 9 files "timings-p-convexity-Z[d].txt", + "timings-fc-convexity-Z[d].txt", and + "timings-fcf-convexity-Z[d].txt", corresponding to P-convexity/full + convexity/fast full convexity computation in Z[d]. Each data is a + triplet (number of points, timings in ms, isConvex). + + \image html timings-Z2.png "Computation times (ms) of P-convexity wrt full convexity in Z2 as a function of the cardinal of the digital set. P-convexity is generally 2-3x faster to compute." + \image html timings-Z3.png "Computation times (ms) of P-convexity wrt full convexity in Z3 as a function of the cardinal of the digital set. P-convexity is generally 3-10x faster to compute. The difference is greater for non P-convex / non fully convex sets." + \image html timings-Z4.png "Computation times (ms) of P-convexity wrt full convexity in Z4 as a function of the cardinal of the digital set. P-convexity is generally 3-20x faster to compute. The difference is greater for non P-convex / non fully convex sets." + + \example geometry/volumes/pConvexity-benchmark.cpp +*/ + + +using namespace std; +using namespace DGtal; + +double rand01() { return double( rand() ) / double( RAND_MAX ); } + +template +void +timingsPConvexity( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t nb_vertices, std::size_t range, + double pconvexity_probability = 0.5 ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " P-convexities in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + // Create vertices + std::vector< Point > V; + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + V.push_back( p ); + } + // create 0-convex or fully convex set. + std::vector< Point > X; + bool force_pconvexity = rand01() < pconvexity_probability; + if ( force_pconvexity ) + X = dconv.envelope( V ); + else + { + auto P = dconv.CvxH( V ); + P.getPoints( X ); + } + // Analyse P-convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_pconvex = pconv.isPConvex( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_pconvex ) ); + if ( force_pconvexity && ! is_pconvex ) + trace.warning() << "Invalid computation of either FC* or P-convexity !" << std::endl; + } +} + +template +void +timingsFullConvexity( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t nb_vertices, std::size_t range, + double fconvexity_probability = 0.5 ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " full convexities in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + // Create vertices + std::vector< Point > V; + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + V.push_back( p ); + } + // create 0-convex or fully convex set. + std::vector< Point > X; + bool force_fconvexity = rand01() < fconvexity_probability; + if ( force_fconvexity ) + X = dconv.envelope( V ); + else + { + auto P = dconv.CvxH( V ); + P.getPoints( X ); + } + // Analyse full convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_fconvex = dconv.isFullyConvex( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_fconvex ) ); + if ( force_fconvexity && ! is_fconvex ) + trace.warning() << "Invalid computation of either FC* or full convexity !" << std::endl; + } +} + +template +void +timingsFullConvexityFast( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t nb_vertices, std::size_t range, + double fconvexity_probability = 0.5 ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " full convexities in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + // Create vertices + std::vector< Point > V; + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + V.push_back( p ); + } + // create 0-convex or fully convex set. + std::vector< Point > X; + bool force_fconvexity = rand01() < fconvexity_probability; + if ( force_fconvexity ) + X = dconv.envelope( V ); + else + { + auto P = dconv.CvxH( V ); + P.getPoints( X ); + } + // Analyse full convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_fconvex = dconv.isFullyConvexFast( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_fconvex ) ); + if ( force_fconvexity && ! is_fconvex ) + trace.warning() << "Invalid computation of either FC* or full convexity !" << std::endl; + } +} + + +template +void +timingsPConvexityNonConvex +( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t range ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " P-convexities in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + double filling_probability = 0.1 + 0.9 * double( n ) / double( nb_tries ); + // Create vertices + std::set< Point > S; + std::size_t nb_vertices + = std::size_t( filling_probability * ceil( pow( range, dim ) ) ); + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + S.insert( p ); + } + // create digital set. + std::vector< Point > X( S.cbegin(), S.cend() ); + // Analyse P-convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_pconvex = pconv.isPConvex( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_pconvex ) ); + } +} + +template +void +timingsFullConvexityNonConvex +( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t range ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " full convexities in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + double filling_probability = 0.1 + 0.9 * double( n ) / double( nb_tries ); + // Create vertices + std::set< Point > S; + std::size_t nb_vertices + = std::size_t( filling_probability * ceil( pow( range, dim ) ) ); + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + S.insert( p ); + } + // create digital set. + std::vector< Point > X( S.cbegin(), S.cend() ); + // Analyse full convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_fconvex = dconv.isFullyConvex( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_fconvex ) ); + } +} + + +template +void +timingsFullConvexityFastNonConvex +( std::vector< std::tuple< std::size_t, double, bool > >& results, + std::size_t nb_tries, std::size_t range ) +{ + typedef KhalimskySpaceND KSpace; + typedef typename KSpace::Point Point; + typedef typename KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + DConvexity dconv( Point::diagonal( -1 ), Point::diagonal( range ) ); + PConvexity pconv; + Domain domain( Point::diagonal( 0 ), Point::diagonal( range ) ); + std::cout << "Computing " << nb_tries << " full convexities (fast) in Z" << dim << std::endl; + for ( auto n = 0; n < nb_tries; ++n ) + { + double filling_probability = 0.1 + 0.9 * double( n ) / double( nb_tries ); + // Create vertices + std::set< Point > S; + std::size_t nb_vertices + = std::size_t( filling_probability * ceil( pow( range, dim ) ) ); + for ( auto i = 0; i < nb_vertices; i++ ) { + Point p; + for ( auto j = 0; j < dim; j++ ) p[ j ] = rand() % range; + S.insert( p ); + } + // create digital set. + std::vector< Point > X( S.cbegin(), S.cend() ); + // Analyse full convexity + std::chrono::high_resolution_clock::time_point + t1 = std::chrono::high_resolution_clock::now(); + bool is_fconvex = dconv.isFullyConvexFast( X ); + std::chrono::high_resolution_clock::time_point + t2 = std::chrono::high_resolution_clock::now(); + double dt = std::chrono::duration_cast(t2 - t1).count(); + results.push_back( std::make_tuple( X.size(), dt/1e6, is_fconvex ) ); + } +} + + + +void outputResults( Dimension dim, + const std::vector< std::tuple< std::size_t, double, bool > >& results, + const std::string& fname ) +{ + std::ofstream output( fname ); + output << "# Results of " << results.size() << " P-convexity computations in Z" + << dim << std::endl + << "# Card(X) time(ms) p-convex?" << std::endl; + for ( auto&& r : results ) + output << std::get<0>( r ) << " " << std::get<1>( r ) << " " << std::get<2>( r ) + << std::endl; + output.close(); +} + +/* + Display results using gnuplot + + plot "./timings-p-convexity-Z2.txt" using 1:2 w p, "./timings-p-convexity-Z3.txt" using 1:2 w p,"./timings-p-convexity-Z4.txt" using 1:2 w p, 0.2e-5*x*log(x) w l lw 2 + + plot "./timings-p-convexity-Z2.txt" using 1:($3 == 1 ? $2 : 1/0) title "P-convex in Z2" w p, "./timings-p-convexity-Z2.txt" using 1:($3 == 0 ? $2 : 1/0) title "non P-convex in Z2" w p, 0.2e-5*x*log(x) w l lw 2 + + plot "./timings-p-convexity-Z3.txt" using 1:($3 == 1 ? $2 : 1/0) title "P-convex in Z3" w p, "./timings-p-convexity-Z3.txt" using 1:($3 == 0 ? $2 : 1/0) title "non P-convex in Z3" w p, 0.4e-5*x*log(x) w l lw 2 + + plot "./timings-p-convexity-Z4.txt" using 1:($3 == 1 ? $2 : 1/0) title "P-convex in Z4" w p, "./timings-p-convexity-Z4.txt" using 1:($3 == 0 ? $2 : 1/0) title "non P-convex in Z4" w p, 0.4e-5*x*log(x) w l lw 2 + + set terminal eps font "Helvetica,14" + set key bottom right + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-Z2.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: P-convex charac. (in Z2)" w p pt 5 lc rgb "blue", "./timings-p-convexity-Z2.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: P-convex charac. (in Z2)" w p pt 4 lc rgb "blue", "./timings-fcf-convexity-Z2.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: cellular charac. (in Z2)" w p pt 7 lc rgb "black", "./timings-fcf-convexity-Z2.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: cellular charac. (in Z2)" w p pt 6 lc rgb "black", "./timings-fc-convexity-Z2.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: discrete morphological charac. (in Z2)" w p pt 13 lc rgb "magenta", "./timings-fc-convexity-Z2.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: discrete morphological charac. (in Z2)" w p pt 12 lc rgb "magenta" + + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-Z3.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: P-convex charac. (in Z3)" w p pt 5 lc rgb "blue", "./timings-p-convexity-Z3.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: P-convex charac. (in Z3)" w p pt 4 lc rgb "blue", "./timings-fcf-convexity-Z3.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: cellular charac. (in Z3)" w p pt 7 lc rgb "black", "./timings-fcf-convexity-Z3.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: cellular charac. (in Z3)" w p pt 6 lc rgb "black", "./timings-fc-convexity-Z3.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: discrete morphological charac. (in Z3)" w p pt 13 lc rgb "magenta", "./timings-fc-convexity-Z3.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: discrete morphological charac. (in Z3)" w p pt 12 lc rgb "magenta" + + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-Z4.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: P-convex charac. (in Z4)" w p pt 5 lc rgb "blue", "./timings-p-convexity-Z4.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: P-convex charac. (in Z4)" w p pt 4 lc rgb "blue", "./timings-fcf-convexity-Z4.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: cellular charac. (in Z4)" w p pt 7 lc rgb "black", "./timings-fcf-convexity-Z4.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: cellular charac. (in Z4)" w p pt 6 lc rgb "black", "./timings-fc-convexity-Z4.txt" using 1:($3 == 1 ? $2 : 1/0) title "FC: discrete morphological charac. (in Z4)" w p pt 13 lc rgb "magenta", "./timings-fc-convexity-Z4.txt" using 1:($3 == 0 ? $2 : 1/0) title "non FC: discrete morphological charac. (in Z4)" w p pt 12 lc rgb "magenta" + + set terminal eps font "Helvetica,12" + set key bottom right + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-ncvx-Z2.txt" using 1:2 title "P-convex charac. (in Z2)" w p pt 4 lc rgb "blue", "./timings-fc-convexity-ncvx-Z2.txt" using 1:2 title "discrete morphological charac. (in Z2)" w p pt 12 lc rgb "magenta", "./timings-fcf-convexity-ncvx-Z2.txt" using 1:2 title "cellular charac. (in Z2)" w p pt 6 lc rgb "black" + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-ncvx-Z3.txt" using 1:2 title "P-convex charac. (in Z3)" w p pt 4 lc rgb "blue", "./timings-fc-convexity-ncvx-Z3.txt" using 1:2 title "discrete morphological charac. (in Z3)" w p pt 12 lc rgb "magenta", "./timings-fcf-convexity-ncvx-Z3.txt" using 1:2 title "cellular charac. (in Z3)" w p pt 6 lc rgb "black" + + plot [1e2:1e7][1e-2:1e4] 1e-6*x*log(x) w l lw 3, "./timings-p-convexity-ncvx-Z4.txt" using 1:2 title "P-convex charac. (in Z4)" w p pt 4 lc rgb "blue", "./timings-fc-convexity-ncvx-Z4.txt" using 1:2 title "discrete morphological charac. (in Z4)" w p pt 12 lc rgb "magenta", "./timings-fcf-convexity-ncvx-Z4.txt" using 1:2 title "cellular charac. (in Z4)" w p pt 6 lc rgb "black" + + +*/ + +int main( int argc, char* argv[] ) +{ + // P-convexity + srand( 0 ); + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsPConvexity<2>( R2, 50, 3, 100, 0.5 ); + timingsPConvexity<2>( R2, 50, 4, 200, 0.5 ); + timingsPConvexity<2>( R2, 50, 5, 400, 0.5 ); + timingsPConvexity<2>( R2, 50, 5, 600, 0.5 ); + timingsPConvexity<2>( R2, 50, 5, 800, 0.5 ); + timingsPConvexity<2>( R2, 25, 5,1200, 0.5 ); + timingsPConvexity<2>( R2, 25, 5,2000, 0.5 ); + outputResults( 2, R2, "timings-p-convexity-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsPConvexity<3>( R3, 50, 3, 10, 0.5 ); + timingsPConvexity<3>( R3, 50, 4, 20, 0.5 ); + timingsPConvexity<3>( R3, 50, 5, 40, 0.5 ); + timingsPConvexity<3>( R3, 50, 5, 80, 0.5 ); + timingsPConvexity<3>( R3, 25, 5, 160, 0.5 ); + timingsPConvexity<3>( R3, 25, 5, 320, 0.5 ); + outputResults( 3, R3, "timings-p-convexity-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsPConvexity<4>( R4, 50, 5, 10, 0.5 ); + timingsPConvexity<4>( R4, 50, 5, 15, 0.5 ); + timingsPConvexity<4>( R4, 50, 5, 20, 0.5 ); + timingsPConvexity<4>( R4, 50, 5, 30, 0.5 ); + timingsPConvexity<4>( R4, 25, 5, 40, 0.5 ); + timingsPConvexity<4>( R4, 25, 5, 60, 0.5 ); + timingsPConvexity<4>( R4, 15, 6, 80, 0.5 ); + timingsPConvexity<4>( R4, 15, 6, 100, 0.5 ); + timingsPConvexity<4>( R4, 15, 6, 120, 0.5 ); + outputResults( 4, R4, "timings-p-convexity-Z4.txt" ); + } + + // Full convexity + srand( 0 ); + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsFullConvexity<2>( R2, 50, 3, 100, 0.5 ); + timingsFullConvexity<2>( R2, 50, 4, 200, 0.5 ); + timingsFullConvexity<2>( R2, 50, 5, 400, 0.5 ); + timingsFullConvexity<2>( R2, 50, 5, 600, 0.5 ); + timingsFullConvexity<2>( R2, 50, 5, 800, 0.5 ); + timingsFullConvexity<2>( R2, 25, 5,1200, 0.5 ); + timingsFullConvexity<2>( R2, 25, 5,2000, 0.5 ); + outputResults( 2, R2, "timings-fc-convexity-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsFullConvexity<3>( R3, 50, 3, 10, 0.5 ); + timingsFullConvexity<3>( R3, 50, 4, 20, 0.5 ); + timingsFullConvexity<3>( R3, 50, 5, 40, 0.5 ); + timingsFullConvexity<3>( R3, 50, 5, 80, 0.5 ); + timingsFullConvexity<3>( R3, 25, 5, 160, 0.5 ); + timingsFullConvexity<3>( R3, 25, 5, 320, 0.5 ); + outputResults( 3, R3, "timings-fc-convexity-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsFullConvexity<4>( R4, 50, 5, 10, 0.5 ); + timingsFullConvexity<4>( R4, 50, 5, 15, 0.5 ); + timingsFullConvexity<4>( R4, 50, 5, 20, 0.5 ); + timingsFullConvexity<4>( R4, 50, 5, 30, 0.5 ); + timingsFullConvexity<4>( R4, 25, 5, 40, 0.5 ); + timingsFullConvexity<4>( R4, 25, 5, 60, 0.5 ); + timingsFullConvexity<4>( R4, 15, 6, 80, 0.5 ); + timingsFullConvexity<4>( R4, 10, 6, 100, 0.5 ); + timingsFullConvexity<4>( R4, 5, 6, 120, 0.5 ); + outputResults( 4, R4, "timings-fc-convexity-Z4.txt" ); + } + + // Full convexity fast + srand( 0 ); + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsFullConvexityFast<2>( R2, 50, 3, 100, 0.5 ); + timingsFullConvexityFast<2>( R2, 50, 4, 200, 0.5 ); + timingsFullConvexityFast<2>( R2, 50, 5, 400, 0.5 ); + timingsFullConvexityFast<2>( R2, 50, 5, 600, 0.5 ); + timingsFullConvexityFast<2>( R2, 50, 5, 800, 0.5 ); + timingsFullConvexityFast<2>( R2, 25, 5,1200, 0.5 ); + timingsFullConvexityFast<2>( R2, 25, 5,2000, 0.5 ); + outputResults( 2, R2, "timings-fcf-convexity-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsFullConvexityFast<3>( R3, 50, 3, 10, 0.5 ); + timingsFullConvexityFast<3>( R3, 50, 4, 20, 0.5 ); + timingsFullConvexityFast<3>( R3, 50, 5, 40, 0.5 ); + timingsFullConvexityFast<3>( R3, 50, 5, 80, 0.5 ); + timingsFullConvexityFast<3>( R3, 25, 5, 160, 0.5 ); + timingsFullConvexityFast<3>( R3, 25, 5, 320, 0.5 ); + outputResults( 3, R3, "timings-fcf-convexity-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsFullConvexityFast<4>( R4, 50, 5, 10, 0.5 ); + timingsFullConvexityFast<4>( R4, 50, 5, 15, 0.5 ); + timingsFullConvexityFast<4>( R4, 50, 5, 20, 0.5 ); + timingsFullConvexityFast<4>( R4, 50, 5, 30, 0.5 ); + timingsFullConvexityFast<4>( R4, 25, 5, 40, 0.5 ); + timingsFullConvexityFast<4>( R4, 25, 5, 60, 0.5 ); + timingsFullConvexityFast<4>( R4, 15, 6, 80, 0.5 ); + timingsFullConvexityFast<4>( R4, 10, 6, 100, 0.5 ); + timingsFullConvexityFast<4>( R4, 5, 6, 120, 0.5 ); + outputResults( 4, R4, "timings-fcf-convexity-Z4.txt" ); + } + + // P-convexity + srand( 0 ); + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsPConvexityNonConvex<2>( R2, 50, 100 ); + timingsPConvexityNonConvex<2>( R2, 50, 200 ); + timingsPConvexityNonConvex<2>( R2, 50, 400 ); + timingsPConvexityNonConvex<2>( R2, 50, 600 ); + timingsPConvexityNonConvex<2>( R2, 50, 800 ); + timingsPConvexityNonConvex<2>( R2, 50, 1200 ); + timingsPConvexityNonConvex<2>( R2, 50, 2000 ); + outputResults( 2, R2, "timings-p-convexity-ncvx-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsPConvexityNonConvex<3>( R3, 50, 20 ); + timingsPConvexityNonConvex<3>( R3, 50, 40 ); + timingsPConvexityNonConvex<3>( R3, 50, 80 ); + timingsPConvexityNonConvex<3>( R3, 50, 160 ); + timingsPConvexityNonConvex<3>( R3, 50, 320 ); + outputResults( 3, R3, "timings-p-convexity-ncvx-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsPConvexityNonConvex<4>( R4, 50, 10 ); + timingsPConvexityNonConvex<4>( R4, 50, 20 ); + timingsPConvexityNonConvex<4>( R4, 50, 30 ); + timingsPConvexityNonConvex<4>( R4, 40, 40 ); + timingsPConvexityNonConvex<4>( R4, 20, 50 ); + outputResults( 4, R4, "timings-p-convexity-ncvx-Z4.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsFullConvexityNonConvex<2>( R2, 50, 100 ); + timingsFullConvexityNonConvex<2>( R2, 50, 200 ); + timingsFullConvexityNonConvex<2>( R2, 50, 400 ); + timingsFullConvexityNonConvex<2>( R2, 50, 600 ); + timingsFullConvexityNonConvex<2>( R2, 50, 800 ); + timingsFullConvexityNonConvex<2>( R2, 50, 1200 ); + timingsFullConvexityNonConvex<2>( R2, 50, 2000 ); + outputResults( 2, R2, "timings-fc-convexity-ncvx-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsFullConvexityNonConvex<3>( R3, 50, 20 ); + timingsFullConvexityNonConvex<3>( R3, 50, 40 ); + timingsFullConvexityNonConvex<3>( R3, 50, 80 ); + timingsFullConvexityNonConvex<3>( R3, 40, 160 ); + timingsFullConvexityNonConvex<3>( R3, 25, 320 ); + outputResults( 3, R3, "timings-fc-convexity-ncvx-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsFullConvexityNonConvex<4>( R4, 50, 10 ); + timingsFullConvexityNonConvex<4>( R4, 50, 20 ); + timingsFullConvexityNonConvex<4>( R4, 50, 30 ); + timingsFullConvexityNonConvex<4>( R4, 40, 40 ); + timingsFullConvexityNonConvex<4>( R4, 20, 50 ); + outputResults( 4, R4, "timings-fc-convexity-ncvx-Z4.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R2; + timingsFullConvexityFastNonConvex<2>( R2, 50, 100 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 200 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 400 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 600 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 800 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 1200 ); + timingsFullConvexityFastNonConvex<2>( R2, 50, 2000 ); + outputResults( 2, R2, "timings-fcf-convexity-ncvx-Z2.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R3; + timingsFullConvexityFastNonConvex<3>( R3, 50, 20 ); + timingsFullConvexityFastNonConvex<3>( R3, 50, 40 ); + timingsFullConvexityFastNonConvex<3>( R3, 50, 80 ); + timingsFullConvexityFastNonConvex<3>( R3, 40, 160 ); + timingsFullConvexityFastNonConvex<3>( R3, 25, 320 ); + outputResults( 3, R3, "timings-fcf-convexity-ncvx-Z3.txt" ); + } + if ( false ) + { + std::vector< std::tuple< std::size_t, double, bool > > R4; + timingsFullConvexityFastNonConvex<4>( R4, 50, 10 ); + timingsFullConvexityFastNonConvex<4>( R4, 50, 20 ); + timingsFullConvexityFastNonConvex<4>( R4, 50, 30 ); + timingsFullConvexityFastNonConvex<4>( R4, 40, 40 ); + timingsFullConvexityFastNonConvex<4>( R4, 20, 50 ); + outputResults( 4, R4, "timings-fcf-convexity-ncvx-Z4.txt" ); + } + + + return 0; +} diff --git a/src/DGtal/doc/global.bib b/src/DGtal/doc/global.bib index 4cd8671e93..63e3de10ed 100644 --- a/src/DGtal/doc/global.bib +++ b/src/DGtal/doc/global.bib @@ -858,6 +858,23 @@ @article{lachaud_jmiv_2022 year = 2022 } +@article{feschet_2023_jmiv, + author = {Fabien Feschet and + Jacques{-}Olivier Lachaud}, + title = {An Envelope Operator for Full Convexity to Define Polyhedral Models + in Digital Spaces}, + journal = {J. Math. Imaging Vis.}, + volume = {65}, + number = {5}, + pages = {754--769}, + year = {2023}, + url = {https://doi.org/10.1007/s10851-023-01155-w}, + doi = {10.1007/s10851-023-01155-w}, + timestamp = {Sat, 30 Sep 2023 10:19:35 +0200}, + biburl = {https://dblp.org/rec/journals/jmiv/FeschetL23.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} +} + @InProceedings{feschet_2022_dgmm, author="Feschet, Fabien and Lachaud, Jacques-Olivier", @@ -865,7 +882,7 @@ @InProceedings{feschet_2022_dgmm and Naegel, Beno{\^i}t and Kr{\"a}henb{\"u}hl, Adrien and Tajine, Mohamed", -title="Full Convexity for Polyhedral Models in Digital Spaces", +title="Full Convexity for Polyhedral Models in Digital Spaces", booktitle="Discrete Geometry and Mathematical Morphology", year="2022", publisher="Springer International Publishing", @@ -877,6 +894,23 @@ @InProceedings{feschet_2022_dgmm series={Lecture Notes in Computer Science} } +@InProceedings{feschet_2024_dgmm, +author="Feschet, Fabien +and Lachaud, Jacques-Olivier", +editor="Brunetti, Sara +and Frosini, Andrea +and Rinaldi, Simone", +title="New Characterizations of Full Convexity", +booktitle="Discrete Geometry and Mathematical Morphology (DGMM'2024), April 15-18, Firenze", +year="2024", +publisher="Springer Nature Switzerland", +address="Cham", +pages="41--53", +abstract="Full convexity has been recently proposed as an alternative definition of digital convexity. In contrast to classical definitions, fully convex sets are always connected and even simply connected whatever the dimension, while remaining digitally convex in the usual sense. Several characterizations were proposed in former works, either based on lattice intersection enumeration with several convex hulls, or using the idempotence of an envelope operator. We continue these efforts by studying simple properties of real convex sets whose digital counterparts remain largely misunderstood. First we study if we can define full convexity through variants of the usual continuous convexity via segments inclusion, i.e. ``for all pair of points of X, the straight segment joining them must lie within the set X''. We show an equivalence of full convexity with this segment convexity in dimension 2, and counterexamples starting from dimension 3. If we consider now d-simplices instead of a segment (2-simplex), we achieve an equivalence in arbitrary dimension d. Secondly, we exhibit another characterization of full convexity, which is recursive with respect to the dimension and uses simple axis projections. This latter characterization leads to two immediate applications: a proof that digital balls are indeed fully convex, and a natural progressive measure of full convexity for arbitrary digital sets.", +isbn="978-3-031-57793-2" +} + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% End/Geometry/volumes diff --git a/src/DGtal/geometry/doc/images/full-convexity-measure.png b/src/DGtal/geometry/doc/images/full-convexity-measure.png new file mode 100644 index 0000000000..635ca8f0bc Binary files /dev/null and b/src/DGtal/geometry/doc/images/full-convexity-measure.png differ diff --git a/src/DGtal/geometry/doc/images/timings-Z2.png b/src/DGtal/geometry/doc/images/timings-Z2.png new file mode 100644 index 0000000000..edb71386f1 Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-Z2.png differ diff --git a/src/DGtal/geometry/doc/images/timings-Z3.png b/src/DGtal/geometry/doc/images/timings-Z3.png new file mode 100644 index 0000000000..8817c9d857 Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-Z3.png differ diff --git a/src/DGtal/geometry/doc/images/timings-Z4.png b/src/DGtal/geometry/doc/images/timings-Z4.png new file mode 100644 index 0000000000..ed42edcac9 Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-Z4.png differ diff --git a/src/DGtal/geometry/doc/images/timings-ncvx-Z2.png b/src/DGtal/geometry/doc/images/timings-ncvx-Z2.png new file mode 100644 index 0000000000..e440f884da Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-ncvx-Z2.png differ diff --git a/src/DGtal/geometry/doc/images/timings-ncvx-Z3.png b/src/DGtal/geometry/doc/images/timings-ncvx-Z3.png new file mode 100644 index 0000000000..2efa12f766 Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-ncvx-Z3.png differ diff --git a/src/DGtal/geometry/doc/images/timings-ncvx-Z4.png b/src/DGtal/geometry/doc/images/timings-ncvx-Z4.png new file mode 100644 index 0000000000..1d5962200e Binary files /dev/null and b/src/DGtal/geometry/doc/images/timings-ncvx-Z4.png differ diff --git a/src/DGtal/geometry/doc/moduleDigitalConvexity.dox b/src/DGtal/geometry/doc/moduleDigitalConvexity.dox index ade4ef2d38..20567d66b4 100644 --- a/src/DGtal/geometry/doc/moduleDigitalConvexity.dox +++ b/src/DGtal/geometry/doc/moduleDigitalConvexity.dox @@ -6,7 +6,7 @@ namespace DGtal { /** -@page moduleDigitalConvexity Digital convexity and full digital convexity +@page moduleDigitalConvexity Digital convexity, full convexity and P-convexity @writers Jacques-Olivier Lachaud @@ -15,14 +15,16 @@ namespace DGtal { Part of the \ref packageGeometry. This part of the manual describes tools associated to a new definition -of digital convexity, called the full convexity -\cite lachaud_dgmm_2021 \cite lachaud_jmiv_2022 . This new definition solves many problems -related to the usual definition of digital convexity, like possible -non connectedness or non simple connectedness, while encompassing its -desirable features. Fully convex sets are digitally convex, but are -also connected and simply connected. They have a morphological -characterisation, which induces a simple convexity test -algorithm. As an important example, arithmetic planes are fully convex too. +of digital convexity, called the \b full \b convexity \cite lachaud_dgmm_2021 \cite lachaud_jmiv_2022 . This new definition solves +many problems related to the usual definition of digital convexity, +like possible non connectedness or non simple connectedness, while +encompassing its desirable features. Fully convex sets are digitally +convex, but are also connected and simply connected. They have a +morphological characterisation, which induces a simple convexity test +algorithm. As an important example, arithmetic planes are fully convex +too. Since DGtal release 1.5, \b P-convexity can also be checked in arbitrary +dimension. It is worth to note that it is equivalent to full +convexity, and is generally faster to check \cite feschet_2024_dgmm . [TOC] @@ -36,7 +38,8 @@ testDigitalConvexity.cpp, testEhrhartPolynomial.cpp, geometry/volumes/exampleBoundedLatticePolytopeCount2D.cpp, geometry/volumes/exampleBoundedLatticePolytopeCount3D.cpp, -geometry/volumes/exampleBoundedLatticePolytopeCount4D.cpp . +geometry/volumes/exampleBoundedLatticePolytopeCount4D.cpp, +geometry/volumes/pConvexity-benchmark.cpp . This module relies on module \ref moduleQuickHull for convex hull computations in arbitrary dimensions. @@ -47,7 +50,7 @@ some applications of full convexity. See \ref moduleEnvelope to see how to build fully convex hulls and digital polyhedra. -@section dgtal_dconvexity_sec1 Introduction to full digital convexity +@section dgtal_dconvexity_sec1 Introduction to full convexity The usual definition for \b digital \b convexity is as follows. For some digital set \f$ S \subset \mathbb{Z}^d \f$, \f$ S \f$ is said to be \e @@ -99,6 +102,14 @@ Subconvexity is a useful for notion for digital contour and surface analysis. It tells which subsets of these digital sets are \e tangent to them. +The notion of \b P-convexity has been proposed in \cite feschet_2024_dgmm . A set \f$ S \subset \mathbb{Z}^d \f$ is \b P-convex if and only if + +- \f$ S \f$ is 0-convex, +- and, if \f$ d > 1 \f$, the $d$ projections of $S$ along each axis is P-convex (in \f$ \mathbb{Z}^{d-1} \f$). + +P-convexity is equivalent to full convexity as shown in \cite feschet_2024_dgmm . + + @section dgtal_dconvexity_sec2 Classes and functions related to digital convexity Three classes help to check digital convexity. @@ -115,6 +126,10 @@ Three classes help to check digital convexity. BoundedLatticePolytope and CellGeometry objects and to check digital convexity and subconvexity. +- PConvexity provides functions to check digital convexity and + P-convexity, as well a full convexity measure that is + characteristics of full convex sets (or equivalently P-convex sets). + @subsection dgtal_dconvexity_sec21 Lattice polytopes @@ -200,7 +215,7 @@ Lattice point retrieval services: - BoundedLatticePolytope::insertPoints inserts the lattice points in the polytope into some point set The class ConvexityHelper also provides several static methods related to BoundedLatticePolytope: -- ConvexityHelper::computeLatticePolytope computes a BoundedLatticePolytope from an arbitrary range of points (use QuickHull algorithm, \see moduleQuickHull). +- ConvexityHelper::computeLatticePolytope computes a BoundedLatticePolytope from an arbitrary range of points (use QuickHull algorithm, see also \ref moduleQuickHull ). - ConvexityHelper::compute3DTriangle computes the BoundedLatticePolytope enclosing a 3D triangle (faster way than above, but limited to 3D triangles). - ConvexityHelper::computeDegeneratedTriangle computes the BoundedLatticePolytope enclosing a degenerated triangle (the three points are aligned or non distinct, but may lie in arbitrary dimension). - ConvexityHelper::computeSegment computes the BoundedLatticePolytope enclosing a segment in arbitrary dimension. @@ -414,8 +429,225 @@ polynomial. There exists faster ways to compute it, which are however much more complex. See for instance LattE software: https://www.math.ucdavis.edu/~latte . +@section dgtal_dconvexity_sec3 P-convexity and convexity measures + +\b P-convexity is an equivalenr definition to full convexity, but with +a recursive definition \cite feschet_2024_dgmm . It provides thus a +quite simple characterization of full convexity, which is implemented +in class PConvexity. + +- constructor PConvexity::PConvexity allows you to force a safe internal integer representation for convexity computation (default choice induces `int64_t`), +- method PConvexity::is0Convex checks if a given range of digital points is 0-convex, +- method PConvexity::isPConvex checks if a given range of digital points is P-convex + +\code +// Minimal examples +#include "DGtal/geometry/volumes/PConvexity.h" +... +typedef SpaceND< 2, int > Space; +typedef Space::Point Point; + +PConvexity< Space > pconv; +std::vector V = { Point(0,0), Point(-1,0), Point(1,0), Point(0,1), Point(0,-1) }; +bool ok1 = pconv.is0Convex( V ); // should be true +bool ok2 = pconv.isPConvex( V ); // should be true +\endcode + +Furthermore, the recursive definition of P-convexity induces a natural +measure of full convexity for digital sets. Indeed the classical +d-dimensional \b digital \b convexity \b measure of digital set \a A is +\f[ M_d(A):=\frac{\#(A)}{\#(\mathrm{CvxH}(A) \cap \mathbf{Z}^d)}, \f] +and \f$ M_d(\emptyset)=1 \f$. + +The \b full \b convexity \b measure \f$ M_d^F \f$ for finite digital set \a A is +\f[ M_1^F(A) := M_1(A), \quad M_d^F(A) := M_d(A) \Pi_{k=1}^d M_{d-1}^F( \pi_k(A) )\quad\text{for}\,\, d > 1. \f] -@section dgtal_dconvexity_sec3 Rational polytopes +It coincides with the digital convexity measure in dimension 1, but may differ +starting from dimension 2, and is always less or equal to the convexity measure. + +- method PConvexity::convexityMeasure returns the convexity measure \f$ M_d(A) \f$ of the given range of digital points \a A, +- method PConvexity::fullConvexityMeasure returns the full convexity measure \f$ M_d^F(A) \f$ of the given range of digital points \a A. + +The figure below illustrates the links and the differences between the +two convexity measures Md and MdF on simple 2D examples. As one can +see, the usual convexity measure may not detect disconnectedness and it is +sensitive to specific alignments of pixels. On the contrary, full convexity is +globally more stable to perturbation and is never 1 when sets are +disconnected. + +\image html full-convexity-measure.png "Convexity measure M_d and full convexity measure M_d^F on small 2D digital sets." + + +@section dgtal_dconvexity_sec4 Best algorithm for checking full convexity + +There exists multiple equivalent characterizations of full +convexity. Some of them induce algorithms for checking if a digital +set is indeed fully convex, but the question ``which is the fastest +way'' remains. We recap here the different characterizations for \f$ X +\subset \mathbb{Z}^d \f$ being full convex, which lead to an arbitrary +dimensional algorithm for checking if a given range of \a n points \a +X is indeed fully convex: + +- \b discrete \b morphological \b characterization \cite lachaud_dgmm_2021 \cite lachaud_jmiv_2022 + + \a X is fully convex iff \f$ \forall \alpha \subset \{1, \ldots, + d\}, U_\alpha(X) = \mathrm{CvxH}(U_\alpha(X)) \cap \mathbb{Z}^d \f$, + + where \f$ U_\alpha \f$ is the discrete Minkowski sum defined as \f$ + U_\emptyset(X):= X \f$, and for any subset of directions \f$ \alpha + \in \{1,\ldots,d\} \f$ and a direction \f$ i \in \alpha \f$, \f$ + U_\alpha(X) := U_{\alpha \setminus \{i\}}(X) \cup \mathbf{e}_i (U_{\alpha + \setminus \{i\}}(X)) \f$ (the latter being the unit translation of + the set along direction \a i). + + This is implemented as DigitalConvexity::isFullyConvex . + +- \b cellular \b characterization \cite feschet_2023_jmiv (Lemma 13) + + \a X is fully convex iff \f$ X = \mathrm{Star}(\mathrm{Cvxh}(X)) + \cap \mathbb{Z}^d \f$. + + Furthermore, Theorem 5 of \cite feschet_2023_jmiv tells that \f$ + \mathrm{Star}(\mathrm{CvxH}(X)) \f$ is directly computable from \f$ + \mathrm{CvxH}(U_{\{1,\ldots,d\}}(X)) \f$, so one convex hull + computation is sufficient. + + This is implemented as DigitalConvexity::isFullyConvexFast . + +- \b envelope \b idempotence \cite feschet_2023_jmiv (Theorem 2) + + \a X is fully convex iff \f$ X = FC(X) \f$, where \f$ + FC(X):=\mathrm{Extr}( \mathrm{Skel}( \mathrm{Star}( \mathrm{CvxH}( X + ) ) ) ) \f$. + + This is implemented as DigitalConvexity::envelope, but its speed is + not tested here, since it is similar to the previous one + +- \b P-convexity \b characterization \cite feschet_2024_dgmm + + \a X is fully convex iff \f$ X \f$ is 0-convex, and, if \f$ d > 1 + \f$, the \a d projections of \f$ X \f$ along each axis is P-convex (in \f$ + \mathbb{Z}^{d-1} \f$). + + This is implemented as PConvexity::isPConvex . + +All three methods are tested for dimension 2, 3, and 4. In the first +set of experiments (left of figures) we compare them on generic +digital sets, which are randomly generated in a given range with a +target density from \f$ 10\% \f$ to \f$ 90\% \f$. In the second set of +experiments (right of figures), we limit our comparison to digital sets that are either +digitally 0-convex or fully convex. We then distinguish the timings +for checking full convexity depending on the output full convexity +property of the input set, since it influences the computation time +(typically increases it). + +Figures below sum up the different computation times for dimension 2, 3, and 4. + + + + + + + + + +
+\image html timings-ncvx-Z2.png "" width=98% + +\image html timings-Z2.png "" width=98% +
+This figure displays the respective computation times (ms) in \f$ \mathbb{Z}^2 \f$ +of P-convexity (as squares), discrete morphological characterization +(as diamonds) and cellular characterization (as disks), as a function +of the cardinal of the digital set. \b Left: digital sets are +randomly generated in a given range with a target point density from +\f$ 10\% \f$ to \f$ 90\% \f$. \b Right: digital sets are randomly generated +so that they are either 0-convex or fully convex. +
+ + + + + + + + + +
+\image html timings-ncvx-Z3.png "" width=98% + +\image html timings-Z3.png "" width=98% +
+This figure displays the respective computation times (ms) in \f$ \mathbb{Z}^3 \f$ +of P-convexity (as squares), discrete morphological characterization +(as diamonds) and cellular characterization (as disks), as a function +of the cardinal of the digital set. \b Left: digital sets are +randomly generated in a given range with a target point density from +\f$ 10\% \f$ to \f$ 90\% \f$. \b Right: digital sets are randomly generated +so that they are either 0-convex or fully convex. +
+ + + + + + + + + +
+\image html timings-ncvx-Z4.png "" width=98% + +\image html timings-Z4.png "" width=98% +
+This figure displays the respective computation times (ms) in \f$ \mathbb{Z}^4 \f$ +of P-convexity (as squares), discrete morphological characterization +(as diamonds) and cellular characterization (as disks), as a function +of the cardinal of the digital set. \b Left: digital sets are +randomly generated in a given range with a target point density from +\f$ 10\% \f$ to \f$ 90\% \f$. \b Right: digital sets are randomly generated +so that they are either 0-convex or fully convex. +
+ + +All approaches follow more or less a linearithmic or subquadratic complexity \f$ O(n +\log n) \f$ (tests are limited to dimension lower or equal to +4). However, we can distinguish three cases: + +- if \a X is not even convex, both P-convexity and the discrete + morphological characterization are the fastest with similar results, + since both their first step involves checking 0-convexity. The + cellular characterization is the slowest since it computes directly + a convex hull with additional vertices. + +- if \a X is convex but not fully convex, then the fastest method + remains the P-convexity, followed by the discrete morphological + characterization, and the slowest is the cellular characterization. + +- if \a X is fully convex, then the fastest method is again the + P-convexity, followed by the cellular characterization, and the + slowest is the discrete morphological characterization (especially + when increasing the dimension). + +@note Overall the \b P-convexity characterization (method +PConvexity::isPConvex) is \b almost \b always \b the \b fastest \b way \b to \b check \b the +\b full \b convexity of a given range of digital points, with speed-up from +2 to 100 times faster especially when increasing the dimension. This +new characterization of full convexity is thus for now the best way to +check if a digital set is fully convex. + +\warning One could be surprised by the last graph in 4D. Indeed we +expect convex hull computations in \f$ O(n^2) \f$ in 4D. We observe +here timings close to \f$ \Theta(n \log n) \f$. This is due to the +fact that the digital sets we are considering are 0-convex, so "full +of digital points". Convex hull computations by QuickHull are thus +faster than expected since many points are "hidden" in the shape. It +can also be seen on the left of this figure that timings depend more +on the range of random points than on the density (e.g. see horizontal +strokes of circles, corresponding to density increases), which +confirms the above explanation. + +@section dgtal_dconvexity_sec5 Rational polytopes You can also create bounded rational polytopes, i.e. polytopes with vertices with rational coordinates, with class @@ -451,7 +683,7 @@ You may also check digital convexity and compute cell covers with bounded ration half space constraints, hence the integer type should be chosen accordingly. -@section dgtal_dconvexity_sec4 Further notes +@section dgtal_dconvexity_sec6 Further notes The class BoundedLatticePolytope is different from the class LatticePolytope2D for the following two reasons: - the class LatticePolytope2D is limited to 2D diff --git a/src/DGtal/geometry/volumes/PConvexity.h b/src/DGtal/geometry/volumes/PConvexity.h new file mode 100644 index 0000000000..e029e8a515 --- /dev/null +++ b/src/DGtal/geometry/volumes/PConvexity.h @@ -0,0 +1,522 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +#pragma once + +/** + * @file PConvexity.h + * @author Jacques-Olivier Lachaud (\c jacques-olivier.lachaud@univ-savoie.fr ) + * Laboratory of Mathematics (CNRS, UMR 5127), University of Savoie, France + * + * @date 2024/06/20 + * + * Header file for module PConvexity.cpp + * + * This file is part of the DGtal library. + */ + +#if defined(PConvexity_RECURSES) +#error Recursive header files inclusion detected in PConvexity.h +#else // defined(PConvexity_RECURSES) +/** Prevents recursive inclusion of headers. */ +#define PConvexity_RECURSES + +#if !defined PConvexity_h +/** Prevents repeated inclusion of headers. */ +#define PConvexity_h + +////////////////////////////////////////////////////////////////////////////// +// Inclusions +#include +#include +#include "DGtal/base/Common.h" +#include "DGtal/kernel/CInteger.h" +#include "DGtal/kernel/CSpace.h" +#include "DGtal/geometry/volumes/ConvexityHelper.h" +////////////////////////////////////////////////////////////////////////////// + +namespace DGtal +{ + namespace detail + { + /// Hidden class to represent the P-convexity in a recursive way. + /// Only used to compute P-convexity, but not exposed to users. + /// + /// @note This d-dimensional object builds d (d-1)-dimensional + /// similar objects, but the k-th remembers to build only k lower + /// dimensional ones. + /// + /// @tparam dim the dimension of the digital space + /// @tparam TInteger any model of integer (used to represent digital point coordinates). + template < Dimension dim, + typename TInteger = DGtal::int32_t > + struct RecursivePConvexity { + /// Integer must be a model of the concept CInteger. + BOOST_CONCEPT_ASSERT(( concepts::CInteger ) ); + using Integer = TInteger; + using Point = DGtal::PointVector< dim, Integer >; + using ProjPoint = DGtal::PointVector< dim-1, Integer >; + using ProjPConvexity = DGtal::detail::RecursivePConvexity< dim - 1, Integer >; + + /// Parameter bd is used to build exactly 2^d - 1 PConvexity objects + /// when starting at dimension d. + /// @param bd the maximum axis of projection. + RecursivePConvexity( Dimension bd = dim ) + { + init( bd ); + } + + /// @param bd the maximum axis of projection. + void init( Dimension bd = dim ) + { + for ( Dimension j = 0; j < bd; j++ ) + projp.push_back( ProjPConvexity( j ) ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe when 'true' performs convex hull computations + /// with arbitrary precision integer (if available), otherwise + /// chooses a compromise between speed and precision (int64_t). + /// + /// @return 'true' if and only if X is a digitally convex set in the + /// classic sense, i.e. \f$ Conv(X) \cap Z^d = X \f$. + /// + /// @pre X must not contain any duplicates. + static + bool is0Convex( const std::vector< Point >& X, bool safe ) + { + if ( X.empty() ) return true; + // Build polytope according to internal integer type. + if ( safe ) + { + typedef typename DGtal::detail::ConvexityHelperInternalInteger< Integer, true >::Type + InternalInteger; + const auto P = ConvexityHelper< dim, Integer, InternalInteger >:: + computeLatticePolytope( X, false, false ); + const std::size_t number_lattice_points_in_P = P.count(); + return number_lattice_points_in_P == X.size(); + } + else + { + typedef typename DGtal::detail::ConvexityHelperInternalInteger< Integer, false >::Type + InternalInteger; + const auto P = ConvexityHelper< dim, Integer, InternalInteger >:: + computeLatticePolytope( X, false, false ); + const std::size_t number_lattice_points_in_P = P.count(); + return number_lattice_points_in_P == X.size(); + } + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe when 'true' performs convex hull computations + /// with arbitrary precision integer (if available), otherwise + /// chooses a compromise between speed and precision (int64_t). + /// + /// @return 'true' if and only if X is a P-convex digital set. + /// + /// @pre X must not contain any duplicates. + bool isPConvex( const std::vector< Point >& X, bool safe ) const + { + if ( ! is0Convex( X, safe ) ) return false; + for ( std::size_t j = 0; j < projp.size(); j++ ) + { + const auto pi_j_X = project( X, j ); + if ( ! projp[ j ].isPConvex( pi_j_X, safe ) ) return false; + } + return true; + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe when 'true' performs convex hull computations + /// with arbitrary precision integer (if available), otherwise + /// chooses a compromise between speed and precision (int64_t). + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is digitally convex, and + /// less otherwise. + static + double convexityMeasure( const std::vector< Point >& X, bool safe ) + { + if ( X.empty() ) return 1.0; + // Build polytope according to internal integer type. + if ( safe ) + { + typedef typename DGtal::detail::ConvexityHelperInternalInteger< Integer, true >::Type + InternalInteger; + const auto P = ConvexityHelper< dim, Integer, InternalInteger >:: + computeLatticePolytope( X, false, false ); + const std::size_t number_lattice_points_in_P = P.count(); + return double( X.size() ) / double( number_lattice_points_in_P ); + } + else + { + typedef typename DGtal::detail::ConvexityHelperInternalInteger< Integer, false >::Type + InternalInteger; + const auto P = ConvexityHelper< dim, Integer, InternalInteger >:: + computeLatticePolytope( X, false, false ); + const std::size_t number_lattice_points_in_P = P.count(); + return double( X.size() ) / double( number_lattice_points_in_P ); + } + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe when 'true' performs convex hull computations + /// with arbitrary precision integer (if available), otherwise + /// chooses a compromise between speed and precision (int64_t). + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is P-convex (or + /// equivalently fully convex), and less otherwise. + double fullConvexityMeasure( const std::vector< Point >& X, bool safe ) const + { + double m = convexityMeasure( X, safe ); + for ( std::size_t j = 0; j < projp.size(); j++ ) + { + auto pX = project( X, j ); + m *= projp[ j ].fullConvexityMeasure( pX, safe ); + } + return m; + } + + /// Projects a point \a p along dimension \a a. + /// + /// @param[in] p any digital point + /// @param[in] a any dimension + /// @return the digital point of dimension (d-1) with omitted a-th coordinate. + static + ProjPoint project( const Point& p, Dimension a ) + { + ProjPoint pp; + Dimension j = 0; + for ( Dimension i = 0; i < Point::dimension; i++ ) + if ( i != a ) pp[ j++ ] = p[ i ]; + return pp; + } + + /// Projects the range of points \a p along dimension \a a. + /// + /// @param[in] p any range of digital points + /// @param[in] a any dimension + /// + /// @return the range of digital points of dimension (d-1) with + /// omitted a-th coordinate. + /// + /// @post the returned range has no duplicates. + static + std::vector< ProjPoint > project( const std::vector< Point >& p, Dimension a ) + { + std::vector< ProjPoint > pp( p.size() ); + for ( std::size_t i = 0; i < p.size(); i++ ) + pp[ i ] = project( p[ i ], a ); + std::sort( pp.begin(), pp.end() ); + auto last = std::unique( pp.begin(), pp.end() ); + pp.erase( last, pp.end() ); + return pp; + } + + /// The array of lower dimensional P-convexities. + std::vector< ProjPConvexity > projp; + }; + + /// Hidden class to represent the P-convexity in a recursive way. + /// Only used to compute P-convexity, but not exposed to users. + /// Specialization for dimension 1 + /// + /// @note This d-dimensional object builds d (d-1)-dimensional + /// similar objects, but the k-th remembers to build only k lower + /// dimensional ones. + /// + /// @tparam dim the dimension of the digital space + /// @tparam TInteger any model of integer (used to represent digital point coordinates). + template < typename TInteger > + struct RecursivePConvexity< 1, TInteger> { + /// Integer must be a model of the concept CInteger. + BOOST_CONCEPT_ASSERT(( concepts::CInteger ) ); + using Integer = TInteger; + using Point = PointVector< 1, Integer >; + + /// Default constructor. Nothing to do. + RecursivePConvexity( Dimension /* unused parameter in 1D specialization */ ) + {} + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe is a not used parameter for dimension 1, but is + /// kept for the meta-programming recursive definition of + /// P-convexity. + /// + /// @return 'true' if and only if X is a digital set in the + /// classic sense, i.e. \f$ Conv(X) \cap Z^d = X \f$. + /// + /// @note Unused second parameter. + static + bool is0Convex( std::vector< Point > X, bool safe ) + { + (void) safe; + std::sort( X.begin(), X.end() ); + return X.empty() + || ( ( Integer(X.back()[ 0 ]) - Integer(X.front()[ 0 ]) + Integer(1) ) + == Integer( X.size() ) ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe is a not used parameter for dimension 1, but is + /// kept for the meta-programming recursive definition of + /// P-convexity. + /// + /// @return 'true' if and only if X is a P-convex digital set. + /// + /// @pre X must not contain any duplicates. + static + bool isPConvex( const std::vector< Point >& X, bool safe ) + { + return is0Convex( X, safe ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe is a not used parameter for dimension 1, but is + /// kept for the meta-programming recursive definition of + /// P-convexity. + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is digitally convex, and + /// less otherwise. + static + double convexityMeasure( std::vector< Point > X, bool safe ) + { + (void) safe; //< not used in dimension 1. + if ( X.empty() ) return 1.0; + std::sort( X.begin(), X.end() ); + Integer nb = Integer(X.back()[ 0 ]) - Integer(X.front()[ 0 ]) + Integer(1); + return double( X.size() ) / double( nb ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @param safe is a not used parameter for dimension 1, but is + /// kept for the meta-programming recursive definition of + /// P-convexity. + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is P-convex (or + /// equivalently fully convex), and less otherwise. + static + double fullConvexityMeasure( const std::vector< Point >& X, bool safe ) + { + return convexityMeasure( X, safe ); + } + + }; + + } // namespace detail + + + + ///////////////////////////////////////////////////////////////////////////// + // template class PConvexity + /** + Description of template class 'PConvexity'

\brief Aim: A + class to check if digital sets are P-convex. The P-convexity is + defined as follows: + A digital set X subset of \f$ \mathbb{Z}^d \f$ is P-convex iff + - if d=1, then X must be an interval of integers, possibly empty + - otherwise X must be 0-convex (digitally convex) and the projection of X along any dimension must be P-convex too. + + It is a model of boost::CopyConstructible, + boost::DefaultConstructible, boost::Assignable. + + @tparam TSpace an arbitrary model of digital space, i.e. see CSpace. + */ + template < typename TSpace > + class PConvexity + { + BOOST_CONCEPT_ASSERT(( concepts::CSpace< TSpace > )); + + public: + typedef PConvexity Self; + typedef TSpace Space; + typedef typename Space::Integer Integer; + typedef typename Space::Point Point; + + static const Dimension dimension = Space::dimension; + using RPConvexity = DGtal::detail::RecursivePConvexity< dimension, Integer >; + + // ----------------------- Standard services -------------------------------------- + public: + /// @name Standard services (construction, initialization, assignment) + /// @{ + + /// Destructor. + ~PConvexity() = default; + + /// Main constructor. + /// + /// @param safe when 'true' performs convex hull computations with arbitrary + /// precision integer (if available), otherwise chooses a + /// compromise between speed and precision (int64_t). + PConvexity( bool safe = false ) + : myRPC(), mySafe( safe ) + {} + + /// @} + + // ----------------------- Convexity services -------------------------------------- + public: + /// @name Convexity services + /// @{ + + /// @param X any range of lattice points (without duplicates) + /// + /// @return 'true' if and only if X is a digitally convex set in the + /// classic sense, i.e. \f$ Conv(X) \cap Z^d = X \f$. + /// + /// @pre X must not contain any duplicates. + bool is0Convex( const std::vector< Point >& X ) const + { + return myRPC.is0Convex( X, mySafe ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @return 'true' if and only if X is a P-convex digital set. + /// + /// @pre X must not contain any duplicates. + bool isPConvex( const std::vector< Point >& X ) const + { + return myRPC.isPConvex( X, mySafe ); + } + + /// @} + + // ----------------------- Measure services -------------------------------------- + public: + /// @name Measure services + /// @{ + + /// @param X any range of lattice points (without duplicates) + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is digitally convex, and + /// less otherwise. + double convexityMeasure( const std::vector< Point >& X ) const + { + return myRPC.convexityMeasure( X, mySafe ); + } + + /// @param X any range of lattice points (without duplicates) + /// + /// @pre X must not contain any duplicates. + /// + /// @return a measure that has value 1.0 when X is P-convex (or + /// equivalently fully convex), and less otherwise. + double fullConvexityMeasure( const std::vector< Point >& X ) const + { + return myRPC.fullConvexityMeasure( X, mySafe ); + } + + /// @} + + // ----------------------- Interface -------------------------------------- + public: + /// @name Interface services + /// @{ + + /** + * Writes/Displays the object on an output stream. + * @param out the output stream where the object is written. + */ + void selfDisplay ( std::ostream & out ) const + { + out << "[PConvexity dim=" << dimension + << " safe=" << ( mySafe ? "True" : "False" ) + << " #bits=" << ( sizeof( Integer ) * 8 ) << "]"; + } + + /** + * Checks the validity/consistency of the object. Only invalid if dimension < 1. + * + * @return 'true' if the object is valid, 'false' otherwise. + */ + bool isValid() const + { + return dimension >= 1; + } + + /// @} + + // ------------------------- Protected Datas ------------------------------ + protected: + + /// The recursive PConvexity object used to determine P-convexity. + RPConvexity myRPC; + + /// when 'true' performs convex hull computations with arbitrary + /// precision integer (if available), otherwise chooses a + /// compromise between speed and precision (int64_t). + bool mySafe; + + // ------------------------- Private Datas -------------------------------- + private: + + // ------------------------- Internals ------------------------------------ + private: + + }; // end of class PConvexity + + /// @name Functions related to PConvexity (output) + /// @{ + + /** + * Overloads 'operator<<' for displaying objects of class 'PConvexity'. + * @param out the output stream where the object is written. + * @param object the object of class 'PConvexity' to write. + * @return the output stream after the writing. + */ + template + std::ostream& + operator<< ( std::ostream & out, + const PConvexity & object ) + { + object.selfDisplay( out ); + return out; + } + + /// @} + +} // namespace DGtal + + +/////////////////////////////////////////////////////////////////////////////// +// Includes inline functions. + +// // +/////////////////////////////////////////////////////////////////////////////// + +#endif // !defined PConvexity_h + +#undef PConvexity_RECURSES +#endif // else defined(PConvexity_RECURSES) diff --git a/tests/geometry/volumes/CMakeLists.txt b/tests/geometry/volumes/CMakeLists.txt index 7b05399a64..1fd98a15e9 100644 --- a/tests/geometry/volumes/CMakeLists.txt +++ b/tests/geometry/volumes/CMakeLists.txt @@ -9,6 +9,7 @@ set(DGTAL_TESTS_VOLUMES_SRC testBoundedRationalPolytope testCellGeometry testDigitalConvexity + testPConvexity testConvexityHelper testFullConvexity testEhrhartPolynomial diff --git a/tests/geometry/volumes/testPConvexity.cpp b/tests/geometry/volumes/testPConvexity.cpp new file mode 100644 index 0000000000..b4f3294a6c --- /dev/null +++ b/tests/geometry/volumes/testPConvexity.cpp @@ -0,0 +1,233 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + **/ + +/** + * @file testPConvexity.cpp + * @ingroup Tests + * @author Jacques-Olivier Lachaud (\c jacques-olivier.lachaud@univ-savoie.fr ) + * Laboratory of Mathematics (CNRS, UMR 5127), University Savoie Mont Blanc, France + * + * @date 2024/06/21 + * + * Functions for testing P-convexity. + * + * This file is part of the DGtal library. + */ + +/////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include "DGtal/base/Common.h" +#include "DGtal/kernel/SpaceND.h" +#include "DGtal/kernel/domains/HyperRectDomain.h" +#include "DGtal/kernel/sets/DigitalSetBySTLSet.h" +#include "DGtal/topology/KhalimskySpaceND.h" +#include "DGtal/shapes/Shapes.h" +#include "DGtal/geometry/volumes/PConvexity.h" +#include "DGtal/geometry/volumes/DigitalConvexity.h" +#include "DGtalCatch.h" +/////////////////////////////////////////////////////////////////////////////// + +using namespace std; +using namespace DGtal; + + +/////////////////////////////////////////////////////////////////////////////// +// Functions for testing class PConvexity. +/////////////////////////////////////////////////////////////////////////////// + +SCENARIO( "PConvexity< Z2 > P-convexity tests", "[p_convexity][2d]" ) +{ + typedef SpaceND< 2, int > Space; + typedef Space::Point Point; + typedef PConvexity< Space > Convexity; + + Convexity pconv; + + std::vector V1 = { Point(0,0), Point(-1,0), Point(1,0), Point(0,1) }; + REQUIRE( pconv.is0Convex( V1 ) ); + REQUIRE( pconv.isPConvex( V1 ) ); + REQUIRE( pconv.convexityMeasure( V1 ) == 1.0 ); + REQUIRE( pconv.fullConvexityMeasure( V1 ) == 1.0 ); + std::vector V2 = { Point(-1,0), Point(1,0), Point(0,1) }; + REQUIRE( ! pconv.is0Convex( V2 ) ); + REQUIRE( ! pconv.isPConvex( V2 ) ); + REQUIRE( pconv.convexityMeasure( V2 ) < 1.0 ); + REQUIRE( pconv.fullConvexityMeasure( V2 ) < 1.0 ); + std::vector V3 = { Point(0,0), Point(-1,0), Point(1,0) }; + REQUIRE( pconv.is0Convex( V3 ) ); + REQUIRE( pconv.isPConvex( V3 ) ); + REQUIRE( pconv.convexityMeasure( V3 ) == 1.0 ); + REQUIRE( pconv.fullConvexityMeasure( V3 ) == 1.0 ); + std::vector V4 = { Point(0,0), Point(-1,0), Point(1,0), Point(0,1), + Point(0,-1) }; + REQUIRE( pconv.is0Convex( V4 ) ); + REQUIRE( pconv.isPConvex( V4 ) ); + std::vector V5 = { Point(-1,0), Point(0,0), Point(3,1) }; + REQUIRE( pconv.is0Convex( V5 ) ); + REQUIRE( ! pconv.isPConvex( V5 ) ); + REQUIRE( pconv.convexityMeasure( V5 ) == 1.0 ); + REQUIRE( pconv.fullConvexityMeasure( V5 ) < 1.0 ); +} + +SCENARIO( "PConvexity< Z3 > ball tests", "[p_convexity][3d]" ) +{ + GIVEN( "Given a 3D digital ball of radius 5 " ) { + typedef SpaceND<3,int> Space; + typedef Space::Point Point; + typedef PConvexity< Space > Convexity; + typedef HyperRectDomain< Space > Domain; + typedef DigitalSetBySTLSet< Domain > DigitalSet; + + Convexity pconv; + Point lo = Point::diagonal( -7 ); + Point hi = Point::diagonal( 7 ); + Point c = Point::zero; + Domain domain( lo, hi ); + DigitalSet ball ( domain ); + Shapes< Domain >::addNorm2Ball( ball, c, 5 ); + std::vector V( ball.begin(), ball.end() ); + bool cvx0 = pconv.is0Convex( V ); + bool fcvx = pconv.isPConvex( V ); + THEN( "It is a 0-convex and P-convex by morphological characterization" ) { + REQUIRE( cvx0 ); + REQUIRE( fcvx ); + } + THEN( "Then both its convexity measure and its full convexity measure is 1.0" ) { + REQUIRE( pconv.convexityMeasure( V ) == 1.0 ); + REQUIRE( pconv.fullConvexityMeasure( V ) == 1.0 ); + } + } +} + +SCENARIO( "PConvexity< Z4 > ball tests", "[p_convexity][4d]" ) +{ + GIVEN( "Given a 4D digital ball of radius 5 " ) { + typedef SpaceND<4,int> Space; + typedef Space::Point Point; + typedef PConvexity< Space > Convexity; + typedef HyperRectDomain< Space > Domain; + typedef DigitalSetBySTLSet< Domain > DigitalSet; + + Convexity conv; + Point lo = Point::diagonal( -7 ); + Point hi = Point::diagonal( 7 ); + Point c = Point::zero; + Domain domain( lo, hi ); + DigitalSet ball ( domain ); + Shapes< Domain >::addNorm2Ball( ball, c, 5 ); + std::vector V( ball.begin(), ball.end() ); + bool cvx0 = conv.is0Convex( V ); + bool fcvx = conv.isPConvex( V ); + THEN( "It is a 0-convex and P-convex by morphological characterization" ) { + REQUIRE( cvx0 ); + REQUIRE( fcvx ); + } + THEN( "Then both its convexity measure and its full convexity measure is 1.0" ) { + REQUIRE( conv.convexityMeasure( V ) == 1.0 ); + REQUIRE( conv.fullConvexityMeasure( V ) == 1.0 ); + } + } +} + + +SCENARIO( "DigitalConvexity< Z3 > fully convex and p-convex tetrahedra", "[p_convexity][full_convexity][convex_simplices][3d]" ) +{ + typedef KhalimskySpaceND<3,int> KSpace; + typedef KSpace::Point Point; + typedef KSpace::Space Space; + typedef HyperRectDomain< Space > Domain; + typedef DigitalConvexity< KSpace > DConvexity; + typedef PConvexity< Space > PConvexity; + + Domain domain( Point( 0, 0, 0 ), Point( 3, 3, 3 ) ); + DConvexity dconv( Point( -1, -1, -1 ), Point( 4, 4, 4 ) ); + PConvexity pconv; + + WHEN( "Computing many tetrahedra in domain (0,0,0)-(4,4,4)." ) { + const unsigned int nb = 100; + unsigned int nbsimplex= 0; + unsigned int nb0 = 0; + unsigned int nb1 = 0; + unsigned int nb2 = 0; + unsigned int nb3 = 0; + unsigned int nb012_not3 = 0; + unsigned int nbf = 0; + unsigned int nbfg = 0; + unsigned int nbffast = 0; + unsigned int nbp = 0; + unsigned int nb0123 = 0; + for ( unsigned int i = 0; i < nb; ++i ) + { + Point a( rand() % 5, rand() % 5, rand() % 5 ); + Point b( rand() % 5, rand() % 5, rand() % 5 ); + Point c( rand() % 5, rand() % 5, rand() % 5 ); + Point d( rand() % 5, rand() % 5, rand() % 5 ); + if ( ! dconv.isSimplexFullDimensional( { a, b, c, d } ) ) continue; + auto tetra = dconv.makeSimplex( { a, b, c, d } ); + std::vector< Point > X; + tetra.getPoints( X ); + bool cvx0 = dconv.isKConvex( tetra, 0 ); + bool cvx1 = dconv.isKConvex( tetra, 1 ); + bool cvx2 = dconv.isKConvex( tetra, 2 ); + bool cvx3 = dconv.isKConvex( tetra, 3 ); + bool cvxf = dconv.isFullyConvex( tetra ); + bool cvxfg = dconv.isFullyConvex( X, false ); + bool cvxffast = dconv.isFullyConvexFast( X ); + bool cvxp = pconv.isPConvex( X ); + if ( cvxf != cvxfg || cvxf != cvxffast || cvxf != cvxp ) { + std::cout << "[" << cvx0 << cvx1 << cvx2 << cvx3 << "] " + << "[" << cvxf << "] [" << cvxfg + << "] [" << cvxffast << "]" + << "] [" << cvxp << "]" + << a << b << c << d << std::endl; + } + nbsimplex += 1; + nb0 += cvx0 ? 1 : 0; + nb1 += cvx1 ? 1 : 0; + nb2 += cvx2 ? 1 : 0; + nb3 += cvx3 ? 1 : 0; + nbf += cvxf ? 1 : 0; + nbfg += cvxfg ? 1 : 0; + nbffast += cvxffast ? 1 : 0; + nbp += cvxp ? 1 : 0; + nb0123 += ( cvx0 && cvx1 && cvx2 && cvx3 ) ? 1 : 0; + nb012_not3+= ( cvx0 && cvx1 && cvx2 && ! cvx3 ) ? 1 : 0; + } + THEN( "All valid tetrahedra are 0-convex." ) { + REQUIRE( nb0 == nbsimplex ); + } + THEN( "There are less 1-convex, 2-convex and 3-convex than 0-convex." ) { + REQUIRE( nb1 < nb0 ); + REQUIRE( nb2 < nb0 ); + REQUIRE( nb3 < nb0 ); + } + THEN( "When the tetrahedron is 0-convex, 1-convex and 2-convex, then it is 3-convex, so fully convex and also P-convex." ) { + REQUIRE( nb1 <= nb3 ); + REQUIRE( nb2 <= nb3 ); + REQUIRE( nb012_not3 == 0 ); + REQUIRE( nbf == nb0123 ); + REQUIRE( nbf == nbp ); + } + THEN( "All methods for computing full convexity and P-convexity agree." ) { + REQUIRE( nbf == nbfg ); + REQUIRE( nbf == nbffast ); + REQUIRE( nbf == nbp ); + } + } +} +