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.
Files changed (541) hide show
  1. snappy/CyOpenGL.cpython-310-aarch64-linux-gnu.so +0 -0
  2. snappy/SnapPy.cpython-310-aarch64-linux-gnu.so +0 -0
  3. snappy/SnapPy.ico +0 -0
  4. snappy/SnapPy.png +0 -0
  5. snappy/SnapPyHP.cpython-310-aarch64-linux-gnu.so +0 -0
  6. snappy/__init__.py +534 -0
  7. snappy/app.py +604 -0
  8. snappy/app_menus.py +372 -0
  9. snappy/browser.py +998 -0
  10. snappy/cache.py +25 -0
  11. snappy/canonical.py +249 -0
  12. snappy/cusps/__init__.py +280 -0
  13. snappy/cusps/cusp_area_matrix.py +98 -0
  14. snappy/cusps/cusp_areas_from_matrix.py +96 -0
  15. snappy/cusps/maximal_cusp_area_matrix.py +136 -0
  16. snappy/cusps/short_slopes_for_cusp.py +217 -0
  17. snappy/cusps/test.py +22 -0
  18. snappy/cusps/trig_cusp_area_matrix.py +63 -0
  19. snappy/database.py +454 -0
  20. snappy/db_utilities.py +79 -0
  21. snappy/decorated_isosig.py +717 -0
  22. snappy/dev/__init__.py +0 -0
  23. snappy/dev/extended_ptolemy/__init__.py +8 -0
  24. snappy/dev/extended_ptolemy/closed.py +106 -0
  25. snappy/dev/extended_ptolemy/complexVolumesClosed.py +149 -0
  26. snappy/dev/extended_ptolemy/direct.py +42 -0
  27. snappy/dev/extended_ptolemy/extended.py +406 -0
  28. snappy/dev/extended_ptolemy/giac_helper.py +43 -0
  29. snappy/dev/extended_ptolemy/giac_rur.py +129 -0
  30. snappy/dev/extended_ptolemy/gluing.py +46 -0
  31. snappy/dev/extended_ptolemy/phc_wrapper.py +220 -0
  32. snappy/dev/extended_ptolemy/printMatrices.py +70 -0
  33. snappy/dev/vericlosed/__init__.py +1 -0
  34. snappy/dev/vericlosed/computeApproxHyperbolicStructureNew.py +159 -0
  35. snappy/dev/vericlosed/computeApproxHyperbolicStructureOrb.py +90 -0
  36. snappy/dev/vericlosed/computeVerifiedHyperbolicStructure.py +111 -0
  37. snappy/dev/vericlosed/gimbalLoopFinder.py +130 -0
  38. snappy/dev/vericlosed/hyperbolicStructure.py +313 -0
  39. snappy/dev/vericlosed/krawczykCertifiedEdgeLengthsEngine.py +165 -0
  40. snappy/dev/vericlosed/oneVertexTruncatedComplex.py +122 -0
  41. snappy/dev/vericlosed/orb/__init__.py +1 -0
  42. snappy/dev/vericlosed/orb/orb_solution_for_snappea_finite_triangulation_mac +0 -0
  43. snappy/dev/vericlosed/parseVertexGramMatrixFile.py +47 -0
  44. snappy/dev/vericlosed/polishApproxHyperbolicStructure.py +61 -0
  45. snappy/dev/vericlosed/test.py +54 -0
  46. snappy/dev/vericlosed/truncatedComplex.py +176 -0
  47. snappy/dev/vericlosed/verificationError.py +58 -0
  48. snappy/dev/vericlosed/verifyHyperbolicStructureEngine.py +177 -0
  49. snappy/doc/_images/SnapPy-196.png +0 -0
  50. snappy/doc/_images/m004_paper_plane_on_systole.jpg +0 -0
  51. snappy/doc/_images/m125_paper_plane.jpg +0 -0
  52. snappy/doc/_images/mac.png +0 -0
  53. snappy/doc/_images/o9_00000_systole_paper_plane.jpg +0 -0
  54. snappy/doc/_images/o9_00000_systole_paper_plane_closer.jpg +0 -0
  55. snappy/doc/_images/plink-action.png +0 -0
  56. snappy/doc/_images/ubuntu.png +0 -0
  57. snappy/doc/_images/win7.png +0 -0
  58. snappy/doc/_sources/additional_classes.rst.txt +40 -0
  59. snappy/doc/_sources/bugs.rst.txt +14 -0
  60. snappy/doc/_sources/censuses.rst.txt +52 -0
  61. snappy/doc/_sources/credits.rst.txt +81 -0
  62. snappy/doc/_sources/development.rst.txt +261 -0
  63. snappy/doc/_sources/index.rst.txt +215 -0
  64. snappy/doc/_sources/installing.rst.txt +249 -0
  65. snappy/doc/_sources/manifold.rst.txt +6 -0
  66. snappy/doc/_sources/manifoldhp.rst.txt +46 -0
  67. snappy/doc/_sources/news.rst.txt +425 -0
  68. snappy/doc/_sources/other.rst.txt +25 -0
  69. snappy/doc/_sources/platonic_census.rst.txt +20 -0
  70. snappy/doc/_sources/plink.rst.txt +102 -0
  71. snappy/doc/_sources/ptolemy.rst.txt +66 -0
  72. snappy/doc/_sources/ptolemy_classes.rst.txt +42 -0
  73. snappy/doc/_sources/ptolemy_examples1.rst.txt +298 -0
  74. snappy/doc/_sources/ptolemy_examples2.rst.txt +363 -0
  75. snappy/doc/_sources/ptolemy_examples3.rst.txt +301 -0
  76. snappy/doc/_sources/ptolemy_examples4.rst.txt +61 -0
  77. snappy/doc/_sources/ptolemy_prelim.rst.txt +105 -0
  78. snappy/doc/_sources/screenshots.rst.txt +21 -0
  79. snappy/doc/_sources/snap.rst.txt +87 -0
  80. snappy/doc/_sources/snappy.rst.txt +28 -0
  81. snappy/doc/_sources/spherogram.rst.txt +103 -0
  82. snappy/doc/_sources/todo.rst.txt +47 -0
  83. snappy/doc/_sources/triangulation.rst.txt +11 -0
  84. snappy/doc/_sources/tutorial.rst.txt +49 -0
  85. snappy/doc/_sources/verify.rst.txt +210 -0
  86. snappy/doc/_sources/verify_internals.rst.txt +79 -0
  87. snappy/doc/_static/SnapPy-horizontal-128.png +0 -0
  88. snappy/doc/_static/SnapPy.ico +0 -0
  89. snappy/doc/_static/_sphinx_javascript_frameworks_compat.js +123 -0
  90. snappy/doc/_static/basic.css +906 -0
  91. snappy/doc/_static/css/badge_only.css +1 -0
  92. snappy/doc/_static/css/fonts/Roboto-Slab-Bold.woff +0 -0
  93. snappy/doc/_static/css/fonts/Roboto-Slab-Bold.woff2 +0 -0
  94. snappy/doc/_static/css/fonts/Roboto-Slab-Regular.woff +0 -0
  95. snappy/doc/_static/css/fonts/Roboto-Slab-Regular.woff2 +0 -0
  96. snappy/doc/_static/css/fonts/fontawesome-webfont.eot +0 -0
  97. snappy/doc/_static/css/fonts/fontawesome-webfont.svg +2671 -0
  98. snappy/doc/_static/css/fonts/fontawesome-webfont.ttf +0 -0
  99. snappy/doc/_static/css/fonts/fontawesome-webfont.woff +0 -0
  100. snappy/doc/_static/css/fonts/fontawesome-webfont.woff2 +0 -0
  101. snappy/doc/_static/css/fonts/lato-bold-italic.woff +0 -0
  102. snappy/doc/_static/css/fonts/lato-bold-italic.woff2 +0 -0
  103. snappy/doc/_static/css/fonts/lato-bold.woff +0 -0
  104. snappy/doc/_static/css/fonts/lato-bold.woff2 +0 -0
  105. snappy/doc/_static/css/fonts/lato-normal-italic.woff +0 -0
  106. snappy/doc/_static/css/fonts/lato-normal-italic.woff2 +0 -0
  107. snappy/doc/_static/css/fonts/lato-normal.woff +0 -0
  108. snappy/doc/_static/css/fonts/lato-normal.woff2 +0 -0
  109. snappy/doc/_static/css/theme.css +4 -0
  110. snappy/doc/_static/doctools.js +149 -0
  111. snappy/doc/_static/documentation_options.js +13 -0
  112. snappy/doc/_static/file.png +0 -0
  113. snappy/doc/_static/fonts/Lato/lato-bold.eot +0 -0
  114. snappy/doc/_static/fonts/Lato/lato-bold.ttf +0 -0
  115. snappy/doc/_static/fonts/Lato/lato-bold.woff +0 -0
  116. snappy/doc/_static/fonts/Lato/lato-bold.woff2 +0 -0
  117. snappy/doc/_static/fonts/Lato/lato-bolditalic.eot +0 -0
  118. snappy/doc/_static/fonts/Lato/lato-bolditalic.ttf +0 -0
  119. snappy/doc/_static/fonts/Lato/lato-bolditalic.woff +0 -0
  120. snappy/doc/_static/fonts/Lato/lato-bolditalic.woff2 +0 -0
  121. snappy/doc/_static/fonts/Lato/lato-italic.eot +0 -0
  122. snappy/doc/_static/fonts/Lato/lato-italic.ttf +0 -0
  123. snappy/doc/_static/fonts/Lato/lato-italic.woff +0 -0
  124. snappy/doc/_static/fonts/Lato/lato-italic.woff2 +0 -0
  125. snappy/doc/_static/fonts/Lato/lato-regular.eot +0 -0
  126. snappy/doc/_static/fonts/Lato/lato-regular.ttf +0 -0
  127. snappy/doc/_static/fonts/Lato/lato-regular.woff +0 -0
  128. snappy/doc/_static/fonts/Lato/lato-regular.woff2 +0 -0
  129. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot +0 -0
  130. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf +0 -0
  131. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff +0 -0
  132. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 +0 -0
  133. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot +0 -0
  134. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf +0 -0
  135. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff +0 -0
  136. snappy/doc/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 +0 -0
  137. snappy/doc/_static/jquery.js +2 -0
  138. snappy/doc/_static/js/badge_only.js +1 -0
  139. snappy/doc/_static/js/theme.js +1 -0
  140. snappy/doc/_static/js/versions.js +228 -0
  141. snappy/doc/_static/language_data.js +192 -0
  142. snappy/doc/_static/minus.png +0 -0
  143. snappy/doc/_static/plus.png +0 -0
  144. snappy/doc/_static/pygments.css +75 -0
  145. snappy/doc/_static/searchtools.js +635 -0
  146. snappy/doc/_static/snappy_furo.css +33 -0
  147. snappy/doc/_static/snappy_sphinx_rtd_theme.css +42 -0
  148. snappy/doc/_static/sphinx_highlight.js +154 -0
  149. snappy/doc/additional_classes.html +1500 -0
  150. snappy/doc/bugs.html +132 -0
  151. snappy/doc/censuses.html +453 -0
  152. snappy/doc/credits.html +184 -0
  153. snappy/doc/development.html +385 -0
  154. snappy/doc/doc-latest/additional_classes.html +1500 -0
  155. snappy/doc/doc-latest/bugs.html +132 -0
  156. snappy/doc/doc-latest/censuses.html +453 -0
  157. snappy/doc/doc-latest/credits.html +184 -0
  158. snappy/doc/doc-latest/development.html +385 -0
  159. snappy/doc/doc-latest/genindex.html +1349 -0
  160. snappy/doc/doc-latest/index.html +287 -0
  161. snappy/doc/doc-latest/installing.html +346 -0
  162. snappy/doc/doc-latest/manifold.html +3632 -0
  163. snappy/doc/doc-latest/manifoldhp.html +180 -0
  164. snappy/doc/doc-latest/news.html +438 -0
  165. snappy/doc/doc-latest/objects.inv +0 -0
  166. snappy/doc/doc-latest/other.html +160 -0
  167. snappy/doc/doc-latest/platonic_census.html +376 -0
  168. snappy/doc/doc-latest/plink.html +210 -0
  169. snappy/doc/doc-latest/ptolemy.html +253 -0
  170. snappy/doc/doc-latest/ptolemy_classes.html +1144 -0
  171. snappy/doc/doc-latest/ptolemy_examples1.html +409 -0
  172. snappy/doc/doc-latest/ptolemy_examples2.html +471 -0
  173. snappy/doc/doc-latest/ptolemy_examples3.html +414 -0
  174. snappy/doc/doc-latest/ptolemy_examples4.html +195 -0
  175. snappy/doc/doc-latest/ptolemy_prelim.html +248 -0
  176. snappy/doc/doc-latest/py-modindex.html +165 -0
  177. snappy/doc/doc-latest/screenshots.html +141 -0
  178. snappy/doc/doc-latest/search.html +135 -0
  179. snappy/doc/doc-latest/searchindex.js +1 -0
  180. snappy/doc/doc-latest/snap.html +202 -0
  181. snappy/doc/doc-latest/snappy.html +181 -0
  182. snappy/doc/doc-latest/spherogram.html +1346 -0
  183. snappy/doc/doc-latest/todo.html +166 -0
  184. snappy/doc/doc-latest/triangulation.html +1676 -0
  185. snappy/doc/doc-latest/tutorial.html +159 -0
  186. snappy/doc/doc-latest/verify.html +330 -0
  187. snappy/doc/doc-latest/verify_internals.html +1235 -0
  188. snappy/doc/genindex.html +1349 -0
  189. snappy/doc/index.html +287 -0
  190. snappy/doc/installing.html +346 -0
  191. snappy/doc/manifold.html +3632 -0
  192. snappy/doc/manifoldhp.html +180 -0
  193. snappy/doc/news.html +438 -0
  194. snappy/doc/objects.inv +0 -0
  195. snappy/doc/other.html +160 -0
  196. snappy/doc/platonic_census.html +376 -0
  197. snappy/doc/plink.html +210 -0
  198. snappy/doc/ptolemy.html +253 -0
  199. snappy/doc/ptolemy_classes.html +1144 -0
  200. snappy/doc/ptolemy_examples1.html +409 -0
  201. snappy/doc/ptolemy_examples2.html +471 -0
  202. snappy/doc/ptolemy_examples3.html +414 -0
  203. snappy/doc/ptolemy_examples4.html +195 -0
  204. snappy/doc/ptolemy_prelim.html +248 -0
  205. snappy/doc/py-modindex.html +165 -0
  206. snappy/doc/screenshots.html +141 -0
  207. snappy/doc/search.html +135 -0
  208. snappy/doc/searchindex.js +1 -0
  209. snappy/doc/snap.html +202 -0
  210. snappy/doc/snappy.html +181 -0
  211. snappy/doc/spherogram.html +1346 -0
  212. snappy/doc/todo.html +166 -0
  213. snappy/doc/triangulation.html +1676 -0
  214. snappy/doc/tutorial.html +159 -0
  215. snappy/doc/verify.html +330 -0
  216. snappy/doc/verify_internals.html +1235 -0
  217. snappy/drilling/__init__.py +456 -0
  218. snappy/drilling/barycentric.py +103 -0
  219. snappy/drilling/constants.py +5 -0
  220. snappy/drilling/crush.py +270 -0
  221. snappy/drilling/cusps.py +125 -0
  222. snappy/drilling/debug.py +242 -0
  223. snappy/drilling/epsilons.py +6 -0
  224. snappy/drilling/exceptions.py +55 -0
  225. snappy/drilling/moves.py +620 -0
  226. snappy/drilling/peripheral_curves.py +210 -0
  227. snappy/drilling/perturb.py +188 -0
  228. snappy/drilling/shorten.py +36 -0
  229. snappy/drilling/subdivide.py +274 -0
  230. snappy/drilling/test.py +23 -0
  231. snappy/drilling/test_cases.py +132 -0
  232. snappy/drilling/tracing.py +351 -0
  233. snappy/exceptions.py +26 -0
  234. snappy/export_stl.py +120 -0
  235. snappy/exterior_to_link/__init__.py +2 -0
  236. snappy/exterior_to_link/barycentric_geometry.py +463 -0
  237. snappy/exterior_to_link/exceptions.py +6 -0
  238. snappy/exterior_to_link/geodesic_map.json +14408 -0
  239. snappy/exterior_to_link/hyp_utils.py +112 -0
  240. snappy/exterior_to_link/link_projection.py +323 -0
  241. snappy/exterior_to_link/main.py +198 -0
  242. snappy/exterior_to_link/mcomplex_with_expansion.py +261 -0
  243. snappy/exterior_to_link/mcomplex_with_link.py +687 -0
  244. snappy/exterior_to_link/mcomplex_with_memory.py +162 -0
  245. snappy/exterior_to_link/pl_utils.py +491 -0
  246. snappy/exterior_to_link/put_in_S3.py +156 -0
  247. snappy/exterior_to_link/rational_linear_algebra.py +130 -0
  248. snappy/exterior_to_link/rational_linear_algebra_wrapped.py +135 -0
  249. snappy/exterior_to_link/simplify_to_base_tri.py +114 -0
  250. snappy/exterior_to_link/stored_moves.py +475 -0
  251. snappy/exterior_to_link/test.py +31 -0
  252. snappy/filedialog.py +28 -0
  253. snappy/geometric_structure/__init__.py +212 -0
  254. snappy/geometric_structure/cusp_neighborhood/__init__.py +3 -0
  255. snappy/geometric_structure/cusp_neighborhood/complex_cusp_cross_section.py +691 -0
  256. snappy/geometric_structure/cusp_neighborhood/cusp_cross_section_base.py +480 -0
  257. snappy/geometric_structure/cusp_neighborhood/exceptions.py +41 -0
  258. snappy/geometric_structure/cusp_neighborhood/real_cusp_cross_section.py +294 -0
  259. snappy/geometric_structure/cusp_neighborhood/tiles_for_cusp_neighborhood.py +156 -0
  260. snappy/geometric_structure/cusp_neighborhood/vertices.py +35 -0
  261. snappy/geometric_structure/geodesic/__init__.py +0 -0
  262. snappy/geometric_structure/geodesic/add_core_curves.py +152 -0
  263. snappy/geometric_structure/geodesic/avoid_core_curves.py +369 -0
  264. snappy/geometric_structure/geodesic/canonical_representatives.py +52 -0
  265. snappy/geometric_structure/geodesic/check_away_from_core_curve.py +60 -0
  266. snappy/geometric_structure/geodesic/constants.py +6 -0
  267. snappy/geometric_structure/geodesic/exceptions.py +22 -0
  268. snappy/geometric_structure/geodesic/fixed_points.py +106 -0
  269. snappy/geometric_structure/geodesic/geodesic_start_point_info.py +435 -0
  270. snappy/geometric_structure/geodesic/graph_trace_helper.py +67 -0
  271. snappy/geometric_structure/geodesic/line.py +30 -0
  272. snappy/geometric_structure/geodesic/multiplicity.py +127 -0
  273. snappy/geometric_structure/geodesic/tiles_for_geodesic.py +128 -0
  274. snappy/geometric_structure/test.py +22 -0
  275. snappy/gui.py +121 -0
  276. snappy/horoviewer.py +443 -0
  277. snappy/hyperboloid/__init__.py +212 -0
  278. snappy/hyperboloid/distances.py +259 -0
  279. snappy/hyperboloid/horoball.py +19 -0
  280. snappy/hyperboloid/line.py +35 -0
  281. snappy/hyperboloid/point.py +9 -0
  282. snappy/hyperboloid/triangle.py +29 -0
  283. snappy/info_icon.gif +0 -0
  284. snappy/infowindow.py +65 -0
  285. snappy/isometry_signature.py +389 -0
  286. snappy/len_spec/__init__.py +609 -0
  287. snappy/len_spec/geodesic_info.py +129 -0
  288. snappy/len_spec/geodesic_key_info_dict.py +116 -0
  289. snappy/len_spec/geodesic_piece.py +146 -0
  290. snappy/len_spec/geometric_structure.py +182 -0
  291. snappy/len_spec/geometry.py +136 -0
  292. snappy/len_spec/length_spectrum_geodesic_info.py +185 -0
  293. snappy/len_spec/spine.py +128 -0
  294. snappy/len_spec/test.py +24 -0
  295. snappy/len_spec/test_cases.py +69 -0
  296. snappy/len_spec/tile.py +276 -0
  297. snappy/len_spec/word.py +86 -0
  298. snappy/manifolds/HTWKnots/alternating.gz +0 -0
  299. snappy/manifolds/HTWKnots/nonalternating.gz +0 -0
  300. snappy/manifolds/__init__.py +3 -0
  301. snappy/margulis/__init__.py +332 -0
  302. snappy/margulis/cusp_neighborhood_neighborhood.py +66 -0
  303. snappy/margulis/geodesic_neighborhood.py +152 -0
  304. snappy/margulis/margulis_info.py +21 -0
  305. snappy/margulis/mu_from_neighborhood_pair.py +175 -0
  306. snappy/margulis/neighborhood.py +29 -0
  307. snappy/margulis/test.py +22 -0
  308. snappy/math_basics.py +187 -0
  309. snappy/matrix.py +525 -0
  310. snappy/number.py +657 -0
  311. snappy/numeric_output_checker.py +345 -0
  312. snappy/pari.py +41 -0
  313. snappy/phone_home.py +57 -0
  314. snappy/polyviewer.py +259 -0
  315. snappy/ptolemy/__init__.py +17 -0
  316. snappy/ptolemy/component.py +103 -0
  317. snappy/ptolemy/coordinates.py +2290 -0
  318. snappy/ptolemy/fieldExtensions.py +153 -0
  319. snappy/ptolemy/findLoops.py +473 -0
  320. snappy/ptolemy/geometricRep.py +59 -0
  321. snappy/ptolemy/homology.py +165 -0
  322. snappy/ptolemy/magma/default.magma_template +229 -0
  323. snappy/ptolemy/magma/radicalsOfPrimaryDecomposition.magma_template +79 -0
  324. snappy/ptolemy/manifoldMethods.py +395 -0
  325. snappy/ptolemy/matrix.py +350 -0
  326. snappy/ptolemy/numericalSolutionsToGroebnerBasis.py +113 -0
  327. snappy/ptolemy/polynomial.py +856 -0
  328. snappy/ptolemy/processComponents.py +173 -0
  329. snappy/ptolemy/processFileBase.py +247 -0
  330. snappy/ptolemy/processFileDispatch.py +46 -0
  331. snappy/ptolemy/processMagmaFile.py +392 -0
  332. snappy/ptolemy/processRurFile.py +150 -0
  333. snappy/ptolemy/ptolemyGeneralizedObstructionClass.py +102 -0
  334. snappy/ptolemy/ptolemyObstructionClass.py +64 -0
  335. snappy/ptolemy/ptolemyVariety.py +995 -0
  336. snappy/ptolemy/ptolemyVarietyPrimeIdealGroebnerBasis.py +140 -0
  337. snappy/ptolemy/reginaWrapper.py +698 -0
  338. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c0.magma_out.bz2 +0 -0
  339. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c1.magma_out.bz2 +0 -0
  340. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c2.magma_out.bz2 +0 -0
  341. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c3.magma_out.bz2 +0 -0
  342. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c4.magma_out.bz2 +0 -0
  343. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c5.magma_out.bz2 +0 -0
  344. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c6.magma_out.bz2 +0 -0
  345. snappy/ptolemy/regina_testing_files/DT_mcbbiceaibjklmdfgh__sl2_c7.magma_out.bz2 +0 -0
  346. snappy/ptolemy/regina_testing_files_generalized/m003__sl3_c0.magma_out.bz2 +0 -0
  347. snappy/ptolemy/regina_testing_files_generalized/m003__sl3_c1.magma_out.bz2 +0 -0
  348. snappy/ptolemy/regina_testing_files_generalized/m015__sl2_c0.magma_out.bz2 +0 -0
  349. snappy/ptolemy/regina_testing_files_generalized/m015__sl2_c1.magma_out.bz2 +0 -0
  350. snappy/ptolemy/regina_testing_files_generalized/m015__sl3_c0.magma_out.bz2 +0 -0
  351. snappy/ptolemy/regina_testing_files_generalized/m015__sl3_c1.magma_out.bz2 +0 -0
  352. snappy/ptolemy/rur.py +545 -0
  353. snappy/ptolemy/solutionsToPrimeIdealGroebnerBasis.py +277 -0
  354. snappy/ptolemy/test.py +1126 -0
  355. snappy/ptolemy/testing_files/3_1__sl2_c0.magma_out.bz2 +0 -0
  356. snappy/ptolemy/testing_files/3_1__sl2_c1.magma_out.bz2 +0 -0
  357. snappy/ptolemy/testing_files/4_1__sl2_c0.magma_out.bz2 +0 -0
  358. snappy/ptolemy/testing_files/4_1__sl2_c1.magma_out.bz2 +0 -0
  359. snappy/ptolemy/testing_files/4_1__sl3_c0.magma_out.bz2 +0 -0
  360. snappy/ptolemy/testing_files/4_1__sl4_c0.magma_out.bz2 +0 -0
  361. snappy/ptolemy/testing_files/4_1__sl4_c1.magma_out.bz2 +0 -0
  362. snappy/ptolemy/testing_files/5_2__sl2_c0.magma_out.bz2 +0 -0
  363. snappy/ptolemy/testing_files/5_2__sl2_c1.magma_out.bz2 +0 -0
  364. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c0.magma_out.bz2 +0 -0
  365. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c1.magma_out.bz2 +0 -0
  366. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c2.magma_out.bz2 +0 -0
  367. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c3.magma_out.bz2 +0 -0
  368. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c4.magma_out.bz2 +0 -0
  369. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c5.magma_out.bz2 +0 -0
  370. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c6.magma_out.bz2 +0 -0
  371. snappy/ptolemy/testing_files/DT_mcbbiceaibjklmdfgh__sl2_c7.magma_out.bz2 +0 -0
  372. snappy/ptolemy/testing_files/data/pgl2/OrientableCuspedCensus/03_tetrahedra/m019__sl2_c0.magma_out +95 -0
  373. snappy/ptolemy/testing_files/data/pgl2/OrientableCuspedCensus/03_tetrahedra/m019__sl2_c1.magma_out +95 -0
  374. snappy/ptolemy/testing_files/m015__sl3_c0.magma_out.bz2 +0 -0
  375. snappy/ptolemy/testing_files/m135__sl2_c0.magma_out.bz2 +0 -0
  376. snappy/ptolemy/testing_files/m135__sl2_c1.magma_out.bz2 +0 -0
  377. snappy/ptolemy/testing_files/m135__sl2_c2.magma_out.bz2 +0 -0
  378. snappy/ptolemy/testing_files/m135__sl2_c3.magma_out.bz2 +0 -0
  379. snappy/ptolemy/testing_files/m135__sl2_c4.magma_out.bz2 +0 -0
  380. snappy/ptolemy/testing_files/m135__sl2_c5.magma_out.bz2 +0 -0
  381. snappy/ptolemy/testing_files/m135__sl2_c6.magma_out.bz2 +0 -0
  382. snappy/ptolemy/testing_files/m135__sl2_c7.magma_out.bz2 +0 -0
  383. snappy/ptolemy/testing_files/s000__sl2_c0.magma_out.bz2 +0 -0
  384. snappy/ptolemy/testing_files/s000__sl2_c1.magma_out.bz2 +0 -0
  385. snappy/ptolemy/testing_files/t00000__sl2_c0.magma_out.bz2 +0 -0
  386. snappy/ptolemy/testing_files/t00000__sl2_c1.magma_out.bz2 +0 -0
  387. snappy/ptolemy/testing_files/v0000__sl2_c0.magma_out.bz2 +0 -0
  388. snappy/ptolemy/testing_files/v0000__sl2_c1.magma_out.bz2 +0 -0
  389. snappy/ptolemy/testing_files/v0000__sl2_c2.magma_out.bz2 +0 -0
  390. snappy/ptolemy/testing_files/v0000__sl2_c3.magma_out.bz2 +0 -0
  391. snappy/ptolemy/testing_files_generalized/m003__sl2_c0.magma_out.bz2 +0 -0
  392. snappy/ptolemy/testing_files_generalized/m003__sl2_c1.magma_out.bz2 +0 -0
  393. snappy/ptolemy/testing_files_generalized/m003__sl3_c0.magma_out.bz2 +0 -0
  394. snappy/ptolemy/testing_files_generalized/m003__sl3_c1.magma_out.bz2 +0 -0
  395. snappy/ptolemy/testing_files_generalized/m004__sl2_c0.magma_out.bz2 +0 -0
  396. snappy/ptolemy/testing_files_generalized/m004__sl2_c1.magma_out.bz2 +0 -0
  397. snappy/ptolemy/testing_files_generalized/m015__sl2_c1.magma_out.bz2 +0 -0
  398. snappy/ptolemy/testing_files_generalized/m015__sl3_c0.magma_out.bz2 +0 -0
  399. snappy/ptolemy/testing_files_rur/m052__sl3_c0.rur.bz2 +0 -0
  400. snappy/ptolemy/utilities.py +236 -0
  401. snappy/raytracing/__init__.py +64 -0
  402. snappy/raytracing/additional_horospheres.py +64 -0
  403. snappy/raytracing/additional_len_spec_choices.py +63 -0
  404. snappy/raytracing/cohomology_fractal.py +197 -0
  405. snappy/raytracing/eyeball.py +124 -0
  406. snappy/raytracing/finite_raytracing_data.py +237 -0
  407. snappy/raytracing/finite_viewer.py +590 -0
  408. snappy/raytracing/geodesic_tube_info.py +174 -0
  409. snappy/raytracing/geodesics.py +246 -0
  410. snappy/raytracing/geodesics_window.py +258 -0
  411. snappy/raytracing/gui_utilities.py +293 -0
  412. snappy/raytracing/hyperboloid_navigation.py +556 -0
  413. snappy/raytracing/hyperboloid_utilities.py +234 -0
  414. snappy/raytracing/ideal_raytracing_data.py +592 -0
  415. snappy/raytracing/inside_viewer.py +974 -0
  416. snappy/raytracing/pack.py +22 -0
  417. snappy/raytracing/raytracing_data.py +126 -0
  418. snappy/raytracing/raytracing_view.py +454 -0
  419. snappy/raytracing/shaders/Eye.png +0 -0
  420. snappy/raytracing/shaders/NonGeometric.png +0 -0
  421. snappy/raytracing/shaders/__init__.py +101 -0
  422. snappy/raytracing/shaders/fragment.glsl +1744 -0
  423. snappy/raytracing/test.py +29 -0
  424. snappy/raytracing/tooltip.py +146 -0
  425. snappy/raytracing/upper_halfspace_utilities.py +98 -0
  426. snappy/raytracing/view_scale_controller.py +98 -0
  427. snappy/raytracing/zoom_slider/__init__.py +263 -0
  428. snappy/raytracing/zoom_slider/inward.png +0 -0
  429. snappy/raytracing/zoom_slider/inward18.png +0 -0
  430. snappy/raytracing/zoom_slider/outward.png +0 -0
  431. snappy/raytracing/zoom_slider/outward18.png +0 -0
  432. snappy/raytracing/zoom_slider/test.py +20 -0
  433. snappy/sage_helper.py +119 -0
  434. snappy/settings.py +407 -0
  435. snappy/shell.py +53 -0
  436. snappy/snap/__init__.py +117 -0
  437. snappy/snap/character_varieties.py +375 -0
  438. snappy/snap/find_field.py +372 -0
  439. snappy/snap/fox_milnor.py +271 -0
  440. snappy/snap/fundamental_polyhedron.py +569 -0
  441. snappy/snap/generators.py +39 -0
  442. snappy/snap/interval_reps.py +81 -0
  443. snappy/snap/kernel_structures.py +128 -0
  444. snappy/snap/mcomplex_base.py +18 -0
  445. snappy/snap/nsagetools.py +716 -0
  446. snappy/snap/peripheral/__init__.py +1 -0
  447. snappy/snap/peripheral/dual_cellulation.py +219 -0
  448. snappy/snap/peripheral/link.py +127 -0
  449. snappy/snap/peripheral/peripheral.py +159 -0
  450. snappy/snap/peripheral/surface.py +522 -0
  451. snappy/snap/peripheral/test.py +35 -0
  452. snappy/snap/polished_reps.py +335 -0
  453. snappy/snap/shapes.py +152 -0
  454. snappy/snap/slice_obs_HKL/__init__.py +194 -0
  455. snappy/snap/slice_obs_HKL/basics.py +236 -0
  456. snappy/snap/slice_obs_HKL/direct.py +217 -0
  457. snappy/snap/slice_obs_HKL/poly_norm.py +212 -0
  458. snappy/snap/slice_obs_HKL/rep_theory.py +424 -0
  459. snappy/snap/t3mlite/__init__.py +2 -0
  460. snappy/snap/t3mlite/arrow.py +243 -0
  461. snappy/snap/t3mlite/corner.py +22 -0
  462. snappy/snap/t3mlite/edge.py +172 -0
  463. snappy/snap/t3mlite/face.py +37 -0
  464. snappy/snap/t3mlite/files.py +211 -0
  465. snappy/snap/t3mlite/homology.py +53 -0
  466. snappy/snap/t3mlite/linalg.py +419 -0
  467. snappy/snap/t3mlite/mcomplex.py +1499 -0
  468. snappy/snap/t3mlite/perm4.py +320 -0
  469. snappy/snap/t3mlite/setup.py +12 -0
  470. snappy/snap/t3mlite/simplex.py +199 -0
  471. snappy/snap/t3mlite/spun.py +297 -0
  472. snappy/snap/t3mlite/surface.py +519 -0
  473. snappy/snap/t3mlite/test.py +20 -0
  474. snappy/snap/t3mlite/test_vs_regina.py +86 -0
  475. snappy/snap/t3mlite/tetrahedron.py +109 -0
  476. snappy/snap/t3mlite/vertex.py +42 -0
  477. snappy/snap/test.py +139 -0
  478. snappy/snap/utilities.py +288 -0
  479. snappy/test.py +213 -0
  480. snappy/test_cases.py +263 -0
  481. snappy/testing.py +131 -0
  482. snappy/tiling/__init__.py +2 -0
  483. snappy/tiling/dict_based_set.py +79 -0
  484. snappy/tiling/floor.py +49 -0
  485. snappy/tiling/hyperboloid_dict.py +54 -0
  486. snappy/tiling/iter_utils.py +78 -0
  487. snappy/tiling/lifted_tetrahedron.py +22 -0
  488. snappy/tiling/lifted_tetrahedron_set.py +54 -0
  489. snappy/tiling/quotient_dict.py +70 -0
  490. snappy/tiling/real_hash_dict.py +164 -0
  491. snappy/tiling/test.py +23 -0
  492. snappy/tiling/tile.py +224 -0
  493. snappy/tiling/triangle.py +33 -0
  494. snappy/tkterminal.py +920 -0
  495. snappy/twister/__init__.py +20 -0
  496. snappy/twister/main.py +646 -0
  497. snappy/twister/surfaces/S_0_1 +3 -0
  498. snappy/twister/surfaces/S_0_2 +3 -0
  499. snappy/twister/surfaces/S_0_4 +7 -0
  500. snappy/twister/surfaces/S_0_4_Lantern +8 -0
  501. snappy/twister/surfaces/S_1 +3 -0
  502. snappy/twister/surfaces/S_1_1 +4 -0
  503. snappy/twister/surfaces/S_1_2 +5 -0
  504. snappy/twister/surfaces/S_1_2_5 +6 -0
  505. snappy/twister/surfaces/S_2 +6 -0
  506. snappy/twister/surfaces/S_2_1 +8 -0
  507. snappy/twister/surfaces/S_2_heeg +10 -0
  508. snappy/twister/surfaces/S_3 +8 -0
  509. snappy/twister/surfaces/S_3_1 +10 -0
  510. snappy/twister/surfaces/S_4_1 +12 -0
  511. snappy/twister/surfaces/S_5_1 +14 -0
  512. snappy/twister/surfaces/heeg_fig8 +9 -0
  513. snappy/twister/twister_core.cpython-310-aarch64-linux-gnu.so +0 -0
  514. snappy/upper_halfspace/__init__.py +146 -0
  515. snappy/upper_halfspace/ideal_point.py +29 -0
  516. snappy/verify/__init__.py +13 -0
  517. snappy/verify/canonical.py +542 -0
  518. snappy/verify/complex_volume/__init__.py +18 -0
  519. snappy/verify/complex_volume/adjust_torsion.py +86 -0
  520. snappy/verify/complex_volume/closed.py +168 -0
  521. snappy/verify/complex_volume/compute_ptolemys.py +90 -0
  522. snappy/verify/complex_volume/cusped.py +56 -0
  523. snappy/verify/complex_volume/extended_bloch.py +201 -0
  524. snappy/verify/cusp_translations.py +85 -0
  525. snappy/verify/edge_equations.py +80 -0
  526. snappy/verify/exceptions.py +254 -0
  527. snappy/verify/hyperbolicity.py +224 -0
  528. snappy/verify/interval_newton_shapes_engine.py +523 -0
  529. snappy/verify/interval_tree.py +400 -0
  530. snappy/verify/krawczyk_shapes_engine.py +518 -0
  531. snappy/verify/real_algebra.py +286 -0
  532. snappy/verify/shapes.py +25 -0
  533. snappy/verify/square_extensions.py +1005 -0
  534. snappy/verify/test.py +72 -0
  535. snappy/verify/volume.py +128 -0
  536. snappy/version.py +2 -0
  537. snappy-3.3.dist-info/METADATA +58 -0
  538. snappy-3.3.dist-info/RECORD +541 -0
  539. snappy-3.3.dist-info/WHEEL +6 -0
  540. snappy-3.3.dist-info/entry_points.txt +2 -0
  541. 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)]