snappy 3.3__cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snappy/CyOpenGL.cpython-310-aarch64-linux-gnu.so +0 -0
- snappy/SnapPy.cpython-310-aarch64-linux-gnu.so +0 -0
- snappy/SnapPy.ico +0 -0
- snappy/SnapPy.png +0 -0
- snappy/SnapPyHP.cpython-310-aarch64-linux-gnu.so +0 -0
- snappy/__init__.py +534 -0
- snappy/app.py +604 -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 +280 -0
- snappy/cusps/cusp_area_matrix.py +98 -0
- snappy/cusps/cusp_areas_from_matrix.py +96 -0
- snappy/cusps/maximal_cusp_area_matrix.py +136 -0
- snappy/cusps/short_slopes_for_cusp.py +217 -0
- snappy/cusps/test.py +22 -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 +717 -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/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 +52 -0
- snappy/doc/_sources/credits.rst.txt +81 -0
- snappy/doc/_sources/development.rst.txt +261 -0
- snappy/doc/_sources/index.rst.txt +215 -0
- snappy/doc/_sources/installing.rst.txt +249 -0
- snappy/doc/_sources/manifold.rst.txt +6 -0
- snappy/doc/_sources/manifoldhp.rst.txt +46 -0
- snappy/doc/_sources/news.rst.txt +425 -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 +906 -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 +149 -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 +192 -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 +635 -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 +453 -0
- snappy/doc/credits.html +184 -0
- snappy/doc/development.html +385 -0
- snappy/doc/doc-latest/additional_classes.html +1500 -0
- snappy/doc/doc-latest/bugs.html +132 -0
- snappy/doc/doc-latest/censuses.html +453 -0
- snappy/doc/doc-latest/credits.html +184 -0
- snappy/doc/doc-latest/development.html +385 -0
- snappy/doc/doc-latest/genindex.html +1349 -0
- snappy/doc/doc-latest/index.html +287 -0
- snappy/doc/doc-latest/installing.html +346 -0
- snappy/doc/doc-latest/manifold.html +3632 -0
- snappy/doc/doc-latest/manifoldhp.html +180 -0
- snappy/doc/doc-latest/news.html +438 -0
- snappy/doc/doc-latest/objects.inv +0 -0
- snappy/doc/doc-latest/other.html +160 -0
- snappy/doc/doc-latest/platonic_census.html +376 -0
- snappy/doc/doc-latest/plink.html +210 -0
- snappy/doc/doc-latest/ptolemy.html +253 -0
- snappy/doc/doc-latest/ptolemy_classes.html +1144 -0
- snappy/doc/doc-latest/ptolemy_examples1.html +409 -0
- snappy/doc/doc-latest/ptolemy_examples2.html +471 -0
- snappy/doc/doc-latest/ptolemy_examples3.html +414 -0
- snappy/doc/doc-latest/ptolemy_examples4.html +195 -0
- snappy/doc/doc-latest/ptolemy_prelim.html +248 -0
- snappy/doc/doc-latest/py-modindex.html +165 -0
- snappy/doc/doc-latest/screenshots.html +141 -0
- snappy/doc/doc-latest/search.html +135 -0
- snappy/doc/doc-latest/searchindex.js +1 -0
- snappy/doc/doc-latest/snap.html +202 -0
- snappy/doc/doc-latest/snappy.html +181 -0
- snappy/doc/doc-latest/spherogram.html +1346 -0
- snappy/doc/doc-latest/todo.html +166 -0
- snappy/doc/doc-latest/triangulation.html +1676 -0
- snappy/doc/doc-latest/tutorial.html +159 -0
- snappy/doc/doc-latest/verify.html +330 -0
- snappy/doc/doc-latest/verify_internals.html +1235 -0
- snappy/doc/genindex.html +1349 -0
- snappy/doc/index.html +287 -0
- snappy/doc/installing.html +346 -0
- snappy/doc/manifold.html +3632 -0
- snappy/doc/manifoldhp.html +180 -0
- snappy/doc/news.html +438 -0
- snappy/doc/objects.inv +0 -0
- snappy/doc/other.html +160 -0
- snappy/doc/platonic_census.html +376 -0
- snappy/doc/plink.html +210 -0
- snappy/doc/ptolemy.html +253 -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 +1346 -0
- snappy/doc/todo.html +166 -0
- snappy/doc/triangulation.html +1676 -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 +132 -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 +198 -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 +130 -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 +691 -0
- snappy/geometric_structure/cusp_neighborhood/cusp_cross_section_base.py +480 -0
- snappy/geometric_structure/cusp_neighborhood/exceptions.py +41 -0
- snappy/geometric_structure/cusp_neighborhood/real_cusp_cross_section.py +294 -0
- snappy/geometric_structure/cusp_neighborhood/tiles_for_cusp_neighborhood.py +156 -0
- snappy/geometric_structure/cusp_neighborhood/vertices.py +35 -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_representatives.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 +106 -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 +128 -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 +259 -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 +389 -0
- snappy/len_spec/__init__.py +609 -0
- snappy/len_spec/geodesic_info.py +129 -0
- snappy/len_spec/geodesic_key_info_dict.py +116 -0
- snappy/len_spec/geodesic_piece.py +146 -0
- snappy/len_spec/geometric_structure.py +182 -0
- snappy/len_spec/geometry.py +136 -0
- snappy/len_spec/length_spectrum_geodesic_info.py +185 -0
- snappy/len_spec/spine.py +128 -0
- snappy/len_spec/test.py +24 -0
- snappy/len_spec/test_cases.py +69 -0
- snappy/len_spec/tile.py +276 -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/margulis/__init__.py +332 -0
- snappy/margulis/cusp_neighborhood_neighborhood.py +66 -0
- snappy/margulis/geodesic_neighborhood.py +152 -0
- snappy/margulis/margulis_info.py +21 -0
- snappy/margulis/mu_from_neighborhood_pair.py +175 -0
- snappy/margulis/neighborhood.py +29 -0
- snappy/margulis/test.py +22 -0
- snappy/math_basics.py +187 -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 +856 -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 +995 -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 +124 -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 +119 -0
- snappy/settings.py +407 -0
- snappy/shell.py +53 -0
- snappy/snap/__init__.py +117 -0
- snappy/snap/character_varieties.py +375 -0
- snappy/snap/find_field.py +372 -0
- snappy/snap/fox_milnor.py +271 -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 +716 -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/__init__.py +194 -0
- snappy/snap/slice_obs_HKL/basics.py +236 -0
- snappy/snap/slice_obs_HKL/direct.py +217 -0
- snappy/snap/slice_obs_HKL/poly_norm.py +212 -0
- snappy/snap/slice_obs_HKL/rep_theory.py +424 -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 +139 -0
- snappy/snap/utilities.py +288 -0
- snappy/test.py +213 -0
- snappy/test_cases.py +263 -0
- snappy/testing.py +131 -0
- snappy/tiling/__init__.py +2 -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/quotient_dict.py +70 -0
- snappy/tiling/real_hash_dict.py +164 -0
- snappy/tiling/test.py +23 -0
- snappy/tiling/tile.py +224 -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-310-aarch64-linux-gnu.so +0 -0
- snappy/upper_halfspace/__init__.py +146 -0
- snappy/upper_halfspace/ideal_point.py +29 -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/real_algebra.py +286 -0
- snappy/verify/shapes.py +25 -0
- snappy/verify/square_extensions.py +1005 -0
- snappy/verify/test.py +72 -0
- snappy/verify/volume.py +128 -0
- snappy/version.py +2 -0
- snappy-3.3.dist-info/METADATA +58 -0
- snappy-3.3.dist-info/RECORD +541 -0
- snappy-3.3.dist-info/WHEEL +6 -0
- snappy-3.3.dist-info/entry_points.txt +2 -0
- snappy-3.3.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)))
|