diff --git a/src/wmtk/CMakeLists.txt b/src/wmtk/CMakeLists.txt index bd155cf7a1..4391ef83f8 100644 --- a/src/wmtk/CMakeLists.txt +++ b/src/wmtk/CMakeLists.txt @@ -5,10 +5,14 @@ set(SRC_FILES Mesh.hpp PointMesh.cpp PointMesh.hpp + EdgeMesh.hpp + EdgeMesh.cpp TriMesh.cpp TriMesh.hpp TetMesh.cpp TetMesh.hpp + EdgeMeshOperationExecutor.hpp + EdgeMeshOperationExecutor.cpp TriMeshOperationExecutor.hpp TriMeshOperationExecutor.cpp TetMeshOperationExecutor.hpp diff --git a/src/wmtk/EdgeMesh.cpp b/src/wmtk/EdgeMesh.cpp new file mode 100644 index 0000000000..8ee61b4184 --- /dev/null +++ b/src/wmtk/EdgeMesh.cpp @@ -0,0 +1,262 @@ +#include "EdgeMesh.hpp" + + +#include +#include +#include +#include +namespace wmtk { +EdgeMesh::EdgeMesh() + : Mesh(1) + , m_ve_handle(register_attribute("m_ve", PrimitiveType::Vertex, 1)) + , m_ev_handle(register_attribute("m_ev", PrimitiveType::Edge, 2)) + , m_ee_handle(register_attribute("m_ee", PrimitiveType::Edge, 2)) +{} +EdgeMesh::EdgeMesh(const EdgeMesh& o) = default; +EdgeMesh::EdgeMesh(EdgeMesh&& o) = default; +EdgeMesh& EdgeMesh::operator=(const EdgeMesh& o) = default; +EdgeMesh& EdgeMesh::operator=(EdgeMesh&& o) = default; + +Tuple EdgeMesh::split_edge(const Tuple& t, Accessor& hash_accessor) +{ + EdgeMesh::EdgeMeshOperationExecutor executor(*this, t, hash_accessor); + return executor.split_edge(); +} + +Tuple EdgeMesh::collapse_edge(const Tuple& t, Accessor& hash_accessor) +{ + EdgeMesh::EdgeMeshOperationExecutor executor(*this, t, hash_accessor); + return executor.collapse_edge(); +} + +long EdgeMesh::id(const Tuple& tuple, PrimitiveType type) const +{ + switch (type) { + case PrimitiveType::Vertex: { + ConstAccessor ev_accessor = create_const_accessor(m_ev_handle); + auto ev = ev_accessor.vector_attribute(tuple); + return ev(tuple.m_local_vid); + } + case PrimitiveType::Edge: { + return tuple.m_global_cid; + } + case PrimitiveType::Face: + case PrimitiveType::Tetrahedron: + default: throw std::runtime_error("Tuple id: Invalid primitive type"); + } +} + +bool EdgeMesh::is_boundary(const Tuple& tuple) const +{ + return is_boundary_vertex(tuple); +} + +bool EdgeMesh::is_boundary_vertex(const Tuple& tuple) const +{ + assert(is_valid_slow(tuple)); + ConstAccessor ee_accessor = create_const_accessor(m_ee_handle); + return ee_accessor.vector_attribute(tuple)(tuple.m_local_vid) < 0; +} + +Tuple EdgeMesh::switch_tuple(const Tuple& tuple, PrimitiveType type) const +{ + assert(is_valid_slow(tuple)); + bool ccw = is_ccw(tuple); + + switch (type) { + case PrimitiveType::Vertex: + return Tuple( + 1 - tuple.m_local_vid, + tuple.m_local_eid, + tuple.m_local_fid, + tuple.m_global_cid, + tuple.m_hash); + case PrimitiveType::Edge: { + const long gvid = id(tuple, PrimitiveType::Vertex); + + ConstAccessor ee_accessor = create_const_accessor(m_ee_handle); + auto ee = ee_accessor.vector_attribute(tuple); + + long gcid_new = ee(tuple.m_local_vid); + + // This is for special case self-loop, just to make sure the local vid of the returned + // tuple is the same as the input. (When doing double-switch this is needed) + if (gcid_new == tuple.m_global_cid) { + return tuple; + } + + long lvid_new = -1; + + ConstAccessor ev_accessor = create_const_accessor(m_ev_handle); + auto ev = ev_accessor.index_access().vector_attribute(gcid_new); + + for (long i = 0; i < 2; ++i) { + if (ev(i) == gvid) { + lvid_new = i; + // break; + } + } + assert(lvid_new != -1); + + ConstAccessor hash_accessor = get_const_cell_hash_accessor(); + + const Tuple res( + lvid_new, + tuple.m_local_eid, + tuple.m_local_fid, + gcid_new, + get_cell_hash(gcid_new, hash_accessor)); + assert(is_valid(res, hash_accessor)); + return res; + } + case PrimitiveType::Face: + case PrimitiveType::Tetrahedron: + default: throw std::runtime_error("Tuple switch: Invalid primitive type"); break; + } +} + +bool EdgeMesh::is_ccw(const Tuple& tuple) const +{ + assert(is_valid_slow(tuple)); + return tuple.m_local_vid == 0; +} + +void EdgeMesh::initialize( + Eigen::Ref EV, + Eigen::Ref EE, + Eigen::Ref VE) +{ + // reserve memory for attributes + + std::vector cap{static_cast(VE.rows()), static_cast(EE.rows())}; + + set_capacities(cap); + + // get accessors for topology + Accessor ev_accessor = create_accessor(m_ev_handle); + Accessor ee_accessor = create_accessor(m_ee_handle); + Accessor ve_accessor = create_accessor(m_ve_handle); + + Accessor v_flag_accessor = get_flag_accessor(PrimitiveType::Vertex); + Accessor e_flag_accessor = get_flag_accessor(PrimitiveType::Edge); + + // iterate over the matrices and fill attributes + + for (long i = 0; i < capacity(PrimitiveType::Edge); ++i) { + ev_accessor.index_access().vector_attribute(i) = EV.row(i).transpose(); + ee_accessor.index_access().vector_attribute(i) = EE.row(i).transpose(); + + e_flag_accessor.index_access().scalar_attribute(i) |= 0x1; + } + // m_ve + for (long i = 0; i < capacity(PrimitiveType::Vertex); ++i) { + ve_accessor.index_access().scalar_attribute(i) = VE(i); + v_flag_accessor.index_access().scalar_attribute(i) |= 0x1; + } +} + +void EdgeMesh::initialize(Eigen::Ref E) +{ + auto [EE, VE] = edgemesh_topology_initialization(E); + initialize(E, EE, VE); +} + +long EdgeMesh::_debug_id(const Tuple& tuple, PrimitiveType type) const +{ + wmtk::logger().warn("This function must only be used for debugging!!"); + return id(tuple, type); +} + +Tuple EdgeMesh::tuple_from_id(const PrimitiveType type, const long gid) const +{ + switch (type) { + case PrimitiveType::Vertex: { + return vertex_tuple_from_id(gid); + } + case PrimitiveType::Edge: { + return edge_tuple_from_id(gid); + } + case PrimitiveType::Face: { + throw std::runtime_error("no tet tuple supported for edgemesh"); + break; + } + case PrimitiveType::Tetrahedron: { + throw std::runtime_error("no tet tuple supported for edgemesh"); + break; + } + default: throw std::runtime_error("Invalid primitive type"); break; + } +} + +Tuple EdgeMesh::vertex_tuple_from_id(long id) const +{ + ConstAccessor ve_accessor = create_const_accessor(m_ve_handle); + auto e = ve_accessor.index_access().scalar_attribute(id); + ConstAccessor ev_accessor = create_const_accessor(m_ev_handle); + auto ev = ev_accessor.index_access().vector_attribute(e); + for (long i = 0; i < 2; ++i) { + if (ev(i) == id) { + Tuple v_tuple = Tuple(i, -1, -1, e, get_cell_hash_slow(e)); + return v_tuple; + } + } + throw std::runtime_error("vertex_tuple_from_id failed"); +} + +Tuple EdgeMesh::edge_tuple_from_id(long id) const +{ + Tuple e_tuple = Tuple(0, -1, -1, id, get_cell_hash_slow(id)); + + assert(is_valid_slow(e_tuple)); + return e_tuple; +} + +bool EdgeMesh::is_valid(const Tuple& tuple, ConstAccessor& hash_accessor) const +{ + if (tuple.is_null()) return false; + + if (tuple.m_local_vid < 0 || tuple.m_global_cid < 0) return false; + + return Mesh::is_hash_valid(tuple, hash_accessor); +} + +bool EdgeMesh::is_connectivity_valid() const +{ + // get accessors for topology + ConstAccessor ev_accessor = create_const_accessor(m_ev_handle); + ConstAccessor ee_accessor = create_const_accessor(m_ee_handle); + ConstAccessor ve_accessor = create_const_accessor(m_ve_handle); + ConstAccessor v_flag_accessor = get_flag_accessor(PrimitiveType::Vertex); + ConstAccessor e_flag_accessor = get_flag_accessor(PrimitiveType::Edge); + + // VE and EV + for (long i = 0; i < capacity(PrimitiveType::Vertex); ++i) { + if (v_flag_accessor.index_access().scalar_attribute(i) == 0) { + wmtk::logger().debug("Vertex {} is deleted", i); + continue; + } + int cnt = 0; + for (long j = 0; j < 2; ++j) { + if (ev_accessor.index_access().vector_attribute( + ve_accessor.index_access().scalar_attribute(i))[j] == i) { + cnt++; + } + } + if (cnt == 0) { + return false; + } + } + + // EV and EE + for (long i = 0; i < capacity(PrimitiveType::Edge); ++i) { + if (e_flag_accessor.index_access().scalar_attribute(i) == 0) { + wmtk::logger().debug("Edge {} is deleted", i); + continue; + } + // TODO: need to handle cornor case (self-loop) + } + + return true; +} + +} // namespace wmtk diff --git a/src/wmtk/EdgeMesh.hpp b/src/wmtk/EdgeMesh.hpp new file mode 100644 index 0000000000..4bf35c37ff --- /dev/null +++ b/src/wmtk/EdgeMesh.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "Mesh.hpp" +#include "Tuple.hpp" + +#include + +namespace wmtk { + +class EdgeMesh : public Mesh +{ +public: + EdgeMesh(); + EdgeMesh(const EdgeMesh& o); + EdgeMesh(EdgeMesh&& o); + EdgeMesh& operator=(const EdgeMesh& o); + EdgeMesh& operator=(EdgeMesh&& o); + + PrimitiveType top_simplex_type() const override { return PrimitiveType::Edge; } + + Tuple split_edge(const Tuple& t, Accessor& hash_accessor); + + Tuple collapse_edge(const Tuple& t, Accessor& hash_accessor); + + Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const override; + + bool is_ccw(const Tuple& tuple) const override; + bool is_boundary(const Tuple& tuple) const override; + bool is_boundary_vertex(const Tuple& tuple) const override; + bool is_boundary_edge(const Tuple& tuple) const override + { + throw("This function doesn't make sense for EdgeMesh"); + } + + void initialize(Eigen::Ref E); + + void initialize( + Eigen::Ref EV, + Eigen::Ref EE, + Eigen::Ref VE); + + long _debug_id(const Tuple& tuple, PrimitiveType type) const; + long _debug_id(const Simplex& simplex) const + { + return _debug_id(simplex.tuple(), simplex.primitive_type()); + } + + + bool is_valid(const Tuple& tuple, ConstAccessor& hash_accessor) const override; + + bool is_connectivity_valid() const override; + +protected: + long id(const Tuple& tuple, PrimitiveType type) const override; + long id(const Simplex& simplex) const { return id(simplex.tuple(), simplex.primitive_type()); } + + long id_vertex(const Tuple& tuple) const { return id(tuple, PrimitiveType::Vertex); } + long id_edge(const Tuple& tuple) const { return id(tuple, PrimitiveType::Edge); } + + /** + * @brief internal function that returns the tuple of requested type, and has the global index + * cid + * + * @param gid + * @return Tuple + */ + Tuple tuple_from_id(const PrimitiveType type, const long gid) const override; + +protected: + attribute::MeshAttributeHandle m_ve_handle; + + attribute::MeshAttributeHandle m_ev_handle; + attribute::MeshAttributeHandle m_ee_handle; + + Tuple vertex_tuple_from_id(long id) const; + Tuple edge_tuple_from_id(long id) const; + + // internal structure that encapsulations the actual execution of split and collapse + class EdgeMeshOperationExecutor; +}; + +} // namespace wmtk diff --git a/src/wmtk/EdgeMeshOperationExecutor.cpp b/src/wmtk/EdgeMeshOperationExecutor.cpp new file mode 100644 index 0000000000..abdc4ee307 --- /dev/null +++ b/src/wmtk/EdgeMeshOperationExecutor.cpp @@ -0,0 +1,243 @@ + +#include "EdgeMeshOperationExecutor.hpp" + +namespace wmtk { +// constructor +EdgeMesh::EdgeMeshOperationExecutor::EdgeMeshOperationExecutor( + EdgeMesh& m, + const Tuple& operating_tuple, + Accessor& hash_acc) + : flag_accessors{{m.get_flag_accessor(PrimitiveType::Vertex), m.get_flag_accessor(PrimitiveType::Edge)}} + , ee_accessor(m.create_accessor(m.m_ee_handle)) + , ev_accessor(m.create_accessor(m.m_ev_handle)) + , ve_accessor(m.create_accessor(m.m_ve_handle)) + , hash_accessor(hash_acc) + , m_mesh(m) + , m_operating_tuple(operating_tuple) + +{ + Tuple operating_tuple_switch_vertex = m_mesh.switch_vertex(operating_tuple); + // store ids of incident vertices + m_operating_edge_id = m_mesh.id_edge(m_operating_tuple); + m_spine_vids[0] = m_mesh.id_vertex(m_operating_tuple); + m_spine_vids[1] = m_mesh.id_vertex(operating_tuple_switch_vertex); + + // update hash on neighborhood + cell_ids_to_update_hash.emplace_back(m_mesh.id_edge(m_operating_tuple)); + if (!m_mesh.is_boundary(m_operating_tuple)) { + m_neighbor_eids[0] = m_mesh.id_edge(m_mesh.switch_edge(m_operating_tuple)); + cell_ids_to_update_hash.emplace_back(m_neighbor_eids[0]); + } + if (!m_mesh.is_boundary(operating_tuple_switch_vertex)) { + m_neighbor_eids[1] = m_mesh.id_edge(m_mesh.switch_edge(operating_tuple_switch_vertex)); + cell_ids_to_update_hash.emplace_back(m_neighbor_eids[1]); + } + + if (m_neighbor_eids[0] == m_neighbor_eids[1] && m_neighbor_eids[0] == m_operating_edge_id) { + m_is_self_loop = true; + } +} + +void EdgeMesh::EdgeMeshOperationExecutor::delete_simplices() +{ + for (size_t d = 0; d < simplex_ids_to_delete.size(); ++d) { + for (const long id : simplex_ids_to_delete[d]) { + flag_accessors[d].index_access().scalar_attribute(id) = 0; + } + } +} + +void EdgeMesh::EdgeMeshOperationExecutor::update_cell_hash() +{ + m_mesh.update_cell_hashes(cell_ids_to_update_hash, hash_accessor); +} + +const std::array, 2> +EdgeMesh::EdgeMeshOperationExecutor::get_split_simplices_to_delete( + const Tuple& tuple, + const EdgeMesh& m) +{ + std::array, 2> ids; + ids[1].emplace_back(m.id_edge(tuple)); + return ids; +} + +const std::array, 2> +EdgeMesh::EdgeMeshOperationExecutor::get_collapse_simplices_to_delete( + const Tuple& tuple, + const EdgeMesh& m) +{ + std::array, 2> ids; + ids[0].emplace_back(m.id_vertex(tuple)); + ids[1].emplace_back(m.id_edge(tuple)); + return ids; +} + +std::vector EdgeMesh::EdgeMeshOperationExecutor::prepare_operating_tuples_for_child_meshes() + const +{ + // this function is designed as a helper for multi_mesh + throw("this function is not implemented"); + // return m_mesh.m_multi_mesh_manager.map_edge_tuple_to_all_children( + // m_mesh, + // Simplex::edge(m_operating_tuple)); + return std::vector(); +} + +Tuple EdgeMesh::EdgeMeshOperationExecutor::split_edge() +{ + return split_edge_single_mesh(); + // TODO: Implement for multi_mesh in the future +} + +Tuple EdgeMesh::EdgeMeshOperationExecutor::split_edge_single_mesh() +{ + simplex_ids_to_delete = get_split_simplices_to_delete(m_operating_tuple, m_mesh); + + // create new vertex + const std::vector new_vids = this->request_simplex_indices(PrimitiveType::Vertex, 1); + assert(new_vids.size() == 1); + const long v_new = new_vids[0]; + // create new edges + // new_eids[i] is connect to m_neighbor_eids[i] and m_spine_vids[i] + const std::vector new_eids = this->request_simplex_indices(PrimitiveType::Edge, 2); + assert(new_eids.size() == 2); + + const long local_vid = m_mesh.is_ccw(m_operating_tuple) ? 0 : 1; + + // update ee + { + // for 2 new edges + auto ee_new_0 = ee_accessor.index_access().vector_attribute(new_eids[0]); + auto ee_new_1 = ee_accessor.index_access().vector_attribute(new_eids[1]); + ee_new_0[local_vid ^ 1] = new_eids[1]; + ee_new_1[local_vid] = new_eids[0]; + if (m_is_self_loop) { + ee_new_0[local_vid] = new_eids[1]; + ee_new_1[local_vid ^ 1] = new_eids[0]; + } else { + ee_new_0[local_vid] = m_neighbor_eids[0]; + ee_new_1[local_vid ^ 1] = m_neighbor_eids[1]; + // for neighbor edges + for (long i = 0; i < 2; i++) { + if (m_neighbor_eids[i] != -1) { + auto ee_neighbor = + ee_accessor.index_access().vector_attribute(m_neighbor_eids[i]); + auto ev_neighbor = + ev_accessor.index_access().vector_attribute(m_neighbor_eids[i]); + for (long j = 0; j < 2; j++) { + if (ee_neighbor[j] == m_operating_edge_id && + ev_neighbor[j] == m_spine_vids[i]) { + ee_neighbor[j] = new_eids[i]; + break; + } + } + } + } + } + } + + // update ev + { + // for new edges + auto ev_new_0 = ev_accessor.index_access().vector_attribute(new_eids[0]); + auto ev_new_1 = ev_accessor.index_access().vector_attribute(new_eids[1]); + ev_new_0[local_vid] = m_spine_vids[0]; + ev_new_0[local_vid ^ 1] = v_new; + ev_new_1[local_vid] = v_new; + ev_new_1[local_vid ^ 1] = m_spine_vids[1]; + } + + // update ve + { + // for new vertex + ve_accessor.index_access().scalar_attribute(v_new) = new_eids[0]; + + // for spine vertices + ve_accessor.index_access().scalar_attribute(m_spine_vids[0]) = new_eids[0]; + ve_accessor.index_access().scalar_attribute(m_spine_vids[1]) = new_eids[1]; + } + update_cell_hash(); + delete_simplices(); + + // prepare return Tuple + return m_mesh.edge_tuple_from_id(new_eids[0]); +} + +void EdgeMesh::EdgeMeshOperationExecutor::update_hash_in_map(EdgeMesh& child_mesh) +{ + // TODO: Implement for multi_mesh in the future +} + +Tuple EdgeMesh::EdgeMeshOperationExecutor::collapse_edge() +{ + return collapse_edge_single_mesh(); + // TODO: Implement for multi_mesh in the future +} + + +Tuple EdgeMesh::EdgeMeshOperationExecutor::collapse_edge_single_mesh() +{ + // check if the collapse is valid + if (m_is_self_loop || (m_mesh.is_boundary(m_operating_tuple) && + m_mesh.is_boundary(m_mesh.switch_vertex(m_operating_tuple)))) { + return Tuple(); + } + + simplex_ids_to_delete = get_collapse_simplices_to_delete(m_operating_tuple, m_mesh); + + // update ee + { + // for neighbor edges + for (long i = 0; i < 2; i++) { + if (m_neighbor_eids[i] != -1) { + auto ee_neighbor = ee_accessor.index_access().vector_attribute(m_neighbor_eids[i]); + for (long j = 0; j < 2; j++) { + if (ee_neighbor[j] == m_operating_edge_id) { + ee_neighbor[j] = m_neighbor_eids[i ^ 1]; + break; + } + } + } + } + } + + // update ev + { + if (m_neighbor_eids[0] != -1) { + auto ev_neighbor = ev_accessor.index_access().vector_attribute(m_neighbor_eids[0]); + for (long j = 0; j < 2; j++) { + if (ev_neighbor[j] == m_spine_vids[0]) { + ev_neighbor[j] = m_spine_vids[1]; + } + } + } + } + + + // update ve + { + ve_accessor.index_access().scalar_attribute(m_spine_vids[1]) = m_neighbor_eids[1]; + } + + update_cell_hash(); + delete_simplices(); + + const long ret_eid = m_neighbor_eids[0] == -1 ? m_neighbor_eids[1] : m_neighbor_eids[0]; + Tuple ret_tuple = m_mesh.edge_tuple_from_id(ret_eid); + + if (m_mesh.id_vertex(ret_tuple) != m_spine_vids[1]) { + ret_tuple = m_mesh.switch_vertex(ret_tuple); + } + return ret_tuple; +} + +std::vector EdgeMesh::EdgeMeshOperationExecutor::request_simplex_indices( + const PrimitiveType type, + long count) +{ + m_mesh.reserve_attributes(type, m_mesh.capacity(type) + count); + return m_mesh.request_simplex_indices(type, count); +} + +} // namespace wmtk diff --git a/src/wmtk/EdgeMeshOperationExecutor.hpp b/src/wmtk/EdgeMeshOperationExecutor.hpp new file mode 100644 index 0000000000..a0dace515f --- /dev/null +++ b/src/wmtk/EdgeMeshOperationExecutor.hpp @@ -0,0 +1,68 @@ +#pragma once +#include +#include "EdgeMesh.hpp" +#include "SimplicialComplex.hpp" +#include "Tuple.hpp" +namespace wmtk { +class EdgeMesh::EdgeMeshOperationExecutor +{ +public: + EdgeMeshOperationExecutor(EdgeMesh& m, const Tuple& operating_tuple, Accessor& hash_acc); + void delete_simplices(); + void update_cell_hash(); + + std::array, 2> flag_accessors; + Accessor ee_accessor; + Accessor ev_accessor; + Accessor ve_accessor; + Accessor& hash_accessor; + + /** + * @brief gather all simplices that are deleted in a split + * + * The deleted simplex is the edge itself + * @return std::array, 2> first vector contains the vertex ids, second vector + * contains the edge ids + */ + static const std::array, 2> get_split_simplices_to_delete( + const Tuple& tuple, + const EdgeMesh& m); + + /** + * @brief gather all simplices that are deleted in a collapse + * + * The deleted simplices are the vertex and the edge of the input tuple + * @return std::array, 2> first vector contains the vertex ids, second vector + * contains the edge ids + */ + static const std::array, 2> get_collapse_simplices_to_delete( + const Tuple& tuple, + const EdgeMesh& m); + + const std::array& incident_vids() const { return m_spine_vids; } + + long operating_edge_id() const { return m_operating_edge_id; } + + Tuple split_edge(); + Tuple collapse_edge(); + Tuple split_edge_single_mesh(); + Tuple collapse_edge_single_mesh(); + + std::vector request_simplex_indices(const PrimitiveType type, long count); + + std::array, 2> simplex_ids_to_delete; + std::vector cell_ids_to_update_hash; + EdgeMesh& m_mesh; + Tuple m_operating_tuple; + + +private: + std::vector prepare_operating_tuples_for_child_meshes() const; + void update_hash_in_map(EdgeMesh& child_mesh); + bool m_is_self_loop = false; + // common simplicies + std::array m_spine_vids; // V_A_id, V_B_id; + std::array m_neighbor_eids = {-1, -1}; + long m_operating_edge_id; +}; +} // namespace wmtk diff --git a/src/wmtk/MultiMeshManager.hpp b/src/wmtk/MultiMeshManager.hpp index 7e380bc88f..d5efb446a0 100644 --- a/src/wmtk/MultiMeshManager.hpp +++ b/src/wmtk/MultiMeshManager.hpp @@ -54,7 +54,7 @@ class MultiMeshManager // Map functions //=========== //=========== - // Note that when we map a M-tuplefrom a K-complex to a J-complex there are different + // Note that when we map a M-tuple from a K-complex to a J-complex there are different // relationships necessary if K == J // if M == K then this is unique // if M < K then this is many to many @@ -62,7 +62,8 @@ class MultiMeshManager // if M == K then it is one to many // if M < K then it is many to many // - // Note also that functions that end with _tuple or _tuples willl return tuples rather than simplices + // Note also that functions that end with _tuple or _tuples willl return tuples rather than + // simplices //=========== // Simplex maps @@ -138,8 +139,6 @@ class MultiMeshManager map_to_child_tuples(const Mesh& my_mesh, long child_id, const Simplex& simplex) const; - - static Tuple map_tuple_between_meshes( const Mesh& source_mesh, const Mesh& target_mesh, diff --git a/src/wmtk/TriMesh.cpp b/src/wmtk/TriMesh.cpp index a5d6fdf46b..a795653587 100644 --- a/src/wmtk/TriMesh.cpp +++ b/src/wmtk/TriMesh.cpp @@ -331,7 +331,7 @@ bool TriMesh::is_connectivity_valid() const cnt++; } } - if (cnt != 1) { + if (cnt == 0) { // std::cout << "EF and FE not compatible" << std::endl; return false; } @@ -350,7 +350,7 @@ bool TriMesh::is_connectivity_valid() const cnt++; } } - if (cnt != 1) { + if (cnt == 0) { // std::cout << "VF and FV not compatible" << std::endl; return false; } diff --git a/src/wmtk/TriMeshOperationExecutor.cpp b/src/wmtk/TriMeshOperationExecutor.cpp index 52c158669b..7d63235b96 100644 --- a/src/wmtk/TriMeshOperationExecutor.cpp +++ b/src/wmtk/TriMeshOperationExecutor.cpp @@ -1,6 +1,6 @@ #include "TriMeshOperationExecutor.hpp" - +#include "SimplicialComplex.hpp" namespace wmtk { auto TriMesh::TriMeshOperationExecutor::get_incident_face_data(Tuple t) -> IncidentFaceData diff --git a/src/wmtk/Tuple.hpp b/src/wmtk/Tuple.hpp index 0837f1cf05..bcee43587a 100644 --- a/src/wmtk/Tuple.hpp +++ b/src/wmtk/Tuple.hpp @@ -7,8 +7,8 @@ namespace wmtk { class Mesh; class PointMesh; -class EdgeMesh; class TriMesh; +class EdgeMesh; class TetMesh; namespace utils { class TupleInspector; diff --git a/src/wmtk/Types.hpp b/src/wmtk/Types.hpp index 0ae79579c7..0f9a9926ba 100644 --- a/src/wmtk/Types.hpp +++ b/src/wmtk/Types.hpp @@ -3,7 +3,6 @@ #include namespace wmtk { - template using RowVectors = Eigen::Matrix; @@ -20,6 +19,7 @@ using RowVectorX = RowVector; using VectorXl = VectorX; using RowVector2d = RowVector; using RowVector3d = RowVector; +using RowVectors2l = RowVectors; using RowVectors3l = RowVectors; using RowVectors4l = RowVectors; using RowVectors6l = RowVectors; diff --git a/src/wmtk/attribute/ConstAccessor.hpp b/src/wmtk/attribute/ConstAccessor.hpp index 3b8d7bc2ea..fdac496c10 100644 --- a/src/wmtk/attribute/ConstAccessor.hpp +++ b/src/wmtk/attribute/ConstAccessor.hpp @@ -7,6 +7,11 @@ class Mesh; class TriMesh; class TetMesh; class TriMeshOperationExecutor; +class EdgeMesh; +namespace tests { +class DEBUG_TriMesh; +class DEBUG_EdgeMesh; +} // namespace tests } // namespace wmtk namespace wmtk::attribute { @@ -18,8 +23,11 @@ class ConstAccessor : protected TupleAccessor friend class wmtk::Mesh; friend class wmtk::TetMesh; friend class wmtk::TriMesh; + friend class wmtk::EdgeMesh; friend class wmtk::PointMesh; friend class wmtk::TriMeshOperationExecutor; + friend class wmtk::tests::DEBUG_TriMesh; + friend class wmtk::tests::DEBUG_EdgeMesh; using Scalar = T; friend class AttributeCache; @@ -51,12 +59,12 @@ class ConstAccessor : protected TupleAccessor using BaseType::attribute; // access to Attribute object being used here // shows the depth of scope stacks if they exist, mostly for debug - using CachingBaseType::stack_depth; using CachingBaseType::has_stack; + using CachingBaseType::stack_depth; protected: - using TupleBaseType::caching_base_type; using TupleBaseType::base_type; + using TupleBaseType::caching_base_type; using TupleBaseType::scalar_attribute; using TupleBaseType::vector_attribute; diff --git a/src/wmtk/attribute/MutableAccessor.hpp b/src/wmtk/attribute/MutableAccessor.hpp index 57b54373e9..4524854774 100644 --- a/src/wmtk/attribute/MutableAccessor.hpp +++ b/src/wmtk/attribute/MutableAccessor.hpp @@ -15,6 +15,7 @@ class MutableAccessor : public ConstAccessor friend class wmtk::Mesh; friend class wmtk::TetMesh; friend class wmtk::TriMesh; + friend class wmtk::EdgeMesh; friend class wmtk::PointMesh; friend class wmtk::TriMeshOperationExecutor; using CachingBaseType = CachingAccessor; @@ -30,8 +31,9 @@ class MutableAccessor : public ConstAccessor using ConstAccessorType::vector_attribute; - using CachingBaseType::stack_depth; using CachingBaseType::has_stack; + using CachingBaseType::stack_depth; + protected: using ConstAccessorType::base_type; using ConstAccessorType::caching_base_type; diff --git a/src/wmtk/utils/CMakeLists.txt b/src/wmtk/utils/CMakeLists.txt index 5242c768ce..ca0947f014 100644 --- a/src/wmtk/utils/CMakeLists.txt +++ b/src/wmtk/utils/CMakeLists.txt @@ -2,6 +2,8 @@ set(SRC_FILES Logger.cpp Logger.hpp + edgemesh_topology_initialization.h + edgemesh_topology_initialization.cpp trimesh_topology_initialization.h trimesh_topology_initialization.cpp tetmesh_topology_initialization.h diff --git a/src/wmtk/utils/edgemesh_topology_initialization.cpp b/src/wmtk/utils/edgemesh_topology_initialization.cpp new file mode 100644 index 0000000000..ca9b595b04 --- /dev/null +++ b/src/wmtk/utils/edgemesh_topology_initialization.cpp @@ -0,0 +1,53 @@ +#include "edgemesh_topology_initialization.h" +#include +#include +#include + +namespace wmtk { + +std::tuple edgemesh_topology_initialization( + Eigen::Ref E) +{ + RowVectors2l EE; + VectorXl VE; + + const long vertex_count = E.maxCoeff() + 1; + + // store the complete vertex-edge connnectivity + std::vector> complete_VE(vertex_count); + + // compute VE + VE.resize(vertex_count, 1); + for (long i = 0; i < E.rows(); ++i) { + for (long j = 0; j < E.cols(); ++j) { + VE[E(i, j)] = i; + complete_VE[E(i, j)].push_back(i); + } + } + + EE.resize(E.rows(), 2); + // compute EE & connectivity check + for (long i = 0; i < complete_VE.size(); ++i) { + assert(complete_VE[i].size() > 0 || complete_VE[i].size() < 3); + if (complete_VE[i].size() == 1) { + // boundary vertex + if (E(complete_VE[i][0], 0) == i) { + EE(complete_VE[i][0], 0) = -1; + } else { + EE(complete_VE[i][0], 1) = -1; + } + } else { + // non-boundary vertex + for (long k = 0; k < 2; ++k) { + if (E(complete_VE[i][k], 0) == i) { + EE(complete_VE[i][k], 0) = complete_VE[i][1 - k]; + } + if (E(complete_VE[i][k], 1) == i) { + EE(complete_VE[i][k], 1) = complete_VE[i][1 - k]; + } + } + } + } + return {EE, VE}; +} +} // namespace wmtk \ No newline at end of file diff --git a/src/wmtk/utils/edgemesh_topology_initialization.h b/src/wmtk/utils/edgemesh_topology_initialization.h new file mode 100644 index 0000000000..55f85e9b58 --- /dev/null +++ b/src/wmtk/utils/edgemesh_topology_initialization.h @@ -0,0 +1,12 @@ + +#pragma once + +#include + + +namespace wmtk { + +std::tuple edgemesh_topology_initialization( + Eigen::Ref E); // returns {EE, VE} + +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5f011cddab..83b05a8544 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,12 +12,14 @@ set(TEST_SOURCES test_topology.cpp test_autogen.cpp test_tuple.cpp + test_tuple_1d.cpp test_tuple_2d.cpp test_tuple_3d.cpp test_io.cpp test_execution.cpp test_simplex_collection.cpp test_simplicial_complex.cpp + test_1d_operations.cpp test_2d_operations.cpp test_2d_operation_construction.cpp test_accessor.cpp @@ -25,8 +27,12 @@ set(TEST_SOURCES test_3d_operations.cpp test_multi_mesh.cpp tools/DEBUG_PointMesh.hpp + tools/DEBUG_EdgeMesh.hpp + tools/DEBUG_EdgeMesh.cpp tools/DEBUG_TriMesh.hpp tools/DEBUG_TriMesh.cpp + tools/EdgeMesh_examples.hpp + tools/EdgeMesh_examples.cpp tools/TriMesh_examples.hpp tools/TriMesh_examples.cpp tools/redirect_logger_to_cout.hpp diff --git a/tests/test_1d_operations.cpp b/tests/test_1d_operations.cpp new file mode 100644 index 0000000000..0ad9cfec84 --- /dev/null +++ b/tests/test_1d_operations.cpp @@ -0,0 +1,460 @@ +#include + +#include +#include +#include +#include +#include +#include "tools/DEBUG_EdgeMesh.hpp" +#include "tools/EdgeMesh_examples.hpp" + +using namespace wmtk; +using namespace wmtk::tests; + +using EM = EdgeMesh; +using MapResult = typename Eigen::Matrix::MapType; +using EMOE = decltype(std::declval().get_emoe( + wmtk::Tuple(), + std::declval&>())); + +constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PE = PrimitiveType::Edge; + +TEST_CASE("simplices_to_delete_for_split_1D", "[operations][1D]") +{ + SECTION("single line") + { + DEBUG_EdgeMesh m = single_line(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + executor.split_edge(); + REQUIRE(m.is_connectivity_valid()); + const auto& ids_to_delete = executor.simplex_ids_to_delete; + REQUIRE(ids_to_delete[0].size() == 0); + REQUIRE(ids_to_delete[1].size() == 1); + REQUIRE(ids_to_delete[1][0] == edge_id); + } + + SECTION("self loop") + { + DEBUG_EdgeMesh m = self_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + executor.split_edge(); + REQUIRE(m.is_connectivity_valid()); + const auto& ids_to_delete = executor.simplex_ids_to_delete; + REQUIRE(ids_to_delete[0].size() == 0); + REQUIRE(ids_to_delete[1].size() == 1); + REQUIRE(ids_to_delete[1][0] == edge_id); + } +} + +TEST_CASE("simplices_to_delete_for_collapse_1D", "[operations][1D]") +{ + SECTION("multiple_lines") + { + DEBUG_EdgeMesh m = multiple_lines(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 2; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + auto executor = m.get_emoe(edge, hash_accessor); + + executor.collapse_edge(); + // REQUIRE(m.is_connectivity_valid()); + const auto& ids_to_delete = executor.simplex_ids_to_delete; + REQUIRE(ids_to_delete[0].size() == 1); + REQUIRE(ids_to_delete[0][0] == 2); + REQUIRE(ids_to_delete[1].size() == 1); + REQUIRE(ids_to_delete[1][0] == edge_id); + } + + SECTION("two_line_loop") + { + DEBUG_EdgeMesh m = two_line_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + auto executor = m.get_emoe(edge, hash_accessor); + + executor.collapse_edge(); + const auto& ids_to_delete = executor.simplex_ids_to_delete; + REQUIRE(ids_to_delete[0].size() == 1); + REQUIRE(ids_to_delete[0][0] == 0); + REQUIRE(ids_to_delete[1].size() == 1); + REQUIRE(ids_to_delete[1][0] == edge_id); + } +} + + +TEST_CASE("collapse_edge_1D", "[operations][1D]") +{ + SECTION("multiple_lines") + { + DEBUG_EdgeMesh m = multiple_lines(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 2; + Tuple edge = m.tuple_from_edge_id(edge_id); + + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + const long vertex_id = m._debug_id(edge, PV); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.collapse_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // collapse operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 1); + CHECK(m._debug_id(ret_tuple, PV) == 3); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + CHECK(m.is_simplex_deleted(PV, vertex_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(3) == 3); + CHECK(ee.vector_attribute(1)[1] == 3); + CHECK(ee.vector_attribute(3)[0] == 1); + CHECK(ev.vector_attribute(1)[1] == 3); + CHECK(ev.vector_attribute(3)[0] == 3); + } + + SECTION("single_line") + { + DEBUG_EdgeMesh m = single_line(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.collapse_edge(); + CHECK(ret_tuple.is_null()); // collapse opearation is invalid + CHECK(m.is_connectivity_valid()); + } + + SECTION("self_loop") + { + DEBUG_EdgeMesh m = self_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.collapse_edge(); + CHECK(ret_tuple.is_null()); // collapse opearation is invalid + CHECK(m.is_connectivity_valid()); + } + + SECTION("two_line_loop") + { + DEBUG_EdgeMesh m = two_line_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.collapse_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // collapse operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 1); + CHECK(m._debug_id(ret_tuple, PV) == 1); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + CHECK(m.is_simplex_deleted(PV, vertex_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(1) == 1); + CHECK(ee.vector_attribute(1)[0] == 1); + CHECK(ee.vector_attribute(1)[1] == 1); + CHECK(ev.vector_attribute(1)[0] == 1); + CHECK(ev.vector_attribute(1)[1] == 1); + } + + SECTION("loop_lines") + { + DEBUG_EdgeMesh m = loop_lines(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.collapse_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // collapse operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 5); + CHECK(m._debug_id(ret_tuple, PV) == 1); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + CHECK(m.is_simplex_deleted(PV, vertex_id)); + + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(1) == 1); + CHECK(ee.vector_attribute(1)[0] == 5); + CHECK(ee.vector_attribute(5)[1] == 1); + CHECK(ev.vector_attribute(1)[0] == 1); + CHECK(ev.vector_attribute(5)[1] == 1); + } +} + +TEST_CASE("split_edge_1D", "[operations][1D]") +{ + SECTION("multiple_lines") + { + DEBUG_EdgeMesh m = multiple_lines(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 2; + Tuple edge = m.tuple_from_edge_id(edge_id); + + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + const long vertex_id = m._debug_id(edge, PV); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.split_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // split operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 5); + CHECK(m._debug_id(ret_tuple, PV) == vertex_id); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(3) == 6); + CHECK(ve.scalar_attribute(6) == 5); + CHECK(ve.scalar_attribute(2) == 5); + + CHECK(ee.vector_attribute(1)[1] == 5); + CHECK(ee.vector_attribute(5)[0] == 1); + CHECK(ee.vector_attribute(5)[1] == 6); + CHECK(ee.vector_attribute(6)[0] == 5); + CHECK(ee.vector_attribute(6)[1] == 3); + CHECK(ee.vector_attribute(3)[0] == 6); + + CHECK(ev.vector_attribute(5)[0] == 2); + CHECK(ev.vector_attribute(5)[1] == 6); + CHECK(ev.vector_attribute(6)[0] == 6); + CHECK(ev.vector_attribute(6)[1] == 3); + } + + SECTION("single_line") + { + DEBUG_EdgeMesh m = single_line(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.split_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // split opearation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 1); + CHECK(m._debug_id(ret_tuple, PV) == vertex_id); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(0) == 1); + CHECK(ve.scalar_attribute(2) == 1); + CHECK(ve.scalar_attribute(1) == 2); + + CHECK(ee.vector_attribute(1)[0] == -1); + CHECK(ee.vector_attribute(1)[1] == 2); + CHECK(ee.vector_attribute(2)[0] == 1); + CHECK(ee.vector_attribute(2)[1] == -1); + + CHECK(ev.vector_attribute(1)[0] == 0); + CHECK(ev.vector_attribute(1)[1] == 2); + CHECK(ev.vector_attribute(2)[0] == 2); + CHECK(ev.vector_attribute(2)[1] == 1); + } + + SECTION("self_loop") + { + DEBUG_EdgeMesh m = self_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.split_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // split opearation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 1); + CHECK(m._debug_id(ret_tuple, PV) == vertex_id); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(0) == 2); + CHECK(ve.scalar_attribute(1) == 1); + + CHECK(ee.vector_attribute(1)[0] == 2); + CHECK(ee.vector_attribute(1)[1] == 2); + CHECK(ee.vector_attribute(2)[0] == 1); + CHECK(ee.vector_attribute(2)[1] == 1); + + CHECK(ev.vector_attribute(1)[0] == 0); + CHECK(ev.vector_attribute(1)[1] == 1); + CHECK(ev.vector_attribute(2)[0] == 1); + CHECK(ev.vector_attribute(2)[1] == 0); + } + + SECTION("two_line_loop") + { + DEBUG_EdgeMesh m = two_line_loop(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.split_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // split operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 2); + CHECK(m._debug_id(ret_tuple, PV) == vertex_id); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(0) == 2); + CHECK(ve.scalar_attribute(1) == 3); + CHECK(ve.scalar_attribute(2) == 2); + + CHECK(ee.vector_attribute(1)[1] == 2); + CHECK(ee.vector_attribute(2)[0] == 1); + CHECK(ee.vector_attribute(2)[1] == 3); + CHECK(ee.vector_attribute(3)[0] == 2); + CHECK(ee.vector_attribute(3)[1] == 1); + CHECK(ee.vector_attribute(1)[0] == 3); + + CHECK(ev.vector_attribute(2)[0] == 0); + CHECK(ev.vector_attribute(2)[1] == 2); + CHECK(ev.vector_attribute(3)[0] == 2); + CHECK(ev.vector_attribute(3)[1] == 1); + } + + SECTION("loop_lines") + { + DEBUG_EdgeMesh m = loop_lines(); + REQUIRE(m.is_connectivity_valid()); + + const long edge_id = 0; + Tuple edge = m.tuple_from_edge_id(edge_id); + const long vertex_id = m._debug_id(edge, PV); + Accessor hash_accessor = m.get_cell_hash_accessor(); + REQUIRE(m.is_valid(edge, hash_accessor)); + + auto executor = m.get_emoe(edge, hash_accessor); + + const Tuple ret_tuple = executor.split_edge(); + CHECK(m.is_connectivity_valid()); + CHECK(!ret_tuple.is_null()); // split operation is valid + // check return tuple + CHECK(m._debug_id(ret_tuple, PE) == 6); + CHECK(m._debug_id(ret_tuple, PV) == vertex_id); + // check delete + CHECK(m.is_simplex_deleted(PE, edge_id)); + + auto ve = m.create_base_accessor(m.ve_handle()); + auto ee = m.create_base_accessor(m.e_handle(PE)); + auto ev = m.create_base_accessor(m.e_handle(PV)); + // check ve, ee, ev + CHECK(ve.scalar_attribute(0) == 6); + CHECK(ve.scalar_attribute(1) == 7); + CHECK(ve.scalar_attribute(6) == 6); + + CHECK(ee.vector_attribute(5)[1] == 6); + CHECK(ee.vector_attribute(6)[0] == 5); + CHECK(ee.vector_attribute(6)[1] == 7); + CHECK(ee.vector_attribute(7)[0] == 6); + CHECK(ee.vector_attribute(7)[1] == 1); + CHECK(ee.vector_attribute(1)[0] == 7); + + CHECK(ev.vector_attribute(6)[0] == 0); + CHECK(ev.vector_attribute(6)[1] == 6); + CHECK(ev.vector_attribute(7)[0] == 6); + CHECK(ev.vector_attribute(7)[1] == 1); + } +} \ No newline at end of file diff --git a/tests/test_2d_operation_construction.cpp b/tests/test_2d_operation_construction.cpp index f31e6171fc..1fbae22455 100644 --- a/tests/test_2d_operation_construction.cpp +++ b/tests/test_2d_operation_construction.cpp @@ -51,7 +51,6 @@ DEBUG_TriMesh test_split(const DEBUG_TriMesh& mesh, long edge_index, bool should return test_split(mesh, e, should_succeed); } - // because TriMesh::collapse_edge isn'ta waare of preconditions we need to tell the system whether // something should succeed DEBUG_TriMesh test_collapse(const DEBUG_TriMesh& mesh, const Tuple& e, bool should_succeed) diff --git a/tests/test_topology.cpp b/tests/test_topology.cpp index c1e6e3ccd5..40644a9ef1 100644 --- a/tests/test_topology.cpp +++ b/tests/test_topology.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -334,3 +335,67 @@ TEST_CASE("topology_of_tet_bunny", "[topology][3D]") } } } + +TEST_CASE("topology_test_1d", "[topology][1D]") +{ + Eigen::Matrix E; + SECTION("single_line") + { + /* + 0 ---- 1 + */ + E.resize(1, 2); + E << 0, 1; + } + SECTION("multiple_lines") + { + /* + 5 -- 2 -- 0 -- 1 -- 4 -- 3 + */ + E.resize(5, 2); + E << 0, 1, 1, 4, 3, 4, 2, 0, 5, 2; + } + SECTION("loop_lines") + { + /* + 5 -- 2 -- 0 -- 1 -- 4 -- 3 -- 5* + */ + E.resize(6, 2); + E << 0, 1, 1, 4, 3, 4, 2, 0, 5, 2, 5, 3; + } + SECTION("two_line_loop") + { + /* + 0 -- 1 -- 0* + */ + E.resize(2, 2); + E << 0, 1, 1, 0; + } + SECTION("self_loop") + { + /* + 0 -- 0* + */ + E.resize(1, 2); + E << 0, 0; + } + + const auto [EE, VE] = edgemesh_topology_initialization(E); + + // 1. Test relationship between VE and EV + for (int i = 0; i < VE.size(); ++i) { + CHECK((E.row(VE(i)).array() == i).any()); + } + + // 2. Test relationship between EV and EE + for (int i = 0; i < EE.rows(); ++i) { + for (int j = 0; j < 2; ++j) { + long nb = EE(i, j); + if (nb < 0) continue; + + CHECK((EE.row(nb).array() == i).any()); + + // TODO add checks + } + } +} diff --git a/tests/test_tuple_1d.cpp b/tests/test_tuple_1d.cpp new file mode 100644 index 0000000000..263d0aa500 --- /dev/null +++ b/tests/test_tuple_1d.cpp @@ -0,0 +1,308 @@ +#include +#include +#include +#include +#include +#include +#include "tools/DEBUG_EdgeMesh.hpp" +#include "tools/EdgeMesh_examples.hpp" + +using namespace wmtk; +using namespace wmtk::tests; + +TEST_CASE("1D_initialize", "[mesh_creation],[tuple_1d]") +{ + DEBUG_EdgeMesh m; + std::vector edges, vertices; + + SECTION("init from RowVectors2l") + { + RowVectors2l lines; + lines.resize(3, 2); + lines << 0, 1, 1, 2, 2, 3; + + m.initialize(lines); + + vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 4); + edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 3); + + REQUIRE(m.is_connectivity_valid()); + } + SECTION("init single line") + { + m = single_line(); + + vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 2); + edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 1); + + REQUIRE(m.is_connectivity_valid()); + } + SECTION("init multiple lines") + { + m = multiple_lines(); + + vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 6); + edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 5); + + REQUIRE(m.is_connectivity_valid()); + } + SECTION("init loop lines") + { + m = loop_lines(); + + vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 6); + edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 6); + + REQUIRE(m.is_connectivity_valid()); + } + SECTION("init self loop") + { + m = self_loop(); + + vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 1); + edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 1); + + REQUIRE(m.is_connectivity_valid()); + } + + auto const_hash_accessor = m.get_const_cell_hash_accessor(); + for (size_t i = 0; i < edges.size(); ++i) { + REQUIRE(m.is_valid(edges[i], const_hash_accessor)); + } +} + +TEST_CASE("1D_single_line", "[tuple_generation], [tuple_1d]") +{ + DEBUG_EdgeMesh m = single_line(); + + SECTION("vertices") + { + const std::vector vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 2); + CHECK(m.id(vertices[0], PrimitiveType::Vertex) == 0); + CHECK(m.id(vertices[1], PrimitiveType::Vertex) == 1); + CHECK(m.id(vertices[0], PrimitiveType::Edge) == 0); + CHECK(m.id(vertices[1], PrimitiveType::Edge) == 0); + } + SECTION("edges") + { + const std::vector edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 1); + } +} + +TEST_CASE("1D_multiple_lines", "[tuple_generation], [tuple_1d]") +{ + DEBUG_EdgeMesh m = multiple_lines(); + + SECTION("vertices") + { + const std::vector vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 6); + for (long i = 0; i < 6; ++i) { + CHECK(m.id(vertices[i], PrimitiveType::Vertex) == i); + } + CHECK(m.id(vertices[0], PrimitiveType::Edge) == 0); + CHECK(m.id(vertices[5], PrimitiveType::Edge) == 4); + } + SECTION("edges") + { + const std::vector edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 5); + for (long i = 0; i < 5; ++i) { + CHECK(m.id(edges[i], PrimitiveType::Edge) == i); + } + } +} + +TEST_CASE("1D_loop_lines", "[tuple_generation], [tuple_1d]") +{ + DEBUG_EdgeMesh m = loop_lines(); + + SECTION("vertices") + { + const std::vector vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 6); + for (long i = 0; i < 6; ++i) { + CHECK(m.id(vertices[i], PrimitiveType::Vertex) == i); + } + } + SECTION("edges") + { + const std::vector edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 6); + for (long i = 0; i < 6; ++i) { + CHECK(m.id(edges[i], PrimitiveType::Edge) == i); + } + } +} + + +TEST_CASE("1D_self_loop", "[tuple_generation], [tuple_1d]") +{ + DEBUG_EdgeMesh m = self_loop(); + + SECTION("vertices") + { + const std::vector vertices = m.get_all(PrimitiveType::Vertex); + REQUIRE(vertices.size() == 1); + CHECK(m._debug_id(vertices[0], PrimitiveType::Vertex) == 0); + } + SECTION("edges") + { + const std::vector edges = m.get_all(PrimitiveType::Edge); + REQUIRE(edges.size() == 1); + } +} + +TEST_CASE("1D_random_switches", "[tuple_operation],[tuple_1d]") +{ + DEBUG_EdgeMesh m = loop_lines(); + ConstAccessor hash_accessor = m.get_const_cell_hash_accessor(); + SECTION("vertices") + { + const std::vector vertex_tuples = m.get_all(PrimitiveType::Vertex); + for (size_t i = 0; i < vertex_tuples.size(); ++i) { + Tuple t = vertex_tuples[i]; + for (size_t j = 0; j < 10; j++) { + switch (rand() % 2) { + case 0: t = m.switch_tuple(t, PrimitiveType::Vertex); break; + case 1: + if (!m.is_boundary(t)) { + t = m.switch_tuple(t, PrimitiveType::Edge); + } + break; + default: break; + } + CHECK(m.is_valid(t, hash_accessor)); + } + } + } + + SECTION("edges") + { + const std::vector edge_tuples = m.get_all(PrimitiveType::Edge); + for (size_t i = 0; i < edge_tuples.size(); ++i) { + Tuple t = edge_tuples[i]; + for (size_t j = 0; j < 10; j++) { + switch (rand() % 2) { + case 0: t = m.switch_tuple(t, PrimitiveType::Vertex); break; + case 1: + if (!m.is_boundary(t)) { + t = m.switch_tuple(t, PrimitiveType::Edge); + } + break; + default: break; + } + CHECK(m.is_valid(t, hash_accessor)); + } + } + } +} + +TEST_CASE("1D_is_boundary", "[tuple_1d]") +{ + DEBUG_EdgeMesh m; + size_t n_boundary_vertices_expected = std::numeric_limits::max(); + + SECTION("single_line") + { + m = single_line(); + n_boundary_vertices_expected = 2; + } + + SECTION("multiple_lines") + { + m = multiple_lines(); + n_boundary_vertices_expected = 2; + } + + SECTION("loop_lines") + { + m = loop_lines(); + n_boundary_vertices_expected = 0; + } + + SECTION("two_line_loop") + { + m = two_line_loop(); + n_boundary_vertices_expected = 0; + } + + SECTION("self_loop") + { + m = self_loop(); + n_boundary_vertices_expected = 0; + } + + // count boundary vertices + size_t n_boundary_vertices = 0; + for (const Tuple& v : m.get_all(PrimitiveType::Vertex)) { + if (m.is_boundary(v)) { + ++n_boundary_vertices; + } + } + + CHECK(n_boundary_vertices == n_boundary_vertices_expected); +} + +TEST_CASE("1D_double_switches", "[tuple_operation],[tuple_1d]") +{ + // checking for every tuple t: + // (1) t.switch_vertex().switch_vertex() == t + // (2) t.switch_edge().switch_edge() == t + + DEBUG_EdgeMesh m; + SECTION("single_line") + { + m = single_line(); + } + SECTION("multiple_lines") + { + m = multiple_lines(); + } + SECTION("two_line_loop") + { + m = two_line_loop(); + } + SECTION("loop_lines") + { + m = loop_lines(); + } + SECTION("self_loop") + { + m = self_loop(); + } + + // vertices + const std::vector vertices = m.get_all(PrimitiveType::Vertex); + for (const auto& t : vertices) { + const Tuple t_after_v = m.switch_vertex(m.switch_vertex(t)); + CHECK(t == t_after_v); + if (!m.is_boundary(t)) { + const Tuple t_after_e = m.switch_edge(m.switch_edge(t)); + CHECK(t == t_after_e); + } + } + + // edges + const std::vector edges = m.get_all(PrimitiveType::Edge); + for (const auto& t : edges) { + const Tuple t_after_v = m.switch_vertex(m.switch_vertex(t)); + CHECK(t == t_after_v); + if (!m.is_boundary(t)) { + const Tuple t_after_e = m.switch_edge(m.switch_edge(t)); + CHECK(t == t_after_e); + } + } +} \ No newline at end of file diff --git a/tests/tools/DEBUG_EdgeMesh.cpp b/tests/tools/DEBUG_EdgeMesh.cpp new file mode 100644 index 0000000000..65ee274ee8 --- /dev/null +++ b/tests/tools/DEBUG_EdgeMesh.cpp @@ -0,0 +1,135 @@ +#include "DEBUG_EdgeMesh.hpp" +#include + +namespace wmtk::tests { + +DEBUG_EdgeMesh::DEBUG_EdgeMesh(const EdgeMesh& m) + : EdgeMesh(m) +{} +DEBUG_EdgeMesh::DEBUG_EdgeMesh(EdgeMesh&& m) + : EdgeMesh(std::move(m)) +{} + + +bool DEBUG_EdgeMesh::operator==(const DEBUG_EdgeMesh& o) const +{ + throw("This function is not tested yet"); + return static_cast(*this) == static_cast(o); +} +bool DEBUG_EdgeMesh::operator!=(const DEBUG_EdgeMesh& o) const +{ + throw("This function is not tested yet"); + return !(*this == o); +} + +void DEBUG_EdgeMesh::print_state() const +{ + throw("This function is not implemented. maybe redundant"); +} + +void DEBUG_EdgeMesh::print_ve() const +{ + throw("this function was written in the style of DEBUG_TriMesh::print_vf() but was not tested " + "yet"); + auto ev_accessor = create_base_accessor(e_handle(PrimitiveType::Vertex)); + auto e_flag_accessor = get_flag_accessor(PrimitiveType::Edge); + for (long id = 0; id < capacity(PrimitiveType::Edge); ++id) { + auto ev = ev_accessor.const_vector_attribute(id); + if (e_flag_accessor.const_scalar_attribute(tuple_from_id(PrimitiveType::Edge, id)) == 0) { + std::cout << "edge " << id << " is deleted" << std::endl; + } else { + std::cout << ev(0) << " " << ev(1) << std::endl; + } + } +} + +Eigen::Matrix DEBUG_EdgeMesh::ev_from_eid(const long eid) const +{ + throw("this function is never used"); + auto ev_accessor = create_base_accessor(e_handle(PrimitiveType::Vertex)); + return ev_accessor.vector_attribute(eid); +} + +auto DEBUG_EdgeMesh::edge_tuple_from_vids(const long v1, const long v2) const -> Tuple +{ + throw("this function is never used"); + ConstAccessor ev = create_accessor(m_ev_handle); + for (long eid = 0; eid < capacity(PrimitiveType::Edge); ++eid) { + Tuple edge = edge_tuple_from_id(eid); + auto ev0 = ev.const_vector_attribute(edge); + long local_vid1 = -1, local_vid2 = -1; + for (long i = 0; i < ev0.size(); ++i) { + if (ev0[i] == v1) { + local_vid1 = i; + } + if (ev0[i] == v2) { + local_vid2 = i; + } + } + if (local_vid1 != -1 && local_vid2 != -1) { + return Tuple(local_vid1, -1, -1, eid, get_cell_hash_slow(eid)); + } + } + return Tuple(); +} + +auto DEBUG_EdgeMesh::tuple_from_edge_id(const long eid) const -> Tuple +{ + return tuple_from_id(PrimitiveType::Edge, eid); +} + + +const MeshAttributeHandle& DEBUG_EdgeMesh::e_handle(const PrimitiveType type) const +{ + switch (type) { + case PrimitiveType::Vertex: return m_ev_handle; + case PrimitiveType::Edge: return m_ee_handle; + case PrimitiveType::Face:; + default: throw std::runtime_error("Invalid PrimitiveType"); + } +} + +const MeshAttributeHandle& DEBUG_EdgeMesh::ve_handle() const +{ + return m_ve_handle; +} + +const MeshAttributeHandle& DEBUG_EdgeMesh::ev_handle() const +{ + return m_ev_handle; +} + + +void DEBUG_EdgeMesh::reserve_attributes(PrimitiveType type, long size) +{ + Mesh::reserve_attributes(type, size); +} + + +long DEBUG_EdgeMesh::id(const Tuple& tuple, PrimitiveType type) const +{ + return EdgeMesh::id(tuple, type); +} +long DEBUG_EdgeMesh::id(const Simplex& s) const +{ + return id(s.tuple(), s.primitive_type()); +} +Accessor DEBUG_EdgeMesh::get_cell_hash_accessor() +{ + return EdgeMesh::get_cell_hash_accessor(); +} +/** + * @brief returns the EdgeMeshOperationExecutor + */ +auto DEBUG_EdgeMesh::get_emoe(const Tuple& t, Accessor& hash_accessor) + -> EdgeMeshOperationExecutor +{ + return EdgeMeshOperationExecutor(*this, t, hash_accessor); +} + +bool DEBUG_EdgeMesh::is_simplex_deleted(PrimitiveType type, const long id) const +{ + const auto flag_accessor = get_flag_accessor(type); + return flag_accessor.index_access().scalar_attribute(id) == 0; +} +} // namespace wmtk::tests diff --git a/tests/tools/DEBUG_EdgeMesh.hpp b/tests/tools/DEBUG_EdgeMesh.hpp new file mode 100644 index 0000000000..9d7be7fa1f --- /dev/null +++ b/tests/tools/DEBUG_EdgeMesh.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include + +namespace wmtk::tests { +class DEBUG_EdgeMesh : public EdgeMesh +{ +public: + using EdgeMesh::EdgeMesh; + DEBUG_EdgeMesh(const EdgeMesh& m); + DEBUG_EdgeMesh(EdgeMesh&& m); + using EdgeMesh::operator=; + + + bool operator==(const DEBUG_EdgeMesh& o) const; + bool operator!=(const DEBUG_EdgeMesh& o) const; + + // uses spdlog to print out a variety of information about the mesh + void print_state() const; + + void print_ve() const; + Eigen::Matrix ev_from_eid(const long eid) const; + + auto edge_tuple_from_vids(const long v1, const long v2) const -> Tuple; + auto tuple_from_edge_id(const long eid) const -> Tuple; + + template + attribute::AccessorBase create_base_accessor(const MeshAttributeHandle& handle) + { + return attribute::AccessorBase(*this, handle); + } + + template + attribute::AccessorBase create_const_base_accessor( + const MeshAttributeHandle& handle) const + { + return attribute::AccessorBase(const_cast(*this), handle); + } + template + attribute::AccessorBase create_base_accessor(const MeshAttributeHandle& handle) const + { + return create_const_base_accessor(handle); + } + + const MeshAttributeHandle& e_handle(const PrimitiveType type) const; + + const MeshAttributeHandle& ve_handle() const; + + const MeshAttributeHandle& ev_handle() const; + + + void reserve_attributes(PrimitiveType type, long size); + + + long id(const Tuple& tuple, PrimitiveType type) const override; + long id(const Simplex& s) const; + /** + * @brief returns the TriMeshOperationExecutor + */ + using EdgeMesh::tuple_from_id; + + Accessor get_cell_hash_accessor(); + + EdgeMeshOperationExecutor get_emoe(const Tuple& t, Accessor& hash_accessor); + + bool is_simplex_deleted(PrimitiveType type, const long id) const; +}; + +} // namespace wmtk::tests diff --git a/tests/tools/EdgeMesh_examples.cpp b/tests/tools/EdgeMesh_examples.cpp new file mode 100644 index 0000000000..8540c06aed --- /dev/null +++ b/tests/tools/EdgeMesh_examples.cpp @@ -0,0 +1,75 @@ +#include "EdgeMesh_examples.hpp" + + +namespace wmtk::tests { +EdgeMesh single_line() +{ + EdgeMesh m; + RowVectors2l edges; + edges.resize(1, 2); + + edges.row(0) << 0, 1; + + m.initialize(edges); + return m; +} + +EdgeMesh multiple_lines() +{ + EdgeMesh m; + RowVectors2l edges; + edges.resize(5, 2); + + edges.row(0) << 0, 1; + edges.row(1) << 1, 2; + edges.row(2) << 2, 3; + edges.row(3) << 3, 4; + edges.row(4) << 4, 5; + + m.initialize(edges); + return m; +} + +EdgeMesh loop_lines() +{ + EdgeMesh m; + RowVectors2l edges; + edges.resize(6, 2); + + edges.row(0) << 0, 1; + edges.row(1) << 1, 2; + edges.row(2) << 2, 3; + edges.row(3) << 3, 4; + edges.row(4) << 4, 5; + edges.row(5) << 5, 0; + + m.initialize(edges); + return m; +} + +EdgeMesh self_loop() +{ + EdgeMesh m; + RowVectors2l edges; + + edges.resize(1, 2); + edges.row(0) << 0, 0; + + m.initialize(edges); + return m; +} + +EdgeMesh two_line_loop() +{ + EdgeMesh m; + RowVectors2l edges; + edges.resize(2, 2); + + edges.row(0) << 0, 1; + edges.row(1) << 1, 0; + + m.initialize(edges); + return m; +} + +} // namespace wmtk::tests \ No newline at end of file diff --git a/tests/tools/EdgeMesh_examples.hpp b/tests/tools/EdgeMesh_examples.hpp new file mode 100644 index 0000000000..61abc82cc4 --- /dev/null +++ b/tests/tools/EdgeMesh_examples.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace wmtk::tests { + +/* + 0 --- 1 +*/ +EdgeMesh single_line(); + +/* + 0 -- 1 -- 2 -- 3 -- 4 -- 5 +*/ +EdgeMesh multiple_lines(); + +/* + 0 -- 1 -- 2 -- 3 -- 4 -- 5 -- 0* +*/ + +EdgeMesh loop_lines(); + +/* + 0 --- 0 +*/ +EdgeMesh self_loop(); + +/* + 0 -- 1 -- 0* +*/ +EdgeMesh two_line_loop(); + +} // namespace wmtk::tests \ No newline at end of file