bool ElemCutter::is_outside (const Elem &elem, const std::vector<Real> &vertex_distance_func) const { libmesh_assert_equal_to (elem.n_vertices(), vertex_distance_func.size()); for (std::vector<Real>::const_iterator it=vertex_distance_func.begin(); it!=vertex_distance_func.end(); ++it) if (*it < 0.) return false; // if the distance function is nonnegative, we are outside return true; }
void QComposite<QSubCell>::init (const Elem &elem, const std::vector<Real> &vertex_distance_func, unsigned int p_level) { libmesh_assert_equal_to (vertex_distance_func.size(), elem.n_vertices()); libmesh_assert_equal_to (_dim, elem.dim()); // if we are not cut, revert to simple base class init() method. if (!_elem_cutter.is_cut (elem, vertex_distance_func)) { _q_subcell.init (elem.type(), p_level); _points = _q_subcell.get_points(); _weights = _q_subcell.get_weights(); //this->print_info(); return; } // Get a pointer to the element's reference element. We want to // perform cutting on the reference element such that the quadrature // point locations of the subelements live in the reference // coordinate system, thereby eliminating the need for inverse // mapping. const Elem *reference_elem = elem.reference_elem(); libmesh_assert (reference_elem != NULL); _elem_cutter(*reference_elem, vertex_distance_func); //_elem_cutter(elem, vertex_distance_func); // clear our state & accumulate points from subelements _points.clear(); _weights.clear(); // inside subelem { const std::vector<Elem const*> &inside_elem (_elem_cutter.inside_elements()); std::cout << inside_elem.size() << " elements inside\n"; this->add_subelem_values(inside_elem); } // outside subelem { const std::vector<Elem const*> &outside_elem (_elem_cutter.outside_elements()); std::cout << outside_elem.size() << " elements outside\n"; this->add_subelem_values(outside_elem); } this->print_info(); }
bool ElemCutter::is_cut (const Elem &elem, const std::vector<Real> &vertex_distance_func) const { libmesh_assert_equal_to (elem.n_vertices(), vertex_distance_func.size()); Real vmin = vertex_distance_func.front(), vmax = vmin; for (std::vector<Real>::const_iterator it=vertex_distance_func.begin(); it!=vertex_distance_func.end(); ++it) { vmin = std::min (vmin, *it); vmax = std::max (vmax, *it); } // if the distance function changes sign, we're cut. return (vmin*vmax < 0.); }
void ElemCutter::operator()(const Elem & elem, const std::vector<Real> & vertex_distance_func) { libmesh_assert_equal_to (vertex_distance_func.size(), elem.n_vertices()); _inside_elem.clear(); _outside_elem.clear(); // check for quick return? { // completely outside? if (this->is_outside(elem, vertex_distance_func)) { //std::cout << "element completely outside\n"; _outside_elem.push_back(& elem); return; } // completely inside? else if (this->is_inside(elem, vertex_distance_func)) { //std::cout << "element completely inside\n"; _inside_elem.push_back(&elem); return; } libmesh_assert (this->is_cut (elem, vertex_distance_func)); } // we now know we are in a cut element, find the intersecting points. this->find_intersection_points (elem, vertex_distance_func); // and then dispatch the proper method switch (elem.dim()) { case 1: this->cut_1D(elem, vertex_distance_func); break; case 2: this->cut_2D(elem, vertex_distance_func); break; case 3: this->cut_3D(elem, vertex_distance_func); break; default: libmesh_error_msg("Invalid element dimension: " << elem.dim()); } }
void ElemCutter::cut_3D (const Elem & elem, const std::vector<Real> & vertex_distance_func) { #ifndef LIBMESH_HAVE_TETGEN // current implementation requires tetgen! libMesh::err << "ERROR: current libMesh ElemCutter 3D implementation requires\n" << " the \"tetgen\" library!\n" << std::endl; libmesh_not_implemented(); #else // OK, LIBMESH_HAVE_TETGEN std::cout << "Inside cut cell element!\n"; libmesh_assert (_inside_mesh_3D.get() != nullptr); libmesh_assert (_outside_mesh_3D.get() != nullptr); _inside_mesh_3D->clear(); _outside_mesh_3D->clear(); for (unsigned int v=0; v<elem.n_vertices(); v++) { if (vertex_distance_func[v] >= 0.) _outside_mesh_3D->add_point (elem.point(v)); if (vertex_distance_func[v] <= 0.) _inside_mesh_3D->add_point (elem.point(v)); } for (const auto & pt : _intersection_pts) { _inside_mesh_3D->add_point(pt); _outside_mesh_3D->add_point(pt); } // Triangulate! _tetgen_inside->triangulate_pointset(); //_inside_mesh_3D->print_info(); _tetgen_outside->triangulate_pointset(); //_outside_mesh_3D->print_info(); // (below generates some horribly expensive meshes, // but seems immune to the 0 volume problem). // _tetgen_inside->pointset_convexhull(); // _inside_mesh_3D->find_neighbors(); // _inside_mesh_3D->print_info(); // _tetgen_inside->triangulate_conformingDelaunayMesh (1.e3, 100.); // _inside_mesh_3D->print_info(); // _tetgen_outside->pointset_convexhull(); // _outside_mesh_3D->find_neighbors(); // _outside_mesh_3D->print_info(); // _tetgen_outside->triangulate_conformingDelaunayMesh (1.e3, 100.); // _outside_mesh_3D->print_info(); std::ostringstream name; name << "cut_cell_" << cut_cntr++ << ".dat"; _inside_mesh_3D->write ("in_" + name.str()); _outside_mesh_3D->write ("out_" + name.str()); // finally, add the elements to our lists. _inside_elem.clear(); _outside_elem.clear(); for (const auto & elem : _inside_mesh_3D->element_ptr_range()) if (elem->volume() > std::numeric_limits<Real>::epsilon()) _inside_elem.push_back (elem); for (const auto & elem : _outside_mesh_3D->element_ptr_range()) if (elem->volume() > std::numeric_limits<Real>::epsilon()) _outside_elem.push_back (elem); #endif }
void ElemCutter::cut_2D (const Elem & elem, const std::vector<Real> & vertex_distance_func) { #ifndef LIBMESH_HAVE_TRIANGLE // current implementation requires triangle! libMesh::err << "ERROR: current libMesh ElemCutter 2D implementation requires\n" << " the \"triangle\" library!\n" << std::endl; libmesh_not_implemented(); #else // OK, LIBMESH_HAVE_TRIANGLE std::cout << "Inside cut face element!\n"; libmesh_assert (_inside_mesh_2D.get() != nullptr); libmesh_assert (_outside_mesh_2D.get() != nullptr); _inside_mesh_2D->clear(); _outside_mesh_2D->clear(); for (unsigned int v=0; v<elem.n_vertices(); v++) { if (vertex_distance_func[v] >= 0.) _outside_mesh_2D->add_point (elem.point(v)); if (vertex_distance_func[v] <= 0.) _inside_mesh_2D->add_point (elem.point(v)); } for (const auto & pt : _intersection_pts) { _inside_mesh_2D->add_point(pt); _outside_mesh_2D->add_point(pt); } // Customize the variables for the triangulation // we will be cutting reference cell, and want as few triangles // as possible, so jack this up larger than the area we will be // triangulating so we are governed only by accurately defining // the boundaries. _triangle_inside->desired_area() = 100.; _triangle_outside->desired_area() = 100.; // allow for small angles _triangle_inside->minimum_angle() = 5.; _triangle_outside->minimum_angle() = 5.; // Turn off Laplacian mesh smoothing after generation. _triangle_inside->smooth_after_generating() = false; _triangle_outside->smooth_after_generating() = false; // Triangulate! _triangle_inside->triangulate(); _triangle_outside->triangulate(); // std::ostringstream name; // name << "cut_face_" // << cut_cntr++ // << ".dat"; // _inside_mesh_2D->write ("in_" + name.str()); // _outside_mesh_2D->write ("out_" + name.str()); // finally, add the elements to our lists. _inside_elem.clear(); _outside_elem.clear(); for (const auto & elem : _inside_mesh_2D->element_ptr_range()) _inside_elem.push_back (elem); for (const auto & elem : _outside_mesh_2D->element_ptr_range()) _outside_elem.push_back (elem); #endif }
void MeshBase::detect_interior_parents() { // This requires an inspection on every processor parallel_object_only(); // Check if the mesh contains mixed dimensions. If so, then set interior parents, otherwise return. if (this->elem_dimensions().size() == 1) return; //This map will be used to set interior parents LIBMESH_BEST_UNORDERED_MAP<dof_id_type, std::vector<dof_id_type> > node_to_elem; const_element_iterator el = this->active_elements_begin(); const_element_iterator end = this->active_elements_end(); for (; el!=end; ++el) { const Elem * elem = *el; // Populating the node_to_elem map, same as MeshTools::build_nodes_to_elem_map for (unsigned int n=0; n<elem->n_vertices(); n++) { libmesh_assert_less (elem->id(), this->max_elem_id()); node_to_elem[elem->node(n)].push_back(elem->id()); } } // Automatically set interior parents el = this->elements_begin(); for (; el!=end; ++el) { Elem * element = *el; // Ignore an 3D element or an element that already has an interior parent if (element->dim()>=LIBMESH_DIM || element->interior_parent()) continue; // Start by generating a SET of elements that are dim+1 to the current // element at each vertex of the current element, thus ignoring interior nodes. // If one of the SET of elements is empty, then we will not have an interior parent // since an interior parent must be connected to all vertices of the current element std::vector< std::set<dof_id_type> > neighbors( element->n_vertices() ); bool found_interior_parents = false; for (dof_id_type n=0; n < element->n_vertices(); n++) { std::vector<dof_id_type> & element_ids = node_to_elem[element->node(n)]; for (std::vector<dof_id_type>::iterator e_it = element_ids.begin(); e_it != element_ids.end(); e_it++) { dof_id_type eid = *e_it; if (this->elem(eid)->dim() == element->dim()+1) neighbors[n].insert(eid); } if (neighbors[n].size()>0) { found_interior_parents = true; } else { // We have found an empty set, no reason to continue // Ensure we set this flag to false before the break since it could have // been set to true for previous vertex found_interior_parents = false; break; } } // If we have successfully generated a set of elements for each vertex, we will compare // the set for vertex 0 will the sets for the vertices until we find a id that exists in // all sets. If found, this is our an interior parent id. The interior parent id found // will be the lowest element id if there is potential for multiple interior parents. if (found_interior_parents) { std::set<dof_id_type> & neighbors_0 = neighbors[0]; for (std::set<dof_id_type>::iterator e_it = neighbors_0.begin(); e_it != neighbors_0.end(); e_it++) { found_interior_parents=false; dof_id_type interior_parent_id = *e_it; for (dof_id_type n=1; n < element->n_vertices(); n++) { if (neighbors[n].find(interior_parent_id)!=neighbors[n].end()) { found_interior_parents=true; } else { found_interior_parents=false; break; } } if (found_interior_parents) { element->set_interior_parent(this->elem(interior_parent_id)); break; } } } } }
// The actual implementation of building elements. void InfElemBuilder::build_inf_elem(const Point& origin, const bool x_sym, const bool y_sym, const bool z_sym, const bool be_verbose, std::set< std::pair<dof_id_type, unsigned int> >* inner_faces) { if (be_verbose) { #ifdef DEBUG libMesh::out << " Building Infinite Elements:" << std::endl; libMesh::out << " updating element neighbor tables..." << std::endl; #else libMesh::out << " Verbose mode disabled in non-debug mode." << std::endl; #endif } // update element neighbors this->_mesh.find_neighbors(); START_LOG("build_inf_elem()", "InfElemBuilder"); // A set for storing element number, side number pairs. // pair.first == element number, pair.second == side number std::set< std::pair<dof_id_type,unsigned int> > faces; std::set< std::pair<dof_id_type,unsigned int> > ofaces; // A set for storing node numbers on the outer faces. std::set<dof_id_type> onodes; // The distance to the farthest point in the mesh from the origin Real max_r=0.; // The index of the farthest point in the mesh from the origin int max_r_node = -1; #ifdef DEBUG if (be_verbose) { libMesh::out << " collecting boundary sides"; if (x_sym || y_sym || z_sym) libMesh::out << ", skipping sides in symmetry planes..." << std::endl; else libMesh::out << "..." << std::endl; } #endif // Iterate through all elements and sides, collect indices of all active // boundary sides in the faces set. Skip sides which lie in symmetry planes. // Later, sides of the inner boundary will be sorted out. { MeshBase::element_iterator it = this->_mesh.active_elements_begin(); const MeshBase::element_iterator end = this->_mesh.active_elements_end(); for(; it != end; ++it) { Elem* elem = *it; for (unsigned int s=0; s<elem->n_neighbors(); s++) { // check if elem(e) is on the boundary if (elem->neighbor(s) == NULL) { // note that it is safe to use the Elem::side() method, // which gives a non-full-ordered element AutoPtr<Elem> side(elem->build_side(s)); // bool flags for symmetry detection bool sym_side=false; bool on_x_sym=true; bool on_y_sym=true; bool on_z_sym=true; // Loop over the nodes to check whether they are on the symmetry planes, // and therefore sufficient to use a non-full-ordered side element for(unsigned int n=0; n<side->n_nodes(); n++) { const Point dist_from_origin = this->_mesh.point(side->node(n)) - origin; if(x_sym) if( std::abs(dist_from_origin(0)) > 1.e-3 ) on_x_sym=false; if(y_sym) if( std::abs(dist_from_origin(1)) > 1.e-3 ) on_y_sym=false; if(z_sym) if( std::abs(dist_from_origin(2)) > 1.e-3 ) on_z_sym=false; // if(x_sym) // if( std::abs(dist_from_origin(0)) > 1.e-6 ) // on_x_sym=false; // if(y_sym) // if( std::abs(dist_from_origin(1)) > 1.e-6 ) // on_y_sym=false; // if(z_sym) // if( std::abs(dist_from_origin(2)) > 1.e-6 ) // on_z_sym=false; //find the node most distant from origin Real r = dist_from_origin.size(); if (r > max_r) { max_r = r; max_r_node=side->node(n); } } sym_side = (x_sym && on_x_sym) || (y_sym && on_y_sym) || (z_sym && on_z_sym); if (!sym_side) faces.insert( std::make_pair(elem->id(), s) ); } // neighbor(s) == NULL } // sides } // elems } // If a boundary side has one node on the outer boundary, // all points of this side are on the outer boundary. // Start with the node most distant from origin, which has // to be on the outer boundary, then recursively find all // sides and nodes connected to it. Found sides are moved // from faces to ofaces, nodes are collected in onodes. // Here, the search is done iteratively, because, depending on // the mesh, a very high level of recursion might be necessary. if (max_r_node > 0) onodes.insert(max_r_node); { std::set< std::pair<dof_id_type,unsigned int> >::iterator face_it = faces.begin(); unsigned int facesfound=0; while (face_it != faces.end()) { std::pair<dof_id_type, unsigned int> p; p = *face_it; // This has to be a full-ordered side element, // since we need the correct n_nodes, AutoPtr<Elem> side(this->_mesh.elem(p.first)->build_side(p.second)); bool found=false; for(unsigned int sn=0; sn<side->n_nodes(); sn++) if(onodes.count(side->node(sn))) { found=true; break; } // If a new oface is found, include its nodes in onodes if(found) { for(unsigned int sn=0; sn<side->n_nodes(); sn++) onodes.insert(side->node(sn)); ofaces.insert(p); face_it++; // iteration is done here faces.erase(p); facesfound++; } else face_it++; // iteration is done here // If at least one new oface was found in this cycle, // do another search cycle. if(facesfound>0 && face_it == faces.end()) { facesfound = 0; face_it = faces.begin(); } } } #ifdef DEBUG if (be_verbose) libMesh::out << " found " << faces.size() << " inner and " << ofaces.size() << " outer boundary faces" << std::endl; #endif // When the user provided a non-null pointer to // inner_faces, that implies he wants to have // this std::set. For now, simply copy the data. if (inner_faces != NULL) *inner_faces = faces; // free memory, clear our local variable, no need // for it any more. faces.clear(); // outer_nodes maps onodes to their duplicates std::map<dof_id_type, Node *> outer_nodes; // We may need to pick our own object ids in parallel dof_id_type old_max_node_id = _mesh.max_node_id(); dof_id_type old_max_elem_id = _mesh.max_elem_id(); // for each boundary node, add an outer_node with // double distance from origin. std::set<dof_id_type>::iterator on_it = onodes.begin(); for( ; on_it != onodes.end(); ++on_it) { Point p = (Point(this->_mesh.point(*on_it)) * 2) - origin; if (_mesh.is_serial()) { // Add with a default id in serial outer_nodes[*on_it]=this->_mesh.add_point(p); } else { // Pick a unique id in parallel Node &bnode = _mesh.node(*on_it); dof_id_type new_id = bnode.id() + old_max_node_id; outer_nodes[*on_it] = this->_mesh.add_point(p, new_id, bnode.processor_id()); } } #ifdef DEBUG // for verbose, remember n_elem dof_id_type n_conventional_elem = this->_mesh.n_elem(); #endif // build Elems based on boundary side type std::set< std::pair<dof_id_type,unsigned int> >::iterator face_it = ofaces.begin(); for( ; face_it != ofaces.end(); ++face_it) { // Shortcut to the pair being iterated over std::pair<dof_id_type,unsigned int> p = *face_it; // build a full-ordered side element to get the base nodes AutoPtr<Elem> side(this->_mesh.elem(p.first)->build_side(p.second)); // create cell depending on side type, assign nodes, // use braces to force scope. bool is_higher_order_elem = false; { Elem* el; switch(side->type()) { // 3D infinite elements // TRIs case TRI3: el=new InfPrism6; break; case TRI6: el=new InfPrism12; is_higher_order_elem = true; break; // QUADs case QUAD4: el=new InfHex8; break; case QUAD8: el=new InfHex16; is_higher_order_elem = true; break; case QUAD9: el=new InfHex18; // the method of assigning nodes (which follows below) // omits in the case of QUAD9 the bubble node; therefore // we assign these first by hand here. el->set_node(16) = side->get_node(8); el->set_node(17) = outer_nodes[side->node(8)]; is_higher_order_elem=true; break; // 2D infinite elements case EDGE2: el=new InfQuad4; break; case EDGE3: el=new InfQuad6; el->set_node(4) = side->get_node(2); break; // 1D infinite elements not supported default: libMesh::out << "InfElemBuilder::build_inf_elem(Point, bool, bool, bool, bool): " << "invalid face element " << std::endl; continue; } // In parallel, assign unique ids to the new element if (!_mesh.is_serial()) { Elem *belem = _mesh.elem(p.first); el->processor_id() = belem->processor_id(); // We'd better not have elements with more than 6 sides el->set_id (belem->id() * 6 + p.second + old_max_elem_id); } // assign vertices to the new infinite element const unsigned int n_base_vertices = side->n_vertices(); for(unsigned int i=0; i<n_base_vertices; i++) { el->set_node(i ) = side->get_node(i); el->set_node(i+n_base_vertices) = outer_nodes[side->node(i)]; } // when this is a higher order element, // assign also the nodes in between if (is_higher_order_elem) { // n_safe_base_nodes is the number of nodes in \p side // that may be safely assigned using below for loop. // Actually, n_safe_base_nodes is _identical_ with el->n_vertices(), // since for QUAD9, the 9th node was already assigned above const unsigned int n_safe_base_nodes = el->n_vertices(); for(unsigned int i=n_base_vertices; i<n_safe_base_nodes; i++) { el->set_node(i+n_base_vertices) = side->get_node(i); el->set_node(i+n_safe_base_nodes) = outer_nodes[side->node(i)]; } } // add infinite element to mesh this->_mesh.add_elem(el); } // el goes out of scope } // for #ifdef DEBUG _mesh.libmesh_assert_valid_parallel_ids(); if (be_verbose) libMesh::out << " added " << this->_mesh.n_elem() - n_conventional_elem << " infinite elements and " << onodes.size() << " nodes to the mesh" << std::endl << std::endl; #endif STOP_LOG("build_inf_elem()", "InfElemBuilder"); }