From b51661eb5c469b235da03b2dc3cc9c277f518cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Klinkovsk=C3=BD?= <klinkovsky@mmg.fjfi.cvut.cz> Date: Thu, 30 Dec 2021 12:34:32 +0100 Subject: [PATCH] Added function to deduplicate points in MeshBuilder --- src/TNL/Meshes/MeshBuilder.h | 131 +++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/src/TNL/Meshes/MeshBuilder.h b/src/TNL/Meshes/MeshBuilder.h index aa10a8a245..e3676484b4 100644 --- a/src/TNL/Meshes/MeshBuilder.h +++ b/src/TNL/Meshes/MeshBuilder.h @@ -16,6 +16,9 @@ #pragma once +#include <numeric> // std::iota +#include <vector> + #include <TNL/Containers/Vector.h> #include <TNL/Meshes/Topologies/Polyhedron.h> @@ -31,14 +34,16 @@ public: using GlobalIndexType = typename MeshTraitsType::GlobalIndexType; using LocalIndexType = typename MeshTraitsType::LocalIndexType; using PointType = typename MeshTraitsType::PointType; + using PointArrayType = typename MeshTraitsType::PointArrayType; + using BoolVector = Containers::Vector< bool, Devices::Host, GlobalIndexType >; using CellTopology = typename MeshTraitsType::CellTopology; using CellSeedMatrixType = typename MeshTraitsType::CellSeedMatrixType; using CellSeedType = typename CellSeedMatrixType::EntitySeedMatrixSeed; using FaceSeedMatrixType = typename MeshTraitsType::FaceSeedMatrixType; using FaceSeedType = typename FaceSeedMatrixType::EntitySeedMatrixSeed; using NeighborCountsArray = typename MeshTraitsType::NeighborCountsArray; - - void setEntitiesCount( const GlobalIndexType& points, + + void setEntitiesCount( const GlobalIndexType& points, const GlobalIndexType& cells = 0, const GlobalIndexType& faces = 0 ) { @@ -109,20 +114,136 @@ public: return this->cellSeeds.getSeed( index ); } + void deduplicatePoints( const double numericalThreshold = 1e-9 ) + { + // prepare vector with an identity permutationutation + std::vector< GlobalIndexType > permutation( points.getSize() ); + std::iota( permutation.begin(), permutation.end(), (GlobalIndexType) 0 ); + + // workaround for lexicographical sorting + // FIXME: https://mmg-gitlab.fjfi.cvut.cz/gitlab/tnl/tnl-dev/-/issues/79 + auto lexless = [numericalThreshold, this] (const GlobalIndexType& a, const GlobalIndexType& b) -> bool + { + const PointType& left = this->points[ a ]; + const PointType& right = this->points[ b ]; + for( LocalIndexType i = 0; i < PointType::getSize(); i++ ) + if( TNL::abs( left[ i ] - right[ i ] ) > numericalThreshold ) + return left[ i ] < right[ i ]; + return false; + }; + + // sort points in lexicographical order + std::stable_sort( permutation.begin(), permutation.end(), lexless ); + + // old -> new index mapping for points + std::vector< GlobalIndexType > points_perm_to_new( points.getSize() ); + + // find duplicate points + GlobalIndexType uniquePointsCount = 0; + // first index is unique + points_perm_to_new[ permutation[ 0 ] ] = uniquePointsCount++; + for( GlobalIndexType i = 1; i < points.getSize(); i++ ) { + const PointType& curr = points[ permutation[ i ] ]; + const PointType& prev = points[ permutation[ i - 1 ] ]; + if( maxNorm( curr - prev ) > numericalThreshold ) + // unique point + points_perm_to_new[ permutation[ i ] ] = uniquePointsCount++; + else + // duplicate point - use previous index + points_perm_to_new[ permutation[ i ] ] = uniquePointsCount - 1; + } + + // if all points are unique, we are done + if( uniquePointsCount == points.getSize() ) + return; + + std::cout << "Found " << points.getSize() - uniquePointsCount << " duplicate points (total " << points.getSize() << ", unique " << uniquePointsCount << ")" << std::endl; + + // copy this->points and this->pointsSet, drop duplicate points + // (trying to do this in-place is not worth it, since even Array::reallocate + // needs to allocate a temporary array and copy the elements) + PointArrayType newPoints( uniquePointsCount ); + BoolVector newPointsSet( uniquePointsCount ); + std::vector< GlobalIndexType > points_old_to_new( points.getSize() ); + // TODO: this can almost be parallelized, except we have multiple writes for the duplicate points + for( std::size_t i = 0; i < points_perm_to_new.size(); i++ ) { + const GlobalIndexType oldIndex = permutation[ i ]; + const GlobalIndexType newIndex = points_perm_to_new[ oldIndex ]; + newPoints[ newIndex ] = points[ oldIndex ]; + newPointsSet[ newIndex ] = pointsSet[ oldIndex ]; + points_old_to_new[ oldIndex ] = newIndex; + } + points = std::move( newPoints ); + pointsSet = std::move( newPointsSet ); + // reset permutation and points_perm_to_new - we need just points_old_to_new further on + permutation.clear(); + permutation.shrink_to_fit(); + points_perm_to_new.clear(); + points_perm_to_new.shrink_to_fit(); + + auto remap_matrix = [uniquePointsCount, &points_old_to_new] ( auto& seeds ) + { + // TODO: optimize out useless copy and counting corner counts - it would be sufficient to just remap column indices and then shrink the columns count + std::remove_reference_t< decltype( seeds ) > newSeeds; + newSeeds.setDimensions( seeds.getMatrix().getRows(), uniquePointsCount ); + newSeeds.setEntityCornersCounts( seeds.getEntityCornerCounts() ); + // TODO: parallelize (we have the IndexPermutationApplier) + for( GlobalIndexType i = 0; i < newSeeds.getEntitiesCount(); i++ ) { + const auto oldSeed = seeds.getSeed( i ); + auto seed = newSeeds.getSeed( i ); + for( LocalIndexType j = 0; j < seed.getCornersCount(); j++ ) { + const GlobalIndexType newIndex = points_old_to_new[ oldSeed.getCornerId( j ) ]; + seed.setCornerId( j, newIndex ); + } + } + seeds = std::move( newSeeds ); + }; + + // remap points in this->faceSeeds or this->cellSeeds + if( faceSeeds.empty() ) + remap_matrix( cellSeeds ); + else + remap_matrix( faceSeeds ); + } + bool build( MeshType& mesh ) { if( ! this->validate() ) return false; + pointsSet.reset(); mesh.init( this->points, this->faceSeeds, this->cellSeeds ); return true; } private: - using PointArrayType = typename MeshTraitsType::PointArrayType; - using BoolVector = Containers::Vector< bool, Devices::Host, GlobalIndexType >; - bool validate() const { + // verify that matrix dimensions are consistent with points + if( faceSeeds.empty() ) { + // no face seeds - cell seeds refer to points + if( cellSeeds.getMatrix().getColumns() != points.getSize() ) { + std::cerr << "Mesh builder error: Inconsistent size of the cellSeeds matrix (it has " + << cellSeeds.getMatrix().getColumns() << " columns, but there are " << points.getSize() + << " points)." << std::endl; + return false; + } + } + else { + // cell seeds refer to faces and face seeds refer to points + if( cellSeeds.getMatrix().getColumns() != faceSeeds.getMatrix().getRows() ) { + std::cerr << "Mesh builder error: Inconsistent size of the cellSeeds matrix (it has " + << cellSeeds.getMatrix().getColumns() << " columns, but there are " << faceSeeds.getMatrix().getRows() + << " faces)." << std::endl; + return false; + } + if( faceSeeds.getMatrix().getColumns() != points.getSize() ) { + std::cerr << "Mesh builder error: Inconsistent size of the faceSeeds matrix (it has " + << faceSeeds.getMatrix().getColumns() << " columns, but there are " << points.getSize() + << " points)." << std::endl; + return false; + } + } + if( min( pointsSet ) != true ) { std::cerr << "Mesh builder error: Not all points were set." << std::endl; return false; -- GitLab