passagemath-schemes 10.8.1a4__cp314-cp314t-macosx_13_0_arm64.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 (312) hide show
  1. passagemath_schemes/.dylibs/libflint.22.0.dylib +0 -0
  2. passagemath_schemes/.dylibs/libgmp.10.dylib +0 -0
  3. passagemath_schemes/.dylibs/libgmpxx.4.dylib +0 -0
  4. passagemath_schemes/.dylibs/libmpfr.6.dylib +0 -0
  5. passagemath_schemes/__init__.py +3 -0
  6. passagemath_schemes-10.8.1a4.dist-info/METADATA +203 -0
  7. passagemath_schemes-10.8.1a4.dist-info/METADATA.bak +204 -0
  8. passagemath_schemes-10.8.1a4.dist-info/RECORD +312 -0
  9. passagemath_schemes-10.8.1a4.dist-info/WHEEL +6 -0
  10. passagemath_schemes-10.8.1a4.dist-info/top_level.txt +3 -0
  11. sage/all__sagemath_schemes.py +23 -0
  12. sage/databases/all__sagemath_schemes.py +7 -0
  13. sage/databases/cremona.py +1723 -0
  14. sage/dynamics/all__sagemath_schemes.py +2 -0
  15. sage/dynamics/arithmetic_dynamics/affine_ds.py +1083 -0
  16. sage/dynamics/arithmetic_dynamics/all.py +14 -0
  17. sage/dynamics/arithmetic_dynamics/berkovich_ds.py +1101 -0
  18. sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py +1543 -0
  19. sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +2426 -0
  20. sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +1169 -0
  21. sage/dynamics/arithmetic_dynamics/generic_ds.py +663 -0
  22. sage/dynamics/arithmetic_dynamics/product_projective_ds.py +339 -0
  23. sage/dynamics/arithmetic_dynamics/projective_ds.py +9556 -0
  24. sage/dynamics/arithmetic_dynamics/projective_ds_helper.cpython-314t-darwin.so +0 -0
  25. sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +301 -0
  26. sage/dynamics/arithmetic_dynamics/wehlerK3.py +2578 -0
  27. sage/lfunctions/all.py +18 -0
  28. sage/lfunctions/dokchitser.py +727 -0
  29. sage/lfunctions/pari.py +971 -0
  30. sage/lfunctions/zero_sums.cpython-314t-darwin.so +0 -0
  31. sage/lfunctions/zero_sums.pyx +1847 -0
  32. sage/modular/abvar/abvar.py +5132 -0
  33. sage/modular/abvar/abvar_ambient_jacobian.py +414 -0
  34. sage/modular/abvar/abvar_newform.py +246 -0
  35. sage/modular/abvar/all.py +8 -0
  36. sage/modular/abvar/constructor.py +187 -0
  37. sage/modular/abvar/cuspidal_subgroup.py +371 -0
  38. sage/modular/abvar/finite_subgroup.py +896 -0
  39. sage/modular/abvar/homology.py +721 -0
  40. sage/modular/abvar/homspace.py +989 -0
  41. sage/modular/abvar/lseries.py +415 -0
  42. sage/modular/abvar/morphism.py +935 -0
  43. sage/modular/abvar/torsion_point.py +274 -0
  44. sage/modular/abvar/torsion_subgroup.py +741 -0
  45. sage/modular/all.py +43 -0
  46. sage/modular/arithgroup/all.py +20 -0
  47. sage/modular/arithgroup/arithgroup_element.cpython-314t-darwin.so +0 -0
  48. sage/modular/arithgroup/arithgroup_element.pyx +474 -0
  49. sage/modular/arithgroup/arithgroup_generic.py +1406 -0
  50. sage/modular/arithgroup/arithgroup_perm.py +2692 -0
  51. sage/modular/arithgroup/congroup.cpython-314t-darwin.so +0 -0
  52. sage/modular/arithgroup/congroup.pyx +334 -0
  53. sage/modular/arithgroup/congroup_gamma.py +361 -0
  54. sage/modular/arithgroup/congroup_gamma0.py +692 -0
  55. sage/modular/arithgroup/congroup_gamma1.py +659 -0
  56. sage/modular/arithgroup/congroup_gammaH.py +1491 -0
  57. sage/modular/arithgroup/congroup_generic.py +630 -0
  58. sage/modular/arithgroup/congroup_sl2z.py +266 -0
  59. sage/modular/arithgroup/farey_symbol.cpython-314t-darwin.so +0 -0
  60. sage/modular/arithgroup/farey_symbol.pyx +1067 -0
  61. sage/modular/arithgroup/tests.py +425 -0
  62. sage/modular/btquotients/all.py +4 -0
  63. sage/modular/btquotients/btquotient.py +3736 -0
  64. sage/modular/btquotients/pautomorphicform.py +2564 -0
  65. sage/modular/buzzard.py +100 -0
  66. sage/modular/congroup.py +29 -0
  67. sage/modular/congroup_element.py +13 -0
  68. sage/modular/cusps.py +1107 -0
  69. sage/modular/cusps_nf.py +1270 -0
  70. sage/modular/dims.py +571 -0
  71. sage/modular/dirichlet.py +3310 -0
  72. sage/modular/drinfeld_modform/all.py +2 -0
  73. sage/modular/drinfeld_modform/element.py +446 -0
  74. sage/modular/drinfeld_modform/ring.py +773 -0
  75. sage/modular/drinfeld_modform/tutorial.py +236 -0
  76. sage/modular/etaproducts.py +1076 -0
  77. sage/modular/hecke/algebra.py +725 -0
  78. sage/modular/hecke/all.py +19 -0
  79. sage/modular/hecke/ambient_module.py +994 -0
  80. sage/modular/hecke/degenmap.py +119 -0
  81. sage/modular/hecke/element.py +302 -0
  82. sage/modular/hecke/hecke_operator.py +736 -0
  83. sage/modular/hecke/homspace.py +185 -0
  84. sage/modular/hecke/module.py +1744 -0
  85. sage/modular/hecke/morphism.py +139 -0
  86. sage/modular/hecke/submodule.py +970 -0
  87. sage/modular/hypergeometric_misc.cpython-314t-darwin.so +0 -0
  88. sage/modular/hypergeometric_misc.pxd +4 -0
  89. sage/modular/hypergeometric_misc.pyx +166 -0
  90. sage/modular/hypergeometric_motive.py +2020 -0
  91. sage/modular/local_comp/all.py +2 -0
  92. sage/modular/local_comp/liftings.py +292 -0
  93. sage/modular/local_comp/local_comp.py +1070 -0
  94. sage/modular/local_comp/smoothchar.py +1825 -0
  95. sage/modular/local_comp/type_space.py +748 -0
  96. sage/modular/modform/all.py +30 -0
  97. sage/modular/modform/ambient.py +817 -0
  98. sage/modular/modform/ambient_R.py +177 -0
  99. sage/modular/modform/ambient_eps.py +306 -0
  100. sage/modular/modform/ambient_g0.py +120 -0
  101. sage/modular/modform/ambient_g1.py +199 -0
  102. sage/modular/modform/constructor.py +545 -0
  103. sage/modular/modform/cuspidal_submodule.py +708 -0
  104. sage/modular/modform/defaults.py +14 -0
  105. sage/modular/modform/eis_series.py +487 -0
  106. sage/modular/modform/eisenstein_submodule.py +663 -0
  107. sage/modular/modform/element.py +4105 -0
  108. sage/modular/modform/half_integral.py +154 -0
  109. sage/modular/modform/hecke_operator_on_qexp.py +247 -0
  110. sage/modular/modform/j_invariant.py +47 -0
  111. sage/modular/modform/l_series_gross_zagier.py +127 -0
  112. sage/modular/modform/l_series_gross_zagier_coeffs.cpython-314t-darwin.so +0 -0
  113. sage/modular/modform/l_series_gross_zagier_coeffs.pyx +177 -0
  114. sage/modular/modform/notes.py +45 -0
  115. sage/modular/modform/numerical.py +514 -0
  116. sage/modular/modform/periods.py +14 -0
  117. sage/modular/modform/ring.py +1257 -0
  118. sage/modular/modform/space.py +1859 -0
  119. sage/modular/modform/submodule.py +118 -0
  120. sage/modular/modform/tests.py +64 -0
  121. sage/modular/modform/theta.py +110 -0
  122. sage/modular/modform/vm_basis.py +380 -0
  123. sage/modular/modform/weight1.py +221 -0
  124. sage/modular/modform_hecketriangle/abstract_ring.py +1932 -0
  125. sage/modular/modform_hecketriangle/abstract_space.py +2527 -0
  126. sage/modular/modform_hecketriangle/all.py +30 -0
  127. sage/modular/modform_hecketriangle/analytic_type.py +590 -0
  128. sage/modular/modform_hecketriangle/constructor.py +416 -0
  129. sage/modular/modform_hecketriangle/element.py +351 -0
  130. sage/modular/modform_hecketriangle/functors.py +752 -0
  131. sage/modular/modform_hecketriangle/graded_ring.py +541 -0
  132. sage/modular/modform_hecketriangle/graded_ring_element.py +2225 -0
  133. sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +3349 -0
  134. sage/modular/modform_hecketriangle/hecke_triangle_groups.py +1426 -0
  135. sage/modular/modform_hecketriangle/readme.py +1214 -0
  136. sage/modular/modform_hecketriangle/series_constructor.py +580 -0
  137. sage/modular/modform_hecketriangle/space.py +1037 -0
  138. sage/modular/modform_hecketriangle/subspace.py +423 -0
  139. sage/modular/modsym/all.py +17 -0
  140. sage/modular/modsym/ambient.py +3844 -0
  141. sage/modular/modsym/boundary.py +1420 -0
  142. sage/modular/modsym/element.py +336 -0
  143. sage/modular/modsym/g1list.py +178 -0
  144. sage/modular/modsym/ghlist.py +182 -0
  145. sage/modular/modsym/hecke_operator.py +73 -0
  146. sage/modular/modsym/manin_symbol.cpython-314t-darwin.so +0 -0
  147. sage/modular/modsym/manin_symbol.pxd +5 -0
  148. sage/modular/modsym/manin_symbol.pyx +497 -0
  149. sage/modular/modsym/manin_symbol_list.py +1291 -0
  150. sage/modular/modsym/modsym.py +400 -0
  151. sage/modular/modsym/modular_symbols.py +384 -0
  152. sage/modular/modsym/p1list_nf.py +1241 -0
  153. sage/modular/modsym/relation_matrix.py +591 -0
  154. sage/modular/modsym/relation_matrix_pyx.cpython-314t-darwin.so +0 -0
  155. sage/modular/modsym/relation_matrix_pyx.pyx +108 -0
  156. sage/modular/modsym/space.py +2468 -0
  157. sage/modular/modsym/subspace.py +455 -0
  158. sage/modular/modsym/tests.py +376 -0
  159. sage/modular/multiple_zeta.py +2635 -0
  160. sage/modular/multiple_zeta_F_algebra.py +789 -0
  161. sage/modular/overconvergent/all.py +6 -0
  162. sage/modular/overconvergent/genus0.py +1879 -0
  163. sage/modular/overconvergent/hecke_series.py +1187 -0
  164. sage/modular/overconvergent/weightspace.py +776 -0
  165. sage/modular/pollack_stevens/all.py +4 -0
  166. sage/modular/pollack_stevens/distributions.py +874 -0
  167. sage/modular/pollack_stevens/fund_domain.py +1572 -0
  168. sage/modular/pollack_stevens/manin_map.py +856 -0
  169. sage/modular/pollack_stevens/modsym.py +1590 -0
  170. sage/modular/pollack_stevens/padic_lseries.py +417 -0
  171. sage/modular/pollack_stevens/sigma0.py +534 -0
  172. sage/modular/pollack_stevens/space.py +1078 -0
  173. sage/modular/quasimodform/all.py +3 -0
  174. sage/modular/quasimodform/element.py +846 -0
  175. sage/modular/quasimodform/ring.py +826 -0
  176. sage/modular/quatalg/all.py +3 -0
  177. sage/modular/quatalg/brandt.py +1642 -0
  178. sage/modular/ssmod/all.py +8 -0
  179. sage/modular/ssmod/ssmod.py +827 -0
  180. sage/rings/all__sagemath_schemes.py +1 -0
  181. sage/rings/polynomial/all__sagemath_schemes.py +1 -0
  182. sage/rings/polynomial/binary_form_reduce.py +585 -0
  183. sage/schemes/all.py +41 -0
  184. sage/schemes/berkovich/all.py +6 -0
  185. sage/schemes/berkovich/berkovich_cp_element.py +2582 -0
  186. sage/schemes/berkovich/berkovich_space.py +700 -0
  187. sage/schemes/curves/affine_curve.py +2924 -0
  188. sage/schemes/curves/all.py +33 -0
  189. sage/schemes/curves/closed_point.py +434 -0
  190. sage/schemes/curves/constructor.py +397 -0
  191. sage/schemes/curves/curve.py +542 -0
  192. sage/schemes/curves/plane_curve_arrangement.py +1283 -0
  193. sage/schemes/curves/point.py +463 -0
  194. sage/schemes/curves/projective_curve.py +3203 -0
  195. sage/schemes/curves/weighted_projective_curve.py +106 -0
  196. sage/schemes/curves/zariski_vankampen.py +1931 -0
  197. sage/schemes/cyclic_covers/all.py +2 -0
  198. sage/schemes/cyclic_covers/charpoly_frobenius.py +320 -0
  199. sage/schemes/cyclic_covers/constructor.py +137 -0
  200. sage/schemes/cyclic_covers/cycliccover_finite_field.py +1309 -0
  201. sage/schemes/cyclic_covers/cycliccover_generic.py +310 -0
  202. sage/schemes/elliptic_curves/BSD.py +991 -0
  203. sage/schemes/elliptic_curves/Qcurves.py +592 -0
  204. sage/schemes/elliptic_curves/addition_formulas_ring.py +94 -0
  205. sage/schemes/elliptic_curves/all.py +49 -0
  206. sage/schemes/elliptic_curves/cardinality.py +609 -0
  207. sage/schemes/elliptic_curves/cm.py +1103 -0
  208. sage/schemes/elliptic_curves/constructor.py +1530 -0
  209. sage/schemes/elliptic_curves/ec_database.py +175 -0
  210. sage/schemes/elliptic_curves/ell_curve_isogeny.py +3971 -0
  211. sage/schemes/elliptic_curves/ell_egros.py +457 -0
  212. sage/schemes/elliptic_curves/ell_field.py +2837 -0
  213. sage/schemes/elliptic_curves/ell_finite_field.py +3249 -0
  214. sage/schemes/elliptic_curves/ell_generic.py +3760 -0
  215. sage/schemes/elliptic_curves/ell_local_data.py +1207 -0
  216. sage/schemes/elliptic_curves/ell_modular_symbols.py +775 -0
  217. sage/schemes/elliptic_curves/ell_number_field.py +4220 -0
  218. sage/schemes/elliptic_curves/ell_padic_field.py +107 -0
  219. sage/schemes/elliptic_curves/ell_point.py +4944 -0
  220. sage/schemes/elliptic_curves/ell_rational_field.py +7184 -0
  221. sage/schemes/elliptic_curves/ell_tate_curve.py +671 -0
  222. sage/schemes/elliptic_curves/ell_torsion.py +436 -0
  223. sage/schemes/elliptic_curves/ell_wp.py +352 -0
  224. sage/schemes/elliptic_curves/formal_group.py +760 -0
  225. sage/schemes/elliptic_curves/gal_reps.py +1459 -0
  226. sage/schemes/elliptic_curves/gal_reps_number_field.py +1663 -0
  227. sage/schemes/elliptic_curves/gp_simon.py +152 -0
  228. sage/schemes/elliptic_curves/heegner.py +7328 -0
  229. sage/schemes/elliptic_curves/height.py +2108 -0
  230. sage/schemes/elliptic_curves/hom.py +1788 -0
  231. sage/schemes/elliptic_curves/hom_composite.py +1084 -0
  232. sage/schemes/elliptic_curves/hom_fractional.py +544 -0
  233. sage/schemes/elliptic_curves/hom_frobenius.py +522 -0
  234. sage/schemes/elliptic_curves/hom_scalar.py +531 -0
  235. sage/schemes/elliptic_curves/hom_sum.py +681 -0
  236. sage/schemes/elliptic_curves/hom_velusqrt.py +1290 -0
  237. sage/schemes/elliptic_curves/homset.py +271 -0
  238. sage/schemes/elliptic_curves/isogeny_class.py +1523 -0
  239. sage/schemes/elliptic_curves/isogeny_small_degree.py +2797 -0
  240. sage/schemes/elliptic_curves/jacobian.py +247 -0
  241. sage/schemes/elliptic_curves/kodaira_symbol.py +344 -0
  242. sage/schemes/elliptic_curves/kraus.py +1014 -0
  243. sage/schemes/elliptic_curves/lseries_ell.py +915 -0
  244. sage/schemes/elliptic_curves/mod5family.py +105 -0
  245. sage/schemes/elliptic_curves/mod_poly.py +197 -0
  246. sage/schemes/elliptic_curves/mod_sym_num.cpython-314t-darwin.so +0 -0
  247. sage/schemes/elliptic_curves/mod_sym_num.pyx +3796 -0
  248. sage/schemes/elliptic_curves/modular_parametrization.py +305 -0
  249. sage/schemes/elliptic_curves/padic_lseries.py +1793 -0
  250. sage/schemes/elliptic_curves/padics.py +1816 -0
  251. sage/schemes/elliptic_curves/period_lattice.py +2234 -0
  252. sage/schemes/elliptic_curves/period_lattice_region.cpython-314t-darwin.so +0 -0
  253. sage/schemes/elliptic_curves/period_lattice_region.pyx +722 -0
  254. sage/schemes/elliptic_curves/saturation.py +716 -0
  255. sage/schemes/elliptic_curves/sha_tate.py +1158 -0
  256. sage/schemes/elliptic_curves/weierstrass_morphism.py +1117 -0
  257. sage/schemes/elliptic_curves/weierstrass_transform.py +200 -0
  258. sage/schemes/hyperelliptic_curves/all.py +6 -0
  259. sage/schemes/hyperelliptic_curves/constructor.py +369 -0
  260. sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +1948 -0
  261. sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +192 -0
  262. sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +936 -0
  263. sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +1332 -0
  264. sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +84 -0
  265. sage/schemes/hyperelliptic_curves/invariants.py +410 -0
  266. sage/schemes/hyperelliptic_curves/jacobian_endomorphism_utils.py +312 -0
  267. sage/schemes/hyperelliptic_curves/jacobian_g2.py +32 -0
  268. sage/schemes/hyperelliptic_curves/jacobian_generic.py +437 -0
  269. sage/schemes/hyperelliptic_curves/jacobian_homset.py +186 -0
  270. sage/schemes/hyperelliptic_curves/jacobian_morphism.py +878 -0
  271. sage/schemes/hyperelliptic_curves/kummer_surface.py +99 -0
  272. sage/schemes/hyperelliptic_curves/mestre.py +302 -0
  273. sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +3863 -0
  274. sage/schemes/jacobians/abstract_jacobian.py +277 -0
  275. sage/schemes/jacobians/all.py +2 -0
  276. sage/schemes/overview.py +161 -0
  277. sage/schemes/plane_conics/all.py +22 -0
  278. sage/schemes/plane_conics/con_field.py +1296 -0
  279. sage/schemes/plane_conics/con_finite_field.py +158 -0
  280. sage/schemes/plane_conics/con_number_field.py +456 -0
  281. sage/schemes/plane_conics/con_rational_field.py +406 -0
  282. sage/schemes/plane_conics/con_rational_function_field.py +581 -0
  283. sage/schemes/plane_conics/constructor.py +249 -0
  284. sage/schemes/plane_quartics/all.py +2 -0
  285. sage/schemes/plane_quartics/quartic_constructor.py +71 -0
  286. sage/schemes/plane_quartics/quartic_generic.py +53 -0
  287. sage/schemes/riemann_surfaces/all.py +1 -0
  288. sage/schemes/riemann_surfaces/riemann_surface.py +4177 -0
  289. sage_wheels/share/cremona/cremona_mini.db +0 -0
  290. sage_wheels/share/ellcurves/rank0 +30427 -0
  291. sage_wheels/share/ellcurves/rank1 +31871 -0
  292. sage_wheels/share/ellcurves/rank10 +6 -0
  293. sage_wheels/share/ellcurves/rank11 +6 -0
  294. sage_wheels/share/ellcurves/rank12 +1 -0
  295. sage_wheels/share/ellcurves/rank14 +1 -0
  296. sage_wheels/share/ellcurves/rank15 +1 -0
  297. sage_wheels/share/ellcurves/rank17 +1 -0
  298. sage_wheels/share/ellcurves/rank19 +1 -0
  299. sage_wheels/share/ellcurves/rank2 +2388 -0
  300. sage_wheels/share/ellcurves/rank20 +1 -0
  301. sage_wheels/share/ellcurves/rank21 +1 -0
  302. sage_wheels/share/ellcurves/rank22 +1 -0
  303. sage_wheels/share/ellcurves/rank23 +1 -0
  304. sage_wheels/share/ellcurves/rank24 +1 -0
  305. sage_wheels/share/ellcurves/rank28 +1 -0
  306. sage_wheels/share/ellcurves/rank3 +836 -0
  307. sage_wheels/share/ellcurves/rank4 +10 -0
  308. sage_wheels/share/ellcurves/rank5 +5 -0
  309. sage_wheels/share/ellcurves/rank6 +5 -0
  310. sage_wheels/share/ellcurves/rank7 +5 -0
  311. sage_wheels/share/ellcurves/rank8 +6 -0
  312. sage_wheels/share/ellcurves/rank9 +7 -0
