.. _program_listing_file_src_triangulation_layers.hpp: Program Listing for File triangulation_layers.hpp ================================================= |exhale_lsh| :ref:`Return to documentation for file ` (``src/triangulation_layers.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /* This file is part of brille. Copyright © 2019,2020 Greg Tucker brille is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. brille 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with brille. If not, see . */ #ifndef BRILLE_TRIANGULATION_HPP_ #define BRILLE_TRIANGULATION_HPP_ #include #include #include #include #include #include #include "array_latvec.hpp" // defines bArray #include "tetgen.h" #include "debug.hpp" #include "polyhedron.hpp" #include "approx.hpp" namespace brille { template static size_t find_first(const std::array& x, const T val){ auto at = std::find(x.begin(), x.end(), val); if (at == x.end()) throw std::logic_error("Value not found?!"); return std::distance(x.begin(), at); } class TetTriLayer{ using ind_t = brille::ind_t; ind_t nVertices; ind_t nTetrahedra; bArray vertex_positions; // (nVertices, 3) bArray vertices_per_tetrahedron; // (nTetrahedra, 4) std::vector> tetrahedra_per_vertex; // (nVertices,)(1+,) std::vector> neighbours_per_tetrahedron; // (nTetrahedra,)(1+,) bArray circum_centres; // (nTetrahedra, 3); std::vector circum_radii; // (nTetrahedra,) public: ind_t number_of_vertices(void) const {return nVertices;} ind_t number_of_tetrahedra(void) const {return nTetrahedra;} const bArray& get_vertex_positions(void) const {return vertex_positions;} const bArray& get_vertices_per_tetrahedron(void) const {return vertices_per_tetrahedron;} const bArray& get_circum_centres(void) const {return circum_centres;} const std::vector& get_circum_radii(void) const {return circum_radii;} Polyhedron get_tetrahedron(const ind_t idx) const { if (nTetrahedra <= idx) throw std::out_of_range("The requested tetrahedron does not exist."); auto tet = vertices_per_tetrahedron.view(idx); std::vector> vpf{{0,1,2},{0,2,3},{1,0,3},{1,3,2}}; return Polyhedron(vertex_positions.extract(tet), vpf); } explicit TetTriLayer() : nVertices(0), nTetrahedra(0), vertex_positions(0u,3u), vertices_per_tetrahedron(0u,4u), circum_centres(0u,3u) {} TetTriLayer(const tetgenio& tgio) : vertex_positions(0u,3u), vertices_per_tetrahedron(0u,4u), circum_centres(0u,3u) { nVertices = static_cast(tgio.numberofpoints); nTetrahedra = static_cast(tgio.numberoftetrahedra); // copy-over all vertex positions: vertex_positions.resize(nVertices); for (ind_t i=0; i(tgio.tetrahedronlist[i*tgio.numberofcorners+j]); // Construct the tetrahedra per vertex vector of vectors tetrahedra_per_vertex.resize(nVertices); for (ind_t i=0; i= 0) neighbours_per_tetrahedron[i].push_back(static_cast(tgio.neighborlist[i*4u+j])); // ensure that all tetrahedra have positive (orient3d) volume this->correct_tetrahedra_vertex_ordering(); // Calculate the circumsphere information: this->determine_circumspheres(); } // Create a string full of object information: std::string to_string(void) const { std::string str; str = std::to_string(nVertices) + " vertices"; str += " in " + std::to_string(nTetrahedra) + " tetrahedra"; return str; } // ind_t locate(const bArray& x, std::vector& v, std::vector& w) const { // // no specified tetrahedra to check against, so check them all // std::vector tosearch(nTetrahedra); // std::iota(tosearch.begin(), tosearch.end(), 0u); // return locate(tosearch, x, v, w); // } // ind_t locate(const std::vector& tosearch, const bArray& x, std::vector& v, std::vector& w) const { // if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) // throw std::runtime_error("locate requires a single 3-element vector."); // if (std::any_of(tosearch.begin(), tosearch.end(), [this](ind_t a){return a>=this->nTetrahedra;})) // throw std::domain_error("Out-of-bounds tetrahedra index to search"); // ind_t found = this->unsafe_locate(tosearch, x, v, w); // if (found >= nTetrahedra) // throw std::runtime_error("The point was not located!"); // return found; // } // ind_t unsafe_locate(const std::vector& tosearch, const bArray& x, std::vector& v, std::vector& w) const { // std::array ws; // v.clear(); // w.clear(); // make sure w is back to zero-elements // for (ind_t idx: tosearch){ // if (this->unsafe_might_contain(idx, x) && this->unsafe_contains(idx, x, ws)){ // // unsafe_contains sets the weights in ws // for (ind_t i=0; i<4u; ++i) if (!brille::approx::scalar(ws[i], 0.)){ // v.push_back(vertices_per_tetrahedron.val(idx,i)); // w.push_back(ws[i]); // } // return idx; // } // } // return nTetrahedra; // } ind_t locate(const bArray& x, std::vector>& vw) const { if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) throw std::runtime_error("locate requires a single 3-element vector."); return unsafe_locate(x, vw); } ind_t unsafe_locate(const bArray& x, std::vector>& vw) const { // no specified tetrahedra to check against, so check them all std::vector tosearch(nTetrahedra); std::iota(tosearch.begin(), tosearch.end(), 0u); return unsafe_locate(tosearch, x, vw); } ind_t locate(const std::vector& tosearch, const bArray& x, std::vector>& vw) const { if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) throw std::runtime_error("locate requires a single 3-element vector."); if (std::any_of(tosearch.begin(), tosearch.end(), [this](ind_t a){return a>=this->nTetrahedra;})) throw std::domain_error("Out-of-bounds tetrahedra index to search"); ind_t found = this->unsafe_locate(tosearch, x, vw); if (found >= nTetrahedra) throw std::runtime_error("The point was not located!"); return found; } ind_t unsafe_locate(const std::vector& tosearch, const bArray& x, std::vector>& vw) const { std::array ws; vw.clear();// make sure w is back to zero-elements for (ind_t idx: tosearch){ if (this->unsafe_might_contain(idx, x) && this->unsafe_contains(idx, x, ws)){ // unsafe_contains sets the weights in ws for (ind_t i=0; i<4u; ++i) if (!brille::approx::scalar(ws[i], 0.)) vw.push_back(std::make_pair(vertices_per_tetrahedron.val(idx,i), ws[i])); return idx; } } return nTetrahedra; } std::vector neighbours(const ind_t vert) const { if (vert >= this->nVertices){ std::string msg = "The provided vertex index is out of bounds"; throw std::out_of_range(msg); } std::vector n; ind_t v; for (ind_t t: this->tetrahedra_per_vertex[vert]) for (ind_t j=0; j<4u; ++j) { v = this->vertices_per_tetrahedron.val(t,j); if ( v!= vert && std::find(n.begin(), n.end(), v) == n.end() ) n.push_back(v); } return n; } double volume(const ind_t tet) const { const ind_t* i = vertices_per_tetrahedron.ptr(tet,0); double v; v = orient3d( vertex_positions.ptr(i[0],0), vertex_positions.ptr(i[1],0), vertex_positions.ptr(i[2],0), vertex_positions.ptr(i[3],0) )/6.0; return v; } std::array volume_statistics() const { // total volume, minimum volume, maximum volume std::array vs{0.,(std::numeric_limits::max)(),std::numeric_limits::lowest()}; for (ind_t i=0; ivolume(i); vs[0] += v; // add the volume to the total if (vvs[2]) vs[2] = v; // new maximum } return vs; } bool might_contain(const ind_t tet, const bArray& x) const { if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) throw std::runtime_error("x must be a single 3-vector"); if (tet >= nTetrahedra) return false; return this->unsafe_might_contain(tet, x); } bool contains(const ind_t tet, const bArray& x) const{ if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) throw std::runtime_error("x must be a single 3-vector"); if (tet >= nTetrahedra) return false; return this->unsafe_contains(tet, x); } std::set collect_keys() const { long long ntets = brille::utils::u2s(this->number_of_tetrahedra()); ind_t nvert = this->number_of_vertices(); std::set keys; #pragma omp parallel for default(none) shared(ntets, keys, nvert) for (long long si=0; si(si); auto v = vertices_per_tetrahedron.view(i).to_std(); std::set t = permutation_table_keys_from_indicies(v.begin(), v.end(), nvert); #pragma omp critical { keys.insert(t.begin(), t.end()); } } return keys; } protected: bool unsafe_might_contain(const ind_t tet, const bArray& x) const { return norm(x-circum_centres.view(tet)).all(brille::cmp::le, circum_radii[tet]); } bool unsafe_contains(const ind_t tet, const bArray& x) const { std::array w{0.,0.,0.,0.}; return this->unsafe_contains(tet,x,w); } bool unsafe_contains(const ind_t tet, const bArray& x, std::array& w) const { this->weights(tet, x, w); return std::all_of(w.begin(), w.end(), [](double z){ return (z>0.||brille::approx::scalar(z,0.)); }); } void weights(const ind_t tet, const bArray& x, std::array& w) const { const ind_t* i = vertices_per_tetrahedron.ptr(tet); double vol6 = 6.0*this->volume(tet); w[0] = orient3d( x.ptr(0), vertex_positions.ptr(i[1]), vertex_positions.ptr(i[2]), vertex_positions.ptr(i[3]) )/vol6; w[1] = orient3d( vertex_positions.ptr(i[0]), x.ptr(0), vertex_positions.ptr(i[2]), vertex_positions.ptr(i[3]) )/vol6; w[2] = orient3d( vertex_positions.ptr(i[0]), vertex_positions.ptr(i[1]), x.ptr(0), vertex_positions.ptr(i[3]) )/vol6; w[3] = orient3d( vertex_positions.ptr(i[0]), vertex_positions.ptr(i[1]), vertex_positions.ptr(i[2]), x.ptr(0) )/vol6; } void correct_tetrahedra_vertex_ordering(void){ for (ind_t i=0; ivolume(i))) // the volume of tetrahedra i is negative vertices_per_tetrahedron.swap(i, 0,1); // swap two vertices to switch sign } void determine_circumspheres(void){ // ensure that the properties can hold all data circum_centres.resize(nTetrahedra); circum_radii.resize(nTetrahedra); verbose_update("Pull together the circumsphere information for all tetrahedra"); tetgenmesh tgm; // to get access to circumsphere for (ind_t i=0; i TetSet; // typedef std::map TetMap; typedef std::vector TetMap; // std::vector layers; std::vector connections; public: explicit TetTri() {}; TetTri(const std::vector& l) : layers(l) { this->find_connections(); } void find_connections(const size_t highest=0){ if (highest < layers.size()-1) for (size_t i=highest; iconnect(i,i+1)); } // make general locate and neighbour methods which drill-down through the layers // ind_t locate(const bArray& x, std::vector& v, std::vector& w) const { // if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) // throw std::runtime_error("locate requires a single 3-element vector."); // if (layers.size() < 1) // throw std::runtime_error("Can not locate without triangulation"); // // find the point within the highest-layer tetrahedra: // ind_t idx = layers[0].locate(x,v,w); // // use the layer-connection map to restrict the search in the next layer's tetrahedra // for (size_t i=1; i& x, std::vector& v) const{ // std::vector w; // return this->locate(x, v, w); // } std::vector> locate(const bArray& x) const { if (x.ndim()!=2u || x.size(0)!=1u || x.size(1)!=3u) throw std::runtime_error("locate requires a single 3-element vector."); if (layers.size() < 1) throw std::runtime_error("Can not locate without triangulation"); // setup temporary vertex(index) and weight vectors std::vector> vw; // find the point within the highest-layer tetrahedra: ind_t idx = layers[0].unsafe_locate(x,vw); // use the layer-connection map to restrict the search in the next layer's tetrahedra for (size_t i=1; i neighbours(const bArray& x) const { std::vector> vw = this->locate(x); if (vw.size() != 1u){ std::string msg = "The provided point is not a mesh vertex."; throw std::runtime_error(msg); } return layers.back().neighbours(vw[0].first); } std::vector neighbours(const ind_t v) const { return layers.back().neighbours(v); } std::string to_string() const { std::string str=""; if (layers.size()>1){ str += "Layers|"; for (auto layer: layers) str += layer.to_string() + "|"; } else { str += layers[0].to_string(); } return str; } // provide convenience functions which pass-through to the lowest layer ind_t number_of_tetrahedra() const {return layers.back().number_of_tetrahedra(); } ind_t number_of_vertices() const {return layers.back().number_of_vertices(); } const bArray& get_vertex_positions() const {return layers.back().get_vertex_positions(); } const bArray& get_vertices_per_tetrahedron() const { return layers.back().get_vertices_per_tetrahedron(); } std::set collect_keys() const {return layers.back().collect_keys();} private: TetMap connect(const size_t high, const size_t low) const{ omp_set_num_threads(omp_get_max_threads()); Stopwatch<> stopwatch; stopwatch.tic(); TetMap map(layers[high].number_of_tetrahedra()); long mapsize = brille::utils::u2s(map.size()); #if defined(__GNUC__) && !defined(__llvm__) && __GNUC__ < 9 // this version is necessary with g++ <= 8.3.0 #pragma omp parallel for default(none) shared(map, mapsize) schedule(dynamic) #else // this version is necessary with g++ == 9.2.0 #pragma omp parallel for default(none) shared(map, mapsize, high, low) schedule(dynamic) #endif for (long ui=0; ui(ui); // initialize the map map[i] = TetSet(); auto cchi = layers[high].get_circum_centres().view(i); // get a Polyhedron object for the ith higher-tetrahedra in case we need it Polyhedron tethi = layers[high].get_tetrahedron(i); std::vector sumrad; for (double r: layers[low].get_circum_radii()) sumrad.push_back(layers[high].get_circum_radii()[i]+r); // if two circumsphere centers are closer than the sum of their radii // they are close enough to possibly overlap: auto close_enough = norm(layers[low].get_circum_centres() - cchi).is(brille::cmp::le, sumrad); for (ind_t j=0; j < close_enough.size(); ++j) if (close_enough[j]) { bool add = false; // check if any vertex of the jth lower-tetrahedra is inside of the ith higher-tetrahedra for (ind_t k=0; k<4u; ++k) { if (!add && layers[high].contains(i, layers[low].get_vertex_positions().view(layers[low].get_vertices_per_tetrahedron().val(j,k)))) add = true; } // even if no vertex is inside of the ith higher-tetrahedra, the two tetrahedra // can overlap -- and checking for this overlap is complicated. // make the Polyhedron class do the heavy lifting. // if (add || tethi.intersects(ll.get_tetrahedron(j))) map[i].push_back(j); if (add || layers[low].get_tetrahedron(j).intersects(tethi)) map[i].push_back(j); } } stopwatch.toc(); info_update("Connect ",layers[high].number_of_tetrahedra()," to ",layers[low].number_of_tetrahedra()," completed in ",stopwatch.elapsed()," ms"); // we now have a TetMap which contains, for every tetrahedral index of the // higher level, all tetrahedral indices of the lower level which touch the // higher tetrahedron or share some part of its volume. return map; } }; template TetTriLayer triangulate_one_layer(const bArray& verts, const std::vector>& vpf, const double max_cell_size=-1.0, const int max_mesh_points=-1) { assert(verts.ndim()==2 && verts.size(1)==3); // otherwise we can't make a 3-D triangulation // create the tetgenbehavior object which contains all options/switches for tetrahedralize verbose_update("Creating `tetgenbehavior` object"); tetgenbehavior tgb; tgb.plc = 1; // we will always tetrahedralize a piecewise linear complex tgb.quality = 1; // we will (almost) always improve the tetrahedral mesh tgb.neighout = 1; // we *need* the neighbour information to be stored into tgo. if (max_cell_size > 0){ // volume constraint with a specified size tgb.fixedvolume = 1; tgb.maxvolume = max_cell_size; } else{ //volume constraint without a specified size? tgb.varvolume = 1; } if (max_mesh_points>0) tgb.steinerleft = max_mesh_points; #ifndef VERBOSE_MESHING tgb.quiet = 1; #endif #ifdef VERBOSE_MESHING tgb.verbose = 10000; #endif // make the input and output tetgenio objects and fill the input with our polyhedron verbose_update("Creating input and output `tetgenio` objects"); tetgenio tgi, tgo; // we have to handle initializing points/facets, but tetgenio has a destructor // which handles deleting all non-NULL fields. verbose_update("Initialize and fill the input object's pointlist parameter"); tgi.numberofpoints = static_cast(verts.size(0)); tgi.pointlist = nullptr; tgi.pointlist = new double[3*tgi.numberofpoints]; tgi.pointmarkerlist = nullptr; tgi.pointmarkerlist = new int[tgi.numberofpoints]; //tgi.point2tetlist = new int[tgi.numberofpoints]; using ind_t = brille::ind_t; int idx=0; for (ind_t i=0; i(i); for (ind_t j=0; j(vpf.size()); tgi.facetlist = new tetgenio::facet[tgi.numberoffacets]; tgi.facetmarkerlist = nullptr; tgi.facetmarkerlist = new int[tgi.numberoffacets]; for (ind_t i=0; i(i); tgi.facetlist[i].numberofpolygons = 1; tgi.facetlist[i].polygonlist = new tetgenio::polygon[1]; tgi.facetlist[i].polygonlist[0].numberofvertices = static_cast(vpf[i].size()); tgi.facetlist[i].polygonlist[0].vertexlist = nullptr; tgi.facetlist[i].polygonlist[0].vertexlist = new int[tgi.facetlist[i].polygonlist[0].numberofvertices]; for (ind_t j=0; j TetTri triangulate(const bArray& verts, const std::vector>& vpf, const double max_cell_size=-1.0, const int layer_count=5, const int max_mesh_points=-1) { // profile_update("Create layered triangulation"); assert(verts.ndim()==2 && verts.size(1)==3); std::vector layers; if (layer_count < 2){ profile_update("Less than two layers requested"); layers.push_back(triangulate_one_layer(verts, vpf, max_cell_size, max_mesh_points)); } else { profile_update(layer_count," layers requested"); // the highest-layer is the most-basic tetrahedral triangulation for the input layers.push_back(triangulate_one_layer(verts, vpf, -1, max_mesh_points)); if (max_cell_size > 0.0){ std::array layer_vol_stats = layers[0].volume_statistics(); profile_update("Highest layer has volume total, min, max: ", layer_vol_stats); double highest_maxvol = layer_vol_stats[2]; TetTriLayer lowest = triangulate_one_layer(verts, vpf, max_cell_size, max_mesh_points); layer_vol_stats = lowest.volume_statistics(); profile_update(" Lowest layer has volume total, min, max: ", layer_vol_stats); double lowest_maxvol = layer_vol_stats[2]; double exponent = std::log(highest_maxvol/lowest_maxvol)/std::log(static_cast(layer_count)); profile_update("So an exponent of ",exponent," will be used."); for (int i=layer_count-1; i>1; --i){ // start from second highest layer, finish at second lowest double layer_maxvol = lowest_maxvol * std::pow(static_cast(i), exponent); layers.push_back(triangulate_one_layer(verts, vpf, layer_maxvol, max_mesh_points)); } // keep the lowest layer as well layers.push_back(lowest); } } return TetTri(layers); } } // end namespace brille #endif // _TRIANGULATION_H_