snappy 3.2__cp313-cp313-macosx_11_0_arm64.whl
Sign up to get free protection for your applications and to get access to all the features.
- snappy/CyOpenGL.cpython-313-darwin.so +0 -0
- snappy/SnapPy.cpython-313-darwin.so +0 -0
- snappy/SnapPy.ico +0 -0
- snappy/SnapPy.png +0 -0
- snappy/SnapPyHP.cpython-313-darwin.so +0 -0
- snappy/__init__.py +760 -0
- snappy/app.py +605 -0
- snappy/app_menus.py +372 -0
- snappy/browser.py +998 -0
- snappy/cache.py +25 -0
- snappy/canonical.py +249 -0
- snappy/cusps/__init__.py +38 -0
- snappy/cusps/cusp_area_matrix.py +101 -0
- snappy/cusps/cusp_areas_from_matrix.py +173 -0
- snappy/cusps/maximal_cusp_area_matrix.py +136 -0
- snappy/cusps/test.py +21 -0
- snappy/cusps/trig_cusp_area_matrix.py +63 -0
- snappy/database.py +454 -0
- snappy/db_utilities.py +79 -0
- snappy/decorated_isosig.py +710 -0
- snappy/dev/__init__.py +0 -0
- snappy/dev/extended_ptolemy/__init__.py +8 -0
- snappy/dev/extended_ptolemy/closed.py +106 -0
- snappy/dev/extended_ptolemy/complexVolumesClosed.py +149 -0
- snappy/dev/extended_ptolemy/direct.py +42 -0
- snappy/dev/extended_ptolemy/extended.py +406 -0
- snappy/dev/extended_ptolemy/giac_helper.py +43 -0
- snappy/dev/extended_ptolemy/giac_rur.py +129 -0
- snappy/dev/extended_ptolemy/gluing.py +46 -0
- snappy/dev/extended_ptolemy/phc_wrapper.py +220 -0
- snappy/dev/extended_ptolemy/printMatrices.py +70 -0
- snappy/dev/vericlosed/__init__.py +1 -0
- snappy/dev/vericlosed/computeApproxHyperbolicStructureNew.py +159 -0
- snappy/dev/vericlosed/computeApproxHyperbolicStructureOrb.py +90 -0
- snappy/dev/vericlosed/computeVerifiedHyperbolicStructure.py +111 -0
- snappy/dev/vericlosed/gimbalLoopFinder.py +130 -0
- snappy/dev/vericlosed/hyperbolicStructure.py +313 -0
- snappy/dev/vericlosed/krawczykCertifiedEdgeLengthsEngine.py +165 -0
- snappy/dev/vericlosed/oneVertexTruncatedComplex.py +122 -0
- snappy/dev/vericlosed/orb/__init__.py +1 -0
- snappy/dev/vericlosed/orb/orb_solution_for_snappea_finite_triangulation_mac +0 -0
- snappy/dev/vericlosed/parseVertexGramMatrixFile.py +47 -0
- snappy/dev/vericlosed/polishApproxHyperbolicStructure.py +61 -0
- snappy/dev/vericlosed/test.py +54 -0
- snappy/dev/vericlosed/truncatedComplex.py +176 -0
- snappy/dev/vericlosed/verificationError.py +58 -0
- snappy/dev/vericlosed/verifyHyperbolicStructureEngine.py +177 -0
- snappy/doc/_images/SnapPy-196.png +0 -0
- snappy/doc/_images/geodesics.jpg +0 -0
- snappy/doc/_images/m004_paper_plane_on_systole.jpg +0 -0
- snappy/doc/_images/m125_paper_plane.jpg +0 -0
- snappy/doc/_images/mac.png +0 -0
- snappy/doc/_images/o9_00000_systole_paper_plane.jpg +0 -0
- snappy/doc/_images/o9_00000_systole_paper_plane_closer.jpg +0 -0
- snappy/doc/_images/plink-action.png +0 -0
- snappy/doc/_images/ubuntu.png +0 -0
- snappy/doc/_images/win7.png +0 -0
- snappy/doc/_sources/additional_classes.rst.txt +40 -0
- snappy/doc/_sources/bugs.rst.txt +14 -0
- snappy/doc/_sources/censuses.rst.txt +51 -0
- snappy/doc/_sources/credits.rst.txt +75 -0
- snappy/doc/_sources/development.rst.txt +259 -0
- snappy/doc/_sources/index.rst.txt +182 -0
- snappy/doc/_sources/installing.rst.txt +247 -0
- snappy/doc/_sources/manifold.rst.txt +6 -0
- snappy/doc/_sources/manifoldhp.rst.txt +46 -0
- snappy/doc/_sources/news.rst.txt +355 -0
- snappy/doc/_sources/other.rst.txt +25 -0
- snappy/doc/_sources/platonic_census.rst.txt +20 -0
- snappy/doc/_sources/plink.rst.txt +102 -0
- snappy/doc/_sources/ptolemy.rst.txt +66 -0
- snappy/doc/_sources/ptolemy_classes.rst.txt +42 -0
- snappy/doc/_sources/ptolemy_examples1.rst.txt +298 -0
- snappy/doc/_sources/ptolemy_examples2.rst.txt +363 -0
- snappy/doc/_sources/ptolemy_examples3.rst.txt +301 -0
- snappy/doc/_sources/ptolemy_examples4.rst.txt +61 -0
- snappy/doc/_sources/ptolemy_prelim.rst.txt +105 -0
- snappy/doc/_sources/screenshots.rst.txt +21 -0
- snappy/doc/_sources/snap.rst.txt +87 -0
- snappy/doc/_sources/snappy.rst.txt +28 -0
- snappy/doc/_sources/spherogram.rst.txt +103 -0
- snappy/doc/_sources/todo.rst.txt +47 -0
- snappy/doc/_sources/triangulation.rst.txt +11 -0
- snappy/doc/_sources/tutorial.rst.txt +49 -0
- snappy/doc/_sources/verify.rst.txt +210 -0
- snappy/doc/_sources/verify_internals.rst.txt +79 -0
- snappy/doc/_static/SnapPy-horizontal-128.png +0 -0
- snappy/doc/_static/SnapPy.ico +0 -0
- snappy/doc/_static/_sphinx_javascript_frameworks_compat.js +123 -0
- snappy/doc/_static/basic.css +925 -0
- snappy/doc/_static/css/badge_only.css +1 -0
- snappy/doc/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
- snappy/doc/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
- snappy/doc/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
- snappy/doc/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
- snappy/doc/_static/css/fonts/fontawesome-webfont.eot +0 -0
- snappy/doc/_static/css/fonts/fontawesome-webfont.svg +2671 -0
- snappy/doc/_static/css/fonts/fontawesome-webfont.ttf +0 -0
- snappy/doc/_static/css/fonts/fontawesome-webfont.woff +0 -0
- snappy/doc/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
- snappy/doc/_static/css/fonts/lato-bold-italic.woff +0 -0
- snappy/doc/_static/css/fonts/lato-bold-italic.woff2 +0 -0
- snappy/doc/_static/css/fonts/lato-bold.woff +0 -0
- snappy/doc/_static/css/fonts/lato-bold.woff2 +0 -0
- snappy/doc/_static/css/fonts/lato-normal-italic.woff +0 -0
- snappy/doc/_static/css/fonts/lato-normal-italic.woff2 +0 -0
- snappy/doc/_static/css/fonts/lato-normal.woff +0 -0
- snappy/doc/_static/css/fonts/lato-normal.woff2 +0 -0
- snappy/doc/_static/css/theme.css +4 -0
- snappy/doc/_static/doctools.js +156 -0
- snappy/doc/_static/documentation_options.js +13 -0
- snappy/doc/_static/file.png +0 -0
- snappy/doc/_static/fonts/Lato/lato-bold.eot +0 -0
- snappy/doc/_static/fonts/Lato/lato-bold.ttf +0 -0
- snappy/doc/_static/fonts/Lato/lato-bold.woff +0 -0
- snappy/doc/_static/fonts/Lato/lato-bold.woff2 +0 -0
- snappy/doc/_static/fonts/Lato/lato-bolditalic.eot +0 -0
- snappy/doc/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
- snappy/doc/_static/fonts/Lato/lato-bolditalic.woff +0 -0
- snappy/doc/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
- snappy/doc/_static/fonts/Lato/lato-italic.eot +0 -0
- snappy/doc/_static/fonts/Lato/lato-italic.ttf +0 -0
- snappy/doc/_static/fonts/Lato/lato-italic.woff +0 -0
- snappy/doc/_static/fonts/Lato/lato-italic.woff2 +0 -0
- snappy/doc/_static/fonts/Lato/lato-regular.eot +0 -0
- snappy/doc/_static/fonts/Lato/lato-regular.ttf +0 -0
- snappy/doc/_static/fonts/Lato/lato-regular.woff +0 -0
- snappy/doc/_static/fonts/Lato/lato-regular.woff2 +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
- snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
- snappy/doc/_static/jquery.js +2 -0
- snappy/doc/_static/js/badge_only.js +1 -0
- snappy/doc/_static/js/theme.js +1 -0
- snappy/doc/_static/js/versions.js +228 -0
- snappy/doc/_static/language_data.js +199 -0
- snappy/doc/_static/minus.png +0 -0
- snappy/doc/_static/plus.png +0 -0
- snappy/doc/_static/pygments.css +75 -0
- snappy/doc/_static/searchtools.js +620 -0
- snappy/doc/_static/snappy_furo.css +33 -0
- snappy/doc/_static/snappy_sphinx_rtd_theme.css +42 -0
- snappy/doc/_static/sphinx_highlight.js +154 -0
- snappy/doc/additional_classes.html +1500 -0
- snappy/doc/bugs.html +132 -0
- snappy/doc/censuses.html +427 -0
- snappy/doc/credits.html +181 -0
- snappy/doc/development.html +384 -0
- snappy/doc/genindex.html +1331 -0
- snappy/doc/index.html +262 -0
- snappy/doc/installing.html +346 -0
- snappy/doc/manifold.html +3452 -0
- snappy/doc/manifoldhp.html +180 -0
- snappy/doc/news.html +388 -0
- snappy/doc/objects.inv +0 -0
- snappy/doc/other.html +161 -0
- snappy/doc/platonic_census.html +375 -0
- snappy/doc/plink.html +210 -0
- snappy/doc/ptolemy.html +254 -0
- snappy/doc/ptolemy_classes.html +1144 -0
- snappy/doc/ptolemy_examples1.html +409 -0
- snappy/doc/ptolemy_examples2.html +471 -0
- snappy/doc/ptolemy_examples3.html +414 -0
- snappy/doc/ptolemy_examples4.html +195 -0
- snappy/doc/ptolemy_prelim.html +248 -0
- snappy/doc/py-modindex.html +165 -0
- snappy/doc/screenshots.html +141 -0
- snappy/doc/search.html +135 -0
- snappy/doc/searchindex.js +1 -0
- snappy/doc/snap.html +202 -0
- snappy/doc/snappy.html +181 -0
- snappy/doc/spherogram.html +1211 -0
- snappy/doc/todo.html +166 -0
- snappy/doc/triangulation.html +1584 -0
- snappy/doc/tutorial.html +159 -0
- snappy/doc/verify.html +330 -0
- snappy/doc/verify_internals.html +1235 -0
- snappy/drilling/__init__.py +456 -0
- snappy/drilling/barycentric.py +103 -0
- snappy/drilling/constants.py +5 -0
- snappy/drilling/crush.py +270 -0
- snappy/drilling/cusps.py +125 -0
- snappy/drilling/debug.py +242 -0
- snappy/drilling/epsilons.py +6 -0
- snappy/drilling/exceptions.py +55 -0
- snappy/drilling/moves.py +620 -0
- snappy/drilling/peripheral_curves.py +210 -0
- snappy/drilling/perturb.py +188 -0
- snappy/drilling/shorten.py +36 -0
- snappy/drilling/subdivide.py +274 -0
- snappy/drilling/test.py +23 -0
- snappy/drilling/test_cases.py +126 -0
- snappy/drilling/tracing.py +351 -0
- snappy/exceptions.py +26 -0
- snappy/export_stl.py +120 -0
- snappy/exterior_to_link/__init__.py +2 -0
- snappy/exterior_to_link/barycentric_geometry.py +463 -0
- snappy/exterior_to_link/exceptions.py +6 -0
- snappy/exterior_to_link/geodesic_map.json +14408 -0
- snappy/exterior_to_link/hyp_utils.py +112 -0
- snappy/exterior_to_link/link_projection.py +323 -0
- snappy/exterior_to_link/main.py +197 -0
- snappy/exterior_to_link/mcomplex_with_expansion.py +261 -0
- snappy/exterior_to_link/mcomplex_with_link.py +687 -0
- snappy/exterior_to_link/mcomplex_with_memory.py +162 -0
- snappy/exterior_to_link/pl_utils.py +491 -0
- snappy/exterior_to_link/put_in_S3.py +156 -0
- snappy/exterior_to_link/rational_linear_algebra.py +123 -0
- snappy/exterior_to_link/rational_linear_algebra_wrapped.py +135 -0
- snappy/exterior_to_link/simplify_to_base_tri.py +114 -0
- snappy/exterior_to_link/stored_moves.py +475 -0
- snappy/exterior_to_link/test.py +31 -0
- snappy/filedialog.py +28 -0
- snappy/geometric_structure/__init__.py +212 -0
- snappy/geometric_structure/cusp_neighborhood/__init__.py +3 -0
- snappy/geometric_structure/cusp_neighborhood/complex_cusp_cross_section.py +697 -0
- snappy/geometric_structure/cusp_neighborhood/cusp_cross_section_base.py +484 -0
- snappy/geometric_structure/cusp_neighborhood/exceptions.py +42 -0
- snappy/geometric_structure/cusp_neighborhood/real_cusp_cross_section.py +298 -0
- snappy/geometric_structure/cusp_neighborhood/tiles_for_cusp_neighborhood.py +159 -0
- snappy/geometric_structure/cusp_neighborhood/vertices.py +32 -0
- snappy/geometric_structure/geodesic/__init__.py +0 -0
- snappy/geometric_structure/geodesic/add_core_curves.py +152 -0
- snappy/geometric_structure/geodesic/avoid_core_curves.py +369 -0
- snappy/geometric_structure/geodesic/canonical_keys.py +52 -0
- snappy/geometric_structure/geodesic/check_away_from_core_curve.py +60 -0
- snappy/geometric_structure/geodesic/constants.py +6 -0
- snappy/geometric_structure/geodesic/exceptions.py +22 -0
- snappy/geometric_structure/geodesic/fixed_points.py +93 -0
- snappy/geometric_structure/geodesic/geodesic_start_point_info.py +435 -0
- snappy/geometric_structure/geodesic/graph_trace_helper.py +67 -0
- snappy/geometric_structure/geodesic/line.py +30 -0
- snappy/geometric_structure/geodesic/multiplicity.py +127 -0
- snappy/geometric_structure/geodesic/tiles_for_geodesic.py +101 -0
- snappy/geometric_structure/test.py +22 -0
- snappy/gui.py +121 -0
- snappy/horoviewer.py +443 -0
- snappy/hyperboloid/__init__.py +212 -0
- snappy/hyperboloid/distances.py +245 -0
- snappy/hyperboloid/horoball.py +19 -0
- snappy/hyperboloid/line.py +35 -0
- snappy/hyperboloid/point.py +9 -0
- snappy/hyperboloid/triangle.py +29 -0
- snappy/info_icon.gif +0 -0
- snappy/infowindow.py +65 -0
- snappy/isometry_signature.py +382 -0
- snappy/len_spec/__init__.py +596 -0
- snappy/len_spec/geodesic_info.py +110 -0
- snappy/len_spec/geodesic_key_info_dict.py +117 -0
- snappy/len_spec/geodesic_piece.py +143 -0
- snappy/len_spec/geometric_structure.py +182 -0
- snappy/len_spec/geometry.py +80 -0
- snappy/len_spec/length_spectrum_geodesic_info.py +170 -0
- snappy/len_spec/spine.py +206 -0
- snappy/len_spec/test.py +24 -0
- snappy/len_spec/test_cases.py +69 -0
- snappy/len_spec/tile.py +275 -0
- snappy/len_spec/word.py +86 -0
- snappy/manifolds/HTWKnots/alternating.gz +0 -0
- snappy/manifolds/HTWKnots/nonalternating.gz +0 -0
- snappy/manifolds/__init__.py +3 -0
- snappy/math_basics.py +176 -0
- snappy/matrix.py +525 -0
- snappy/number.py +657 -0
- snappy/numeric_output_checker.py +345 -0
- snappy/pari.py +41 -0
- snappy/phone_home.py +57 -0
- snappy/polyviewer.py +259 -0
- snappy/ptolemy/__init__.py +17 -0
- snappy/ptolemy/component.py +103 -0
- snappy/ptolemy/coordinates.py +2290 -0
- snappy/ptolemy/fieldExtensions.py +153 -0
- snappy/ptolemy/findLoops.py +473 -0
- snappy/ptolemy/geometricRep.py +59 -0
- snappy/ptolemy/homology.py +165 -0
- snappy/ptolemy/magma/default.magma_template +229 -0
- snappy/ptolemy/magma/radicalsOfPrimaryDecomposition.magma_template +79 -0
- snappy/ptolemy/manifoldMethods.py +395 -0
- snappy/ptolemy/matrix.py +350 -0
- snappy/ptolemy/numericalSolutionsToGroebnerBasis.py +113 -0
- snappy/ptolemy/polynomial.py +857 -0
- snappy/ptolemy/processComponents.py +173 -0
- snappy/ptolemy/processFileBase.py +247 -0
- snappy/ptolemy/processFileDispatch.py +46 -0
- snappy/ptolemy/processMagmaFile.py +392 -0
- snappy/ptolemy/processRurFile.py +150 -0
- snappy/ptolemy/ptolemyGeneralizedObstructionClass.py +102 -0
- snappy/ptolemy/ptolemyObstructionClass.py +64 -0
- snappy/ptolemy/ptolemyVariety.py +1029 -0
- snappy/ptolemy/ptolemyVarietyPrimeIdealGroebnerBasis.py +140 -0
- snappy/ptolemy/reginaWrapper.py +698 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c2.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c3.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c4.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c5.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c6.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c7.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m003__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m003__sl3_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m015__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m015__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m015__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/regina_testing_files_generalized/m015__sl3_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/rur.py +545 -0
- snappy/ptolemy/solutionsToPrimeIdealGroebnerBasis.py +277 -0
- snappy/ptolemy/test.py +1126 -0
- snappy/ptolemy/testing_files/3_1__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/3_1__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/4_1__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/4_1__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/4_1__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/4_1__sl4_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/4_1__sl4_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/5_2__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/5_2__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c2.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c3.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c4.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c5.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c6.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c7.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/data/pgl2/OrientableCuspedCensus/03_tetrahedra/m019__sl2_c0.magma_out +95 -0
- snappy/ptolemy/testing_files/data/pgl2/OrientableCuspedCensus/03_tetrahedra/m019__sl2_c1.magma_out +95 -0
- snappy/ptolemy/testing_files/m015__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c2.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c3.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c4.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c5.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c6.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/m135__sl2_c7.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/s000__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/s000__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/t00000__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/t00000__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/v0000__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/v0000__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/v0000__sl2_c2.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files/v0000__sl2_c3.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m003__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m003__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m003__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m003__sl3_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m004__sl2_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m004__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m015__sl2_c1.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_generalized/m015__sl3_c0.magma_out.bz2 +0 -0
- snappy/ptolemy/testing_files_rur/m052__sl3_c0.rur.bz2 +0 -0
- snappy/ptolemy/utilities.py +236 -0
- snappy/raytracing/__init__.py +64 -0
- snappy/raytracing/additional_horospheres.py +64 -0
- snappy/raytracing/additional_len_spec_choices.py +63 -0
- snappy/raytracing/cohomology_fractal.py +197 -0
- snappy/raytracing/eyeball.py +123 -0
- snappy/raytracing/finite_raytracing_data.py +237 -0
- snappy/raytracing/finite_viewer.py +590 -0
- snappy/raytracing/geodesic_tube_info.py +174 -0
- snappy/raytracing/geodesics.py +246 -0
- snappy/raytracing/geodesics_window.py +258 -0
- snappy/raytracing/gui_utilities.py +293 -0
- snappy/raytracing/hyperboloid_navigation.py +556 -0
- snappy/raytracing/hyperboloid_utilities.py +234 -0
- snappy/raytracing/ideal_raytracing_data.py +592 -0
- snappy/raytracing/inside_viewer.py +974 -0
- snappy/raytracing/pack.py +22 -0
- snappy/raytracing/raytracing_data.py +126 -0
- snappy/raytracing/raytracing_view.py +454 -0
- snappy/raytracing/shaders/Eye.png +0 -0
- snappy/raytracing/shaders/NonGeometric.png +0 -0
- snappy/raytracing/shaders/__init__.py +101 -0
- snappy/raytracing/shaders/fragment.glsl +1744 -0
- snappy/raytracing/test.py +29 -0
- snappy/raytracing/tooltip.py +146 -0
- snappy/raytracing/upper_halfspace_utilities.py +98 -0
- snappy/raytracing/view_scale_controller.py +98 -0
- snappy/raytracing/zoom_slider/__init__.py +263 -0
- snappy/raytracing/zoom_slider/inward.png +0 -0
- snappy/raytracing/zoom_slider/inward18.png +0 -0
- snappy/raytracing/zoom_slider/outward.png +0 -0
- snappy/raytracing/zoom_slider/outward18.png +0 -0
- snappy/raytracing/zoom_slider/test.py +20 -0
- snappy/sage_helper.py +117 -0
- snappy/settings.py +409 -0
- snappy/shell.py +53 -0
- snappy/snap/__init__.py +114 -0
- snappy/snap/character_varieties.py +375 -0
- snappy/snap/find_field.py +372 -0
- snappy/snap/fundamental_polyhedron.py +569 -0
- snappy/snap/generators.py +39 -0
- snappy/snap/interval_reps.py +81 -0
- snappy/snap/kernel_structures.py +128 -0
- snappy/snap/mcomplex_base.py +18 -0
- snappy/snap/nsagetools.py +702 -0
- snappy/snap/peripheral/__init__.py +1 -0
- snappy/snap/peripheral/dual_cellulation.py +219 -0
- snappy/snap/peripheral/link.py +127 -0
- snappy/snap/peripheral/peripheral.py +159 -0
- snappy/snap/peripheral/surface.py +522 -0
- snappy/snap/peripheral/test.py +35 -0
- snappy/snap/polished_reps.py +335 -0
- snappy/snap/shapes.py +152 -0
- snappy/snap/slice_obs_HKL.py +668 -0
- snappy/snap/t3mlite/__init__.py +2 -0
- snappy/snap/t3mlite/arrow.py +243 -0
- snappy/snap/t3mlite/corner.py +22 -0
- snappy/snap/t3mlite/edge.py +172 -0
- snappy/snap/t3mlite/face.py +37 -0
- snappy/snap/t3mlite/files.py +211 -0
- snappy/snap/t3mlite/homology.py +53 -0
- snappy/snap/t3mlite/linalg.py +419 -0
- snappy/snap/t3mlite/mcomplex.py +1499 -0
- snappy/snap/t3mlite/perm4.py +320 -0
- snappy/snap/t3mlite/setup.py +12 -0
- snappy/snap/t3mlite/simplex.py +199 -0
- snappy/snap/t3mlite/spun.py +297 -0
- snappy/snap/t3mlite/surface.py +519 -0
- snappy/snap/t3mlite/test.py +20 -0
- snappy/snap/t3mlite/test_vs_regina.py +86 -0
- snappy/snap/t3mlite/tetrahedron.py +109 -0
- snappy/snap/t3mlite/vertex.py +42 -0
- snappy/snap/test.py +134 -0
- snappy/snap/utilities.py +288 -0
- snappy/test.py +209 -0
- snappy/test_cases.py +263 -0
- snappy/testing.py +131 -0
- snappy/tiling/__init__.py +2 -0
- snappy/tiling/canonical_key_dict.py +59 -0
- snappy/tiling/dict_based_set.py +79 -0
- snappy/tiling/floor.py +49 -0
- snappy/tiling/hyperboloid_dict.py +54 -0
- snappy/tiling/iter_utils.py +78 -0
- snappy/tiling/lifted_tetrahedron.py +22 -0
- snappy/tiling/lifted_tetrahedron_set.py +54 -0
- snappy/tiling/real_hash_dict.py +164 -0
- snappy/tiling/test.py +23 -0
- snappy/tiling/tile.py +215 -0
- snappy/tiling/triangle.py +33 -0
- snappy/tkterminal.py +920 -0
- snappy/twister/__init__.py +20 -0
- snappy/twister/main.py +646 -0
- snappy/twister/surfaces/S_0_1 +3 -0
- snappy/twister/surfaces/S_0_2 +3 -0
- snappy/twister/surfaces/S_0_4 +7 -0
- snappy/twister/surfaces/S_0_4_Lantern +8 -0
- snappy/twister/surfaces/S_1 +3 -0
- snappy/twister/surfaces/S_1_1 +4 -0
- snappy/twister/surfaces/S_1_2 +5 -0
- snappy/twister/surfaces/S_1_2_5 +6 -0
- snappy/twister/surfaces/S_2 +6 -0
- snappy/twister/surfaces/S_2_1 +8 -0
- snappy/twister/surfaces/S_2_heeg +10 -0
- snappy/twister/surfaces/S_3 +8 -0
- snappy/twister/surfaces/S_3_1 +10 -0
- snappy/twister/surfaces/S_4_1 +12 -0
- snappy/twister/surfaces/S_5_1 +14 -0
- snappy/twister/surfaces/heeg_fig8 +9 -0
- snappy/twister/twister_core.cpython-313-darwin.so +0 -0
- snappy/upper_halfspace/__init__.py +146 -0
- snappy/upper_halfspace/ideal_point.py +26 -0
- snappy/verify/__init__.py +13 -0
- snappy/verify/canonical.py +542 -0
- snappy/verify/complex_volume/__init__.py +18 -0
- snappy/verify/complex_volume/adjust_torsion.py +86 -0
- snappy/verify/complex_volume/closed.py +168 -0
- snappy/verify/complex_volume/compute_ptolemys.py +90 -0
- snappy/verify/complex_volume/cusped.py +56 -0
- snappy/verify/complex_volume/extended_bloch.py +201 -0
- snappy/verify/cusp_translations.py +85 -0
- snappy/verify/edge_equations.py +80 -0
- snappy/verify/exceptions.py +254 -0
- snappy/verify/hyperbolicity.py +224 -0
- snappy/verify/interval_newton_shapes_engine.py +523 -0
- snappy/verify/interval_tree.py +400 -0
- snappy/verify/krawczyk_shapes_engine.py +518 -0
- snappy/verify/maximal_cusp_area_matrix/__init__.py +46 -0
- snappy/verify/maximal_cusp_area_matrix/cusp_tiling_engine.py +419 -0
- snappy/verify/maximal_cusp_area_matrix/cusp_translate_engine.py +153 -0
- snappy/verify/real_algebra.py +286 -0
- snappy/verify/shapes.py +25 -0
- snappy/verify/short_slopes.py +200 -0
- snappy/verify/square_extensions.py +1005 -0
- snappy/verify/test.py +78 -0
- snappy/verify/upper_halfspace/__init__.py +9 -0
- snappy/verify/upper_halfspace/extended_matrix.py +100 -0
- snappy/verify/upper_halfspace/finite_point.py +283 -0
- snappy/verify/upper_halfspace/ideal_point.py +426 -0
- snappy/verify/volume.py +128 -0
- snappy/version.py +2 -0
- snappy-3.2.dist-info/METADATA +58 -0
- snappy-3.2.dist-info/RECORD +503 -0
- snappy-3.2.dist-info/WHEEL +5 -0
- snappy-3.2.dist-info/entry_points.txt +2 -0
- snappy-3.2.dist-info/top_level.txt +28 -0
@@ -0,0 +1,1499 @@
|
|
1
|
+
# $Id: mcomplex.py,v 1.14 2009/08/20 15:58:58 t3m Exp $
|
2
|
+
# t3m - software for studying triangulated 3-manifolds
|
3
|
+
# Copyright (C) 2002 Marc Culler, Nathan Dunfield and others
|
4
|
+
#
|
5
|
+
# This program is distributed under the terms of the
|
6
|
+
# GNU General Public License, version 2 or later, as published by
|
7
|
+
# the Free Software Foundation. See the file GPL.txt for details.
|
8
|
+
|
9
|
+
from .simplex import *
|
10
|
+
from .tetrahedron import Tetrahedron
|
11
|
+
from .corner import Corner
|
12
|
+
from .arrow import Arrow
|
13
|
+
from .face import Face
|
14
|
+
from .edge import Edge
|
15
|
+
from .vertex import Vertex
|
16
|
+
from .surface import Surface, SpunSurface, ClosedSurface, ClosedSurfaceInCusped
|
17
|
+
from .perm4 import Perm4, inv
|
18
|
+
from . import files
|
19
|
+
from . import linalg
|
20
|
+
from . import homology
|
21
|
+
import sys
|
22
|
+
import random
|
23
|
+
import io
|
24
|
+
|
25
|
+
try:
|
26
|
+
import snappy
|
27
|
+
except ImportError:
|
28
|
+
snappy = None
|
29
|
+
|
30
|
+
VERBOSE = 0
|
31
|
+
|
32
|
+
# Globals needed for normal surfaces:
|
33
|
+
|
34
|
+
# The height shift dictionaries for the three quad types.
|
35
|
+
# Shift[ tet edge ] is a tuple of the shifts of the three quad
|
36
|
+
# types (Q03, Q13, Q23) along that edge.
|
37
|
+
#
|
38
|
+
# That is the first entry E01:(-1, 1, 0) means that Q03 shifts
|
39
|
+
# by -1 along E01, Q13 shifts by +1 along E01 and Q23 doesn't
|
40
|
+
# shift.
|
41
|
+
|
42
|
+
Shift = {E01:(-1,1,0), E02:(1,0,-1), E21:(0,-1,1),
|
43
|
+
E32:(-1,1,0), E31:(1,0,-1), E03:(0,-1,1)}
|
44
|
+
|
45
|
+
# The solution vector templates for the four vertex types.
|
46
|
+
|
47
|
+
VertexVector = {V0:(1,0,0,0), V1:(0,1,0,0),
|
48
|
+
V2:(0,0,1,0), V3:(0,0,0,1)}
|
49
|
+
|
50
|
+
|
51
|
+
def edge_and_arrow(edge_or_arrow):
|
52
|
+
"""
|
53
|
+
Given and edge or an arrow, returns the corresponding compatible
|
54
|
+
(edge, arrow) pair.
|
55
|
+
"""
|
56
|
+
if isinstance(edge_or_arrow, Edge):
|
57
|
+
edge = edge_or_arrow
|
58
|
+
arrow = Arrow(edge.Corners[0].Subsimplex,
|
59
|
+
LeftFace[edge.Corners[0].Subsimplex],
|
60
|
+
edge.Corners[0].Tetrahedron)
|
61
|
+
else:
|
62
|
+
if not isinstance(edge_or_arrow, Arrow):
|
63
|
+
raise ValueError('Input edge_or_arrow is neither')
|
64
|
+
arrow = edge_or_arrow.copy()
|
65
|
+
edge = arrow.axis()
|
66
|
+
return edge, arrow
|
67
|
+
|
68
|
+
|
69
|
+
class Insanity(Exception):
|
70
|
+
pass
|
71
|
+
|
72
|
+
|
73
|
+
class Mcomplex:
|
74
|
+
"""
|
75
|
+
An Mcomplex is a union of tetrahedra with faces identified in
|
76
|
+
pairs. The edges (vertices) are equivalence classes under the
|
77
|
+
induced equivalence relation on the set of edges (vertices) of the
|
78
|
+
tetrahedra.
|
79
|
+
|
80
|
+
>>> T = Mcomplex([Tetrahedron()])
|
81
|
+
>>> len(T), len(T.Vertices)
|
82
|
+
(1, 4)
|
83
|
+
>>> T = Mcomplex('m004')
|
84
|
+
>>> len(T)
|
85
|
+
2
|
86
|
+
>>> tet_data = [([0,1,0,1], [(2,1,0,3), (0,3,2,1), (2,1,0,3), (0,1,3,2)]),
|
87
|
+
... ([1,1,0,0], [(1,0,2,3), (1,0,2,3), (0,1,3,2), (0,3,2,1)])]
|
88
|
+
>>> S = Mcomplex(tet_data)
|
89
|
+
>>> len(S)
|
90
|
+
2
|
91
|
+
"""
|
92
|
+
def __init__(self, tetrahedron_list=None):
|
93
|
+
if tetrahedron_list is None:
|
94
|
+
tetrahedron_list = []
|
95
|
+
elif isinstance(tetrahedron_list, str) and snappy is None:
|
96
|
+
tetrahedron_list = tets_from_data(files.read_SnapPea_file(file_name=tetrahedron_list))
|
97
|
+
elif snappy:
|
98
|
+
if isinstance(tetrahedron_list, str):
|
99
|
+
tetrahedron_list = snappy.Triangulation(tetrahedron_list,
|
100
|
+
remove_finite_vertices=False)
|
101
|
+
if hasattr(tetrahedron_list, '_get_tetrahedra_gluing_data'):
|
102
|
+
tetrahedron_list = tets_from_data(
|
103
|
+
tetrahedron_list._get_tetrahedra_gluing_data())
|
104
|
+
if isinstance(tetrahedron_list, (list, tuple)):
|
105
|
+
if len(tetrahedron_list) > 0 and not isinstance(tetrahedron_list[0], Tetrahedron):
|
106
|
+
tetrahedron_list = tets_from_data(tetrahedron_list)
|
107
|
+
|
108
|
+
self.Tetrahedra = tetrahedron_list
|
109
|
+
self.Edges = []
|
110
|
+
self.Faces = []
|
111
|
+
self.Vertices = []
|
112
|
+
self.NormalSurfaces = []
|
113
|
+
self.AlmostNormalSurfaces = []
|
114
|
+
self.build()
|
115
|
+
|
116
|
+
def copy(self, base_arrow=None):
|
117
|
+
new_tets = []
|
118
|
+
new_to_old = {}
|
119
|
+
old_to_new = {}
|
120
|
+
for tet in self.Tetrahedra:
|
121
|
+
new_tet = Tetrahedron()
|
122
|
+
old_to_new[tet] = new_tet
|
123
|
+
new_to_old[new_tet] = tet
|
124
|
+
new_tets.append(new_tet)
|
125
|
+
for new_tet in new_tets:
|
126
|
+
for face in TwoSubsimplices:
|
127
|
+
new_tet.attach(face,
|
128
|
+
old_to_new[new_to_old[new_tet].Neighbor[face]],
|
129
|
+
new_to_old[new_tet].Gluing[face].tuple())
|
130
|
+
if base_arrow is None:
|
131
|
+
return self.__class__(new_tets)
|
132
|
+
else:
|
133
|
+
new_arrow = base_arrow.copy()
|
134
|
+
new_arrow.Tetrahedron = old_to_new[base_arrow.Tetrahedron]
|
135
|
+
return (self.__class__(new_tets), new_arrow)
|
136
|
+
|
137
|
+
def build(self):
|
138
|
+
for i in range(len(self.Tetrahedra)):
|
139
|
+
self.Tetrahedra[i].Index = i
|
140
|
+
self.build_face_classes()
|
141
|
+
self.build_edge_classes()
|
142
|
+
self.build_vertex_classes()
|
143
|
+
self.build_one_skeleton()
|
144
|
+
self.LinkGenera = [vertex.link_genus() for vertex in self.Vertices]
|
145
|
+
|
146
|
+
def rebuild(self):
|
147
|
+
for tet in self.Tetrahedra:
|
148
|
+
tet.clear_Class()
|
149
|
+
for face in self.Faces:
|
150
|
+
face.erase()
|
151
|
+
for edge in self.Edges:
|
152
|
+
edge.erase()
|
153
|
+
for vertex in self.Vertices:
|
154
|
+
vertex.erase()
|
155
|
+
self.Faces = []
|
156
|
+
self.Edges = []
|
157
|
+
self.Vertices = []
|
158
|
+
self.build()
|
159
|
+
|
160
|
+
def add_tet(self, tet):
|
161
|
+
self.Tetrahedra.append(tet)
|
162
|
+
|
163
|
+
def clear_tet(self,tet):
|
164
|
+
"""
|
165
|
+
Remove the face, edge and vertex classes of a tetrahedron.
|
166
|
+
This should destroy the faces, edges and vertices that meet
|
167
|
+
the tetrahedron. A call to build_face_classes,
|
168
|
+
build_edge_classes or build_vertex_classes will then rebuild
|
169
|
+
the neighborhood without having to rebuild the whole manifold.
|
170
|
+
"""
|
171
|
+
for two_subsimplex in TwoSubsimplices:
|
172
|
+
face = tet.Class[two_subsimplex]
|
173
|
+
if face is not None:
|
174
|
+
face.erase()
|
175
|
+
try:
|
176
|
+
self.Faces.remove(face)
|
177
|
+
except ValueError:
|
178
|
+
pass
|
179
|
+
|
180
|
+
for one_subsimplex in OneSubsimplices:
|
181
|
+
edge = tet.Class[one_subsimplex]
|
182
|
+
if edge is not None:
|
183
|
+
edge.erase()
|
184
|
+
try:
|
185
|
+
self.Edges.remove(edge)
|
186
|
+
except ValueError:
|
187
|
+
pass
|
188
|
+
|
189
|
+
for zero_subsimplex in ZeroSubsimplices:
|
190
|
+
vertex = tet.Class[zero_subsimplex]
|
191
|
+
if vertex is not None:
|
192
|
+
vertex.erase()
|
193
|
+
try:
|
194
|
+
self.Vertices.remove(vertex)
|
195
|
+
except ValueError:
|
196
|
+
pass
|
197
|
+
|
198
|
+
def delete_tet(self, tet):
|
199
|
+
"""
|
200
|
+
Clear a tetrahedron, then remove it from the Tetrahedron list.
|
201
|
+
"""
|
202
|
+
self.clear_tet(tet)
|
203
|
+
tet.erase()
|
204
|
+
self.Tetrahedra.remove(tet)
|
205
|
+
|
206
|
+
def new_arrow(self):
|
207
|
+
"""
|
208
|
+
Add one new tetrahedron and return one of its arrows.
|
209
|
+
"""
|
210
|
+
tet = Tetrahedron()
|
211
|
+
self.add_tet(tet)
|
212
|
+
return Arrow(E01,F3,tet)
|
213
|
+
|
214
|
+
def new_arrows(self,n):
|
215
|
+
return [self.new_arrow() for i in range(n)]
|
216
|
+
|
217
|
+
def new_tet(self):
|
218
|
+
tet = Tetrahedron()
|
219
|
+
self.add_tet(tet)
|
220
|
+
return tet
|
221
|
+
|
222
|
+
def new_tets(self,n):
|
223
|
+
return [self.new_tet() for i in range(n)]
|
224
|
+
|
225
|
+
def _triangulation_data(self):
|
226
|
+
ans = []
|
227
|
+
# We don't assume that the indices of the Tetraheda are equal
|
228
|
+
# to range(len(self))
|
229
|
+
tet_to_index = {T:i for i, T in enumerate(self.Tetrahedra)}
|
230
|
+
for T in self.Tetrahedra:
|
231
|
+
neighbors, perms = [], []
|
232
|
+
for v in TwoSubsimplices:
|
233
|
+
if T.Neighbor[v] is None:
|
234
|
+
neighbor, perm = None, None
|
235
|
+
else:
|
236
|
+
neighbor = tet_to_index[T.Neighbor[v]]
|
237
|
+
perm = T.Gluing[v].tuple()
|
238
|
+
neighbors.append(neighbor)
|
239
|
+
perms.append(perm)
|
240
|
+
ans.append((neighbors, perms))
|
241
|
+
return ans
|
242
|
+
|
243
|
+
def __len__(self):
|
244
|
+
"""
|
245
|
+
Return the number of tetrahedra
|
246
|
+
"""
|
247
|
+
return len(self.Tetrahedra)
|
248
|
+
|
249
|
+
def __getitem__(self, index):
|
250
|
+
"""
|
251
|
+
M[i] refers to the ith Tetrahedron of the mcomplex M.
|
252
|
+
"""
|
253
|
+
return self.Tetrahedra[index]
|
254
|
+
|
255
|
+
def info(self, out=sys.stdout):
|
256
|
+
"""
|
257
|
+
M.info() describes the Mcomplex.
|
258
|
+
"""
|
259
|
+
try:
|
260
|
+
out.write( "Mcomplex with %d Tetrahedra\n\n" % len(self) )
|
261
|
+
for tet in self.Tetrahedra:
|
262
|
+
tet.info(out)
|
263
|
+
out.write("\nEdges:\n")
|
264
|
+
for edge in self.Edges:
|
265
|
+
edge.info(out)
|
266
|
+
except OSError:
|
267
|
+
pass
|
268
|
+
|
269
|
+
def build_edge_classes(self):
|
270
|
+
"""
|
271
|
+
Construct the edge classes and compute valences.
|
272
|
+
"""
|
273
|
+
for tet in self.Tetrahedra:
|
274
|
+
for one_subsimplex in OneSubsimplices:
|
275
|
+
if ( tet.Class[one_subsimplex] is None ):
|
276
|
+
newEdge = Edge()
|
277
|
+
self.Edges.append(newEdge)
|
278
|
+
first_arrow = Arrow(one_subsimplex, RightFace[one_subsimplex], tet)
|
279
|
+
a = first_arrow.copy()
|
280
|
+
sanity_check = 0
|
281
|
+
boundary_hits = 0
|
282
|
+
while 1:
|
283
|
+
# Walk around the edge.
|
284
|
+
if sanity_check > 6*len(self.Tetrahedra):
|
285
|
+
raise Insanity('Bad gluing data: could not construct edge link.')
|
286
|
+
# Record the corners and edge classes as we go.
|
287
|
+
newEdge._add_corner(a)
|
288
|
+
a.Tetrahedron.Class[a.Edge] = newEdge
|
289
|
+
if a.next() is None:
|
290
|
+
# We hit the boundary!
|
291
|
+
# Go back to the beginning and walk to the right.
|
292
|
+
# If this is our second boundary hit, we are done.
|
293
|
+
if not boundary_hits == 0:
|
294
|
+
newEdge.RightBdryArrow = a.copy()
|
295
|
+
# Make sure the corner list goes from right to left.
|
296
|
+
newEdge.Corners.reverse()
|
297
|
+
break
|
298
|
+
else:
|
299
|
+
boundary_hits = 1
|
300
|
+
newEdge.LeftBdryArrow = a.copy()
|
301
|
+
newEdge.IntOrBdry = 'bdry'
|
302
|
+
a = first_arrow.copy()
|
303
|
+
a.reverse()
|
304
|
+
# Don't record the first corner twice.
|
305
|
+
del newEdge.Corners[0]
|
306
|
+
# Reverse the corner list since we are now going right.
|
307
|
+
newEdge.Corners.reverse()
|
308
|
+
else:
|
309
|
+
# Stop if we get back to where we started.
|
310
|
+
if a == first_arrow:
|
311
|
+
newEdge.IntOrBdry = 'int'
|
312
|
+
break
|
313
|
+
sanity_check = sanity_check + 1
|
314
|
+
self.EdgeValences = [edge.valence() for edge in self.Edges]
|
315
|
+
for i in range(len(self.Edges)):
|
316
|
+
self.Edges[i].Index = i
|
317
|
+
|
318
|
+
def build_vertex_classes(self):
|
319
|
+
for tet in self.Tetrahedra:
|
320
|
+
for zero_subsimplex in ZeroSubsimplices:
|
321
|
+
if ( tet.Class[zero_subsimplex] is None ):
|
322
|
+
newVertex = Vertex()
|
323
|
+
self.Vertices.append(newVertex)
|
324
|
+
self.walk_vertex(newVertex,zero_subsimplex,tet)
|
325
|
+
for i in range(len(self.Vertices)):
|
326
|
+
self.Vertices[i].Index = i
|
327
|
+
|
328
|
+
def walk_vertex(self,vertex,zero_subsimplex,tet):
|
329
|
+
if (tet.Class[zero_subsimplex] is not None ):
|
330
|
+
return
|
331
|
+
else:
|
332
|
+
tet.Class[zero_subsimplex] = vertex
|
333
|
+
vertex.Corners.append(Corner(tet,zero_subsimplex))
|
334
|
+
for two_subsimplex in TwoSubsimplices:
|
335
|
+
if ( is_subset(zero_subsimplex,two_subsimplex)
|
336
|
+
and
|
337
|
+
tet.Gluing[two_subsimplex] is not None):
|
338
|
+
self.walk_vertex(vertex,
|
339
|
+
tet.Gluing[two_subsimplex].image(zero_subsimplex),
|
340
|
+
tet.Neighbor[two_subsimplex])
|
341
|
+
|
342
|
+
def build_one_skeleton(self):
|
343
|
+
"""
|
344
|
+
Construct the 1-skeleton, i.e. record which edges are
|
345
|
+
connected to which vertices. This assumes that Edges and Vertices
|
346
|
+
have already been built.
|
347
|
+
"""
|
348
|
+
for edge in self.Edges:
|
349
|
+
tet = edge.Corners[0].Tetrahedron
|
350
|
+
one_subsimplex = edge.Corners[0].Subsimplex
|
351
|
+
tail = tet.Class[Tail[one_subsimplex]]
|
352
|
+
head = tet.Class[Head[one_subsimplex]]
|
353
|
+
edge.Vertices = [tail , head]
|
354
|
+
tail.Edges.append(edge)
|
355
|
+
head.Edges.append(edge)
|
356
|
+
if edge.IntOrBdry == 'bdry':
|
357
|
+
tail.IntOrBdry = 'bdry'
|
358
|
+
head.IntOrBdry = 'bdry'
|
359
|
+
for vertex in self.Vertices:
|
360
|
+
if vertex.IntOrBdry == '':
|
361
|
+
vertex.IntOrBdry = 'int'
|
362
|
+
|
363
|
+
def build_face_classes(self):
|
364
|
+
"""
|
365
|
+
Construct the faces.
|
366
|
+
"""
|
367
|
+
for tet in self.Tetrahedra:
|
368
|
+
for two_subsimplex in TwoSubsimplices:
|
369
|
+
if ( tet.Class[two_subsimplex] is None ):
|
370
|
+
newFace = Face()
|
371
|
+
self.Faces.append(newFace)
|
372
|
+
newFace.Corners.append(Corner(tet,two_subsimplex))
|
373
|
+
tet.Class[two_subsimplex] = newFace
|
374
|
+
othertet = tet.Neighbor[two_subsimplex]
|
375
|
+
if othertet:
|
376
|
+
newFace.IntOrBdry = 'int'
|
377
|
+
othersubsimplex = tet.Gluing[two_subsimplex].image(two_subsimplex)
|
378
|
+
newFace.Corners.append(Corner(othertet, othersubsimplex))
|
379
|
+
othertet.Class[othersubsimplex] = newFace
|
380
|
+
else:
|
381
|
+
newFace.IntOrBdry = 'bdry'
|
382
|
+
for i in range(len(self.Faces)):
|
383
|
+
self.Faces[i].Index = i
|
384
|
+
|
385
|
+
def orient(self):
|
386
|
+
"""
|
387
|
+
The simplification moves below assume that the Mcomplex is oriented.
|
388
|
+
Yes, oriented, not just orientable. An Mcomplex has been oriented if
|
389
|
+
all of the gluing permutations are odd. The orient method walks through
|
390
|
+
the manifold reorienting tetrahedra to try to get all of the gluing
|
391
|
+
permutations to be odd. Returns True on success, False if the manifold is
|
392
|
+
not orientable.
|
393
|
+
"""
|
394
|
+
for tet in self.Tetrahedra:
|
395
|
+
tet.Checked = 0
|
396
|
+
self.walk_and_orient(self[0], 1)
|
397
|
+
self.rebuild()
|
398
|
+
return self.is_oriented()
|
399
|
+
|
400
|
+
def is_oriented(self):
|
401
|
+
for tet in self.Tetrahedra:
|
402
|
+
for two_subsimplex in TwoSubsimplices:
|
403
|
+
if (not tet.Neighbor[two_subsimplex] is None
|
404
|
+
and tet.Gluing[two_subsimplex].sign() == 0):
|
405
|
+
return False
|
406
|
+
return True
|
407
|
+
|
408
|
+
def walk_and_orient(self, tet, sign):
|
409
|
+
if tet.Checked == 1:
|
410
|
+
return
|
411
|
+
tet.Checked = 1
|
412
|
+
if sign == 0:
|
413
|
+
tet.reverse()
|
414
|
+
for ssimp in TwoSubsimplices:
|
415
|
+
if tet.Neighbor[ssimp] is not None:
|
416
|
+
self.walk_and_orient(tet.Neighbor[ssimp], tet.Gluing[ssimp].sign())
|
417
|
+
|
418
|
+
def build_matrix(self):
|
419
|
+
"""
|
420
|
+
Convention is that the ordered quads are (Q03, Q13, Q23).
|
421
|
+
"""
|
422
|
+
int_edges = [edge for edge in self.Edges if edge.IntOrBdry == 'int']
|
423
|
+
self.QuadMatrix = linalg.Matrix(len(int_edges), 3*len(self))
|
424
|
+
for edge in int_edges:
|
425
|
+
for corner in edge.Corners:
|
426
|
+
i = int_edges.index(edge)
|
427
|
+
j = corner.Tetrahedron.Index
|
428
|
+
for k in range(3):
|
429
|
+
self.QuadMatrix[i,3*j+k] += Shift[corner.Subsimplex][k]
|
430
|
+
self.build_vertex_incidences()
|
431
|
+
|
432
|
+
def build_vertex_incidences(self):
|
433
|
+
for vertex in self.Vertices:
|
434
|
+
vertex.IncidenceVector = linalg.Vector( 4*len(self) )
|
435
|
+
for corner in vertex.Corners:
|
436
|
+
j = corner.Tetrahedron.Index
|
437
|
+
vertex.IncidenceVector[4*j:4*j+4] += VertexVector[corner.Subsimplex]
|
438
|
+
|
439
|
+
def find_normal_surfaces(self, modp=0, print_progress=False,
|
440
|
+
algorithm='FXrays'):
|
441
|
+
"""
|
442
|
+
Convention is that the ordered quads are (Q03, Q13, Q23).
|
443
|
+
"""
|
444
|
+
self.NormalSurfaces = []
|
445
|
+
self.build_matrix()
|
446
|
+
if algorithm == 'FXrays':
|
447
|
+
try:
|
448
|
+
import FXrays
|
449
|
+
except ImportError:
|
450
|
+
raise ImportError("You need to install the FXrays module"
|
451
|
+
"if you want to find normal surfaces.")
|
452
|
+
coeff_list = FXrays.find_Xrays(self.QuadMatrix.nrows(),
|
453
|
+
self.QuadMatrix.ncols(),
|
454
|
+
self.QuadMatrix.entries(), modp,
|
455
|
+
print_progress=print_progress)
|
456
|
+
|
457
|
+
elif algorithm == 'regina':
|
458
|
+
T = self.regina_triangulation()
|
459
|
+
import regina
|
460
|
+
coeff_list = []
|
461
|
+
tets = range(len(self))
|
462
|
+
surfaces = regina.NNormalSurfaceList.enumerate(T, regina.NS_QUAD)
|
463
|
+
for i in range(surfaces.getNumberOfSurfaces()):
|
464
|
+
S = surfaces.getSurface(i)
|
465
|
+
coeff_vector = [int(S.getQuadCoord(tet, quad).stringValue())
|
466
|
+
for tet in tets for quad in (2, 1, 0)]
|
467
|
+
coeff_list.append(coeff_vector)
|
468
|
+
|
469
|
+
else:
|
470
|
+
raise ValueError("Algorithm must be in {'FXrays', 'regina'}")
|
471
|
+
|
472
|
+
for coeff_vector in coeff_list:
|
473
|
+
if max(self.LinkGenera) == 0:
|
474
|
+
self.NormalSurfaces.append(ClosedSurface(self, coeff_vector))
|
475
|
+
elif self.LinkGenera.count(1) == len(self.LinkGenera):
|
476
|
+
self.NormalSurfaces.append(SpunSurface(self, coeff_vector))
|
477
|
+
else:
|
478
|
+
self.NormalSurfaces.append(Surface(self, coeff_vector))
|
479
|
+
|
480
|
+
def normal_surface_info(self, out=sys.stdout):
|
481
|
+
try:
|
482
|
+
for surface in self.NormalSurfaces:
|
483
|
+
out.write("-------------------------------------\n\n")
|
484
|
+
surface.info(self, out)
|
485
|
+
out.write('\n')
|
486
|
+
except OSError:
|
487
|
+
pass
|
488
|
+
|
489
|
+
def almost_normal_surface_info(self, out=sys.stdout):
|
490
|
+
try:
|
491
|
+
for surface in self.AlmostNormalSurfaces:
|
492
|
+
out.write("-------------------------------------\n\n")
|
493
|
+
surface.info(self, out)
|
494
|
+
out.write('\n')
|
495
|
+
except OSError:
|
496
|
+
pass
|
497
|
+
|
498
|
+
# Simplification Moves
|
499
|
+
#
|
500
|
+
# The simplification moves require that the list of edge classes
|
501
|
+
# be up to date. Edge classes are recomputed as part of each
|
502
|
+
# move. The vertex classes are not used, nor are they updated, by
|
503
|
+
# these moves, with the exception of randomize.
|
504
|
+
|
505
|
+
def _face_permits_two_to_three(self, a, b):
|
506
|
+
S, T = a.Tetrahedron, b.Tetrahedron
|
507
|
+
if S is None:
|
508
|
+
return False, 'Tetrahedron not attached to face'
|
509
|
+
if S == T:
|
510
|
+
return False, 'Two tetrahedra are the same'
|
511
|
+
return True, None
|
512
|
+
|
513
|
+
def _two_to_three_move_hook(self, old_arrow, new_arrows):
|
514
|
+
pass
|
515
|
+
|
516
|
+
def two_to_three(self, face_or_arrow, tet=None,
|
517
|
+
return_arrow=False, must_succeed=False,
|
518
|
+
unsafe_mode=False):
|
519
|
+
"""
|
520
|
+
Perform a 2-to-3 Pachner move on the face specified by
|
521
|
+
(face_or_arrow, tet), replacing the two tetrahedra with three
|
522
|
+
tetrahedra around an edge.
|
523
|
+
|
524
|
+
Returns ``True`` or ``False`` depending on whether the
|
525
|
+
requested move succeeded. When ``must_succeed`` is ``True``,
|
526
|
+
it instead raises an exception if the requested move is
|
527
|
+
topologically impossible.
|
528
|
+
|
529
|
+
When ``unsafe_mode`` is ``True`` it does not rebuild the edge
|
530
|
+
classes; in any mode, it does not rebuild the vertex classes.
|
531
|
+
"""
|
532
|
+
|
533
|
+
if isinstance(face_or_arrow, Arrow):
|
534
|
+
assert tet is None
|
535
|
+
arrow = face_or_arrow
|
536
|
+
a = arrow.copy()
|
537
|
+
else:
|
538
|
+
arrow = None
|
539
|
+
a = Arrow(PickAnEdge[face_or_arrow], face_or_arrow, tet)
|
540
|
+
|
541
|
+
a = a.copy()
|
542
|
+
b = a.glued()
|
543
|
+
if not unsafe_mode:
|
544
|
+
possible, reason = self._face_permits_two_to_three(a, b)
|
545
|
+
if not possible:
|
546
|
+
if must_succeed:
|
547
|
+
raise ValueError(reason)
|
548
|
+
return False
|
549
|
+
a_orig = a.copy()
|
550
|
+
new = self.new_arrows(3)
|
551
|
+
for i in range(3):
|
552
|
+
new[i].glue(new[(i + 1) % 3])
|
553
|
+
a.reverse()
|
554
|
+
for c in new:
|
555
|
+
c.opposite().glue(a.glued())
|
556
|
+
c.reverse().glue(b.glued())
|
557
|
+
a.rotate(-1)
|
558
|
+
b.rotate(1)
|
559
|
+
for c in new:
|
560
|
+
c.reverse()
|
561
|
+
c.opposite()
|
562
|
+
self._two_to_three_move_hook(a_orig, new)
|
563
|
+
self.delete_tet(a.Tetrahedron)
|
564
|
+
self.delete_tet(b.Tetrahedron)
|
565
|
+
if not unsafe_mode:
|
566
|
+
self.build_edge_classes()
|
567
|
+
if VERBOSE:
|
568
|
+
print('2->3')
|
569
|
+
print(self.EdgeValences)
|
570
|
+
if return_arrow:
|
571
|
+
return new[1].north_head().get_arrow()
|
572
|
+
else:
|
573
|
+
return True
|
574
|
+
|
575
|
+
def _edge_permits_three_to_two(self, edge):
|
576
|
+
if not edge.IntOrBdry == 'int':
|
577
|
+
return False, 'Cannot do move on exterior edge'
|
578
|
+
if edge.valence() != 3:
|
579
|
+
return False, 'Edge has valence %d not 3' % edge.valence()
|
580
|
+
if not edge.distinct():
|
581
|
+
return False, 'Tets around edge are not distinct'
|
582
|
+
return True, None
|
583
|
+
|
584
|
+
def _three_to_two_move_hook(self, old_arrow, new_arrows):
|
585
|
+
pass
|
586
|
+
|
587
|
+
def three_to_two(self, edge_or_arrow, return_arrow=False,
|
588
|
+
must_succeed=False, unsafe_mode=False):
|
589
|
+
"""
|
590
|
+
Replaces the star of an edge of valence 3 by two tetrahedra.
|
591
|
+
|
592
|
+
Options and return value are the same as ``two_to_three``.
|
593
|
+
"""
|
594
|
+
edge, a = edge_and_arrow(edge_or_arrow)
|
595
|
+
if not unsafe_mode:
|
596
|
+
possible, reason = self._edge_permits_three_to_two(edge)
|
597
|
+
if not possible:
|
598
|
+
if must_succeed:
|
599
|
+
raise ValueError(reason)
|
600
|
+
return False
|
601
|
+
|
602
|
+
a_orig = a.copy()
|
603
|
+
b = self.new_arrow()
|
604
|
+
c = self.new_arrow()
|
605
|
+
b.glue(c)
|
606
|
+
b_orig = b.copy()
|
607
|
+
b.reverse()
|
608
|
+
b_to_return = b.copy()
|
609
|
+
for i in range(3):
|
610
|
+
b.glue(a.opposite().glued())
|
611
|
+
c.glue(a.reverse().glued())
|
612
|
+
b.rotate(-1)
|
613
|
+
c.rotate(1)
|
614
|
+
a.reverse().opposite().next()
|
615
|
+
|
616
|
+
self._three_to_two_move_hook(a_orig, (b_orig, b, c))
|
617
|
+
if unsafe_mode:
|
618
|
+
tet0 = a_orig.Tetrahedron
|
619
|
+
tet1 = a_orig.next().Tetrahedron
|
620
|
+
tet2 = a_orig.next().Tetrahedron
|
621
|
+
self.delete_tet(tet0)
|
622
|
+
self.delete_tet(tet1)
|
623
|
+
self.delete_tet(tet2)
|
624
|
+
else:
|
625
|
+
for corner in edge.Corners:
|
626
|
+
self.delete_tet(corner.Tetrahedron)
|
627
|
+
if not unsafe_mode:
|
628
|
+
self.build_edge_classes()
|
629
|
+
if VERBOSE:
|
630
|
+
print('3->2')
|
631
|
+
print(self.EdgeValences)
|
632
|
+
if return_arrow:
|
633
|
+
return b_to_return
|
634
|
+
return True
|
635
|
+
|
636
|
+
def _arrow_permits_two_to_zero(self, arrow):
|
637
|
+
edge = arrow.axis()
|
638
|
+
if not edge.IntOrBdry == 'int':
|
639
|
+
return False, 'Cannot do move on exterior edge'
|
640
|
+
if edge.valence() != 2:
|
641
|
+
return False, 'Edge has valence %d not 2' % edge.valence()
|
642
|
+
if not edge.distinct():
|
643
|
+
return False, 'Tets around edge are not distinct'
|
644
|
+
if arrow.equator() == arrow.glued().equator():
|
645
|
+
return False, 'Edges opposite the valence 2 edge are the same'
|
646
|
+
# You'd think we should exclude the following, but bizarrely
|
647
|
+
# everything is fine when this happens, which is quite
|
648
|
+
# frequently in some settings.
|
649
|
+
#
|
650
|
+
# T0 = edge.Corners[0].Tetrahedron
|
651
|
+
# T1 = edge.Corners[1].Tetrahedron
|
652
|
+
# if (T0 in T0.Neighbor.values()) or (T1 in T1.Neighbor.values()):
|
653
|
+
# return False, 'One tet is glued to itself'
|
654
|
+
return True, None
|
655
|
+
|
656
|
+
def _two_to_zero_hook(self, old_arrow):
|
657
|
+
pass
|
658
|
+
|
659
|
+
def two_to_zero(self, edge_or_arrow, must_succeed=False, unsafe_mode=False):
|
660
|
+
"""
|
661
|
+
Flatten the star of an edge of valence 2 to eliminate two
|
662
|
+
tetrahedra.
|
663
|
+
|
664
|
+
Options and return value are the same as ``two_to_three``.
|
665
|
+
"""
|
666
|
+
edge, a = edge_and_arrow(edge_or_arrow)
|
667
|
+
b = a.glued()
|
668
|
+
|
669
|
+
possible, reason = self._arrow_permits_two_to_zero(a)
|
670
|
+
if not possible:
|
671
|
+
if must_succeed:
|
672
|
+
raise ValueError(reason)
|
673
|
+
return False
|
674
|
+
|
675
|
+
self._two_to_zero_hook(a)
|
676
|
+
a.opposite().glued().reverse().glue(b.opposite().glued())
|
677
|
+
a.reverse().glued().reverse().glue(b.reverse().glued())
|
678
|
+
|
679
|
+
for corner in edge.Corners:
|
680
|
+
self.delete_tet(corner.Tetrahedron)
|
681
|
+
if not unsafe_mode:
|
682
|
+
self.build_edge_classes()
|
683
|
+
if VERBOSE:
|
684
|
+
print('2->0')
|
685
|
+
print(self.EdgeValences)
|
686
|
+
return True
|
687
|
+
|
688
|
+
def zero_to_two(self, arrow1, gap):
|
689
|
+
"""
|
690
|
+
Blow up two adjacent faces into a pair of tetrahedra. The
|
691
|
+
faces are specified by passing an arrow specifying the first
|
692
|
+
face and an integer n. The second face is obtained by
|
693
|
+
reversing the arrow and applying next() n times. Thus there
|
694
|
+
are n faces between the two that are involved in the blow up.
|
695
|
+
Returns ``True`` on success, ``False`` if the move cannot be
|
696
|
+
performed.
|
697
|
+
"""
|
698
|
+
arrow2 = arrow1.copy().reverse()
|
699
|
+
count = 0
|
700
|
+
while count < gap:
|
701
|
+
if arrow2.next() is None:
|
702
|
+
return False
|
703
|
+
count = count + 1
|
704
|
+
# Do we *also* need the old test, which was
|
705
|
+
# b.Tetrahedron == arrow1.Tetrahedron ?
|
706
|
+
if arrow1.face_class() == arrow2.face_class():
|
707
|
+
return 0
|
708
|
+
a = arrow1.glued()
|
709
|
+
b = arrow2.glued()
|
710
|
+
c = self.new_arrows(2)
|
711
|
+
c[0].glue(c[1])
|
712
|
+
c[1].glue(c[0])
|
713
|
+
c[0].opposite().glue(a)
|
714
|
+
c[0].reverse().glue(b)
|
715
|
+
c[1].opposite().glue(arrow1.reverse())
|
716
|
+
c[1].reverse().glue(arrow2.reverse())
|
717
|
+
self.clear_tet(arrow1.Tetrahedron)
|
718
|
+
self.clear_tet(arrow2.Tetrahedron)
|
719
|
+
self.build_edge_classes()
|
720
|
+
if VERBOSE:
|
721
|
+
print('0->2')
|
722
|
+
print(self.EdgeValences)
|
723
|
+
return True
|
724
|
+
|
725
|
+
def _edge_permits_four_to_four(self, edge):
|
726
|
+
if not edge.IntOrBdry == 'int':
|
727
|
+
return False, 'Cannot do move on exterior edge'
|
728
|
+
if edge.valence() != 4:
|
729
|
+
return False, 'Edge has valence %d not 4' % edge.valence()
|
730
|
+
if not edge.distinct():
|
731
|
+
return False, 'Tets around edge are not distinct'
|
732
|
+
return True, None
|
733
|
+
|
734
|
+
def _four_to_four_move_hook(self, old_arrow, new_arrows):
|
735
|
+
pass
|
736
|
+
|
737
|
+
def four_to_four(self, edge_or_arrow, must_succeed=False, unsafe_mode=False):
|
738
|
+
"""
|
739
|
+
Replace an edge of valence 4 by another diagonal of the
|
740
|
+
octahedron formed by the star of the edge. There are two
|
741
|
+
choices for this diagonal. If you care which one is used then
|
742
|
+
pass an arrow representing the edge of valence four. The head
|
743
|
+
of the arrow will be an endpoint of the new diagonal. If you
|
744
|
+
don't care, just pass an edge. The choice of diagonal will
|
745
|
+
then be made randomly.
|
746
|
+
|
747
|
+
Options and return value are the same as ``two_to_three``.
|
748
|
+
"""
|
749
|
+
edge, a = edge_and_arrow(edge_or_arrow)
|
750
|
+
a_orig = a.copy()
|
751
|
+
|
752
|
+
possible, reason = self._edge_permits_four_to_four(edge)
|
753
|
+
if not possible:
|
754
|
+
if must_succeed:
|
755
|
+
raise ValueError(reason)
|
756
|
+
return False
|
757
|
+
|
758
|
+
c = self.new_arrows(4)
|
759
|
+
c_orig = [x.copy() for x in c]
|
760
|
+
for i in range(4):
|
761
|
+
c[i].glue(c[(i + 1) % 4])
|
762
|
+
b = a.glued().reverse()
|
763
|
+
c[0].opposite().glue(a.rotate(1).glued())
|
764
|
+
c[1].opposite().glue(b.rotate(-1).glued())
|
765
|
+
c[2].opposite().glue(b.rotate(-1).glued())
|
766
|
+
c[3].opposite().glue(a.rotate(1).glued())
|
767
|
+
a.rotate(1).reverse().next()
|
768
|
+
b.rotate(-1).reverse().next()
|
769
|
+
c[0].reverse().glue(a.rotate(-1).glued())
|
770
|
+
c[1].reverse().glue(b.rotate(1).glued())
|
771
|
+
c[2].reverse().glue(b.rotate(1).glued())
|
772
|
+
c[3].reverse().glue(a.rotate(-1).glued())
|
773
|
+
|
774
|
+
self._four_to_four_move_hook(a_orig, c_orig)
|
775
|
+
for corner in edge.Corners:
|
776
|
+
self.delete_tet(corner.Tetrahedron)
|
777
|
+
|
778
|
+
if not unsafe_mode:
|
779
|
+
self.build_edge_classes()
|
780
|
+
if VERBOSE:
|
781
|
+
print('4->4')
|
782
|
+
print(self.EdgeValences)
|
783
|
+
|
784
|
+
return True
|
785
|
+
|
786
|
+
def attack_valence_one(self):
|
787
|
+
"""
|
788
|
+
Modify the triangulation near a valence 1 edge, creating a
|
789
|
+
valence 2 edge that can likely be eliminated, reducing the
|
790
|
+
number of tetrahedra by one.
|
791
|
+
"""
|
792
|
+
if len(self) == 1:
|
793
|
+
return False
|
794
|
+
for e in self.Edges:
|
795
|
+
if e.valence() == 1:
|
796
|
+
corner = e.Corners[0]
|
797
|
+
tet = corner.Tetrahedron
|
798
|
+
sub = corner.Subsimplex
|
799
|
+
other_faces = [face for face in TwoSubsimplices
|
800
|
+
if not is_subset(sub, face)]
|
801
|
+
assert len(other_faces) == 2
|
802
|
+
face = other_faces[0]
|
803
|
+
self.two_to_three(face, tet, must_succeed=True)
|
804
|
+
return True
|
805
|
+
return False
|
806
|
+
|
807
|
+
def eliminate_valence_two(self):
|
808
|
+
"""
|
809
|
+
Perform a single ``two_to_zero`` move on a valence 2 edge, if
|
810
|
+
any such is possible.
|
811
|
+
"""
|
812
|
+
did_simplify = False
|
813
|
+
progress = True
|
814
|
+
while progress:
|
815
|
+
progress = False
|
816
|
+
for edge in self.Edges:
|
817
|
+
if edge.valence() == 2:
|
818
|
+
if self.two_to_zero(edge):
|
819
|
+
progress, did_simplify = True, True
|
820
|
+
break
|
821
|
+
return did_simplify
|
822
|
+
|
823
|
+
def eliminate_valence_three(self):
|
824
|
+
"""
|
825
|
+
Perform a single ``three_to_two`` move on a valence 3 edge, if
|
826
|
+
any such is possible.
|
827
|
+
"""
|
828
|
+
did_simplify = False
|
829
|
+
progress = True
|
830
|
+
while progress:
|
831
|
+
progress = False
|
832
|
+
for edge in self.Edges:
|
833
|
+
if edge.valence() == 3:
|
834
|
+
if self.three_to_two(edge):
|
835
|
+
progress, did_simplify = True, True
|
836
|
+
break
|
837
|
+
return did_simplify
|
838
|
+
|
839
|
+
def easy_simplify(self):
|
840
|
+
"""
|
841
|
+
Perform moves eliminating edges of valence 1, 2, and 3,
|
842
|
+
monotonically reducing the number of tetrahedra until no
|
843
|
+
further such moves are possible. Returns whether or not the
|
844
|
+
number of tetrahedra was reduced.
|
845
|
+
|
846
|
+
>>> M = Mcomplex('zLALvwvMwLzzAQPQQkbcbeijmoomvwuvust'
|
847
|
+
... 'wwytxtyxyahkswpmakguadppmrssxbkoxsi')
|
848
|
+
>>> M.easy_simplify()
|
849
|
+
True
|
850
|
+
>>> len(M)
|
851
|
+
1
|
852
|
+
>>> M.rebuild(); M.isosig()
|
853
|
+
'bkaagj'
|
854
|
+
"""
|
855
|
+
|
856
|
+
init_tet = len(self)
|
857
|
+
progress = True
|
858
|
+
while progress:
|
859
|
+
curr_tet = len(self)
|
860
|
+
while self.attack_valence_one():
|
861
|
+
pass
|
862
|
+
while self.eliminate_valence_two() | self.eliminate_valence_three():
|
863
|
+
pass
|
864
|
+
progress = len(self) < curr_tet
|
865
|
+
|
866
|
+
return len(self) < init_tet
|
867
|
+
|
868
|
+
def jiggle(self):
|
869
|
+
"""
|
870
|
+
Do a random ``four_to_four`` move if one is possible.
|
871
|
+
"""
|
872
|
+
fours = [edge for edge in self.Edges
|
873
|
+
if edge.valence() == 4 and edge.IntOrBdry == 'int']
|
874
|
+
if len(fours) == 0:
|
875
|
+
return False
|
876
|
+
return self.four_to_four(random.choice(fours))
|
877
|
+
|
878
|
+
JIGGLE_LIMIT = 6
|
879
|
+
|
880
|
+
def simplify(self, jiggle_limit=None):
|
881
|
+
"""
|
882
|
+
Try to simplify the triangulation using only moves that do not
|
883
|
+
increase the total number of tetrahedra, using a combination
|
884
|
+
of ``jiggle`` and ``easy_simplify``.
|
885
|
+
|
886
|
+
When ``jiggle_limit`` is ``None``, it defaults to
|
887
|
+
``self.JIGGLE_LIMIT`` which is typically 6.
|
888
|
+
"""
|
889
|
+
|
890
|
+
if jiggle_limit is None:
|
891
|
+
jiggle_limit = self.JIGGLE_LIMIT
|
892
|
+
|
893
|
+
init_tet = len(self)
|
894
|
+
for j in range(jiggle_limit):
|
895
|
+
self.easy_simplify()
|
896
|
+
if not self.jiggle():
|
897
|
+
break
|
898
|
+
self.eliminate_valence_two()
|
899
|
+
return len(self) < init_tet
|
900
|
+
|
901
|
+
def blowup(self, n):
|
902
|
+
"""
|
903
|
+
Do ``n`` randomly chosen ``two_to_three`` moves.
|
904
|
+
"""
|
905
|
+
for i in range(n):
|
906
|
+
rand_tet = self[ random.randint(0, len(self) - 1) ]
|
907
|
+
rand_face = TwoSubsimplices[random.randint(0,3)]
|
908
|
+
self.two_to_three(rand_face, rand_tet)
|
909
|
+
self.eliminate_valence_two()
|
910
|
+
return len(self)
|
911
|
+
|
912
|
+
def blowup2(self, n):
|
913
|
+
"""
|
914
|
+
Create ``n`` edges of valence 2 in random places, removing valence
|
915
|
+
3 edges whenever they appear.
|
916
|
+
"""
|
917
|
+
|
918
|
+
for i in range(n):
|
919
|
+
rand_edge = self.Edges[ random.randint(0, len(self.Edges) - 1) ]
|
920
|
+
j = random.randint(0, len(rand_edge.Corners) - 1)
|
921
|
+
k = random.randint(0, len(rand_edge.Corners) - 1 - j)
|
922
|
+
one_subsimplex = rand_edge.Corners[j].Subsimplex
|
923
|
+
two_subsimplex = LeftFace[one_subsimplex]
|
924
|
+
a = Arrow(one_subsimplex, two_subsimplex,
|
925
|
+
rand_edge.Corners[j].Tetrahedron)
|
926
|
+
self.zero_to_two(a, k)
|
927
|
+
self.eliminate_valence_three()
|
928
|
+
return len(self)
|
929
|
+
|
930
|
+
BLOW_UP_MULTIPLE = 6
|
931
|
+
|
932
|
+
def randomize(self, blow_up_multiple=None):
|
933
|
+
"""
|
934
|
+
Do ``blow_up_multiple`` times the current number of tetrahedra
|
935
|
+
random ``two_to_three`` moves, and then ``simplify``.
|
936
|
+
|
937
|
+
If ``blow_up_multiple`` is ``None``, it defaults to
|
938
|
+
``self.BLOW_UP_MULTIPLE`` which is typically 6.
|
939
|
+
|
940
|
+
Unlike the other simplification methods, this one rebuilds the
|
941
|
+
vertices.
|
942
|
+
"""
|
943
|
+
if blow_up_multiple is None:
|
944
|
+
blow_up_multiple = self.BLOW_UP_MULTIPLE
|
945
|
+
self.blowup(blow_up_multiple * len(self))
|
946
|
+
self.simplify()
|
947
|
+
self.rebuild()
|
948
|
+
return len(self)
|
949
|
+
|
950
|
+
def bdry_neighbor(self, arrow):
|
951
|
+
"""
|
952
|
+
Find a boundary face adjoining a given boundary face.
|
953
|
+
Given an Arrow representing a boundary face, return the Arrow
|
954
|
+
representing the boundary face that shares the Arrow's Edge.
|
955
|
+
"""
|
956
|
+
if arrow.next() is not None:
|
957
|
+
raise Insanity("That boundary face is not on the boundary!")
|
958
|
+
edge = arrow.Tetrahedron.Class[arrow.Edge]
|
959
|
+
if edge.LeftBdryArrow == arrow:
|
960
|
+
return edge.RightBdryArrow
|
961
|
+
else:
|
962
|
+
return edge.LeftBdryArrow
|
963
|
+
|
964
|
+
def add_fan(self, edge, n):
|
965
|
+
"""
|
966
|
+
Adds a fan of ``n`` tetrahedra onto a boundary edge and rebuilds.
|
967
|
+
"""
|
968
|
+
if not edge.IntOrBdry == 'bdry':
|
969
|
+
return 0
|
970
|
+
a = edge.LeftBdryArrow
|
971
|
+
b = edge.RightBdryArrow.reverse()
|
972
|
+
if n == 0:
|
973
|
+
a.glue(b)
|
974
|
+
return 1
|
975
|
+
new = self.new_arrows(n)
|
976
|
+
a.glue( new[0] )
|
977
|
+
for j in range(len(new) - 1):
|
978
|
+
new[j].glue( new[j + 1] )
|
979
|
+
new[-1].glue( b )
|
980
|
+
self.rebuild()
|
981
|
+
return 1
|
982
|
+
|
983
|
+
def split_star(self,edge):
|
984
|
+
"""
|
985
|
+
Subdivides the star of an edge e. If the edge has an embedded
|
986
|
+
star then this operation first subdivides the edge, producing
|
987
|
+
one new vertex and two new edges. Next each tetrahedron which
|
988
|
+
meets the edge is divided into two tetrahedra along a face
|
989
|
+
which is the join of the new vertex to the edge opposite to e.
|
990
|
+
The edge e must not be self-adjacent in any 2-simplex for this
|
991
|
+
operation to be possible. However, it is allowed for a
|
992
|
+
tetrahedron to have two opposite edges identified to e. In
|
993
|
+
this case the tetrahedron is split into four tetrahedra,
|
994
|
+
forming the join of two segments of length 2. In order to
|
995
|
+
deal with this situation we work our way around the edge
|
996
|
+
making the identifications as we go. The first time that we
|
997
|
+
encounter a corner of a certain tetrahedron it gets split into
|
998
|
+
two. Those two are glued into place and may be encountered
|
999
|
+
later in the process, at which time each of them get split in
|
1000
|
+
two.
|
1001
|
+
|
1002
|
+
Returns an arrow associated to the "top half" of the original edge
|
1003
|
+
and the "first" tetrahedron adjacent to that edge, or 0 if the edge
|
1004
|
+
is self-adjacent.
|
1005
|
+
"""
|
1006
|
+
|
1007
|
+
if edge.selfadjacent():
|
1008
|
+
return 0
|
1009
|
+
# Collect the garbage as we go -- some of the new tets may
|
1010
|
+
# turn into garbage later on.
|
1011
|
+
garbage = []
|
1012
|
+
# Remember where we started.
|
1013
|
+
first_arrow = edge.get_arrow().next()
|
1014
|
+
first_bottom,first_top = self.new_arrows(2)
|
1015
|
+
a = first_arrow.copy()
|
1016
|
+
bottom = first_bottom.copy()
|
1017
|
+
top = first_top.copy()
|
1018
|
+
# Work around the edge.
|
1019
|
+
while 1:
|
1020
|
+
garbage.append(a.Tetrahedron)
|
1021
|
+
# Glue the two new tetrahedra together.
|
1022
|
+
bottom.glue(top)
|
1023
|
+
# Attach the top face of our new pair.
|
1024
|
+
a.opposite()
|
1025
|
+
above = a.glued()
|
1026
|
+
if above.is_null():
|
1027
|
+
# This may mean that our first tetrahedron had opposite edges attached
|
1028
|
+
# to our edge. We are splitting it for the second time, which will
|
1029
|
+
# cause first_top and first_bottom to get dumped onto the garbage heap.
|
1030
|
+
# We have to create a new first_top or first_bottom here, or we won't
|
1031
|
+
# be able to close everything up at the end. (On the other hand, we
|
1032
|
+
# may just have found a boundary face.)
|
1033
|
+
check = a.copy().opposite().reverse()
|
1034
|
+
new_first = top.copy().opposite().reverse()
|
1035
|
+
if check == first_top:
|
1036
|
+
first_top = new_first
|
1037
|
+
elif check == first_bottom:
|
1038
|
+
first_bottom = new_first
|
1039
|
+
else:
|
1040
|
+
top.glue(above)
|
1041
|
+
# Attach the bottom face of the new pair.
|
1042
|
+
bottom.reverse()
|
1043
|
+
a.reverse()
|
1044
|
+
below = a.glued()
|
1045
|
+
if below.is_null():
|
1046
|
+
# See comment above.
|
1047
|
+
check = a.copy().opposite().reverse()
|
1048
|
+
new_first = bottom.copy().opposite().reverse()
|
1049
|
+
if check == first_bottom:
|
1050
|
+
first_bottom = new_first
|
1051
|
+
elif check == first_top:
|
1052
|
+
first_top = new_first
|
1053
|
+
else:
|
1054
|
+
bottom.glue(below)
|
1055
|
+
bottom.reverse()
|
1056
|
+
# Now move on around the edge.
|
1057
|
+
a.reverse()
|
1058
|
+
a.opposite()
|
1059
|
+
a.next()
|
1060
|
+
if a == first_arrow:
|
1061
|
+
break
|
1062
|
+
next_bottom, next_top = self.new_arrows(2)
|
1063
|
+
top.opposite()
|
1064
|
+
bottom.opposite()
|
1065
|
+
top.glue(next_top)
|
1066
|
+
bottom.glue(next_bottom)
|
1067
|
+
top = next_top.opposite()
|
1068
|
+
bottom = next_bottom.opposite()
|
1069
|
+
# OK. We are back to the beginning. Close it up.
|
1070
|
+
top.opposite()
|
1071
|
+
bottom.opposite()
|
1072
|
+
top.glue(first_top.opposite())
|
1073
|
+
bottom.glue(first_bottom.opposite())
|
1074
|
+
# Clean up the garbage.
|
1075
|
+
for tet in garbage:
|
1076
|
+
self.delete_tet(tet)
|
1077
|
+
self.rebuild()
|
1078
|
+
return first_top
|
1079
|
+
|
1080
|
+
def smash_star(self, edge):
|
1081
|
+
"""
|
1082
|
+
If an edge joins distinct vertices and has an embedded open
|
1083
|
+
star then the following method will smash each 3-simplex in
|
1084
|
+
the star down to a 2-simplex, and smash the edge to a vertex,
|
1085
|
+
reducing the number of vertices by 1. Returns ``True`` on
|
1086
|
+
success, ``False`` on failure.
|
1087
|
+
"""
|
1088
|
+
if not edge.distinct() or edge.Vertices[0] == edge.Vertices[1]:
|
1089
|
+
return False
|
1090
|
+
start = edge.get_arrow()
|
1091
|
+
a = start.copy()
|
1092
|
+
garbage = []
|
1093
|
+
while 1:
|
1094
|
+
garbage.append(a.Tetrahedron)
|
1095
|
+
top = a.opposite().glued()
|
1096
|
+
bottom = a.reverse().glued().reverse()
|
1097
|
+
bottom.glue(top)
|
1098
|
+
a.reverse().opposite().next()
|
1099
|
+
if a == start:
|
1100
|
+
break
|
1101
|
+
for tet in garbage:
|
1102
|
+
self.delete_tet(tet)
|
1103
|
+
self.rebuild()
|
1104
|
+
return True
|
1105
|
+
|
1106
|
+
def smash_all_edges(self):
|
1107
|
+
"""
|
1108
|
+
Collapse edges to reduce the number of vertices as much as
|
1109
|
+
possible. Returns whether the number of vertices has been
|
1110
|
+
reduced to one.
|
1111
|
+
"""
|
1112
|
+
success = True
|
1113
|
+
while len(self.Vertices) > 1 and success:
|
1114
|
+
success = False
|
1115
|
+
edges = sorted(self.Edges, key=lambda E:E.valence(), reverse=True)
|
1116
|
+
edges = self.Edges
|
1117
|
+
for edge in edges:
|
1118
|
+
if self.smash_star(edge):
|
1119
|
+
success = True
|
1120
|
+
break
|
1121
|
+
|
1122
|
+
return len(self.Vertices) == 1
|
1123
|
+
|
1124
|
+
def replace_star(self, arrow, top_arrows, bottom_arrows):
|
1125
|
+
"""
|
1126
|
+
This method takes an arrow and replaces its star with
|
1127
|
+
other_complex attaching that complex via top_arrows and
|
1128
|
+
bottom_arrows where: Let a be the arrow defining the same
|
1129
|
+
directed edge as arrow which is the ith such arrow counting
|
1130
|
+
around the star. Then a.glued() is glued to top_arrow[i] and
|
1131
|
+
a.reverse().glued() is glued to bottom_arrow[i].
|
1132
|
+
|
1133
|
+
NOTE: If it fails, you need to delete any tets that you were
|
1134
|
+
trying to add.
|
1135
|
+
"""
|
1136
|
+
|
1137
|
+
edge = arrow.Tetrahedron.Class[arrow.Edge]
|
1138
|
+
a = arrow.copy().opposite()
|
1139
|
+
|
1140
|
+
# check to make sure that the replacement will work
|
1141
|
+
|
1142
|
+
if not edge.IntOrBdry == 'int':
|
1143
|
+
return None
|
1144
|
+
if not edge.distinct():
|
1145
|
+
return None
|
1146
|
+
valence = edge.valence()
|
1147
|
+
if len(top_arrows) != valence or len(bottom_arrows) != valence:
|
1148
|
+
return None
|
1149
|
+
|
1150
|
+
# Attach other_complex to manifold replace star of arrow
|
1151
|
+
#
|
1152
|
+
# It's important that we do things incrementally as follows in
|
1153
|
+
# case two outside faces of the star are glued together.
|
1154
|
+
|
1155
|
+
for i in range(valence):
|
1156
|
+
top_arrows[i].glue(a.glued())
|
1157
|
+
a.reverse()
|
1158
|
+
bottom_arrows[i].glue(a.glued())
|
1159
|
+
a.reverse()
|
1160
|
+
|
1161
|
+
# Now advance a to represent the same edge but in the next
|
1162
|
+
# tetrahedra in the star.
|
1163
|
+
a.opposite()
|
1164
|
+
a.next()
|
1165
|
+
a.opposite()
|
1166
|
+
|
1167
|
+
# Delete old star
|
1168
|
+
|
1169
|
+
for corner in edge.Corners:
|
1170
|
+
self.delete_tet(corner.Tetrahedron)
|
1171
|
+
|
1172
|
+
# Rebuild mcomplex
|
1173
|
+
self.build_edge_classes()
|
1174
|
+
self.orient()
|
1175
|
+
|
1176
|
+
return True
|
1177
|
+
|
1178
|
+
def suspension_of_polygon(self, num_sides_of_polygon):
|
1179
|
+
"""
|
1180
|
+
This method adds the suspension of a triangulation of a
|
1181
|
+
polygon to self.Tetrahedra and returns::
|
1182
|
+
|
1183
|
+
(top_arrows, bottom_arrows)
|
1184
|
+
|
1185
|
+
Currently the choice of triangulation of the polygon is one
|
1186
|
+
that is the cone over an edge. Probably this should be
|
1187
|
+
generalized. top_arrows and bottom arrows are for gluing in
|
1188
|
+
this complex via the method ``replace_star``.
|
1189
|
+
"""
|
1190
|
+
top_tets = self.new_tets(num_sides_of_polygon - 2)
|
1191
|
+
bottom_tets = self.new_tets(num_sides_of_polygon - 2)
|
1192
|
+
n = len(top_tets)
|
1193
|
+
|
1194
|
+
# glue top and bottom together
|
1195
|
+
for i in range(n):
|
1196
|
+
top_tets[i].attach( F3, bottom_tets[i], (0, 2, 1, 3) )
|
1197
|
+
|
1198
|
+
# glue each tet to its neighbor
|
1199
|
+
|
1200
|
+
for i in range(n-1):
|
1201
|
+
top_tets[i].attach(F0, top_tets[i+1], (1, 0, 2, 3) )
|
1202
|
+
bottom_tets[i].attach(F0, bottom_tets[i+1], (2, 1 , 0, 3) )
|
1203
|
+
|
1204
|
+
# make arrows
|
1205
|
+
|
1206
|
+
top_arrows = [ Arrow( comp(E13), F1, top_tets[0]) ]
|
1207
|
+
bottom_arrows = [ Arrow( comp(E23), F2, bottom_tets[0]) ]
|
1208
|
+
for i in range(n):
|
1209
|
+
top_arrows.append(Arrow( comp(E23), F2, top_tets[i]))
|
1210
|
+
bottom_arrows.append(Arrow( comp(E13), F1, bottom_tets[i]))
|
1211
|
+
|
1212
|
+
top_arrows.append(Arrow(comp(E03), F0, top_tets[i]))
|
1213
|
+
bottom_arrows.append(Arrow(comp(E03), F0, bottom_tets[i]))
|
1214
|
+
|
1215
|
+
return (top_arrows, bottom_arrows)
|
1216
|
+
|
1217
|
+
def save(self, filename, format="snappy"):
|
1218
|
+
"""
|
1219
|
+
Nontypical example showing saving to a string buffer:
|
1220
|
+
|
1221
|
+
>>> import io
|
1222
|
+
>>> buffer = io.StringIO()
|
1223
|
+
>>> T = Mcomplex('v3551')
|
1224
|
+
>>> T.save(buffer, 'snappy')
|
1225
|
+
>>> T.save(buffer, 'geo')
|
1226
|
+
>>> T.save(buffer, 'spine')
|
1227
|
+
>>> len(buffer.getvalue())
|
1228
|
+
1936
|
1229
|
+
"""
|
1230
|
+
if not hasattr(filename, 'write'):
|
1231
|
+
file = open(filename, 'w')
|
1232
|
+
close = True
|
1233
|
+
else:
|
1234
|
+
file = filename
|
1235
|
+
close = False
|
1236
|
+
if format == "snappy":
|
1237
|
+
files.write_SnapPea_file(self, file)
|
1238
|
+
elif format == "geo":
|
1239
|
+
files.write_geo_file(self, file)
|
1240
|
+
elif format == "spine":
|
1241
|
+
files.write_spine_file(self, file)
|
1242
|
+
if close:
|
1243
|
+
file.close()
|
1244
|
+
|
1245
|
+
def _snappea_file_contents(self):
|
1246
|
+
data = io.StringIO()
|
1247
|
+
data.name = 'from_t3m'
|
1248
|
+
files.write_SnapPea_file(self, data)
|
1249
|
+
return data.getvalue()
|
1250
|
+
|
1251
|
+
def snappy_triangulation(self, remove_finite_vertices=True):
|
1252
|
+
"""
|
1253
|
+
>>> Mcomplex('4_1').snappy_manifold().homology()
|
1254
|
+
Z
|
1255
|
+
|
1256
|
+
WARNING: Code implicitly assumes all vertex links are orientable.
|
1257
|
+
"""
|
1258
|
+
# We don't assume that the indices of the Tetraheda are equal
|
1259
|
+
# to range(len(self))
|
1260
|
+
tet_to_index = {T:i for i, T in enumerate(self.Tetrahedra)}
|
1261
|
+
|
1262
|
+
# Initially set all to -1, which corresponds to a finite vertex
|
1263
|
+
to_cusp_index = {vertex:-1 for vertex in self.Vertices}
|
1264
|
+
torus_cusps = 0
|
1265
|
+
for vertex in self.Vertices:
|
1266
|
+
g = vertex.link_genus()
|
1267
|
+
if g > 1:
|
1268
|
+
raise ValueError('Link of vertex has genus more than 1.')
|
1269
|
+
if g == 1:
|
1270
|
+
to_cusp_index[vertex] = torus_cusps
|
1271
|
+
torus_cusps += 1
|
1272
|
+
|
1273
|
+
tet_data, cusp_indices, peripheral_curves = [], [], []
|
1274
|
+
|
1275
|
+
for tet in self.Tetrahedra:
|
1276
|
+
neighbors, perms = [], []
|
1277
|
+
for face in TwoSubsimplices:
|
1278
|
+
if tet.Neighbor[face] is None:
|
1279
|
+
raise ValueError('SnapPy triangulations cannot have boundary')
|
1280
|
+
|
1281
|
+
neighbor = tet_to_index[tet.Neighbor[face]]
|
1282
|
+
perm = tet.Gluing[face].tuple()
|
1283
|
+
neighbors.append(neighbor)
|
1284
|
+
perms.append(perm)
|
1285
|
+
tet_data.append((neighbors, perms))
|
1286
|
+
|
1287
|
+
cusp_indices.append([to_cusp_index[tet.Class[vert]]
|
1288
|
+
for vert in ZeroSubsimplices])
|
1289
|
+
if hasattr(tet, 'PeripheralCurves'):
|
1290
|
+
for curve in tet.PeripheralCurves:
|
1291
|
+
for sheet in curve:
|
1292
|
+
one_curve_data = []
|
1293
|
+
for v in ZeroSubsimplices:
|
1294
|
+
for f in TwoSubsimplices:
|
1295
|
+
one_curve_data.append(sheet[v][f])
|
1296
|
+
peripheral_curves.append(one_curve_data)
|
1297
|
+
else:
|
1298
|
+
for i in range(4):
|
1299
|
+
peripheral_curves.append(16*[0])
|
1300
|
+
|
1301
|
+
M = snappy.Triangulation('empty')
|
1302
|
+
M._from_tetrahedra_gluing_data(tetrahedra_data=tet_data,
|
1303
|
+
num_or_cusps=torus_cusps,
|
1304
|
+
num_nonor_cusps=0,
|
1305
|
+
cusp_indices=cusp_indices,
|
1306
|
+
peripheral_curves=peripheral_curves,
|
1307
|
+
remove_finite_vertices=remove_finite_vertices)
|
1308
|
+
return M
|
1309
|
+
|
1310
|
+
def snappy_manifold(self):
|
1311
|
+
return self.snappy_triangulation().with_hyperbolic_structure()
|
1312
|
+
|
1313
|
+
def isosig(self):
|
1314
|
+
contents = self._snappea_file_contents()
|
1315
|
+
T = snappy.Triangulation(contents, remove_finite_vertices=False)
|
1316
|
+
return T.triangulation_isosig(decorated=False)
|
1317
|
+
|
1318
|
+
def regina_triangulation(self):
|
1319
|
+
"""
|
1320
|
+
>>> M = Mcomplex('K14n1234')
|
1321
|
+
>>> try:
|
1322
|
+
... T = M.regina_triangulation()
|
1323
|
+
... assert M.isosig() == T.isoSig()
|
1324
|
+
... except ImportError:
|
1325
|
+
... pass
|
1326
|
+
"""
|
1327
|
+
try:
|
1328
|
+
import regina
|
1329
|
+
except ImportError:
|
1330
|
+
raise ImportError('Regina module not available')
|
1331
|
+
|
1332
|
+
T = regina.Triangulation3()
|
1333
|
+
regina_tets = {tet:T.newTetrahedron() for tet in self}
|
1334
|
+
self.rebuild()
|
1335
|
+
for face in self.Faces:
|
1336
|
+
if face.IntOrBdry == 'int':
|
1337
|
+
corner = face.Corners[0]
|
1338
|
+
tet0 = corner.Tetrahedron
|
1339
|
+
face0 = corner.Subsimplex
|
1340
|
+
tet1 = tet0.Neighbor[face0]
|
1341
|
+
perm = tet0.Gluing[face0]
|
1342
|
+
|
1343
|
+
r_tet0 = regina_tets[tet0]
|
1344
|
+
r_tet1 = regina_tets[tet1]
|
1345
|
+
r_face = FaceIndex[face0]
|
1346
|
+
r_perm = regina.Perm4(*perm.tuple())
|
1347
|
+
r_tet0.join(r_face, r_tet1, r_perm)
|
1348
|
+
|
1349
|
+
return T
|
1350
|
+
|
1351
|
+
def boundary_maps(self):
|
1352
|
+
"""
|
1353
|
+
The boundary maps in the homology chain complex of the
|
1354
|
+
underlying cell-complex of a Mcomplex.
|
1355
|
+
|
1356
|
+
>>> M = Mcomplex('o9_12345')
|
1357
|
+
>>> len(M.boundary_maps()) == 3
|
1358
|
+
True
|
1359
|
+
"""
|
1360
|
+
return homology.boundary_maps(self)
|
1361
|
+
|
1362
|
+
def isomorphisms_to(self, other, orientation_preserving=False, at_most_one=False):
|
1363
|
+
"""
|
1364
|
+
Return the list of isomorphisms between the MComplexes M and N.
|
1365
|
+
If `at_most_one` is `True`, only returns the first one found (but
|
1366
|
+
still as a list).
|
1367
|
+
|
1368
|
+
>>> tri_data = [([0,1,0,1], [(2,1,0,3), (0,3,2,1), (2,1,0,3), (0,1,3,2)]),
|
1369
|
+
... ([1,1,0,0], [(1,0,2,3), (1,0,2,3), (0,1,3,2), (0,3,2,1)])]
|
1370
|
+
>>> M = Mcomplex(tri_data)
|
1371
|
+
>>> N = Mcomplex(M.isosig())
|
1372
|
+
>>> isos = M.isomorphisms_to(N); len(isos)
|
1373
|
+
4
|
1374
|
+
>>> isos[0]
|
1375
|
+
{0: [tet0, (0, 2, 1, 3)], 1: [tet1, (0, 2, 1, 3)]}
|
1376
|
+
>>> len(M.isomorphisms_to(N, orientation_preserving=True))
|
1377
|
+
2
|
1378
|
+
>>> M.two_to_three(Arrow(E01, F3, M[0])); M.rebuild()
|
1379
|
+
True
|
1380
|
+
>>> len(M), len(N)
|
1381
|
+
(3, 2)
|
1382
|
+
>>> M.isomorphisms_to(N)
|
1383
|
+
[]
|
1384
|
+
>>> F = Mcomplex('m004')
|
1385
|
+
>>> N.isomorphisms_to(F)
|
1386
|
+
[]
|
1387
|
+
>>> N = Mcomplex(M.isosig())
|
1388
|
+
>>> M.isomorphisms_to(N, at_most_one=True)[0]
|
1389
|
+
{0: [tet1, (0, 2, 3, 1)], 1: [tet2, (0, 2, 3, 1)], 2: [tet0, (0, 3, 1, 2)]}
|
1390
|
+
>>> M = Mcomplex(tri_data)
|
1391
|
+
>>> M.two_to_three(Arrow(E01, F3, M[0])); M.two_to_three(Arrow(E01, F3, M[1]))
|
1392
|
+
True
|
1393
|
+
True
|
1394
|
+
>>> M.rebuild()
|
1395
|
+
>>> len(M) == 4
|
1396
|
+
True
|
1397
|
+
>>> N = Mcomplex(M.isosig())
|
1398
|
+
>>> M.isomorphisms_to(N, at_most_one=True)[0] # doctest: +NORMALIZE_WHITESPACE
|
1399
|
+
{0: [tet0, (1, 3, 0, 2)], 1: [tet1, (3, 0, 1, 2)],
|
1400
|
+
2: [tet3, (2, 0, 3, 1)], 3: [tet2, (3, 1, 2, 0)]}
|
1401
|
+
"""
|
1402
|
+
M, N = self, other
|
1403
|
+
if not isinstance(N, Mcomplex):
|
1404
|
+
raise ValueError('The other triangulation must be an Mcomplex')
|
1405
|
+
|
1406
|
+
if len(M) != len(N):
|
1407
|
+
return []
|
1408
|
+
t_M0 = M[0]
|
1409
|
+
|
1410
|
+
if orientation_preserving:
|
1411
|
+
if not (M.is_oriented() and N.is_oriented()):
|
1412
|
+
raise ValueError('Asked for orientation preserving isomorphisms '
|
1413
|
+
'of unoriented triangulations')
|
1414
|
+
permutations = list(Perm4.A4()) # even perms only
|
1415
|
+
else:
|
1416
|
+
permutations = list(Perm4.S4())
|
1417
|
+
|
1418
|
+
isomorphisms = []
|
1419
|
+
# We will try and build an isomorphism from M to N that sends t_M0 to t_N0
|
1420
|
+
for t_N0 in N:
|
1421
|
+
# for each way t_M can be identified with t_N
|
1422
|
+
for perm in permutations:
|
1423
|
+
# initially the map is not defined
|
1424
|
+
iso = {k:None for k in range(len(M))}
|
1425
|
+
# set up first map t_M -> t_N
|
1426
|
+
# temporary way of encoding gluing.
|
1427
|
+
iso[0] = [t_N0, perm]
|
1428
|
+
tet_queue = [t_M0]
|
1429
|
+
while tet_queue != []:
|
1430
|
+
t_M = tet_queue.pop()
|
1431
|
+
t_N = iso[t_M.Index][0]
|
1432
|
+
perm = iso[t_M.Index][1]
|
1433
|
+
|
1434
|
+
# Now, for each face F of t_0, package tet that meets
|
1435
|
+
# t_0 along F in list neighbors
|
1436
|
+
neighbors_M = [t_M.Neighbor[face] for face in TwoSubsimplices]
|
1437
|
+
# Need info in N too.
|
1438
|
+
neighbors_N = [t_N.Neighbor[perm.image(face)] for face in TwoSubsimplices]
|
1439
|
+
|
1440
|
+
# record gluings for each face
|
1441
|
+
gluings_M = [t_M.Gluing[face] for face in TwoSubsimplices]
|
1442
|
+
gluings_N = [t_N.Gluing[perm.image(face)] for face in TwoSubsimplices]
|
1443
|
+
# check compatibility
|
1444
|
+
maps = [gluings_N[k]*perm*inv(gluings_M[k]) for k in [0,1,2,3]]
|
1445
|
+
|
1446
|
+
# now we try and update iso and hope there are no
|
1447
|
+
# incompatibilities
|
1448
|
+
for i in range(len(neighbors_M)):
|
1449
|
+
t = neighbors_M[i]
|
1450
|
+
s = neighbors_N[i]
|
1451
|
+
map = maps[i]
|
1452
|
+
|
1453
|
+
if iso[t.Index] is not None:
|
1454
|
+
if iso[t.Index][0] != s or iso[t.Index][1].tuple() != map.tuple():
|
1455
|
+
# not an iso!
|
1456
|
+
iso = {k:None for k in range(len(M))} # reset iso
|
1457
|
+
tet_queue = [] # clear queue
|
1458
|
+
break
|
1459
|
+
else:
|
1460
|
+
# iso[t] hasn't been set, so we set it and
|
1461
|
+
# move forward with it on the queue
|
1462
|
+
iso[t.Index] = [s,map]
|
1463
|
+
tet_queue = tet_queue + [t]
|
1464
|
+
|
1465
|
+
# did we succeed, or do we need to reset everything
|
1466
|
+
if None not in list(iso.values()): # we succeed!
|
1467
|
+
isomorphisms.append(iso.copy())
|
1468
|
+
if at_most_one:
|
1469
|
+
return isomorphisms
|
1470
|
+
# otherwise, we failed and the loop goes on
|
1471
|
+
|
1472
|
+
return isomorphisms
|
1473
|
+
|
1474
|
+
|
1475
|
+
def tets_from_data(fake_tets):
|
1476
|
+
"""
|
1477
|
+
Takes a list where the ith element represents the gluing data
|
1478
|
+
for the ith tetraherda::
|
1479
|
+
|
1480
|
+
( [Neighbors], [Glueings] )
|
1481
|
+
|
1482
|
+
and creates the corresponding glued Tetraherda.
|
1483
|
+
"""
|
1484
|
+
fake_tets = fake_tets
|
1485
|
+
num_tets = len(fake_tets)
|
1486
|
+
tets = [Tetrahedron() for i in range(num_tets)]
|
1487
|
+
for i in range(num_tets):
|
1488
|
+
neighbors, perms = fake_tets[i]
|
1489
|
+
for k in range(4):
|
1490
|
+
tets[i].attach(TwoSubsimplices[k], tets[neighbors[k]], perms[k])
|
1491
|
+
return tets
|
1492
|
+
|
1493
|
+
|
1494
|
+
def read_geo_file(filename):
|
1495
|
+
return Mcomplex(tets_from_data(files.read_geo_file(filename)))
|
1496
|
+
|
1497
|
+
|
1498
|
+
def read_SnapPea_file(filename):
|
1499
|
+
return Mcomplex(tets_from_data(files.read_SnapPea_file(filename)))
|