snappy 3.0.3__cp38-cp38-macosx_11_0_arm64.whl → 3.2__cp38-cp38-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-38-darwin.so +0 -0
- snappy/SnapPy.cpython-38-darwin.so +0 -0
- snappy/SnapPyHP.cpython-38-darwin.so +0 -0
- snappy/__init__.py +373 -426
- snappy/app.py +240 -75
- snappy/app_menus.py +93 -78
- snappy/browser.py +87 -63
- snappy/cache.py +5 -8
- snappy/canonical.py +249 -0
- snappy/{verify/cusp_shapes.py → cusps/__init__.py} +11 -19
- snappy/cusps/cusp_area_matrix.py +101 -0
- snappy/{verify/cusp_areas.py → cusps/cusp_areas_from_matrix.py} +39 -54
- 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 +40 -31
- snappy/db_utilities.py +13 -14
- snappy/decorated_isosig.py +377 -133
- snappy/dev/extended_ptolemy/complexVolumesClosed.py +42 -9
- snappy/dev/extended_ptolemy/extended.py +32 -25
- snappy/dev/extended_ptolemy/giac_rur.py +23 -8
- snappy/dev/extended_ptolemy/phc_wrapper.py +10 -10
- snappy/dev/vericlosed/computeApproxHyperbolicStructureOrb.py +2 -1
- snappy/dev/vericlosed/gimbalLoopFinder.py +5 -5
- snappy/dev/vericlosed/hyperbolicStructure.py +3 -3
- snappy/dev/vericlosed/oneVertexTruncatedComplex.py +2 -2
- snappy/dev/vericlosed/truncatedComplex.py +3 -2
- snappy/dev/vericlosed/verifyHyperbolicStructureEngine.py +4 -3
- 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/o9_00000_systole_paper_plane.jpg +0 -0
- snappy/doc/_images/o9_00000_systole_paper_plane_closer.jpg +0 -0
- snappy/doc/_sources/additional_classes.rst.txt +1 -0
- snappy/doc/_sources/credits.rst.txt +6 -1
- snappy/doc/_sources/development.rst.txt +69 -50
- snappy/doc/_sources/index.rst.txt +101 -66
- snappy/doc/_sources/installing.rst.txt +148 -165
- snappy/doc/_sources/news.rst.txt +136 -32
- snappy/doc/_sources/ptolemy.rst.txt +1 -1
- snappy/doc/_sources/ptolemy_examples1.rst.txt +9 -8
- snappy/doc/_sources/ptolemy_examples2.rst.txt +3 -3
- snappy/doc/_sources/ptolemy_examples3.rst.txt +14 -14
- snappy/doc/_sources/ptolemy_prelim.rst.txt +1 -1
- snappy/doc/_sources/snap.rst.txt +2 -2
- snappy/doc/_sources/snappy.rst.txt +1 -1
- snappy/doc/_sources/triangulation.rst.txt +3 -2
- snappy/doc/_sources/verify.rst.txt +89 -29
- snappy/doc/_sources/verify_internals.rst.txt +5 -16
- 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 +47 -27
- 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 +107 -274
- snappy/doc/_static/documentation_options.js +6 -5
- 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 -2
- 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 +3 -101
- snappy/doc/_static/pygments.css +1 -0
- snappy/doc/_static/searchtools.js +489 -398
- 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 +688 -263
- snappy/doc/bugs.html +107 -94
- snappy/doc/censuses.html +155 -127
- snappy/doc/credits.html +115 -104
- snappy/doc/development.html +184 -146
- snappy/doc/genindex.html +287 -204
- snappy/doc/index.html +189 -150
- snappy/doc/installing.html +259 -266
- snappy/doc/manifold.html +1626 -592
- snappy/doc/manifoldhp.html +119 -105
- snappy/doc/news.html +198 -104
- snappy/doc/objects.inv +0 -0
- snappy/doc/other.html +117 -105
- snappy/doc/platonic_census.html +161 -114
- snappy/doc/plink.html +113 -105
- snappy/doc/ptolemy.html +131 -108
- snappy/doc/ptolemy_classes.html +242 -223
- snappy/doc/ptolemy_examples1.html +144 -130
- snappy/doc/ptolemy_examples2.html +141 -129
- snappy/doc/ptolemy_examples3.html +148 -132
- snappy/doc/ptolemy_examples4.html +131 -111
- snappy/doc/ptolemy_prelim.html +162 -138
- snappy/doc/py-modindex.html +104 -69
- snappy/doc/screenshots.html +117 -108
- snappy/doc/search.html +115 -84
- snappy/doc/searchindex.js +1 -1
- snappy/doc/snap.html +109 -96
- snappy/doc/snappy.html +134 -97
- snappy/doc/spherogram.html +259 -187
- snappy/doc/todo.html +107 -94
- snappy/doc/triangulation.html +1380 -111
- snappy/doc/tutorial.html +107 -94
- snappy/doc/verify.html +194 -125
- snappy/doc/verify_internals.html +248 -686
- 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 +23 -3
- snappy/export_stl.py +20 -14
- 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/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 +36 -36
- snappy/horoviewer.py +50 -48
- 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/{infodialog.py → infowindow.py} +32 -33
- 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/__init__.py +1 -1
- snappy/math_basics.py +176 -0
- snappy/matrix.py +525 -0
- snappy/number.py +97 -21
- snappy/numeric_output_checker.py +37 -27
- snappy/pari.py +30 -69
- snappy/phone_home.py +25 -20
- snappy/polyviewer.py +39 -37
- snappy/ptolemy/__init__.py +4 -6
- snappy/ptolemy/component.py +14 -12
- snappy/ptolemy/coordinates.py +312 -295
- snappy/ptolemy/fieldExtensions.py +14 -12
- snappy/ptolemy/findLoops.py +43 -31
- snappy/ptolemy/geometricRep.py +24 -26
- snappy/ptolemy/homology.py +12 -7
- snappy/ptolemy/manifoldMethods.py +69 -70
- snappy/ptolemy/matrix.py +65 -26
- snappy/ptolemy/numericalSolutionsToGroebnerBasis.py +18 -14
- snappy/ptolemy/polynomial.py +125 -119
- snappy/ptolemy/processComponents.py +36 -30
- snappy/ptolemy/processFileBase.py +79 -18
- snappy/ptolemy/processFileDispatch.py +13 -14
- snappy/ptolemy/processMagmaFile.py +44 -39
- snappy/ptolemy/processRurFile.py +18 -11
- snappy/ptolemy/ptolemyGeneralizedObstructionClass.py +20 -17
- snappy/ptolemy/ptolemyObstructionClass.py +13 -17
- snappy/ptolemy/ptolemyVariety.py +190 -121
- snappy/ptolemy/ptolemyVarietyPrimeIdealGroebnerBasis.py +20 -19
- snappy/ptolemy/reginaWrapper.py +25 -29
- snappy/ptolemy/rur.py +6 -14
- snappy/ptolemy/solutionsToPrimeIdealGroebnerBasis.py +27 -22
- snappy/ptolemy/test.py +247 -188
- snappy/ptolemy/utilities.py +41 -43
- 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 +10 -6
- snappy/raytracing/eyeball.py +123 -0
- snappy/raytracing/finite_raytracing_data.py +48 -38
- snappy/raytracing/finite_viewer.py +218 -210
- 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 +152 -40
- snappy/raytracing/hyperboloid_navigation.py +102 -52
- snappy/raytracing/hyperboloid_utilities.py +114 -261
- snappy/raytracing/ideal_raytracing_data.py +256 -179
- snappy/raytracing/inside_viewer.py +522 -253
- snappy/raytracing/pack.py +22 -0
- snappy/raytracing/raytracing_data.py +46 -34
- snappy/raytracing/raytracing_view.py +190 -109
- snappy/raytracing/shaders/Eye.png +0 -0
- snappy/raytracing/shaders/NonGeometric.png +0 -0
- snappy/raytracing/shaders/__init__.py +60 -4
- snappy/raytracing/shaders/fragment.glsl +575 -148
- 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 +32 -29
- snappy/raytracing/zoom_slider/test.py +2 -0
- snappy/sage_helper.py +69 -123
- snappy/{preferences.py → settings.py} +167 -145
- snappy/shell.py +4 -0
- snappy/snap/__init__.py +12 -8
- snappy/snap/character_varieties.py +24 -18
- snappy/snap/find_field.py +35 -34
- snappy/snap/fundamental_polyhedron.py +99 -85
- snappy/snap/generators.py +6 -8
- snappy/snap/interval_reps.py +18 -6
- snappy/snap/kernel_structures.py +8 -3
- snappy/snap/mcomplex_base.py +1 -2
- snappy/snap/nsagetools.py +107 -53
- snappy/snap/peripheral/__init__.py +1 -1
- snappy/snap/peripheral/dual_cellulation.py +15 -7
- snappy/snap/peripheral/link.py +20 -16
- snappy/snap/peripheral/peripheral.py +22 -14
- snappy/snap/peripheral/surface.py +47 -50
- snappy/snap/peripheral/test.py +8 -8
- snappy/snap/polished_reps.py +65 -40
- snappy/snap/shapes.py +41 -22
- snappy/snap/slice_obs_HKL.py +64 -25
- snappy/snap/t3mlite/arrow.py +88 -51
- snappy/snap/t3mlite/corner.py +5 -6
- snappy/snap/t3mlite/edge.py +32 -21
- snappy/snap/t3mlite/face.py +7 -9
- snappy/snap/t3mlite/files.py +31 -23
- snappy/snap/t3mlite/homology.py +14 -10
- snappy/snap/t3mlite/linalg.py +158 -56
- snappy/snap/t3mlite/mcomplex.py +739 -291
- snappy/snap/t3mlite/perm4.py +236 -84
- snappy/snap/t3mlite/setup.py +9 -10
- snappy/snap/t3mlite/simplex.py +65 -48
- snappy/snap/t3mlite/spun.py +42 -30
- snappy/snap/t3mlite/surface.py +45 -45
- snappy/snap/t3mlite/test.py +3 -0
- snappy/snap/t3mlite/test_vs_regina.py +17 -13
- snappy/snap/t3mlite/tetrahedron.py +25 -24
- snappy/snap/t3mlite/vertex.py +8 -13
- snappy/snap/test.py +45 -52
- snappy/snap/utilities.py +66 -65
- snappy/test.py +155 -158
- 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 +313 -203
- snappy/twister/main.py +1 -8
- snappy/twister/twister_core.cpython-38-darwin.so +0 -0
- snappy/upper_halfspace/__init__.py +146 -0
- snappy/upper_halfspace/ideal_point.py +26 -0
- snappy/verify/__init__.py +4 -8
- snappy/verify/{verifyCanonical.py → canonical.py} +114 -97
- snappy/verify/complex_volume/__init__.py +3 -2
- snappy/verify/complex_volume/adjust_torsion.py +13 -11
- snappy/verify/complex_volume/closed.py +29 -24
- snappy/verify/complex_volume/compute_ptolemys.py +8 -6
- snappy/verify/complex_volume/cusped.py +10 -9
- snappy/verify/complex_volume/extended_bloch.py +14 -12
- snappy/verify/{cuspTranslations.py → cusp_translations.py} +15 -14
- snappy/verify/edge_equations.py +80 -0
- snappy/verify/exceptions.py +23 -56
- snappy/verify/{verifyHyperbolicity.py → hyperbolicity.py} +19 -15
- snappy/verify/interval_newton_shapes_engine.py +51 -211
- snappy/verify/interval_tree.py +27 -25
- snappy/verify/krawczyk_shapes_engine.py +47 -50
- snappy/verify/maximal_cusp_area_matrix/__init__.py +17 -86
- snappy/verify/maximal_cusp_area_matrix/cusp_tiling_engine.py +58 -48
- snappy/verify/maximal_cusp_area_matrix/cusp_translate_engine.py +53 -57
- snappy/verify/{realAlgebra.py → real_algebra.py} +26 -20
- snappy/verify/shapes.py +10 -7
- snappy/verify/short_slopes.py +41 -42
- snappy/verify/{squareExtensions.py → square_extensions.py} +96 -92
- snappy/verify/test.py +59 -57
- snappy/verify/upper_halfspace/extended_matrix.py +5 -5
- snappy/verify/upper_halfspace/finite_point.py +44 -31
- snappy/verify/upper_halfspace/ideal_point.py +69 -57
- snappy/verify/volume.py +15 -12
- snappy/version.py +2 -3
- {snappy-3.0.3.dist-info → snappy-3.2.dist-info}/METADATA +14 -12
- snappy-3.2.dist-info/RECORD +503 -0
- {snappy-3.0.3.dist-info → snappy-3.2.dist-info}/WHEEL +1 -1
- {snappy-3.0.3.dist-info → snappy-3.2.dist-info}/entry_points.txt +0 -1
- {snappy-3.0.3.dist-info → snappy-3.2.dist-info}/top_level.txt +10 -1
- snappy/doc/_sources/verify_canon.rst.txt +0 -90
- snappy/doc/_static/classic.css +0 -266
- snappy/doc/_static/jquery-3.5.1.js +0 -10872
- snappy/doc/_static/sidebar.js +0 -159
- snappy/doc/_static/underscore-1.13.1.js +0 -2042
- snappy/doc/_static/underscore.js +0 -6
- snappy/doc/verify_canon.html +0 -283
- snappy/ppm_to_png.py +0 -243
- snappy/togl/__init__.py +0 -3
- snappy/togl/darwin-tk8.6/Togl2.1/LICENSE +0 -28
- snappy/togl/darwin-tk8.6/Togl2.1/libTogl2.1.dylib +0 -0
- snappy/togl/darwin-tk8.6/Togl2.1/pkgIndex.tcl +0 -5
- snappy/togl/linux2-x86_64-tk8.6/Togl2.1/LICENSE +0 -28
- snappy/togl/linux2-x86_64-tk8.6/Togl2.1/libTogl2.1.so +0 -0
- snappy/togl/linux2-x86_64-tk8.6/Togl2.1/pkgIndex.tcl +0 -5
- snappy/togl/win32VC-tk8.6/Togl2.1/LICENSE +0 -28
- snappy/togl/win32VC-tk8.6/Togl2.1/Togl21.dll +0 -0
- snappy/togl/win32VC-tk8.6/Togl2.1/Togl21.lib +0 -0
- snappy/togl/win32VC-tk8.6/Togl2.1/pkgIndex.tcl +0 -6
- snappy/togl/win32VC-x86_64-tk8.6/Togl2.1/LICENSE +0 -28
- snappy/togl/win32VC-x86_64-tk8.6/Togl2.1/Togl21.dll +0 -0
- snappy/togl/win32VC-x86_64-tk8.6/Togl2.1/Togl21.lib +0 -0
- snappy/togl/win32VC-x86_64-tk8.6/Togl2.1/pkgIndex.tcl +0 -6
- snappy/verify/cuspCrossSection.py +0 -1413
- snappy/verify/mathHelpers.py +0 -64
- snappy-3.0.3.dist-info/RECORD +0 -360
snappy/snap/t3mlite/mcomplex.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
#$Id: mcomplex.py,v 1.14 2009/08/20 15:58:58 t3m Exp $
|
1
|
+
# $Id: mcomplex.py,v 1.14 2009/08/20 15:58:58 t3m Exp $
|
3
2
|
# t3m - software for studying triangulated 3-manifolds
|
4
3
|
# Copyright (C) 2002 Marc Culler, Nathan Dunfield and others
|
5
4
|
#
|
@@ -15,10 +14,13 @@ from .face import Face
|
|
15
14
|
from .edge import Edge
|
16
15
|
from .vertex import Vertex
|
17
16
|
from .surface import Surface, SpunSurface, ClosedSurface, ClosedSurfaceInCusped
|
17
|
+
from .perm4 import Perm4, inv
|
18
18
|
from . import files
|
19
19
|
from . import linalg
|
20
20
|
from . import homology
|
21
|
-
import
|
21
|
+
import sys
|
22
|
+
import random
|
23
|
+
import io
|
22
24
|
|
23
25
|
try:
|
24
26
|
import snappy
|
@@ -45,15 +47,36 @@ Shift = {E01:(-1,1,0), E02:(1,0,-1), E21:(0,-1,1),
|
|
45
47
|
VertexVector = {V0:(1,0,0,0), V1:(0,1,0,0),
|
46
48
|
V2:(0,0,1,0), V3:(0,0,0,1)}
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
+
|
51
68
|
|
52
69
|
class Insanity(Exception):
|
53
70
|
pass
|
54
71
|
|
72
|
+
|
55
73
|
class Mcomplex:
|
56
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
|
+
|
57
80
|
>>> T = Mcomplex([Tetrahedron()])
|
58
81
|
>>> len(T), len(T.Vertices)
|
59
82
|
(1, 4)
|
@@ -69,7 +92,7 @@ class Mcomplex:
|
|
69
92
|
def __init__(self, tetrahedron_list=None):
|
70
93
|
if tetrahedron_list is None:
|
71
94
|
tetrahedron_list = []
|
72
|
-
elif isinstance(tetrahedron_list, str) and snappy
|
95
|
+
elif isinstance(tetrahedron_list, str) and snappy is None:
|
73
96
|
tetrahedron_list = tets_from_data(files.read_SnapPea_file(file_name=tetrahedron_list))
|
74
97
|
elif snappy:
|
75
98
|
if isinstance(tetrahedron_list, str):
|
@@ -83,14 +106,14 @@ class Mcomplex:
|
|
83
106
|
tetrahedron_list = tets_from_data(tetrahedron_list)
|
84
107
|
|
85
108
|
self.Tetrahedra = tetrahedron_list
|
86
|
-
self.Edges
|
87
|
-
self.Faces
|
88
|
-
self.Vertices
|
89
|
-
self.NormalSurfaces
|
109
|
+
self.Edges = []
|
110
|
+
self.Faces = []
|
111
|
+
self.Vertices = []
|
112
|
+
self.NormalSurfaces = []
|
90
113
|
self.AlmostNormalSurfaces = []
|
91
114
|
self.build()
|
92
115
|
|
93
|
-
def copy(self, base_arrow
|
116
|
+
def copy(self, base_arrow=None):
|
94
117
|
new_tets = []
|
95
118
|
new_to_old = {}
|
96
119
|
old_to_new = {}
|
@@ -104,12 +127,12 @@ class Mcomplex:
|
|
104
127
|
new_tet.attach(face,
|
105
128
|
old_to_new[new_to_old[new_tet].Neighbor[face]],
|
106
129
|
new_to_old[new_tet].Gluing[face].tuple())
|
107
|
-
if base_arrow
|
108
|
-
return
|
130
|
+
if base_arrow is None:
|
131
|
+
return self.__class__(new_tets)
|
109
132
|
else:
|
110
133
|
new_arrow = base_arrow.copy()
|
111
134
|
new_arrow.Tetrahedron = old_to_new[base_arrow.Tetrahedron]
|
112
|
-
return (
|
135
|
+
return (self.__class__(new_tets), new_arrow)
|
113
136
|
|
114
137
|
def build(self):
|
115
138
|
for i in range(len(self.Tetrahedra)):
|
@@ -137,60 +160,60 @@ class Mcomplex:
|
|
137
160
|
def add_tet(self, tet):
|
138
161
|
self.Tetrahedra.append(tet)
|
139
162
|
|
140
|
-
# Remove the face, edge and vertex classes of a tetrahedron. This
|
141
|
-
# should destroy the faces, edges and vertices that meet the
|
142
|
-
# tetrahedron. A call to build_face_classes, build_edge_classes or
|
143
|
-
# build_vertex_classes will then rebuild the neighborhood without
|
144
|
-
# having to rebuild the whole manifold. #
|
145
|
-
|
146
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
|
+
"""
|
147
171
|
for two_subsimplex in TwoSubsimplices:
|
148
172
|
face = tet.Class[two_subsimplex]
|
149
|
-
if
|
173
|
+
if face is not None:
|
150
174
|
face.erase()
|
151
175
|
try:
|
152
176
|
self.Faces.remove(face)
|
153
177
|
except ValueError:
|
154
178
|
pass
|
179
|
+
|
155
180
|
for one_subsimplex in OneSubsimplices:
|
156
181
|
edge = tet.Class[one_subsimplex]
|
157
|
-
if
|
182
|
+
if edge is not None:
|
158
183
|
edge.erase()
|
159
184
|
try:
|
160
185
|
self.Edges.remove(edge)
|
161
186
|
except ValueError:
|
162
187
|
pass
|
188
|
+
|
163
189
|
for zero_subsimplex in ZeroSubsimplices:
|
164
190
|
vertex = tet.Class[zero_subsimplex]
|
165
|
-
if
|
191
|
+
if vertex is not None:
|
166
192
|
vertex.erase()
|
167
193
|
try:
|
168
194
|
self.Vertices.remove(vertex)
|
169
195
|
except ValueError:
|
170
196
|
pass
|
171
197
|
|
172
|
-
# Clear a tetrahedron, then remove it from the Tetrahedron list.
|
173
|
-
#
|
174
198
|
def delete_tet(self, tet):
|
199
|
+
"""
|
200
|
+
Clear a tetrahedron, then remove it from the Tetrahedron list.
|
201
|
+
"""
|
175
202
|
self.clear_tet(tet)
|
176
203
|
tet.erase()
|
177
204
|
self.Tetrahedra.remove(tet)
|
178
205
|
|
179
|
-
# Add one new tetrahedron and return one of its arrows.
|
180
|
-
|
181
206
|
def new_arrow(self):
|
207
|
+
"""
|
208
|
+
Add one new tetrahedron and return one of its arrows.
|
209
|
+
"""
|
182
210
|
tet = Tetrahedron()
|
183
211
|
self.add_tet(tet)
|
184
212
|
return Arrow(E01,F3,tet)
|
185
213
|
|
186
|
-
# Or, add a whole bunch of them.
|
187
|
-
#
|
188
214
|
def new_arrows(self,n):
|
189
215
|
return [self.new_arrow() for i in range(n)]
|
190
216
|
|
191
|
-
# Below two methods added June, 22 1999 by NMD
|
192
|
-
# Sometimes we might want to add tets without arrows
|
193
|
-
|
194
217
|
def new_tet(self):
|
195
218
|
tet = Tetrahedron()
|
196
219
|
self.add_tet(tet)
|
@@ -199,19 +222,40 @@ class Mcomplex:
|
|
199
222
|
def new_tets(self,n):
|
200
223
|
return [self.new_tet() for i in range(n)]
|
201
224
|
|
202
|
-
|
203
|
-
|
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
|
+
|
204
243
|
def __len__(self):
|
244
|
+
"""
|
245
|
+
Return the number of tetrahedra
|
246
|
+
"""
|
205
247
|
return len(self.Tetrahedra)
|
206
248
|
|
207
|
-
# M[i] refers to the ith Tetrahedron of the mcomplex M.
|
208
|
-
#
|
209
249
|
def __getitem__(self, index):
|
250
|
+
"""
|
251
|
+
M[i] refers to the ith Tetrahedron of the mcomplex M.
|
252
|
+
"""
|
210
253
|
return self.Tetrahedra[index]
|
211
254
|
|
212
|
-
# M.info() describes the Mcomplex.
|
213
|
-
#
|
214
255
|
def info(self, out=sys.stdout):
|
256
|
+
"""
|
257
|
+
M.info() describes the Mcomplex.
|
258
|
+
"""
|
215
259
|
try:
|
216
260
|
out.write( "Mcomplex with %d Tetrahedra\n\n" % len(self) )
|
217
261
|
for tet in self.Tetrahedra:
|
@@ -219,15 +263,16 @@ class Mcomplex:
|
|
219
263
|
out.write("\nEdges:\n")
|
220
264
|
for edge in self.Edges:
|
221
265
|
edge.info(out)
|
222
|
-
except
|
266
|
+
except OSError:
|
223
267
|
pass
|
224
268
|
|
225
|
-
# Construct the edge classes and compute valences.
|
226
|
-
#
|
227
269
|
def build_edge_classes(self):
|
270
|
+
"""
|
271
|
+
Construct the edge classes and compute valences.
|
272
|
+
"""
|
228
273
|
for tet in self.Tetrahedra:
|
229
274
|
for one_subsimplex in OneSubsimplices:
|
230
|
-
if ( tet.Class[one_subsimplex]
|
275
|
+
if ( tet.Class[one_subsimplex] is None ):
|
231
276
|
newEdge = Edge()
|
232
277
|
self.Edges.append(newEdge)
|
233
278
|
first_arrow = Arrow(one_subsimplex, RightFace[one_subsimplex], tet)
|
@@ -241,9 +286,9 @@ class Mcomplex:
|
|
241
286
|
# Record the corners and edge classes as we go.
|
242
287
|
newEdge._add_corner(a)
|
243
288
|
a.Tetrahedron.Class[a.Edge] = newEdge
|
244
|
-
if a.next()
|
245
|
-
|
246
|
-
|
289
|
+
if a.next() is None:
|
290
|
+
# We hit the boundary!
|
291
|
+
# Go back to the beginning and walk to the right.
|
247
292
|
# If this is our second boundary hit, we are done.
|
248
293
|
if not boundary_hits == 0:
|
249
294
|
newEdge.RightBdryArrow = a.copy()
|
@@ -270,12 +315,10 @@ class Mcomplex:
|
|
270
315
|
for i in range(len(self.Edges)):
|
271
316
|
self.Edges[i].Index = i
|
272
317
|
|
273
|
-
# Construct the vertices.
|
274
|
-
#
|
275
318
|
def build_vertex_classes(self):
|
276
319
|
for tet in self.Tetrahedra:
|
277
320
|
for zero_subsimplex in ZeroSubsimplices:
|
278
|
-
if ( tet.Class[zero_subsimplex]
|
321
|
+
if ( tet.Class[zero_subsimplex] is None ):
|
279
322
|
newVertex = Vertex()
|
280
323
|
self.Vertices.append(newVertex)
|
281
324
|
self.walk_vertex(newVertex,zero_subsimplex,tet)
|
@@ -283,7 +326,7 @@ class Mcomplex:
|
|
283
326
|
self.Vertices[i].Index = i
|
284
327
|
|
285
328
|
def walk_vertex(self,vertex,zero_subsimplex,tet):
|
286
|
-
if (tet.Class[zero_subsimplex]
|
329
|
+
if (tet.Class[zero_subsimplex] is not None ):
|
287
330
|
return
|
288
331
|
else:
|
289
332
|
tet.Class[zero_subsimplex] = vertex
|
@@ -291,20 +334,21 @@ class Mcomplex:
|
|
291
334
|
for two_subsimplex in TwoSubsimplices:
|
292
335
|
if ( is_subset(zero_subsimplex,two_subsimplex)
|
293
336
|
and
|
294
|
-
tet.Gluing[two_subsimplex]
|
337
|
+
tet.Gluing[two_subsimplex] is not None):
|
295
338
|
self.walk_vertex(vertex,
|
296
339
|
tet.Gluing[two_subsimplex].image(zero_subsimplex),
|
297
340
|
tet.Neighbor[two_subsimplex])
|
298
341
|
|
299
|
-
# Construct the 1-skeleton, i.e. record which edges are connected to
|
300
|
-
# which vertices. This assumes that Edges and Vertices have already been
|
301
|
-
# built.
|
302
|
-
#
|
303
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
|
+
"""
|
304
348
|
for edge in self.Edges:
|
305
349
|
tet = edge.Corners[0].Tetrahedron
|
306
350
|
one_subsimplex = edge.Corners[0].Subsimplex
|
307
|
-
tail
|
351
|
+
tail = tet.Class[Tail[one_subsimplex]]
|
308
352
|
head = tet.Class[Head[one_subsimplex]]
|
309
353
|
edge.Vertices = [tail , head]
|
310
354
|
tail.Edges.append(edge)
|
@@ -316,11 +360,13 @@ class Mcomplex:
|
|
316
360
|
if vertex.IntOrBdry == '':
|
317
361
|
vertex.IntOrBdry = 'int'
|
318
362
|
|
319
|
-
#Construct the faces.
|
320
363
|
def build_face_classes(self):
|
364
|
+
"""
|
365
|
+
Construct the faces.
|
366
|
+
"""
|
321
367
|
for tet in self.Tetrahedra:
|
322
368
|
for two_subsimplex in TwoSubsimplices:
|
323
|
-
if ( tet.Class[two_subsimplex]
|
369
|
+
if ( tet.Class[two_subsimplex] is None ):
|
324
370
|
newFace = Face()
|
325
371
|
self.Faces.append(newFace)
|
326
372
|
newFace.Corners.append(Corner(tet,two_subsimplex))
|
@@ -336,27 +382,28 @@ class Mcomplex:
|
|
336
382
|
for i in range(len(self.Faces)):
|
337
383
|
self.Faces[i].Index = i
|
338
384
|
|
339
|
-
#
|
340
|
-
# Orientation
|
341
|
-
#
|
342
|
-
# The simplification moves below assume that the Mcomplex is oriented.
|
343
|
-
# Yes, oriented, not just orientable. An Mcomplex has been oriented if
|
344
|
-
# all of the gluing permutations are odd. The orient method walks through
|
345
|
-
# the manifold reorienting tetrahedra to try to get all of the gluing
|
346
|
-
# permutations to be odd. Returns 1 on success, 0 if the manifold is
|
347
|
-
# not orientable.
|
348
|
-
#
|
349
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
|
+
"""
|
350
394
|
for tet in self.Tetrahedra:
|
351
395
|
tet.Checked = 0
|
352
396
|
self.walk_and_orient(self[0], 1)
|
353
397
|
self.rebuild()
|
398
|
+
return self.is_oriented()
|
399
|
+
|
400
|
+
def is_oriented(self):
|
354
401
|
for tet in self.Tetrahedra:
|
355
402
|
for two_subsimplex in TwoSubsimplices:
|
356
|
-
if (not tet.Neighbor[two_subsimplex]
|
403
|
+
if (not tet.Neighbor[two_subsimplex] is None
|
357
404
|
and tet.Gluing[two_subsimplex].sign() == 0):
|
358
|
-
return
|
359
|
-
return
|
405
|
+
return False
|
406
|
+
return True
|
360
407
|
|
361
408
|
def walk_and_orient(self, tet, sign):
|
362
409
|
if tet.Checked == 1:
|
@@ -365,14 +412,13 @@ class Mcomplex:
|
|
365
412
|
if sign == 0:
|
366
413
|
tet.reverse()
|
367
414
|
for ssimp in TwoSubsimplices:
|
368
|
-
if
|
415
|
+
if tet.Neighbor[ssimp] is not None:
|
369
416
|
self.walk_and_orient(tet.Neighbor[ssimp], tet.Gluing[ssimp].sign())
|
370
417
|
|
371
|
-
# Normal Surfaces
|
372
|
-
#
|
373
|
-
# NOTE: convention is that the ordered quads are (Q03, Q13, Q23).
|
374
|
-
|
375
418
|
def build_matrix(self):
|
419
|
+
"""
|
420
|
+
Convention is that the ordered quads are (Q03, Q13, Q23).
|
421
|
+
"""
|
376
422
|
int_edges = [edge for edge in self.Edges if edge.IntOrBdry == 'int']
|
377
423
|
self.QuadMatrix = linalg.Matrix(len(int_edges), 3*len(self))
|
378
424
|
for edge in int_edges:
|
@@ -392,6 +438,9 @@ class Mcomplex:
|
|
392
438
|
|
393
439
|
def find_normal_surfaces(self, modp=0, print_progress=False,
|
394
440
|
algorithm='FXrays'):
|
441
|
+
"""
|
442
|
+
Convention is that the ordered quads are (Q03, Q13, Q23).
|
443
|
+
"""
|
395
444
|
self.NormalSurfaces = []
|
396
445
|
self.build_matrix()
|
397
446
|
if algorithm == 'FXrays':
|
@@ -428,16 +477,13 @@ class Mcomplex:
|
|
428
477
|
else:
|
429
478
|
self.NormalSurfaces.append(Surface(self, coeff_vector))
|
430
479
|
|
431
|
-
|
432
|
-
# We need find_almost_normal_surfaces()
|
433
|
-
|
434
480
|
def normal_surface_info(self, out=sys.stdout):
|
435
481
|
try:
|
436
482
|
for surface in self.NormalSurfaces:
|
437
483
|
out.write("-------------------------------------\n\n")
|
438
484
|
surface.info(self, out)
|
439
485
|
out.write('\n')
|
440
|
-
except
|
486
|
+
except OSError:
|
441
487
|
pass
|
442
488
|
|
443
489
|
def almost_normal_surface_info(self, out=sys.stdout):
|
@@ -446,113 +492,214 @@ class Mcomplex:
|
|
446
492
|
out.write("-------------------------------------\n\n")
|
447
493
|
surface.info(self, out)
|
448
494
|
out.write('\n')
|
449
|
-
except
|
495
|
+
except OSError:
|
450
496
|
pass
|
451
497
|
|
452
|
-
#
|
453
|
-
#
|
454
|
-
#
|
455
|
-
#
|
456
|
-
#
|
457
|
-
#
|
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.
|
458
523
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
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()
|
463
542
|
b = a.glued()
|
464
|
-
if
|
465
|
-
|
466
|
-
|
467
|
-
|
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()
|
468
550
|
new = self.new_arrows(3)
|
469
551
|
for i in range(3):
|
470
|
-
new[i].glue(new[(i+1)%3])
|
552
|
+
new[i].glue(new[(i + 1) % 3])
|
471
553
|
a.reverse()
|
472
554
|
for c in new:
|
473
555
|
c.opposite().glue(a.glued())
|
474
556
|
c.reverse().glue(b.glued())
|
475
557
|
a.rotate(-1)
|
476
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)
|
477
563
|
self.delete_tet(a.Tetrahedron)
|
478
564
|
self.delete_tet(b.Tetrahedron)
|
479
|
-
|
565
|
+
if not unsafe_mode:
|
566
|
+
self.build_edge_classes()
|
480
567
|
if VERBOSE:
|
481
568
|
print('2->3')
|
482
569
|
print(self.EdgeValences)
|
483
|
-
|
484
|
-
|
570
|
+
if return_arrow:
|
571
|
+
return new[1].north_head().get_arrow()
|
572
|
+
else:
|
573
|
+
return True
|
485
574
|
|
486
|
-
|
487
|
-
# Returns 0 if the edge is a boundary edge.
|
488
|
-
#
|
489
|
-
def three_to_two(self, edge):
|
575
|
+
def _edge_permits_three_to_two(self, edge):
|
490
576
|
if not edge.IntOrBdry == 'int':
|
491
|
-
return
|
492
|
-
if edge.valence() != 3
|
493
|
-
return
|
494
|
-
|
495
|
-
|
496
|
-
|
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()
|
497
603
|
b = self.new_arrow()
|
498
604
|
c = self.new_arrow()
|
499
605
|
b.glue(c)
|
606
|
+
b_orig = b.copy()
|
500
607
|
b.reverse()
|
608
|
+
b_to_return = b.copy()
|
501
609
|
for i in range(3):
|
502
610
|
b.glue(a.opposite().glued())
|
503
611
|
c.glue(a.reverse().glued())
|
504
612
|
b.rotate(-1)
|
505
613
|
c.rotate(1)
|
506
614
|
a.reverse().opposite().next()
|
507
|
-
|
508
|
-
|
509
|
-
|
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()
|
510
629
|
if VERBOSE:
|
511
630
|
print('3->2')
|
512
631
|
print(self.EdgeValences)
|
513
|
-
|
632
|
+
if return_arrow:
|
633
|
+
return b_to_return
|
634
|
+
return True
|
514
635
|
|
515
|
-
|
516
|
-
|
517
|
-
#
|
518
|
-
def two_to_zero(self, edge):
|
636
|
+
def _arrow_permits_two_to_zero(self, arrow):
|
637
|
+
edge = arrow.axis()
|
519
638
|
if not edge.IntOrBdry == 'int':
|
520
|
-
return
|
521
|
-
if edge.valence() != 2
|
522
|
-
return
|
523
|
-
|
524
|
-
|
525
|
-
|
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)
|
526
667
|
b = a.glued()
|
527
668
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
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)
|
532
676
|
a.opposite().glued().reverse().glue(b.opposite().glued())
|
533
677
|
a.reverse().glued().reverse().glue(b.reverse().glued())
|
534
678
|
|
535
679
|
for corner in edge.Corners:
|
536
680
|
self.delete_tet(corner.Tetrahedron)
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
681
|
+
if not unsafe_mode:
|
682
|
+
self.build_edge_classes()
|
683
|
+
if VERBOSE:
|
684
|
+
print('2->0')
|
685
|
+
print(self.EdgeValences)
|
686
|
+
return True
|
542
687
|
|
543
|
-
# Blow up two adjacent faces into a pair of tetrahedra.
|
544
|
-
# The faces are specified by passing an arrow specifying the first face
|
545
|
-
# and an integer n. The second face is obtained by reversing the
|
546
|
-
# arrow and applying next() n times. Thus there are n faces between
|
547
|
-
# the two that are involved in the blow up. Returns 1 on success,
|
548
|
-
# 0 if the move cannot be performed.
|
549
|
-
#
|
550
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
|
+
"""
|
551
698
|
arrow2 = arrow1.copy().reverse()
|
552
699
|
count = 0
|
553
700
|
while count < gap:
|
554
|
-
if arrow2.next()
|
555
|
-
return
|
701
|
+
if arrow2.next() is None:
|
702
|
+
return False
|
556
703
|
count = count + 1
|
557
704
|
# Do we *also* need the old test, which was
|
558
705
|
# b.Tetrahedron == arrow1.Tetrahedron ?
|
@@ -573,35 +720,45 @@ class Mcomplex:
|
|
573
720
|
if VERBOSE:
|
574
721
|
print('0->2')
|
575
722
|
print(self.EdgeValences)
|
576
|
-
return
|
577
|
-
|
578
|
-
# Replace an edge of valence 4 by another diagonal of the octahedron
|
579
|
-
# formed by the star of the edge. There are two choices for this
|
580
|
-
# diagonal. If you care which one is used then pass an arrow
|
581
|
-
# representing the edge of valence four. The head of the arrow will
|
582
|
-
# be an endpoint of the new diagonal. If you don't care, just pass an
|
583
|
-
# edge. The choice of diagonal will then be made randomly. Returns 1
|
584
|
-
# on success, 0 if the move cannot be performed.
|
585
|
-
#
|
586
|
-
def four_to_four(self, edge_or_arrow):
|
587
|
-
if edge_or_arrow.__class__ == Edge:
|
588
|
-
edge = edge_or_arrow
|
589
|
-
a = Arrow(edge.Corners[0].Subsimplex,
|
590
|
-
LeftFace[edge.Corners[0].Subsimplex],
|
591
|
-
edge.Corners[0].Tetrahedron)
|
592
|
-
if random.randint(0,1) == 0:
|
593
|
-
a.reverse()
|
594
|
-
if edge_or_arrow.__class__ == Arrow:
|
595
|
-
a = edge_or_arrow
|
596
|
-
edge = a.Tetrahedron.Class[a.Edge]
|
723
|
+
return True
|
597
724
|
|
725
|
+
def _edge_permits_four_to_four(self, edge):
|
598
726
|
if not edge.IntOrBdry == 'int':
|
599
|
-
return
|
600
|
-
if edge.valence() != 4
|
601
|
-
return
|
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
|
+
|
602
758
|
c = self.new_arrows(4)
|
759
|
+
c_orig = [x.copy() for x in c]
|
603
760
|
for i in range(4):
|
604
|
-
c[i].glue(
|
761
|
+
c[i].glue(c[(i + 1) % 4])
|
605
762
|
b = a.glued().reverse()
|
606
763
|
c[0].opposite().glue(a.rotate(1).glued())
|
607
764
|
c[1].opposite().glue(b.rotate(-1).glued())
|
@@ -613,76 +770,138 @@ class Mcomplex:
|
|
613
770
|
c[1].reverse().glue(b.rotate(1).glued())
|
614
771
|
c[2].reverse().glue(b.rotate(1).glued())
|
615
772
|
c[3].reverse().glue(a.rotate(-1).glued())
|
773
|
+
|
774
|
+
self._four_to_four_move_hook(a_orig, c_orig)
|
616
775
|
for corner in edge.Corners:
|
617
776
|
self.delete_tet(corner.Tetrahedron)
|
618
|
-
|
619
|
-
if
|
620
|
-
|
621
|
-
|
622
|
-
|
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
|
623
806
|
|
624
807
|
def eliminate_valence_two(self):
|
625
|
-
|
626
|
-
|
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
|
627
814
|
while progress:
|
628
|
-
progress =
|
815
|
+
progress = False
|
629
816
|
for edge in self.Edges:
|
630
817
|
if edge.valence() == 2:
|
631
818
|
if self.two_to_zero(edge):
|
632
|
-
progress, did_simplify =
|
819
|
+
progress, did_simplify = True, True
|
633
820
|
break
|
634
821
|
return did_simplify
|
635
822
|
|
636
823
|
def eliminate_valence_three(self):
|
637
|
-
|
638
|
-
|
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
|
639
830
|
while progress:
|
640
|
-
progress =
|
831
|
+
progress = False
|
641
832
|
for edge in self.Edges:
|
642
833
|
if edge.valence() == 3:
|
643
834
|
if self.three_to_two(edge):
|
644
|
-
progress, did_simplify =
|
835
|
+
progress, did_simplify = True, True
|
645
836
|
break
|
646
837
|
return did_simplify
|
647
838
|
|
648
839
|
def easy_simplify(self):
|
649
|
-
|
650
|
-
|
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
|
651
858
|
while progress:
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
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
|
658
867
|
|
659
868
|
def jiggle(self):
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
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))
|
667
877
|
|
668
878
|
JIGGLE_LIMIT = 6
|
669
879
|
|
670
|
-
def simplify(self):
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
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():
|
679
897
|
break
|
680
898
|
self.eliminate_valence_two()
|
681
|
-
return
|
899
|
+
return len(self) < init_tet
|
682
900
|
|
683
|
-
|
684
|
-
|
685
|
-
|
901
|
+
def blowup(self, n):
|
902
|
+
"""
|
903
|
+
Do ``n`` randomly chosen ``two_to_three`` moves.
|
904
|
+
"""
|
686
905
|
for i in range(n):
|
687
906
|
rand_tet = self[ random.randint(0, len(self) - 1) ]
|
688
907
|
rand_face = TwoSubsimplices[random.randint(0,3)]
|
@@ -690,10 +909,12 @@ class Mcomplex:
|
|
690
909
|
self.eliminate_valence_two()
|
691
910
|
return len(self)
|
692
911
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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
|
+
|
697
918
|
for i in range(n):
|
698
919
|
rand_edge = self.Edges[ random.randint(0, len(self.Edges) - 1) ]
|
699
920
|
j = random.randint(0, len(rand_edge.Corners) - 1)
|
@@ -706,20 +927,33 @@ class Mcomplex:
|
|
706
927
|
self.eliminate_valence_three()
|
707
928
|
return len(self)
|
708
929
|
|
709
|
-
|
710
|
-
|
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))
|
711
946
|
self.simplify()
|
712
947
|
self.rebuild()
|
713
948
|
return len(self)
|
714
949
|
|
715
|
-
# Boundary Modifications:
|
716
|
-
#
|
717
|
-
# Find a boundary face adjoining a given boundary face.
|
718
|
-
# Given an Arrow representing a boundary face, return the Arrow
|
719
|
-
# representing the boundary face that shares the Arrow's Edge.
|
720
|
-
#
|
721
950
|
def bdry_neighbor(self, arrow):
|
722
|
-
|
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:
|
723
957
|
raise Insanity("That boundary face is not on the boundary!")
|
724
958
|
edge = arrow.Tetrahedron.Class[arrow.Edge]
|
725
959
|
if edge.LeftBdryArrow == arrow:
|
@@ -727,9 +961,10 @@ class Mcomplex:
|
|
727
961
|
else:
|
728
962
|
return edge.LeftBdryArrow
|
729
963
|
|
730
|
-
# Adds a "fan" of n tetrahedra onto a boundary edge and rebuilds.
|
731
|
-
#
|
732
964
|
def add_fan(self, edge, n):
|
965
|
+
"""
|
966
|
+
Adds a fan of ``n`` tetrahedra onto a boundary edge and rebuilds.
|
967
|
+
"""
|
733
968
|
if not edge.IntOrBdry == 'bdry':
|
734
969
|
return 0
|
735
970
|
a = edge.LeftBdryArrow
|
@@ -745,27 +980,30 @@ class Mcomplex:
|
|
745
980
|
self.rebuild()
|
746
981
|
return 1
|
747
982
|
|
748
|
-
# The following method subdivides the star of an edge e. If the
|
749
|
-
# edge has an embedded star then this operation first subdivides the
|
750
|
-
# edge, producing one new vertex and two new edges. Next each
|
751
|
-
# tetrahedron which meets the edge is divided into two tetrahedra
|
752
|
-
# along a face which is the join of the new vertex to the edge
|
753
|
-
# opposite to e. The edge e must not be self-adjacent in any
|
754
|
-
# 2-simplex for this operation to be possible. However, it is
|
755
|
-
# allowed for a tetrahedron to have two opposite edges identified
|
756
|
-
# to e. In this case the tetrahedron is split into four
|
757
|
-
# tetrahedra, forming the join of two segments of length 2. In
|
758
|
-
# order to deal with this situation we work our way around the edge
|
759
|
-
# making the identifications as we go. The first time that we
|
760
|
-
# encounter a corner of a certain tetrahedron it gets split into two.
|
761
|
-
# Those two are glued into place and may be encountered later in the
|
762
|
-
# process, at which time each of them get split in two.
|
763
|
-
#
|
764
|
-
# Returns an arrow associated to the "top half" of the original edge
|
765
|
-
# and the "first" tetrahedron adjacent to that edge, or 0 if the edge
|
766
|
-
# is self-adjacent.
|
767
|
-
|
768
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
|
+
|
769
1007
|
if edge.selfadjacent():
|
770
1008
|
return 0
|
771
1009
|
# Collect the garbage as we go -- some of the new tets may
|
@@ -839,16 +1077,16 @@ class Mcomplex:
|
|
839
1077
|
self.rebuild()
|
840
1078
|
return first_top
|
841
1079
|
|
842
|
-
# If an edge joins distinct vertices and has an embedded open star then
|
843
|
-
# the following method will smash each 3-simplex in the star down to a
|
844
|
-
# 2-simplex, and smash the edge to a vertex, reducing the number of
|
845
|
-
# vertices by 1. Returns 1 on success, 0 on failure.
|
846
|
-
|
847
1080
|
def smash_star(self, edge):
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
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
|
852
1090
|
start = edge.get_arrow()
|
853
1091
|
a = start.copy()
|
854
1092
|
garbage = []
|
@@ -863,33 +1101,53 @@ class Mcomplex:
|
|
863
1101
|
for tet in garbage:
|
864
1102
|
self.delete_tet(tet)
|
865
1103
|
self.rebuild()
|
866
|
-
return
|
867
|
-
|
868
|
-
# Functions below added by NMD June 22, 1999.
|
869
|
-
|
870
|
-
# The following method takes an arrow and replaces its star with
|
871
|
-
# other_complex attaching that complex via top_arrows and
|
872
|
-
# bottom_arrows where: Let a be the arrow defining the same
|
873
|
-
# directed edge as arrow which is the ith such arrow counting
|
874
|
-
# around the star. Then a.glued() is glued to top_arrow[i]
|
875
|
-
# and a.reverse().glued() is glued to bottom_arrow[i].
|
1104
|
+
return True
|
876
1105
|
|
877
|
-
|
878
|
-
|
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
|
879
1123
|
|
880
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
|
+
|
881
1137
|
edge = arrow.Tetrahedron.Class[arrow.Edge]
|
882
1138
|
a = arrow.copy().opposite()
|
883
1139
|
|
884
1140
|
# check to make sure that the replacement will work
|
885
1141
|
|
886
|
-
if not edge.IntOrBdry == 'int':
|
887
|
-
|
1142
|
+
if not edge.IntOrBdry == 'int':
|
1143
|
+
return None
|
1144
|
+
if not edge.distinct():
|
1145
|
+
return None
|
888
1146
|
valence = edge.valence()
|
889
1147
|
if len(top_arrows) != valence or len(bottom_arrows) != valence:
|
890
1148
|
return None
|
891
1149
|
|
892
|
-
#
|
1150
|
+
# Attach other_complex to manifold replace star of arrow
|
893
1151
|
#
|
894
1152
|
# It's important that we do things incrementally as follows in
|
895
1153
|
# case two outside faces of the star are glued together.
|
@@ -900,7 +1158,7 @@ class Mcomplex:
|
|
900
1158
|
bottom_arrows[i].glue(a.glued())
|
901
1159
|
a.reverse()
|
902
1160
|
|
903
|
-
#
|
1161
|
+
# Now advance a to represent the same edge but in the next
|
904
1162
|
# tetrahedra in the star.
|
905
1163
|
a.opposite()
|
906
1164
|
a.next()
|
@@ -915,21 +1173,20 @@ class Mcomplex:
|
|
915
1173
|
self.build_edge_classes()
|
916
1174
|
self.orient()
|
917
1175
|
|
918
|
-
return
|
1176
|
+
return True
|
919
1177
|
|
920
|
-
|
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::
|
921
1182
|
|
922
|
-
|
923
|
-
# to self.Tetrahedra and returns
|
924
|
-
#
|
925
|
-
# (top_arrows, bottom_arrows)
|
926
|
-
#
|
927
|
-
# Currently the choice of triangulation of the
|
928
|
-
# polygon is one that is the cone over an edge. Probably this
|
929
|
-
# should be generalized. top_arrows and bottom arrows are for
|
930
|
-
# gluing in this complex via the method
|
1183
|
+
(top_arrows, bottom_arrows)
|
931
1184
|
|
932
|
-
|
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
|
+
"""
|
933
1190
|
top_tets = self.new_tets(num_sides_of_polygon - 2)
|
934
1191
|
bottom_tets = self.new_tets(num_sides_of_polygon - 2)
|
935
1192
|
n = len(top_tets)
|
@@ -991,27 +1248,105 @@ class Mcomplex:
|
|
991
1248
|
files.write_SnapPea_file(self, data)
|
992
1249
|
return data.getvalue()
|
993
1250
|
|
994
|
-
def snappy_triangulation(self):
|
1251
|
+
def snappy_triangulation(self, remove_finite_vertices=True):
|
995
1252
|
"""
|
996
1253
|
>>> Mcomplex('4_1').snappy_manifold().homology()
|
997
1254
|
Z
|
1255
|
+
|
1256
|
+
WARNING: Code implicitly assumes all vertex links are orientable.
|
998
1257
|
"""
|
999
|
-
|
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
|
1000
1309
|
|
1001
1310
|
def snappy_manifold(self):
|
1002
1311
|
return self.snappy_triangulation().with_hyperbolic_structure()
|
1003
1312
|
|
1004
1313
|
def isosig(self):
|
1005
|
-
|
1006
|
-
|
1314
|
+
contents = self._snappea_file_contents()
|
1315
|
+
T = snappy.Triangulation(contents, remove_finite_vertices=False)
|
1316
|
+
return T.triangulation_isosig(decorated=False)
|
1007
1317
|
|
1008
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
|
+
"""
|
1009
1327
|
try:
|
1010
1328
|
import regina
|
1011
1329
|
except ImportError:
|
1012
1330
|
raise ImportError('Regina module not available')
|
1013
|
-
|
1014
|
-
|
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
|
1015
1350
|
|
1016
1351
|
def boundary_maps(self):
|
1017
1352
|
"""
|
@@ -1024,15 +1359,128 @@ class Mcomplex:
|
|
1024
1359
|
"""
|
1025
1360
|
return homology.boundary_maps(self)
|
1026
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
|
1027
1473
|
|
1028
|
-
# Takes a list where the ith element represents the glueing data
|
1029
|
-
# for the ith tetraherda:
|
1030
|
-
#
|
1031
|
-
# ( [Neighbors], [Glueings] )
|
1032
|
-
#
|
1033
|
-
# and creates the corresponding Mcomplex
|
1034
1474
|
|
1035
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
|
+
"""
|
1036
1484
|
fake_tets = fake_tets
|
1037
1485
|
num_tets = len(fake_tets)
|
1038
1486
|
tets = [Tetrahedron() for i in range(num_tets)]
|