snappy 3.0.3__cp38-cp38-macosx_11_0_arm64.whl → 3.2__cp38-cp38-macosx_11_0_arm64.whl

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