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,2290 @@
|
|
|
1
|
+
from .component import ZeroDimensionalComponent
|
|
2
|
+
from .rur import RUR
|
|
3
|
+
from . import matrix
|
|
4
|
+
from . import findLoops
|
|
5
|
+
from . import utilities
|
|
6
|
+
from ..sage_helper import _within_sage
|
|
7
|
+
from ..pari import Gen, pari
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PtolemyCannotBeCheckedError(Exception):
|
|
12
|
+
def __init__(self):
|
|
13
|
+
msg = (
|
|
14
|
+
"Use .cross_ratios().check_against_manifold(...) since checking "
|
|
15
|
+
"Ptolemy coordinates for non-trivial generalized obstruction "
|
|
16
|
+
"class is not supported.")
|
|
17
|
+
Exception.__init__(self, msg)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LogToCloseToBranchCutError(Exception):
|
|
21
|
+
"""
|
|
22
|
+
An exception raised when taking log(-x) for some real number x
|
|
23
|
+
Due to numerical inaccuracies, we cannot know in this case whether to take
|
|
24
|
+
-Pi or Pi as imaginary part.
|
|
25
|
+
"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RelationViolationError(Exception):
|
|
30
|
+
"""
|
|
31
|
+
An exception raised when some supposed relation doesn't hold exactly
|
|
32
|
+
or numerical.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, value, epsilon, comment):
|
|
36
|
+
self.value = value
|
|
37
|
+
self.epsilon = epsilon
|
|
38
|
+
self.comment = comment
|
|
39
|
+
|
|
40
|
+
def __str__(self):
|
|
41
|
+
r = self.comment + " is violated, "
|
|
42
|
+
r += "difference is %s" % self.value
|
|
43
|
+
if self.epsilon is None:
|
|
44
|
+
return r + " (exact values)"
|
|
45
|
+
return r + " (epsilon = %s)" % self.epsilon
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class NotPU21Representation:
|
|
49
|
+
"""
|
|
50
|
+
Returned by is_pu_2_1_representation if cross ratios do not fulfill
|
|
51
|
+
conditions to be a PU(2,1)-representation.
|
|
52
|
+
Contains the reason why cross ratios fail to do so.
|
|
53
|
+
Cast to bool evaluates to False.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, reason):
|
|
57
|
+
self.reason = reason
|
|
58
|
+
|
|
59
|
+
def __repr__(self):
|
|
60
|
+
return "NotPU21Representation(reason = %r)" % self.reason
|
|
61
|
+
|
|
62
|
+
def __bool__(self):
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
__nonzero__ = __bool__ # backwards compatibility python 2x
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NumericalMethodError(Exception):
|
|
69
|
+
def __init__(self, method):
|
|
70
|
+
self.method = method
|
|
71
|
+
|
|
72
|
+
def __str__(self):
|
|
73
|
+
return "Method %s only supported for numerical values" % self.method
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ExactMethodError(Exception):
|
|
77
|
+
def __init__(self, method):
|
|
78
|
+
self.method = method
|
|
79
|
+
|
|
80
|
+
def __str__(self):
|
|
81
|
+
return "Method %s only supported for exact values" % self.method
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _check_relation(value, epsilon, comment):
|
|
85
|
+
if epsilon is None:
|
|
86
|
+
if not value == 0:
|
|
87
|
+
raise RelationViolationError(value, epsilon, comment)
|
|
88
|
+
else:
|
|
89
|
+
if not abs(value) < epsilon:
|
|
90
|
+
raise RelationViolationError(value, epsilon, comment)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class PtolemyCoordinates(dict):
|
|
94
|
+
"""
|
|
95
|
+
Represents a solution of a Ptolemy variety as python dictionary.
|
|
96
|
+
|
|
97
|
+
=== Examples ===
|
|
98
|
+
|
|
99
|
+
Construct solution from magma output:
|
|
100
|
+
|
|
101
|
+
>>> from snappy.ptolemy.processMagmaFile import _magma_output_for_4_1__sl3, solutions_from_magma
|
|
102
|
+
>>> from snappy import Manifold
|
|
103
|
+
>>> solutions = solutions_from_magma(_magma_output_for_4_1__sl3)
|
|
104
|
+
>>> solution = solutions[2]
|
|
105
|
+
|
|
106
|
+
Access a Ptolemy coordinate:
|
|
107
|
+
|
|
108
|
+
>>> solution['c_2100_0']
|
|
109
|
+
1
|
|
110
|
+
|
|
111
|
+
>>> solution.number_field()
|
|
112
|
+
x^2 + x + 1
|
|
113
|
+
|
|
114
|
+
Solution is always 0 dimensional:
|
|
115
|
+
|
|
116
|
+
>>> solution.dimension
|
|
117
|
+
0
|
|
118
|
+
|
|
119
|
+
Check that it is really a solution, exactly:
|
|
120
|
+
|
|
121
|
+
>>> solution.check_against_manifold()
|
|
122
|
+
|
|
123
|
+
If the solution was not created through the ptolemy module
|
|
124
|
+
and thus not associated to a manifold, we need to explicitly
|
|
125
|
+
specify one:
|
|
126
|
+
|
|
127
|
+
>>> myDict = {}
|
|
128
|
+
>>> for key, value in solution.items():
|
|
129
|
+
... myDict[key] = value
|
|
130
|
+
>>> mysolution = PtolemyCoordinates(myDict)
|
|
131
|
+
>>> M = Manifold("4_1")
|
|
132
|
+
>>> solution.check_against_manifold(M)
|
|
133
|
+
|
|
134
|
+
Turn into (Galois conjugate) numerical solutions:
|
|
135
|
+
|
|
136
|
+
>>> old_precision = pari.set_real_precision(100) # with high precision
|
|
137
|
+
>>> numerical_solutions = solution.numerical()
|
|
138
|
+
|
|
139
|
+
Check that it is a solution, numerically:
|
|
140
|
+
|
|
141
|
+
>>> numerical_solutions[0].check_against_manifold(M, 1e-80)
|
|
142
|
+
>>> pari.set_real_precision(old_precision) # reset pari engine
|
|
143
|
+
100
|
|
144
|
+
|
|
145
|
+
Compute cross ratios from the Ptolemy coordinates (cross ratios
|
|
146
|
+
according to SnapPy convention, see help(solution.cross_ratios):
|
|
147
|
+
|
|
148
|
+
>>> cross = solution.cross_ratios()
|
|
149
|
+
>>> cross['z_0001_0']
|
|
150
|
+
Mod(-x, x^2 + x + 1)
|
|
151
|
+
|
|
152
|
+
Compute volumes:
|
|
153
|
+
|
|
154
|
+
>>> volumes = cross.volume_numerical()
|
|
155
|
+
|
|
156
|
+
Check that volume is 4 times the geometric one:
|
|
157
|
+
|
|
158
|
+
>>> volume = volumes[0].abs()
|
|
159
|
+
>>> diff = abs(4 * M.volume() - volume)
|
|
160
|
+
>>> diff < 1e-9
|
|
161
|
+
True
|
|
162
|
+
|
|
163
|
+
Compute flattenings:
|
|
164
|
+
|
|
165
|
+
>>> flattenings = solution.flattenings_numerical()
|
|
166
|
+
|
|
167
|
+
Compute complex volumes:
|
|
168
|
+
|
|
169
|
+
>>> cvols = [flattening.complex_volume() for flattening in flattenings]
|
|
170
|
+
>>> volume = cvols[0].real().abs()
|
|
171
|
+
>>> chernSimons = cvols[0].imag()
|
|
172
|
+
>>> diff = abs(4 * M.volume() - volume)
|
|
173
|
+
>>> diff < 1e-9
|
|
174
|
+
True
|
|
175
|
+
|
|
176
|
+
>>> from snappy import pari
|
|
177
|
+
>>> normalized = chernSimons * 6 / (pari('Pi')**2)
|
|
178
|
+
|
|
179
|
+
Check that Chern Simons is zero up to 6 torsion:
|
|
180
|
+
|
|
181
|
+
>>> normalized - normalized.round() < 1e-9
|
|
182
|
+
True
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, d, is_numerical=True, py_eval_section=None,
|
|
186
|
+
manifold_thunk=lambda : None,
|
|
187
|
+
non_trivial_generalized_obstruction_class=False):
|
|
188
|
+
|
|
189
|
+
self._manifold_thunk = manifold_thunk
|
|
190
|
+
|
|
191
|
+
self._is_numerical = is_numerical
|
|
192
|
+
self.dimension = 0
|
|
193
|
+
|
|
194
|
+
self._non_trivial_generalized_obstruction_class = (
|
|
195
|
+
non_trivial_generalized_obstruction_class)
|
|
196
|
+
processed_dict = d
|
|
197
|
+
|
|
198
|
+
if py_eval_section is not None:
|
|
199
|
+
# process the extra information that is given by
|
|
200
|
+
# ptolemyVariety's py_eval_section
|
|
201
|
+
|
|
202
|
+
processed_dict = py_eval_section['variable_dict'](d)
|
|
203
|
+
if py_eval_section.get(
|
|
204
|
+
'non_trivial_generalized_obstruction_class'):
|
|
205
|
+
self._non_trivial_generalized_obstruction_class = True
|
|
206
|
+
|
|
207
|
+
# Caches the matrices that label the short and long edges
|
|
208
|
+
# of the truncated simplices building the manifold
|
|
209
|
+
self._edge_cache = {}
|
|
210
|
+
|
|
211
|
+
# Caches the images of a fundamental group generator
|
|
212
|
+
self._matrix_cache = []
|
|
213
|
+
self._inverse_matrix_cache = []
|
|
214
|
+
|
|
215
|
+
super().__init__(processed_dict)
|
|
216
|
+
|
|
217
|
+
def __repr__(self):
|
|
218
|
+
dict_repr = dict.__repr__(self)
|
|
219
|
+
return "PtolemyCoordinates(%s, is_numerical = %r, ...)" % (
|
|
220
|
+
dict_repr, self._is_numerical)
|
|
221
|
+
|
|
222
|
+
def _repr_pretty_(self, p, cycle):
|
|
223
|
+
if cycle:
|
|
224
|
+
p.text('PtolemyCoordinates(...)')
|
|
225
|
+
else:
|
|
226
|
+
with p.group(4, 'PtolemyCoordinates(',')'):
|
|
227
|
+
p.breakable()
|
|
228
|
+
p.pretty(dict(self))
|
|
229
|
+
p.text(',')
|
|
230
|
+
p.breakable()
|
|
231
|
+
p.text('is_numerical = %r, ...' % self._is_numerical)
|
|
232
|
+
|
|
233
|
+
def get_manifold(self):
|
|
234
|
+
"""
|
|
235
|
+
Get the manifold for which this structure represents a solution
|
|
236
|
+
to the Ptolemy variety.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
return self._manifold_thunk()
|
|
240
|
+
|
|
241
|
+
def num_tetrahedra(self):
|
|
242
|
+
"""
|
|
243
|
+
The number of tetrahedra for which we have Ptolemy coordinates.
|
|
244
|
+
"""
|
|
245
|
+
return _num_tetrahedra(self)
|
|
246
|
+
|
|
247
|
+
def N(self):
|
|
248
|
+
"""
|
|
249
|
+
Get the *N* where these coordinates are for SL/PSL(*N*, **C**)-representations.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
N, has_obstruction = _N_and_has_obstruction_for_ptolemys(self)
|
|
253
|
+
return N
|
|
254
|
+
|
|
255
|
+
def has_obstruction(self):
|
|
256
|
+
"""
|
|
257
|
+
Whether the Ptolemy variety has legacy obstruction class that
|
|
258
|
+
modifies the Ptolemy relation to
|
|
259
|
+
"""
|
|
260
|
+
N, has_obstruction = _N_and_has_obstruction_for_ptolemys(self)
|
|
261
|
+
return has_obstruction
|
|
262
|
+
|
|
263
|
+
def number_field(self):
|
|
264
|
+
"""
|
|
265
|
+
For an exact solution, return the number_field spanned by the
|
|
266
|
+
Ptolemy coordinates. If number_field is Q, return None.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
if self._is_numerical:
|
|
270
|
+
raise ExactMethodError("number_field")
|
|
271
|
+
|
|
272
|
+
return _get_number_field(self)
|
|
273
|
+
|
|
274
|
+
def numerical(self):
|
|
275
|
+
"""
|
|
276
|
+
Turn exact solutions into numerical solutions using pari.
|
|
277
|
+
|
|
278
|
+
Take an exact solution:
|
|
279
|
+
|
|
280
|
+
>>> from snappy.ptolemy.processMagmaFile import _magma_output_for_4_1__sl3, solutions_from_magma
|
|
281
|
+
>>> solutions = solutions_from_magma(_magma_output_for_4_1__sl3)
|
|
282
|
+
>>> solution = solutions[2]
|
|
283
|
+
|
|
284
|
+
Turn into a numerical solution:
|
|
285
|
+
|
|
286
|
+
>>> old_precision = pari.set_real_precision(100) # with high precision
|
|
287
|
+
>>> numerical_solutions = solution.numerical()
|
|
288
|
+
>>> pari.set_real_precision(old_precision) # reset pari engine
|
|
289
|
+
100
|
|
290
|
+
|
|
291
|
+
Pick one of the Galois conjugates:
|
|
292
|
+
|
|
293
|
+
>>> numerical_solution = numerical_solutions[0]
|
|
294
|
+
>>> value = numerical_solution['c_1110_0']
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
if self._is_numerical:
|
|
298
|
+
return self
|
|
299
|
+
return ZeroDimensionalComponent(
|
|
300
|
+
[ PtolemyCoordinates(
|
|
301
|
+
d, is_numerical=True,
|
|
302
|
+
manifold_thunk=self._manifold_thunk,
|
|
303
|
+
non_trivial_generalized_obstruction_class=(
|
|
304
|
+
self._non_trivial_generalized_obstruction_class))
|
|
305
|
+
for d in _to_numerical(self) ])
|
|
306
|
+
|
|
307
|
+
def to_PUR(self):
|
|
308
|
+
"""
|
|
309
|
+
If any Ptolemy coordinates are given as Rational Univariate
|
|
310
|
+
Representation, convert them to Polynomial Univariate Representation and
|
|
311
|
+
return the result.
|
|
312
|
+
|
|
313
|
+
See to_PUR of RUR.
|
|
314
|
+
|
|
315
|
+
This conversion might lead to very large coefficients.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
return PtolemyCoordinates(
|
|
319
|
+
_apply_to_RURs(self, RUR.to_PUR),
|
|
320
|
+
is_numerical=self._is_numerical,
|
|
321
|
+
manifold_thunk=self._manifold_thunk,
|
|
322
|
+
non_trivial_generalized_obstruction_class=(
|
|
323
|
+
self._non_trivial_generalized_obstruction_class))
|
|
324
|
+
|
|
325
|
+
def multiply_terms_in_RUR(self):
|
|
326
|
+
"""
|
|
327
|
+
If a Ptolemy coordinate is given as Rational Univariate Representation
|
|
328
|
+
with numerator and denominator being a product, multiply the terms and
|
|
329
|
+
return the result.
|
|
330
|
+
|
|
331
|
+
See multiply_terms of RUR.
|
|
332
|
+
|
|
333
|
+
This loses information about how the numerator and denominator are
|
|
334
|
+
factorised.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
return PtolemyCoordinates(
|
|
338
|
+
_apply_to_RURs(self, RUR.multiply_terms),
|
|
339
|
+
is_numerical=self._is_numerical,
|
|
340
|
+
manifold_thunk=self._manifold_thunk,
|
|
341
|
+
non_trivial_generalized_obstruction_class=(
|
|
342
|
+
self._non_trivial_generalized_obstruction_class))
|
|
343
|
+
|
|
344
|
+
def multiply_and_simplify_terms_in_RUR(self):
|
|
345
|
+
"""
|
|
346
|
+
If a Ptolemy coordinate is given as Rational Univariate Representation
|
|
347
|
+
with numerator and denominator being a product, multiply the terms,
|
|
348
|
+
reduce the fraction and return the result.
|
|
349
|
+
|
|
350
|
+
See multiply_and_simplify_terms of RUR.
|
|
351
|
+
|
|
352
|
+
This loses information about how the numerator and denominator are
|
|
353
|
+
factorised.
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
return PtolemyCoordinates(
|
|
358
|
+
_apply_to_RURs(self, RUR.multiply_and_simplify_terms),
|
|
359
|
+
is_numerical=self._is_numerical,
|
|
360
|
+
manifold_thunk=self._manifold_thunk,
|
|
361
|
+
non_trivial_generalized_obstruction_class=(
|
|
362
|
+
self._non_trivial_generalized_obstruction_class))
|
|
363
|
+
|
|
364
|
+
def cross_ratios(self):
|
|
365
|
+
"""
|
|
366
|
+
Compute cross ratios from Ptolemy coordinates. The cross ratios are
|
|
367
|
+
according to the SnapPy convention, so we have::
|
|
368
|
+
|
|
369
|
+
z = 1 - 1/zp, zp = 1 - 1/zpp, zpp = 1 - 1/z
|
|
370
|
+
|
|
371
|
+
where::
|
|
372
|
+
|
|
373
|
+
z is at the edge 01 and equal to s0 * s1 * (c_1010 * c_0101) / (c_1001 * c_0110)
|
|
374
|
+
zp is at the edge 02 and equal to - s0 * s2 * (c_1001 * c_0110) / (c_1100 * c_0011)
|
|
375
|
+
zpp is at the edge 03 and equal to s0 * s3 * (c_1100 * c_0011) / (c_0101 * c_1010).
|
|
376
|
+
|
|
377
|
+
Note that this is different from the convention used in
|
|
378
|
+
Garoufalidis, Goerner, Zickert:
|
|
379
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
380
|
+
https://arxiv.org/abs/1207.6711
|
|
381
|
+
|
|
382
|
+
Take an exact solution:
|
|
383
|
+
|
|
384
|
+
>>> from snappy.ptolemy.processMagmaFile import _magma_output_for_4_1__sl3, solutions_from_magma
|
|
385
|
+
>>> solutions = solutions_from_magma(_magma_output_for_4_1__sl3)
|
|
386
|
+
>>> solution = solutions[2]
|
|
387
|
+
|
|
388
|
+
Turn into cross Ratios:
|
|
389
|
+
|
|
390
|
+
>>> crossRatios = solution.cross_ratios()
|
|
391
|
+
|
|
392
|
+
Get a cross ratio:
|
|
393
|
+
|
|
394
|
+
>>> crossRatios['zp_0010_0']
|
|
395
|
+
Mod(-x, x^2 + x + 1)
|
|
396
|
+
|
|
397
|
+
Check the relationship between cross ratios:
|
|
398
|
+
|
|
399
|
+
>>> crossRatios['z_0010_0'] == 1 - 1 / crossRatios['zp_0010_0']
|
|
400
|
+
True
|
|
401
|
+
|
|
402
|
+
>>> crossRatios['zp_0010_0'] == 1 - 1 / crossRatios['zpp_0010_0']
|
|
403
|
+
True
|
|
404
|
+
|
|
405
|
+
>>> crossRatios['zpp_0010_0'] == 1 - 1 / crossRatios['z_0010_0']
|
|
406
|
+
True
|
|
407
|
+
|
|
408
|
+
Get information about what one can do with cross ratios
|
|
409
|
+
"""
|
|
410
|
+
return CrossRatios(_ptolemy_to_cross_ratio(self)[0],
|
|
411
|
+
is_numerical=self._is_numerical,
|
|
412
|
+
manifold_thunk=self._manifold_thunk)
|
|
413
|
+
|
|
414
|
+
def cross_ratios_numerical(self):
|
|
415
|
+
"""
|
|
416
|
+
Turn exact solutions into numerical and then compute cross ratios.
|
|
417
|
+
See numerical() and cross_ratios().
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
if self._is_numerical:
|
|
421
|
+
return self.cross_ratios()
|
|
422
|
+
else:
|
|
423
|
+
return ZeroDimensionalComponent(
|
|
424
|
+
[num.cross_ratios() for num in self.numerical()])
|
|
425
|
+
|
|
426
|
+
def flattenings_numerical(self):
|
|
427
|
+
"""
|
|
428
|
+
Turn into numerical solutions and compute flattenings, see
|
|
429
|
+
help(snappy.ptolemy.coordinates.Flattenings)
|
|
430
|
+
Also see numerical()
|
|
431
|
+
|
|
432
|
+
Get Ptolemy coordinates.
|
|
433
|
+
|
|
434
|
+
>>> from snappy.ptolemy.processMagmaFile import _magma_output_for_4_1__sl3, solutions_from_magma
|
|
435
|
+
>>> solutions = solutions_from_magma(_magma_output_for_4_1__sl3)
|
|
436
|
+
>>> solution = solutions[2]
|
|
437
|
+
|
|
438
|
+
Compute a numerical solution
|
|
439
|
+
|
|
440
|
+
>>> flattenings = solution.flattenings_numerical()
|
|
441
|
+
|
|
442
|
+
Get more information with help(flattenings[0])
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
if self._is_numerical:
|
|
446
|
+
# Used as a factor when taking log's to shift the branch slightly
|
|
447
|
+
# from the standard branch cut at the negative real line
|
|
448
|
+
branch_factor = 1
|
|
449
|
+
|
|
450
|
+
# Try different branch cuts 1000 times
|
|
451
|
+
for i in range(1000):
|
|
452
|
+
try:
|
|
453
|
+
# get the dictionary containing flattenings
|
|
454
|
+
# and the evenN that describes in what
|
|
455
|
+
# flavor of the Extended Bloch group the result lives in
|
|
456
|
+
d, evenN = _ptolemy_to_cross_ratio(
|
|
457
|
+
self,
|
|
458
|
+
branch_factor,
|
|
459
|
+
self._non_trivial_generalized_obstruction_class,
|
|
460
|
+
as_flattenings=True)
|
|
461
|
+
|
|
462
|
+
return Flattenings(d,
|
|
463
|
+
manifold_thunk=self._manifold_thunk,
|
|
464
|
+
evenN=evenN)
|
|
465
|
+
except LogToCloseToBranchCutError:
|
|
466
|
+
# Values to close to the branch cut, just multiply
|
|
467
|
+
# by a small offset
|
|
468
|
+
branch_factor *= pari('exp(0.0001 * I)')
|
|
469
|
+
|
|
470
|
+
raise Exception("Could not find non-ambiguous branch cut for log")
|
|
471
|
+
else:
|
|
472
|
+
return ZeroDimensionalComponent(
|
|
473
|
+
[num.flattenings_numerical() for num in self.numerical()])
|
|
474
|
+
|
|
475
|
+
def volume_numerical(self, drop_negative_vols=False):
|
|
476
|
+
"""
|
|
477
|
+
Turn into (Galois conjugate) numerical solutions and compute volumes.
|
|
478
|
+
If already numerical, only return the one volume.
|
|
479
|
+
See numerical().
|
|
480
|
+
|
|
481
|
+
If drop_negative_vols = True is given as optional argument,
|
|
482
|
+
only return non-negative volumes.
|
|
483
|
+
"""
|
|
484
|
+
if self._is_numerical:
|
|
485
|
+
return self.cross_ratios().volume_numerical()
|
|
486
|
+
else:
|
|
487
|
+
vols = ZeroDimensionalComponent(
|
|
488
|
+
[num.volume_numerical() for num in self.numerical()])
|
|
489
|
+
if drop_negative_vols:
|
|
490
|
+
return [vol for vol in vols if vol > -1e-12]
|
|
491
|
+
return vols
|
|
492
|
+
|
|
493
|
+
def complex_volume_numerical(self,
|
|
494
|
+
drop_negative_vols=False,
|
|
495
|
+
with_modulo=False):
|
|
496
|
+
"""
|
|
497
|
+
Turn into (Galois conjugate) numerical solutions and compute complex
|
|
498
|
+
volumes. If already numerical, return the volume.
|
|
499
|
+
|
|
500
|
+
Complex volume is defined up to i*pi**2/6.
|
|
501
|
+
|
|
502
|
+
See numerical(). If drop_negative_vols = True is given as optional
|
|
503
|
+
argument, only return complex volumes with non-negative real part.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
if self._is_numerical:
|
|
507
|
+
return self.flattenings_numerical().complex_volume(
|
|
508
|
+
with_modulo=with_modulo)
|
|
509
|
+
else:
|
|
510
|
+
cvols = ZeroDimensionalComponent(
|
|
511
|
+
[ num.flattenings_numerical().complex_volume(
|
|
512
|
+
with_modulo=with_modulo)
|
|
513
|
+
for num in self.numerical()])
|
|
514
|
+
if drop_negative_vols:
|
|
515
|
+
return [cvol for cvol in cvols if cvol.real() > -1e-12]
|
|
516
|
+
return cvols
|
|
517
|
+
|
|
518
|
+
def _coordinate_at_tet_and_point(self, tet, pt):
|
|
519
|
+
"""
|
|
520
|
+
Given the index of a tetrahedron and a quadruple (any iterable) of
|
|
521
|
+
integer to mark an integral point on that tetrahedron, returns the
|
|
522
|
+
associated Ptolemy coordinate.
|
|
523
|
+
If this is a vertex Ptolemy coordinate, always return 1 without
|
|
524
|
+
checking for it in the dictionary.
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
# Handle the vertex Ptolemy coordinate case
|
|
528
|
+
if sum(pt) in pt:
|
|
529
|
+
return 1
|
|
530
|
+
|
|
531
|
+
# Normal case
|
|
532
|
+
return self['c_%d%d%d%d' % tuple(pt) + '_%d' % tet]
|
|
533
|
+
|
|
534
|
+
def _get_obstruction_variable(self, face, tet):
|
|
535
|
+
"""
|
|
536
|
+
Get the obstruction variable sigma_face for the given face and
|
|
537
|
+
tetrahedron. Return 1 if there is no such obstruction class.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
key = "s_%d_%d" % (face, tet)
|
|
541
|
+
return self.get(key, +1) # Default to 1 if no obstruction class given
|
|
542
|
+
|
|
543
|
+
@staticmethod
|
|
544
|
+
def _three_perm_sign(v0, v1, v2):
|
|
545
|
+
"""
|
|
546
|
+
Returns the sign of the permutation necessary to bring the three
|
|
547
|
+
elements v0, v1, v2 in order.
|
|
548
|
+
"""
|
|
549
|
+
if v0 < v2 and v2 < v1:
|
|
550
|
+
return -1
|
|
551
|
+
if v1 < v0 and v0 < v2:
|
|
552
|
+
return -1
|
|
553
|
+
if v2 < v1 and v1 < v0:
|
|
554
|
+
return -1
|
|
555
|
+
return +1
|
|
556
|
+
|
|
557
|
+
def diamond_coordinate(self, tet, v0, v1, v2, pt):
|
|
558
|
+
"""
|
|
559
|
+
Returns the diamond coordinate for tetrahedron with index tet
|
|
560
|
+
for the face with vertices v0, v1, v2 (integers between 0 and 3) and
|
|
561
|
+
integral point pt (quadruple adding up to N-2).
|
|
562
|
+
|
|
563
|
+
See Definition 10.1:
|
|
564
|
+
Garoufalidis, Goerner, Zickert:
|
|
565
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
566
|
+
https://arxiv.org/abs/1207.6711
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
# Integral points that are indices of Ptolemy coordinates
|
|
570
|
+
pt_v0_v0 = [ a + 2 * _kronecker_delta(v0, i)
|
|
571
|
+
for i, a in enumerate(pt) ]
|
|
572
|
+
pt_v0_v1 = [ a + _kronecker_delta(v0, i) + _kronecker_delta(v1, i)
|
|
573
|
+
for i, a in enumerate(pt) ]
|
|
574
|
+
pt_v0_v2 = [ a + _kronecker_delta(v0, i) + _kronecker_delta(v2, i)
|
|
575
|
+
for i, a in enumerate(pt) ]
|
|
576
|
+
pt_v1_v2 = [ a + _kronecker_delta(v1, i) + _kronecker_delta(v2, i)
|
|
577
|
+
for i, a in enumerate(pt) ]
|
|
578
|
+
|
|
579
|
+
# Ptolemy coordinates involved
|
|
580
|
+
c_pt_v0_v0 = self._coordinate_at_tet_and_point(tet, pt_v0_v0)
|
|
581
|
+
c_pt_v0_v1 = self._coordinate_at_tet_and_point(tet, pt_v0_v1)
|
|
582
|
+
c_pt_v0_v2 = self._coordinate_at_tet_and_point(tet, pt_v0_v2)
|
|
583
|
+
c_pt_v1_v2 = self._coordinate_at_tet_and_point(tet, pt_v1_v2)
|
|
584
|
+
|
|
585
|
+
# Obstruction variable
|
|
586
|
+
# See Definition 9.23 of
|
|
587
|
+
# Garoufalidis, Thurston, Zickert
|
|
588
|
+
# The Complex Volume of SL(n,C)-Representations of 3-Manifolds
|
|
589
|
+
# httpss://arxiv.org/abs/1111.2828
|
|
590
|
+
face = next(iter(set(range(4)) - {v0, v1, v2}))
|
|
591
|
+
obstruction = self._get_obstruction_variable(face, tet)
|
|
592
|
+
|
|
593
|
+
# The epsilon permutation sign from Definition 10.1 of
|
|
594
|
+
# Garoufalidis, Goerner, Zickert:
|
|
595
|
+
# Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
596
|
+
# https://arxiv.org/abs/1207.6711
|
|
597
|
+
s = PtolemyCoordinates._three_perm_sign(v0, v1, v2)
|
|
598
|
+
|
|
599
|
+
# The equation from the same Definition
|
|
600
|
+
return - (obstruction * s *
|
|
601
|
+
(c_pt_v0_v0 * c_pt_v1_v2) /
|
|
602
|
+
(c_pt_v0_v1 * c_pt_v0_v2))
|
|
603
|
+
|
|
604
|
+
def ratio_coordinate(self, tet, v0, v1, pt):
|
|
605
|
+
"""
|
|
606
|
+
Returns the ratio coordinate for tetrahedron with index tet
|
|
607
|
+
for the edge from v0 to v1 (integers between 0 and 3) and integral
|
|
608
|
+
point pt (quadruple adding up N-1) on the edge.
|
|
609
|
+
|
|
610
|
+
See Definition 10.2:
|
|
611
|
+
Garoufalidis, Goerner, Zickert:
|
|
612
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
613
|
+
https://arxiv.org/abs/1207.6711
|
|
614
|
+
|
|
615
|
+
Note that this definition turned out to have the wrong sign. Multiply
|
|
616
|
+
the result by -1 if v1 < v0 and N is even.
|
|
617
|
+
"""
|
|
618
|
+
|
|
619
|
+
# Integral points on the edge
|
|
620
|
+
pt_v0 = [ a + _kronecker_delta(v0, i) for i, a in enumerate(pt) ]
|
|
621
|
+
pt_v1 = [ a + _kronecker_delta(v1, i) for i, a in enumerate(pt) ]
|
|
622
|
+
|
|
623
|
+
# Ptolemy coordinates at those integral points
|
|
624
|
+
c_pt_v0 = self._coordinate_at_tet_and_point(tet, pt_v0)
|
|
625
|
+
c_pt_v1 = self._coordinate_at_tet_and_point(tet, pt_v1)
|
|
626
|
+
|
|
627
|
+
# Sign
|
|
628
|
+
s = (-1) ** pt[v1]
|
|
629
|
+
|
|
630
|
+
if v1 < v0 and (self.N() % 2 == 0):
|
|
631
|
+
s *= -1
|
|
632
|
+
|
|
633
|
+
# Equation from Definition 10.2
|
|
634
|
+
return s * c_pt_v1 / c_pt_v0
|
|
635
|
+
|
|
636
|
+
def _get_identity_matrix(self):
|
|
637
|
+
|
|
638
|
+
# Get N
|
|
639
|
+
N = self.N()
|
|
640
|
+
|
|
641
|
+
return [[_kronecker_delta(i, j) for i in range(N)] for j in range(N)]
|
|
642
|
+
|
|
643
|
+
def long_edge(self, tet, v0, v1, v2):
|
|
644
|
+
"""
|
|
645
|
+
The matrix that labels a long edge from v0 to v1 (integers between 0
|
|
646
|
+
and 3) of a (doubly) truncated simplex corresponding to an ideal
|
|
647
|
+
tetrahedron with index tet.
|
|
648
|
+
|
|
649
|
+
This matrix was labeled alpha^{v0v1v2} (v2 does not matter for non
|
|
650
|
+
double-truncated simplex) in Figure 18 of
|
|
651
|
+
Garoufalidis, Goerner, Zickert:
|
|
652
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
653
|
+
https://arxiv.org/abs/1207.6711
|
|
654
|
+
|
|
655
|
+
It is computed using equation 10.4. Note that the ratio coordinate
|
|
656
|
+
is different from the definition in the paper (see ratio_coordinate).
|
|
657
|
+
|
|
658
|
+
The resulting matrix is given as a python list of lists.
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
# Key for the cache
|
|
662
|
+
key = 'long_%d_%d%d' % (tet, v0, v1)
|
|
663
|
+
|
|
664
|
+
# Fill cache if necessary
|
|
665
|
+
if key not in self._edge_cache:
|
|
666
|
+
|
|
667
|
+
# Get N
|
|
668
|
+
N = self.N()
|
|
669
|
+
|
|
670
|
+
# Start with the 0 matrix
|
|
671
|
+
m = [[0 for i in range(N)] for j in range(N)]
|
|
672
|
+
|
|
673
|
+
# Traverse the edge to fill the counter diagonal elements
|
|
674
|
+
# with the ratio coordinates
|
|
675
|
+
for c in range(N):
|
|
676
|
+
r = N - 1 - c
|
|
677
|
+
pt = [ r * _kronecker_delta(v0, i) + c * _kronecker_delta(v1, i)
|
|
678
|
+
for i in range(4) ]
|
|
679
|
+
m[r][c] = self.ratio_coordinate(tet, v0, v1, pt)
|
|
680
|
+
|
|
681
|
+
# Set in cache
|
|
682
|
+
self._edge_cache[key] = m
|
|
683
|
+
|
|
684
|
+
# Return
|
|
685
|
+
return self._edge_cache[key]
|
|
686
|
+
|
|
687
|
+
def middle_edge(self, tet, v0, v1, v2):
|
|
688
|
+
"""
|
|
689
|
+
The matrix that labels a middle edge on the face v0, v1, v2 (integers
|
|
690
|
+
between 0 and 3) of a doubly truncated simplex (or a short edge of the
|
|
691
|
+
truncated simplex) corresponding to an ideal tetrahedron with index
|
|
692
|
+
tet.
|
|
693
|
+
|
|
694
|
+
This matrix was labeled beta^{v0v1v2} in Figure 18 of
|
|
695
|
+
Garoufalidis, Goerner, Zickert:
|
|
696
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
697
|
+
https://arxiv.org/abs/1207.6711
|
|
698
|
+
|
|
699
|
+
It is computed using equation 10.4.
|
|
700
|
+
|
|
701
|
+
The resulting matrix is given as a python list of lists.
|
|
702
|
+
"""
|
|
703
|
+
|
|
704
|
+
# Key for the cache
|
|
705
|
+
key = 'middle_%d_%d%d%d' % (tet, v0, v1, v2)
|
|
706
|
+
|
|
707
|
+
# Fill cache if necessary
|
|
708
|
+
if key not in self._edge_cache:
|
|
709
|
+
|
|
710
|
+
# Get N
|
|
711
|
+
N = self.N()
|
|
712
|
+
|
|
713
|
+
# Start with identity
|
|
714
|
+
m = self._get_identity_matrix()
|
|
715
|
+
|
|
716
|
+
# Compute the product in equation 10.4
|
|
717
|
+
for a0, a1, a2 in utilities.triples_with_fixed_sum_iterator(N - 2):
|
|
718
|
+
|
|
719
|
+
# Get integral point for diamond coordinate
|
|
720
|
+
pt = [ a1 * _kronecker_delta(v0, i) +
|
|
721
|
+
a2 * _kronecker_delta(v1, i) +
|
|
722
|
+
a0 * _kronecker_delta(v2, i) for i in range(4) ]
|
|
723
|
+
|
|
724
|
+
# Compute diamond coordinate
|
|
725
|
+
diamond = self.diamond_coordinate(tet, v0, v1, v2, pt)
|
|
726
|
+
|
|
727
|
+
# Multiply result with the x matrix
|
|
728
|
+
m = matrix.matrix_mult(m, _X(N, a1 + 1, diamond))
|
|
729
|
+
|
|
730
|
+
# Fill cache
|
|
731
|
+
self._edge_cache[key] = m
|
|
732
|
+
|
|
733
|
+
return self._edge_cache[key]
|
|
734
|
+
|
|
735
|
+
def short_edge(self, tet, v0, v1, v2):
|
|
736
|
+
"""
|
|
737
|
+
Returns the identity. This is because we can think of the matrices
|
|
738
|
+
given by Ptolemy coordinates of living on truncated simplices which
|
|
739
|
+
can be though of as doubly truncated simplices where all short edges
|
|
740
|
+
are collapsed, hence labeled by the identity.
|
|
741
|
+
|
|
742
|
+
See equation 10.4 in
|
|
743
|
+
Garoufalidis, Goerner, Zickert:
|
|
744
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
745
|
+
https://arxiv.org/abs/1207.6711
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
# Key for the cache
|
|
749
|
+
key = 'short'
|
|
750
|
+
|
|
751
|
+
# Fill cache if necessary
|
|
752
|
+
if key not in self._edge_cache:
|
|
753
|
+
|
|
754
|
+
# Get N
|
|
755
|
+
N = self.N()
|
|
756
|
+
|
|
757
|
+
# Take the identity matrix
|
|
758
|
+
m = self._get_identity_matrix()
|
|
759
|
+
|
|
760
|
+
# Fill cache
|
|
761
|
+
self._edge_cache[key] = m
|
|
762
|
+
|
|
763
|
+
return self._edge_cache[key]
|
|
764
|
+
|
|
765
|
+
def _init_matrix_and_inverse_cache(self):
|
|
766
|
+
# Fill the caches of matrices corresponding to the
|
|
767
|
+
# fundamental group generators and their inverses
|
|
768
|
+
|
|
769
|
+
if self._matrix_cache and self._inverse_matrix_cache:
|
|
770
|
+
return
|
|
771
|
+
|
|
772
|
+
# Compute all the matrices for the generators and there inverses
|
|
773
|
+
# The short edges of the doubly truncated simplices are all identities
|
|
774
|
+
# (they will be collapset in the truncated simplex), thus we give them
|
|
775
|
+
# no penalty.
|
|
776
|
+
self._matrix_cache, self._inverse_matrix_cache = (
|
|
777
|
+
findLoops.images_of_original_generators(self,
|
|
778
|
+
penalties=(0, 1, 1)))
|
|
779
|
+
|
|
780
|
+
def evaluate_word(self, word, G=None):
|
|
781
|
+
"""
|
|
782
|
+
Given a word in the generators of the fundamental group,
|
|
783
|
+
compute the corresponding matrix. By default, these are the
|
|
784
|
+
generators of the unsimplified presentation of the fundamental
|
|
785
|
+
group. An optional SnapPy fundamental group can be given if the
|
|
786
|
+
words are in generators of a different presentation, e.g.,
|
|
787
|
+
c.evaluate_word(word, M.fundamental_group(True)) to
|
|
788
|
+
evaluate a word in the simplified presentation returned by
|
|
789
|
+
M.fundamental_group(True).
|
|
790
|
+
|
|
791
|
+
For now, the matrix is returned as list of lists.
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
# Init the matrices corresponding to generators
|
|
795
|
+
self._init_matrix_and_inverse_cache()
|
|
796
|
+
|
|
797
|
+
return findLoops.evaluate_word(
|
|
798
|
+
self._get_identity_matrix(),
|
|
799
|
+
self._matrix_cache,
|
|
800
|
+
self._inverse_matrix_cache,
|
|
801
|
+
word,
|
|
802
|
+
G)
|
|
803
|
+
|
|
804
|
+
def _testing_assert_identity(self, m,
|
|
805
|
+
allow_sign_if_obstruction_class=False):
|
|
806
|
+
|
|
807
|
+
N = self.N()
|
|
808
|
+
|
|
809
|
+
null = [[0 for i in range(N)] for j in range(N)]
|
|
810
|
+
identity = self._get_identity_matrix()
|
|
811
|
+
|
|
812
|
+
if allow_sign_if_obstruction_class and self.has_obstruction():
|
|
813
|
+
|
|
814
|
+
if not (matrix.matrix_add(m, identity) == null or
|
|
815
|
+
matrix.matrix_sub(m, identity) == null):
|
|
816
|
+
raise Exception("Cocycle condition violated: %s" % m)
|
|
817
|
+
|
|
818
|
+
else:
|
|
819
|
+
|
|
820
|
+
if not matrix.matrix_sub(m, identity) == null:
|
|
821
|
+
raise Exception("Cocycle condition violated: %s" % m)
|
|
822
|
+
|
|
823
|
+
def _testing_check_cocycles(self):
|
|
824
|
+
for tet in range(self.num_tetrahedra()):
|
|
825
|
+
# Check middle edges is inverse when direction reversed
|
|
826
|
+
for v in [(0,1,2),(0,1,3),(0,2,1),(0,2,3),(0,3,1),(0,3,2),
|
|
827
|
+
(1,0,2),(1,0,3),(1,2,0),(1,2,3),(1,3,0),(1,3,2),
|
|
828
|
+
(2,0,1),(2,0,3),(2,1,0),(2,1,3),(2,3,0),(2,3,1),
|
|
829
|
+
(3,0,1),(3,0,2),(3,1,0),(3,1,2),(3,2,0),(3,2,1)]:
|
|
830
|
+
m1 = self.middle_edge(tet,v[0],v[1],v[2])
|
|
831
|
+
m2 = self.middle_edge(tet,v[0],v[2],v[1])
|
|
832
|
+
self._testing_assert_identity(
|
|
833
|
+
matrix.matrix_mult(m1, m2))
|
|
834
|
+
|
|
835
|
+
# Check long edges is inverse when direction reversed
|
|
836
|
+
for v in [(0,1,2), (0,2,1), (0,3,1),
|
|
837
|
+
(1,0,2), (1,2,0), (1,3,0),
|
|
838
|
+
(2,0,1), (2,1,0), (2,3,0),
|
|
839
|
+
(3,0,1), (3,1,0), (3,2,0)]:
|
|
840
|
+
m1 = self.long_edge(tet,v[0],v[1],v[2])
|
|
841
|
+
m2 = self.long_edge(tet,v[1],v[0],v[2])
|
|
842
|
+
self._testing_assert_identity(
|
|
843
|
+
matrix.matrix_mult(m1, m2))
|
|
844
|
+
|
|
845
|
+
# Check triangle for each vertex
|
|
846
|
+
for v in [(0,1,2,3), (1,2,3,0), (2,3,0,1), (3,0,1,2)]:
|
|
847
|
+
m1 = self.middle_edge(tet, v[0], v[1], v[2])
|
|
848
|
+
m2 = self.middle_edge(tet, v[0], v[2], v[3])
|
|
849
|
+
m3 = self.middle_edge(tet, v[0], v[3], v[1])
|
|
850
|
+
|
|
851
|
+
self._testing_assert_identity(
|
|
852
|
+
matrix.matrix_mult(
|
|
853
|
+
m1, matrix.matrix_mult(m2, m3)))
|
|
854
|
+
|
|
855
|
+
# Check hexagon for each face
|
|
856
|
+
for v in [(0,1,2), (0,1,3), (0,2,3), (1,2,3)]:
|
|
857
|
+
m1 = self.middle_edge(tet,v[0],v[1],v[2])
|
|
858
|
+
m2 = self.long_edge( tet,v[0],v[2],v[1])
|
|
859
|
+
m3 = self.middle_edge(tet,v[2],v[0],v[1])
|
|
860
|
+
m4 = self.long_edge( tet,v[2],v[1],v[0])
|
|
861
|
+
m5 = self.middle_edge(tet,v[1],v[2],v[0])
|
|
862
|
+
m6 = self.long_edge( tet,v[1],v[0],v[2])
|
|
863
|
+
self._testing_assert_identity(
|
|
864
|
+
matrix.matrix_mult(
|
|
865
|
+
m1,
|
|
866
|
+
matrix.matrix_mult(
|
|
867
|
+
m2,
|
|
868
|
+
matrix.matrix_mult(
|
|
869
|
+
m3,
|
|
870
|
+
matrix.matrix_mult(
|
|
871
|
+
m4,
|
|
872
|
+
matrix.matrix_mult(m5,m6))))), True)
|
|
873
|
+
|
|
874
|
+
def check_against_manifold(self, M=None, epsilon=None):
|
|
875
|
+
"""
|
|
876
|
+
Checks that the given solution really is a solution to the Ptolemy
|
|
877
|
+
variety of a manifold. See help(ptolemy.PtolemyCoordinates) for
|
|
878
|
+
more example.
|
|
879
|
+
|
|
880
|
+
=== Arguments ===
|
|
881
|
+
|
|
882
|
+
* M --- manifold to check this for
|
|
883
|
+
* epsilon --- maximal allowed error when checking the relations, use
|
|
884
|
+
None for exact comparison.
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
if M is None:
|
|
888
|
+
M = self.get_manifold()
|
|
889
|
+
|
|
890
|
+
if M is None:
|
|
891
|
+
raise Exception("Need to give manifold")
|
|
892
|
+
|
|
893
|
+
if self._non_trivial_generalized_obstruction_class:
|
|
894
|
+
raise PtolemyCannotBeCheckedError()
|
|
895
|
+
|
|
896
|
+
num_tets = self.num_tetrahedra()
|
|
897
|
+
N, has_obstruction_class = _N_and_has_obstruction_for_ptolemys(self)
|
|
898
|
+
|
|
899
|
+
if not M.num_tetrahedra() == num_tets:
|
|
900
|
+
raise Exception("Number tetrahedra not matching")
|
|
901
|
+
|
|
902
|
+
if has_obstruction_class:
|
|
903
|
+
# check cocycle condition
|
|
904
|
+
for tet in range(num_tets):
|
|
905
|
+
_check_relation(
|
|
906
|
+
self._get_obstruction_variable(0, tet) *
|
|
907
|
+
self._get_obstruction_variable(1, tet) *
|
|
908
|
+
self._get_obstruction_variable(2, tet) *
|
|
909
|
+
self._get_obstruction_variable(3, tet) - 1,
|
|
910
|
+
epsilon,
|
|
911
|
+
"Obstruction cocycle")
|
|
912
|
+
# check identified faces
|
|
913
|
+
for dummy_sign, power, var1, var2 in (
|
|
914
|
+
M._ptolemy_equations_identified_face_classes()):
|
|
915
|
+
_check_relation(
|
|
916
|
+
self[var1] - self[var2],
|
|
917
|
+
epsilon,
|
|
918
|
+
"Identification of face classes")
|
|
919
|
+
|
|
920
|
+
# Check identified Ptolemy coordinates
|
|
921
|
+
for sign, power, var1, var2 in (
|
|
922
|
+
M._ptolemy_equations_identified_coordinates(N)):
|
|
923
|
+
_check_relation(
|
|
924
|
+
self[var1] - sign * self[var2],
|
|
925
|
+
epsilon,
|
|
926
|
+
"Identification of Ptolemy coordinates")
|
|
927
|
+
|
|
928
|
+
# Check Ptolemy relationship
|
|
929
|
+
for tet in range(num_tets):
|
|
930
|
+
for index in utilities.quadruples_with_fixed_sum_iterator(N - 2):
|
|
931
|
+
|
|
932
|
+
def get_ptolemy_coordinate(addl_index):
|
|
933
|
+
total_index = matrix.vector_add(index, addl_index)
|
|
934
|
+
key = "c_%d%d%d%d" % tuple(total_index) + "_%d" % tet
|
|
935
|
+
return self[key]
|
|
936
|
+
|
|
937
|
+
s0 = self._get_obstruction_variable(0, tet)
|
|
938
|
+
s1 = self._get_obstruction_variable(1, tet)
|
|
939
|
+
s2 = self._get_obstruction_variable(2, tet)
|
|
940
|
+
s3 = self._get_obstruction_variable(3, tet)
|
|
941
|
+
|
|
942
|
+
rel = ( s0 * s1 * get_ptolemy_coordinate((1,1,0,0))
|
|
943
|
+
* get_ptolemy_coordinate((0,0,1,1))
|
|
944
|
+
- s0 * s2 * get_ptolemy_coordinate((1,0,1,0))
|
|
945
|
+
* get_ptolemy_coordinate((0,1,0,1))
|
|
946
|
+
+ s0 * s3 * get_ptolemy_coordinate((1,0,0,1))
|
|
947
|
+
* get_ptolemy_coordinate((0,1,1,0)))
|
|
948
|
+
|
|
949
|
+
_check_relation(rel,
|
|
950
|
+
epsilon,
|
|
951
|
+
"Ptolemy relation")
|
|
952
|
+
|
|
953
|
+
def is_geometric(self, epsilon=1e-6):
|
|
954
|
+
"""
|
|
955
|
+
Returns true if all shapes corresponding to this solution have positive
|
|
956
|
+
imaginary part.
|
|
957
|
+
|
|
958
|
+
If the solutions are exact, it returns true if one of the corresponding
|
|
959
|
+
numerical solutions is geometric.
|
|
960
|
+
|
|
961
|
+
An optional epsilon can be given. An imaginary part of a shape is
|
|
962
|
+
considered positive if it is larger than this epsilon.
|
|
963
|
+
"""
|
|
964
|
+
|
|
965
|
+
if self._is_numerical:
|
|
966
|
+
return self.cross_ratios().is_geometric(epsilon)
|
|
967
|
+
else:
|
|
968
|
+
for numerical_sol in self.numerical():
|
|
969
|
+
if numerical_sol.is_geometric(epsilon):
|
|
970
|
+
return True
|
|
971
|
+
return False
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
class Flattenings(dict):
|
|
975
|
+
"""
|
|
976
|
+
Represents a flattening assigned to each edge of a simplex as dictionary.
|
|
977
|
+
|
|
978
|
+
We assign to each pair of parallel edges of each simplex a triple (w, z, p)
|
|
979
|
+
such that::
|
|
980
|
+
|
|
981
|
+
w = log(z) + p * (2 * pi * i / N) where N is fixed and even.
|
|
982
|
+
|
|
983
|
+
For N = 2, the three triples belonging to a simplex form a combinatorial
|
|
984
|
+
flattening (w0, w1, w2) as defined in Definition 3.1 in
|
|
985
|
+
Walter D. Neumann, Extended Bloch group and the Cheeger-Chern-Simons class
|
|
986
|
+
https://arxiv.org/abs/math.GT/0307092
|
|
987
|
+
|
|
988
|
+
For N > 2, the three triples form a generalized combinatorial flattening
|
|
989
|
+
(w0, w1, w2) that gives an element in the generalized Extended Bloch group
|
|
990
|
+
which is the Extended Bloch group corresponding to the Riemann surface
|
|
991
|
+
given by::
|
|
992
|
+
|
|
993
|
+
u1 * e^w0 + u2 * e^w1 = 1
|
|
994
|
+
|
|
995
|
+
where u1^N = u2^N = 1.
|
|
996
|
+
|
|
997
|
+
A representation in SL(n,C) and SL(n,C)/{+1,-1} with n even gives an element
|
|
998
|
+
in the generalized Extended Bloch group for N = 2.
|
|
999
|
+
A representation in PSL(n,C) with n even in the group for N = n.
|
|
1000
|
+
A representation in PSL(n,C) with n odd in the group for N = 2 * n.
|
|
1001
|
+
|
|
1002
|
+
This work has not been published yet.
|
|
1003
|
+
|
|
1004
|
+
If f is a flattening, then in the notation of Neumann, the value of::
|
|
1005
|
+
|
|
1006
|
+
f['z_xxxx_y'] is (w0, z, p)
|
|
1007
|
+
f['zp_xxxx_y'] is (w1, z', q)
|
|
1008
|
+
f['zpp_xxxx_y'] is (w2, z'', r).
|
|
1009
|
+
"""
|
|
1010
|
+
|
|
1011
|
+
def __init__(self, d, manifold_thunk=lambda : None, evenN=2):
|
|
1012
|
+
super().__init__(d)
|
|
1013
|
+
self._is_numerical = True
|
|
1014
|
+
self._manifold_thunk = manifold_thunk
|
|
1015
|
+
self.dimension = 0
|
|
1016
|
+
|
|
1017
|
+
# The N for which we get the generalized Extended Bloch group
|
|
1018
|
+
self._evenN = evenN
|
|
1019
|
+
|
|
1020
|
+
def __repr__(self):
|
|
1021
|
+
dict_repr = dict.__repr__(self)
|
|
1022
|
+
return "Flattenings(%s, ...)" % dict_repr
|
|
1023
|
+
|
|
1024
|
+
def _repr_pretty_(self, p, cycle):
|
|
1025
|
+
if cycle:
|
|
1026
|
+
p.text('Flattenings(...)')
|
|
1027
|
+
else:
|
|
1028
|
+
with p.group(4, 'Flattenings(',')'):
|
|
1029
|
+
p.breakable()
|
|
1030
|
+
p.pretty(dict(self))
|
|
1031
|
+
p.text(', ...')
|
|
1032
|
+
|
|
1033
|
+
def get_manifold(self):
|
|
1034
|
+
"""
|
|
1035
|
+
Get the manifold for which this structure represents a flattening.
|
|
1036
|
+
"""
|
|
1037
|
+
|
|
1038
|
+
return self._manifold_thunk()
|
|
1039
|
+
|
|
1040
|
+
def num_tetrahedra(self):
|
|
1041
|
+
"""
|
|
1042
|
+
The number of tetrahedra for which we have cross ratios.
|
|
1043
|
+
"""
|
|
1044
|
+
return _num_tetrahedra(self)
|
|
1045
|
+
|
|
1046
|
+
def N(self):
|
|
1047
|
+
"""
|
|
1048
|
+
Get the N such that these cross ratios are for
|
|
1049
|
+
SL/PSL(N,C)-representations.
|
|
1050
|
+
"""
|
|
1051
|
+
|
|
1052
|
+
return _N_for_shapes(self)
|
|
1053
|
+
|
|
1054
|
+
@classmethod
|
|
1055
|
+
def from_tetrahedra_shapes_of_manifold(cls, M):
|
|
1056
|
+
"""
|
|
1057
|
+
Takes as argument a manifold and produces (weak) flattenings using
|
|
1058
|
+
the tetrahedra_shapes of the manifold M.
|
|
1059
|
+
|
|
1060
|
+
>>> from snappy import Manifold
|
|
1061
|
+
>>> M = Manifold("5_2")
|
|
1062
|
+
>>> flattenings = Flattenings.from_tetrahedra_shapes_of_manifold(M)
|
|
1063
|
+
>>> flattenings.check_against_manifold(M)
|
|
1064
|
+
>>> flattenings.check_against_manifold()
|
|
1065
|
+
"""
|
|
1066
|
+
|
|
1067
|
+
PiI = pari('Pi * I')
|
|
1068
|
+
|
|
1069
|
+
num_tets = M.num_tetrahedra()
|
|
1070
|
+
|
|
1071
|
+
z_cross_ratios = M.tetrahedra_shapes(
|
|
1072
|
+
part='rect', dec_prec=pari.get_real_precision())
|
|
1073
|
+
|
|
1074
|
+
all_cross_ratios = sum(
|
|
1075
|
+
[ [z, 1 / (1-z), 1 - 1/z] for z in z_cross_ratios], [])
|
|
1076
|
+
|
|
1077
|
+
log_all_cross_ratios = [ z.log() for z in all_cross_ratios ]
|
|
1078
|
+
|
|
1079
|
+
def flattening_condition(r):
|
|
1080
|
+
return (3 * r * [0]
|
|
1081
|
+
+ 3 * [1]
|
|
1082
|
+
+ 3 * (num_tets - r - 1) * [0])
|
|
1083
|
+
|
|
1084
|
+
flattening_conditions = [
|
|
1085
|
+
flattening_condition(r) for r in range(num_tets)]
|
|
1086
|
+
|
|
1087
|
+
try: # works for snappy.SnapPy.SimpleMatrix
|
|
1088
|
+
equations = M.gluing_equations().data
|
|
1089
|
+
except AttributeError: # works Sage's matrix class
|
|
1090
|
+
equations = [
|
|
1091
|
+
[ int(c) for c in row] for row in M.gluing_equations().rows()]
|
|
1092
|
+
|
|
1093
|
+
all_equations = equations + flattening_conditions
|
|
1094
|
+
|
|
1095
|
+
u, v, d_mat = matrix.smith_normal_form(all_equations)
|
|
1096
|
+
|
|
1097
|
+
extra_cols = len(all_equations[0]) - len(all_equations)
|
|
1098
|
+
|
|
1099
|
+
d = [d_mat[r][r + extra_cols] for r in range(len(d_mat))]
|
|
1100
|
+
|
|
1101
|
+
# errors to the gluing equations and flattening condition
|
|
1102
|
+
# when using the logarithms without adding p * pi * i as complex
|
|
1103
|
+
# numbers
|
|
1104
|
+
errors = matrix.matrix_mult_vector(all_equations,
|
|
1105
|
+
log_all_cross_ratios)
|
|
1106
|
+
|
|
1107
|
+
# divide by pi * i and turn into integers
|
|
1108
|
+
int_errors = [ (x / PiI).real().round() for x in errors ]
|
|
1109
|
+
|
|
1110
|
+
int_errors_in_other_basis = matrix.matrix_mult_vector(u, int_errors)
|
|
1111
|
+
|
|
1112
|
+
def quotient(x, y):
|
|
1113
|
+
if x == 0 and y == 0:
|
|
1114
|
+
return 0
|
|
1115
|
+
|
|
1116
|
+
assert x % y == 0, "%s %s" % (x, y)
|
|
1117
|
+
return x / y
|
|
1118
|
+
|
|
1119
|
+
flattenings_in_other_basis = (
|
|
1120
|
+
extra_cols * [0] +
|
|
1121
|
+
[ - quotient(x, y)
|
|
1122
|
+
for x, y in zip(int_errors_in_other_basis, d) ])
|
|
1123
|
+
|
|
1124
|
+
flattenings = matrix.matrix_mult_vector(v, flattenings_in_other_basis)
|
|
1125
|
+
|
|
1126
|
+
assert (matrix.matrix_mult_vector(all_equations, flattenings) ==
|
|
1127
|
+
[-x for x in int_errors])
|
|
1128
|
+
|
|
1129
|
+
keys = sum([ ['z_0000_%d' % i,
|
|
1130
|
+
'zp_0000_%d' % i,
|
|
1131
|
+
'zpp_0000_%d' % i] for i in range(num_tets)],[])
|
|
1132
|
+
|
|
1133
|
+
Mcopy = M.copy()
|
|
1134
|
+
|
|
1135
|
+
return Flattenings(
|
|
1136
|
+
{k: (log + PiI * p, z, p)
|
|
1137
|
+
for k, log, z, p in zip(keys, log_all_cross_ratios,
|
|
1138
|
+
all_cross_ratios, flattenings)},
|
|
1139
|
+
manifold_thunk=lambda : Mcopy)
|
|
1140
|
+
|
|
1141
|
+
def get_order(self):
|
|
1142
|
+
"""
|
|
1143
|
+
Returns the number N. This flattening represents an element in the
|
|
1144
|
+
generalized Extended Bloch group for the Riemann surface given by
|
|
1145
|
+
u1 * e^w0 + u2 * e^w1 = 1 where u1^N = u2^N = 1.
|
|
1146
|
+
"""
|
|
1147
|
+
|
|
1148
|
+
return self._evenN
|
|
1149
|
+
|
|
1150
|
+
def get_zpq_triple(self, key_z):
|
|
1151
|
+
"""
|
|
1152
|
+
Gives a flattening as triple [z;p,q] representing an element
|
|
1153
|
+
in the generalized Extended Bloch group similar to the way the
|
|
1154
|
+
triple [z;p,q] is used in Lemma 3.2 in
|
|
1155
|
+
Walter D. Neumann, Extended Bloch group and the Cheeger-Chern-Simons class
|
|
1156
|
+
https://arxiv.org/abs/math.GT/0307092
|
|
1157
|
+
"""
|
|
1158
|
+
if not key_z[:2] == 'z_':
|
|
1159
|
+
raise Exception("Need to be called with cross ratio variable z_....")
|
|
1160
|
+
key_zp = 'zp_' + key_z[2:]
|
|
1161
|
+
|
|
1162
|
+
w, z, p = self[key_z]
|
|
1163
|
+
wp, zp, q_canonical_branch_cut = self[key_zp]
|
|
1164
|
+
|
|
1165
|
+
# Note that the q in l(z;p,q) and in Definition 3.1 are different if
|
|
1166
|
+
# z is on the real axis and > 1!!!
|
|
1167
|
+
# Thus we need to compute the q again here according to the formula
|
|
1168
|
+
# for l(z;p,q)
|
|
1169
|
+
|
|
1170
|
+
pari_z = _convert_to_pari_float(z)
|
|
1171
|
+
|
|
1172
|
+
f = pari('2 * Pi * I') / self._evenN
|
|
1173
|
+
|
|
1174
|
+
q_dilog_branch_cut = ((wp + (1-pari_z).log()) / f).round()
|
|
1175
|
+
|
|
1176
|
+
return (z, p, q_dilog_branch_cut)
|
|
1177
|
+
|
|
1178
|
+
def complex_volume(self, with_modulo=False):
|
|
1179
|
+
"""
|
|
1180
|
+
Compute complex volume. The complex volume is defined only up to
|
|
1181
|
+
some multiple of m where m = i * pi**2/6 for PSL(2,C) and SL(N,C)
|
|
1182
|
+
and m = i * pi**2/18 for PSL(3,C).
|
|
1183
|
+
|
|
1184
|
+
When called with with_modulo = True, gives a pair
|
|
1185
|
+
(volume, m)
|
|
1186
|
+
"""
|
|
1187
|
+
|
|
1188
|
+
if self._evenN == 2:
|
|
1189
|
+
m = pari('Pi^2/6')
|
|
1190
|
+
else:
|
|
1191
|
+
m = pari('Pi^2/18')
|
|
1192
|
+
|
|
1193
|
+
sum_L_functions = sum(
|
|
1194
|
+
[
|
|
1195
|
+
_L_function(
|
|
1196
|
+
self.get_zpq_triple(key), self._evenN)
|
|
1197
|
+
for key in list(self.keys())
|
|
1198
|
+
if key[:2] == 'z_' ])
|
|
1199
|
+
|
|
1200
|
+
cvol = sum_L_functions / pari('I')
|
|
1201
|
+
vol = cvol.real()
|
|
1202
|
+
cs = cvol.imag() % m
|
|
1203
|
+
|
|
1204
|
+
if cs > m/2 + pari('1e-12'):
|
|
1205
|
+
cs = cs - m
|
|
1206
|
+
|
|
1207
|
+
cvol = vol + cs * pari('I')
|
|
1208
|
+
|
|
1209
|
+
if with_modulo:
|
|
1210
|
+
if self._evenN not in [2, 6]:
|
|
1211
|
+
raise Exception("Unknown torsion")
|
|
1212
|
+
|
|
1213
|
+
return cvol, m * pari('I')
|
|
1214
|
+
return cvol
|
|
1215
|
+
|
|
1216
|
+
def check_against_manifold(self, M=None, epsilon=1e-10):
|
|
1217
|
+
"""
|
|
1218
|
+
Checks that the flattening really is a solution to the logarithmic
|
|
1219
|
+
PGL(N,C) gluing equations of a manifold. Usage similar to
|
|
1220
|
+
check_against_manifold of Ptolemy Coordinates, see
|
|
1221
|
+
help(ptolemy.Coordinates) for similar examples.
|
|
1222
|
+
|
|
1223
|
+
=== Arguments ===
|
|
1224
|
+
|
|
1225
|
+
M --- manifold to check this for
|
|
1226
|
+
epsilon --- maximal allowed error when checking the equations
|
|
1227
|
+
"""
|
|
1228
|
+
|
|
1229
|
+
if M is None:
|
|
1230
|
+
M = self.get_manifold()
|
|
1231
|
+
|
|
1232
|
+
if M is None:
|
|
1233
|
+
raise Exception("Need to give manifold")
|
|
1234
|
+
|
|
1235
|
+
f = pari('2 * Pi * I') / self._evenN
|
|
1236
|
+
|
|
1237
|
+
for w, z, p in list(self.values()):
|
|
1238
|
+
_check_relation(
|
|
1239
|
+
w - (z.log() + f * p),
|
|
1240
|
+
epsilon,
|
|
1241
|
+
"Flattening relation w == log(z) + PiI * p")
|
|
1242
|
+
|
|
1243
|
+
for k in list(self.keys()):
|
|
1244
|
+
if k[:2] == 'z_':
|
|
1245
|
+
w, z, p = self[k]
|
|
1246
|
+
wp, zp, q = self['zp_'+k[2:]]
|
|
1247
|
+
wpp, zpp, r = self['zpp_'+k[2:]]
|
|
1248
|
+
_check_relation(
|
|
1249
|
+
w + wp + wpp,
|
|
1250
|
+
epsilon,
|
|
1251
|
+
"Flattening relation w0 + w1 + w2 == 0")
|
|
1252
|
+
|
|
1253
|
+
some_z = list(self.keys())[0]
|
|
1254
|
+
variable_name, index, tet_index = some_z.split('_')
|
|
1255
|
+
if variable_name not in ['z', 'zp', 'zpp']:
|
|
1256
|
+
raise Exception("Variable not z, zp, or, zpp")
|
|
1257
|
+
if len(index) != 4:
|
|
1258
|
+
raise Exception("Not 4 indices")
|
|
1259
|
+
N = sum([int(x) for x in index]) + 2
|
|
1260
|
+
|
|
1261
|
+
matrix_with_explanations = M.gluing_equations_pgl(
|
|
1262
|
+
N, equation_type='all')
|
|
1263
|
+
|
|
1264
|
+
matrix = matrix_with_explanations.matrix
|
|
1265
|
+
rows = matrix_with_explanations.explain_rows
|
|
1266
|
+
cols = matrix_with_explanations.explain_columns
|
|
1267
|
+
|
|
1268
|
+
for row in range(len(rows)):
|
|
1269
|
+
s = 0
|
|
1270
|
+
for col in range(len(cols)):
|
|
1271
|
+
flattening_variable = cols[col]
|
|
1272
|
+
w, z, p = self[flattening_variable]
|
|
1273
|
+
s = s + w
|
|
1274
|
+
_check_relation(
|
|
1275
|
+
s,
|
|
1276
|
+
epsilon,
|
|
1277
|
+
"Gluing equation %s" % rows[row])
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
class CrossRatios(dict):
|
|
1281
|
+
"""
|
|
1282
|
+
Represents assigned shape parameters/cross ratios as
|
|
1283
|
+
dictionary. The cross ratios are according to SnapPy convention, so we
|
|
1284
|
+
have::
|
|
1285
|
+
|
|
1286
|
+
z = 1 - 1/zp, zp = 1 - 1/zpp, zpp = 1 - 1/z
|
|
1287
|
+
|
|
1288
|
+
where::
|
|
1289
|
+
|
|
1290
|
+
z is at the edge 01 and equal to s0 * s1 * (c_1010 * c_0101) / (c_1001 * c_0110)
|
|
1291
|
+
zp is at the edge 02 and equal to s0 * s2 * (c_1001 * c_0110) / (c_1100 * c_0011)
|
|
1292
|
+
zpp is at the edge 03 and equal to s0 * s3 * (c_1100 * c_0011) / (c_0101 * c_1010).
|
|
1293
|
+
|
|
1294
|
+
Note that this is different from the convention used in
|
|
1295
|
+
Garoufalidis, Goerner, Zickert:
|
|
1296
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1297
|
+
https://arxiv.org/abs/1207.6711
|
|
1298
|
+
"""
|
|
1299
|
+
|
|
1300
|
+
def __init__(self, d, is_numerical=True, manifold_thunk=None):
|
|
1301
|
+
super().__init__(d)
|
|
1302
|
+
self._is_numerical = is_numerical
|
|
1303
|
+
self._manifold_thunk = manifold_thunk
|
|
1304
|
+
|
|
1305
|
+
# Caches the matrices that label the short and long edges
|
|
1306
|
+
# of the truncated simplices building the manifold
|
|
1307
|
+
self._edge_cache = {}
|
|
1308
|
+
|
|
1309
|
+
# Caches the images of a fundamental group generator
|
|
1310
|
+
self._matrix_cache = []
|
|
1311
|
+
self._inverse_matrix_cache = []
|
|
1312
|
+
|
|
1313
|
+
self.dimension = 0
|
|
1314
|
+
|
|
1315
|
+
@staticmethod
|
|
1316
|
+
def from_snappy_manifold(M, dec_prec=None, bits_prec=None,
|
|
1317
|
+
intervals=False):
|
|
1318
|
+
"""
|
|
1319
|
+
Constructs an assignment of shape parameters/cross ratios using
|
|
1320
|
+
the tetrahehdra_shapes method of a given SnapPy manifold. The optional
|
|
1321
|
+
parameters are the same as that of tetrahedra_shapes.
|
|
1322
|
+
"""
|
|
1323
|
+
|
|
1324
|
+
shapes = M.tetrahedra_shapes('rect', dec_prec=dec_prec,
|
|
1325
|
+
bits_prec=bits_prec,
|
|
1326
|
+
intervals=intervals)
|
|
1327
|
+
d = {}
|
|
1328
|
+
for i, shape in enumerate(shapes):
|
|
1329
|
+
d['z_0000_%d' % i] = shape
|
|
1330
|
+
d['zp_0000_%d' % i] = 1 / (1 - shape)
|
|
1331
|
+
d['zpp_0000_%d' % i] = 1 - 1 / shape
|
|
1332
|
+
|
|
1333
|
+
return CrossRatios(d, is_numerical=True,
|
|
1334
|
+
manifold_thunk=lambda M=M: M)
|
|
1335
|
+
|
|
1336
|
+
def __repr__(self):
|
|
1337
|
+
dict_repr = dict.__repr__(self)
|
|
1338
|
+
return "CrossRatios(%s, is_numerical = %r, ...)" % (
|
|
1339
|
+
dict_repr, self._is_numerical)
|
|
1340
|
+
|
|
1341
|
+
def _repr_pretty_(self, p, cycle):
|
|
1342
|
+
if cycle:
|
|
1343
|
+
p.text('CrossRatios(...)')
|
|
1344
|
+
else:
|
|
1345
|
+
with p.group(4, 'CrossRatios(',')'):
|
|
1346
|
+
p.breakable()
|
|
1347
|
+
p.pretty(dict(self))
|
|
1348
|
+
p.text(',')
|
|
1349
|
+
p.breakable()
|
|
1350
|
+
p.text('is_numerical = %r, ...' % self._is_numerical)
|
|
1351
|
+
|
|
1352
|
+
def get_manifold(self):
|
|
1353
|
+
"""
|
|
1354
|
+
Get the manifold for which this structure represents a solution
|
|
1355
|
+
to the gluing equations.
|
|
1356
|
+
"""
|
|
1357
|
+
|
|
1358
|
+
return self._manifold_thunk()
|
|
1359
|
+
|
|
1360
|
+
def num_tetrahedra(self):
|
|
1361
|
+
"""
|
|
1362
|
+
The number of tetrahedra for which we have cross ratios.
|
|
1363
|
+
"""
|
|
1364
|
+
return _num_tetrahedra(self)
|
|
1365
|
+
|
|
1366
|
+
def N(self):
|
|
1367
|
+
"""
|
|
1368
|
+
Get the N such that these cross ratios are for
|
|
1369
|
+
SL/PSL(N,C)-representations.
|
|
1370
|
+
"""
|
|
1371
|
+
|
|
1372
|
+
return _N_for_shapes(self)
|
|
1373
|
+
|
|
1374
|
+
def numerical(self):
|
|
1375
|
+
"""
|
|
1376
|
+
Turn exact solutions into numerical solutions using pari. Similar to
|
|
1377
|
+
numerical() of PtolemyCoordinates. See help(ptolemy.PtolemyCoordinates)
|
|
1378
|
+
for example.
|
|
1379
|
+
"""
|
|
1380
|
+
if self._is_numerical:
|
|
1381
|
+
return self
|
|
1382
|
+
return ZeroDimensionalComponent([
|
|
1383
|
+
CrossRatios(d, is_numerical=True,
|
|
1384
|
+
manifold_thunk=self._manifold_thunk)
|
|
1385
|
+
for d in _to_numerical(self) ])
|
|
1386
|
+
|
|
1387
|
+
def to_PUR(self):
|
|
1388
|
+
"""
|
|
1389
|
+
If any Ptolemy coordinates are given as Rational Univariate
|
|
1390
|
+
Representation, convert them to Polynomial Univariate Representation and
|
|
1391
|
+
return the result.
|
|
1392
|
+
|
|
1393
|
+
See to_PUR of RUR.
|
|
1394
|
+
|
|
1395
|
+
This conversion might lead to very large coefficients.
|
|
1396
|
+
"""
|
|
1397
|
+
|
|
1398
|
+
return CrossRatios(
|
|
1399
|
+
_apply_to_RURs(self, RUR.to_PUR),
|
|
1400
|
+
is_numerical=self._is_numerical,
|
|
1401
|
+
manifold_thunk=self._manifold_thunk)
|
|
1402
|
+
|
|
1403
|
+
def multiply_terms_in_RUR(self):
|
|
1404
|
+
"""
|
|
1405
|
+
If a cross ratio is given as Rational Univariate Representation
|
|
1406
|
+
with numerator and denominator being a product, multiply the terms and
|
|
1407
|
+
return the result.
|
|
1408
|
+
|
|
1409
|
+
See multiply_terms of RUR.
|
|
1410
|
+
|
|
1411
|
+
This loses information about how the numerator and denominator are
|
|
1412
|
+
factorised.
|
|
1413
|
+
"""
|
|
1414
|
+
|
|
1415
|
+
return CrossRatios(
|
|
1416
|
+
_apply_to_RURs(self, RUR.multiply_terms),
|
|
1417
|
+
is_numerical=self._is_numerical,
|
|
1418
|
+
manifold_thunk=self._manifold_thunk)
|
|
1419
|
+
|
|
1420
|
+
def multiply_and_simplify_terms_in_RUR(self):
|
|
1421
|
+
"""
|
|
1422
|
+
If a cross ratio is given as Rational Univariate Representation
|
|
1423
|
+
with numerator and denominator being a product, multiply the terms,
|
|
1424
|
+
reduce the fraction and return the result.
|
|
1425
|
+
|
|
1426
|
+
See multiply_and_simplify_terms of RUR.
|
|
1427
|
+
|
|
1428
|
+
This loses information about how the numerator and denominator are
|
|
1429
|
+
factorised.
|
|
1430
|
+
|
|
1431
|
+
"""
|
|
1432
|
+
|
|
1433
|
+
return CrossRatios(
|
|
1434
|
+
_apply_to_RURs(self, RUR.multiply_and_simplify_terms),
|
|
1435
|
+
is_numerical=self._is_numerical,
|
|
1436
|
+
manifold_thunk=self._manifold_thunk)
|
|
1437
|
+
|
|
1438
|
+
def volume_numerical(self, drop_negative_vols=False):
|
|
1439
|
+
"""
|
|
1440
|
+
Turn into (Galois conjugate) numerical solutions and compute volumes.
|
|
1441
|
+
If already numerical, only compute the one volume.
|
|
1442
|
+
See numerical().
|
|
1443
|
+
|
|
1444
|
+
If drop_negative_vols = True is given as optional argument,
|
|
1445
|
+
only return non-negative volumes.
|
|
1446
|
+
"""
|
|
1447
|
+
if self._is_numerical:
|
|
1448
|
+
return sum([_volume(z) for key, z in list(self.items()) if 'z_' in key])
|
|
1449
|
+
else:
|
|
1450
|
+
vols = ZeroDimensionalComponent(
|
|
1451
|
+
[num.volume_numerical() for num in self.numerical()])
|
|
1452
|
+
if drop_negative_vols:
|
|
1453
|
+
return [vol for vol in vols if vol > -1e-12]
|
|
1454
|
+
return vols
|
|
1455
|
+
|
|
1456
|
+
@staticmethod
|
|
1457
|
+
def _cyclic_three_perm_sign(v0, v1, v2):
|
|
1458
|
+
"""
|
|
1459
|
+
Returns +1 or -1. It is +1 if and only if (v0, v1, v2) is in the
|
|
1460
|
+
orbit of (0, 1, 2) under the A4-action.
|
|
1461
|
+
"""
|
|
1462
|
+
|
|
1463
|
+
for t in [(v0,v1,v2), (v1,v2,v0), (v2,v0,v1)]:
|
|
1464
|
+
if t in [(0,1,2), (1,3,2), (2,3,0), (3,1,0)]:
|
|
1465
|
+
return +1
|
|
1466
|
+
return -1
|
|
1467
|
+
|
|
1468
|
+
def _shape_at_tet_point_and_edge(self, tet, pt, edge):
|
|
1469
|
+
"""
|
|
1470
|
+
Given the index of a tetrahedron and two quadruples (any iterabel) of
|
|
1471
|
+
integers, give the cross ratio at that integral point and edge of that
|
|
1472
|
+
tetrahedron.
|
|
1473
|
+
This method translates the SnapPy conventions of labeling simplices
|
|
1474
|
+
and the conventions in Definition 4.2 of
|
|
1475
|
+
|
|
1476
|
+
Garoufalidis, Goerner, Zickert:
|
|
1477
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1478
|
+
https://arxiv.org/abs/1207.6711
|
|
1479
|
+
"""
|
|
1480
|
+
|
|
1481
|
+
postfix = '_%d%d%d%d' % tuple(pt) + '_%d' % tet
|
|
1482
|
+
|
|
1483
|
+
if tuple(edge) in [(1,1,0,0), (0,0,1,1)]:
|
|
1484
|
+
return self['z' + postfix]
|
|
1485
|
+
|
|
1486
|
+
if tuple(edge) in [(1,0,1,0), (0,1,0,1)]:
|
|
1487
|
+
return self['zp' + postfix]
|
|
1488
|
+
|
|
1489
|
+
if tuple(edge) in [(1,0,0,1), (0,1,1,0)]:
|
|
1490
|
+
return self['zpp' + postfix]
|
|
1491
|
+
|
|
1492
|
+
raise Exception("Invalid edge " + str(edge))
|
|
1493
|
+
|
|
1494
|
+
def x_coordinate(self, tet, pt):
|
|
1495
|
+
"""
|
|
1496
|
+
Returns the X-coordinate for the tetrahedron with index tet
|
|
1497
|
+
at the point pt (quadruple of integers adding up to N).
|
|
1498
|
+
|
|
1499
|
+
See Definition 10.9:
|
|
1500
|
+
Garoufalidis, Goerner, Zickert:
|
|
1501
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1502
|
+
https://arxiv.org/abs/1207.6711
|
|
1503
|
+
"""
|
|
1504
|
+
|
|
1505
|
+
result = 1
|
|
1506
|
+
|
|
1507
|
+
for v0 in range(4):
|
|
1508
|
+
for v1 in range(v0 + 1, 4):
|
|
1509
|
+
e = [ _kronecker_delta(v0, i) +
|
|
1510
|
+
_kronecker_delta(v1, i) for i in range(4) ]
|
|
1511
|
+
p = [ x1 - x2 for x1, x2 in zip(pt, e) ]
|
|
1512
|
+
if all(x >= 0 for x in p):
|
|
1513
|
+
result *= self._shape_at_tet_point_and_edge(tet, p, e)
|
|
1514
|
+
|
|
1515
|
+
return -result
|
|
1516
|
+
|
|
1517
|
+
def _get_identity_matrix(self):
|
|
1518
|
+
|
|
1519
|
+
# Get N
|
|
1520
|
+
N = self.N()
|
|
1521
|
+
|
|
1522
|
+
return [[_kronecker_delta(i, j) for i in range(N)] for j in range(N)]
|
|
1523
|
+
|
|
1524
|
+
def long_edge(self, tet, v0, v1, v2):
|
|
1525
|
+
"""
|
|
1526
|
+
The matrix that labels a long edge starting at vertex (v0, v1, v2)
|
|
1527
|
+
of a doubly truncated simplex corresponding to the ideal tetrahedron
|
|
1528
|
+
with index tet.
|
|
1529
|
+
|
|
1530
|
+
This matrix was labeled alpha^{v0v1v2} in Figure 18 of
|
|
1531
|
+
Garoufalidis, Goerner, Zickert:
|
|
1532
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1533
|
+
https://arxiv.org/abs/1207.6711
|
|
1534
|
+
|
|
1535
|
+
It is computed using equation 10.22.
|
|
1536
|
+
|
|
1537
|
+
The resulting matrix is given as a python list of lists.
|
|
1538
|
+
"""
|
|
1539
|
+
|
|
1540
|
+
# Key for cache
|
|
1541
|
+
key = 'long_edge'
|
|
1542
|
+
|
|
1543
|
+
# Fill cache if necessary
|
|
1544
|
+
if key not in self._edge_cache:
|
|
1545
|
+
|
|
1546
|
+
# Get N
|
|
1547
|
+
N = self.N()
|
|
1548
|
+
|
|
1549
|
+
# It is just the counter diagonal matrix
|
|
1550
|
+
m = [ [ _kronecker_delta(i+j, N-1) for i in range(N) ]
|
|
1551
|
+
for j in range(N)]
|
|
1552
|
+
|
|
1553
|
+
# Set in cache
|
|
1554
|
+
self._edge_cache[key] = m
|
|
1555
|
+
|
|
1556
|
+
return self._edge_cache[key]
|
|
1557
|
+
|
|
1558
|
+
def middle_edge(self, tet, v0, v1, v2):
|
|
1559
|
+
"""
|
|
1560
|
+
The matrix that labels a middle edge starting at vertex (v0, v1, v2)
|
|
1561
|
+
of a doubly truncated simplex corresponding to the ideal tetrahedron
|
|
1562
|
+
with index tet.
|
|
1563
|
+
|
|
1564
|
+
This matrix was labeled beta^{v0v1v2} in Figure 18 of
|
|
1565
|
+
Garoufalidis, Goerner, Zickert:
|
|
1566
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1567
|
+
https://arxiv.org/abs/1207.6711
|
|
1568
|
+
|
|
1569
|
+
It is computed using equation 10.22.
|
|
1570
|
+
|
|
1571
|
+
The resulting matrix is given as a python list of lists.
|
|
1572
|
+
"""
|
|
1573
|
+
|
|
1574
|
+
# Key for the cache
|
|
1575
|
+
key = 'middle_%d_%d%d%d' % (tet, v0, v1, v2)
|
|
1576
|
+
|
|
1577
|
+
# Fill cache if necessary
|
|
1578
|
+
if key not in self._edge_cache:
|
|
1579
|
+
|
|
1580
|
+
# Get N
|
|
1581
|
+
N = self.N()
|
|
1582
|
+
|
|
1583
|
+
# The epsilon permutation sign
|
|
1584
|
+
sgn = CrossRatios._cyclic_three_perm_sign(v0, v1, v2)
|
|
1585
|
+
|
|
1586
|
+
# Start with identity
|
|
1587
|
+
m = self._get_identity_matrix()
|
|
1588
|
+
|
|
1589
|
+
for k in range(1, N):
|
|
1590
|
+
# Compute first product
|
|
1591
|
+
prod1 = self._get_identity_matrix()
|
|
1592
|
+
for i in range(1, N - k + 1):
|
|
1593
|
+
prod1 = matrix.matrix_mult(prod1, _X(N, i, 1))
|
|
1594
|
+
|
|
1595
|
+
# Compute second product
|
|
1596
|
+
prod2 = self._get_identity_matrix()
|
|
1597
|
+
for i in range(1, N - k):
|
|
1598
|
+
pt = [ k * _kronecker_delta(v2, j) +
|
|
1599
|
+
i * _kronecker_delta(v0, j) +
|
|
1600
|
+
(N-k-i) * _kronecker_delta(v1, j)
|
|
1601
|
+
for j in range(4) ]
|
|
1602
|
+
|
|
1603
|
+
# Note that the sgn is different from the paper
|
|
1604
|
+
# because we are using SnapPy conventions for
|
|
1605
|
+
# cross ratios here
|
|
1606
|
+
|
|
1607
|
+
prod2 = matrix.matrix_mult(
|
|
1608
|
+
prod2,
|
|
1609
|
+
_H(N, i, self.x_coordinate(tet, pt) ** -sgn))
|
|
1610
|
+
|
|
1611
|
+
m = matrix.matrix_mult(m,
|
|
1612
|
+
matrix.matrix_mult(prod1, prod2))
|
|
1613
|
+
|
|
1614
|
+
# Matrix from Equation 10.1
|
|
1615
|
+
dpm = [ [ - (-1) ** (N - i) * _kronecker_delta(i, j)
|
|
1616
|
+
for i in range(N) ]
|
|
1617
|
+
for j in range(N) ]
|
|
1618
|
+
|
|
1619
|
+
m = matrix.matrix_mult(m, dpm)
|
|
1620
|
+
|
|
1621
|
+
# Set in cache
|
|
1622
|
+
self._edge_cache[key] = m
|
|
1623
|
+
|
|
1624
|
+
return self._edge_cache[key]
|
|
1625
|
+
|
|
1626
|
+
def short_edge(self, tet, v0, v1, v2):
|
|
1627
|
+
"""
|
|
1628
|
+
The matrix that labels a long edge starting at vertex (v0, v1, v2)
|
|
1629
|
+
of a doubly truncated simplex corresponding to the ideal tetrahedron
|
|
1630
|
+
with index tet.
|
|
1631
|
+
|
|
1632
|
+
This matrix was labeled gamma^{v0v1v2} in Figure 18 of
|
|
1633
|
+
Garoufalidis, Goerner, Zickert:
|
|
1634
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
1635
|
+
https://arxiv.org/abs/1207.6711
|
|
1636
|
+
|
|
1637
|
+
It is computed using equation 10.22.
|
|
1638
|
+
|
|
1639
|
+
The resulting matrix is given as a python list of lists.
|
|
1640
|
+
"""
|
|
1641
|
+
|
|
1642
|
+
# Key for the cache
|
|
1643
|
+
key = 'short_%d_%d%d%d' % (tet, v0, v1, v2)
|
|
1644
|
+
|
|
1645
|
+
# Fill cache if necessary
|
|
1646
|
+
if key not in self._edge_cache:
|
|
1647
|
+
|
|
1648
|
+
edge = [ _kronecker_delta(v0, i) +
|
|
1649
|
+
_kronecker_delta(v1, i) for i in range(4) ]
|
|
1650
|
+
|
|
1651
|
+
# The epsilon permutation sign
|
|
1652
|
+
sgn = CrossRatios._cyclic_three_perm_sign(v0, v1, v2)
|
|
1653
|
+
|
|
1654
|
+
# Get N
|
|
1655
|
+
N = self.N()
|
|
1656
|
+
|
|
1657
|
+
# Start with identity
|
|
1658
|
+
m = self._get_identity_matrix()
|
|
1659
|
+
|
|
1660
|
+
# Compute the product in equation 10.22
|
|
1661
|
+
for a0 in range(N-1):
|
|
1662
|
+
a1 = N - 2 - a0
|
|
1663
|
+
pt = [ a0 * _kronecker_delta(v0, i) +
|
|
1664
|
+
a1 * _kronecker_delta(v1, i) for i in range(4) ]
|
|
1665
|
+
|
|
1666
|
+
cross_ratio = self._shape_at_tet_point_and_edge(tet, pt, edge)
|
|
1667
|
+
|
|
1668
|
+
# Multiply result with the H matrix
|
|
1669
|
+
|
|
1670
|
+
# Note that the sgn is different from the paper
|
|
1671
|
+
# because we are using SnapPy conventions for
|
|
1672
|
+
# cross ratios here
|
|
1673
|
+
|
|
1674
|
+
m = matrix.matrix_mult(m, _H(N, a0 + 1, cross_ratio ** sgn))
|
|
1675
|
+
|
|
1676
|
+
# Fill cache
|
|
1677
|
+
self._edge_cache[key] = m
|
|
1678
|
+
|
|
1679
|
+
return self._edge_cache[key]
|
|
1680
|
+
|
|
1681
|
+
def _init_matrix_and_inverse_cache(self):
|
|
1682
|
+
# Fill the caches of matrices corresponding to the
|
|
1683
|
+
# fundamental group generators and their inverses
|
|
1684
|
+
|
|
1685
|
+
if self._matrix_cache and self._inverse_matrix_cache:
|
|
1686
|
+
return
|
|
1687
|
+
|
|
1688
|
+
# Compute all the matrices for the generators and there inverses
|
|
1689
|
+
# The long edges of the doubly truncated simplex are all unit
|
|
1690
|
+
# counter-diagonal so they do not increase the
|
|
1691
|
+
# size of any polynomial coefficients. We thus don't give them penalty.
|
|
1692
|
+
self._matrix_cache, self._inverse_matrix_cache = (
|
|
1693
|
+
findLoops.images_of_original_generators(self,
|
|
1694
|
+
penalties=(0, 1, 1)))
|
|
1695
|
+
|
|
1696
|
+
def evaluate_word(self, word, G=None):
|
|
1697
|
+
"""
|
|
1698
|
+
Given a word in the generators of the fundamental group,
|
|
1699
|
+
compute the corresponding matrix. By default, these are the
|
|
1700
|
+
generators of the unsimplified presentation of the fundamental
|
|
1701
|
+
group. An optional SnapPy fundamental group can be given if the
|
|
1702
|
+
words are in generators of a different presentation, e.g.,
|
|
1703
|
+
c.evaluate_word(word, M.fundamental_group(True)) to
|
|
1704
|
+
evaluate a word in the simplified presentation returned by
|
|
1705
|
+
M.fundamental_group(True).
|
|
1706
|
+
|
|
1707
|
+
For now, the matrix is returned as list of lists.
|
|
1708
|
+
"""
|
|
1709
|
+
|
|
1710
|
+
# Init the matrices corresponding to generators
|
|
1711
|
+
self._init_matrix_and_inverse_cache()
|
|
1712
|
+
|
|
1713
|
+
return findLoops.evaluate_word(
|
|
1714
|
+
self._get_identity_matrix(),
|
|
1715
|
+
self._matrix_cache,
|
|
1716
|
+
self._inverse_matrix_cache,
|
|
1717
|
+
word,
|
|
1718
|
+
G)
|
|
1719
|
+
|
|
1720
|
+
def check_against_manifold(self, M=None, epsilon=None):
|
|
1721
|
+
"""
|
|
1722
|
+
Checks that the given solution really is a solution to the PGL(N,C) gluing
|
|
1723
|
+
equations of a manifold. Usage similar to check_against_manifold of
|
|
1724
|
+
PtolemyCoordinates. See help(ptolemy.PtolemtyCoordinates) for example.
|
|
1725
|
+
|
|
1726
|
+
=== Arguments ===
|
|
1727
|
+
|
|
1728
|
+
M --- manifold to check this for
|
|
1729
|
+
epsilon --- maximal allowed error when checking the relations, use
|
|
1730
|
+
None for exact comparison.
|
|
1731
|
+
"""
|
|
1732
|
+
if M is None:
|
|
1733
|
+
M = self.get_manifold()
|
|
1734
|
+
|
|
1735
|
+
if M is None:
|
|
1736
|
+
raise Exception("Need to give manifold")
|
|
1737
|
+
|
|
1738
|
+
some_z = list(self.keys())[0]
|
|
1739
|
+
variable_name, index, tet_index = some_z.split('_')
|
|
1740
|
+
if variable_name not in ['z', 'zp', 'zpp']:
|
|
1741
|
+
raise Exception("Variable not z, zp, or, zpp")
|
|
1742
|
+
if len(index) != 4:
|
|
1743
|
+
raise Exception("Not 4 indices")
|
|
1744
|
+
N = sum([int(x) for x in index]) + 2
|
|
1745
|
+
|
|
1746
|
+
matrix_with_explanations = M.gluing_equations_pgl(
|
|
1747
|
+
N, equation_type='all')
|
|
1748
|
+
|
|
1749
|
+
matrix = matrix_with_explanations.matrix
|
|
1750
|
+
rows = matrix_with_explanations.explain_rows
|
|
1751
|
+
cols = matrix_with_explanations.explain_columns
|
|
1752
|
+
|
|
1753
|
+
for row in range(len(rows)):
|
|
1754
|
+
product = 1
|
|
1755
|
+
for col in range(len(cols)):
|
|
1756
|
+
cross_ratio_variable = cols[col]
|
|
1757
|
+
cross_ratio_value = self[cross_ratio_variable]
|
|
1758
|
+
product = product * (cross_ratio_value ** matrix[row,col])
|
|
1759
|
+
_check_relation(
|
|
1760
|
+
product - 1,
|
|
1761
|
+
epsilon,
|
|
1762
|
+
"Gluing equation %s" % rows[row])
|
|
1763
|
+
|
|
1764
|
+
def induced_representation(self, N):
|
|
1765
|
+
"""
|
|
1766
|
+
Given a PSL(2,C) representation constructs the induced representation
|
|
1767
|
+
for the given N.
|
|
1768
|
+
The induced representation is in SL(N,C) if N is odd and
|
|
1769
|
+
SL(N,C) / {+1,-1} if N is even and is described in the Introduction of
|
|
1770
|
+
Garoufalidis, Thurston, Zickert
|
|
1771
|
+
The Complex Volume of SL(n,C)-Representations of 3-Manifolds
|
|
1772
|
+
https://arxiv.org/abs/1111.2828
|
|
1773
|
+
|
|
1774
|
+
There is a canonical group homomorphism SL(2,C)->SL(N,C) coming from
|
|
1775
|
+
the the natural SL(2,C)-action on the vector space Sym^{N-1}(C^2).
|
|
1776
|
+
This homomorphisms decends to a homomorphism from PSL(2,C) if one
|
|
1777
|
+
divides the right side by {+1,-1} when N is even.
|
|
1778
|
+
Composing a representation with this homomorphism gives the induced
|
|
1779
|
+
representation.
|
|
1780
|
+
"""
|
|
1781
|
+
|
|
1782
|
+
num_tetrahedra = self.num_tetrahedra()
|
|
1783
|
+
|
|
1784
|
+
if self.N() != 2:
|
|
1785
|
+
raise Exception(
|
|
1786
|
+
"Cross ratios need to come from a PSL(2,C) representation")
|
|
1787
|
+
|
|
1788
|
+
def key_value_pair(v, t, index):
|
|
1789
|
+
new_key = v + '_%d%d%d%d' % tuple(index) + '_%d' % t
|
|
1790
|
+
old_key = v + '_0000' + '_%d' % t
|
|
1791
|
+
return (new_key, self[old_key])
|
|
1792
|
+
|
|
1793
|
+
d = dict(
|
|
1794
|
+
[ key_value_pair(v, t, index)
|
|
1795
|
+
for v in ['z', 'zp', 'zpp']
|
|
1796
|
+
for t in range(num_tetrahedra)
|
|
1797
|
+
for index in utilities.quadruples_with_fixed_sum_iterator(N-2)])
|
|
1798
|
+
|
|
1799
|
+
return CrossRatios(d,
|
|
1800
|
+
is_numerical=self._is_numerical,
|
|
1801
|
+
manifold_thunk=self._manifold_thunk)
|
|
1802
|
+
|
|
1803
|
+
def is_real(self, epsilon):
|
|
1804
|
+
"""
|
|
1805
|
+
Returns True if all cross ratios are real (have absolute imaginary
|
|
1806
|
+
part < epsilon where epsilon is given as argument).
|
|
1807
|
+
This means that the corresponding representation is in PSL(N,R).
|
|
1808
|
+
"""
|
|
1809
|
+
|
|
1810
|
+
if not self._is_numerical:
|
|
1811
|
+
raise NumericalMethodError("is_real")
|
|
1812
|
+
|
|
1813
|
+
for v in self.values():
|
|
1814
|
+
if v.imag().abs() > epsilon:
|
|
1815
|
+
return False
|
|
1816
|
+
return True
|
|
1817
|
+
|
|
1818
|
+
def is_induced_from_psl2(self, epsilon=None):
|
|
1819
|
+
"""
|
|
1820
|
+
For each simplex and each edges, checks that all cross ratios of that
|
|
1821
|
+
simplex that are parallel to that each are the same (maximal absolute
|
|
1822
|
+
difference is the epsilon given as argument).
|
|
1823
|
+
This means that the corresponding representation is induced by a
|
|
1824
|
+
PSL(2,C) representation.
|
|
1825
|
+
"""
|
|
1826
|
+
|
|
1827
|
+
# Create an auxiliary dictionary containing one z, zp, zpp per tet
|
|
1828
|
+
d = { }
|
|
1829
|
+
|
|
1830
|
+
for key, value in self.items():
|
|
1831
|
+
variable_name, index, tet_index = key.split('_')
|
|
1832
|
+
if variable_name not in ['z', 'zp', 'zpp']:
|
|
1833
|
+
raise Exception("Variable not z, zp, or, zpp")
|
|
1834
|
+
if len(index) != 4:
|
|
1835
|
+
raise Exception("Not 4 indices")
|
|
1836
|
+
|
|
1837
|
+
# The key in the auxiliary dictionary
|
|
1838
|
+
short_key = variable_name + '_' + tet_index
|
|
1839
|
+
|
|
1840
|
+
# Get the old value in the auxiliary dictionary
|
|
1841
|
+
old_value = d.setdefault(short_key, value)
|
|
1842
|
+
|
|
1843
|
+
if epsilon is None:
|
|
1844
|
+
if value != old_value:
|
|
1845
|
+
return False
|
|
1846
|
+
else:
|
|
1847
|
+
if (value - old_value).abs() > epsilon:
|
|
1848
|
+
return False
|
|
1849
|
+
|
|
1850
|
+
return True
|
|
1851
|
+
|
|
1852
|
+
def is_pu_2_1_representation(self, epsilon, epsilon2=None):
|
|
1853
|
+
r"""
|
|
1854
|
+
Returns True if the representation is also a
|
|
1855
|
+
PU(2,1)-representation. This uses Proposition 3.5 and the
|
|
1856
|
+
remark following that proposition in [FKR2013]_.
|
|
1857
|
+
|
|
1858
|
+
If a condition given in that Proposition is violated, the method returns
|
|
1859
|
+
an object whose Boolean value is still False and that indicates which condition
|
|
1860
|
+
was violated. Thus, this method can still be used in ``if`` statements.
|
|
1861
|
+
|
|
1862
|
+
The method tests the following complex equalities and inequalities:
|
|
1863
|
+
|
|
1864
|
+
* the three complex equations given in (3.5.1) of [FKR2013]_.
|
|
1865
|
+
* the inequality z\ :sub:`ijl` :math:`\\not=` -1.
|
|
1866
|
+
|
|
1867
|
+
**Remark:** It does not check whether all z\ :sub:`ij` * z\ :sub:`ji` are real or
|
|
1868
|
+
not as these are still valid CR configurations, see the remark following
|
|
1869
|
+
Proposition 3.5.
|
|
1870
|
+
|
|
1871
|
+
The user has to supply an epsilon: an equality/inequality is considered
|
|
1872
|
+
to be true if and only if the absolute value | LHS - RHS | of difference between the
|
|
1873
|
+
left and right hand side is less/greater than epsilon.
|
|
1874
|
+
|
|
1875
|
+
The user can supply another parameter, epsilon2. If any | LHS - RHS | is in
|
|
1876
|
+
the interval [epsilon, epsilon2], this method fails with an exception
|
|
1877
|
+
as the value of | LHS - RHS | is an ambiguous interval where
|
|
1878
|
+
it is unclear whether inequality fails to hold because it truly does
|
|
1879
|
+
hold or just because of numerical noise.
|
|
1880
|
+
"""
|
|
1881
|
+
|
|
1882
|
+
def is_zero(val):
|
|
1883
|
+
if val.abs() < epsilon:
|
|
1884
|
+
return True
|
|
1885
|
+
if epsilon2:
|
|
1886
|
+
if not epsilon2 < val.abs():
|
|
1887
|
+
raise Exception(
|
|
1888
|
+
"Ambiguous error when determining whether a "
|
|
1889
|
+
"condition was fulfilled or nor: %s" % val)
|
|
1890
|
+
return False
|
|
1891
|
+
|
|
1892
|
+
def mainCondition(key_zij, key_zji, key_zkl, key_zlk):
|
|
1893
|
+
|
|
1894
|
+
lhs = (self[key_zij] * self[key_zji])
|
|
1895
|
+
rhs = (self[key_zkl] * self[key_zlk]).conj()
|
|
1896
|
+
|
|
1897
|
+
if not is_zero(lhs - rhs):
|
|
1898
|
+
reason = "%s * %s = conjugate(%s * %s) not fulfilled" % (
|
|
1899
|
+
key_zij, key_zji, key_zkl, key_zlk)
|
|
1900
|
+
return NotPU21Representation(reason)
|
|
1901
|
+
|
|
1902
|
+
return True
|
|
1903
|
+
|
|
1904
|
+
def tripleRatioCondition(key_zji, key_zki, key_zli):
|
|
1905
|
+
|
|
1906
|
+
tripleRatio = self[key_zji] * self[key_zki] * self[key_zli]
|
|
1907
|
+
|
|
1908
|
+
if is_zero(tripleRatio - 1):
|
|
1909
|
+
reason = 'Triple ratio %s * %s * %s = 1' % (
|
|
1910
|
+
key_zji, key_zki, key_zli)
|
|
1911
|
+
return NotPU21Representation(reason)
|
|
1912
|
+
|
|
1913
|
+
return True
|
|
1914
|
+
|
|
1915
|
+
if self.N() != 3:
|
|
1916
|
+
raise Exception("PU(2,1)-representations only allowed for N = 3")
|
|
1917
|
+
|
|
1918
|
+
if not self._is_numerical:
|
|
1919
|
+
raise NumericalMethodError("is_pu_2_1_representation")
|
|
1920
|
+
|
|
1921
|
+
for t in range(self.num_tetrahedra()):
|
|
1922
|
+
|
|
1923
|
+
m0 = mainCondition("z_1000_%d" % t, "z_0100_%d" % t,
|
|
1924
|
+
"z_0010_%d" % t, "z_0001_%d" % t)
|
|
1925
|
+
if not m0:
|
|
1926
|
+
return m0
|
|
1927
|
+
|
|
1928
|
+
m1 = mainCondition("zp_1000_%d" % t, "zp_0010_%d" % t,
|
|
1929
|
+
"zp_0100_%d" % t, "zp_0001_%d" % t)
|
|
1930
|
+
if not m1:
|
|
1931
|
+
return m1
|
|
1932
|
+
|
|
1933
|
+
m2 = mainCondition("zpp_1000_%d" % t, "zpp_0001_%d" % t,
|
|
1934
|
+
"zpp_0100_%d" % t, "zpp_0010_%d" % t)
|
|
1935
|
+
if not m2:
|
|
1936
|
+
return m2
|
|
1937
|
+
|
|
1938
|
+
t0 = tripleRatioCondition( "z_0100_%d" % t,
|
|
1939
|
+
"zp_0010_%d" % t,
|
|
1940
|
+
"zpp_0001_%d" % t)
|
|
1941
|
+
if not t0:
|
|
1942
|
+
return t0
|
|
1943
|
+
|
|
1944
|
+
t1 = tripleRatioCondition( "z_1000_%d" % t,
|
|
1945
|
+
"zp_0001_%d" % t,
|
|
1946
|
+
"zpp_0010_%d" % t)
|
|
1947
|
+
if not t1:
|
|
1948
|
+
return t1
|
|
1949
|
+
|
|
1950
|
+
t2 = tripleRatioCondition( "z_0001_%d" % t,
|
|
1951
|
+
"zp_1000_%d" % t,
|
|
1952
|
+
"zpp_0100_%d" % t)
|
|
1953
|
+
if not t2:
|
|
1954
|
+
return t2
|
|
1955
|
+
|
|
1956
|
+
t3 = tripleRatioCondition( "z_0010_%d" % t,
|
|
1957
|
+
"zp_0100_%d" % t,
|
|
1958
|
+
"zpp_1000_%d" % t)
|
|
1959
|
+
if not t3:
|
|
1960
|
+
return t3
|
|
1961
|
+
|
|
1962
|
+
return True
|
|
1963
|
+
|
|
1964
|
+
def is_geometric(self, epsilon=1e-6):
|
|
1965
|
+
"""
|
|
1966
|
+
Returns true if all shapes corresponding to this solution have positive
|
|
1967
|
+
imaginary part.
|
|
1968
|
+
|
|
1969
|
+
If the solutions are exact, it returns true if one of the corresponding
|
|
1970
|
+
numerical solutions is geometric.
|
|
1971
|
+
|
|
1972
|
+
An optional epsilon can be given. An imaginary part of a shape is
|
|
1973
|
+
considered positive if it is larger than this epsilon.
|
|
1974
|
+
"""
|
|
1975
|
+
|
|
1976
|
+
if self._is_numerical:
|
|
1977
|
+
for v in self.values():
|
|
1978
|
+
if not v.imag() > 0:
|
|
1979
|
+
return False
|
|
1980
|
+
return True
|
|
1981
|
+
else:
|
|
1982
|
+
for numerical_sol in self.numerical():
|
|
1983
|
+
if numerical_sol.is_geometric(epsilon):
|
|
1984
|
+
return True
|
|
1985
|
+
return False
|
|
1986
|
+
|
|
1987
|
+
|
|
1988
|
+
def _ptolemy_to_cross_ratio(solution_dict,
|
|
1989
|
+
branch_factor=1,
|
|
1990
|
+
non_trivial_generalized_obstruction_class=False,
|
|
1991
|
+
as_flattenings=False):
|
|
1992
|
+
|
|
1993
|
+
N, has_obstruction = _N_and_has_obstruction_for_ptolemys(solution_dict)
|
|
1994
|
+
num_tets = _num_tetrahedra(solution_dict)
|
|
1995
|
+
|
|
1996
|
+
if N % 2:
|
|
1997
|
+
evenN = 2 * N
|
|
1998
|
+
else:
|
|
1999
|
+
evenN = N
|
|
2000
|
+
|
|
2001
|
+
if not non_trivial_generalized_obstruction_class:
|
|
2002
|
+
evenN = 2
|
|
2003
|
+
|
|
2004
|
+
if as_flattenings:
|
|
2005
|
+
f = pari('2 * Pi * I') / evenN
|
|
2006
|
+
|
|
2007
|
+
def compute_cross_ratios_and_flattenings(tet, index):
|
|
2008
|
+
def get_ptolemy_coordinate(addl_index):
|
|
2009
|
+
total_index = matrix.vector_add(index, addl_index)
|
|
2010
|
+
key = "c_%d%d%d%d" % tuple(total_index) + "_%d" % tet
|
|
2011
|
+
return solution_dict[key]
|
|
2012
|
+
|
|
2013
|
+
def get_obstruction_variable(face):
|
|
2014
|
+
key = "s_%d_%d" % (face, tet)
|
|
2015
|
+
return solution_dict[key]
|
|
2016
|
+
|
|
2017
|
+
c1010 = get_ptolemy_coordinate((1,0,1,0))
|
|
2018
|
+
c1001 = get_ptolemy_coordinate((1,0,0,1))
|
|
2019
|
+
c0110 = get_ptolemy_coordinate((0,1,1,0))
|
|
2020
|
+
c0101 = get_ptolemy_coordinate((0,1,0,1))
|
|
2021
|
+
c1100 = get_ptolemy_coordinate((1,1,0,0))
|
|
2022
|
+
c0011 = get_ptolemy_coordinate((0,0,1,1))
|
|
2023
|
+
|
|
2024
|
+
z = (c1010 * c0101) / (c1001 * c0110)
|
|
2025
|
+
zp = - (c1001 * c0110) / (c1100 * c0011)
|
|
2026
|
+
zpp = (c1100 * c0011) / (c1010 * c0101)
|
|
2027
|
+
|
|
2028
|
+
if has_obstruction:
|
|
2029
|
+
s0 = get_obstruction_variable(0)
|
|
2030
|
+
s1 = get_obstruction_variable(1)
|
|
2031
|
+
s2 = get_obstruction_variable(2)
|
|
2032
|
+
s3 = get_obstruction_variable(3)
|
|
2033
|
+
z = s0 * s1 * z
|
|
2034
|
+
zp = s0 * s2 * zp
|
|
2035
|
+
zpp = s0 * s3 * zpp
|
|
2036
|
+
|
|
2037
|
+
variable_end = '_%d%d%d%d' % tuple(index) + '_%d' % tet
|
|
2038
|
+
|
|
2039
|
+
if as_flattenings:
|
|
2040
|
+
def make_triple(w, z):
|
|
2041
|
+
z = _convert_to_pari_float(z)
|
|
2042
|
+
return (w, z, ((w - z .log()) / f).round())
|
|
2043
|
+
|
|
2044
|
+
w = _compute_flattening(c1010, c0101, c1001, c0110,
|
|
2045
|
+
branch_factor, evenN)
|
|
2046
|
+
wp = _compute_flattening(c1001, c0110, c1100, c0011,
|
|
2047
|
+
branch_factor, evenN)
|
|
2048
|
+
wpp = _compute_flattening(c1100, c0011, c1010, c0101,
|
|
2049
|
+
branch_factor, evenN)
|
|
2050
|
+
|
|
2051
|
+
return [
|
|
2052
|
+
('z' + variable_end, make_triple(w ,z )),
|
|
2053
|
+
('zp' + variable_end, make_triple(wp ,zp )),
|
|
2054
|
+
('zpp' + variable_end, make_triple(wpp,zpp)) ]
|
|
2055
|
+
|
|
2056
|
+
else:
|
|
2057
|
+
return [
|
|
2058
|
+
('z' + variable_end, z),
|
|
2059
|
+
('zp' + variable_end, zp),
|
|
2060
|
+
('zpp' + variable_end, zpp) ]
|
|
2061
|
+
|
|
2062
|
+
return dict(
|
|
2063
|
+
sum([compute_cross_ratios_and_flattenings(tet,index)
|
|
2064
|
+
for tet in range(num_tets)
|
|
2065
|
+
for index in utilities.quadruples_with_fixed_sum_iterator(N - 2)],
|
|
2066
|
+
[])), evenN
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
def _num_tetrahedra(solution_dict):
|
|
2070
|
+
return max( [ int(key.split('_')[-1])
|
|
2071
|
+
for key in solution_dict.keys() ] ) + 1
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
def _N_for_shapes(solution_dict):
|
|
2075
|
+
|
|
2076
|
+
def get_N(key):
|
|
2077
|
+
m = re.match(r'zp{0,2}_(\d{4})_\d+$', key)
|
|
2078
|
+
if not m:
|
|
2079
|
+
raise Exception("Not a valid shape key: '%s'" % key)
|
|
2080
|
+
return sum([int(char) for char in m.group(1)]) + 2
|
|
2081
|
+
|
|
2082
|
+
l = [ get_N(key) for key in solution_dict.keys() ]
|
|
2083
|
+
if not len(set(l)) == 1:
|
|
2084
|
+
raise Exception("Shape keys for different N")
|
|
2085
|
+
|
|
2086
|
+
return l[0]
|
|
2087
|
+
|
|
2088
|
+
|
|
2089
|
+
def _N_and_has_obstruction_for_ptolemys(solution_dict):
|
|
2090
|
+
|
|
2091
|
+
def get_N(key):
|
|
2092
|
+
m = re.match(r'c_(\d{4})_\d+$', key)
|
|
2093
|
+
if not m:
|
|
2094
|
+
raise Exception("Not a valid Ptolemy key: '%s'" % key)
|
|
2095
|
+
return sum([int(char) for char in m.group(1)])
|
|
2096
|
+
|
|
2097
|
+
has_obstruction = False
|
|
2098
|
+
|
|
2099
|
+
l = set()
|
|
2100
|
+
for key in solution_dict.keys():
|
|
2101
|
+
if re.match(r's_\d_\d+$', key):
|
|
2102
|
+
has_obstruction = True
|
|
2103
|
+
else:
|
|
2104
|
+
l.add(get_N(key))
|
|
2105
|
+
|
|
2106
|
+
if not len(l) == 1:
|
|
2107
|
+
raise Exception("Ptolemy keys for different N")
|
|
2108
|
+
|
|
2109
|
+
for N in l:
|
|
2110
|
+
return N, has_obstruction
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
def _get_number_field(d):
|
|
2114
|
+
for value in d.values():
|
|
2115
|
+
|
|
2116
|
+
if isinstance(value, RUR):
|
|
2117
|
+
nf = value.number_field()
|
|
2118
|
+
if nf:
|
|
2119
|
+
return nf
|
|
2120
|
+
|
|
2121
|
+
if type(value) == Gen and value.type() == 't_POLMOD':
|
|
2122
|
+
return value.mod()
|
|
2123
|
+
|
|
2124
|
+
return None
|
|
2125
|
+
|
|
2126
|
+
|
|
2127
|
+
def _evaluate_at_root(p, root):
|
|
2128
|
+
|
|
2129
|
+
if type(p) == Gen and p.type() == 't_POLMOD':
|
|
2130
|
+
return p.lift().substpol('x', root)
|
|
2131
|
+
|
|
2132
|
+
if isinstance(p, RUR):
|
|
2133
|
+
return p.evaluate_at_root(root)
|
|
2134
|
+
|
|
2135
|
+
return p
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
def _to_numerical(d):
|
|
2139
|
+
|
|
2140
|
+
number_field = _get_number_field(d)
|
|
2141
|
+
|
|
2142
|
+
if number_field is None:
|
|
2143
|
+
roots = [ pari(0) ]
|
|
2144
|
+
else:
|
|
2145
|
+
# Bug in cypari: pari(str(number_field)).polroots()
|
|
2146
|
+
# gives less precision
|
|
2147
|
+
roots = pari('polroots(%s)' % number_field)
|
|
2148
|
+
|
|
2149
|
+
def evaluate_all_for_root(root):
|
|
2150
|
+
|
|
2151
|
+
def evaluate_key_for_root(key, value):
|
|
2152
|
+
|
|
2153
|
+
v = _evaluate_at_root(value, root)
|
|
2154
|
+
|
|
2155
|
+
if key[:2] == 'z_':
|
|
2156
|
+
z = v
|
|
2157
|
+
zp = 1 / (1 - z)
|
|
2158
|
+
zpp = 1 - 1 / z
|
|
2159
|
+
|
|
2160
|
+
return [(key, z),
|
|
2161
|
+
('zp_' + key[2:], zp),
|
|
2162
|
+
('zpp_' + key[2:], zpp)]
|
|
2163
|
+
elif key[:3] == 'zp_' or key[:4] == 'zpp_':
|
|
2164
|
+
return []
|
|
2165
|
+
else:
|
|
2166
|
+
return [(key, v)]
|
|
2167
|
+
|
|
2168
|
+
return dict(sum(
|
|
2169
|
+
[ evaluate_key_for_root(key, value)
|
|
2170
|
+
for key, value in d.items() ], []))
|
|
2171
|
+
|
|
2172
|
+
return [ evaluate_all_for_root(root) for root in roots ]
|
|
2173
|
+
|
|
2174
|
+
|
|
2175
|
+
def _apply_to_RURs(d, RUR_method):
|
|
2176
|
+
|
|
2177
|
+
def _apply_to_RUR(v):
|
|
2178
|
+
if isinstance(v, RUR):
|
|
2179
|
+
return RUR_method(v)
|
|
2180
|
+
return v
|
|
2181
|
+
|
|
2182
|
+
return {k: _apply_to_RUR(v) for k, v in d.items()}
|
|
2183
|
+
|
|
2184
|
+
|
|
2185
|
+
def _convert_to_pari_float(z):
|
|
2186
|
+
|
|
2187
|
+
if type(z) == Gen and z.type() in ['t_INT', 't_FRAC']:
|
|
2188
|
+
return z * pari('1.0')
|
|
2189
|
+
|
|
2190
|
+
return pari(z)
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
def _compute_flattening(a, b, c, d, branch_factor, N=2):
|
|
2194
|
+
|
|
2195
|
+
PiMinusEpsilon = pari(3.141592)
|
|
2196
|
+
|
|
2197
|
+
def safe_log(z):
|
|
2198
|
+
|
|
2199
|
+
l = (branch_factor * z**N).log()
|
|
2200
|
+
|
|
2201
|
+
if l.imag().abs() > PiMinusEpsilon:
|
|
2202
|
+
raise LogToCloseToBranchCutError()
|
|
2203
|
+
|
|
2204
|
+
return l / N
|
|
2205
|
+
|
|
2206
|
+
a = _convert_to_pari_float(a)
|
|
2207
|
+
b = _convert_to_pari_float(b)
|
|
2208
|
+
c = _convert_to_pari_float(c)
|
|
2209
|
+
d = _convert_to_pari_float(d)
|
|
2210
|
+
|
|
2211
|
+
w = safe_log(a) + safe_log(b) - safe_log(c) - safe_log(d)
|
|
2212
|
+
|
|
2213
|
+
return w
|
|
2214
|
+
|
|
2215
|
+
# bug in pari
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
def _dilog(z):
|
|
2219
|
+
return pari("dilog(%s)" % z)
|
|
2220
|
+
|
|
2221
|
+
|
|
2222
|
+
def _L_function(zpq_triple, evenN=2):
|
|
2223
|
+
|
|
2224
|
+
z, p, q = zpq_triple
|
|
2225
|
+
|
|
2226
|
+
z = _convert_to_pari_float(z)
|
|
2227
|
+
p = _convert_to_pari_float(p)
|
|
2228
|
+
q = _convert_to_pari_float(q)
|
|
2229
|
+
|
|
2230
|
+
f = pari('2 * Pi * I') / evenN
|
|
2231
|
+
Pi2 = pari('Pi * Pi')
|
|
2232
|
+
|
|
2233
|
+
return ( _dilog(z)
|
|
2234
|
+
+ (z.log() + p * f) * ((1-z).log() + q * f) / 2
|
|
2235
|
+
- Pi2 / 6)
|
|
2236
|
+
|
|
2237
|
+
|
|
2238
|
+
def _volume(z):
|
|
2239
|
+
|
|
2240
|
+
z = _convert_to_pari_float(z)
|
|
2241
|
+
|
|
2242
|
+
return (1-z).arg() * z.abs().log() + _dilog(z).imag()
|
|
2243
|
+
|
|
2244
|
+
|
|
2245
|
+
def _kronecker_delta(i, j):
|
|
2246
|
+
"""
|
|
2247
|
+
Kronecker Delta, returns 1 if and only if i and j are equal, other 0.
|
|
2248
|
+
"""
|
|
2249
|
+
|
|
2250
|
+
if i == j:
|
|
2251
|
+
return 1
|
|
2252
|
+
else:
|
|
2253
|
+
return 0
|
|
2254
|
+
|
|
2255
|
+
|
|
2256
|
+
def _X(N, k, v):
|
|
2257
|
+
"""
|
|
2258
|
+
Returns the NxN matrix with off-diagonal entry v at position k, that
|
|
2259
|
+
is the entry at row k and column k+1 is v.
|
|
2260
|
+
|
|
2261
|
+
See (10.2) of
|
|
2262
|
+
Garoufalidis, Goerner, Zickert:
|
|
2263
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
2264
|
+
https://arxiv.org/abs/1207.6711
|
|
2265
|
+
"""
|
|
2266
|
+
|
|
2267
|
+
m = [[_kronecker_delta(i,j) for i in range(N)] for j in range(N)]
|
|
2268
|
+
m[k-1][k] = v
|
|
2269
|
+
return m
|
|
2270
|
+
|
|
2271
|
+
|
|
2272
|
+
def _H(N, k, x):
|
|
2273
|
+
"""
|
|
2274
|
+
Returns the NxN diagonal matrix where the first k diagonal entries are x
|
|
2275
|
+
and all other entries are 1.
|
|
2276
|
+
|
|
2277
|
+
See (10.1) of
|
|
2278
|
+
Garoufalidis, Goerner, Zickert:
|
|
2279
|
+
Gluing Equations for PGL(n,C)-Representations of 3-Manifolds
|
|
2280
|
+
https://arxiv.org/abs/1207.6711
|
|
2281
|
+
"""
|
|
2282
|
+
|
|
2283
|
+
def _entry(i, j):
|
|
2284
|
+
if i != j:
|
|
2285
|
+
return 0
|
|
2286
|
+
if i < k:
|
|
2287
|
+
return x
|
|
2288
|
+
return 1
|
|
2289
|
+
|
|
2290
|
+
return [[_entry(i,j) for i in range(N)] for j in range(N)]
|