@@ -0,0 +1,1931 @@
1
+ # sage_setup: distribution = sagemath-schemes
2
+ # sage.doctest: needs sage.geometry.polyhedron sage.graphs sage.groups sage.rings.number_field
3
+ r"""
4
+ Zariski-Van Kampen method implementation
5
+
6
+ This file contains functions to compute the fundamental group of
7
+ the complement of a curve in the complex affine or projective plane,
8
+ using Zariski-Van Kampen approach. It depends on the package ``sirocco``.
9
+
10
+ The current implementation allows to compute a presentation of the
11
+ fundamental group of curves over the rationals or number fields with
12
+ a fixed embedding on `\QQbar`.
13
+
14
+ Instead of computing a representation of the braid monodromy, we
15
+ choose several base points and a system of paths joining them that
16
+ generate all the necessary loops around the points of the discriminant.
17
+ The group is generated by the free groups over these points, and
18
+ braids over these paths give relations between these generators.
19
+ This big group presentation is simplified at the end.
20
+
21
+ AUTHORS:
22
+
23
+ - Miguel Marco (2015-09-30): Initial version
24
+
25
+ EXAMPLES::
26
+
27
+ sage: # needs sirocco
28
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy
29
+ sage: R.<x, y> = QQ[]
30
+ sage: f = y^3 + x^3 - 1
31
+ sage: braid_monodromy(f)
32
+ ([s1*s0, s1*s0, s1*s0], {0: 0, 1: 0, 2: 0}, {}, 3)
33
+ sage: fundamental_group(f)
34
+ Finitely presented group < x0 | >
35
+ """
36
+ # ****************************************************************************
37
+ # Copyright (C) 2015 Miguel Marco <mmarco@unizar.es>
38
+ #
39
+ # This program is free software: you can redistribute it and/or modify
40
+ # it under the terms of the GNU General Public License as published by
41
+ # the Free Software Foundation, either version 2 of the License, or
42
+ # (at your option) any later version.
43
+ # https://www.gnu.org/licenses/
44
+ # ****************************************************************************
45
+ from copy import copy
46
+ from itertools import combinations
47
+ from typing import Any
48
+
49
+ from sage.combinat.permutation import Permutation
50
+ from sage.functions.generalized import sign
51
+ from sage.geometry.voronoi_diagram import VoronoiDiagram
52
+ from sage.graphs.graph import Graph
53
+ from sage.groups.braid import BraidGroup
54
+ from sage.groups.free_group import FreeGroup
55
+ from sage.groups.perm_gps.permgroup_named import SymmetricGroup
56
+ from sage.matrix.constructor import matrix
57
+ from sage.misc.cachefunc import cached_function
58
+ from sage.misc.flatten import flatten
59
+ from sage.misc.lazy_import import lazy_import
60
+ from sage.misc.misc_c import prod
61
+ from sage.parallel.decorate import parallel
62
+ from sage.rings.complex_interval_field import ComplexIntervalField
63
+ from sage.rings.complex_mpfr import ComplexField
64
+ from sage.rings.integer_ring import ZZ
65
+ from sage.rings.number_field.number_field import NumberField
66
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
67
+ from sage.rings.qqbar import QQbar
68
+ from sage.rings.rational_field import QQ
69
+ from sage.rings.real_mpfr import RealField
70
+ from sage.schemes.curves.constructor import Curve
71
+
72
+ lazy_import('sage.libs.braiding', ['leftnormalform', 'rightnormalform'])
73
+
74
+ roots_interval_cache: dict[tuple, Any] = {}
75
+
76
+
77
+ def braid_from_piecewise(strands):
78
+ r"""
79
+ Compute the braid corresponding to the piecewise linear curves strands.
80
+
81
+ INPUT:
82
+
83
+ - ``strands`` -- list of lists of tuples ``(t, c1, c2)``, where ``t``
84
+ is a number between 0 and 1, and ``c1`` and ``c2`` are rationals
85
+ or algebraic reals
86
+
87
+ OUTPUT: the braid formed by the piecewise linear strands
88
+
89
+ EXAMPLES::
90
+
91
+ sage: # needs sirocco
92
+ sage: from sage.schemes.curves.zariski_vankampen import braid_from_piecewise
93
+ sage: paths = [[(0, 0, 1), (0.2, -1, -0.5), (0.8, -1, 0), (1, 0, -1)],
94
+ ....: [(0, -1, 0), (0.5, 0, -1), (1, 1, 0)],
95
+ ....: [(0, 1, 0), (0.5, 1, 1), (1, 0, 1)]]
96
+ sage: braid_from_piecewise(paths)
97
+ s0*s1
98
+ """
99
+ L = strands
100
+ i = min(val[1][0] for val in L)
101
+ totalpoints = [[[a[0][1], a[0][2]]] for a in L]
102
+ indices = [1 for a in range(len(L))]
103
+ while i < 1:
104
+ for j, val in enumerate(L):
105
+ if val[indices[j]][0] > i:
106
+ xauxr = val[indices[j] - 1][1]
107
+ xauxi = val[indices[j] - 1][2]
108
+ yauxr = val[indices[j]][1]
109
+ yauxi = val[indices[j]][2]
110
+ aaux = val[indices[j] - 1][0]
111
+ baux = val[indices[j]][0]
112
+ interpolar = xauxr + (yauxr - xauxr) * (i - aaux) / (baux - aaux)
113
+ interpolai = xauxi + (yauxi - xauxi) * (i - aaux) / (baux - aaux)
114
+ totalpoints[j].append([interpolar, interpolai])
115
+ else:
116
+ totalpoints[j].append([val[indices[j]][1],
117
+ val[indices[j]][2]])
118
+ indices[j] = indices[j] + 1
119
+ i = min(val[indices[k]][0] for k, val in enumerate(L))
120
+
121
+ for j, val in enumerate(L):
122
+ totalpoints[j].append([val[-1][1], val[-1][2]])
123
+ braid = []
124
+ G = SymmetricGroup(len(totalpoints))
125
+
126
+ def sgn(x, y):
127
+ if x < y:
128
+ return 1
129
+ if x > y:
130
+ return -1
131
+ return 0
132
+
133
+ for i in range(len(totalpoints[0]) - 1):
134
+ l1 = [totalpoints[j][i] for j in range(len(L))]
135
+ l2 = [totalpoints[j][i + 1] for j in range(len(L))]
136
+ M = [[l1[s], l2[s]] for s in range(len(l1))]
137
+ M.sort()
138
+ l1 = [a[0] for a in M]
139
+ l2 = [a[1] for a in M]
140
+ cruces = []
141
+ for j, l2j in enumerate(l2):
142
+ l1j = l1[j]
143
+ for k in range(j):
144
+ if l2j < l2[k]:
145
+ t = (l1j[0] - l1[k][0]) / ((l2[k][0] - l2j[0]) + (l1j[0] - l1[k][0]))
146
+ s = sgn(l1[k][1] * (1 - t) + t * l2[k][1],
147
+ l1j[1] * (1 - t) + t * l2j[1])
148
+ cruces.append([t, k, j, s])
149
+ if cruces:
150
+ cruces.sort()
151
+ P = G(Permutation([]))
152
+ while cruces:
153
+ # we select the crosses in the same t
154
+ crucesl = [c for c in cruces if c[0] == cruces[0][0]]
155
+ crossesl = [(P(c[2] + 1) - P(c[1] + 1), c[1], c[2], c[3])
156
+ for c in crucesl]
157
+ cruces = cruces[len(crucesl):]
158
+ while crossesl:
159
+ crossesl.sort()
160
+ c = crossesl.pop(0)
161
+ braid.append(c[3] * min(map(P, [c[1] + 1, c[2] + 1])))
162
+ P = G(Permutation([(c[1] + 1, c[2] + 1)])) * P
163
+ crossesl = [(P(cr[2] + 1) - P(cr[1] + 1),
164
+ cr[1], cr[2], cr[3]) for cr in crossesl]
165
+
166
+ B = BraidGroup(len(L))
167
+ return B(braid)
168
+
169
+
170
+ def discrim(pols) -> tuple:
171
+ r"""
172
+ Return the points in the discriminant of the product of the polynomials
173
+ of a list or tuple ``pols``.
174
+
175
+ The result is the set of values of the first variable for which
176
+ two roots in the second variable coincide.
177
+
178
+ INPUT:
179
+
180
+ - ``pols`` -- list or tuple of polynomials in two variables with
181
+ coefficients in a number field with a fixed embedding in `\QQbar`
182
+
183
+ OUTPUT: a tuple with the roots of the discriminant in `\QQbar`
184
+
185
+ EXAMPLES::
186
+
187
+ sage: from sage.schemes.curves.zariski_vankampen import discrim
188
+ sage: R.<x, y> = QQ[]
189
+ sage: flist = (y^3 + x^3 - 1, 2 * x + y)
190
+ sage: sorted((discrim(flist)))
191
+ [-0.522757958574711?,
192
+ -0.500000000000000? - 0.866025403784439?*I,
193
+ -0.500000000000000? + 0.866025403784439?*I,
194
+ 0.2613789792873551? - 0.4527216721561923?*I,
195
+ 0.2613789792873551? + 0.4527216721561923?*I,
196
+ 1]
197
+ """
198
+ x, y = pols[0].parent().gens()
199
+ field = pols[0].base_ring()
200
+ pol_ring = PolynomialRing(field, (x,))
201
+
202
+ @parallel
203
+ def discrim_pairs(f, g):
204
+ if g is None:
205
+ return pol_ring(f.discriminant(y))
206
+ return pol_ring(f.resultant(g, y))
207
+
208
+ pairs = [(f, None) for f in pols] + [tuple(t) for t
209
+ in combinations(pols, 2)]
210
+ fdiscrim = discrim_pairs(pairs)
211
+ rts = ()
212
+ poly = 1
213
+ for u in fdiscrim:
214
+ h0 = u[1].radical()
215
+ h1 = h0 // h0.gcd(poly)
216
+ rts += tuple(h1.roots(QQbar, multiplicities=False))
217
+ poly = poly * h1
218
+ return rts
219
+
220
+
221
+ @cached_function
222
+ def corrected_voronoi_diagram(points) -> VoronoiDiagram:
223
+ r"""
224
+ Compute a Voronoi diagram of a set of points with rational coordinates.
225
+
226
+ The given points are granted to lie one in each bounded region.
227
+
228
+ INPUT:
229
+
230
+ - ``points`` -- tuple of complex numbers
231
+
232
+ OUTPUT:
233
+
234
+ A Voronoi diagram constructed from rational approximations of the points,
235
+ with the guarantee that each bounded region contains exactly one of the
236
+ input points.
237
+
238
+ EXAMPLES::
239
+
240
+ sage: from sage.schemes.curves.zariski_vankampen import corrected_voronoi_diagram
241
+ sage: points = (2, I, 0.000001, 0, 0.000001*I)
242
+ sage: V = corrected_voronoi_diagram(points)
243
+ sage: V
244
+ The Voronoi diagram of 9 points of dimension 2 in the Rational Field
245
+ sage: V.regions()
246
+ {P(-7, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices and 2 rays,
247
+ P(0, -7): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices and 2 rays,
248
+ P(0, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices,
249
+ P(0, 1): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices,
250
+ P(0, 1/1000000): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices,
251
+ P(0, 7): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 3 vertices and 2 rays,
252
+ P(1/1000000, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices,
253
+ P(2, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices,
254
+ P(7, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices and 2 rays}
255
+ """
256
+ prec = 53
257
+ point_coordinates = [(p.real(), p.imag()) for p in points]
258
+ while True:
259
+ RF = RealField(prec)
260
+ apprpoints = {(QQ(RF(p[0])), QQ(RF(p[1]))): p
261
+ for p in point_coordinates}
262
+ added_points = 3 * max(map(abs, flatten(apprpoints))) + 1
263
+ configuration = list(apprpoints.keys()) + [(added_points, 0),
264
+ (-added_points, 0),
265
+ (0, added_points),
266
+ (0, -added_points)]
267
+ V = VoronoiDiagram(configuration)
268
+ valid = True
269
+ for r in V.regions().items():
270
+ if (not r[1].rays() and
271
+ not r[1].interior_contains(apprpoints[r[0].affine()])):
272
+ prec += 53
273
+ valid = False
274
+ break
275
+ if valid:
276
+ break
277
+ return V
278
+
279
+
280
+ def orient_circuit(circuit, convex=False, precision=53, verbose=False) -> tuple:
281
+ r"""
282
+ Reverse a circuit if it goes clockwise; otherwise leave it unchanged.
283
+
284
+ INPUT:
285
+
286
+ - ``circuit`` -- a circuit in the graph of a Voronoi Diagram, given
287
+ by a list of edges
288
+
289
+ - ``convex`` -- boolean (default: ``False``); if set to ``True`` a simpler
290
+ computation is made
291
+
292
+ - ``precision`` -- bits of precision (default: 53)
293
+
294
+ - ``verbose`` -- boolean (default: ``False``); for testing purposes
295
+
296
+ OUTPUT:
297
+
298
+ The same circuit if it goes counterclockwise, and its reversed otherwise,
299
+ given as the ordered list of vertices with identical extremities.
300
+
301
+ EXAMPLES::
302
+
303
+ sage: from sage.schemes.curves.zariski_vankampen import orient_circuit
304
+ sage: points = [(-4, 0), (4, 0), (0, 4), (0, -4), (0, 0)]
305
+ sage: V = VoronoiDiagram(points)
306
+ sage: E = Graph()
307
+ sage: for reg in V.regions().values():
308
+ ....: if reg.rays() or reg.lines():
309
+ ....: E = E.union(reg.vertex_graph())
310
+ sage: E.vertices(sort=True)
311
+ [A vertex at (-2, -2),
312
+ A vertex at (-2, 2),
313
+ A vertex at (2, -2),
314
+ A vertex at (2, 2)]
315
+ sage: cir = E.eulerian_circuit()
316
+ sage: cir
317
+ [(A vertex at (-2, -2), A vertex at (2, -2), None),
318
+ (A vertex at (2, -2), A vertex at (2, 2), None),
319
+ (A vertex at (2, 2), A vertex at (-2, 2), None),
320
+ (A vertex at (-2, 2), A vertex at (-2, -2), None)]
321
+ sage: cir_oriented = orient_circuit(cir); cir_oriented
322
+ (A vertex at (-2, -2), A vertex at (2, -2), A vertex at (2, 2),
323
+ A vertex at (-2, 2), A vertex at (-2, -2))
324
+ sage: cirinv = list(reversed([(c[1],c[0],c[2]) for c in cir]))
325
+ sage: cirinv
326
+ [(A vertex at (-2, -2), A vertex at (-2, 2), None),
327
+ (A vertex at (-2, 2), A vertex at (2, 2), None),
328
+ (A vertex at (2, 2), A vertex at (2, -2), None),
329
+ (A vertex at (2, -2), A vertex at (-2, -2), None)]
330
+ sage: orient_circuit(cirinv) == cir_oriented
331
+ True
332
+ sage: cir_oriented == orient_circuit(cir, convex=True)
333
+ True
334
+ sage: P0=[(1,1/2),(0,1),(1,1)]; P1=[(0,3/2),(-1,0)]
335
+ sage: Q = Polyhedron(P0).vertices()
336
+ sage: Q = [Q[2], Q[0], Q[1]] + [_ for _ in reversed(Polyhedron(P1).vertices())]
337
+ sage: Q
338
+ [A vertex at (1, 1/2), A vertex at (0, 1), A vertex at (1, 1),
339
+ A vertex at (0, 3/2), A vertex at (-1, 0)]
340
+ sage: E = Graph()
341
+ sage: for v, w in zip(Q, Q[1:] + [Q[0]]):
342
+ ....: E.add_edge((v, w))
343
+ sage: cir = orient_circuit(E.eulerian_circuit(), precision=1, verbose=True)
344
+ 2
345
+ sage: cir
346
+ (A vertex at (1, 1/2), A vertex at (0, 1), A vertex at (1, 1),
347
+ A vertex at (0, 3/2), A vertex at (-1, 0), A vertex at (1, 1/2))
348
+ """
349
+ vectors = [v[1].vector() - v[0].vector() for v in circuit]
350
+ circuit_vertex = (circuit[0][0],) + tuple(e[1] for e in circuit)
351
+ circuit_vertex = tuple(circuit_vertex)
352
+ if convex:
353
+ pr = matrix([vectors[0], vectors[1]]).determinant()
354
+ if pr > 0:
355
+ # return circuit
356
+ return circuit_vertex
357
+ elif pr < 0:
358
+ return tuple(reversed(circuit_vertex))
359
+ prec = precision
360
+ while True:
361
+ CIF = ComplexIntervalField(prec)
362
+ totalangle = sum((CIF(*vectors[i]) / CIF(*vectors[i - 1])).argument()
363
+ for i in range(len(vectors)))
364
+ if totalangle < 0:
365
+ return tuple(reversed(circuit_vertex))
366
+ if totalangle > 0:
367
+ return circuit_vertex
368
+ prec *= 2
369
+ if verbose:
370
+ print(prec)
371
+
372
+
373
+ def voronoi_cells(V, vertical_lines=frozenset()) -> tuple:
374
+ r"""
375
+ Compute the graph, the boundary graph, a base point, a positive orientation
376
+ of the boundary graph, and the dual graph of a corrected Voronoi diagram.
377
+
378
+ INPUT:
379
+
380
+ - ``V`` -- a corrected Voronoi diagram
381
+
382
+ - ``vertical_lines`` -- frozenset (default: ``frozenset()``); indices of the
383
+ vertical lines
384
+
385
+ OUTPUT:
386
+
387
+ - ``G`` -- the graph of the 1-skeleton of ``V``
388
+ - ``E`` -- the subgraph of the boundary
389
+ - ``p`` -- a vertex in ``E``
390
+ - ``EC`` -- list of vertices (representing a counterclockwise orientation
391
+ of ``E``) with identical first and last elements)
392
+ - ``DG`` -- the dual graph of ``V``, where the vertices are labelled
393
+ by the compact regions of ``V`` and the edges by their dual edges
394
+ - ``vertical_regions`` -- dictionary for the regions associated
395
+ with vertical lines
396
+
397
+ EXAMPLES::
398
+
399
+ sage: from sage.schemes.curves.zariski_vankampen import corrected_voronoi_diagram, voronoi_cells
400
+ sage: points = (2, I, 0.000001, 0, 0.000001*I)
401
+ sage: V = corrected_voronoi_diagram(points)
402
+ sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((1,)))
403
+ sage: Gv = G.vertices(sort=True)
404
+ sage: Ge = G.edges(sort=True)
405
+ sage: len(Gv), len(Ge)
406
+ (12, 16)
407
+ sage: Ev = E.vertices(sort=True); Ev
408
+ [A vertex at (-4, 4),
409
+ A vertex at (-49000001/14000000, 1000001/2000000),
410
+ A vertex at (-7/2, -7/2),
411
+ A vertex at (-7/2, 1/2000000),
412
+ A vertex at (1/2000000, -7/2),
413
+ A vertex at (2000001/2000000, -24500001/7000000),
414
+ A vertex at (11/4, 4),
415
+ A vertex at (9/2, -9/2),
416
+ A vertex at (9/2, 9/2)]
417
+ sage: Ev.index(p)
418
+ 7
419
+ sage: EC
420
+ (A vertex at (9/2, -9/2),
421
+ A vertex at (9/2, 9/2),
422
+ A vertex at (11/4, 4),
423
+ A vertex at (-4, 4),
424
+ A vertex at (-49000001/14000000, 1000001/2000000),
425
+ A vertex at (-7/2, 1/2000000),
426
+ A vertex at (-7/2, -7/2),
427
+ A vertex at (1/2000000, -7/2),
428
+ A vertex at (2000001/2000000, -24500001/7000000),
429
+ A vertex at (9/2, -9/2))
430
+ sage: len(DG.vertices(sort=True)), len(DG.edges(sort=True))
431
+ (5, 7)
432
+ sage: edg = DG.edges(sort=True)[0]; edg
433
+ ((0,
434
+ (A vertex at (9/2, -9/2),
435
+ A vertex at (9/2, 9/2),
436
+ A vertex at (11/4, 4),
437
+ A vertex at (2000001/2000000, 500001/1000000),
438
+ A vertex at (2000001/2000000, -24500001/7000000),
439
+ A vertex at (9/2, -9/2))),
440
+ (1,
441
+ (A vertex at (-49000001/14000000, 1000001/2000000),
442
+ A vertex at (1000001/2000000, 1000001/2000000),
443
+ A vertex at (2000001/2000000, 500001/1000000),
444
+ A vertex at (11/4, 4),
445
+ A vertex at (-4, 4),
446
+ A vertex at (-49000001/14000000, 1000001/2000000))),
447
+ (A vertex at (2000001/2000000, 500001/1000000), A vertex at (11/4, 4), None))
448
+ sage: edg[-1] in Ge
449
+ True
450
+ sage: VR
451
+ {1: (A vertex at (-49000001/14000000, 1000001/2000000),
452
+ A vertex at (1000001/2000000, 1000001/2000000),
453
+ A vertex at (2000001/2000000, 500001/1000000),
454
+ A vertex at (11/4, 4),
455
+ A vertex at (-4, 4),
456
+ A vertex at (-49000001/14000000, 1000001/2000000))}
457
+ """
458
+ regions = V.regions()
459
+ points = [p for p in V.regions().keys() if V.regions()[p].is_compact()]
460
+ compact_regions = [regions[p] for p in points]
461
+ vertical_regions = {}
462
+ non_compact_regions = [reg for reg in V.regions().values()
463
+ if not reg.is_compact()]
464
+ G = Graph([u.vertices() for v in compact_regions for u in v.faces(1)],
465
+ format='list_of_edges')
466
+ E = Graph([u.vertices() for v in non_compact_regions for u in v.faces(1)
467
+ if u.is_compact()], format='list_of_edges')
468
+ p = next(E.vertex_iterator())
469
+ EC = orient_circuit(E.eulerian_circuit())
470
+ DG = Graph()
471
+ for i, reg in enumerate(compact_regions):
472
+ Greg0 = orient_circuit(reg.graph().eulerian_circuit(), convex=True)
473
+ if i in vertical_lines:
474
+ vertical_regions[i] = Greg0
475
+ DG.add_vertex((i, Greg0))
476
+ for e in G.edges(sort=True):
477
+ a, b = e[:2]
478
+ regs = [v for v in DG.vertices(sort=True) if a in v[1] and b in v[1]]
479
+ if len(regs) == 2:
480
+ DG.add_edge(regs[0], regs[1], e)
481
+ return (G, E, p, EC, DG, vertical_regions)
482
+
483
+
484
+ def followstrand(f, factors, x0, x1, y0a, prec=53) -> list[tuple]:
485
+ r"""
486
+ Return a piecewise linear approximation of the homotopy continuation
487
+ of the root ``y0a`` from ``x0`` to ``x1``.
488
+
489
+ INPUT:
490
+
491
+ - ``f`` -- an irreducible polynomial in two variables
492
+ - ``factors`` -- list of irreducible polynomials in two variables
493
+ - ``x0`` -- a complex value, where the homotopy starts
494
+ - ``x1`` -- a complex value, where the homotopy ends
495
+ - ``y0a`` -- an approximate solution of the polynomial `F(y) = f(x_0, y)`
496
+ - ``prec`` -- the precision to use
497
+
498
+ OUTPUT:
499
+
500
+ A list of values `(t, y_{tr}, y_{ti})` such that:
501
+
502
+ - ``t`` is a real number between zero and one
503
+ - `f(t \cdot x_1 + (1-t) \cdot x_0, y_{tr} + I \cdot y_{ti})`
504
+ is zero (or a good enough approximation)
505
+ - the piecewise linear path determined by the points has a tubular
506
+ neighborhood where the actual homotopy continuation path lies, and
507
+ no other root of ``f``, nor any root of the polynomials in ``factors``,
508
+ intersects it.
509
+
510
+ EXAMPLES::
511
+
512
+ sage: # needs sirocco
513
+ sage: from sage.schemes.curves.zariski_vankampen import followstrand
514
+ sage: R.<x, y> = QQ[]
515
+ sage: f = x^2 + y^3
516
+ sage: x0 = CC(1, 0)
517
+ sage: x1 = CC(1, 0.5)
518
+ sage: followstrand(f, [], x0, x1, -1.0) # abs tol 1e-15
519
+ [(0.0, -1.0, 0.0),
520
+ (0.7500000000000001, -1.015090921153253, -0.24752813818386948),
521
+ (1.0, -1.026166099551513, -0.32768940253604323)]
522
+ sage: fup = f.subs({y: y - 1/10})
523
+ sage: fdown = f.subs({y: y + 1/10})
524
+ sage: followstrand(f, [fup, fdown], x0, x1, -1.0) # abs tol 1e-15
525
+ [(0.0, -1.0, 0.0),
526
+ (0.5303300858899107, -1.0076747107983448, -0.17588022709184917),
527
+ (0.7651655429449553, -1.015686131039112, -0.25243563967299404),
528
+ (1.0, -1.026166099551513, -0.3276894025360433)]
529
+ """
530
+ if f.degree() == 1:
531
+ CF = ComplexField(prec)
532
+ g = f.change_ring(CF)
533
+ (x, y) = g.parent().gens()
534
+ y0 = CF[y](g.subs({x: x0})).roots()[0][0]
535
+ y1 = CF[y](g.subs({x: x1})).roots()[0][0]
536
+ res = [(0.0, y0.real(), y0.imag()), (1.0, y1.real(), y1.imag())]
537
+ return res
538
+ CIF = ComplexIntervalField(prec)
539
+ CC = ComplexField(prec)
540
+ G = f.change_ring(QQbar).change_ring(CIF)
541
+ x, y = G.parent().gens()
542
+ g = G.subs({x: (1 - x) * CIF(x0) + x * CIF(x1)})
543
+ coefs = []
544
+ deg = g.total_degree()
545
+ for d in range(deg + 1):
546
+ for i in range(d + 1):
547
+ c = CIF(g.coefficient({x: d - i, y: i}))
548
+ cr = c.real()
549
+ ci = c.imag()
550
+ coefs += list(cr.endpoints())
551
+ coefs += list(ci.endpoints())
552
+ yr = CC(y0a).real()
553
+ yi = CC(y0a).imag()
554
+ coefsfactors = []
555
+ degsfactors = []
556
+ for fc in factors:
557
+ degfc = fc.degree()
558
+ degsfactors.append(degfc)
559
+ G = fc.change_ring(QQbar).change_ring(CIF)
560
+ g = G.subs({x: (1 - x) * CIF(x0) + x * CIF(x1)})
561
+ for d in range(degfc + 1):
562
+ for i in range(d + 1):
563
+ c = CIF(g.coefficient({x: d - i, y: i}))
564
+ cr = c.real()
565
+ ci = c.imag()
566
+ coefsfactors += list(cr.endpoints())
567
+ coefsfactors += list(ci.endpoints())
568
+ from sage.libs.sirocco import (contpath, contpath_mp,
569
+ contpath_comps, contpath_mp_comps)
570
+ try:
571
+ if prec == 53:
572
+ if factors:
573
+ points = contpath_comps(deg, coefs, yr, yi, degsfactors, coefsfactors)
574
+ else:
575
+ points = contpath(deg, coefs, yr, yi)
576
+ elif factors:
577
+ points = contpath_mp_comps(deg, coefs, yr, yi, prec, degsfactors, coefsfactors)
578
+ else:
579
+ points = contpath_mp(deg, coefs, yr, yi, prec)
580
+ return points
581
+ except Exception:
582
+ return followstrand(f, factors, x0, x1, y0a, 2 * prec)
583
+
584
+
585
+ def newton(f, x0, i0):
586
+ r"""
587
+ Return the interval Newton operator.
588
+
589
+ INPUT:
590
+
591
+ - ``f`` -- a univariate polynomial
592
+ - ``x0`` -- a number
593
+ - ``I0`` -- an interval
594
+
595
+ OUTPUT:
596
+
597
+ The interval `x_0-\frac{f(x_0)}{f'(I_0)}`
598
+
599
+ EXAMPLES::
600
+
601
+ sage: from sage.schemes.curves.zariski_vankampen import newton
602
+ sage: R.<x> = QQbar[]
603
+ sage: f = x^3 + x
604
+ sage: x0 = 1/10
605
+ sage: I0 = RIF((-1/5,1/5))
606
+ sage: n = newton(f, x0, I0)
607
+ sage: n
608
+ 0.0?
609
+ sage: n.real().endpoints()
610
+ (-0.0147727272727274, 0.00982142857142862)
611
+ sage: n.imag().endpoints()
612
+ (0.000000000000000, -0.000000000000000)
613
+ """
614
+ return x0 - f(x0) / f.derivative()(i0)
615
+
616
+
617
+ def fieldI(field: NumberField) -> NumberField:
618
+ r"""
619
+ Return the (either double or trivial) extension of a number field which contains ``I``.
620
+
621
+ INPUT:
622
+
623
+ - ``field`` -- a number field with an embedding in `\QQbar`
624
+
625
+ OUTPUT: the extension ``F`` of ``field`` containing ``I`` with an embedding in `\QQbar`
626
+
627
+ EXAMPLES::
628
+
629
+ sage: from sage.schemes.curves.zariski_vankampen import fieldI
630
+ sage: x = polygen(QQ, 'x')
631
+ sage: p = QQ[x](x^5 + 2 * x + 1)
632
+ sage: a0 = p.roots(QQbar, multiplicities=False)[0]
633
+ sage: F0.<a> = NumberField(p, embedding=a0)
634
+ sage: fieldI(F0)
635
+ Number Field in prim with defining polynomial
636
+ x^10 + 5*x^8 + 14*x^6 - 2*x^5 - 10*x^4 + 20*x^3 - 11*x^2 - 14*x + 10
637
+ with prim = 0.4863890359345430? + 1.000000000000000?*I
638
+ sage: F0 = CyclotomicField(5)
639
+ sage: fieldI(F0)
640
+ Number Field in prim with defining polynomial
641
+ x^8 - 2*x^7 + 7*x^6 - 10*x^5 + 16*x^4 - 10*x^3 - 2*x^2 + 4*x + 1
642
+ with prim = -0.3090169943749474? + 0.04894348370484643?*I
643
+ sage: fieldI(QuadraticField(3))
644
+ Number Field in prim with defining polynomial x^4 - 4*x^2 + 16
645
+ with prim = -1.732050807568878? + 1.000000000000000?*I
646
+ sage: fieldI(QuadraticField(-3))
647
+ Number Field in prim with defining polynomial x^4 + 8*x^2 + 4
648
+ with prim = 0.?e-18 - 0.732050807568878?*I
649
+
650
+ If ``I`` is already in the field, the result is the field itself::
651
+
652
+ sage: from sage.schemes.curves.zariski_vankampen import fieldI
653
+ sage: p = QQ[x](x^4 + 1)
654
+ sage: a0 = p.roots(QQbar, multiplicities=False)[0]
655
+ sage: F0.<a> = NumberField(p, embedding=a0)
656
+ sage: F1 = fieldI(F0)
657
+ sage: F0 == F1
658
+ True
659
+ sage: QuadraticField(-1) == fieldI(QuadraticField(-1))
660
+ True
661
+ """
662
+ I0 = QQbar.gen()
663
+ if I0 in field:
664
+ return field
665
+ field_a = field[I0]
666
+ field_b = field_a.absolute_field('b0')
667
+ b0 = field_b.gen()
668
+ q = b0.minpoly()
669
+ qembd = field_b.embeddings(QQbar)
670
+ for h1 in qembd:
671
+ b1 = h1(b0)
672
+ b2 = h1(field_b(field_a.gen(0)))
673
+ b3 = QQbar(field.gen(0))
674
+ F1 = NumberField(q, 'prim', embedding=b1)
675
+ if b3 in F1 and b2.imag() > 0:
676
+ return F1
677
+
678
+
679
+ @parallel
680
+ def roots_interval(f, x0) -> dict:
681
+ """
682
+ Find disjoint intervals that isolate the roots of a polynomial for a fixed
683
+ value of the first variable.
684
+
685
+ INPUT:
686
+
687
+ - ``f`` -- a bivariate squarefree polynomial
688
+ - ``x0`` -- a Gauss rational number corresponding to the first coordinate
689
+
690
+ The intervals are taken as big as possible to be able to detect when two
691
+ approximate roots of `f(x_0, y)` correspond to the same exact root, where
692
+ `f` is the product of the polynomials in `flist`.
693
+
694
+ The result is given as a dictionary, where the keys are
695
+ approximations to the roots with rational real and imaginary
696
+ parts, and the values are intervals containing them.
697
+
698
+ EXAMPLES::
699
+
700
+ sage: from sage.schemes.curves.zariski_vankampen import roots_interval, fieldI
701
+ sage: R.<x, y> = QQ[]
702
+ sage: K = fieldI(QQ)
703
+ sage: f = y^3 - x^2
704
+ sage: f = f.change_ring(K)
705
+ sage: ri = roots_interval(f, 1)
706
+ sage: ri
707
+ {-138907099/160396102*I - 1/2: -1.? - 1.?*I,
708
+ 138907099/160396102*I - 1/2: -1.? + 1.?*I,
709
+ 1: 1.? + 0.?*I}
710
+ sage: [r.endpoints() for r in ri.values()]
711
+ [(0.566987298107781 - 0.433012701892219*I,
712
+ 1.43301270189222 + 0.433012701892219*I,
713
+ 0.566987298107781 + 0.433012701892219*I,
714
+ 1.43301270189222 - 0.433012701892219*I),
715
+ (-0.933012701892219 - 1.29903810567666*I,
716
+ -0.0669872981077806 - 0.433012701892219*I,
717
+ -0.933012701892219 - 0.433012701892219*I,
718
+ -0.0669872981077806 - 1.29903810567666*I),
719
+ (-0.933012701892219 + 0.433012701892219*I,
720
+ -0.0669872981077806 + 1.29903810567666*I,
721
+ -0.933012701892219 + 1.29903810567666*I,
722
+ -0.0669872981077806 + 0.433012701892219*I)]
723
+ """
724
+ F1 = f.base_ring()
725
+ x, y = f.parent().gens()
726
+ fx = F1[y](f.subs({x: F1(x0)}))
727
+ roots = fx.roots(QQbar, multiplicities=False)
728
+ result = {}
729
+ for i, r in enumerate(roots):
730
+ prec = 53
731
+ IF = ComplexIntervalField(prec)
732
+ CF = ComplexField(prec)
733
+ divisor = 4
734
+ diam = min((CF(r) - CF(r0)).abs()
735
+ for r0 in roots[:i] + roots[i + 1:]) / divisor
736
+ envelop = IF(diam) * IF((-1, 1), (-1, 1))
737
+ while newton(fx, r, r + envelop) not in r + envelop:
738
+ prec += 53
739
+ IF = ComplexIntervalField(prec)
740
+ CF = ComplexField(prec)
741
+ divisor *= 2
742
+ diam = min((CF(r) - CF(r0)).abs()
743
+ for r0 in roots[:i] + roots[i + 1:]) / divisor
744
+ envelop = IF(diam) * IF((-1, 1), (-1, 1))
745
+ qapr = QQ(CF(r).real()) + QQbar.gen() * QQ(CF(r).imag())
746
+ if qapr not in r + envelop:
747
+ raise ValueError("could not approximate roots with exact values")
748
+ result[qapr] = r + envelop
749
+ return result
750
+
751
+
752
+ def roots_interval_cached(f, x0) -> dict:
753
+ r"""
754
+ Cached version of :func:`roots_interval`.
755
+
756
+ TESTS::
757
+
758
+ sage: from sage.schemes.curves.zariski_vankampen import roots_interval, roots_interval_cached, roots_interval_cache, fieldI
759
+ sage: R.<x, y> = QQ[]
760
+ sage: K = fieldI(QQ)
761
+ sage: f = y^3 - x^2
762
+ sage: f = f.change_ring(K)
763
+ sage: (f, 1) in roots_interval_cache
764
+ False
765
+ sage: ri = roots_interval_cached(f, 1)
766
+ sage: ri
767
+ {-138907099/160396102*I - 1/2: -1.? - 1.?*I,
768
+ 138907099/160396102*I - 1/2: -1.? + 1.?*I,
769
+ 1: 1.? + 0.?*I}
770
+ sage: (f, 1) in roots_interval_cache
771
+ True
772
+ """
773
+ global roots_interval_cache
774
+ try:
775
+ return roots_interval_cache[(f, x0)]
776
+ except KeyError:
777
+ result = roots_interval(f, x0)
778
+ roots_interval_cache[(f, x0)] = result
779
+ return result
780
+
781
+
782
+ def populate_roots_interval_cache(inputs) -> None:
783
+ r"""
784
+ Call :func:`roots_interval` to the inputs that have not been
785
+ computed previously, and cache them.
786
+
787
+ INPUT:
788
+
789
+ - ``inputs`` -- list of tuples ``(f, x0)``
790
+
791
+ EXAMPLES::
792
+
793
+ sage: from sage.schemes.curves.zariski_vankampen import populate_roots_interval_cache, roots_interval_cache, fieldI
794
+ sage: R.<x,y> = QQ[]
795
+ sage: K=fieldI(QQ)
796
+ sage: f = y^5 - x^2
797
+ sage: f = f.change_ring(K)
798
+ sage: (f, 3) in roots_interval_cache
799
+ False
800
+ sage: populate_roots_interval_cache([(f, 3)])
801
+ sage: (f, 3) in roots_interval_cache
802
+ True
803
+ sage: roots_interval_cache[(f, 3)]
804
+ {-1.255469441943070? - 0.9121519421827974?*I: -2.? - 1.?*I,
805
+ -1.255469441943070? + 0.9121519421827974?*I: -2.? + 1.?*I,
806
+ 0.4795466549853897? - 1.475892845355996?*I: 1.? - 2.?*I,
807
+ 0.4795466549853897? + 1.475892845355996?*I: 1.? + 2.?*I,
808
+ 14421467174121563/9293107134194871: 2.? + 0.?*I}
809
+ """
810
+ global roots_interval_cache
811
+ tocompute = [inp for inp in inputs if inp not in roots_interval_cache]
812
+ problem_par = True
813
+ while problem_par: # hack to deal with random fails in parallelization
814
+ try:
815
+ result = roots_interval(tocompute)
816
+ for r in result:
817
+ roots_interval_cache[r[0][0]] = r[1]
818
+ problem_par = False
819
+ except TypeError:
820
+ pass
821
+
822
+
823
+ @parallel
824
+ def braid_in_segment(glist, x0, x1, precision={}):
825
+ """
826
+ Return the braid formed by the `y` roots of ``f`` when `x` moves
827
+ from ``x0`` to ``x1``.
828
+
829
+ INPUT:
830
+
831
+ - ``glist`` -- tuple of polynomials in two variables
832
+ - ``x0`` -- a Gauss rational
833
+ - ``x1`` -- a Gauss rational
834
+ - ``precision`` -- dictionary (default: `{}`) which assigns a number
835
+ precision bits to each element of ``glist``
836
+
837
+ OUTPUT: a braid
838
+
839
+ EXAMPLES::
840
+
841
+ sage: from sage.schemes.curves.zariski_vankampen import braid_in_segment, fieldI
842
+ sage: R.<x, y> = QQ[]
843
+ sage: K = fieldI(QQ)
844
+ sage: f = x^2 + y^3
845
+ sage: f = f.change_ring(K)
846
+ sage: x0 = 1
847
+ sage: x1 = 1 + I / 2
848
+ sage: braid_in_segment(tuple(_[0] for _ in f.factor()), x0, x1) # needs sirocco
849
+ s1
850
+
851
+ TESTS:
852
+
853
+ Check that :issue:`26503` is fixed::
854
+
855
+ sage: # needs sage.rings.real_mpfr sage.symbolic
856
+ sage: wp = QQ['t']([1, 1, 1]).roots(QQbar)[0][0]
857
+ sage: Kw.<wp> = NumberField(wp.minpoly(), embedding=wp)
858
+ sage: R.<x, y> = Kw[]
859
+ sage: z = -wp - 1
860
+ sage: f = y * (y + z) * x * (x - 1) * (x - y) * (x + z * y - 1) * (x + z * y + wp)
861
+ sage: from sage.schemes.curves.zariski_vankampen import fieldI, braid_in_segment
862
+ sage: Kw1 = fieldI(Kw)
863
+ sage: g = f.subs({x: x + 2 * y})
864
+ sage: g = g.change_ring(Kw1)
865
+ sage: p1 = QQbar(sqrt(-1/3))
866
+ sage: p1a = CC(p1)
867
+ sage: p1b = QQ(p1a.real()) + I*QQ(p1a.imag())
868
+ sage: p2 = QQbar(1/2 + sqrt(-1/3)/2)
869
+ sage: p2a = CC(p2)
870
+ sage: p2b = QQ(p2a.real()) + I*QQ(p2a.imag())
871
+ sage: glist = tuple([_[0] for _ in g.factor()])
872
+ sage: B = braid_in_segment(glist, p1b, p2b); B # needs sirocco
873
+ s5*s3^-1
874
+ """
875
+ precision1 = precision.copy()
876
+ g = prod(glist)
877
+ F1 = g.base_ring()
878
+ x, y = g.parent().gens()
879
+ intervals = {}
880
+ if not precision1:
881
+ precision1 = {f: 53 for f in glist}
882
+ y0s = []
883
+ for f in glist:
884
+ if f.variables() == (y,):
885
+ f0 = F1[y](f)
886
+ else:
887
+ f0 = F1[y](f.subs({x: F1(x0)}))
888
+ y0sf = f0.roots(QQbar, multiplicities=False)
889
+ y0s += list(y0sf)
890
+ while True:
891
+ CIFp = ComplexIntervalField(precision1[f])
892
+ intervals[f] = [r.interval(CIFp) for r in y0sf]
893
+ if not any(a.overlaps(b) for a, b in
894
+ combinations(intervals[f], 2)):
895
+ break
896
+ precision1[f] *= 2
897
+ strands = []
898
+ for f in glist:
899
+ for i in intervals[f]:
900
+ aux = followstrand(f, [p for p in glist if p != f],
901
+ x0, x1, i.center(), precision1[f])
902
+ strands.append(aux)
903
+ complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b]
904
+ for b in strands]
905
+ centralbraid = braid_from_piecewise(complexstrands)
906
+ initialstrands = []
907
+ finalstrands = []
908
+ initialintervals = roots_interval_cached(g, x0)
909
+ finalintervals = roots_interval_cached(g, x1)
910
+ I1 = QQbar.gen()
911
+ for cs in complexstrands:
912
+ ip = cs[0][1] + I1 * cs[0][2]
913
+ fp = cs[-1][1] + I1 * cs[-1][2]
914
+ matched = 0
915
+ for center, interval in initialintervals.items():
916
+ if ip in interval:
917
+ initialstrands.append([(0, center.real(), center.imag()),
918
+ (1, cs[0][1], cs[0][2])])
919
+ matched += 1
920
+ if matched != 1:
921
+ precision1 = {f: precision1[f] * 2 for f in glist}
922
+ return braid_in_segment(glist, x0, x1, precision=precision1)
923
+
924
+ matched = 0
925
+ for center, interval in finalintervals.items():
926
+ if fp in interval:
927
+ finalstrands.append([(0, cs[-1][1], cs[-1][2]),
928
+ (1, center.real(), center.imag())])
929
+ matched += 1
930
+ if matched != 1:
931
+ precision1 = {f: precision1[f] * 2 for f in glist}
932
+ return braid_in_segment(glist, x0, x1, precision=precision1)
933
+ initialbraid = braid_from_piecewise(initialstrands)
934
+ finalbraid = braid_from_piecewise(finalstrands)
935
+
936
+ return initialbraid * centralbraid * finalbraid
937
+
938
+
939
+ def geometric_basis(G, E, EC0, p, dual_graph,
940
+ vertical_regions={}) -> tuple[list, dict]:
941
+ r"""
942
+ Return a geometric basis, based on a vertex.
943
+
944
+ INPUT:
945
+
946
+ - ``G`` -- a graph with the bounded edges of a Voronoi Diagram
947
+
948
+ - ``E`` -- a subgraph of ``G`` which is a cycle containing the bounded
949
+ edges touching an unbounded region of a Voronoi Diagram
950
+
951
+ - ``EC0`` -- a counterclockwise orientation of the vertices of ``E``
952
+
953
+ - ``p`` -- a vertex of ``E``
954
+
955
+ - ``dual_graph`` -- a dual graph for a plane embedding of ``G`` such that
956
+ ``E`` is the boundary of the non-bounded component of the complement.
957
+ The edges are labelled as the dual edges and the vertices are labelled
958
+ by a tuple whose first element is the an integer for the position and the
959
+ second one is the cyclic ordered list of vertices in the region
960
+
961
+ - ``vertical_regions`` -- dictionary (default: `{}`); its keys are
962
+ the vertices of ``dual_graph`` to fix regions associated with
963
+ vertical lines
964
+
965
+ OUTPUT: a geometric basis and a dictionary
966
+
967
+ The geometric basis is formed by a list of sequences of paths. Each path is a
968
+ ist of vertices, that form a closed path in ``G``, based at ``p``, that goes
969
+ to a region, surrounds it, and comes back by the same path it came. The
970
+ concatenation of all these paths is equivalent to ``E``.
971
+
972
+ The dictionary associates to each vertical line the index of the generator
973
+ of the geometric basis associated to it.
974
+
975
+ EXAMPLES::
976
+
977
+ sage: from sage.schemes.curves.zariski_vankampen import geometric_basis, corrected_voronoi_diagram, voronoi_cells
978
+ sage: points = (0, -1, I, 1, -I)
979
+ sage: V = corrected_voronoi_diagram(points)
980
+ sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((0 .. 4)))
981
+ sage: gb, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR)
982
+ sage: gb
983
+ [[A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2),
984
+ A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2), A vertex at (1/2, -1/2),
985
+ A vertex at (1/2, 1/2), A vertex at (-1/2, 1/2), A vertex at (-5/2, 5/2),
986
+ A vertex at (5/2, 5/2), A vertex at (5/2, -5/2)],
987
+ [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2),
988
+ A vertex at (-1/2, 1/2), A vertex at (1/2, 1/2), A vertex at (5/2, 5/2),
989
+ A vertex at (5/2, -5/2)],
990
+ [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (1/2, 1/2),
991
+ A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)], [A vertex at (5/2, -5/2),
992
+ A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), A vertex at (-1/2, 1/2),
993
+ A vertex at (-5/2, 5/2), A vertex at (-5/2, -5/2), A vertex at (-1/2, -1/2),
994
+ A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)],
995
+ [A vertex at (5/2, -5/2), A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2),
996
+ A vertex at (-5/2, -5/2), A vertex at (5/2, -5/2)]]
997
+ sage: vd
998
+ {0: 0, 1: 3, 2: 1, 3: 2, 4: 4}
999
+ """
1000
+ i = EC0.index(p)
1001
+ EC = EC0[i:-1] + EC0[:i + 1]
1002
+ # A counterclockwise eulerian circuit on the boundary,
1003
+ # starting and ending at p
1004
+ if G.size() == E.size():
1005
+ if E.is_cycle():
1006
+ j = next(dual_graph.vertex_iterator())[0]
1007
+ if j in vertical_regions:
1008
+ vd = {j: 0}
1009
+ else:
1010
+ vd = {}
1011
+ return [EC], vd
1012
+ edges_E = E.edges(sort=True)
1013
+ InternalEdges = [e for e in G.edges(sort=True) if e not in edges_E]
1014
+ InternalVertices = [v for e in InternalEdges for v in e[:2]]
1015
+ Internal = G.subgraph(vertices=InternalVertices, edges=InternalEdges)
1016
+ for i, ECi in enumerate(EC): # q and r are the points we will cut through
1017
+ if ECi in Internal:
1018
+ EI = [v for v in E if v in
1019
+ Internal.connected_component_containing_vertex(ECi, sort=True)
1020
+ and v != ECi]
1021
+ if EI:
1022
+ q = ECi
1023
+ connecting_path = list(EC[:i])
1024
+ break
1025
+ if EC[-i] in Internal:
1026
+ EI = [v for v in E if v in
1027
+ Internal.connected_component_containing_vertex(EC[-i], sort=True)
1028
+ and v != EC[-i]]
1029
+ if EI:
1030
+ q = EC[-i]
1031
+ connecting_path = list(reversed(EC[-i:]))
1032
+ break
1033
+ # Precompute distances from q in E and I
1034
+ E_dist_q = E.shortest_path_lengths(q)
1035
+ I_dist_q = Internal.shortest_path_lengths(q)
1036
+ distancequotients = [(E_dist_q[v]**2 / I_dist_q[v], v) for v in EI]
1037
+ r = max(distancequotients)[1]
1038
+ cutpath = Internal.shortest_path(q, r)
1039
+ for i, v in enumerate(cutpath):
1040
+ if i > 0 and v in EC:
1041
+ r = v
1042
+ cutpath = cutpath[:i + 1]
1043
+ break
1044
+ qi = EC.index(q)
1045
+ ri = EC.index(r)
1046
+ Ecut = copy(E)
1047
+ Ecut.delete_vertices([q, r])
1048
+ subgraphs = Ecut.connected_components_subgraphs()
1049
+ if len(subgraphs) == 2:
1050
+ E1, E2 = subgraphs
1051
+ if EC[qi + 1] in E2:
1052
+ E1, E2 = E2, E1
1053
+ elif len(subgraphs) == 1:
1054
+ E1 = subgraphs[0]
1055
+ E2 = Graph()
1056
+ E2.add_edge(q, r, None)
1057
+ if r == EC[qi + 1]:
1058
+ E1, E2 = E2, E1
1059
+ for v in [q, r]:
1060
+ for n in E.neighbor_iterator(v):
1061
+ if n in E1 and n not in (q, r):
1062
+ E1.add_edge(v, n, None)
1063
+ if n in E2 and n not in (q, r):
1064
+ E2.add_edge(v, n, None)
1065
+
1066
+ for i in range(len(cutpath) - 1):
1067
+ E1.add_edge(cutpath[i], cutpath[i + 1], None)
1068
+ E2.add_edge(cutpath[i], cutpath[i + 1], None)
1069
+ Gd = copy(dual_graph)
1070
+ to_delete = [e for e in Gd.edges(sort=True) if e[2][0] in cutpath and
1071
+ e[2][1] in cutpath]
1072
+ Gd.delete_edges(to_delete)
1073
+ Gd1, Gd2 = Gd.connected_components_subgraphs()
1074
+ edges_2 = []
1075
+ vertices_2 = []
1076
+ for reg in Gd2.vertices(sort=True):
1077
+ vertices_2 += reg[1][:-1]
1078
+ reg_circuit = reg[1]
1079
+ edges_2 += [(v1, reg_circuit[i + 1])
1080
+ for i, v1 in enumerate(reg_circuit[:-1])]
1081
+ edges_2 += [(v1, reg_circuit[i - 1])
1082
+ for i, v1 in enumerate(reg_circuit[1:])]
1083
+ G2 = G.subgraph(vertices=vertices_2, edges=edges_2)
1084
+ edges_1 = []
1085
+ vertices_1 = []
1086
+ for reg in Gd1.vertices(sort=True):
1087
+ vertices_1 += reg[1]
1088
+ reg_circuit = reg[1] + (reg[1][0],)
1089
+ edges_1 += [(v1, reg_circuit[i + 1])
1090
+ for i, v1 in enumerate(reg_circuit[:-1])]
1091
+ edges_1 += [(v1, reg_circuit[i - 1])
1092
+ for i, v1 in enumerate(reg_circuit[1:])]
1093
+ G1 = G.subgraph(vertices=vertices_1, edges=edges_1)
1094
+ if EC[qi + 1] in G2:
1095
+ G1, G2 = G2, G1
1096
+ Gd1, Gd2 = Gd2, Gd1
1097
+
1098
+ if qi < ri:
1099
+ EC1 = [EC[j] for j in range(qi, ri)] + list(reversed(cutpath))
1100
+ EC2 = cutpath + list(EC[ri + 1: -1] + EC[: qi + 1])
1101
+ else:
1102
+ EC1 = list(EC[qi:] + EC[1:ri]) + list(reversed(cutpath))
1103
+ EC2 = cutpath + list(EC[ri + 1:qi + 1])
1104
+
1105
+ gb1, vd1 = geometric_basis(G1, E1, EC1, q, Gd1, vertical_regions=vertical_regions)
1106
+ gb2, vd2 = geometric_basis(G2, E2, EC2, q, Gd2, vertical_regions=vertical_regions)
1107
+
1108
+ vd = {j: vd1[j] for j in vd1}
1109
+ m = len(gb1)
1110
+ for j in vd2.keys():
1111
+ vd[j] = vd2[j] + m
1112
+ reverse_connecting = list(reversed(connecting_path))
1113
+ resul = [connecting_path + path + reverse_connecting
1114
+ for path in gb1 + gb2]
1115
+ for r in resul:
1116
+ i = 0
1117
+ while i < len(r) - 2:
1118
+ if r[i] == r[i + 2]:
1119
+ r.pop(i)
1120
+ r.pop(i)
1121
+ if i:
1122
+ i -= 1
1123
+ else:
1124
+ i += 1
1125
+ return (resul, vd)
1126
+
1127
+
1128
+ def vertical_lines_in_braidmon(pols) -> list[int]:
1129
+ r"""
1130
+ Return the vertical lines in ``pols``, unless
1131
+ one of the other components has a vertical asymptote.
1132
+
1133
+ INPUT:
1134
+
1135
+ - ``pols`` -- a list of polynomials with two variables whose
1136
+ product equals ``f``
1137
+
1138
+ OUTPUT:
1139
+
1140
+ A list with the indices of the vertical lines in ``flist`` if there is
1141
+ no other component with vertical asymptote; otherwise it returns an empty
1142
+ list.
1143
+
1144
+ EXAMPLES::
1145
+
1146
+ sage: from sage.schemes.curves.zariski_vankampen import vertical_lines_in_braidmon
1147
+ sage: R.<x, y> = QQ[]
1148
+ sage: flist = [x^2 - y^3, x, x + 3 * y - 5, 1 - x]
1149
+ sage: vertical_lines_in_braidmon(flist)
1150
+ [1, 3]
1151
+ sage: flist += [x * y - 1]
1152
+ sage: vertical_lines_in_braidmon(flist)
1153
+ []
1154
+ sage: vertical_lines_in_braidmon([])
1155
+ []
1156
+ """
1157
+ if not pols:
1158
+ return []
1159
+ res = []
1160
+ for j, f in enumerate(pols):
1161
+ C = Curve(f)
1162
+ vertical_asymptote = C.has_vertical_asymptote()
1163
+ if vertical_asymptote:
1164
+ return []
1165
+ if C.is_vertical_line():
1166
+ res.append(j)
1167
+ return res
1168
+
1169
+
1170
+ def strand_components(f, pols, p1) -> tuple[list, dict]:
1171
+ r"""
1172
+ Compute only the assignment from strands to elements of ``flist``.
1173
+
1174
+ INPUT:
1175
+
1176
+ - ``f`` -- a reduced polynomial with two variables, over a number field
1177
+ with an embedding in the complex numbers
1178
+
1179
+ - ``pols`` -- a list of polynomials with two variables whose
1180
+ product equals ``f``
1181
+
1182
+ - ``p1`` -- a Gauss rational
1183
+
1184
+ OUTPUT:
1185
+
1186
+ - A list and a dictionary. The first one is an ordered list of pairs
1187
+ consisting of ``(z,i)`` where ``z`` is a root of ``f(p_1,y)``
1188
+ and `i` is the position of the polynomial in the list whose root
1189
+ is ``z``. The second one attaches a number `i` (strand) to a
1190
+ number `j` (a polynomial in the list).
1191
+
1192
+ EXAMPLES::
1193
+
1194
+ sage: from sage.schemes.curves.zariski_vankampen import strand_components
1195
+ sage: R.<x, y> = QQ[]
1196
+ sage: flist = [x^2 - y^3, x + 3 * y - 5]
1197
+ sage: strand_components(prod(flist), flist, 1)
1198
+ ([(-0.500000000000000? - 0.866025403784439?*I, 0),
1199
+ (-0.500000000000000? + 0.866025403784439?*I, 0),
1200
+ (1, 0), (1.333333333333334?, 1)], {0: 0, 1: 0, 2: 0, 3: 1})
1201
+ """
1202
+ x, y = f.parent().gens()
1203
+ F = pols[0].base_ring()
1204
+ strands = {}
1205
+ roots_base = []
1206
+ for i, h in enumerate(pols):
1207
+ h0 = h.subs({x: p1})
1208
+ h1 = F[y](h0)
1209
+ rt = h1.roots(QQbar, multiplicities=False)
1210
+ roots_base += [(r, i) for r in rt]
1211
+ roots_base.sort()
1212
+ strands = {i: par[1] for i, par in enumerate(roots_base)}
1213
+ return (roots_base, strands)
1214
+
1215
+
1216
+ def braid_monodromy(f, arrangement=(), vertical=False) -> tuple:
1217
+ r"""
1218
+ Compute the braid monodromy of a projection of the curve defined by
1219
+ a polynomial.
1220
+
1221
+ INPUT:
1222
+
1223
+ - ``f`` -- a polynomial with two variables, over a number field
1224
+ with an embedding in the complex numbers
1225
+
1226
+ - ``arrangement`` -- tuple (default: ``()``); an optional tuple
1227
+ of polynomials whose product equals ``f``
1228
+
1229
+ - ``vertical`` -- boolean (default: ``False``); if set to ``True``,
1230
+ ``arrangements`` contains more than one polynomial, some of them
1231
+ are of degree `1` in `x` and degree `0` in `y`, and none of
1232
+ the other components have vertical asymptotes, then these
1233
+ components are marked as *vertical* and not used for the computation
1234
+ of the braid monodromy. The other ones are marked as *horizontal*. If
1235
+ a vertical component does not pass through a singular points of the
1236
+ projection of the horizontal components a trivial braid is added
1237
+ to the list.
1238
+
1239
+ OUTPUT:
1240
+
1241
+ - A list of braids, images by the braid monodromy of a geometric
1242
+ basis of the complement of the discriminant of `f` in `\CC`.
1243
+
1244
+ - A dictionary: ``i``, index of a strand is sent to the index of
1245
+ the corresponding factor in ``arrangement``.
1246
+
1247
+ - Another dictionary ``dv``, only relevant if ``vertical`` is ``True``.
1248
+ If ``j`` is the index
1249
+ of a braid corresponding to a vertical line with index ``i``
1250
+ in ``arrangement``, then ``dv[j] = i``.
1251
+
1252
+ - A nonnegative integer: the number of strands of the braids,
1253
+ only necessary if the list of braids is empty.
1254
+
1255
+ .. NOTE::
1256
+
1257
+ The projection over the `x` axis is used if there are no vertical
1258
+ asymptotes. Otherwise, a linear change of variables is done to fall
1259
+ into the previous case except if the only vertical asymptotes are lines
1260
+ and ``vertical=True``.
1261
+
1262
+ EXAMPLES::
1263
+
1264
+ sage: # needs sirocco
1265
+ sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy
1266
+ sage: R.<x, y> = QQ[]
1267
+ sage: f = (x^2 - y^3) * (x + 3*y - 5)
1268
+ sage: bm = braid_monodromy(f); bm
1269
+ ([s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1,
1270
+ s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1,
1271
+ s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1,
1272
+ s1*s0*s2*s0^-1*s2*s1^-1], {0: 0, 1: 0, 2: 0, 3: 0}, {}, 4)
1273
+ sage: flist = (x^2 - y^3, x + 3*y - 5)
1274
+ sage: bm1 = braid_monodromy(f, arrangement=flist)
1275
+ sage: bm1[0] == bm[0]
1276
+ True
1277
+ sage: bm1[1]
1278
+ {0: 0, 1: 1, 2: 0, 3: 0}
1279
+ sage: braid_monodromy(R(1))
1280
+ ([], {}, {}, 0)
1281
+ sage: braid_monodromy(x*y^2 - 1)
1282
+ ([s0*s1*s0^-1*s1*s0*s1^-1*s0^-1, s0*s1*s0^-1, s0], {0: 0, 1: 0, 2: 0}, {}, 3)
1283
+ sage: L = [x, y, x - 1, x -y]
1284
+ sage: braid_monodromy(prod(L), arrangement=L, vertical=True)
1285
+ ([s^2, 1], {0: 1, 1: 3}, {0: 0, 1: 2}, 2)
1286
+ """
1287
+ global roots_interval_cache
1288
+ F = fieldI(f.base_ring())
1289
+ I1 = F(QQbar.gen())
1290
+ f = f.change_ring(F)
1291
+ if not arrangement:
1292
+ arrangement1 = (f,)
1293
+ else:
1294
+ arrangement1 = tuple(g.change_ring(F) for g in arrangement)
1295
+ x, y = f.parent().gens()
1296
+ if vertical:
1297
+ indices_v = vertical_lines_in_braidmon(arrangement1)
1298
+ else:
1299
+ indices_v = []
1300
+ arrangement_h = tuple(f0 for j, f0 in enumerate(arrangement1)
1301
+ if j not in indices_v)
1302
+ arrangement_v = tuple(f0 for j, f0 in enumerate(arrangement1)
1303
+ if j in indices_v)
1304
+ glist = tuple(fc[0] for f0 in arrangement_h for fc in f0.factor())
1305
+ g = f.parent()(prod(glist))
1306
+ d = g.degree(y)
1307
+ if not arrangement_v: # change of coordinates only if indices_v is empty
1308
+ while g.coefficient(y**d) not in F:
1309
+ g = g.subs({x: x + y})
1310
+ d = g.degree(y)
1311
+ arrangement_h = tuple(f1.subs({x: x + y}) for f1 in arrangement_h)
1312
+ arrangement1 = arrangement_h
1313
+ glist = tuple(f1.subs({x: x + y}) for f1 in glist)
1314
+ disc = discrim(glist) if d > 0 else []
1315
+ vertical_braid = {}
1316
+ transversal = {}
1317
+ vl_list = []
1318
+ for f0 in arrangement_v:
1319
+ pt = [j for j, t in enumerate(disc) if f0.subs({x: t}) == 0]
1320
+ if pt:
1321
+ vertical_braid[f0] = (pt[0], arrangement1.index(f0))
1322
+ vl_list.append(pt[0])
1323
+ else:
1324
+ transversal[f0] = arrangement1.index(f0)
1325
+ vl_list.sort()
1326
+ vl = frozenset(vl_list)
1327
+ if not disc:
1328
+ vertical_braids = {i: transversal[f0]
1329
+ for i, f0 in enumerate(transversal)}
1330
+ if d > 1:
1331
+ result = [BraidGroup(d).one() for p in transversal]
1332
+ else:
1333
+ G = FreeGroup(0) / []
1334
+ result = [G.one() for p in transversal]
1335
+ p1 = F(0)
1336
+ if d > 0:
1337
+ roots_base, strands = strand_components(g, arrangement_h, p1)
1338
+ strands1 = {}
1339
+ for j in range(d):
1340
+ i = strands[j]
1341
+ k = arrangement1.index(arrangement_h[i])
1342
+ strands1[j] = k
1343
+ else:
1344
+ strands1 = {}
1345
+ return (result, strands1, vertical_braids, d)
1346
+ V = corrected_voronoi_diagram(tuple(disc))
1347
+ G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=vl)
1348
+ p0 = (p[0], p[1])
1349
+ p1 = p0[0] + I1 * p0[1]
1350
+ roots_base, strands = strand_components(g, arrangement_h, p1)
1351
+ strands1 = {}
1352
+ for j in range(d):
1353
+ i = strands[j]
1354
+ k = arrangement1.index(arrangement_h[i])
1355
+ strands1[j] = k
1356
+ geombasis, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR)
1357
+ segs_set = set()
1358
+ for p in geombasis:
1359
+ for s0, s1 in zip(p[:-1], p[1:]):
1360
+ if (s1, s0) not in segs_set:
1361
+ segs_set.add((s0, s1))
1362
+ I0 = QQbar.gen()
1363
+ segs = [(a[0] + I0 * a[1], b[0] + I0 * b[1]) for a, b in segs_set]
1364
+ vertices = list(set(flatten(segs)))
1365
+ tocacheverts = tuple([(g, v) for v in vertices])
1366
+ populate_roots_interval_cache(tocacheverts)
1367
+ end_braid_computation = False
1368
+ while not end_braid_computation:
1369
+ try:
1370
+ braidscomputed = (braid_in_segment([(glist, seg[0], seg[1])
1371
+ for seg in segs]))
1372
+ segsbraids = {}
1373
+ for braidcomputed in braidscomputed:
1374
+ seg = (braidcomputed[0][0][1], braidcomputed[0][0][2])
1375
+ beginseg = (QQ(seg[0].real()), QQ(seg[0].imag()))
1376
+ endseg = (QQ(seg[1].real()), QQ(seg[1].imag()))
1377
+ b = braidcomputed[1]
1378
+ segsbraids[(beginseg, endseg)] = b
1379
+ segsbraids[(endseg, beginseg)] = b.inverse()
1380
+ end_braid_computation = True
1381
+ except ChildProcessError: # hack to deal with random fails first time
1382
+ pass
1383
+ B = BraidGroup(d)
1384
+ result = []
1385
+ for path in geombasis:
1386
+ braidpath = B.one()
1387
+ for i in range(len(path) - 1):
1388
+ x0 = tuple(path[i].vector())
1389
+ x1 = tuple(path[i + 1].vector())
1390
+ braidpath = braidpath * segsbraids[(x0, x1)]
1391
+ result.append(braidpath)
1392
+ vertical_braids = {}
1393
+ r = len(result)
1394
+ t = 0
1395
+ for f0 in arrangement_v:
1396
+ if f0 in vertical_braid.keys():
1397
+ k, j = vertical_braid[f0]
1398
+ vertical_braids[vd[k]] = j
1399
+ else:
1400
+ vertical_braids[r + t] = transversal[f0]
1401
+ t += 1
1402
+ result.append(B.one())
1403
+ return (result, strands1, vertical_braids, d)
1404
+
1405
+
1406
+ def conjugate_positive_form(braid) -> list[list]:
1407
+ r"""
1408
+ For a ``braid`` which is conjugate to a product of *disjoint* positive
1409
+ braids a list of such decompositions is given.
1410
+
1411
+ INPUT:
1412
+
1413
+ - ``braid`` -- a braid `\sigma`
1414
+
1415
+ OUTPUT:
1416
+
1417
+ A list of `r` lists. Each such list is another list with two elements, a
1418
+ positive braid `\alpha_i` and a list of permutation braids
1419
+ `\gamma_{1}^{i},\dots,\gamma_{n_i}^{i}` such that if
1420
+ `\gamma_i=\prod_{j=1}^{n_i} \gamma_j^i` then the braids
1421
+ `\tau_i=\gamma_i\alpha_i\gamma_i^{-1}` pairwise commute
1422
+ and `\alpha=\prod_{i=1}^{r} \tau_i`.
1423
+
1424
+ EXAMPLES::
1425
+
1426
+ sage: # needs sage.libs.braiding
1427
+ sage: from sage.schemes.curves.zariski_vankampen import conjugate_positive_form
1428
+ sage: B = BraidGroup(4)
1429
+ sage: t = B((1, 3, 2, -3, 1, 1))
1430
+ sage: cpf = conjugate_positive_form(t); cpf
1431
+ [[(s0*s1)^2, [s0*s2*s1*s0]]]
1432
+ sage: t == prod(prod(b) * a / prod(b) for a, b in cpf)
1433
+ True
1434
+ sage: B = BraidGroup(5)
1435
+ sage: t = B((1, 2, 3, 4, -1, -2, 3, 3, 2, -4))
1436
+ sage: L = conjugate_positive_form(t); L
1437
+ [[s0^2, [s0*s1*s2*s1*s3*s2*s1*s0]], [s3*s2, [s0*s1*s2*s1*s3*s2*s1*s0]]]
1438
+ sage: t == prod(prod(b) * a / prod(b) for a, b in L)
1439
+ True
1440
+ sage: s1 = B.gen(1)^3
1441
+ sage: conjugate_positive_form(s1)
1442
+ [[s1^3, []]]
1443
+ """
1444
+ B = braid.parent()
1445
+ d = B.strands()
1446
+ rnf = rightnormalform(braid)
1447
+ ex = rnf[-1][0]
1448
+ if ex >= 0:
1449
+ A1 = [B(a) for a in rnf[:-1]]
1450
+ braid1 = prod(A1, B.delta() ** ex)
1451
+ sg0 = B.one()
1452
+ else:
1453
+ braid1, sg0 = braid.super_summit_set_element()
1454
+ if ex > 0:
1455
+ blocks = [list(braid1.Tietze())]
1456
+ else:
1457
+ L1 = braid1.Tietze()
1458
+ gns = set(L1)
1459
+ cuts = [j for j in range(d + 1) if j not in gns]
1460
+ blocks = []
1461
+ for i in range(len(cuts) - 1):
1462
+ block = [j for j in L1 if cuts[i] < j < cuts[i + 1]]
1463
+ if block:
1464
+ blocks.append(block)
1465
+ shorts = []
1466
+ for a in blocks:
1467
+ if sg0 == B.one():
1468
+ res0 = [B(a), []]
1469
+ else:
1470
+ bra = sg0 * B(a) / sg0
1471
+ br1, sg = bra.super_summit_set_element()
1472
+ A1 = rightnormalform(sg)
1473
+ par = A1[-1][0] % 2
1474
+ A1 = [B(a0) for a0 in A1[:-1]]
1475
+ res = [br1, A1, par]
1476
+ if res[2]:
1477
+ r0 = res[0].Tietze()
1478
+ res[0] = B([d - i for i in r0])
1479
+ res0 = res[:2]
1480
+ shorts.append(res0)
1481
+ return shorts
1482
+
1483
+
1484
+ @parallel
1485
+ def conjugate_positive_form_p(braid):
1486
+ return conjugate_positive_form(braid)
1487
+
1488
+
1489
+ def braid2rels(L) -> list:
1490
+ r"""
1491
+ Return a minimal set of relations of the group
1492
+ ``F / [(b * F([j])) / F([j]) for j in (1..d)]`` where ``F = FreeGroup(d)``
1493
+ and ``b`` is a conjugate of a positive braid.
1494
+
1495
+ One starts from the non-trivial relations determined by the
1496
+ positive braid and transform them in relations determined by ``b``.
1497
+
1498
+ INPUT:
1499
+
1500
+ - ``L`` -- tuple whose first element is a positive braid and the second
1501
+ element is a list of permutation braids
1502
+
1503
+ OUTPUT:
1504
+
1505
+ A list of Tietze words for a minimal set of relations of
1506
+ ``F / [(g * b) / g for g in F.gens()]``.
1507
+
1508
+ EXAMPLES::
1509
+
1510
+ sage: from sage.schemes.curves.zariski_vankampen import braid2rels
1511
+ sage: B.<s0, s1, s2> = BraidGroup(4)
1512
+ sage: L = ((s1*s0)^2, [s2])
1513
+ sage: braid2rels(L)
1514
+ [(4, 1, -2, -1), (2, -4, -2, 1)]
1515
+ """
1516
+ br = L[0]
1517
+ L1 = L[1]
1518
+ B = br.parent()
1519
+ d = B.strands()
1520
+ F = FreeGroup(d)
1521
+ T = br.Tietze()
1522
+ T1 = set(T)
1523
+ m = len(T1) + 1
1524
+ k = min(T1) - 1
1525
+ B0 = BraidGroup(m)
1526
+ F0 = FreeGroup(m)
1527
+ br0 = B0([j - k for j in T])
1528
+ br0_left = leftnormalform(br0)
1529
+ q, r = ZZ(br0_left[0][0]).quo_rem(2)
1530
+ br1 = B0.delta()**r * prod(map(B0, br0_left[1:]), B0.one())
1531
+ cox = prod(F0.gens())
1532
+ U0 = [cox**q * (f0 * br1) / cox**q / f0 for f0 in F0.gens()[:-1]]
1533
+ U = [tuple(sign(k1) * (abs(k1) + k) for k1 in br.Tietze()) for br in U0]
1534
+ pasos = [B.one()]
1535
+ pasos.extend(reversed(L1))
1536
+ for C in pasos:
1537
+ U = [(F(a) * C.inverse()).Tietze() for a in U]
1538
+ ga = F / U
1539
+ P = ga.gap().PresentationFpGroup()
1540
+ dic = P.TzOptions().sage()
1541
+ dic['protected'] = d
1542
+ dic['printLevel'] = 0
1543
+ P.SetTzOptions(dic)
1544
+ P.TzGoGo()
1545
+ P.TzGoGo()
1546
+ gb = P.FpGroupPresentation().sage()
1547
+ U = [rel.Tietze() for rel in gb.relations()]
1548
+ return U
1549
+
1550
+
1551
+ @parallel
1552
+ def braid2rels_p(L):
1553
+ return braid2rels(L)
1554
+
1555
+
1556
+ @parallel
1557
+ def relation(x, b):
1558
+ return x * b / x
1559
+
1560
+
1561
+ def fundamental_group_from_braid_mon(bm, degree=None,
1562
+ simplified=True, projective=False,
1563
+ puiseux=True, vertical=[]):
1564
+ r"""
1565
+ Return a presentation of the fundamental group computed from
1566
+ a braid monodromy.
1567
+
1568
+ INPUT:
1569
+
1570
+ - ``bm`` -- list of braids
1571
+
1572
+ - ``degree`` -- integer (default: ``None``); only needed if the braid
1573
+ monodromy is an empty list
1574
+
1575
+ - ``simplified`` -- boolean (default: ``True``); if set to ``True`` the
1576
+ presentation will be simplified (see below)
1577
+
1578
+ - ``projective`` -- boolean (default: ``False``); if set to ``True``,
1579
+ the fundamental group of the complement of the projective completion
1580
+ of the curve will be computed, otherwise, the fundamental group of
1581
+ the complement in the affine plane will be computed
1582
+
1583
+ - ``puiseux`` -- boolean (default: ``True``); if set to ``True``
1584
+ a presentation of the fundamental group with the homotopy type
1585
+ of the complement of the affine curve will be computed, adding
1586
+ one relation if ``projective`` is set to ``True``.
1587
+
1588
+ - ``vertical`` -- list of integers (default: ``[]``); the indices in
1589
+ ``[0 .. r - 1]`` of the braids that surround a vertical line
1590
+
1591
+ If ``projective`` is ``False`` and ``puiseux`` is
1592
+ ``True``, a Zariski-VanKampen presentation is returned.
1593
+
1594
+ OUTPUT:
1595
+
1596
+ A presentation of the fundamental group of the complement of the
1597
+ union of the curve with some vertical lines from its braid monodromy.
1598
+
1599
+ EXAMPLES::
1600
+
1601
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group_from_braid_mon
1602
+ sage: B.<s0, s1, s2> = BraidGroup(4)
1603
+ sage: bm = [s1*s2*s0*s1*s0^-1*s1^-1*s0^-1,
1604
+ ....: s0*s1^2*s0*s2*s1*(s0^-1*s1^-1)^2*s0^-1,
1605
+ ....: (s0*s1)^2]
1606
+ sage: g = fundamental_group_from_braid_mon(bm, projective=True) # needs sirocco
1607
+ sage: g.sorted_presentation() # needs sirocco
1608
+ Finitely presented group
1609
+ < x0, x1 | x1^-2*x0^-2, x1^-1*(x0^-1*x1)^2*x0 >
1610
+ sage: print(g.order(), g.abelian_invariants()) # needs sirocco
1611
+ 12 (4,)
1612
+ sage: B2 = BraidGroup(2)
1613
+ sage: bm = [B2(3 * [1])]
1614
+ sage: g = fundamental_group_from_braid_mon(bm, vertical=[0]); g # needs sirocco
1615
+ Finitely presented group
1616
+ < x0, x1, x2 | x2*x0*x1*x2^-1*x1^-1*x0^-1,
1617
+ x2*x0*x1*x0*x1^-1*x0^-1*x2^-1*x1^-1 >
1618
+ sage: fundamental_group_from_braid_mon([]) is None # needs sirocco
1619
+ True
1620
+ sage: fundamental_group_from_braid_mon([], degree=2) # needs sirocco
1621
+ Finitely presented group < x0, x1 | >
1622
+ sage: fundamental_group_from_braid_mon([SymmetricGroup(1).one()]) # needs sirocco
1623
+ Finitely presented group < x | >
1624
+ """
1625
+ vertical0 = sorted(vertical)
1626
+ v = len(vertical0)
1627
+ if not bm:
1628
+ d = degree
1629
+ elif bm[0].parent().order() == 1:
1630
+ d = 1
1631
+ else:
1632
+ d = bm[0].parent().strands()
1633
+ if d is None:
1634
+ return None
1635
+ F = FreeGroup(d)
1636
+ Fv = FreeGroup(d + v)
1637
+ if d == 0:
1638
+ return Fv / []
1639
+ if d == 1:
1640
+ return Fv / [(1, j, -1, -j) for j in range(2, d + v + 1)]
1641
+ bmh = [br for j, br in enumerate(bm) if j not in vertical0]
1642
+ if not puiseux:
1643
+ relations_h = (relation([(x, b) for x in F.gens() for b in bmh]))
1644
+ rel_h = [r[1] for r in relations_h]
1645
+ else:
1646
+ conjugate_desc = conjugate_positive_form_p(bmh)
1647
+ trenzas_desc = [b1[-1] for b1 in conjugate_desc]
1648
+ trenzas_desc_1 = flatten(trenzas_desc, max_level=1)
1649
+ relations_h = braid2rels_p(trenzas_desc_1)
1650
+ rel_h = [r[1] for r in relations_h]
1651
+ rel_h = flatten(rel_h, max_level=1)
1652
+ rel_v = []
1653
+ for j, k in enumerate(vertical0):
1654
+ l1 = d + j + 1
1655
+ br = bm[k]
1656
+ for gen in F.gens():
1657
+ j0 = gen.Tietze()[0]
1658
+ rl = (l1,) + (gen * br).Tietze() + (-l1, -j0)
1659
+ rel_v.append(rl)
1660
+ rel = rel_h + rel_v
1661
+ if projective:
1662
+ rel.append(prod(Fv.gens()).Tietze())
1663
+ G = Fv / rel
1664
+ if simplified:
1665
+ return G.simplified()
1666
+ return G
1667
+
1668
+
1669
+ def fundamental_group(f, simplified=True, projective=False, puiseux=True):
1670
+ r"""
1671
+ Return a presentation of the fundamental group of the complement of
1672
+ the algebraic set defined by the polynomial ``f``.
1673
+
1674
+ INPUT:
1675
+
1676
+ - ``f`` -- a polynomial in two variables, with coefficients in either
1677
+ the rationals or a number field with a fixed embedding in `\QQbar`
1678
+
1679
+ - ``simplified`` -- boolean (default: ``True``); if set to ``True`` the
1680
+ presentation will be simplified (see below)
1681
+
1682
+ - ``projective`` -- boolean (default: ``False``); if set to ``True``,
1683
+ the fundamental group of the complement of the projective completion
1684
+ of the curve will be computed, otherwise, the fundamental group of
1685
+ the complement in the affine plane will be computed
1686
+
1687
+ - ``puiseux`` -- boolean (default: ``True``); if set to ``True``,
1688
+ a presentation of the fundamental group with the homotopy type
1689
+ of the complement of the affine curve is computed. If the Euler
1690
+ characteristic does not match, the homotopy type is obtained
1691
+ with a wedge of 2-spheres. One relation is added if ``projective``
1692
+ is set to ``True``.
1693
+
1694
+ If ``projective`` is ``False`` and ``puiseux`` is
1695
+ ``True``, a Zariski-VanKampen presentation is returned.
1696
+
1697
+ OUTPUT:
1698
+
1699
+ A presentation of the fundamental group of the complement of the
1700
+ curve defined by ``f``.
1701
+
1702
+ EXAMPLES::
1703
+
1704
+ sage: # needs sirocco
1705
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy
1706
+ sage: R.<x, y> = QQ[]
1707
+ sage: f = x^2 + y^3
1708
+ sage: fundamental_group(f).sorted_presentation()
1709
+ Finitely presented group < x0, x1 | x1^-1*x0^-1*x1^-1*x0*x1*x0 >
1710
+ sage: fundamental_group(f, simplified=False, puiseux=False).sorted_presentation()
1711
+ Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0*x1,
1712
+ x2^-1*x0*x1*x0^-1,
1713
+ x1^-1*x0^-1*x1^-1*x0*x1*x0 >
1714
+ sage: fundamental_group(f, projective=True)
1715
+ Finitely presented group < x0 | x0^3 >
1716
+ sage: fundamental_group(f).sorted_presentation()
1717
+ Finitely presented group < x0, x1 | x1^-1*x0^-1*x1^-1*x0*x1*x0 >
1718
+
1719
+ ::
1720
+
1721
+ sage: # needs sirocco
1722
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group
1723
+ sage: R.<x, y> = QQ[]
1724
+ sage: f = y^3 + x^3
1725
+ sage: fundamental_group(f).sorted_presentation()
1726
+ Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0^-1*x2*x0*x1,
1727
+ x2^-1*x1^-1*x2*x0*x1*x0^-1 >
1728
+
1729
+ It is also possible to have coefficients in a number field with a
1730
+ fixed embedding in `\QQbar`::
1731
+
1732
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group
1733
+ sage: zeta = QQbar['x']('x^2 + x+ 1').roots(multiplicities=False)[0]
1734
+ sage: zeta
1735
+ -0.50000000000000000? - 0.866025403784439?*I
1736
+ sage: F = NumberField(zeta.minpoly(), 'zeta', embedding=zeta)
1737
+ sage: F.inject_variables()
1738
+ Defining zeta
1739
+ sage: R.<x, y> = F[]
1740
+ sage: f = y^3 + x^3 + zeta * x + 1
1741
+ sage: fundamental_group(f) # needs sirocco
1742
+ Finitely presented group < x0 | >
1743
+
1744
+ We compute the fundamental group of the complement of a
1745
+ quartic using the ``puiseux`` option::
1746
+
1747
+ sage: # optional - sirocco
1748
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group
1749
+ sage: R.<x, y> = QQ[]
1750
+ sage: f = x^2 * y^2 + x^2 + y^2 - 2 * x * y * (x + y + 1)
1751
+ sage: g = fundamental_group(f); g.sorted_presentation()
1752
+ Finitely presented group < x0, x1 | x1^-2*x0^2, (x1^-1*x0)^3 >
1753
+ sage: g = fundamental_group(f, projective=True)
1754
+ sage: g.order(), g.abelian_invariants()
1755
+ (12, (4,))
1756
+ sage: fundamental_group(y * (y - 1))
1757
+ Finitely presented group < x0, x1 | >
1758
+ """
1759
+ g = f
1760
+ x, y = g.parent().gens()
1761
+ F = g.parent().base_ring()
1762
+ d = g.degree(y)
1763
+ while g.coefficient(y**d) not in F:
1764
+ g = g.subs({x: x + y})
1765
+ d = g.degree(y)
1766
+ if projective:
1767
+ while g.degree(y) < g.degree():
1768
+ g = g.subs({x: x + y})
1769
+ bm = braid_monodromy(g)[0]
1770
+ if not bm:
1771
+ d = g.degree(y)
1772
+ else:
1773
+ d = bm[0].parent().strands()
1774
+ return fundamental_group_from_braid_mon(bm, degree=d,
1775
+ simplified=simplified,
1776
+ projective=projective,
1777
+ puiseux=puiseux)
1778
+
1779
+
1780
+ def fundamental_group_arrangement(flist, simplified=True, projective=False,
1781
+ puiseux=True, vertical=False,
1782
+ braid_data=None):
1783
+ r"""
1784
+ Compute the fundamental group of the complement of a curve
1785
+ defined by a list of polynomials with the extra information
1786
+ about the correspondence of the generators
1787
+ and meridians of the elements of the list.
1788
+
1789
+ INPUT:
1790
+
1791
+ - ``flist`` -- a tuple of polynomial with two variables, over a number
1792
+ field with an embedding in the complex numbers
1793
+
1794
+ - ``simplified`` -- boolean (default: ``True``); if set to ``True`` the
1795
+ presentation will be simplified (see below)
1796
+
1797
+ - ``projective`` -- boolean (default: ``False``); if set to ``True``,
1798
+ the fundamental group of the complement of the projective completion
1799
+ of the curve will be computed, otherwise, the fundamental group of
1800
+ the complement in the affine plane will be computed
1801
+
1802
+ - ``puiseux`` -- boolean (default: ``True``); if set to ``True``
1803
+ a presentation of the fundamental group with the homotopy type
1804
+ of the complement of the affine curve will be computed, adding
1805
+ one relation if ``projective`` is set to ``True``.
1806
+
1807
+ - ``vertical`` -- boolean (default: ``False``); if set to ``True``,
1808
+ whenever no curve has vertical asymptotes the computation of braid
1809
+ monodromy is simpler if some lines are vertical
1810
+
1811
+ - ``braid_data`` -- tuple (default: ``None``); if it is not the default
1812
+ it is the output of ``fundamental_group_from_braid_mon`` previously
1813
+ computed
1814
+
1815
+ OUTPUT:
1816
+
1817
+ - A list of braids. The braids correspond to paths based in the same point;
1818
+ each of these paths is the conjugated of a loop around one of the points
1819
+ in the discriminant of the projection of ``f``.
1820
+
1821
+ - A dictionary attaching to ``j`` a tuple a list of elements
1822
+ of the group which are meridians of the curve in position ``j``.
1823
+ If ``projective`` is ``False`` and the `y`-degree of the horizontal
1824
+ components coincide with the total degree, another key is added
1825
+ to give a meridian of the line at infinity.
1826
+
1827
+ EXAMPLES::
1828
+
1829
+ sage: # needs sirocco
1830
+ sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy
1831
+ sage: from sage.schemes.curves.zariski_vankampen import fundamental_group_arrangement
1832
+ sage: R.<x, y> = QQ[]
1833
+ sage: flist = [x^2 - y^3, x + 3 * y - 5]
1834
+ sage: g, dic = fundamental_group_arrangement(flist)
1835
+ sage: g.sorted_presentation()
1836
+ Finitely presented group
1837
+ < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x2^-1*x0^-1*x2^-1*x0*x2*x0,
1838
+ x1^-1*x0^-1*x1*x0 >
1839
+ sage: dic
1840
+ {0: [x0, x2], 1: [x1], 2: [x0^-1*x2^-1*x1^-1*x0^-1]}
1841
+ sage: g, dic = fundamental_group_arrangement(flist, simplified=False, puiseux=False)
1842
+ sage: g.sorted_presentation(), dic
1843
+ (Finitely presented group
1844
+ < x0, x1, x2, x3 | 1, 1, 1, 1, 1, 1, 1,
1845
+ x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2,
1846
+ x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x3*x2,
1847
+ x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2,
1848
+ x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2,
1849
+ x3^-1*x1^-1*x0*x1,
1850
+ x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0,
1851
+ x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0 >,
1852
+ {0: [x0, x2, x3], 1: [x1], 2: [x3^-1*x2^-1*x1^-1*x0^-1]})
1853
+ sage: fundamental_group_arrangement(flist, projective=True)
1854
+ (Finitely presented group < x | >, {0: [x], 1: [x^-3]})
1855
+ sage: fundamental_group_arrangement([])
1856
+ (Finitely presented group < | >, {})
1857
+ sage: g, dic = fundamental_group_arrangement([x * y])
1858
+ sage: g.sorted_presentation(), dic
1859
+ (Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 >,
1860
+ {0: [x0, x1], 1: [x1^-1*x0^-1]})
1861
+ sage: fundamental_group_arrangement([y + x^2])
1862
+ (Finitely presented group < x | >, {0: [x]})
1863
+ sage: fundamental_group_arrangement([y^2 + x], projective=True)
1864
+ (Finitely presented group < x | x^2 >, {0: [x]})
1865
+ sage: L = [x, y, x - 1, x -y]
1866
+ sage: G, dic =fundamental_group_arrangement(L)
1867
+ sage: G.sorted_presentation()
1868
+ Finitely presented group
1869
+ < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x0^-1*x1*x3*x0,
1870
+ x3^-1*x1^-1*x0^-1*x3*x0*x1, x2^-1*x0^-1*x2*x0 >
1871
+ sage: dic
1872
+ {0: [x1], 1: [x3], 2: [x2], 3: [x0], 4: [x3^-1*x2^-1*x1^-1*x0^-1]}
1873
+ sage: fundamental_group_arrangement(L, vertical=True)
1874
+ (Finitely presented group
1875
+ < x0, x1, x2, x3 | x3*x0*x3^-1*x0^-1, x3*x1*x3^-1*x1^-1,
1876
+ x1*x2*x0*x2^-1*x1^-1*x0^-1,
1877
+ x1*x2*x0*x1^-1*x0^-1*x2^-1 >,
1878
+ {0: [x2], 1: [x0], 2: [x3], 3: [x1], 4: [x3^-1*x2^-1*x1^-1*x0^-1]})
1879
+ """
1880
+ if flist:
1881
+ f = prod(flist)
1882
+ R = f.parent()
1883
+ else:
1884
+ R = PolynomialRing(QQ, ('x', 'y'))
1885
+ f = R.one()
1886
+ x, y = R.gens()
1887
+ flist1 = tuple(flist)
1888
+ if vertical and vertical_lines_in_braidmon(flist1):
1889
+ infinity = all(Curve(g).is_vertical_line() or
1890
+ g.degree(y) == g.degree() for g in flist1)
1891
+ else:
1892
+ infinity = any(Curve(g).has_vertical_asymptote() or
1893
+ Curve(g).is_vertical_line() for g in flist1)
1894
+ if not infinity:
1895
+ infinity = all(g.degree(y) == g.degree() for g in flist1)
1896
+ if braid_data:
1897
+ bm, dic, dv, d1 = braid_data
1898
+ elif not flist:
1899
+ bm = []
1900
+ dic = {}
1901
+ dv = {j: j for j, f in flist1}
1902
+ d1 = 0
1903
+ else:
1904
+ bm, dic, dv, d1 = braid_monodromy(f, flist1, vertical=vertical)
1905
+ vert_lines = list(dv)
1906
+ vert_lines.sort()
1907
+ for i, j in enumerate(vert_lines):
1908
+ dic[d1 + i] = dv[j]
1909
+ g = fundamental_group_from_braid_mon(bm, degree=d1, simplified=False,
1910
+ projective=projective,
1911
+ puiseux=puiseux,
1912
+ vertical=vert_lines)
1913
+ if simplified:
1914
+ hom = g.simplification_isomorphism()
1915
+ else:
1916
+ hom = g.hom(codomain=g, im_gens=list(g.gens()), check=False)
1917
+ g1 = hom.codomain()
1918
+ if not flist:
1919
+ return (g1, {})
1920
+ dic1 = {}
1921
+ for i in range(len(flist1)):
1922
+ L = [j1 for j1 in dic if dic[j1] == i]
1923
+ dic1[i] = [hom(g.gen(j)) for j in L]
1924
+ if not projective and infinity:
1925
+ t = prod(hom(a) for a in g.gens()).inverse()
1926
+ dic1[len(flist1)] = [t]
1927
+ n = g1.ngens()
1928
+ rels = [rel.Tietze() for rel in g1.relations()]
1929
+ g1 = FreeGroup(n) / rels
1930
+ dic1 = {i: list({g1(el.Tietze()) for el in dic1[i]}) for i in dic1}
1931
+ return (g1, dic1)