diff --git a/engine/surfaces/CMakeLists.txt b/engine/surfaces/CMakeLists.txt index 2643812f46..58e9d7f291 100644 --- a/engine/surfaces/CMakeLists.txt +++ b/engine/surfaces/CMakeLists.txt @@ -10,6 +10,7 @@ SET ( FILES disctype enumerator enumfilter + fault links normalsurface normalsurfaces diff --git a/engine/surfaces/fault.cpp b/engine/surfaces/fault.cpp new file mode 100644 index 0000000000..0facbbcccb --- /dev/null +++ b/engine/surfaces/fault.cpp @@ -0,0 +1,189 @@ + +/************************************************************************** + * * + * Regina - A Normal Surface Theory Calculator * + * Computational Engine * + * * + * This file: Copyright (c) 2020, Robert C. Haraway, III * + * For further details contact Robert (bobbycyiii@fastmail.com). * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * As an exception, when this program is distributed through (i) the * + * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * + * (iii) Google Play by Google Inc., then that store may impose any * + * digital rights management, device limits and/or redistribution * + * restrictions that are required by its terms of service. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public * + * License along with this program; if not, write to the Free * + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + **************************************************************************/ + + +#include "surfaces/normalsurface.h" +#include "triangulation/dim3.h" + +namespace regina { + bool NormalSurface::separates() const{ + int tri_cpts = triangulation()->countComponents(); + Triangulation<3>* cut_up = cutAlong(); + cut_up->intelligentSimplify(); + int new_cpts = cut_up->countComponents(); + delete cut_up; + return tri_cpts < new_cpts; + } + + bool NormalSurface::isEssentialSphere() const{ + if (!(isConnected() + && isCompact() + && eulerChar() == 2)) + return false; + + if (!separates()) + return true; + + Triangulation<3>* cut_up = cutAlong(); + cut_up->intelligentSimplify(); + + // Cap sphere boundary components. + // Since the original triangulation has no sphere boundary components, + // this caps the sides of this sphere. + // That is, this undoes a (possibly trivial) connect-sum. + cut_up->finiteToIdeal(); + cut_up->intelligentSimplify(); + cut_up->idealToFinite(); + cut_up->intelligentSimplify(); + + // There are two components, + // since the original triangulation was connected, + // and this surface is separating. + cut_up->splitIntoComponents(); + Triangulation<3>* L = (Triangulation<3>*) cut_up->firstChild(); + Triangulation<3>* R = (Triangulation<3>*) L->nextSibling(); + bool bounds_ball = L->isThreeSphere() || R->isThreeSphere(); + delete L; + delete R; + delete cut_up; + return !bounds_ball; + } + + bool NormalSurface::isEssentialKleinBottle() const{ + if (!(isConnected() + && isCompact() + && !isOrientable() + && !hasRealBoundary() + && eulerChar() == 0)){ + return false; + } + + if (!separates()){ + Triangulation<3>* cut_up = cutAlong(); + bool compressible = cut_up->hasCompressingDisc(); + delete cut_up; + return !compressible; + } + + /* + if (!isIncompressible()) + return false; + */ + // is what we would like to write here, + // but presently `isIncompressible` requires closed triangulations. + + // There are two components, + // since the original triangulation was connected, + // and this surface is separating. + Triangulation<3>* cut_up = cutAlong(); + cut_up->splitIntoComponents(); + Triangulation<3>* L = (Triangulation<3>*) cut_up->firstChild(); + Triangulation<3>* R = (Triangulation<3>*) L->nextSibling(); + + bool compressible = true; + bool boundary_parallel = false; + compressible = L->hasCompressingDisc() || R->hasCompressingDisc(); + if (!compressible){ + L->makeDoubleCover(); + R->makeDoubleCover(); + boundary_parallel = L->isTorusXInterval() || R->isTorusXInterval(); + } + delete L; + delete R; + delete cut_up; + return !(compressible || boundary_parallel); + } + + bool NormalSurface::isEssentialTorus() const{ + if (!(isConnected() + && isCompact() + && isOrientable() + && !hasRealBoundary() + && eulerChar() == 0)) + return false; + + if (!separates()) + return true; + /* + if (!isIncompressible()) + return false; + */ + // is what we would like to write here, + // but presently `isIncompressible` requires closed triangulations. + + // There are two components, + // since the original triangulation was connected, + // and this surface is separating. + Triangulation<3>* cut_up = cutAlong(); + cut_up->splitIntoComponents(); + Triangulation<3>* L = (Triangulation<3>*) cut_up->firstChild(); + Triangulation<3>* R = (Triangulation<3>*) L->nextSibling(); + + bool compressible = true; + bool boundary_parallel = false; + compressible = L->hasCompressingDisc() || R->hasCompressingDisc(); + if (!compressible){ + boundary_parallel = L->isTorusXInterval() || R->isTorusXInterval(); + } + delete L; + delete R; + delete cut_up; + return !(compressible || boundary_parallel); + } + + bool NormalSurface::isSolidTorusAnnulus() const{ + if (!(isConnected() + && isCompact() + && isOrientable() + && hasRealBoundary() + && eulerChar() == 0)) + return false; + + Triangulation<3>* cut_up = cutAlong(); + cut_up->intelligentSimplify(); + + bool solid_torus; + if (cut_up->isConnected()) + solid_torus = cut_up->isSolidTorus(); + else { + cut_up->splitIntoComponents(); + Triangulation<3>* L = (Triangulation<3>*)(cut_up->firstChild()); + Triangulation<3>* R = (Triangulation<3>*)(L->nextSibling()); + solid_torus = L->isSolidTorus() && R->isSolidTorus(); + delete L; + delete R; + } + delete cut_up; + return solid_torus; + } + +} diff --git a/engine/surfaces/normalsurface.h b/engine/surfaces/normalsurface.h index 98fc4098c3..58ca0caafd 100644 --- a/engine/surfaces/normalsurface.h +++ b/engine/surfaces/normalsurface.h @@ -1443,6 +1443,57 @@ class REGINA_API NormalSurface : public ShortOutput { */ bool isIncompressible() const; + /** + * Determines whether or not cutting along the surface + * yields a new component. + */ + bool separates() const; + + /** + * Determines whether or not this surface is an essential sphere. + * + * \pre The underlying triangulation is connected + * and has no sphere boundary components. + * \pre This normal surface can be cut along. + * + * @return \c true if this is an essential sphere, otherwise \c false. + */ + bool isEssentialSphere() const; + + /** + * Determines whether or not this surface is an essential Klein bottle. + * + * \pre The underlying triangulation is connected and irreducible, + * and has no compressing disc (i.e. is boundary-incompressible). + * \pre This normal surface can be cut along. + * + * @return \c true if this is an essential Klein bottle, else \c false. + */ + bool isEssentialKleinBottle() const; + + /** + * Determines whether or not this surface is an essential torus. + * + * \pre The underlying triangulation is connected and irreducible, + * and has no compressing disc (i.e. is boundary-incompressible). + * \pre This normal surface can be cut along. + * + * @return \c true if this is an essential torus, else \c false. + */ + bool isEssentialTorus() const; + + /** + * Determines whether or not this surface is an annulus + * cutting along which yields a disjoint union of at most two solid tori, + * which we call a \e solid \e torus \e annulus. + * This is easier to check than whether or not the annulus is essential. + * + * \pre This normal surface can be cut along. + * + * @return \c true if this surface is a solid torus annulus, else \c false. + */ + bool isSolidTorusAnnulus() const; + /** * Cuts the associated triangulation along this surface and * returns a newly created resulting triangulation. diff --git a/engine/triangulation/dim3/decompose.cpp b/engine/triangulation/dim3/decompose.cpp index a8af326319..1fd8087092 100644 --- a/engine/triangulation/dim3/decompose.cpp +++ b/engine/triangulation/dim3/decompose.cpp @@ -39,6 +39,9 @@ #include "surfaces/normalsurface.h" #include "surfaces/normalsurfaces.h" #include "triangulation/dim3.h" +#include "triangulation/generic/boundarycomponent.h" +#include "triangulation/generic/face.h" +#include "triangulation/homologicaldata.h" namespace regina { @@ -561,7 +564,7 @@ bool Triangulation<3>::isSolidTorus() const { delete crushed; } } - + bool Triangulation<3>::knowsSolidTorus() const { if (solidTorus_.known()) return true; @@ -587,6 +590,129 @@ bool Triangulation<3>::knowsSolidTorus() const { return false; } + +Edge<3>* Triangulation<3>::getEmbeddedBoundaryEdge() { + for (BoundaryComponent<3> *k : boundaryComponents()) + for (Edge<3> *e : k->edges()) + if (e->vertex(0) != e->vertex(1)) + return e; + return (Edge<3>*) 0; +} + +Edge <3>* Triangulation<3>::getClosableBoundaryEdge() { + for (BoundaryComponent<3> *k : boundaryComponents()) + for (Edge<3> *e : k->edges()) + if (closeBook(e,true,false)) + return e; + return (Edge<3>*) 0; +} + +bool Triangulation<3>::isTorusXInterval() const { + if (knowsTorusXInterval()){ + return torusXInterval_.value(); + } + + Triangulation<3>* working = new Triangulation<3>(*this, false); + working->intelligentSimplify(); + working->idealToFinite(); + working->intelligentSimplify(); + + // If it's not a homology T2xI, we're done. + if (!working->isConnected() + || working->countBoundaryComponents() != 2 + || working->homology().str() != "2 Z" + || working->homologyRel().str() != "Z" + || working->homologyBdry().str() != "4 Z"){ + delete working; + return (torusXInterval_ = false); + } + + // Crushing will not introduce new boundary faces. + // T2xI is irreducible, so we should be able to crush to a 0-efficient triangulation. + + int surfs = 0; + for (NormalSurface* round = working->hasNonTrivialSphereOrDisc(); + round; + round = working->hasNonTrivialSphereOrDisc()){ + + Triangulation<3>* crushed = round->crush(); + delete round; + delete working; + working = 0; + + int n = crushed->splitIntoComponents(); + if (n == 0){ + // A round surface crushed everything away; we don't have T2xI. + delete crushed; + return (torusXInterval_ = false); + } + + bool gotNewTriangulation = false; + for (Packet* p = crushed->firstChild(); p; p->nextSibling()){ + working = static_cast*>(p); + // If it's a homology T2xI, use it. + // (We can't have two such components, + // since then the original triangulation would not have been a homology T2xI.) + if (working->isConnected() + && working->countBoundaryComponents() == 2 + && working->homology().str() == "2 Z" + && working->homologyRel().str() == "Z" + && working->homologyBdry().str() == "4 Z"){ + // Save our work into a new triangulation before cleaning up crushed. + working = new Triangulation<3>(*working, false); + delete crushed; + // The above deletion presumably deletes crushing's descendants too, + // such as p and working's former value. + // See similar code in isSolidTorus. + gotNewTriangulation = true; + break; + } + } + + if (!gotNewTriangulation){ + // None of the components of the crushing was a homology T2xI. + if (working) + delete working; + return (torusXInterval_ = false); + } + } + + // At this point we should already have boundary components with one vertex each. + // But out of an abundance of caution, we ensure this is the case. + Edge<3>* emb = working->getEmbeddedBoundaryEdge(); + while (emb != 0){ + working->layerOn(emb); + Edge<3>* cls = working->getClosableBoundaryEdge(); + while (cls != 0){ + working->closeBook(cls,false,true); + cls = working->getClosableBoundaryEdge(); + } + emb = working->getEmbeddedBoundaryEdge(); + } + + // We have a 0-efficient homology T2xI. + // So we should move on to the meat of the algorithm, testing Dehn fillings. + + BoundaryComponent<3>* k = working->boundaryComponent(0); + bool isT2xI = true; + for (Edge<3>* e : k->edges()){ + Triangulation<3>* filled = new Triangulation<3>(*working, false); + Edge<3>* cls = filled->edge(e->index()); + filled->closeBook(cls, false, true); + isT2xI = isT2xI && filled->isSolidTorus(); + delete filled; + if (!isT2xI) { + break; + } + } + delete working; + return (torusXInterval_ = isT2xI); +} + +bool Triangulation<3>::knowsTorusXInterval() const { + return torusXInterval_.known(); +} + Packet* Triangulation<3>::makeZeroEfficient() { // Extract a connected sum decomposition. Container* connSum = new Container(); diff --git a/engine/triangulation/dim3/triangulation3.h b/engine/triangulation/dim3/triangulation3.h index c1334c2fca..24310be51f 100644 --- a/engine/triangulation/dim3/triangulation3.h +++ b/engine/triangulation/dim3/triangulation3.h @@ -163,6 +163,8 @@ class REGINA_API Triangulation<3> : /**< Is this a triangulation of a 3-dimensional ball? */ mutable Property solidTorus_; /**< Is this a triangulation of the solid torus? */ + mutable Property torusXInterval_; + /**< Is this a triangulation of the solid torus? */ mutable Property irreducible_; /**< Is this 3-manifold irreducible? */ mutable Property compressingDisc_; @@ -2036,6 +2038,39 @@ class REGINA_API Triangulation<3> : */ bool knowsSolidTorus() const; + /** + * Return a pointer to an embedded boundary edge. + */ + + Edge<3> *getEmbeddedBoundaryEdge(); + + /** + * Return a pointer to a closable boundary edge. + */ + + Edge<3> *getClosableBoundaryEdge(); + + /** + * Determines whether or not the underlying 3-manifold is + * the product of a torus with an interval, + * using the algorithm from + * https://journals.carleton.ca/jocg/index.php/jocg/article/view/433 + * (also available as arXiv:1410.7115). + * + * \warning This algorithm ultimately relies on isSolidTorus(), + * which might run slowly for large triangulations. + * + * \pre This triangulation induces one-vertex triangulations + * on its boundary components. + */ + bool isTorusXInterval() const; + + /** + * Determines whether or not the value of isTorusXInterval is known, + * not the value itself. + */ + bool knowsTorusXInterval() const; + /** * Determines whether the underlying 3-manifold (which must be * closed) is irreducible. In other words, this routine determines diff --git a/python/dim3/triangulation3.cpp b/python/dim3/triangulation3.cpp index 04de3e2da4..0e0e266d6f 100644 --- a/python/dim3/triangulation3.cpp +++ b/python/dim3/triangulation3.cpp @@ -296,6 +296,8 @@ void addTriangulation3(pybind11::module& m) { .def("knowsBall", &Triangulation<3>::knowsBall) .def("isSolidTorus", &Triangulation<3>::isSolidTorus) .def("knowsSolidTorus", &Triangulation<3>::knowsSolidTorus) + .def("isTorusXInterval", &Triangulation<3>::isTorusXInterval) + .def("knowsTorusXInterval", &Triangulation<3>::knowsTorusXInterval) .def("isIrreducible", &Triangulation<3>::isIrreducible) .def("knowsIrreducible", &Triangulation<3>::knowsIrreducible) .def("hasCompressingDisc", &Triangulation<3>::hasCompressingDisc) diff --git a/python/surfaces/normalsurface.cpp b/python/surfaces/normalsurface.cpp index 4cb428ad4b..929fdc54ec 100644 --- a/python/surfaces/normalsurface.cpp +++ b/python/surfaces/normalsurface.cpp @@ -117,6 +117,11 @@ void addNormalSurface(pybind11::module& m) { .def("countBoundaries", &NormalSurface::countBoundaries) .def("isCompressingDisc", &NormalSurface::isCompressingDisc, pybind11::arg("knownConnected") = false) + .def("separates", &NormalSurface::separates) + .def("isEssentialSphere", &NormalSurface::isEssentialSphere) + .def("isEssentialTorus", &NormalSurface::isEssentialTorus) + .def("isEssentialKleinBottle", &NormalSurface::isEssentialKleinBottle) + .def("isSolidTorusAnnulus", &NormalSurface::isSolidTorusAnnulus) .def("isIncompressible", &NormalSurface::isIncompressible) .def("cutAlong", &NormalSurface::cutAlong) .def("crush", &NormalSurface::crush) diff --git a/testsuite/dim3/triangulation3.cpp b/testsuite/dim3/triangulation3.cpp index 8c13394f6c..2129a260f0 100644 --- a/testsuite/dim3/triangulation3.cpp +++ b/testsuite/dim3/triangulation3.cpp @@ -99,6 +99,7 @@ class Triangulation3Test : public TriangulationTest<3> { CPPUNIT_TEST(threeSphereRecognitionLarge); CPPUNIT_TEST(threeBallRecognition); CPPUNIT_TEST(solidTorusRecognition); + CPPUNIT_TEST(torusXIntervalRecognition); CPPUNIT_TEST(turaevViro); CPPUNIT_TEST(barycentricSubdivision); CPPUNIT_TEST(idealToFinite); @@ -2866,6 +2867,145 @@ class Triangulation3Test : public TriangulationTest<3> { runCensusAllBounded(&testSolidTorus4); } + Triangulation<3>* verifyTorusXInterval(Triangulation<3>* tri, + const char* triName = 0) { + if (triName) + tri->setLabel(triName); + + Triangulation<3> bounded(*tri); + if (bounded.isIdeal()) + bounded.idealToFinite(); + clearProperties(bounded); + + Triangulation<3> ideal(*tri); + if (ideal.hasBoundaryTriangles()) + ideal.finiteToIdeal(); + clearProperties(ideal); + + Triangulation<3> boundedBig(bounded); + boundedBig.barycentricSubdivision(); + clearProperties(boundedBig); + + Triangulation<3> idealBig(ideal); + idealBig.barycentricSubdivision(); + clearProperties(idealBig); + + if (! bounded.isTorusXInterval()) { + CPPUNIT_FAIL(("The real T^2xI " + + tri->label() + " is not recognised as such.").c_str()); + } + if (! ideal.isTorusXInterval()) { + CPPUNIT_FAIL(("The ideal T^2xI " + + tri->label() + " is not recognised as such.").c_str()); + } + if (! boundedBig.isTorusXInterval()) { + CPPUNIT_FAIL(("The subdivided real T^2xI " + + tri->label() + " is not recognised as such.").c_str()); + } + if (! idealBig.isTorusXInterval()) { + CPPUNIT_FAIL(("The subdivided ideal T^2xI " + + tri->label() + " is not recognised as such.").c_str()); + } + + return tri; + } + + Triangulation<3>* verifyNotTorusXInterval(Triangulation<3>* tri, + const char* triName = 0) { + if (triName) + tri->setLabel(triName); + + Triangulation<3> bounded(*tri); + if (bounded.isIdeal()) + bounded.idealToFinite(); + clearProperties(bounded); + + Triangulation<3> ideal(*tri); + if (ideal.hasBoundaryTriangles()) + ideal.finiteToIdeal(); + clearProperties(ideal); + + Triangulation<3> boundedBig(bounded); + boundedBig.barycentricSubdivision(); + clearProperties(boundedBig); + + Triangulation<3> idealBig(ideal); + idealBig.barycentricSubdivision(); + clearProperties(idealBig); + + if (bounded.isTorusXInterval()) { + CPPUNIT_FAIL(("The real non-T^2xI " + + tri->label() + " is recognised as a T^2xI.").c_str()); + } + if (ideal.isTorusXInterval()) { + CPPUNIT_FAIL(("The ideal non-T^2xI " + + tri->label() + " is recognised as a T^2xI.").c_str()); + } + if (boundedBig.isTorusXInterval()) { + CPPUNIT_FAIL(("The subdivided real non-T^2xI " + + tri->label() + " is recognised as a T^2xI.").c_str()); + } + if (idealBig.isTorusXInterval()) { + CPPUNIT_FAIL(("The subdivided ideal non-T^2xI " + + tri->label() + " is recognised as a T^2xI.").c_str()); + } + + return tri; + } + + void verifyIsoSigTorusXInterval(const std::string& sigStr) { + Triangulation<3>* t = Triangulation<3>::fromIsoSig(sigStr); + t->setLabel(sigStr); + delete verifyTorusXInterval(t); + } + + void verifyIsoSigNotTorusXInterval(const std::string& sigStr) { + Triangulation<3>* t = Triangulation<3>::fromIsoSig(sigStr); + t->setLabel(sigStr); + delete verifyNotTorusXInterval(t); + } + + void torusXIntervalRecognition() { + Triangulation<3>* tri; + + tri = Triangulation<3>::fromIsoSig("eLAkbbcddadbdb"); + delete verifyTorusXInterval(tri, "Ideal T2xI eLAkbbcddadbdb"); + + tri = new Triangulation<3>(); + delete verifyNotTorusXInterval(tri, "Empty triangulation"); + + tri = new Triangulation<3>(); + tri->newTetrahedron(); + delete verifyNotTorusXInterval(tri, "Single tetrahedron"); + + tri = new Triangulation<3>(); + Tetrahedron<3>* tet = tri->newTetrahedron(); + tet->join(0, tet, Perm<4>(3, 1, 2, 0)); + delete verifyNotTorusXInterval(tri, "Snapped tetrahedron"); + + // Now we check some homology-T2xI manifolds. + + // Some links from 4^2_1 thru 7^2_8 + // in Bailey and Roth's tables from Rolfsen's *Knots and links.* + // (5^2_1 and 7^2_8 have the same exterior.) + + verifyIsoSigNotTorusXInterval("eLPkbdcddabgbg"); + verifyIsoSigNotTorusXInterval("eLPkbdcddhgggb"); + verifyIsoSigNotTorusXInterval("eLMkbcdddaeeda"); + verifyIsoSigNotTorusXInterval("eLMkbcddddedde"); + // verifyIsoSigNotTorusXInterval("gLLMQbcdefffmvftaog"); + // verifyIsoSigNotTorusXInterval("fLLQcbecdeepuwsua"); + // verifyIsoSigNotTorusXInterval("hLLAPkbcdefgggtsfxjjgb"); + // verifyIsoSigNotTorusXInterval("hLLMPkbcdfggfgmvfafwkf"); + // verifyIsoSigNotTorusXInterval("hLLzQkcdegffgguvuqpgvk"); + // verifyIsoSigNotTorusXInterval("iLLLAQccdegfhhghdcltautwa"); + // verifyIsoSigNotTorusXInterval("kLLLALQkceffehijjijiiealshealf"); + verifyIsoSigNotTorusXInterval("eLPkbdcddabobv"); + + // Finally, the connected sum of the Poincare homology sphere and T2xI: + // verifyIsoSigNotTorusXInterval("pLvwwLuPIIIkaddkomnjlllonobabtlqinfjwjnw"); + } + void verifyTV3(Triangulation<3>& t, const std::string& triName) { // Verify the Turaev-Viro invariants for r=3. // The expected values are described in the paper of Turaev diff --git a/testsuite/surfaces/CMakeLists.txt b/testsuite/surfaces/CMakeLists.txt index 15b6922116..d9f5d7013e 100644 --- a/testsuite/surfaces/CMakeLists.txt +++ b/testsuite/surfaces/CMakeLists.txt @@ -2,6 +2,7 @@ # Files to compile SET ( FILES + fault incompressible normalsurfaces ) diff --git a/testsuite/surfaces/fault.cpp b/testsuite/surfaces/fault.cpp new file mode 100644 index 0000000000..fdb802f0d9 --- /dev/null +++ b/testsuite/surfaces/fault.cpp @@ -0,0 +1,909 @@ + +/************************************************************************** + * * + * Regina - A Normal Surface Theory Calculator * + * Test Suite * + * * + * Copyright (c) 2020, Robert C. Haraway, III. * + * For further details contact Robert Haraway (bobbycyiii@fastmail.com). * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * As an exception, when this program is distributed through (i) the * + * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * + * (iii) Google Play by Google Inc., then that store may impose any * + * digital rights management, device limits and/or redistribution * + * restrictions that are required by its terms of service. * + * * + * This program is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * You should have received a copy of the GNU General Public * + * License along with this program; if not, write to the Free * + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + * * + **************************************************************************/ + +#include +#include +#include "manifold/sfs.h" +#include "surfaces/normalcoords.h" +#include "surfaces/normalflags.h" +#include "surfaces/normalsurface.h" +#include "surfaces/normalsurfaces.h" +#include "triangulation/dim3.h" +#include "triangulation/example3.h" +#include "testsuite/dim3/testtriangulation.h" + + +using regina::Example; +using regina::NormalListFlags; +using regina::NormalSurface; +using regina::NormalSurfaces; +using regina::NS_EMBEDDED_ONLY; +using regina::NS_FUNDAMENTAL; +using regina::NS_QUAD; +using regina::NS_VERTEX; +using regina::SFSFibre; +using regina::SFSpace; +using regina::Triangulation; + +using std::ostringstream; + +NormalListFlags NS_VERTEX_EMBEDDED = (NormalListFlags)(NS_VERTEX & NS_EMBEDDED_ONLY); + +class FaultTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(FaultTest); + + CPPUNIT_TEST(separates); + CPPUNIT_TEST(isEssentialSphere); + CPPUNIT_TEST(isEssentialKleinBottle); + CPPUNIT_TEST(isEssentialTorus); + CPPUNIT_TEST(isSolidTorusAnnulus); + + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() { + } + + void tearDown() { + } + + + Triangulation<3>* verifyAllVertexSeparating(Triangulation<3>* tri, + const std::string& triName) { + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool nonseparating_known = false; + for (unsigned i = 0; i < s->size(); ++i) + if (!(s->surface(i)->separates())){ + nonseparating_known = true; + break; + } + delete s; + if (nonseparating_known) + CPPUNIT_FAIL(("A surface in " + triName + + " is computed to be nonseparating.").c_str()); + return tri; + } + + Triangulation<3>* verifyHasVertexNonSeparating(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool all_known_surfaces_separating = true; + for (unsigned i = 0; i < s->size(); ++i) + if (!(s->surface(i)->separates())){ + all_known_surfaces_separating = false; + break; + } + delete s; + if (all_known_surfaces_separating) + CPPUNIT_FAIL(("No surfaces in " + triName + + " were computed to be nonseparating.").c_str()); + return tri; + } + + /* + Triangulation<3>* verifyAllFundamentalSeparating(Triangulation<3>* tri, + const std::string& triName) { + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool nonseparating_known = false; + for (unsigned i = 0; i < s->size(); ++i) + if (!(s->surface(i)->separates())){ + nonseparating_known = true; + break; + } + delete s; + if (nonseparating_known) + CPPUNIT_FAIL(("A surface in " + triName + + " is computed to be nonseparating.").c_str()); + return tri; + } + */ + + Triangulation<3>* verifyHasFundamentalNonSeparating(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_FUNDAMENTAL); + bool all_known_surfaces_separating = true; + for (unsigned i = 0; i < s->size(); ++i) + if (!(s->surface(i)->separates())){ + all_known_surfaces_separating = false; + break; + } + delete s; + if (all_known_surfaces_separating) + CPPUNIT_FAIL(("No surfaces in " + triName + + " were computed to be nonseparating.").c_str()); + return tri; + } + + /** + * All we can conclude from S not being a vertex surface + * is that there is some equation of the form + * + * nS + T = X + Y + * + * for some natural n > 0, trivial surface T, and normal surfaces X, Y. + * Even if there is a nonseparating surface, + * we cannot conclude that some vertex of its support is also nonseparating, + * because the above n might be even, in which case nS is separating. + * So if there is a nonseparating surface in a triangulation, + * nevertheless there might not be a nonseparating vertex-normal such surface. + * + * For example, L(34,13) has H_2(L(34,13),Z_2) = Z_2. + * So L(34,13) admits a nonseparating surface. + * But fLAMcbcbdeehxwqhr = L(34,13) has no nonseparating quad-vertex surface. + * + * However, there must be a nonseparating fundamental normal such surface. + * For instance, in fLAMcbcbdeehxwqhr there is a fundamental P2#P2#P2. + * + * Thus we should have backup tests for manifolds + * with no Z second homology but with Z_2 second homology. + */ + + void separates() { + Triangulation<3>* tri; + + // Manifolds without nonseparating surfaces + + tri = Example<3>::threeSphere(); + delete verifyAllVertexSeparating(tri, "Minimal 3-sphere"); + + tri = Example<3>::simplicialSphere(); + delete verifyAllVertexSeparating(tri, "Pentachoron boundary 3-sphere"); + + tri = Example<3>::ball(); + delete verifyAllVertexSeparating(tri, "One-tetrahedron ball"); + + int p = 3; + int q = 2; + while (p <= 34){ + if (p % 2 != 0){ + tri = Example<3>::lens(p,q); + ostringstream oss; + oss << "L(" << p << "," << q << ")" ; + delete verifyAllVertexSeparating(tri, oss.str()); + } + p = p + q; + q = p - q; + } + + tri = Example<3>::poincareHomologySphere(); + delete verifyAllVertexSeparating(tri, "Poincare homology sphere"); + + tri = Example<3>::weeks(); + delete verifyAllVertexSeparating(tri, "Weeks-Matveev-Fomenko manifold"); + + // Manifolds with nonseparating surfaces + + tri = Example<3>::s2xs1(); + delete verifyHasVertexNonSeparating(tri, "S2xS1"); + + tri = Example<3>::rp2xs1(); + delete verifyHasVertexNonSeparating(tri, "RP2xS1"); + + tri = Example<3>::rp3rp3(); + delete verifyHasVertexNonSeparating(tri, "RP3#RP3"); + + tri = Example<3>::smallClosedNonOrblHyperbolic(); + delete verifyHasVertexNonSeparating(tri, "Smallest known closed nonorientable hyperbolic"); + + p = 3; + q = 2; + while (p <= 34){ + tri = Example<3>::lst(p,q); + delete verifyHasVertexNonSeparating(tri, "Solid torus"); + if (p % 2 == 0){ + tri = Example<3>::lens(p,q); + ostringstream oss; + oss << "L(" << p << "," << q << ")" ; + delete verifyHasFundamentalNonSeparating(tri, oss.str()); + } + p = p + q; + q = p - q; + } + + tri = Example<3>::solidKleinBottle(); + delete verifyHasVertexNonSeparating(tri, "Solid Klein bottle"); + + tri = Example<3>::figureEight(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexNonSeparating(tri, "Figure eight"); + + tri = Example<3>::trefoil(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexNonSeparating(tri, "Trefoil"); + + tri = Example<3>::whiteheadLink(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexNonSeparating(tri, "Whitehead link"); + + tri = Example<3>::gieseking(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexNonSeparating(tri, "Gieseking manifold"); + + tri = Example<3>::cuspedGenusTwoTorus(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexNonSeparating(tri, "Genus two surface x I"); + } + + + Triangulation<3>* verifyAllVertexSpheresTrivial(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_sphere = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isEssentialSphere()){ + found_essential_sphere = true; + break; + } + delete s; + if (found_essential_sphere) + CPPUNIT_FAIL(("The irreducible manifold " + triName + + " was computed to have an essential sphere.").c_str()); + return tri; + } + + Triangulation<3>* verifyHasVertexEssentialSphere(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_sphere = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isEssentialSphere()){ + found_essential_sphere = true; + break; + } + delete s; + if (!found_essential_sphere) + CPPUNIT_FAIL(("No vertex essential spheres were found in " + + triName + ".").c_str()); + return tri; + } + + + void isEssentialSphere() { + Triangulation<3>* tri; + + // Closed irreducible manifolds + tri = Example<3>::threeSphere(); + delete verifyAllVertexSpheresTrivial(tri, "Minimal 3-sphere"); + tri = Example<3>::simplicialSphere(); + delete verifyAllVertexSpheresTrivial(tri, "Pentachoron boundary 3-sphere"); + tri = Example<3>::poincareHomologySphere(); + delete verifyAllVertexSpheresTrivial(tri, "Poincare homology sphere"); + tri = Example<3>::weeks(); + delete verifyAllVertexSpheresTrivial(tri, "Weeks-Matveev-Fomenko manifold"); + int p = 3; + int q = 2; + while (p < 1000){ + tri = Example<3>::lens(p,q); + ostringstream oss; + oss << "L(" << p << "," << q << ")"; + delete verifyAllVertexSpheresTrivial(tri, oss.str()); + p = p + q; + q = p - q; + } + + // Bounded irreducible manifolds + tri = Example<3>::ball(); + delete verifyAllVertexSpheresTrivial(tri, "One tetrahedron ball"); + tri = Example<3>::cuspedGenusTwoTorus(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial( + tri, "Trivial I-bundle over genus two surface"); + tri = Example<3>::figureEight(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial(tri, "Figure eight knot exterior"); + tri = Example<3>::trefoil(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial(tri, "Trefoil knot exterior"); + tri = Example<3>::whiteheadLink(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial(tri, "Whitehead link exterior"); + tri = Example<3>::gieseking(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial(tri, "Gieseking manifold"); + tri = Example<3>::solidKleinBottle(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexSpheresTrivial(tri, "Solid Klein bottle"); + p = 3; + q = 2; + while (p < 1000){ + tri = Example<3>::lst(p,q); + delete verifyAllVertexSpheresTrivial(tri, "Solid torus"); + p = p + q; + q = p - q; + } + + // The prime reducible manifold S^2 x S^1 + tri = Example<3>::s2xs1(); + delete verifyHasVertexEssentialSphere(tri, "S2xS1"); + + // RP3#RP3 has a reducing sphere, + // but in the given triangulation, there is no fundamental reducing sphere. + // Instead, there are two fundamental embedded one-sided RP2s + // that double to reducing spheres. + + tri = Example<3>::rp3rp3(); + delete verifyAllVertexSpheresTrivial(tri, "RP3#RP3"); + + p = 3; + q = 2; + while (p < 100){ + tri = Example<3>::lens(p,q); + tri->connectedSumWith(*tri); + tri->intelligentSimplify(); + ostringstream oss; + oss << "L(" << p << "," << q << ")"; + delete verifyHasVertexEssentialSphere(tri, oss.str() + " # " + oss.str()); + p = p + q; + q = p - q; + } + } + + Triangulation<3>* verifyAllVertexKleinBottlesInessential(Triangulation<3>* tri, + const std::string& triName){ + Triangulation<3>* working = new Triangulation<3>(tri->isoSig()); + NormalSurfaces* s = NormalSurfaces::enumerate( + working, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_klein_bottle = false; + unsigned i = 0; + for (; i < s->size(); ++i){ + const NormalSurface* surf = s->surface(i); + if (surf->isEssentialKleinBottle()){ + found_essential_klein_bottle = true; + break; + } + } + delete s; + if (found_essential_klein_bottle) + CPPUNIT_FAIL((triName + + " has an \"essential Klein bottle.\"").c_str()); + return tri; + } + + Triangulation<3>* verifyHasVertexEssentialKleinBottle(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_klein_bottle = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isEssentialKleinBottle()){ + found_essential_klein_bottle = true; + break; + } + delete s; + if (!found_essential_klein_bottle) + CPPUNIT_FAIL(("No vertex essential Klein bottle found in " + + triName + ".").c_str()); + return tri; + } + + + Triangulation<3>* smallSFS(long alpha0, long beta0, + long alpha1, long beta1, + long alpha2, long beta2){ + const SFSFibre* fiber0 = new SFSFibre(alpha0, beta0); + const SFSFibre* fiber1 = new SFSFibre(alpha1, beta1); + const SFSFibre* fiber2 = new SFSFibre(alpha2, beta2); + SFSpace* sfs = new SFSpace( + SFSpace::o1, 0, 0, 0, 0, 0); + sfs->insertFibre(*fiber0); + sfs->insertFibre(*fiber1); + sfs->insertFibre(*fiber2); + Triangulation<3>* sfs_tri = sfs->construct(); + delete sfs; + delete fiber0; + delete fiber1; + delete fiber2; + return sfs_tri; + } + + void isEssentialKleinBottle() { + Triangulation<3>* tri; + + // Manifolds with no essential Klein bottles + + tri = Example<3>::threeSphere(); + delete verifyAllVertexKleinBottlesInessential(tri, "Minimal 3-sphere"); + + tri = Example<3>::simplicialSphere(); + delete verifyAllVertexKleinBottlesInessential(tri, "Pentachoron boundary 3-sphere"); + + tri = Example<3>::ball(); + delete verifyAllVertexKleinBottlesInessential(tri, "One-tetrahedron ball"); + + int p = 3; + int q = 2; + ostringstream oss; + while (p <= 34){ + tri = Example<3>::lst(p,q); + delete verifyAllVertexKleinBottlesInessential(tri, "Solid torus"); + + tri = Example<3>::lens(p,q); + oss << "L(" << p << "," << q << ")" ; + delete verifyAllVertexKleinBottlesInessential(tri, oss.str()); + oss.clear(); + oss.str(""); + + tri = smallSFS(2,1,2,1,p,-q); + oss << "SFS [S2, (2,1,2,1," << p << "," << q << ")]"; + delete verifyAllVertexKleinBottlesInessential(tri, oss.str() ); + oss.clear(); + oss.str(""); + + p = p + q; + q = p - q; + } + + tri = Example<3>::poincareHomologySphere(); + delete verifyAllVertexKleinBottlesInessential(tri, "Poincare homology sphere"); + + tri = Example<3>::weeks(); + delete verifyAllVertexKleinBottlesInessential(tri, "Weeks-Matveev-Fomenko manifold"); + + tri = Example<3>::s2xs1(); + delete verifyAllVertexKleinBottlesInessential(tri, "S2xS1"); + + tri = Example<3>::rp2xs1(); + delete verifyAllVertexKleinBottlesInessential(tri, "RP2xS1"); + + tri = Example<3>::rp3rp3(); + delete verifyAllVertexKleinBottlesInessential(tri, "RP3#RP3"); + + tri = Example<3>::smallClosedNonOrblHyperbolic(); + delete verifyAllVertexKleinBottlesInessential(tri, "Smallest known closed nonorientable hyperbolic"); + + tri = Example<3>::solidKleinBottle(); + delete verifyAllVertexKleinBottlesInessential(tri, "Solid Klein bottle"); + + tri = Example<3>::figureEight(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexKleinBottlesInessential(tri, "Figure eight"); + + tri = Example<3>::trefoil(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexKleinBottlesInessential(tri, "Trefoil"); + + tri = Example<3>::whiteheadLink(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexKleinBottlesInessential(tri, "Whitehead link"); + + tri = Example<3>::gieseking(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexKleinBottlesInessential(tri, "Gieseking manifold"); + + tri = Example<3>::cuspedGenusTwoTorus(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexKleinBottlesInessential(tri, "Genus two surface x I"); + + // Manifolds with essential Klein bottles + + tri = new Triangulation<3>("gLALQbcceffflpkktua"); + delete verifyHasVertexEssentialKleinBottle(tri, "Doubled twisted I-bundle over K2"); + + tri = new Triangulation<3>("oLLLAAzLMQccefffggjlmnmmnnkkwawpwwjraakru"); + delete verifyHasVertexEssentialKleinBottle(tri, "Doubled Gieseking manifold"); + } + + Triangulation<3>* verifyAllVertexToriInessential(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_torus = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isEssentialTorus()){ + found_essential_torus = true; + break; + } + delete s; + if (found_essential_torus) + CPPUNIT_FAIL(("The atoroidal manifold " + triName + + " was computed to have an essential torus.").c_str()); + return tri; + } + + Triangulation<3>* verifyHasVertexEssentialTorus(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_essential_torus = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isEssentialTorus()){ + found_essential_torus = true; + break; + } + delete s; + if (!found_essential_torus) + CPPUNIT_FAIL(("No essential tori were found in the toroidal manifold " + + triName + ".").c_str()); + return tri; + } + + void isEssentialTorus() { + Triangulation<3>* tri; + + // Simple closed atoroidal manifolds + tri = Example<3>::threeSphere(); + delete verifyAllVertexToriInessential(tri, "Minimal 3-sphere"); + tri = Example<3>::simplicialSphere(); + delete verifyAllVertexToriInessential(tri, "Pentachoron boundary 3-sphere"); + tri = Example<3>::poincareHomologySphere(); + delete verifyAllVertexToriInessential(tri, "Poincare homology sphere"); + tri = Example<3>::weeks(); + delete verifyAllVertexToriInessential(tri, "Weeks-Matveev-Fomenko manifold"); + int p = 3; + int q = 2; + while (p < 1000){ + tri = Example<3>::lens(p,q); + delete verifyAllVertexToriInessential(tri, "Fibonacci lens space"); + tri = smallSFS(2,1,2,1,p,-q); + ostringstream oss; + oss << "SFS [S2: (2,1)(2,1)(" << p << "," << -q << ")]"; + delete verifyAllVertexToriInessential(tri, oss.str()); + p = p + q; + q = p - q; + } + + // Small Seifert fibered spaces... +#define VERIFY_SMALL_SFS_ATOROIDAL(a0,b0,a1,b1,a2,b2) \ + tri = smallSFS(a0,b0,a1,b1,a2,b2); \ + tri->intelligentSimplify(); \ + delete verifyAllVertexToriInessential(tri, "SFS [S2: (a0,b0) (a1,b1) (a2,b2)]") + + // ... of positive orbifold Euler characteristic ... + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,3,-2); + + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,3,-1); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,4,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,5,-4); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,3,-1); + + /* + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,3,1); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,3,2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,4,-1); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,5,-2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,3,2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,4,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,4,-1); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,5,-2); + */ + + // ...and of negative orbifold Euler characteristic.... + /* + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,7,-6); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,7,-5); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,7,-5); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,7,-4); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,7,-2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,8,-5); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,1,8,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,7,-5); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,7,-4); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,7,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,7,-2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,8,-5); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,8,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,3,2,8,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,4,1,5,-4); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,4,1,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,4,1,5,-2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,4,3,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,4,3,5,-2); + */ + VERIFY_SMALL_SFS_ATOROIDAL(2,1,5,2,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,5,2,5,-2); + VERIFY_SMALL_SFS_ATOROIDAL(2,1,5,3,5,-2); + + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,1,4,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,1,4,-1); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,1,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,1,5,-2); + /* + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,2,4,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,2,4,-1); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,2,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,1,3,2,5,-2); + VERIFY_SMALL_SFS_ATOROIDAL(3,2,3,2,4,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,2,3,2,4,-1); + VERIFY_SMALL_SFS_ATOROIDAL(3,2,3,2,5,-3); + VERIFY_SMALL_SFS_ATOROIDAL(3,2,3,2,5,-2); + */ + + // Bounded atoroidal manifolds + tri = Example<3>::ball(); + delete verifyAllVertexToriInessential(tri, "One tetrahedron ball"); + tri = Example<3>::cuspedGenusTwoTorus(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential( + tri, "Trivial I-bundle over genus two surface"); + tri = Example<3>::figureEight(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential(tri, "Figure eight knot exterior"); + tri = Example<3>::trefoil(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential(tri, "Trefoil knot exterior"); + tri = Example<3>::whiteheadLink(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential(tri, "Whitehead link exterior"); + tri = Example<3>::gieseking(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential(tri, "Gieseking manifold"); + tri = Example<3>::solidKleinBottle(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyAllVertexToriInessential(tri, "Solid Klein bottle"); + p = 3; + q = 2; + while (p < 1000){ + tri = Example<3>::lst(p,q); + delete verifyAllVertexToriInessential(tri, "Solid torus"); + p = p + q; + q = p - q; + } + + // Toroidal manifolds + tri = new Triangulation<3>("uLLvPAPAzzvQPQccdeghiihjjlmqspstrstthshgbvrndhakecbcqvndm"); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexEssentialTorus(tri, "Doubled figure eight knot exterior"); + + tri = new Triangulation<3>("iLALzQcbccefhgghlpkkucjjs"); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexEssentialTorus(tri, "Doubled trefoil knot exterior"); + } + + + Triangulation<3>* verifyNoVertexSolidTorusAnnulus(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_solid_torus_annulus = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isSolidTorusAnnulus()){ + found_solid_torus_annulus = true; + break; + } + delete s; + if (found_solid_torus_annulus) + CPPUNIT_FAIL((triName + + " has a \"solid torus annulus.\"").c_str()); + return tri; + } + + Triangulation<3>* verifyHasVertexSolidTorusAnnulus(Triangulation<3>* tri, + const std::string& triName){ + NormalSurfaces* s = NormalSurfaces::enumerate( + tri, NS_QUAD, NS_VERTEX_EMBEDDED); + bool found_solid_torus_annulus = false; + for (unsigned i = 0; i < s->size(); ++i) + if (s->surface(i)->isSolidTorusAnnulus()){ + found_solid_torus_annulus = true; + break; + } + delete s; + if (!found_solid_torus_annulus) + CPPUNIT_FAIL(("No solid torus annulus found in " + + triName + ".").c_str()); + return tri; + } + + void isSolidTorusAnnulus() { + Triangulation<3>* tri; + + // Closed manifolds + tri = Example<3>::threeSphere(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Minimal 3-sphere"); + tri = Example<3>::simplicialSphere(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Pentachoron boundary 3-sphere"); + tri = Example<3>::poincareHomologySphere(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Poincare homology sphere"); + tri = Example<3>::weeks(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Weeks-Matveev-Fomenko manifold"); + int p = 3; + int q = 2; + while (p < 1000){ + tri = Example<3>::lens(p,q); + delete verifyNoVertexSolidTorusAnnulus(tri, "Fibonacci lens space"); + tri = smallSFS(2,1,2,1,p,-q); + ostringstream oss; + oss << "SFS [S2: (2,1)(2,1)(" << p << "," << -q << ")]"; + delete verifyNoVertexSolidTorusAnnulus(tri, oss.str()); + p = p + q; + q = p - q; + } + + // Small Seifert fibered spaces... +#define VERIFY_SMALL_SFS_ANANNULAR(a0,b0,a1,b1,a2,b2) \ + tri = smallSFS(a0,b0,a1,b1,a2,b2); \ + tri->intelligentSimplify(); \ + delete verifyNoVertexSolidTorusAnnulus(tri, "SFS [S2: (a0,b0) (a1,b1) (a2,b2)]") + + // ... of positive orbifold Euler characteristic ... + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,3,-2); + + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,3,-1); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,4,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,5,-4); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,3,-1); + + /* + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,3,1); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,3,2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,4,-1); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,5,-2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,3,2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,4,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,4,-1); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,5,-2); + */ + + // ...and of negative orbifold Euler characteristic.... + /* + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,7,-6); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,7,-5); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,7,-5); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,7,-4); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,7,-2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,8,-5); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,1,8,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,7,-5); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,7,-4); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,7,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,7,-2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,8,-5); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,8,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,3,2,8,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,4,1,5,-4); + VERIFY_SMALL_SFS_ANANNULAR(2,1,4,1,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,4,1,5,-2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,4,3,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,4,3,5,-2); + */ + VERIFY_SMALL_SFS_ANANNULAR(2,1,5,2,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(2,1,5,2,5,-2); + VERIFY_SMALL_SFS_ANANNULAR(2,1,5,3,5,-2); + + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,1,4,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,1,4,-1); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,1,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,1,5,-2); + /* + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,2,4,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,2,4,-1); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,2,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,1,3,2,5,-2); + VERIFY_SMALL_SFS_ANANNULAR(3,2,3,2,4,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,2,3,2,4,-1); + VERIFY_SMALL_SFS_ANANNULAR(3,2,3,2,5,-3); + VERIFY_SMALL_SFS_ANANNULAR(3,2,3,2,5,-2); + */ + + // Bounded manifolds + + tri = Example<3>::ball(); + delete verifyNoVertexSolidTorusAnnulus(tri, "One tetrahedron ball"); + + // The following is not anannular, + // but it does not decompose along an annulus into solid tori. + tri = Example<3>::cuspedGenusTwoTorus(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyNoVertexSolidTorusAnnulus( + tri, "Trivial I-bundle over genus two surface"); + + tri = Example<3>::figureEight(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Figure eight knot exterior"); + + tri = Example<3>::whiteheadLink(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Whitehead link exterior"); + tri = Example<3>::gieseking(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyNoVertexSolidTorusAnnulus(tri, "Gieseking manifold"); + + // Manifolds with solid torus annuli + + p = 3; + q = 2; + while (p < 1000){ + tri = Example<3>::lst(p,q); + ostringstream oss; + oss << "Layered solid torus (" << p << "," << q << ")"; + delete verifyHasVertexSolidTorusAnnulus(tri, oss.str()); + p = p + q; + q = p - q; + } + + tri = Example<3>::solidKleinBottle(); + tri->idealToFinite(); + tri->intelligentSimplify(); + delete verifyHasVertexSolidTorusAnnulus(tri, "Solid Klein bottle"); + + // Although the trefoil knot does admit a solid torus annulus, + // this annulus double covers a Mobius band. + // So it is possible for there not to be a vertex such annulus. + // Even so, the following triangulation does have such an annulus. + /* + tri = Example<3>::trefoil(); + tri->idealToFinite(); + tri->intelligentSimplify(); + */ + // For consistency we remove the call to intelligentSimplify. + tri = new Triangulation<3>("fLHPccdeeeqcieh"); + delete verifyHasVertexSolidTorusAnnulus(tri, "Trefoil knot exterior"); + + tri = new Triangulation<3>("gLKjMcacdeffjkxnqs"); + delete verifyHasVertexSolidTorusAnnulus(tri, "SFS D: (3,1) (5,-2)"); + + tri = new Triangulation<3>("mHLyMzMzMcbcdefghijklldwuxhqxhqxhw"); + delete verifyHasVertexSolidTorusAnnulus(tri, "SFS D: (2,1) (144,-89)"); + } +}; + +void addFaultFinding(CppUnit::TextUi::TestRunner& runner) { + runner.addTest(FaultTest::suite()); +} diff --git a/testsuite/surfaces/testsurfaces.h b/testsuite/surfaces/testsurfaces.h index 01d8d121a3..4c4ed25124 100644 --- a/testsuite/surfaces/testsurfaces.h +++ b/testsuite/surfaces/testsurfaces.h @@ -44,4 +44,5 @@ void addNormalSurfaces(CppUnit::TextUi::TestRunner& runner); void addIncompressible(CppUnit::TextUi::TestRunner& runner); +void addFaultFinding(CppUnit::TextUi::TestRunner& runner); diff --git a/testsuite/testsuite.cpp b/testsuite/testsuite.cpp index 735e38b362..92d97ab277 100644 --- a/testsuite/testsuite.cpp +++ b/testsuite/testsuite.cpp @@ -159,6 +159,7 @@ bool populateTests(CppUnit::TextTestRunner& runner, int argc, char* argv[]) { // Surfaces: sets.insert(std::make_pair("normalsurfaces", &addNormalSurfaces)); sets.insert(std::make_pair("incompressible", &addIncompressible)); + sets.insert(std::make_pair("faultfinding", &addFaultFinding)); // Angle structures: sets.insert(std::make_pair("anglestructures", &addAngleStructures));