passagemath-schemes 10.6.47__cp312-cp312-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 (311) 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.6.47.dist-info/METADATA +204 -0
  7. passagemath_schemes-10.6.47.dist-info/METADATA.bak +205 -0
  8. passagemath_schemes-10.6.47.dist-info/RECORD +311 -0
  9. passagemath_schemes-10.6.47.dist-info/WHEEL +6 -0
  10. passagemath_schemes-10.6.47.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 +9558 -0
  24. sage/dynamics/arithmetic_dynamics/projective_ds_helper.cpython-312-darwin.so +0 -0
  25. sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +301 -0
  26. sage/dynamics/arithmetic_dynamics/wehlerK3.py +2576 -0
  27. sage/lfunctions/all.py +18 -0
  28. sage/lfunctions/dokchitser.py +745 -0
  29. sage/lfunctions/pari.py +818 -0
  30. sage/lfunctions/zero_sums.cpython-312-darwin.so +0 -0
  31. sage/lfunctions/zero_sums.pyx +1847 -0
  32. sage/modular/abvar/abvar.py +5135 -0
  33. sage/modular/abvar/abvar_ambient_jacobian.py +413 -0
  34. sage/modular/abvar/abvar_newform.py +244 -0
  35. sage/modular/abvar/all.py +8 -0
  36. sage/modular/abvar/constructor.py +186 -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 +720 -0
  40. sage/modular/abvar/homspace.py +998 -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 +740 -0
  45. sage/modular/all.py +43 -0
  46. sage/modular/arithgroup/all.py +20 -0
  47. sage/modular/arithgroup/arithgroup_element.cpython-312-darwin.so +0 -0
  48. sage/modular/arithgroup/arithgroup_element.pyx +474 -0
  49. sage/modular/arithgroup/arithgroup_generic.py +1402 -0
  50. sage/modular/arithgroup/arithgroup_perm.py +2692 -0
  51. sage/modular/arithgroup/congroup.cpython-312-darwin.so +0 -0
  52. sage/modular/arithgroup/congroup.pyx +334 -0
  53. sage/modular/arithgroup/congroup_gamma.py +363 -0
  54. sage/modular/arithgroup/congroup_gamma0.py +692 -0
  55. sage/modular/arithgroup/congroup_gamma1.py +653 -0
  56. sage/modular/arithgroup/congroup_gammaH.py +1469 -0
  57. sage/modular/arithgroup/congroup_generic.py +628 -0
  58. sage/modular/arithgroup/congroup_sl2z.py +267 -0
  59. sage/modular/arithgroup/farey_symbol.cpython-312-darwin.so +0 -0
  60. sage/modular/arithgroup/farey_symbol.pyx +1066 -0
  61. sage/modular/arithgroup/tests.py +418 -0
  62. sage/modular/btquotients/all.py +4 -0
  63. sage/modular/btquotients/btquotient.py +3753 -0
  64. sage/modular/btquotients/pautomorphicform.py +2570 -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 +1109 -0
  69. sage/modular/cusps_nf.py +1270 -0
  70. sage/modular/dims.py +569 -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 +1065 -0
  77. sage/modular/hecke/algebra.py +746 -0
  78. sage/modular/hecke/all.py +20 -0
  79. sage/modular/hecke/ambient_module.py +1019 -0
  80. sage/modular/hecke/degenmap.py +119 -0
  81. sage/modular/hecke/element.py +325 -0
  82. sage/modular/hecke/hecke_operator.py +780 -0
  83. sage/modular/hecke/homspace.py +206 -0
  84. sage/modular/hecke/module.py +1767 -0
  85. sage/modular/hecke/morphism.py +174 -0
  86. sage/modular/hecke/submodule.py +989 -0
  87. sage/modular/hypergeometric_misc.cpython-312-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 +2017 -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 +1071 -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 +815 -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 +124 -0
  101. sage/modular/modform/ambient_g1.py +204 -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 +505 -0
  106. sage/modular/modform/eisenstein_submodule.py +663 -0
  107. sage/modular/modform/element.py +4131 -0
  108. sage/modular/modform/find_generators.py +59 -0
  109. sage/modular/modform/half_integral.py +154 -0
  110. sage/modular/modform/hecke_operator_on_qexp.py +247 -0
  111. sage/modular/modform/j_invariant.py +47 -0
  112. sage/modular/modform/l_series_gross_zagier.py +133 -0
  113. sage/modular/modform/l_series_gross_zagier_coeffs.cpython-312-darwin.so +0 -0
  114. sage/modular/modform/l_series_gross_zagier_coeffs.pyx +177 -0
  115. sage/modular/modform/notes.py +45 -0
  116. sage/modular/modform/numerical.py +514 -0
  117. sage/modular/modform/periods.py +14 -0
  118. sage/modular/modform/ring.py +1257 -0
  119. sage/modular/modform/space.py +1860 -0
  120. sage/modular/modform/submodule.py +118 -0
  121. sage/modular/modform/tests.py +64 -0
  122. sage/modular/modform/theta.py +110 -0
  123. sage/modular/modform/vm_basis.py +381 -0
  124. sage/modular/modform/weight1.py +220 -0
  125. sage/modular/modform_hecketriangle/abstract_ring.py +1932 -0
  126. sage/modular/modform_hecketriangle/abstract_space.py +2528 -0
  127. sage/modular/modform_hecketriangle/all.py +30 -0
  128. sage/modular/modform_hecketriangle/analytic_type.py +590 -0
  129. sage/modular/modform_hecketriangle/constructor.py +416 -0
  130. sage/modular/modform_hecketriangle/element.py +351 -0
  131. sage/modular/modform_hecketriangle/functors.py +752 -0
  132. sage/modular/modform_hecketriangle/graded_ring.py +541 -0
  133. sage/modular/modform_hecketriangle/graded_ring_element.py +2225 -0
  134. sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +3352 -0
  135. sage/modular/modform_hecketriangle/hecke_triangle_groups.py +1432 -0
  136. sage/modular/modform_hecketriangle/readme.py +1214 -0
  137. sage/modular/modform_hecketriangle/series_constructor.py +580 -0
  138. sage/modular/modform_hecketriangle/space.py +1037 -0
  139. sage/modular/modform_hecketriangle/subspace.py +423 -0
  140. sage/modular/modsym/all.py +17 -0
  141. sage/modular/modsym/ambient.py +3846 -0
  142. sage/modular/modsym/boundary.py +1420 -0
  143. sage/modular/modsym/element.py +336 -0
  144. sage/modular/modsym/g1list.py +178 -0
  145. sage/modular/modsym/ghlist.py +182 -0
  146. sage/modular/modsym/hecke_operator.py +73 -0
  147. sage/modular/modsym/manin_symbol.cpython-312-darwin.so +0 -0
  148. sage/modular/modsym/manin_symbol.pxd +5 -0
  149. sage/modular/modsym/manin_symbol.pyx +497 -0
  150. sage/modular/modsym/manin_symbol_list.py +1295 -0
  151. sage/modular/modsym/modsym.py +400 -0
  152. sage/modular/modsym/modular_symbols.py +384 -0
  153. sage/modular/modsym/p1list_nf.py +1241 -0
  154. sage/modular/modsym/relation_matrix.py +591 -0
  155. sage/modular/modsym/relation_matrix_pyx.cpython-312-darwin.so +0 -0
  156. sage/modular/modsym/relation_matrix_pyx.pyx +108 -0
  157. sage/modular/modsym/space.py +2468 -0
  158. sage/modular/modsym/subspace.py +455 -0
  159. sage/modular/modsym/tests.py +375 -0
  160. sage/modular/multiple_zeta.py +2632 -0
  161. sage/modular/multiple_zeta_F_algebra.py +786 -0
  162. sage/modular/overconvergent/all.py +6 -0
  163. sage/modular/overconvergent/genus0.py +1878 -0
  164. sage/modular/overconvergent/hecke_series.py +1187 -0
  165. sage/modular/overconvergent/weightspace.py +778 -0
  166. sage/modular/pollack_stevens/all.py +4 -0
  167. sage/modular/pollack_stevens/distributions.py +874 -0
  168. sage/modular/pollack_stevens/fund_domain.py +1572 -0
  169. sage/modular/pollack_stevens/manin_map.py +859 -0
  170. sage/modular/pollack_stevens/modsym.py +1593 -0
  171. sage/modular/pollack_stevens/padic_lseries.py +417 -0
  172. sage/modular/pollack_stevens/sigma0.py +534 -0
  173. sage/modular/pollack_stevens/space.py +1076 -0
  174. sage/modular/quasimodform/all.py +3 -0
  175. sage/modular/quasimodform/element.py +845 -0
  176. sage/modular/quasimodform/ring.py +828 -0
  177. sage/modular/quatalg/all.py +3 -0
  178. sage/modular/quatalg/brandt.py +1642 -0
  179. sage/modular/ssmod/all.py +8 -0
  180. sage/modular/ssmod/ssmod.py +827 -0
  181. sage/rings/all__sagemath_schemes.py +1 -0
  182. sage/rings/polynomial/all__sagemath_schemes.py +1 -0
  183. sage/rings/polynomial/binary_form_reduce.py +585 -0
  184. sage/schemes/all.py +41 -0
  185. sage/schemes/berkovich/all.py +6 -0
  186. sage/schemes/berkovich/berkovich_cp_element.py +2582 -0
  187. sage/schemes/berkovich/berkovich_space.py +748 -0
  188. sage/schemes/curves/affine_curve.py +2928 -0
  189. sage/schemes/curves/all.py +33 -0
  190. sage/schemes/curves/closed_point.py +434 -0
  191. sage/schemes/curves/constructor.py +381 -0
  192. sage/schemes/curves/curve.py +542 -0
  193. sage/schemes/curves/plane_curve_arrangement.py +1283 -0
  194. sage/schemes/curves/point.py +463 -0
  195. sage/schemes/curves/projective_curve.py +3026 -0
  196. sage/schemes/curves/zariski_vankampen.py +1932 -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 +1036 -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 +1102 -0
  208. sage/schemes/elliptic_curves/constructor.py +1552 -0
  209. sage/schemes/elliptic_curves/ec_database.py +175 -0
  210. sage/schemes/elliptic_curves/ell_curve_isogeny.py +3972 -0
  211. sage/schemes/elliptic_curves/ell_egros.py +459 -0
  212. sage/schemes/elliptic_curves/ell_field.py +2836 -0
  213. sage/schemes/elliptic_curves/ell_finite_field.py +3359 -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 +4787 -0
  220. sage/schemes/elliptic_curves/ell_rational_field.py +7368 -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 +1669 -0
  227. sage/schemes/elliptic_curves/gp_simon.py +152 -0
  228. sage/schemes/elliptic_curves/heegner.py +7335 -0
  229. sage/schemes/elliptic_curves/height.py +2109 -0
  230. sage/schemes/elliptic_curves/hom.py +1406 -0
  231. sage/schemes/elliptic_curves/hom_composite.py +934 -0
  232. sage/schemes/elliptic_curves/hom_frobenius.py +522 -0
  233. sage/schemes/elliptic_curves/hom_scalar.py +531 -0
  234. sage/schemes/elliptic_curves/hom_sum.py +682 -0
  235. sage/schemes/elliptic_curves/hom_velusqrt.py +1290 -0
  236. sage/schemes/elliptic_curves/homset.py +271 -0
  237. sage/schemes/elliptic_curves/isogeny_class.py +1521 -0
  238. sage/schemes/elliptic_curves/isogeny_small_degree.py +2797 -0
  239. sage/schemes/elliptic_curves/jacobian.py +237 -0
  240. sage/schemes/elliptic_curves/kodaira_symbol.py +344 -0
  241. sage/schemes/elliptic_curves/kraus.py +1014 -0
  242. sage/schemes/elliptic_curves/lseries_ell.py +943 -0
  243. sage/schemes/elliptic_curves/mod5family.py +105 -0
  244. sage/schemes/elliptic_curves/mod_poly.py +197 -0
  245. sage/schemes/elliptic_curves/mod_sym_num.cpython-312-darwin.so +0 -0
  246. sage/schemes/elliptic_curves/mod_sym_num.pyx +3796 -0
  247. sage/schemes/elliptic_curves/modular_parametrization.py +305 -0
  248. sage/schemes/elliptic_curves/padic_lseries.py +1793 -0
  249. sage/schemes/elliptic_curves/padics.py +1816 -0
  250. sage/schemes/elliptic_curves/period_lattice.py +2234 -0
  251. sage/schemes/elliptic_curves/period_lattice_region.cpython-312-darwin.so +0 -0
  252. sage/schemes/elliptic_curves/period_lattice_region.pyx +722 -0
  253. sage/schemes/elliptic_curves/saturation.py +715 -0
  254. sage/schemes/elliptic_curves/sha_tate.py +1158 -0
  255. sage/schemes/elliptic_curves/weierstrass_morphism.py +1117 -0
  256. sage/schemes/elliptic_curves/weierstrass_transform.py +200 -0
  257. sage/schemes/hyperelliptic_curves/all.py +6 -0
  258. sage/schemes/hyperelliptic_curves/constructor.py +291 -0
  259. sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +1914 -0
  260. sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +192 -0
  261. sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +954 -0
  262. sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +1332 -0
  263. sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +84 -0
  264. sage/schemes/hyperelliptic_curves/invariants.py +410 -0
  265. sage/schemes/hyperelliptic_curves/jacobian_endomorphism_utils.py +315 -0
  266. sage/schemes/hyperelliptic_curves/jacobian_g2.py +32 -0
  267. sage/schemes/hyperelliptic_curves/jacobian_generic.py +419 -0
  268. sage/schemes/hyperelliptic_curves/jacobian_homset.py +186 -0
  269. sage/schemes/hyperelliptic_curves/jacobian_morphism.py +875 -0
  270. sage/schemes/hyperelliptic_curves/kummer_surface.py +99 -0
  271. sage/schemes/hyperelliptic_curves/mestre.py +302 -0
  272. sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +3871 -0
  273. sage/schemes/jacobians/abstract_jacobian.py +277 -0
  274. sage/schemes/jacobians/all.py +2 -0
  275. sage/schemes/overview.py +161 -0
  276. sage/schemes/plane_conics/all.py +22 -0
  277. sage/schemes/plane_conics/con_field.py +1296 -0
  278. sage/schemes/plane_conics/con_finite_field.py +158 -0
  279. sage/schemes/plane_conics/con_number_field.py +456 -0
  280. sage/schemes/plane_conics/con_rational_field.py +406 -0
  281. sage/schemes/plane_conics/con_rational_function_field.py +580 -0
  282. sage/schemes/plane_conics/constructor.py +249 -0
  283. sage/schemes/plane_quartics/all.py +2 -0
  284. sage/schemes/plane_quartics/quartic_constructor.py +71 -0
  285. sage/schemes/plane_quartics/quartic_generic.py +73 -0
  286. sage/schemes/riemann_surfaces/all.py +1 -0
  287. sage/schemes/riemann_surfaces/riemann_surface.py +4117 -0
  288. sage_wheels/share/cremona/cremona_mini.db +0 -0
  289. sage_wheels/share/ellcurves/rank0 +30427 -0
  290. sage_wheels/share/ellcurves/rank1 +31871 -0
  291. sage_wheels/share/ellcurves/rank10 +6 -0
  292. sage_wheels/share/ellcurves/rank11 +6 -0
  293. sage_wheels/share/ellcurves/rank12 +1 -0
  294. sage_wheels/share/ellcurves/rank14 +1 -0
  295. sage_wheels/share/ellcurves/rank15 +1 -0
  296. sage_wheels/share/ellcurves/rank17 +1 -0
  297. sage_wheels/share/ellcurves/rank19 +1 -0
  298. sage_wheels/share/ellcurves/rank2 +2388 -0
  299. sage_wheels/share/ellcurves/rank20 +1 -0
  300. sage_wheels/share/ellcurves/rank21 +1 -0
  301. sage_wheels/share/ellcurves/rank22 +1 -0
  302. sage_wheels/share/ellcurves/rank23 +1 -0
  303. sage_wheels/share/ellcurves/rank24 +1 -0
  304. sage_wheels/share/ellcurves/rank28 +1 -0
  305. sage_wheels/share/ellcurves/rank3 +836 -0
  306. sage_wheels/share/ellcurves/rank4 +10 -0
  307. sage_wheels/share/ellcurves/rank5 +5 -0
  308. sage_wheels/share/ellcurves/rank6 +5 -0
  309. sage_wheels/share/ellcurves/rank7 +5 -0
  310. sage_wheels/share/ellcurves/rank8 +6 -0
  311. sage_wheels/share/ellcurves/rank9 +7 -0
@@ -0,0 +1,4117 @@
1
+ # sage_setup: distribution = sagemath-schemes
2
+ # sage.doctest: needs scipy sage.graphs sage.groups
3
+ r"""
4
+ Riemann matrices and endomorphism rings of algebraic Riemann surfaces
5
+
6
+ This module provides a class, :class:`RiemannSurface`, to model the
7
+ Riemann surface determined by a plane algebraic curve over a subfield
8
+ of the complex numbers.
9
+
10
+ A homology basis is derived from the edges of a Voronoi cell decomposition based
11
+ on the branch locus. The pull-back of these edges to the Riemann surface
12
+ provides a graph on it that contains a homology basis.
13
+
14
+ The class provides methods for computing the Riemann period matrix of the
15
+ surface numerically, using a certified homotopy continuation method due to
16
+ [Kr2016]_.
17
+
18
+ The class also provides facilities for computing the endomorphism ring of the
19
+ period lattice numerically, by determining integer (near) solutions to the
20
+ relevant approximate linear equations.
21
+
22
+ One can also calculate the Abel-Jacobi map on the Riemann surface, and there
23
+ is basic functionality to interface with divisors of curves to facilitate this.
24
+
25
+ AUTHORS:
26
+
27
+ - Alexandre Zotine, Nils Bruin (2017-06-10): initial version
28
+ - Nils Bruin, Jeroen Sijsling (2018-01-05): algebraization, isomorphisms
29
+ - Linden Disney-Hogg, Nils Bruin (2021-06-23): efficient integration
30
+ - Linden Disney-Hogg, Nils Bruin (2022-09-07): Abel-Jacobi map
31
+
32
+ EXAMPLES:
33
+
34
+ We compute the Riemann matrix of a genus 3 curve::
35
+
36
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
37
+ sage: R.<x,y> = QQ[]
38
+ sage: f = x^4-x^3*y+2*x^3+2*x^2*y+2*x^2-2*x*y^2+4*x*y-y^3+3*y^2+2*y+1
39
+ sage: S = RiemannSurface(f, prec=100)
40
+ sage: M = S.riemann_matrix()
41
+
42
+ We test the usual properties, i.e., that the period matrix is symmetric and that
43
+ the imaginary part is positive definite::
44
+
45
+ sage: all(abs(a) < 1e-20 for a in (M-M.T).list())
46
+ True
47
+ sage: iM = Matrix(RDF,3,3,[a.imag_part() for a in M.list()])
48
+ sage: iM.is_positive_definite()
49
+ True
50
+
51
+ We compute the endomorphism ring and check it has `\ZZ`-rank 6::
52
+
53
+ sage: A = S.endomorphism_basis(80,8)
54
+ sage: len(A) == 6
55
+ True
56
+
57
+ In fact it is an order in a number field::
58
+
59
+ sage: T.<t> = QQ[]
60
+ sage: K.<a> = NumberField(t^6 - t^5 + 2*t^4 + 8*t^3 - t^2 - 5*t + 7)
61
+ sage: all(len(a.minpoly().roots(K)) == a.minpoly().degree() for a in A)
62
+ True
63
+
64
+ We can look at an extended example of the Abel-Jacobi functionality. We will
65
+ demonstrate a particular half-canonical divisor on Klein's Curve, known in
66
+ the literature::
67
+
68
+ sage: f = x^3*y + y^3 + x
69
+ sage: S = RiemannSurface(f, integration_method='rigorous')
70
+ sage: BL = S.places_at_branch_locus(); BL
71
+ [Place (x, y, y^2),
72
+ Place (x^7 + 27/4, y + 4/9*x^5, y^2 + 4/3*x^3),
73
+ Place (x^7 + 27/4, y - 2/9*x^5, y^2 + 1/3*x^3)]
74
+
75
+ We can read off out the output of ``places_at_branch_locus`` to choose our
76
+ divisor, and we can calculate the canonical divisor using curve functionality::
77
+
78
+ sage: P0 = 1*BL[0]
79
+ sage: from sage.schemes.curves.constructor import Curve
80
+ sage: C = Curve(f)
81
+ sage: F = C.function_field()
82
+ sage: K = (F(x).differential()).divisor() - F(f.derivative(y)).divisor()
83
+ sage: Pinf, Pinf_prime = C.places_at_infinity()
84
+ sage: if K-3*Pinf-1*Pinf_prime: Pinf, Pinf_prime = (Pinf_prime, Pinf);
85
+ sage: D = P0 + 2*Pinf - Pinf_prime
86
+
87
+ Note we could check using exact techniques that `2D = K`::
88
+
89
+ sage: Z = K - 2*D
90
+ sage: (Z.degree() == 0, len(Z.basis_differential_space()) == S.genus, len(Z.basis_function_space()) == 1)
91
+ (True, True, True)
92
+
93
+ We can also check this using our Abel-Jacobi functions::
94
+
95
+ sage: avoid = C.places_at_infinity()
96
+ sage: Zeq, _ = S.strong_approximation(Z, avoid)
97
+ sage: Zlist = S.divisor_to_divisor_list(Zeq)
98
+ sage: AJ = S.abel_jacobi(Zlist) # long time (1 second)
99
+ sage: S.reduce_over_period_lattice(AJ).norm() < 1e-10 # long time
100
+ True
101
+
102
+ REFERENCES:
103
+
104
+ The initial version of this code was developed alongside [BSZ2019]_.
105
+ """
106
+ # ****************************************************************************
107
+ # Copyright (C) 2017 Alexandre Zotine, Nils Bruin
108
+ #
109
+ # This program is free software: you can redistribute it and/or modify
110
+ # it under the terms of the GNU General Public License as published by
111
+ # the Free Software Foundation, either version 2 of the License, or
112
+ # (at your option) any later version.
113
+ # https://www.gnu.org/licenses/
114
+ # ****************************************************************************
115
+
116
+ from scipy.spatial import Voronoi
117
+ from sage.arith.functions import lcm
118
+ from sage.arith.misc import GCD, algebraic_dependency
119
+ from sage.ext.fast_callable import fast_callable
120
+ from sage.graphs.graph import Graph
121
+ from sage.groups.matrix_gps.finitely_generated import MatrixGroup
122
+ from sage.groups.perm_gps.permgroup_named import SymmetricGroup
123
+ from sage.matrix.constructor import Matrix
124
+ from sage.matrix.special import block_matrix
125
+ from sage.misc.cachefunc import cached_method
126
+ from sage.misc.flatten import flatten
127
+ from sage.misc.functional import numerical_approx
128
+ from sage.misc.lazy_import import lazy_import
129
+ from sage.misc.misc_c import prod
130
+ from sage.modules.free_module import VectorSpace
131
+ from sage.modules.free_module_integer import IntegerLattice
132
+ from sage.numerical.gauss_legendre import integrate_vector, integrate_vector_N
133
+ from sage.rings.complex_mpfr import ComplexField, CDF
134
+ from sage.rings.function_field.constructor import FunctionField
135
+ from sage.rings.function_field.divisor import FunctionFieldDivisor
136
+ from sage.rings.infinity import Infinity
137
+ from sage.rings.integer_ring import ZZ
138
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
139
+ from sage.rings.rational_field import QQ
140
+ from sage.rings.real_mpfr import RealField
141
+ from sage.schemes.curves.constructor import Curve
142
+ import sage.libs.mpmath.all as mpall
143
+
144
+ lazy_import('sage.rings.qqbar', 'number_field_elements_from_algebraics')
145
+
146
+
147
+ def voronoi_ghost(cpoints, n=6, CC=CDF):
148
+ r"""
149
+ Convert a set of complex points to a list of real tuples `(x,y)`, and
150
+ appends n points in a big circle around them.
151
+
152
+ The effect is that, with n >= 3, a Voronoi decomposition will have only
153
+ finite cells around the original points. Furthermore, because the extra
154
+ points are placed on a circle centered on the average of the given points,
155
+ with a radius 3/2 times the largest distance between the center and the
156
+ given points, these finite cells form a simply connected region.
157
+
158
+ INPUT:
159
+
160
+ - ``cpoints`` -- list of complex numbers
161
+
162
+ OUTPUT:
163
+
164
+ A list of real tuples `(x,y)` consisting of the original points and a set of
165
+ points which surround them.
166
+
167
+ EXAMPLES::
168
+
169
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import voronoi_ghost
170
+ sage: L = [1 + 1*I, 1 - 1*I, -1 + 1*I, -1 - 1*I]
171
+ sage: voronoi_ghost(L) # abs tol 1e-6
172
+ [(1.0, 1.0),
173
+ (1.0, -1.0),
174
+ (-1.0, 1.0),
175
+ (-1.0, -1.0),
176
+ (2.121320343559643, 0.0),
177
+ (1.0606601717798216, 1.8371173070873836),
178
+ (-1.060660171779821, 1.8371173070873839),
179
+ (-2.121320343559643, 2.59786816870648e-16),
180
+ (-1.0606601717798223, -1.8371173070873832),
181
+ (1.06066017177982, -1.8371173070873845)]
182
+ """
183
+ cpoints = [CC(c) for c in cpoints]
184
+ average = sum(cpoints) / len(cpoints)
185
+ if len(cpoints) == 1:
186
+ radius = 1
187
+ else:
188
+ radius = 3 * max(abs(c - average) for c in cpoints) / 2
189
+ z = CC.zeta(n)
190
+ extra_points = [average + radius * z**i for i in range(n)]
191
+ return [tuple(c) for c in cpoints + extra_points]
192
+
193
+
194
+ def bisect(L, t):
195
+ r"""
196
+ Find position in a sorted list using bisection.
197
+
198
+ Given a list `L = [(t_0,...),(t_1,...),...(t_n,...)]` with increasing `t_i`,
199
+ find the index i such that `t_i <= t < t_{i+1}` using bisection. The rest of
200
+ the tuple is available for whatever use required.
201
+
202
+ INPUT:
203
+
204
+ - ``L`` -- list of tuples such that the first term of each tuple is a real
205
+ number between 0 and 1. These real numbers must be increasing
206
+
207
+ - ``t`` -- real number between `t_0` and `t_n`
208
+
209
+ OUTPUT: integer i, giving the position in L where t would be in
210
+
211
+ EXAMPLES:
212
+
213
+ Form a list of the desired form, and pick a real number between 0 and 1::
214
+
215
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import bisect
216
+ sage: L = [(0.0, 'a'), (0.3, 'b'), (0.7, 'c'), (0.8, 'd'), (0.9, 'e'), (1.0, 'f')]
217
+ sage: t = 0.5
218
+ sage: bisect(L,t)
219
+ 1
220
+
221
+ Another example which demonstrates that if t is equal to one of the t_i, it
222
+ returns that index::
223
+
224
+ sage: L = [(0.0, 'a'), (0.1, 'b'), (0.45, 'c'), (0.5, 'd'), (0.65, 'e'), (1.0, 'f')]
225
+ sage: t = 0.5
226
+ sage: bisect(L,t)
227
+ 3
228
+ """
229
+ # Defining starting indices for the loop.
230
+ min = 0
231
+ max = len(L) - 1
232
+ # If the input t is not between 0 and 1, raise an error.
233
+ if t < L[min][0] or t > L[max][0]:
234
+ raise ValueError("value for t out of range")
235
+ # Main loop.
236
+ while min < max - 1:
237
+ # Bisect.
238
+ mid = (max + min) // 2
239
+ # If it's equal, return the index we bisected to.
240
+ if t == L[mid][0]:
241
+ return mid
242
+ # If it's smaller, then we're on the left side.
243
+ elif t < L[mid][0]:
244
+ max = mid
245
+ # Otherwise we're on the right side.
246
+ else:
247
+ min = mid
248
+ # Once the loop terminates, we return what the indices converged to.
249
+ return min
250
+
251
+
252
+ def numerical_inverse(C):
253
+ """
254
+ Compute numerical inverse of a matrix via LU decomposition.
255
+
256
+ INPUT:
257
+
258
+ - ``C`` -- a real or complex invertible square matrix
259
+
260
+ EXAMPLES::
261
+
262
+ sage: C = matrix(CC, 3, 3, [-4.5606e-31 + 1.2326e-31*I,
263
+ ....: -0.21313 + 0.24166*I,
264
+ ....: -3.4513e-31 + 0.16111*I,
265
+ ....: -1.0175 + 9.8608e-32*I,
266
+ ....: 0.30912 + 0.19962*I,
267
+ ....: -4.9304e-32 + 0.39923*I,
268
+ ....: 0.96793 - 3.4513e-31*I,
269
+ ....: -0.091587 + 0.19276*I,
270
+ ....: 3.9443e-31 + 0.38552*I])
271
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import numerical_inverse
272
+ sage: 3e-16 < (C^-1*C-C^0).norm() < 1e-15
273
+ True
274
+ sage: (numerical_inverse(C)*C-C^0).norm() < 3e-16
275
+ True
276
+ """
277
+ R = C.parent()
278
+ prec = R.base_ring().prec()
279
+ mpall.mp.prec = prec
280
+ with mpall.workprec(prec):
281
+ Cmp = mpall.matrix([mpall.sage_to_mpmath(list(c), prec) for c in C])
282
+ PLU = mpall.lu(Cmp)
283
+ P, L, U = (R([mpall.mpmath_to_sage(c, prec) for c in M]) for M in PLU)
284
+ return U.inverse() * L.inverse() * P
285
+
286
+
287
+ class ConvergenceError(ValueError):
288
+ r"""
289
+ Error object suitable for raising and catching when Newton iteration fails.
290
+
291
+ EXAMPLES::
292
+
293
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import ConvergenceError
294
+ sage: raise ConvergenceError("test")
295
+ Traceback (most recent call last):
296
+ ...
297
+ ConvergenceError: test
298
+ sage: isinstance(ConvergenceError(),ValueError)
299
+ True
300
+ """
301
+ pass
302
+
303
+
304
+ def differential_basis_baker(f):
305
+ r"""
306
+ Compute a differential basis for a curve that is nonsingular outside (1:0:0),(0:1:0),(0:0:1).
307
+
308
+ Baker's theorem tells us that if a curve has its singularities at the coordinate vertices and meets
309
+ some further easily tested genericity criteria,
310
+ then we can read off a basis for the regular differentials from the interior of the
311
+ Newton polygon spanned by the monomials. While this theorem only applies to special plane curves
312
+ it is worth implementing because the analysis is relatively cheap and it applies to a lot of
313
+ commonly encountered curves (e.g., curves given by a hyperelliptic model). Other advantages include
314
+ that we can do the computation over any exact base ring (the alternative Singular based method for
315
+ computing the adjoint ideal requires the rationals), and that we can avoid being affected by subtle bugs
316
+ in the Singular code.
317
+
318
+ ``None`` is returned when ``f`` does not describe a curve of the relevant type. If ``f`` is of the relevant
319
+ type, but is of genus `0` then ``[]`` is returned (which are both False values, but they are not equal).
320
+
321
+ INPUT:
322
+
323
+ - ``f`` -- a bivariate polynomial
324
+
325
+ EXAMPLES::
326
+
327
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import differential_basis_baker
328
+ sage: R.<x,y> = QQ[]
329
+ sage: f = x^3 + y^3 + x^5*y^5
330
+ sage: differential_basis_baker(f)
331
+ [y^2, x*y, x*y^2, x^2, x^2*y, x^2*y^2, x^2*y^3, x^3*y^2, x^3*y^3]
332
+ sage: f = y^2 - (x-3)^2*x
333
+ sage: differential_basis_baker(f) is None
334
+ True
335
+ sage: differential_basis_baker(x^2+y^2-1)
336
+ []
337
+
338
+ TESTS::
339
+
340
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import differential_basis_baker
341
+ sage: R.<x,y> = QQ[]
342
+ sage: f = y^12 - x*(x - 1)^7
343
+ sage: differential_basis_baker(f) is None
344
+ True
345
+ """
346
+ k = f.base_ring()
347
+ R = PolynomialRing(k, 3, "x,y,z")
348
+ x, y, z = R.gens()
349
+ F = f(x / z, y / z).numerator()
350
+ W = [F] + [F.derivative(v) for v in R.gens()]
351
+ # we check that the singularities lie at (1:0:0),(0:1:0),(0:0:1)
352
+ # by checking that the eliminations of x, y, z result in
353
+ # (principal) ideals generated by a monomial. This is a sufficient
354
+ # condition, but not completely necessary.
355
+ # It's cheap to check, though.
356
+ for c in R.gens():
357
+ B = GCD([W[i].resultant(W[j], c) for i in range(4) for j in range(i)])
358
+ if len(B.monomials()) > 1:
359
+ return None
360
+ from sage.geometry.polyhedron.constructor import Polyhedron
361
+
362
+ D = {(k[0], k[1]): v for k, v in f.monomial_coefficients().items()}
363
+ P = Polyhedron(D)
364
+ kT = k["t"]
365
+ # here we check the additional genericity conditions: that the polynomials
366
+ # along the edges of the Newton polygon are square-free.
367
+ for e in P.bounded_edges():
368
+ h = kT([D.get(tuple(c), 0) for c in Polyhedron(e).integral_points()])
369
+ if not h.is_squarefree():
370
+ return None
371
+ x, y = f.parent().gens()
372
+ return [
373
+ x**(a[0] - 1) * y**(a[1] - 1)
374
+ for a in P.integral_points()
375
+ if P.interior_contains(a)
376
+ ]
377
+
378
+
379
+ def find_closest_element(item, lst):
380
+ r"""
381
+ Return the index of the closest element of a list.
382
+
383
+ Given ``List`` and ``item``, return the index of the element ``l`` of ``List``
384
+ which minimises ``(item-l).abs()``. If there are multiple such elements, the
385
+ first is returned.
386
+
387
+ INPUT:
388
+
389
+ - ``item`` -- value to minimize the distance to over the list
390
+
391
+ - ``lst`` -- list to look for closest element in
392
+
393
+ EXAMPLES::
394
+
395
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import find_closest_element
396
+ sage: i = 5
397
+ sage: l = list(range(10))
398
+ sage: i == find_closest_element(i, l)
399
+ True
400
+
401
+ Note that this method does no checks on the input, but will fail for inputs
402
+ where the absolute value or subtraction do not make sense.
403
+ """
404
+ dists = [(item - l).abs() for l in lst]
405
+ return dists.index(min(dists))
406
+
407
+
408
+ def reparameterize_differential_minpoly(minpoly, z0):
409
+ r"""
410
+ Rewrites a minimal polynomial to write is around `z_0`.
411
+
412
+ Given a minimal polynomial `m(z,g)`, where `g` corresponds to a differential
413
+ on the surface (that is, it is represented as a rational function, and
414
+ implicitly carries a factor `dz`), we rewrite the minpoly in terms of
415
+ variables `\bar{z}, \bar{g}` s.t now `\bar{z}=0 \Leftrightarrow z=z_0`.
416
+
417
+ INPUT:
418
+
419
+ - ``minpoly`` -- a polynomial in two variables, where the first variable
420
+ corresponds to the base coordinate on the Riemann surface
421
+ - ``z0`` -- complex number or infinity; the point about which to
422
+ reparameterize
423
+
424
+ OUTPUT: a polynomial in two variables giving the reparameterize minimal polynomial
425
+
426
+ EXAMPLES:
427
+
428
+ On the curve given by `w^2 - z^3 + 1 = 0`, we have differential
429
+ `\frac{dz}{2w} = \frac{dz}{2\sqrt{z^3-1}}`
430
+ with minimal polynomial `g^2(z^3-1) - 1/4=0`. We can make the substitution
431
+ `\bar{z}=z^{-1}` to parameterise the differential about `z=\infty` as
432
+
433
+ .. MATH::
434
+
435
+ \frac{-\bar{z}^{-2} d\bar{z}}{2\sqrt{\bar{z}^{-3}-1}} = \frac{-d\bar{z}}{2\sqrt{\bar{z}(1-\bar{z}^3)}}.
436
+
437
+ Hence the transformed differential should have minimal polynomial
438
+ `\bar{g}^2 \bar{z} (1 - \bar{z}^3) - 1/4 = 0`, and we can check this::
439
+
440
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, reparameterize_differential_minpoly
441
+ sage: R.<z,w> = QQ[]
442
+ sage: S = RiemannSurface(w^2-z^3+1)
443
+ sage: minpoly = S._cohomology_basis_bounding_data[1][0][2]
444
+ sage: z0 = Infinity
445
+ sage: reparameterize_differential_minpoly(minpoly, z0)
446
+ -zbar^4*gbar^2 + zbar*gbar^2 - 1/4
447
+
448
+ We can further check that reparameterising about `0` is the identity
449
+ operation::
450
+
451
+ sage: reparameterize_differential_minpoly(minpoly, 0)(*minpoly.parent().gens()) == minpoly
452
+ True
453
+
454
+ .. NOTE::
455
+
456
+ As part of the routine, when reparameterising about infinity, a
457
+ rational function is reduced and then the numerator is taken. Over
458
+ an inexact ring this is numerically unstable, and so it is advisable
459
+ to only reparameterize about infinity over an exact ring.
460
+ """
461
+ P = minpoly.parent()
462
+ F = PolynomialRing(P.base_ring(), [str(v) + "bar" for v in P.gens()])
463
+
464
+ try:
465
+ Inf = bool(z0 == z0.parent()(Infinity))
466
+ except TypeError:
467
+ Inf = False
468
+
469
+ if Inf:
470
+ F = F.fraction_field()
471
+ mt = F(minpoly(F.gen(0)**(-1), -F.gen(0)**2 * F.gen(1)))
472
+ mt.reduce()
473
+ mt = mt.numerator()
474
+ else:
475
+ mt = minpoly(F.gen(0) + z0, F.gen(1))
476
+ return mt
477
+
478
+
479
+ class RiemannSurface:
480
+ r"""
481
+ Construct a Riemann Surface. This is specified by the zeroes of a bivariate
482
+ polynomial with rational coefficients `f(z,w) = 0`.
483
+
484
+ INPUT:
485
+
486
+ - ``f`` -- a bivariate polynomial with rational coefficients. The surface is
487
+ interpreted as the covering space of the coordinate plane in the first
488
+ variable.
489
+
490
+ - ``prec`` -- the desired precision of computations on the surface in bits
491
+ (default: 53)
492
+
493
+ - ``certification`` -- boolean (default: ``True``); value indicating
494
+ whether homotopy continuation is certified or not. Uncertified
495
+ homotopy continuation can be faster.
496
+
497
+ - ``differentials`` -- (default: ``None``) if specified, provides a list
498
+ of polynomials `h` such that `h/(df/dw) dz` is a regular
499
+ differential on the Riemann surface. This is taken as a basis of
500
+ the regular differentials, so the genus is assumed to be equal
501
+ to the length of this list. The results from the homology basis
502
+ computation are checked against this value. Providing this
503
+ parameter makes the computation independent from Singular. For
504
+ a nonsingular plane curve of degree `d`, an appropriate set is
505
+ given by the monomials of degree up to `d-3`.
506
+
507
+ - ``integration_method`` -- (default: ``'rigorous'``). String specifying the
508
+ integration method to use when calculating the integrals of differentials.
509
+ The options are ``'heuristic'`` and ``'rigorous'``, the latter of
510
+ which is often the most efficient.
511
+
512
+ EXAMPLES::
513
+
514
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
515
+ sage: R.<z,w> = QQ[]
516
+ sage: f = w^2 - z^3 + 1
517
+ sage: RiemannSurface(f)
518
+ Riemann surface defined by polynomial f = -z^3 + w^2 + 1 = 0, with 53 bits of precision
519
+
520
+ Another Riemann surface with 100 bits of precision::
521
+
522
+ sage: S = RiemannSurface(f, prec=100); S
523
+ Riemann surface defined by polynomial f = -z^3 + w^2 + 1 = 0, with 100 bits of precision
524
+ sage: S.riemann_matrix()^6 #abs tol 0.00000001
525
+ [1.0000000000000000000000000000 - 1.1832913578315177081175928479e-30*I]
526
+
527
+ We can also work with Riemann surfaces that are defined over fields with a
528
+ complex embedding, but since the current interface for computing genus and
529
+ regular differentials in Singular presently does not support extensions of
530
+ `\QQ`, we need to specify a description of the differentials ourselves. We give
531
+ an example of a CM elliptic curve::
532
+
533
+ sage: Qt.<t> = QQ[]
534
+ sage: K.<a> = NumberField(t^2-t+3,embedding=CC(0.5+1.6*I))
535
+ sage: R.<x,y> = K[]
536
+ sage: f = y^2 + y - (x^3 + (1-a)*x^2 - (2+a)*x - 2)
537
+ sage: S = RiemannSurface(f, prec=100, differentials=[1])
538
+ sage: A = S.endomorphism_basis()
539
+ sage: len(A)
540
+ 2
541
+ sage: all(len(T.minpoly().roots(K)) > 0 for T in A)
542
+ True
543
+
544
+ The ``'heuristic'`` integration method uses the method ``integrate_vector``
545
+ defined in ``sage.numerical.gauss_legendre`` to compute integrals of differentials.
546
+ As mentioned there, this works by iteratively doubling the number of nodes
547
+ used in the quadrature, and uses a heuristic based on the rate at which the
548
+ result is seemingly converging to estimate the error. The ``'rigorous'``
549
+ method uses results from [Neu2018]_, and bounds the algebraic integrands on
550
+ circular domains using Cauchy's form of the remainder in Taylor approximation
551
+ coupled to Fujiwara's bound on polynomial roots (see Bruin-DisneyHogg-Gao,
552
+ in preparation). Note this method of bounding on circular domains is also
553
+ implemented in :meth:`_compute_delta`. The net result of this bounding is
554
+ that one can know (an upper bound on) the number of nodes required to achieve
555
+ a certain error. This means that for any given integral, assuming that the
556
+ same number of nodes is required by both methods in order to achieve the
557
+ desired error (not necessarily true in practice), approximately half
558
+ the number of integrand evaluations are required. When the required number
559
+ of nodes is high, e.g. when the precision required is high, this can make
560
+ the ``'rigorous'`` method much faster. However, the ``'rigorous'`` method does
561
+ not benefit as much from the caching of the ``nodes`` method over multiple
562
+ integrals. The result of this is that, for calls of :meth:`matrix_of_integral_values`
563
+ if the computation is 'fast', the heuristic method may outperform the
564
+ rigorous method, but for slower computations the rigorous method can be much
565
+ faster::
566
+
567
+ sage: f = z*w^3 + z^3 + w
568
+ sage: p = 53
569
+ sage: Sh = RiemannSurface(f, prec=p, integration_method='heuristic')
570
+ sage: Sr = RiemannSurface(f, prec=p, integration_method='rigorous')
571
+ sage: from sage.numerical.gauss_legendre import nodes
572
+ sage: import time
573
+ sage: nodes.cache.clear()
574
+ sage: ct = time.time()
575
+ sage: Rh = Sh.riemann_matrix()
576
+ sage: ct1 = time.time()-ct
577
+ sage: nodes.cache.clear()
578
+ sage: ct = time.time()
579
+ sage: Rr = Sr.riemann_matrix()
580
+ sage: ct2 = time.time()-ct
581
+ sage: ct2/ct1 # random
582
+ 1.2429363969691192
583
+
584
+ Note that for the above curve, the branch points are evenly distributed, and
585
+ hence the implicit assumptions in the heuristic method are more sensible,
586
+ meaning that a higher precision is required to see the heuristic method
587
+ being significantly slower than the rigorous method. For a worse conditioned
588
+ curve, this effect is more pronounced::
589
+
590
+ sage: q = 1 / 10
591
+ sage: f = y^2 - (x^2 - 2*x + 1 + q^2) * (x^2 + 2*x + 1 + q^2)
592
+ sage: p = 500
593
+ sage: Sh = RiemannSurface(f, prec=p, integration_method='heuristic')
594
+ sage: Sr = RiemannSurface(f, prec=p, integration_method='rigorous')
595
+ sage: nodes.cache.clear()
596
+ sage: Rh = Sh.riemann_matrix() # long time (8 seconds)
597
+ sage: nodes.cache.clear()
598
+ sage: Rr = Sr.riemann_matrix() # long time (1 seconds)
599
+
600
+ This disparity in timings can get increasingly worse, and testing has shown
601
+ that even for random quadrics the heuristic method can be as bad as 30 times
602
+ slower.
603
+
604
+ TESTS:
605
+
606
+ This elliptic curve has a relatively poorly conditioned set of branch
607
+ points, so it challenges the path choice a bit. The code just verifies that
608
+ the period is quadratic, because the curve has CM, but really the test is
609
+ that the computation completes at all.::
610
+
611
+ sage: prec = 50
612
+ sage: Qx.<t> = QQ[]
613
+ sage: CC = ComplexField(prec)
614
+ sage: g = t^2-t-1
615
+ sage: phiCC = g.roots(CC)[1][0]
616
+ sage: K.<phi> = NumberField(g, embedding=phiCC)
617
+ sage: R.<X,Y> = K[]
618
+ sage: f = Y^2+X*Y+phi*Y-(X^3-X^2-2*phi*X+phi)
619
+ sage: S = RiemannSurface(f,prec=prec, differentials=[1])
620
+ sage: tau = S.riemann_matrix()[0, 0]
621
+ sage: tau.algebraic_dependency(6).degree() == 2
622
+ True
623
+ """
624
+
625
+ def __init__(
626
+ self,
627
+ f,
628
+ prec=53,
629
+ certification=True,
630
+ differentials=None,
631
+ integration_method="rigorous"
632
+ ):
633
+ r"""
634
+ TESTS::
635
+
636
+ sage: R.<z,w> = QQ[]
637
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
638
+ sage: S = RiemannSurface(w^2 - z^3 + 1)
639
+ sage: TestSuite(S).run() #not tested; Unclear what pickling strategy is best.
640
+ """
641
+ # Initializations.
642
+ self._prec = prec
643
+ self._certification = certification
644
+ if not (integration_method == "heuristic" or integration_method == "rigorous"):
645
+ raise ValueError("invalid integration method")
646
+ self._integration_method = integration_method
647
+ self._R = f.parent()
648
+ if len(self._R.gens()) != 2:
649
+ raise ValueError('only bivariate polynomials supported')
650
+ if f.degree() <= 1:
651
+ raise ValueError('equation must be of degree at least 2')
652
+ z, w = self._R.gen(0), self._R.gen(1)
653
+ self._CC = ComplexField(self._prec)
654
+ self._RR = RealField(self._prec)
655
+ self._CCz = PolynomialRing(self._CC, [self._R.gen(0)])
656
+ self._CCw = PolynomialRing(self._CC, [self._R.gen(1)])
657
+ self._RRz = PolynomialRing(self._RR, [self._R.gen(0)])
658
+ self._curve = None
659
+ self.f = f
660
+ if differentials is not None:
661
+ self._differentials = [self._R(a) for a in differentials]
662
+ self.genus = len(self._differentials)
663
+ else:
664
+ B = differential_basis_baker(f)
665
+ if B is not None:
666
+ self._differentials = B
667
+ self.genus = len(B)
668
+ else:
669
+ self._differentials = None
670
+ self.genus = self._R.ideal(self.f).genus()
671
+ if self.genus < 0:
672
+ raise ValueError(
673
+ "Singular reports negative genus. Specify differentials manually."
674
+ )
675
+ self.degree = self.f.degree(w)
676
+ self._dfdw = self.f.derivative(w)
677
+ self._dfdz = self.f.derivative(z)
678
+ self._discriminant = self.f.resultant(self._dfdw, w)
679
+ # Coefficients of the polynomial for use in homotopy continuation.
680
+ self._a0 = self._CCz(self.f.coefficient({w: self.degree})(self._CCz.gen(), 0))
681
+ self._a0roots = self._a0.roots(multiplicities=False)
682
+ self._aks = [
683
+ self._CCz(self.f.coefficient({w: self.degree - k - 1})(self._CCz.gen(), 0))
684
+ for k in range(self.degree)
685
+ ]
686
+ # Compute the branch locus. Takes the square-free part of the discriminant
687
+ # because of numerical issues.
688
+ self.branch_locus = []
689
+ existing_factors = [x[0] for x in self._discriminant.factor()]
690
+ for fac in existing_factors:
691
+ self.branch_locus += self._CCz(fac(self._CCz.gen(), 0)).roots(
692
+ multiplicities=False
693
+ )
694
+ self._f_branch_locus = self.branch_locus
695
+ self._cohomology_basis_bounding_data = self._bounding_data(
696
+ self.cohomology_basis(), exact=True
697
+ )
698
+ RBzg, bounding_data_list = self._cohomology_basis_bounding_data
699
+ minpoly_list = [bd[2] for bd in bounding_data_list]
700
+ # We now want to calculate the additional branchpoints associated to
701
+ # the differentials.
702
+ discriminants = [RBzg(self._discriminant(*RBzg.gens()))]
703
+ for minpoly in minpoly_list:
704
+ F = RBzg(minpoly)
705
+ dF = F.derivative(RBzg.gen(1))
706
+ discriminants += [F.resultant(dF, RBzg.gen(1))]
707
+ combined_discriminant = lcm(discriminants)(*self._R.gens())
708
+ self._differentials_branch_locus = []
709
+ for x in combined_discriminant.factor():
710
+ if x[0] not in existing_factors:
711
+ self._differentials_branch_locus += self._CCz(
712
+ x[0](self._CCz.gen(), 0)
713
+ ).roots(multiplicities=False)
714
+ # We add these branchpoints to the existing.
715
+ # self.branch_locus = self.branch_locus+self._differentials_branch_locus
716
+ # We now want to also check whether Infinity is a branch point of any
717
+ # of the differentials.
718
+ # This will be useful when calculating the Abel-Jacobi map.
719
+ minpoly_list = [
720
+ reparameterize_differential_minpoly(mp, Infinity) for mp in minpoly_list
721
+ ]
722
+ discriminants = []
723
+ for minpoly in minpoly_list:
724
+ F = RBzg(minpoly)
725
+ dF = F.derivative(RBzg.gen(1))
726
+ discriminants += [F.resultant(dF, RBzg.gen(1))]
727
+ discriminant_about_infinity = RBzg(lcm(discriminants))
728
+ if discriminant_about_infinity(0, 0) == 0:
729
+ self._differentials_branch_locus.append(self._CC(Infinity))
730
+
731
+ # Voronoi diagram and the important points associated with it
732
+ self.voronoi_diagram = Voronoi(voronoi_ghost(self.branch_locus, CC=self._CC))
733
+ self._vertices = [self._CC(x0, y0) for x0, y0 in self.voronoi_diagram.vertices]
734
+ self._wvalues = [self.w_values(z0) for z0 in self._vertices]
735
+ # We arbitrarily, but sensibly, set the basepoint to be the rightmost vertex
736
+ self._basepoint = (
737
+ self._vertices.index(sorted(self._vertices, key=lambda z: z.real())[-1]),
738
+ 0,
739
+ )
740
+ self._Sn = SymmetricGroup(range(self.degree))
741
+ self._L = {}
742
+ self._integral_dict = {}
743
+ self._fastcall_f = fast_callable(f, domain=self._CC)
744
+ self._fastcall_dfdw = fast_callable(self._dfdw, domain=self._CC)
745
+ self._fastcall_dfdz = fast_callable(self._dfdz, domain=self._CC)
746
+ self._fastcall_cohomology_basis = [
747
+ fast_callable(h, domain=self._CC) for h in self.cohomology_basis()
748
+ ]
749
+
750
+ def __repr__(self):
751
+ r"""
752
+ Return a string representation of the Riemann surface class.
753
+
754
+ EXAMPLES::
755
+
756
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
757
+ sage: R.<z,w> = QQ[]
758
+ sage: f = w^2 - z^4 + 1
759
+ sage: RiemannSurface(f)
760
+ Riemann surface defined by polynomial f = -z^4 + w^2 + 1 = 0, with 53 bits of precision
761
+ """
762
+ s = (
763
+ "Riemann surface defined by polynomial f = %s = 0, with %s bits of precision"
764
+ % (self.f, self._prec)
765
+ )
766
+ return s
767
+
768
+ def w_values(self, z0):
769
+ r"""
770
+ Return the points lying on the surface above ``z0``.
771
+
772
+ INPUT:
773
+
774
+ - ``z0`` -- complex number; a point in the complex z-plane
775
+
776
+ OUTPUT:
777
+
778
+ A set of complex numbers corresponding to solutions of `f(z_0,w) = 0`.
779
+
780
+ EXAMPLES::
781
+
782
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
783
+ sage: R.<z,w> = QQ[]
784
+ sage: f = w^2 - z^4 + 1
785
+ sage: S = RiemannSurface(f)
786
+
787
+ Find the w-values above the origin, i.e. the solutions of `w^2 + 1 = 0`::
788
+
789
+ sage: S.w_values(0) # abs tol 1e-14
790
+ [-1.00000000000000*I, 1.00000000000000*I]
791
+
792
+ Note that typically the method returns a list of length ``self.degree``,
793
+ but that at ramification points, this may no longer be true::
794
+
795
+ sage: S.w_values(1) # abs tol 1e-14
796
+ [0.000000000000000]
797
+ """
798
+ return self.f(z0, self._CCw.gen(0)).roots(multiplicities=False)
799
+
800
+ @cached_method
801
+ def downstairs_edges(self):
802
+ r"""
803
+ Compute the edgeset of the Voronoi diagram.
804
+
805
+ OUTPUT:
806
+
807
+ A list of integer tuples corresponding to edges between vertices in the
808
+ Voronoi diagram.
809
+
810
+ EXAMPLES:
811
+
812
+ Form a Riemann surface, one with a particularly simple branch locus::
813
+
814
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
815
+ sage: R.<z,w> = QQ[]
816
+ sage: f = w^2 + z^3 - z^2
817
+ sage: S = RiemannSurface(f)
818
+
819
+ Compute the edges::
820
+
821
+ sage: S.downstairs_edges()
822
+ [(0, 1), (0, 5), (1, 4), (2, 3), (2, 4), (3, 5), (4, 5)]
823
+
824
+ This now gives an edgeset which one could use to form a graph.
825
+
826
+ .. NOTE::
827
+
828
+ The numbering of the vertices is given by the Voronoi package.
829
+ """
830
+ # Because of how we constructed the Voronoi diagram, the first n points
831
+ # correspond to the branch locus points.
832
+ # The regions of these points are all of the edges which do not go off
833
+ # to infinity, which are exactly the ones we want.
834
+ n = len(self.branch_locus)
835
+ desired_edges = [
836
+ self.voronoi_diagram.regions[self.voronoi_diagram.point_region[i]]
837
+ for i in range(n)
838
+ ]
839
+ # First construct the edges as a set because the regions will overlap
840
+ # and we do not want to have two of the same edge.
841
+ edges1 = set()
842
+ for c in desired_edges:
843
+ for j in range(len(c) - 1):
844
+ edges1.add(frozenset((c[j], c[j + 1])))
845
+ edges1.add(frozenset((c[0], c[-1])))
846
+ # Then make it into a list and sort it.
847
+ # The sorting is important - it will make computing the monodromy group
848
+ # MUCH easier.
849
+ # We orient all the edges so that we go from lower to higher
850
+ # numbered vertex for the continuation.
851
+ edges = [(i0, i1) if (i0 < i1) else (i1, i0) for (i0, i1) in edges1]
852
+ edges.sort()
853
+ return edges
854
+
855
+ def downstairs_graph(self):
856
+ r"""
857
+ Return the Voronoi decomposition as a planar graph.
858
+
859
+ The result of this routine can be useful to interpret the labelling of
860
+ the vertices. See also :meth:`upstairs_graph`.
861
+
862
+ OUTPUT: the Voronoi decomposition as a graph, with appropriate planar embedding
863
+
864
+ EXAMPLES::
865
+
866
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
867
+ sage: R.<z,w> = QQ[]
868
+ sage: f = w^2 - z^4 + 1
869
+ sage: S = RiemannSurface(f)
870
+ sage: S.downstairs_graph()
871
+ Graph on 11 vertices
872
+ """
873
+ G = Graph(self.downstairs_edges())
874
+ G.set_pos(dict(enumerate(list(v) for v in self._vertices)))
875
+ return G
876
+
877
+ @cached_method
878
+ def upstairs_graph(self):
879
+ r"""
880
+ Return the graph of the upstairs edges.
881
+
882
+ This method can be useful for generating paths in the surface between points labelled
883
+ by upstairs vertices, and verifying that a homology basis is likely computed correctly.
884
+ See also :meth:`downstairs_graph`.
885
+
886
+ OUTPUT:
887
+
888
+ The homotopy-continued Voronoi decomposition as a graph, with appropriate 3D embedding.
889
+
890
+ EXAMPLES::
891
+
892
+ sage: R.<z,w> = QQ[]
893
+ sage: S = Curve(w^2-z^4+1).riemann_surface()
894
+ sage: G = S.upstairs_graph(); G
895
+ Graph on 22 vertices
896
+ sage: G.genus() # needs planarity
897
+ 1
898
+ sage: G.is_connected()
899
+ True
900
+ """
901
+ G = Graph(self.upstairs_edges(), immutable=True)
902
+ G.set_pos(
903
+ {
904
+ (i, j): [
905
+ self._vertices[i].real(),
906
+ self._vertices[i].imag(),
907
+ self.w_values(self._vertices[i])[j].imag(),
908
+ ]
909
+ for i in range(len(self._vertices))
910
+ for j in range(self.degree)
911
+ },
912
+ dim=3,
913
+ )
914
+ return G
915
+
916
+ def _compute_delta(self, z1, epsilon, wvalues=None):
917
+ r"""
918
+ Compute a delta for homotopy continuation when moving along a path.
919
+
920
+ INPUT:
921
+
922
+ - ``z1`` -- complex number in the z-plane
923
+
924
+ - ``epsilon`` -- real number which is the minimum distance between
925
+ the w-values above ``z1``
926
+
927
+ - ``wvalues`` -- list (default: ``None``); if specified, saves
928
+ recomputation
929
+
930
+ OUTPUT: a real number, which is a step size for moving along a path
931
+
932
+ EXAMPLES:
933
+
934
+ Form a Riemann Surface::
935
+
936
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
937
+ sage: R.<z,w> = QQ[]
938
+ sage: f = w^2 - z^4 + 1
939
+ sage: S = RiemannSurface(f)
940
+
941
+ Pick a point which lies on the Voronoi diagram, and compute an
942
+ appropriate epsilon::
943
+
944
+ sage: z1 = S._vertices[0]
945
+ sage: currw = S.w_values(z1)
946
+ sage: n = len(currw)
947
+ sage: epsilon = min([abs(currw[i] - currw[n-j-1]) for i in range(n) for j in range(n-i-1)])/3
948
+ sage: S._compute_delta(z1, epsilon) # abs tol 1e-8
949
+ 0.152628501142363
950
+
951
+ If the Riemann surface does not have certified homotopy continuation,
952
+ then the delta will just be the minimum distance away from a branch
953
+ point::
954
+
955
+ sage: T = RiemannSurface(f, certification=False)
956
+ sage: z1 = T._vertices[0]
957
+ sage: currw = T.w_values(z1)
958
+ sage: n = len(currw)
959
+ sage: epsilon = min([abs(currw[i] - currw[n-j-1]) for i in range(n) for j in range(n-i-1)])/3
960
+ sage: T._compute_delta(z1, epsilon) # abs tol 1e-8
961
+ 0.381881307912987
962
+ """
963
+ if self._certification:
964
+ if wvalues is None:
965
+ wvalues = self.w_values(z1)
966
+ # For computation of rho. Need the branch locus + roots of a0.
967
+ badpoints = self._f_branch_locus + self._a0roots
968
+ rho = min(abs(z1 - z) for z in badpoints) / 2
969
+ Y = max(
970
+ abs(self._fastcall_dfdz(z1, wi) / self._fastcall_dfdw(z1, wi))
971
+ for wi in wvalues
972
+ )
973
+
974
+ # compute M
975
+ upperbounds = [
976
+ sum(ak[k] * (abs(z1) + rho)**k for k in range(ak.degree()))
977
+ for ak in self._aks
978
+ ]
979
+ upperbounds.reverse()
980
+ # If a0 is a constant polynomial, it is obviously bounded below.
981
+ if not self._a0roots:
982
+ lowerbound = self._CC(self._a0) / 2
983
+ else:
984
+ lowerbound = (
985
+ self._a0[self._a0.degree()]
986
+ * prod(abs((zk - z1) - rho) for zk in self._a0roots)
987
+ / 2
988
+ )
989
+ M = 2 * max(
990
+ (upperbounds[k] / lowerbound).abs().nth_root(k + 1)
991
+ for k in range(self.degree - 1)
992
+ )
993
+ return (
994
+ rho
995
+ * (
996
+ ((rho * Y - epsilon)**2 + 4 * epsilon * M).sqrt()
997
+ - (rho * Y + epsilon)
998
+ )
999
+ / (2 * M - 2 * rho * Y)
1000
+ )
1001
+ else:
1002
+ # Instead, we just compute the minimum distance between branch
1003
+ # points and the point in question.
1004
+ return min(abs(b - z1) for b in self._f_branch_locus) / 2
1005
+
1006
+ def homotopy_continuation(self, edge):
1007
+ r"""
1008
+ Perform homotopy continuation along an edge of the Voronoi diagram using
1009
+ Newton iteration.
1010
+
1011
+ INPUT:
1012
+
1013
+ - ``edge`` -- tuple ``(z_start, z_end)`` indicating the straight line
1014
+ over which to perform the homotopy continuation
1015
+
1016
+ OUTPUT:
1017
+
1018
+ A list containing the initialised continuation data. Each entry in the
1019
+ list contains: the `t` values that entry corresponds to, a list of
1020
+ complex numbers corresponding to the points which are reached when
1021
+ continued along the edge when traversing along the direction of the
1022
+ edge, and a value ``epsilon`` giving the minimumdistance between the
1023
+ fibre values divided by 3. The ordering of these points indicates how
1024
+ they have been permuted due to the weaving of the curve.
1025
+
1026
+ EXAMPLES:
1027
+
1028
+ We check that continued values along an edge correspond (up to the
1029
+ appropriate permutation) to what is stored. Note that the permutation
1030
+ was originally computed from this data::
1031
+
1032
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1033
+ sage: R.<z,w> = QQ[]
1034
+ sage: f = z^3*w + w^3 + z
1035
+ sage: S = RiemannSurface(f)
1036
+ sage: edge1 = sorted(S.edge_permutations())[0]
1037
+ sage: sigma = S.edge_permutations()[edge1]
1038
+ sage: edge = [S._vertices[i] for i in edge1]
1039
+ sage: continued_values = S.homotopy_continuation(edge)[-1][1]
1040
+ sage: stored_values = S.w_values(S._vertices[edge1[1]])
1041
+ sage: all(abs(continued_values[i]-stored_values[sigma(i)]) < 1e-8 for i in range(3))
1042
+ True
1043
+ """
1044
+ z_start, z_end = edge
1045
+ z_start = self._CC(z_start)
1046
+ z_end = self._CC(z_end)
1047
+ ZERO = self._RR.zero()
1048
+ ONE = self._RR.one()
1049
+ datastorage = []
1050
+ path_length = abs(z_end - z_start)
1051
+
1052
+ def path(t):
1053
+ return z_start * (1 - t) + z_end * t
1054
+
1055
+ # Primary procedure.
1056
+ T = ZERO
1057
+ currw = self.w_values(path(T))
1058
+ n = len(currw)
1059
+ epsilon = (
1060
+ min([abs(currw[i] - currw[j]) for i in range(1, n) for j in range(i)]) / 3
1061
+ )
1062
+ datastorage += [(T, currw, epsilon)]
1063
+ while T < ONE:
1064
+ delta = self._compute_delta(path(T), epsilon, wvalues=currw) / path_length
1065
+ # Move along the path by delta.
1066
+ T += delta
1067
+ # If T exceeds 1, just set it to 1 and compute.
1068
+ if T > ONE:
1069
+ delta -= T - ONE
1070
+ T = ONE
1071
+ while True:
1072
+ try:
1073
+ neww = self._determine_new_w(path(T), currw, epsilon)
1074
+ except ConvergenceError:
1075
+ delta /= 2
1076
+ T -= delta
1077
+ else:
1078
+ break
1079
+ currw = neww
1080
+ epsilon = (
1081
+ min([abs(currw[i] - currw[j]) for i in range(1, n) for j in range(i)])
1082
+ / 3
1083
+ )
1084
+ datastorage += [(T, currw, epsilon)]
1085
+ return datastorage
1086
+
1087
+ def _determine_new_w(self, z0, oldw, epsilon):
1088
+ r"""
1089
+ A procedure to Newton iterate a list of w-values simultaneously.
1090
+
1091
+ Used primarily for moving along the surface for integration or
1092
+ homotopy continuation.
1093
+
1094
+ INPUT:
1095
+
1096
+ - ``z0`` -- complex number
1097
+
1098
+ - ``oldw`` -- list of w-values which are presumed to be guesses of
1099
+ the w-values above ``z0``
1100
+
1101
+ - ``epsilon`` -- the minimum distance between the points of ``oldw``
1102
+ divided by 3
1103
+
1104
+ OUTPUT:
1105
+
1106
+ A list of points the same length as ``oldw`` corresponding to the new
1107
+ Newton iterated points.
1108
+
1109
+ However, if the Newton iteration exceeds the allotted attempts,
1110
+ or exits the ``epsilon`` ball, raises a convergence error.
1111
+
1112
+ EXAMPLES:
1113
+
1114
+ First, a trivial example where we guess exactly what the roots are::
1115
+
1116
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1117
+ sage: R.<z,w> = QQ[]
1118
+ sage: f = w^2 - z^4 + 1
1119
+ sage: S = RiemannSurface(f)
1120
+ sage: z0 = S._vertices[0]
1121
+ sage: epsilon = 0.1
1122
+ sage: oldw = S.w_values(z0)
1123
+ sage: neww = S._determine_new_w(z0, oldw, epsilon); neww #abs tol 0.00000001
1124
+ [-0.934613146929672 + 2.01088055918363*I,
1125
+ 0.934613146929672 - 2.01088055918363*I]
1126
+
1127
+ Which should be exactly the same as the w-values we started with.::
1128
+
1129
+ sage: abs(neww[0] - oldw[0]) #abs tol 0.00000001
1130
+ 0.000000000000...
1131
+ sage: abs(neww[1] - oldw[1]) #abs tol 0.00000001
1132
+ 0.000000000000...
1133
+
1134
+ Here is an example where we exit the ``epsilon`` bound. This approach is
1135
+ based on the homotopy continuation procedure which traverses along a
1136
+ path and attempts Newton iteration::
1137
+
1138
+ sage: g = z^3*w + w^3 + z
1139
+ sage: T = RiemannSurface(g)
1140
+ sage: z0 = T._vertices[2]*(0.9) + 0.3*I
1141
+ sage: epsilon = 0.5
1142
+ sage: oldw = T.w_values(T._vertices[2])
1143
+ sage: T._determine_new_w(z0, oldw, epsilon)
1144
+ Traceback (most recent call last):
1145
+ ...
1146
+ ConvergenceError: Newton iteration escaped neighbourhood
1147
+
1148
+ .. NOTE::
1149
+
1150
+ Algorithmically, this method is nearly identical to :meth:`_newton_iteration`,
1151
+ but this method takes a list of `w` values.
1152
+ """
1153
+ # Tools of Newton iteration.
1154
+ F = self._fastcall_f
1155
+ dF = self._fastcall_dfdw
1156
+ neww = []
1157
+ prec = self._CC.prec()
1158
+ # Iterate over all roots.
1159
+ for i in range(len(oldw)):
1160
+ delta = F(z0, oldw[i]) / dF(z0, oldw[i])
1161
+ Ndelta = delta.norm()
1162
+ wi = oldw[i] - delta
1163
+ # it is possible in theory that Newton iteration fails to
1164
+ # converge without escaping. We catch this by capping the
1165
+ # number of iterations by 100
1166
+ for j in range(100):
1167
+ # If we exceed the epsilon bound from homotopy continuation,
1168
+ # terminate.
1169
+ if abs(wi - oldw[i]) >= epsilon:
1170
+ raise ConvergenceError("Newton iteration escaped neighbourhood")
1171
+ new_delta = F(z0, wi) / dF(z0, wi)
1172
+ Nnew_delta = new_delta.norm()
1173
+ # If we found the root exactly, or if delta only affects half the digits and
1174
+ # stops getting smaller, we decide that we have converged.
1175
+ if (new_delta == 0) or (
1176
+ Nnew_delta >= Ndelta
1177
+ and Ndelta.sign_mantissa_exponent()[2] + prec
1178
+ < wi.norm().sign_mantissa_exponent()[2]
1179
+ ):
1180
+ neww.append(wi)
1181
+ break
1182
+ delta = new_delta
1183
+ Ndelta = Nnew_delta
1184
+ wi -= delta
1185
+ # If we run 100 iterations without a result, terminate.
1186
+ else:
1187
+ raise ConvergenceError(
1188
+ "Newton iteration fails to converge after %s iterations" % j
1189
+ )
1190
+ return neww
1191
+
1192
+ def _newton_iteration(self, z0, oldw, epsilon):
1193
+ r"""
1194
+ A non-vectorized Newton iteration procedure used for integration.
1195
+
1196
+ INPUT:
1197
+
1198
+ - ``z0`` -- complex number
1199
+
1200
+ - ``oldw`` -- a w-value which is presumed to be a guess of one of
1201
+ the w-values above ``z0``
1202
+
1203
+ - ``epsilon`` -- the minimum distance between the w-values divided by 3
1204
+
1205
+ OUTPUT:
1206
+
1207
+ A complex number, which should be a w-value above ``z0``.
1208
+
1209
+ However, if the Newton iteration exceeds the allotted attempts,
1210
+ or exits the ``epsilon`` ball, raises a convergence error.
1211
+
1212
+ EXAMPLES:
1213
+
1214
+ First, a trivial example where we guess exactly what the root is::
1215
+
1216
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1217
+ sage: R.<z,w> = QQ[]
1218
+ sage: f = w^2 - z^4 + 1
1219
+ sage: S = RiemannSurface(f)
1220
+ sage: z0 = S._vertices[0]
1221
+ sage: epsilon = 0.1
1222
+ sage: oldw = S.w_values(z0)[0]
1223
+ sage: neww = S._newton_iteration(z0,oldw,epsilon); neww # abs tol 0.00000001
1224
+ -0.934613146929672 + 2.01088055918363*I
1225
+
1226
+ Which should be exactly the same as the w-value we started with::
1227
+
1228
+ sage: oldw - neww # abs tol 0.00000001
1229
+ 0.000000000000000
1230
+
1231
+ Here is an example where we exit the epsilon bound. This approach is
1232
+ based on the homotopy continuation procedure which traverses along a
1233
+ path and attempts Newton iteration::
1234
+
1235
+ sage: g = z^3*w + w^3 + z
1236
+ sage: T = RiemannSurface(g)
1237
+ sage: z0 = T._vertices[2]*(0.9) + 0.3*I
1238
+ sage: epsilon = 0.5
1239
+ sage: oldw = T.w_values(T._vertices[2])[1]
1240
+ sage: T._newton_iteration(z0, oldw, epsilon)
1241
+ Traceback (most recent call last):
1242
+ ...
1243
+ ConvergenceError: Newton iteration escaped neighbourhood
1244
+ """
1245
+ F = self._fastcall_f
1246
+ dF = self._fastcall_dfdw
1247
+ prec = self._CC.prec()
1248
+ delta = F(z0, oldw) / dF(z0, oldw)
1249
+ Ndelta = delta.norm()
1250
+ neww = oldw - delta
1251
+ eps_squared = epsilon**2
1252
+ # it is possible in theory that Newton iteration fails to converge
1253
+ # without escaping. We catch this by capping the number of iterations
1254
+ # by 100
1255
+ for j in range(100):
1256
+ if (neww - oldw).norm() > eps_squared:
1257
+ raise ConvergenceError("Newton iteration escaped neighbourhood")
1258
+ new_delta = F(z0, neww) / dF(z0, neww)
1259
+ Nnew_delta = new_delta.norm()
1260
+ # If we found the root exactly, or if delta only affects half the digits and
1261
+ # stops getting smaller, we decide that we have converged.
1262
+ if (new_delta == 0) or (
1263
+ Nnew_delta >= Ndelta
1264
+ and Ndelta.sign_mantissa_exponent()[2] + prec
1265
+ < neww.norm().sign_mantissa_exponent()[2]
1266
+ ):
1267
+ return neww
1268
+ delta = new_delta
1269
+ Ndelta = Nnew_delta
1270
+ neww -= delta
1271
+ raise ConvergenceError("Newton iteration fails to converge")
1272
+
1273
+ @cached_method
1274
+ def upstairs_edges(self):
1275
+ r"""
1276
+ Compute the edgeset of the lift of the downstairs graph onto the Riemann
1277
+ surface.
1278
+
1279
+ OUTPUT:
1280
+
1281
+ An edgeset between vertices (i, j), where i corresponds to the i-th
1282
+ point in the Voronoi diagram vertices, and j is the j-th w-value
1283
+ associated with that point.
1284
+
1285
+ EXAMPLES::
1286
+
1287
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1288
+ sage: R.<z,w> = QQ[]
1289
+ sage: f = w^2 + z^3 - z^2
1290
+ sage: S = RiemannSurface(f)
1291
+ sage: edgeset = S.upstairs_edges()
1292
+ sage: len(edgeset) == S.degree*len(S.downstairs_edges())
1293
+ True
1294
+ sage: {(v[0],w[0]) for v,w in edgeset} == set(S.downstairs_edges())
1295
+ True
1296
+ """
1297
+ edgeset = []
1298
+ n = len(self._wvalues[0])
1299
+ # Lifts each edge individually.
1300
+ for e in self.downstairs_edges():
1301
+ i0, i1 = e
1302
+ d_edge = (self._vertices[i0], self._vertices[i1])
1303
+ # Epsilon for checking w-value later.
1304
+ val = self._wvalues[i1]
1305
+ epsilon = (
1306
+ min(
1307
+ abs(val[i] - val[n - j - 1])
1308
+ for i in range(n)
1309
+ for j in range(n - i - 1)
1310
+ )
1311
+ / 3
1312
+ )
1313
+ # Homotopy continuation along e.
1314
+ self._L[e] = self.homotopy_continuation(d_edge)
1315
+ homotopycont = self._L[e][-1][1]
1316
+ for i in range(len(homotopycont)):
1317
+ # Checks over the w-values of the next point to check which it is.
1318
+ for j in range(len(self._wvalues[i1])):
1319
+ if abs(homotopycont[i] - self._wvalues[i1][j]) < epsilon:
1320
+ # Once it finds the appropriate w-value, adds the edge.
1321
+ edgeset = edgeset + [[(i0, i), (i1, j)]]
1322
+ continue
1323
+ return edgeset
1324
+
1325
+ def _edge_permutation(self, edge):
1326
+ r"""
1327
+ Compute the permutation of the w-values above a point in the z-plane
1328
+ when moving along an edge in the Voronoi diagram.
1329
+
1330
+ INPUT:
1331
+
1332
+ - ``edge`` -- an edge on the Voronoi diagram
1333
+
1334
+ OUTPUT:
1335
+
1336
+ A permutation corresponding to how the roots interchange when moving
1337
+ along the edge.
1338
+
1339
+ EXAMPLES::
1340
+
1341
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1342
+ sage: R.<z,w> = QQ[]
1343
+ sage: f = z^3*w + w^3 + z
1344
+ sage: S = RiemannSurface(f)
1345
+
1346
+ Compute the edge permutation of (1, 2) on the Voronoi diagram::
1347
+
1348
+ sage: S._edge_permutation((1, 2))
1349
+ (0,2,1)
1350
+
1351
+ This indicates that while traversing along the direction of `(2, 9)`,
1352
+ the 2nd and 3rd layers of the Riemann surface are interchanging.
1353
+ """
1354
+ if edge in self.downstairs_edges():
1355
+ # find all upstairs edges that are lifts of the given
1356
+ # downstairs edge and store the corresponding indices at
1357
+ # start and end that label the branches upstairs.
1358
+ L = [
1359
+ (j0, j1)
1360
+ for ((i0, j0), (i1, j1)) in self.upstairs_edges()
1361
+ if edge == (i0, i1)
1362
+ ]
1363
+ # we should be finding exactly "degree" of these
1364
+ assert len(L) == self.degree
1365
+ # and as a corollary of how we construct them, the indices
1366
+ # at the start should be in order
1367
+ assert all(a == b[0] for a, b in enumerate(L))
1368
+ return self._Sn([j1 for j0, j1 in L])
1369
+ raise ValueError("edge not in Voronoi diagram")
1370
+
1371
+ @cached_method
1372
+ def edge_permutations(self) -> dict:
1373
+ r"""
1374
+ Compute the permutations of branches associated to each edge.
1375
+
1376
+ Over the vertices of the Voronoi decomposition around the branch locus,
1377
+ we label the fibres. By following along an edge, the lifts of the edge
1378
+ induce a permutation of that labelling.
1379
+
1380
+ OUTPUT:
1381
+
1382
+ A dictionary with as keys the edges of the Voronoi decomposition and as
1383
+ values the corresponding permutations.
1384
+
1385
+ EXAMPLES::
1386
+
1387
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1388
+ sage: R.<z,w> = QQ[]
1389
+ sage: f = w^2 + z^2+1
1390
+ sage: S = RiemannSurface(f)
1391
+ sage: S.edge_permutations()
1392
+ {(0, 2): (),
1393
+ (0, 4): (),
1394
+ (1, 2): (),
1395
+ (1, 3): (0,1),
1396
+ (1, 6): (),
1397
+ (2, 0): (),
1398
+ (2, 1): (),
1399
+ (2, 5): (0,1),
1400
+ (3, 1): (0,1),
1401
+ (3, 4): (),
1402
+ (4, 0): (),
1403
+ (4, 3): (),
1404
+ (5, 2): (0,1),
1405
+ (5, 7): (),
1406
+ (6, 1): (),
1407
+ (6, 7): (),
1408
+ (7, 5): (),
1409
+ (7, 6): ()}
1410
+ """
1411
+ D = {e: self._edge_permutation(e) for e in self.downstairs_edges()}
1412
+ for (a, b), p in list(D.items()):
1413
+ D[(b, a)] = p**(-1)
1414
+ return D
1415
+
1416
+ @cached_method
1417
+ def monodromy_group(self):
1418
+ r"""
1419
+ Compute local monodromy generators of the Riemann surface.
1420
+
1421
+ For each branch point, the local monodromy is encoded by a permutation.
1422
+ The permutations returned correspond to positively oriented loops around
1423
+ each branch point, with a fixed base point. This means the generators
1424
+ are properly conjugated to ensure that together they generate the global
1425
+ monodromy. The list has an entry for every finite point stored in
1426
+ ``self.branch_locus``, plus an entry for the ramification above infinity.
1427
+
1428
+ OUTPUT:
1429
+
1430
+ A list of permutations, encoding the local monodromy at each branch
1431
+ point.
1432
+
1433
+ EXAMPLES::
1434
+
1435
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1436
+ sage: R.<z, w> = QQ[]
1437
+ sage: f = z^3*w + w^3 + z
1438
+ sage: S = RiemannSurface(f)
1439
+ sage: G = S.monodromy_group(); G
1440
+ [(0,1,2), (0,1), (0,2), (1,2), (1,2), (1,2), (0,1), (0,2), (0,2)]
1441
+
1442
+ The permutations give the local monodromy generators for the branch
1443
+ points::
1444
+
1445
+ sage: list(zip(S.branch_locus + [unsigned_infinity], G)) #abs tol 0.0000001
1446
+ [(0.000000000000000, (0,1,2)),
1447
+ (-1.31362670141929, (0,1)),
1448
+ (-0.819032851784253 - 1.02703471138023*I, (0,2)),
1449
+ (-0.819032851784253 + 1.02703471138023*I, (1,2)),
1450
+ (0.292309440469772 - 1.28069133740100*I, (1,2)),
1451
+ (0.292309440469772 + 1.28069133740100*I, (1,2)),
1452
+ (1.18353676202412 - 0.569961265016465*I, (0,1)),
1453
+ (1.18353676202412 + 0.569961265016465*I, (0,2)),
1454
+ (Infinity, (0,2))]
1455
+
1456
+ We can check the ramification by looking at the cycle lengths and verify
1457
+ it agrees with the Riemann-Hurwitz formula::
1458
+
1459
+ sage: 2*S.genus-2 == -2*S.degree + sum(e-1 for g in G for e in g.cycle_type())
1460
+ True
1461
+ """
1462
+ n = len(self.branch_locus)
1463
+ G = Graph(self.downstairs_edges())
1464
+ # we get all the regions
1465
+ loops = [
1466
+ self.voronoi_diagram.regions[i][:]
1467
+ for i in self.voronoi_diagram.point_region
1468
+ ]
1469
+ # and construct their Voronoi centers as complex numbers
1470
+ centers = self.branch_locus + [
1471
+ self._CC(x, y) for x, y in self.voronoi_diagram.points[n:]
1472
+ ]
1473
+ for center, loop in zip(centers, loops):
1474
+ if -1 in loop:
1475
+ # for loops involving infinity we take the finite part of the path
1476
+ i = loop.index(-1)
1477
+ loop[:] = loop[i + 1 :] + loop[:i]
1478
+ else:
1479
+ # and for finite ones we close the paths
1480
+ loop.append(loop[0])
1481
+ # we make sure the loops are positively oriented wrt. their center
1482
+ v0 = self._vertices[loop[0]]
1483
+ v1 = self._vertices[loop[1]]
1484
+ M = Matrix([list(v0 - center), list(v1 - center)])
1485
+ if M.det() < 0:
1486
+ loop.reverse()
1487
+
1488
+ # we stitch together the paths that are part of loops through
1489
+ # infinity. There should be a unique way of doing so.
1490
+ inf_loops = loops[n:]
1491
+ inf_path = inf_loops.pop()
1492
+ while inf_loops:
1493
+ inf_path += (inf_loops.pop())[1:]
1494
+ assert inf_path[0] == inf_path[-1]
1495
+
1496
+ loops = loops[:n]
1497
+ loops.append(inf_path)
1498
+
1499
+ P0 = loops[0][0]
1500
+ monodromy_gens = []
1501
+ edge_perms = self.edge_permutations()
1502
+ SG = self._Sn
1503
+ for c in loops:
1504
+ to_loop = G.shortest_path(P0, c[0])
1505
+ to_loop_perm = SG.prod(
1506
+ edge_perms[(to_loop[i], to_loop[i + 1])]
1507
+ for i in range(len(to_loop) - 1)
1508
+ )
1509
+ c_perm = SG.prod(edge_perms[(c[i], c[i + 1])] for i in range(len(c) - 1))
1510
+ monodromy_gens.append(to_loop_perm * c_perm * ~to_loop_perm)
1511
+ return monodromy_gens
1512
+
1513
+ @cached_method
1514
+ def homology_basis(self):
1515
+ r"""
1516
+ Compute the homology basis of the Riemann surface.
1517
+
1518
+ OUTPUT:
1519
+
1520
+ A list of paths `L = [P_1, \dots, P_n]`. Each path `P_i` is of the form
1521
+ `(k, [p_1 ... p_m, p_1])`, where `k` is the number of times to traverse
1522
+ the path (if negative, to traverse it backwards), and the `p_i` are
1523
+ vertices of the upstairs graph.
1524
+
1525
+ EXAMPLES:
1526
+
1527
+ In this example, there are two paths that form the homology basis::
1528
+
1529
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1530
+ sage: R.<z,w> = QQ[]
1531
+ sage: g = w^2 - z^4 + 1
1532
+ sage: S = RiemannSurface(g)
1533
+ sage: S.homology_basis() # random
1534
+ [[(1, [(3, 1), (5, 0), (9, 0), (10, 0), (2, 0), (4, 0),
1535
+ (7, 1), (10, 1), (3, 1)])],
1536
+ [(1, [(8, 0), (6, 0), (7, 0), (10, 0), (2, 0), (4, 0),
1537
+ (7, 1), (10, 1), (9, 1), (8, 0)])]]
1538
+
1539
+ In order to check that the answer returned above is reasonable, we
1540
+ test some basic properties. We express the faces of the downstairs graph
1541
+ as ZZ-linear combinations of the edges and check that the projection
1542
+ of the homology basis upstairs projects down to independent linear
1543
+ combinations of an even number of faces::
1544
+
1545
+ sage: # needs planarity
1546
+ sage: dg = S.downstairs_graph()
1547
+ sage: edges = dg.edges(sort=True)
1548
+ sage: E = ZZ^len(edges)
1549
+ sage: edge_to_E = { e[:2]: E.gen(i) for i,e in enumerate(edges)}
1550
+ sage: edge_to_E.update({ (e[1],e[0]): -E.gen(i) for i,e in enumerate(edges)})
1551
+ sage: face_span = E.submodule([sum(edge_to_E[e] for e in f) for f in dg.faces()])
1552
+ sage: def path_to_E(path):
1553
+ ....: k,P = path
1554
+ ....: return k*sum(edge_to_E[(P[i][0],P[i+1][0])] for i in range(len(P)-1))
1555
+ sage: hom_basis = [sum(path_to_E(p) for p in loop) for loop in S.homology_basis()]
1556
+ sage: face_span.submodule(hom_basis).rank()
1557
+ 2
1558
+ sage: [sum(face_span.coordinate_vector(b))%2 for b in hom_basis]
1559
+ [0, 0]
1560
+ """
1561
+ if self.genus == 0:
1562
+ return []
1563
+
1564
+ cycles = self.upstairs_graph().cycle_basis()
1565
+ # Computing the Gram matrix.
1566
+ cn = len(cycles)
1567
+ # Forming a list of lists of zeroes.
1568
+ # Later this will be converted into a matrix.
1569
+ intersectionprod = [[0] * cn for _ in cycles]
1570
+
1571
+ # as it turns out, in extreme examples argument computation
1572
+ # can be quite dominant so we cache this (since we may end up
1573
+ # using these values multiple times)
1574
+ direction_cache = {}
1575
+
1576
+ def direction(center, neighbour):
1577
+ k = (center, neighbour)
1578
+ if k not in direction_cache:
1579
+ theta = (self._vertices[neighbour] - self._vertices[center]).argument()
1580
+ direction_cache[k] = theta
1581
+ return theta
1582
+ else:
1583
+ return direction_cache[k]
1584
+
1585
+ # This loop will start at the entry (0,1), and proceed along the row up
1586
+ # til (0,cn-1).
1587
+ # Then it will go to entry (1,2), and proceed along the row, etc.
1588
+ for i in range(1, cn):
1589
+ for j in range(i):
1590
+ # Initializing the intersection product value.
1591
+ intsum = 0
1592
+ # Intersection of the edges
1593
+ intsec = set(cycles[i]).intersection(set(cycles[j]))
1594
+ for v in intsec:
1595
+ # Get indices of the vertex in the cycles.
1596
+ i0 = cycles[i].index(v)
1597
+ i1 = cycles[j].index(v)
1598
+ # Get the complex value of the vertex v.
1599
+ center = cycles[i][i0][0]
1600
+
1601
+ # We are in the following situation:
1602
+ # We have two paths a_in->v->a_out and
1603
+ # b_in->v->b_out intersecting. We say they
1604
+ # are "positively oriented" if the a-path
1605
+ # and the b-path are oriented as the x and y axes, i.e.,
1606
+ # if, when we walk around v in counter-clockwise direction,
1607
+ # we encounter a_in,b_in,a_out,b_out.
1608
+
1609
+ # we can also have that b_in and/or b_out overlaps with
1610
+ # a_in and/or a_out. If we just score the orientation of
1611
+ # b_in and b_out individually, we can deal with this
1612
+ # by just ignoring the overlapping vertex. The "half"
1613
+ # score will be appropriately complemented at one of the
1614
+ # next vertices.
1615
+
1616
+ a_in = cycles[i][i0 - 1][0]
1617
+ a_out = cycles[i][(i0 + 1) % len(cycles[i])][0]
1618
+ b_in = cycles[j][i1 - 1][0]
1619
+ b_out = cycles[j][(i1 + 1) % len(cycles[j])][0]
1620
+
1621
+ # we can get the angles (and hence the rotation order)
1622
+ # by taking the arguments of the differences.
1623
+
1624
+ a_in_arg = direction(center, a_in)
1625
+ a_out_arg = direction(center, a_out)
1626
+ b_in_arg = direction(center, b_in)
1627
+ b_out_arg = direction(center, b_out)
1628
+
1629
+ # we make sure to test overlap on the indices, so no rounding
1630
+ # problems occur with that.
1631
+
1632
+ if (b_in != a_in) and (b_in != a_out):
1633
+ if (
1634
+ (a_in_arg < b_in_arg < a_out_arg)
1635
+ or (b_in_arg < a_out_arg < a_in_arg)
1636
+ or (a_out_arg < a_in_arg < b_in_arg)
1637
+ ):
1638
+ intsum += 1
1639
+ elif (
1640
+ (a_out_arg < b_in_arg < a_in_arg)
1641
+ or (b_in_arg < a_in_arg < a_out_arg)
1642
+ or (a_in_arg < a_out_arg < b_in_arg)
1643
+ ):
1644
+ intsum -= 1
1645
+ else:
1646
+ raise RuntimeError("impossible edge orientation")
1647
+ if (b_out != a_in) and (b_out != a_out):
1648
+ if (
1649
+ (a_in_arg < b_out_arg < a_out_arg)
1650
+ or (b_out_arg < a_out_arg < a_in_arg)
1651
+ or (a_out_arg < a_in_arg < b_out_arg)
1652
+ ):
1653
+ intsum -= 1
1654
+ elif (
1655
+ (a_out_arg < b_out_arg < a_in_arg)
1656
+ or (b_out_arg < a_in_arg < a_out_arg)
1657
+ or (a_in_arg < a_out_arg < b_out_arg)
1658
+ ):
1659
+ intsum += 1
1660
+ else:
1661
+ raise RuntimeError("impossible edge orientation")
1662
+ assert (intsum % 2) == 0
1663
+ intsum = intsum // 2
1664
+ intersectionprod[i][j] = intsum
1665
+ # Skew Symmetry
1666
+ intersectionprod[j][i] = -intsum
1667
+ Gmatrix = Matrix(intersectionprod)
1668
+ G_normalized, P = Gmatrix.symplectic_form()
1669
+ if G_normalized.rank() != 2 * self.genus:
1670
+ raise RuntimeError("rank of homology pairing mismatches twice stored genus")
1671
+ # Define the cycle sets.
1672
+ acycles = [[] for i in range(self.genus)]
1673
+ bcycles = [[] for i in range(self.genus)]
1674
+ # There are g a and b cycles.
1675
+ for i in range(self.genus):
1676
+ # Range over the size of the Gram matrix.
1677
+ for j in range(cn):
1678
+ # Forms the acycles and bcycles. If the entry in the
1679
+ # transformation matrix is nonzero, it adds the coefficient at
1680
+ # that entry, and the corresponding cycle. (also, forms it
1681
+ # into a loop)
1682
+ if P[i][j] != 0:
1683
+ acycles[i] += [(P[i][j], list(cycles[j]) + [cycles[j][0]])]
1684
+ if P[self.genus + i][j] != 0:
1685
+ bcycles[i] += [
1686
+ (P[self.genus + i][j], list(cycles[j]) + [cycles[j][0]])
1687
+ ]
1688
+ return acycles + bcycles
1689
+
1690
+ def make_zw_interpolator(self, upstairs_edge, initial_continuation=None):
1691
+ r"""
1692
+ Given a downstairs edge for which continuation data has been initialised,
1693
+ return a function that computes `z(t), w(t)` , where `t` in `[0,1]` is a
1694
+ parametrization of the edge.
1695
+
1696
+ INPUT:
1697
+
1698
+ - ``upstairs_edge`` -- tuple ``((z_start, sb), (z_end,))`` giving the
1699
+ start and end values of the base coordinate along the straight-line
1700
+ path and the starting branch
1701
+ - ``initial_continuation`` -- list (optional); output of
1702
+ ``homotopy_continuation`` initialising the continuation data
1703
+
1704
+ OUTPUT:
1705
+
1706
+ A tuple ``(g, d)``, where ``g`` is the function that computes the interpolation
1707
+ along the edge and ``d`` is the difference of the z-values of the end and
1708
+ start point.
1709
+
1710
+ EXAMPLES::
1711
+
1712
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1713
+ sage: R.<z,w> = QQ[]
1714
+ sage: f = w^2 - z^4 + 1
1715
+ sage: S = RiemannSurface(f)
1716
+ sage: _ = S.homology_basis()
1717
+ sage: u_edge = [(0, 0), (1, 0)]
1718
+ sage: d_edge = tuple(u[0] for u in u_edge)
1719
+ sage: u_edge = [(S._vertices[i], j) for i, j in u_edge]
1720
+ sage: initial_continuation = S._L[d_edge]
1721
+ sage: g, d = S.make_zw_interpolator(u_edge, initial_continuation)
1722
+ sage: all(f(*g(i*0.1)).abs() < 1e-13 for i in range(10))
1723
+ True
1724
+ sage: abs((g(1)[0]-g(0)[0]) - d) < 1e-13
1725
+ True
1726
+
1727
+ .. NOTE::
1728
+
1729
+ The interpolator returned by this method can effectively hang if
1730
+ either ``z_start`` or ``z_end`` are branchpoints. In these situations
1731
+ it is better to take a different approach rather than continue to use
1732
+ the interpolator.
1733
+ """
1734
+ downstairs_edge = tuple(u[0] for u in upstairs_edge)
1735
+ z_start, z_end = downstairs_edge
1736
+ z_start = self._CC(z_start)
1737
+ z_end = self._CC(z_end)
1738
+ if initial_continuation is None:
1739
+ initial_continuation = self.homotopy_continuation(downstairs_edge)
1740
+ currL = initial_continuation
1741
+ windex = upstairs_edge[0][1]
1742
+
1743
+ def w_interpolate(t):
1744
+ if t < 0 or t > 1:
1745
+ raise ValueError("t outside path range")
1746
+ if t == 0:
1747
+ return z_start, currL[0][1][windex]
1748
+ elif t == 1:
1749
+ return z_end, currL[-1][1][windex]
1750
+ while True:
1751
+ i = bisect(currL, t)
1752
+ t1, w1, epsilon = currL[i]
1753
+ w1 = w1[windex]
1754
+ t2, w2, _ = currL[i + 1]
1755
+ w2 = w2[windex]
1756
+ z0 = (1 - t) * z_start + t * z_end
1757
+ w0 = self._CC(((t2 - t) * w1 + (t - t1) * w2) / (t2 - t1))
1758
+ try:
1759
+ desired_result = self._newton_iteration(z0, w0, epsilon)
1760
+ except ConvergenceError:
1761
+ pass
1762
+ else:
1763
+ return z0, desired_result
1764
+ # If we did not succeed, we insert a new point in our interpolation list
1765
+ tnew = t
1766
+ while True:
1767
+ tnew = (t1 + tnew) / 2
1768
+ znew = (1 - tnew) * z_start + tnew * z_end
1769
+ try:
1770
+ neww1 = self._determine_new_w(znew, currL[i][1], epsilon)
1771
+ except ConvergenceError:
1772
+ pass
1773
+ else:
1774
+ # When *no* ConvergenceError is raised, we have succeeded and we can exit
1775
+ break
1776
+ # once the loop has succeeded we insert our new value
1777
+ t1 = tnew
1778
+ currL.insert(i + 1, (t1, neww1, epsilon))
1779
+
1780
+ return w_interpolate, (z_end - z_start)
1781
+
1782
+ def simple_vector_line_integral(self, upstairs_edge, differentials):
1783
+ r"""
1784
+ Perform vectorized integration along a straight path.
1785
+
1786
+ INPUT:
1787
+
1788
+ - ``upstairs_edge`` -- tuple; either a pair of integer tuples
1789
+ corresponding to an edge of the upstairs graph, or a tuple
1790
+ ``((z_start, sb), (z_end, ))`` as in the input of
1791
+ ``make_zw_interpolator``
1792
+
1793
+ - ``differentials`` -- list of polynomials; a polynomial `g`
1794
+ represents the differential `g(z,w)/(df/dw) dz` where `f(z,w)=0` is
1795
+ the equation defining the Riemann surface
1796
+
1797
+ OUTPUT: a complex number, the value of the line integral
1798
+
1799
+ EXAMPLES::
1800
+
1801
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1802
+ sage: R.<z,w> = QQ[]
1803
+ sage: f = w^2 - z^4 + 1
1804
+ sage: S = RiemannSurface(f); S
1805
+ Riemann surface defined by polynomial f = -z^4 + w^2 + 1 = 0, with 53 bits of precision
1806
+
1807
+ Since we make use of data from homotopy continuation, we need to compute
1808
+ the necessary data::
1809
+
1810
+ sage: M = S.riemann_matrix()
1811
+ sage: differentials = S.cohomology_basis()
1812
+ sage: S.simple_vector_line_integral([(0, 0), (1, 0)], differentials) #abs tol 0.00000001
1813
+ (1.14590610929717e-16 - 0.352971844594760*I)
1814
+
1815
+ .. NOTE::
1816
+
1817
+ Uses data that :meth:`homology_basis` initializes, and may give incorrect
1818
+ values if :meth:`homology_basis` has not initialized them. In practice
1819
+ it is more efficient to set ``differentials`` to a fast-callable version
1820
+ of differentials to speed up execution.
1821
+ """
1822
+ d_edge = tuple(u[0] for u in upstairs_edge)
1823
+ # Using a try-catch here allows us to retain a certain amount of back compatibility
1824
+ # for users.
1825
+ try:
1826
+ initial_continuation = self._L[d_edge]
1827
+ upstairs_edge = (
1828
+ (self._vertices[d_edge[0]], upstairs_edge[0][1]),
1829
+ (self._vertices[d_edge[1]],),
1830
+ )
1831
+ except KeyError:
1832
+ initial_continuation = self.homotopy_continuation(d_edge)
1833
+ w_of_t, Delta_z = self.make_zw_interpolator(upstairs_edge, initial_continuation)
1834
+ V = VectorSpace(self._CC, len(differentials))
1835
+
1836
+ def integrand(t):
1837
+ zt, wt = w_of_t(t)
1838
+ dfdwt = self._fastcall_dfdw(zt, wt)
1839
+ return V([omega(zt, wt) / dfdwt for omega in differentials])
1840
+
1841
+ return integrate_vector(integrand, self._prec) * Delta_z
1842
+
1843
+ def cohomology_basis(self, option=1):
1844
+ r"""
1845
+ Compute the cohomology basis of this surface.
1846
+
1847
+ INPUT:
1848
+
1849
+ - ``option`` -- presently, this routine uses Singular's ``adjointIdeal``
1850
+ and passes the ``option`` parameter on. Legal values are 1, 2, 3 ,4,
1851
+ where 1 is the default. See the Singular documentation for the
1852
+ meaning. The backend for this function may change, and support for
1853
+ this parameter may disappear.
1854
+
1855
+ OUTPUT:
1856
+
1857
+ This returns a list of polynomials `g` representing the holomorphic
1858
+ differentials `g/(df/dw) dz`, where `f(z,w)=0` is the equation
1859
+ specifying the Riemann surface.
1860
+
1861
+ EXAMPLES::
1862
+
1863
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1864
+ sage: R.<z,w> = QQ[]
1865
+ sage: f = z^3*w + w^3 + z
1866
+ sage: S = RiemannSurface(f)
1867
+ sage: S.cohomology_basis()
1868
+ [1, w, z]
1869
+ """
1870
+ if self.genus == 0:
1871
+ self._differentials = []
1872
+ return self._differentials # [0]
1873
+ if self._differentials is None:
1874
+ # Computes differentials from the adjointIdeal using Singular
1875
+ # First we homogenize
1876
+ base = self.f.base_ring()
1877
+ # It's important we use a degree ordering; see below.
1878
+ R = self._R
1879
+ k = PolynomialRing(base, names='Z,W,U', order='degrevlex')
1880
+ dehom = k.Hom(R)([R.gen(0), R.gen(1), R.one()])
1881
+ fnew = self.f(k.gen(0) / k.gen(2), k.gen(1) / k.gen(2)).numerator()
1882
+
1883
+ # We load the relevant functionality into singularlib
1884
+ import sage.libs.singular.function_factory
1885
+
1886
+ sage.libs.singular.function_factory.lib("paraplanecurves.lib")
1887
+ adjointIdeal = sage.libs.singular.function.singular_function("adjointIdeal")
1888
+ libsing_options = sage.libs.singular.option.LibSingularVerboseOptions()
1889
+
1890
+ # We compute the adjoint ideal (note we need to silence "redefine")
1891
+ redef_save = libsing_options["redefine"]
1892
+ try:
1893
+ libsing_options["redefine"] = False
1894
+ J = adjointIdeal(fnew, option)
1895
+ finally:
1896
+ libsing_options["redefine"] = redef_save
1897
+
1898
+ # We are interested in the (degree-3) subspace of the adjoint ideal.
1899
+ # We compute this by intersecting with (Z,W,U)^(degree-3). Then the
1900
+ # lowest degree generators are a basis of the relevant subspace.
1901
+ d = fnew.total_degree()
1902
+ J2 = k.ideal(J).intersection(
1903
+ k.ideal([k.gen(0), k.gen(1), k.gen(2)])**(d - 3)
1904
+ )
1905
+ generators = [dehom(c) for c in J2.gens() if c.degree() == d - 3]
1906
+ if len(generators) != self.genus:
1907
+ raise ValueError(
1908
+ "computed regular differentials do not match stored genus"
1909
+ )
1910
+ self._differentials = generators
1911
+ return self._differentials
1912
+
1913
+ def _bounding_data(self, differentials, exact=False):
1914
+ r"""
1915
+ Compute the data required to bound a differential on a circle.
1916
+
1917
+ Given a differential, one can bound it on a circular region using its
1918
+ derivative and its minimal polynomial (in the coordinate of the base).
1919
+
1920
+ INPUT:
1921
+
1922
+ - ``differentials`` -- list of polynomials in ``self._R`` giving
1923
+ the numerators of the differentials, as per the output of
1924
+ :meth:`cohomology_basis`
1925
+
1926
+ - ``exact`` -- boolean (default: ``False``); whether to return the minimal
1927
+ polynomials over the exact base ring, or over ``self._CC``
1928
+
1929
+ OUTPUT:
1930
+
1931
+ A tuple ``(Rzg, [(g, dgdz, F, a0_info), ...])`` where each element of
1932
+ the list corresponds to an element of ``differentials``. Introducing the
1933
+ notation ``RBzg = PolynomialRing(self._R, ['z','g'])`` and
1934
+ ``CCzg = PolynomialRing(self._CC, ['z','g'])``, we have that:
1935
+
1936
+ - ``Rzg`` is either ``RBzg`` or ``CCzg`` depending on the value of
1937
+ ``exact``,
1938
+ - ``g`` is the full rational function in ``self._R.fraction_field()``
1939
+ giving the differential,
1940
+ - ``dgdz`` is the derivative of ``g`` with respect to ``self._R.gen(0)``,
1941
+ written in terms of ``self._R.gen(0)`` and ``g``, hence laying in
1942
+ ``RBzg``,
1943
+ - ``F`` is the minimal polynomial of ``g`` over ``self._R.gen(0)``,
1944
+ laying in the polynomial ring ``Rzg``,
1945
+ - ``a0_info`` is a tuple ``(lc, roots)`` where ``lc`` and ``roots`` are
1946
+ the leading coefficient and roots of the polynomial in ``CCzg.gen(0)``
1947
+ that is the coefficient of the term of ``F`` of highest degree in
1948
+ ``CCzg.gen(1)``.
1949
+
1950
+ EXAMPLES::
1951
+
1952
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
1953
+ sage: R.<x,y> = QQ[]
1954
+ sage: f = y^2 - x^3 + 1
1955
+ sage: S = RiemannSurface(f)
1956
+ sage: differentials = S.cohomology_basis(); differentials
1957
+ [1]
1958
+ sage: S._dfdw
1959
+ 2*y
1960
+ sage: S._bounding_data(differentials)
1961
+ (Multivariate Polynomial Ring in z, g over Complex Field with 53 bits of precision,
1962
+ [(1/(2*y),
1963
+ (-3*z^2*g)/(2*z^3 - 2),
1964
+ z^3*g^2 - g^2 - 0.250000000000000,
1965
+ (1.00000000000000,
1966
+ [1.00000000000000,
1967
+ -0.500000000000000 - 0.866025403784439*I,
1968
+ -0.500000000000000 + 0.866025403784439*I]))])
1969
+
1970
+ Note that ``self._bounding_data(self._cohomology_basis(), exact=True)``
1971
+ is stored in ``self._cohomology_basis_bounding_data``::
1972
+
1973
+ sage: S._cohomology_basis_bounding_data
1974
+ (Multivariate Polynomial Ring in z, g over Rational Field,
1975
+ [(1/(2*y),
1976
+ (-3*z^2*g)/(2*z^3 - 2),
1977
+ z^3*g^2 - g^2 - 1/4,
1978
+ (1.00000000000000,
1979
+ [1.00000000000000,
1980
+ -0.500000000000000 - 0.866025403784439*I,
1981
+ -0.500000000000000 + 0.866025403784439*I]))])
1982
+ """
1983
+ # This copies previous work by NB, outputting the zipped list required
1984
+ # for a certified line integral.
1985
+ RB = self._R.base_ring()
1986
+ P = PolynomialRing(RB, "Z")
1987
+ k = P.fraction_field()
1988
+ KP = PolynomialRing(k, "W") # W->fraction field
1989
+ fZW = self.f(P.gen(0), KP.gen(0))
1990
+ L = k.extension(fZW, "Wb")
1991
+ dfdw_L = self._dfdw(P.gen(0), L.gen(0))
1992
+ integrand_list = [h / self._dfdw for h in differentials]
1993
+ # minpoly_univ gives the minimal polynomial for h, in variable x, with
1994
+ # coefficients given by polynomials with coefficients in P (i.e.
1995
+ # rational polynomials in Z).
1996
+ minpoly_univ = [
1997
+ (h(P.gen(0), L.gen(0)) / dfdw_L).minpoly().numerator()
1998
+ for h in differentials
1999
+ ]
2000
+ RBzg = PolynomialRing(RB, ["z", "g"])
2001
+ # The following line changes the variables in these minimal polynomials
2002
+ # as Z -> z, x -> G, then evaluates at G = QQzg.gens(1) ( = g )
2003
+ RBzgG = PolynomialRing(RBzg, "G")
2004
+ minpoly_list = [
2005
+ RBzgG([c(RBzg.gen(0)) for c in list(h)])(RBzg.gen(1)) for h in minpoly_univ
2006
+ ]
2007
+ # h(z,g)=0 --> dg/dz = - dhdz/dhdg
2008
+ dgdz_list = [
2009
+ -h.derivative(RBzg.gen(0)) / h.derivative(RBzg.gen(1)) for h in minpoly_list
2010
+ ]
2011
+
2012
+ CCzg = PolynomialRing(self._CC, ["z", "g"])
2013
+ CCminpoly_list = [CCzg(h) for h in minpoly_list]
2014
+
2015
+ a0_list = [P(h.leading_coefficient()) for h in minpoly_univ]
2016
+ # Note that because the field over which the Riemann surface is defined
2017
+ # is embedded into CC, it has characteristic 0, and so we know the
2018
+ # irreducible factors are all separable, i.e. the roots have multiplicity
2019
+ # one.
2020
+ a0_info = [
2021
+ (
2022
+ self._CC(a0.leading_coefficient()),
2023
+ flatten(
2024
+ [
2025
+ self._CCz(F).roots(multiplicities=False) * m
2026
+ for F, m in a0.factor()
2027
+ ]
2028
+ ),
2029
+ )
2030
+ for a0 in a0_list
2031
+ ]
2032
+ if exact:
2033
+ return RBzg, list(zip(integrand_list, dgdz_list, minpoly_list, a0_info))
2034
+ else:
2035
+ return CCzg, list(zip(integrand_list, dgdz_list, CCminpoly_list, a0_info))
2036
+
2037
+ def rigorous_line_integral(self, upstairs_edge, differentials, bounding_data):
2038
+ r"""
2039
+ Perform vectorized integration along a straight path.
2040
+
2041
+ Using the error bounds for Gauss-Legendre integration found in [Neu2018]_
2042
+ and a method for bounding an algebraic integrand on a circular domains
2043
+ using Cauchy's form of the remainder in Taylor approximation coupled to
2044
+ Fujiwara's bound on polynomial roots (see Bruin-DisneyHogg-Gao, in
2045
+ preparation), this method calculates (semi-)rigorously the integral of a
2046
+ list of differentials along an edge of the upstairs graph.
2047
+
2048
+ INPUT:
2049
+
2050
+ - ``upstairs_edge`` -- tuple; either a pair of integer tuples
2051
+ corresponding to an edge of the upstairs graph, or a tuple
2052
+ ``((z_start, sb), (z_end, ))`` as in the input of
2053
+ ``make_zw_interpolator``
2054
+
2055
+ - ``differentials`` -- list of polynomials; a polynomial `g`
2056
+ represents the differential `g(z,w)/(df/dw) dz` where `f(z,w)=0` is
2057
+ the equation defining the Riemann surface
2058
+
2059
+ - ``bounding_data`` -- tuple containing the data required for bounding
2060
+ the integrands. This should be in the form of the output from
2061
+ :meth:`_bounding_data`.
2062
+
2063
+ OUTPUT: a complex number, the value of the line integral
2064
+
2065
+ EXAMPLES::
2066
+
2067
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2068
+ sage: R.<z,w> = QQ[]
2069
+ sage: f = w^2 - z^4 + 1
2070
+ sage: S = RiemannSurface(f); S
2071
+ Riemann surface defined by polynomial f = -z^4 + w^2 + 1 = 0, with 53 bits of precision
2072
+
2073
+ Since we make use of data from homotopy continuation, we need to compute
2074
+ the necessary data::
2075
+
2076
+ sage: _ = S.homology_basis()
2077
+ sage: differentials = S.cohomology_basis()
2078
+ sage: bounding_data = S._bounding_data(differentials)
2079
+ sage: S.rigorous_line_integral([(0,0), (1,0)], differentials, bounding_data) # abs tol 1e-10
2080
+ (1.80277751848459e-16 - 0.352971844594760*I)
2081
+
2082
+ .. NOTE::
2083
+
2084
+ Uses data that ``homology_basis`` initializes, and may give incorrect
2085
+ values if :meth:`homology_basis` has not initialized them.
2086
+
2087
+ Note also that the data of the differentials is contained within
2088
+ ``bounding_data``. It is, however, still advantageous to have this
2089
+ be a separate argument, as it lets the user supply a fast-callable
2090
+ version of the differentials, to significantly speed up execution
2091
+ of the integrand calls, and not have to re-calculate these
2092
+ fast-callables for every run of the function. This is also the benefit
2093
+ of representing the differentials as a polynomial over a known
2094
+ common denominator.
2095
+
2096
+ .. TODO::
2097
+
2098
+ Note that bounding_data contains the information of the integrands,
2099
+ so one may want to check for consistency between ``bounding_data``
2100
+ and ``differentials``. If so one would not want to do so at the
2101
+ expense of speed.
2102
+
2103
+ Moreover, the current implementation bounds along a line by
2104
+ splitting it up into segments, each of which can be covered entirely
2105
+ by a single circle, and then placing inside that the ellipse
2106
+ required to bound as per [Neu2018]_. This is reliably more efficient
2107
+ than the heuristic method, especially in poorly-conditioned cases
2108
+ where discriminant points are close together around the edges, but
2109
+ in the case where the branch locus is well separated, it can require
2110
+ slightly more nodes than necessary. One may want to include a method
2111
+ here to transition in this regime to an algorithm that covers the
2112
+ entire line with one ellipse, then bounds along that ellipse with
2113
+ multiple circles.
2114
+ """
2115
+ # Note that this, in its current formalism, makes no check that bounding
2116
+ # data at all corresponds to the differentials given. The onus is then
2117
+ # on the design of other functions which use it.
2118
+
2119
+ # CCzg is required to be known as we need to know the ring which the minpolys
2120
+ # lie in.
2121
+ CCzg, bounding_data_list = bounding_data
2122
+ CCz = CCzg.univariate_ring(CCzg.gen(1)).base_ring()
2123
+
2124
+ d_edge = tuple(u[0] for u in upstairs_edge)
2125
+ # Using a try-catch here allows us to retain a certain amount of back
2126
+ # compatibility for users.
2127
+ try:
2128
+ initial_continuation = self._L[d_edge]
2129
+ upstairs_edge = (
2130
+ (self._vertices[d_edge[0]], upstairs_edge[0][1]),
2131
+ (self._vertices[d_edge[1]],),
2132
+ )
2133
+ except KeyError:
2134
+ initial_continuation = self.homotopy_continuation(d_edge)
2135
+
2136
+ zwt, z1_minus_z0 = self.make_zw_interpolator(
2137
+ upstairs_edge, initial_continuation
2138
+ )
2139
+ z0 = zwt(0)[0]
2140
+ z1 = zwt(1)[0]
2141
+
2142
+ # list of (centre, radius) pairs that still need to be processed
2143
+ # None is a sentinel value to indicate that the minimum number of
2144
+ # nodes required to integrate on the corresponding segment within
2145
+ # the required error tolerance is not yet known.
2146
+ ball_stack = [(self._RR(1 / 2), self._RR(1 / 2), None)]
2147
+ alpha = self._RR(912 / 1000)
2148
+ # alpha set manually for scaling purposes. Basic benchmarking shows
2149
+ # that ~0.9 is a sensible value.
2150
+ E_global = self._RR(2)**(-self._prec + 3)
2151
+
2152
+ # Output will iteratively store the output of the integral.
2153
+ V = VectorSpace(self._CC, len(differentials))
2154
+ output = V(0)
2155
+
2156
+ # The purpose of this loop is as follows: We know we will be using
2157
+ # Gauss-Legendre quadrature to do the integral, and results from [Neu2018]_
2158
+ # tell us an upper bound on the number of nodes required to achieve a
2159
+ # given error bound for this quadrature, provided we have a bound for
2160
+ # the integrand on a certain ellipse in the complex plane. The method
2161
+ # developed by Bruin and Gao that uses Cauchy and Fujiwara can bound an
2162
+ # algebraic integrand on a circular region. Hence we need a way to change
2163
+ # from bounding with an ellipse to bounding with a circle. The size of
2164
+ # these circles will be constrained by the distance to the nearest point
2165
+ # where the integrand blows up, i.e. the nearest branchpoint. Basic
2166
+ # benchmarking showed that it was in general a faster method to split
2167
+ # the original line segment into multiple smaller line segments, and
2168
+ # compute the contribution from each of the line segments bounding with
2169
+ # a single circle, the benefits mainly coming when the curve is poorly
2170
+ # conditioned s.t. the branch points are close together. The following
2171
+ # loop does exactly this, repeatedly bisecting a segment if it is not
2172
+ # possible to cover it entirely in a ball which encompasses an appropriate
2173
+ # ellipse.
2174
+ def local_N(ct, rt):
2175
+ cz = (1 - ct) * z0 + ct * z1 # This is the central z-value of our ball.
2176
+ distances = [(cz - b).abs() for b in self.branch_locus]
2177
+ rho_z = min(distances)
2178
+ rho_t = rho_z / (z1_minus_z0).abs()
2179
+ rho_t = alpha * rho_t + (1 - alpha) * rt # sqrt(rho_t*rt) could also work
2180
+ rho_z = rho_t * (z1_minus_z0).abs()
2181
+ delta_z = (alpha * rho_t + (1 - alpha) * rt) * (z1_minus_z0).abs()
2182
+ # delta_z and delta_z^2 / (rho_z * (rho_z - delta_z)) are the two
2183
+ # prefactors that occur in the computation of the magnitude bound
2184
+ # M. delta_z should never be infinite, but the second factor could
2185
+ # be if rho_z - delta_z is 0. Mathematically it would never be 0
2186
+ # as we ensure rho_t > rt before running local_N, but the
2187
+ # floating point operations can ruin this.
2188
+ # The second prefactor is actually homogeneous in
2189
+ # z1_minus_z0.abs(), so we shall compute this factor without those
2190
+ # multiplications as a function of rho_t / rt which should thus be
2191
+ # more resistance to floating-point errors.
2192
+ pf2 = (alpha + (1 - alpha) * (rt / rho_t))**2 / (
2193
+ (1 - alpha) * (1 - rt / rho_t)
2194
+ )
2195
+ expr = (
2196
+ rho_t / rt + ((rho_t / rt)**2 - 1).sqrt()
2197
+ ) # Note this is really exp(arcosh(rho_t/rt))
2198
+ Ni = 3
2199
+ cw = zwt(ct)[1]
2200
+ for g, dgdz, minpoly, (a0lc, a0roots) in bounding_data_list:
2201
+ z_1 = a0lc.abs() * prod((cz - r).abs() - rho_z for r in a0roots)
2202
+ n = minpoly.degree(CCzg.gen(1))
2203
+ ai_new = [
2204
+ CCz(minpoly.coefficient({CCzg.gen(1): i}))(z=cz + self._CCz.gen(0))
2205
+ for i in range(n)
2206
+ ]
2207
+ ai_pos = [self._RRz([c.abs() for c in h.list()]) for h in ai_new]
2208
+ m = [a(rho_z) / z_1 for a in ai_pos]
2209
+ l = len(m)
2210
+ M_tilde = 2 * max(
2211
+ (m[i].abs())**(1 / self._RR(l - i)) for i in range(l)
2212
+ )
2213
+ cg = g(cz, cw)
2214
+ cdgdz = dgdz(cz, cg)
2215
+ M = delta_z * cdgdz.abs() + pf2 * M_tilde
2216
+ N_required = (
2217
+ (M * (self._RR.pi() + 64 / (15 * (expr**2 - 1))) / E_global).log()
2218
+ / (2 * expr.log())
2219
+ )
2220
+ if N_required.is_positive_infinity():
2221
+ return 2**max(60, self._prec)
2222
+ Ni = max(Ni, N_required.ceil())
2223
+ return Ni
2224
+
2225
+ while ball_stack:
2226
+ ct, rt, lN = ball_stack.pop()
2227
+ ncts = [ct - rt / 2, ct + rt / 2]
2228
+ nrt = rt / 2
2229
+
2230
+ if lN is None:
2231
+ cz = (1 - ct) * z0 + ct * z1
2232
+ distances = [(cz - b).abs() for b in self.branch_locus]
2233
+ rho_z = min(distances)
2234
+ rho_t = rho_z / (z1_minus_z0).abs()
2235
+
2236
+ if rho_t <= rt:
2237
+ ball_stack.append((ncts[0], nrt, None))
2238
+ ball_stack.append((ncts[1], nrt, None))
2239
+ continue
2240
+
2241
+ lN = local_N(ct, rt)
2242
+
2243
+ nNs = [local_N(nct, nrt) for nct in ncts]
2244
+
2245
+ if sum(nNs) < lN:
2246
+ ball_stack.append((ncts[0], nrt, nNs[0]))
2247
+ ball_stack.append((ncts[1], nrt, nNs[1]))
2248
+ continue
2249
+
2250
+ if lN % 2 and not lN == 3:
2251
+ lN += 1
2252
+
2253
+ ct_minus_rt = ct - rt
2254
+ two_rt = 2 * rt
2255
+
2256
+ def integrand(t):
2257
+ zt, wt = zwt(ct_minus_rt + t * two_rt)
2258
+ dfdwt = self._fastcall_dfdw(zt, wt)
2259
+ return V([h(zt, wt) / dfdwt for h in differentials])
2260
+
2261
+ output += two_rt * integrate_vector_N(integrand, self._prec, lN)
2262
+
2263
+ return output * z1_minus_z0
2264
+
2265
+ def matrix_of_integral_values(self, differentials, integration_method='heuristic'):
2266
+ r"""
2267
+ Compute the path integrals of the given differentials along the homology
2268
+ basis.
2269
+
2270
+ The returned answer has a row for each differential. If the Riemann
2271
+ surface is given by the equation `f(z,w)=0`, then the differentials are
2272
+ encoded by polynomials g, signifying the differential `g(z,w)/(df/dw)
2273
+ dz`.
2274
+
2275
+ INPUT:
2276
+
2277
+ - ``differentials`` -- list of polynomials
2278
+
2279
+ - ``integration_method`` -- (default: ``'heuristic'``) string specifying
2280
+ the integration method to use. The options are ``'heuristic'`` and
2281
+ ``'rigorous'``.
2282
+
2283
+ OUTPUT:
2284
+
2285
+ A matrix, one row per differential, containing the values of the path
2286
+ integrals along the homology basis of the Riemann surface.
2287
+
2288
+ EXAMPLES::
2289
+
2290
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2291
+ sage: R.<x,y> = QQ[]
2292
+ sage: S = RiemannSurface(x^3 + y^3 + 1)
2293
+ sage: B = S.cohomology_basis()
2294
+ sage: m = S.matrix_of_integral_values(B)
2295
+ sage: parent(m)
2296
+ Full MatrixSpace of 1 by 2 dense matrices over Complex Field with 53 bits of precision
2297
+ sage: (m[0,0]/m[0,1]).algebraic_dependency(3).degree() # curve is CM, so the period is quadratic
2298
+ 2
2299
+
2300
+ .. NOTE::
2301
+
2302
+ If ``differentials is self.cohomology_basis()``, the calculations
2303
+ of the integrals along the edges are written to ``self._integral_dict``.
2304
+ This is as this data will be required when computing the Abel-Jacobi
2305
+ map, and so it is helpful to have is stored rather than recomputing.
2306
+ """
2307
+ cycles = self.homology_basis()
2308
+
2309
+ def normalize_pairs(L):
2310
+ r"""
2311
+ Return a list of edges encoded by the path in L.
2312
+ The edges are normalized to be in the direction in which
2313
+ the homotopy continuation should have been computed along them.
2314
+ """
2315
+ R = []
2316
+ for i in range(len(L) - 1):
2317
+ if L[i][0] < L[i + 1][0]:
2318
+ R.append((L[i], L[i + 1]))
2319
+ else:
2320
+ R.append((L[i + 1], L[i]))
2321
+ return R
2322
+
2323
+ occurring_edges = set()
2324
+ occurring_edges.update(*[normalize_pairs(p[1]) for h in cycles for p in h])
2325
+
2326
+ if differentials is self.cohomology_basis():
2327
+ fcd = self._fastcall_cohomology_basis
2328
+ integral_dict = self._integral_dict
2329
+ else:
2330
+ fcd = [fast_callable(omega, domain=self._CC) for omega in differentials]
2331
+ integral_dict = {}
2332
+
2333
+ if integration_method == "heuristic":
2334
+ line_int = lambda edge: self.simple_vector_line_integral(edge, fcd)
2335
+ elif integration_method == "rigorous":
2336
+ bd = self._bounding_data(differentials)
2337
+ line_int = lambda edge: self.rigorous_line_integral(edge, fcd, bd)
2338
+ else:
2339
+ raise ValueError("invalid integration method")
2340
+
2341
+ integral_dict = {edge: line_int(edge) for edge in occurring_edges}
2342
+
2343
+ rows = []
2344
+ for cycle in cycles:
2345
+ V = VectorSpace(self._CC, len(differentials)).zero()
2346
+ for multiplicity, loop in cycle:
2347
+ for i in range(len(loop) - 1):
2348
+ if loop[i][0] < loop[i + 1][0]:
2349
+ direction = 1
2350
+ upstairs_edge = (loop[i], loop[i + 1])
2351
+ else:
2352
+ direction = -1
2353
+ upstairs_edge = (loop[i + 1], loop[i])
2354
+ V += (multiplicity * direction) * integral_dict[upstairs_edge]
2355
+ rows.append(V)
2356
+ return Matrix(rows).transpose()
2357
+
2358
+ @cached_method
2359
+ def period_matrix(self):
2360
+ r"""
2361
+ Compute the period matrix of the surface.
2362
+
2363
+ OUTPUT: a matrix of complex values
2364
+
2365
+ EXAMPLES::
2366
+
2367
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2368
+ sage: R.<z,w> = QQ[]
2369
+ sage: f = z^3*w + w^3 + z
2370
+ sage: S = RiemannSurface(f, prec=30)
2371
+ sage: M = S.period_matrix()
2372
+
2373
+ The results are highly arbitrary, so it is hard to check if the result
2374
+ produced is correct. The closely related ``riemann_matrix`` is somewhat
2375
+ easier to test.::
2376
+
2377
+ sage: parent(M)
2378
+ Full MatrixSpace of 3 by 6 dense matrices
2379
+ over Complex Field with 30 bits of precision
2380
+ sage: M.rank()
2381
+ 3
2382
+
2383
+ One can check that the two methods give similar answers::
2384
+
2385
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2386
+ sage: R.<x,y> = QQ[]
2387
+ sage: f = y^2 - x^3 + 1
2388
+ sage: S = RiemannSurface(f, integration_method='rigorous')
2389
+ sage: T = RiemannSurface(f, integration_method='heuristic')
2390
+ sage: RM_S = S.riemann_matrix()
2391
+ sage: RM_T = T.riemann_matrix()
2392
+ sage: (RM_S-RM_T).norm() < 1e-10
2393
+ True
2394
+ """
2395
+ differentials = self.cohomology_basis()
2396
+ return self.matrix_of_integral_values(differentials, self._integration_method)
2397
+
2398
+ def riemann_matrix(self):
2399
+ r"""
2400
+ Compute the Riemann matrix.
2401
+
2402
+ OUTPUT: a matrix of complex values
2403
+
2404
+ EXAMPLES::
2405
+
2406
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2407
+ sage: R.<z,w> = QQ[]
2408
+ sage: f = z^3*w + w^3 + z
2409
+ sage: S = RiemannSurface(f, prec=60)
2410
+ sage: M = S.riemann_matrix()
2411
+
2412
+ The Klein quartic has a Riemann matrix with values in a quadratic
2413
+ field::
2414
+
2415
+ sage: x = polygen(QQ)
2416
+ sage: K.<a> = NumberField(x^2 - x + 2)
2417
+ sage: all(len(m.algebraic_dependency(6).roots(K)) > 0 for m in M.list())
2418
+ True
2419
+ """
2420
+ PeriodMatrix = self.period_matrix()
2421
+ Am = PeriodMatrix[0 : self.genus, 0 : self.genus]
2422
+ RM = (
2423
+ numerical_inverse(Am)
2424
+ * PeriodMatrix[0 : self.genus, self.genus : 2 * self.genus]
2425
+ )
2426
+ return RM
2427
+
2428
+ def plot_paths(self):
2429
+ r"""
2430
+ Make a graphical representation of the integration paths.
2431
+
2432
+ This returns a two dimensional plot containing the branch points (in red) and
2433
+ the integration paths (obtained from the Voronoi cells of the branch
2434
+ points). The integration paths are plotted by plotting the points that
2435
+ have been computed for homotopy continuation, so the density gives an
2436
+ indication of where numerically sensitive features occur.
2437
+
2438
+ EXAMPLES::
2439
+
2440
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2441
+ sage: R.<x,y> = QQ[]
2442
+ sage: S = RiemannSurface(y^2 - x^3 - x)
2443
+ sage: S.plot_paths() # needs sage.plot
2444
+ Graphics object consisting of 2 graphics primitives
2445
+ """
2446
+ from sage.plot.point import point2d
2447
+
2448
+ P = []
2449
+
2450
+ # trigger the computation of the homology basis, so that self._L is present
2451
+ self.homology_basis()
2452
+
2453
+ for e in self._L.keys():
2454
+ z0 = self._vertices[e[0]]
2455
+ z1 = self._vertices[e[1]]
2456
+
2457
+ def path(t):
2458
+ return (1 - t) * z0 + t * z1
2459
+
2460
+ T = self._L[e]
2461
+ P += [path(t[0]) for t in T]
2462
+ return point2d(P, size=1) + point2d(self.branch_locus, color='red')
2463
+
2464
+ def plot_paths3d(self, thickness=0.01):
2465
+ r"""
2466
+ Return the homology basis as a graph in 3-space.
2467
+
2468
+ The homology basis of the surface is constructed by taking the Voronoi
2469
+ cells around the branch points and taking the inverse image of the edges
2470
+ on the Riemann surface. If the surface is given by the equation
2471
+ `f(z,w)`, the returned object gives the image of this graph in 3-space
2472
+ with coordinates `\left(\operatorname{Re}(z), \operatorname{Im}(z),
2473
+ \operatorname{Im}(w)\right)`.
2474
+
2475
+ EXAMPLES::
2476
+
2477
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2478
+ sage: R.<x,y> = QQ[]
2479
+ sage: S = RiemannSurface(y^2 - x^3 - x)
2480
+ sage: S.plot_paths3d() # needs sage.plot
2481
+ Graphics3d Object
2482
+ """
2483
+ from sage.plot.graphics import Graphics
2484
+ from sage.plot.plot3d.shapes2 import point3d, line3d
2485
+
2486
+ P = Graphics()
2487
+
2488
+ # trigger the computation of the homology basis, so that
2489
+ # self._L is present
2490
+ self.homology_basis()
2491
+
2492
+ for e in self._L.keys():
2493
+ z0 = self._vertices[e[0]]
2494
+ z1 = self._vertices[e[1]]
2495
+
2496
+ def path(t):
2497
+ z = (1 - t) * z0 + t * z1
2498
+ return (z.real_part(), z.imag_part())
2499
+
2500
+ T = self._L[e]
2501
+ color = "blue"
2502
+ for i in range(self.degree):
2503
+ P += line3d(
2504
+ [path(t[0]) + (t[1][i].imag_part(),) for t in T],
2505
+ color=color,
2506
+ thickness=thickness,
2507
+ )
2508
+ for z, ws in zip(self._vertices, self._wvalues):
2509
+ for w in ws:
2510
+ P += point3d(
2511
+ [z.real_part(), z.imag_part(), w.imag_part()],
2512
+ color='purple',
2513
+ size=20,
2514
+ )
2515
+ return P
2516
+
2517
+ def endomorphism_basis(self, b=None, r=None):
2518
+ r"""
2519
+ Numerically compute a `\ZZ`-basis for the endomorphism ring.
2520
+
2521
+ Let `\left(I | M \right)` be the normalized period matrix (`M` is the
2522
+ `g\times g` :meth:`riemann_matrix`). We consider the system of matrix
2523
+ equations `MA + C = (MB + D)M` where `A, B, C, D` are `g\times g`
2524
+ integer matrices. We determine small integer (near) solutions using LLL
2525
+ reductions. These solutions are returned as `2g \times 2g` integer
2526
+ matrices obtained by stacking `\left(D | B\right)` on top of `\left(C |
2527
+ A\right)`.
2528
+
2529
+ INPUT:
2530
+
2531
+ - ``b`` -- integer (default provided); the equation coefficients are
2532
+ scaled by `2^b` before rounding to integers
2533
+
2534
+ - ``r`` -- integer (default: ``b/4``); solutions that have all
2535
+ coefficients smaller than `2^r` in absolute value are reported as
2536
+ actual solutions
2537
+
2538
+ OUTPUT:
2539
+
2540
+ A list of `2g \times 2g` integer matrices that, for large enough ``r``
2541
+ and ``b-r``, generate the endomorphism ring.
2542
+
2543
+ EXAMPLES::
2544
+
2545
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2546
+ sage: R.<x,y> = QQ[]
2547
+ sage: S = RiemannSurface(x^3 + y^3 + 1)
2548
+ sage: B = S.endomorphism_basis(); B #random
2549
+ [
2550
+ [1 0] [ 0 -1]
2551
+ [0 1], [ 1 1]
2552
+ ]
2553
+ sage: sorted([b.minpoly().disc() for b in B])
2554
+ [-3, 1]
2555
+ """
2556
+ M = self.riemann_matrix()
2557
+ return integer_matrix_relations(M, M, b, r)
2558
+
2559
+ def homomorphism_basis(self, other, b=None, r=None):
2560
+ r"""
2561
+ Numerically compute a `\ZZ`-basis for module of homomorphisms to a given
2562
+ complex torus.
2563
+
2564
+ Given another complex torus (given as the analytic Jacobian of a Riemann
2565
+ surface), numerically compute a basis for the homomorphism module. The
2566
+ answer is returned as a list of `2g \times 2g` integer matrices `T=(D, B; C, A)`
2567
+ such that if the columns of `(I|M_1)` generate the lattice defining the
2568
+ Jacobian of the Riemann surface and the columns of `(I|M_2)` do this for
2569
+ the codomain, then approximately we have `(I|M_2)T=(D+M_2C)(I|M_1)`, i.e., up
2570
+ to a choice of basis for `\CC^g` as a complex vector space, we we
2571
+ realize `(I|M_1)` as a sublattice of `(I|M_2)`.
2572
+
2573
+ INPUT:
2574
+
2575
+ - ``b`` -- integer (default provided); the equation coefficients are
2576
+ scaled by `2^b` before rounding to integers
2577
+
2578
+ - ``r`` -- integer (default: ``b/4``); solutions that have all
2579
+ coefficients smaller than `2^r` in absolute value are reported as
2580
+ actual solutions
2581
+
2582
+ OUTPUT:
2583
+
2584
+ A list of `2g \times 2g` integer matrices that, for large enough ``r``
2585
+ and ``b-r``, generate the homomorphism module.
2586
+
2587
+ EXAMPLES::
2588
+
2589
+ sage: S1 = EllipticCurve("11a1").riemann_surface()
2590
+ sage: S2 = EllipticCurve("11a3").riemann_surface()
2591
+ sage: [m.det() for m in S1.homomorphism_basis(S2)]
2592
+ [5]
2593
+ """
2594
+ M1 = self.riemann_matrix()
2595
+ M2 = other.riemann_matrix()
2596
+ return integer_matrix_relations(M2, M1, b, r)
2597
+
2598
+ def tangent_representation_numerical(self, Rs, other=None):
2599
+ r"""
2600
+ Compute the numerical tangent representations corresponding to the
2601
+ homology representations in ``Rs``.
2602
+
2603
+ The representations on homology ``Rs`` have to be given with respect to
2604
+ the symplectic homology basis of the Jacobian of ``self`` and ``other``.
2605
+ Such matrices can for example be obtained via
2606
+ :meth:`endomorphism_basis`.
2607
+
2608
+ Let `P` and `Q` be the period matrices of ``self`` and ``other``. Then
2609
+ for a homology representation `R`, the corresponding tangential
2610
+ representation `T` satisfies `T P = Q R`.
2611
+
2612
+ INPUT:
2613
+
2614
+ - ``Rs`` -- set of matrices on homology to be converted to their
2615
+ tangent representations
2616
+
2617
+ - ``other`` -- (default: ``self``) the codomain; another Riemann
2618
+ surface
2619
+
2620
+ OUTPUT: the numerical tangent representations of the matrices in ``Rs``
2621
+
2622
+ EXAMPLES::
2623
+
2624
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2625
+ sage: A.<x,y> = QQ[]
2626
+ sage: S = RiemannSurface(y^2 - (x^6 + 2*x^4 + 4*x^2 + 8), prec = 100)
2627
+ sage: P = S.period_matrix()
2628
+ sage: Rs = S.endomorphism_basis()
2629
+ sage: Ts = S.tangent_representation_numerical(Rs)
2630
+ sage: all(((T*P - P*R).norm() < 2^(-80)) for [T, R] in zip(Ts, Rs))
2631
+ True
2632
+ """
2633
+ if not other:
2634
+ other = self
2635
+ P = self.period_matrix()
2636
+ CCP = P.base_ring()
2637
+ g = self.genus
2638
+ Q = other.period_matrix()
2639
+ Ptsubinv = numerical_inverse((P.transpose())[list(range(g))])
2640
+ Ts = []
2641
+ for R in Rs:
2642
+ QRtsub = ((Q * R).transpose())[list(range(g))]
2643
+ Tt = Ptsubinv * QRtsub
2644
+ T = Tt.transpose().change_ring(CCP)
2645
+ Ts.append(T)
2646
+ return Ts
2647
+
2648
+ def tangent_representation_algebraic(self, Rs, other=None, epscomp=None):
2649
+ r"""
2650
+ Compute the algebraic tangent representations corresponding to the
2651
+ homology representations in ``Rs``.
2652
+
2653
+ The representations on homology ``Rs`` have to be given with respect to
2654
+ the symplectic homology basis of the Jacobian of ``self`` and ``other``.
2655
+ Such matrices can for example be obtained via
2656
+ :meth:`endomorphism_basis`.
2657
+
2658
+ Let `P` and `Q` be the period matrices of ``self`` and ``other``. Then
2659
+ for a homology representation `R`, the corresponding tangential
2660
+ representation `T` satisfies `T P = Q R`.
2661
+
2662
+ INPUT:
2663
+
2664
+ - ``Rs`` -- set of matrices on homology to be converted to their
2665
+ tangent representations
2666
+
2667
+ - ``other`` -- (default: ``self``) the codomain; another Riemann
2668
+ surface
2669
+
2670
+ - ``epscomp`` -- real number (default: ``2^(-prec + 30)``). Used to
2671
+ determine whether a complex number is close enough to a root of a
2672
+ polynomial.
2673
+
2674
+ OUTPUT: the algebraic tangent representations of the matrices in ``Rs``
2675
+
2676
+ EXAMPLES::
2677
+
2678
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2679
+ sage: A.<x,y> = QQ[]
2680
+ sage: S = RiemannSurface(y^2 - (x^6 + 2*x^4 + 4*x^2 + 8), prec = 100)
2681
+ sage: Rs = S.endomorphism_basis()
2682
+ sage: Ts = S.tangent_representation_algebraic(Rs)
2683
+ sage: Ts[0].base_ring().maximal_order().discriminant() == 8
2684
+ True
2685
+ """
2686
+ if not epscomp:
2687
+ epscomp = 2**(-self._prec + 30)
2688
+ QQalg = QQ.algebraic_closure()
2689
+
2690
+ def polynomialize_element(alpha):
2691
+ d = 1
2692
+ while True:
2693
+ d += 1
2694
+ dep = algebraic_dependency(alpha, d, height_bound=10**d)
2695
+ if dep and dep(alpha) < epscomp:
2696
+ return dep
2697
+
2698
+ def algebraize_element(alpha):
2699
+ alphaPol = polynomialize_element(alpha)
2700
+ CC = alpha.parent()
2701
+ for tup in alphaPol.roots(QQalg):
2702
+ rt = tup[0]
2703
+ if (alpha - CC(rt)).abs() < epscomp:
2704
+ return rt
2705
+ raise AssertionError("No close root found while algebraizing")
2706
+
2707
+ def algebraize_matrices(Ts):
2708
+ nr = Ts[0].nrows()
2709
+ nc = Ts[0].ncols()
2710
+ TsAlg = [T.apply_map(algebraize_element) for T in Ts]
2711
+ elts = [x for TAl in TsAlg for x in TAl.list()]
2712
+ eltsAlg = number_field_elements_from_algebraics(elts)[1]
2713
+ L = eltsAlg[0].parent()
2714
+ TsAlgL = []
2715
+ for i in range(len(Ts)):
2716
+ TAlgL = [eltsAlg[j] for j in range(i * nr * nc, (i + 1) * nr * nc)]
2717
+ TsAlgL.append(Matrix(L, nr, nc, TAlgL))
2718
+ return TsAlgL
2719
+
2720
+ Ts = self.tangent_representation_numerical(Rs, other=other)
2721
+ return algebraize_matrices(Ts)
2722
+
2723
+ def rosati_involution(self, R):
2724
+ r"""
2725
+ Compute the Rosati involution of an endomorphism.
2726
+
2727
+ The endomorphism in question should be given by its homology
2728
+ representation with respect to the symplectic basis of the Jacobian.
2729
+
2730
+ INPUT:
2731
+
2732
+ - ``R`` -- integral matrix
2733
+
2734
+ OUTPUT: the result of applying the Rosati involution to ``R``
2735
+
2736
+ EXAMPLES::
2737
+
2738
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2739
+ sage: A.<x,y> = QQ[]
2740
+ sage: S = RiemannSurface(y^2 - (x^6 + 2*x^4 + 4*x^2 + 8), prec = 100)
2741
+ sage: Rs = S.endomorphism_basis()
2742
+ sage: S.rosati_involution(S.rosati_involution(Rs[1])) == Rs[1]
2743
+ True
2744
+ """
2745
+
2746
+ def standard_symplectic_matrix(n):
2747
+ one = Matrix.identity(n)
2748
+ zero = Matrix.zero(n)
2749
+ return Matrix.block([[zero, -one], [one, zero]])
2750
+
2751
+ g = self.genus
2752
+ if not (R.nrows() == 2 * g == R.ncols()):
2753
+ raise AssertionError(
2754
+ "Matrix is not the homology representation of an endomorphism"
2755
+ )
2756
+ J = standard_symplectic_matrix(g)
2757
+ return -J * R.transpose() * J
2758
+
2759
+ def symplectic_isomorphisms(self, other=None, hom_basis=None, b=None, r=None):
2760
+ r"""
2761
+ Numerically compute symplectic isomorphisms.
2762
+
2763
+ INPUT:
2764
+
2765
+ - ``other`` -- (default: ``self``) the codomain; another Riemann
2766
+ surface
2767
+
2768
+ - ``hom_basis`` -- (default: ``None``) a `\ZZ`-basis of the
2769
+ homomorphisms from ``self`` to ``other``, as obtained from
2770
+ :meth:`homomorphism_basis`. If you have already calculated this
2771
+ basis, it saves time to pass it via this keyword argument. Otherwise
2772
+ the method will calculate it.
2773
+
2774
+ - ``b`` -- integer (default provided); as for
2775
+ :meth:`homomorphism_basis`, and used in its invocation if
2776
+ (re)calculating said basis
2777
+
2778
+ - ``r`` -- integer (default: ``b/4``); as for
2779
+ :meth:`homomorphism_basis`, and used in its invocation if
2780
+ (re)calculating said basis
2781
+
2782
+ OUTPUT:
2783
+
2784
+ This returns the combinations of the elements of
2785
+ :meth:`homomorphism_basis` that correspond to symplectic
2786
+ isomorphisms between the Jacobians of ``self`` and ``other``.
2787
+
2788
+ EXAMPLES::
2789
+
2790
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2791
+ sage: R.<x,y> = QQ[]
2792
+ sage: f = y^2 - (x^6 + 2*x^4 + 4*x^2 + 8)
2793
+ sage: X = RiemannSurface(f, prec=100)
2794
+ sage: P = X.period_matrix()
2795
+ sage: g = y^2 - (x^6 + x^4 + x^2 + 1)
2796
+ sage: Y = RiemannSurface(g, prec=100)
2797
+ sage: Q = Y.period_matrix()
2798
+ sage: Rs = X.symplectic_isomorphisms(Y)
2799
+ sage: Ts = X.tangent_representation_numerical(Rs, other = Y)
2800
+ sage: test1 = all(((T*P - Q*R).norm() < 2^(-80)) for [T, R] in zip(Ts, Rs))
2801
+ sage: test2 = all(det(R) == 1 for R in Rs)
2802
+ sage: test1 and test2
2803
+ True
2804
+ """
2805
+ if not other:
2806
+ other = self
2807
+ if hom_basis:
2808
+ Rs = hom_basis
2809
+ else:
2810
+ Rs = self.homomorphism_basis(other=other, b=b, r=r)
2811
+ r = len(Rs)
2812
+ g = self.genus
2813
+ A = PolynomialRing(QQ, r, "x")
2814
+ gensA = A.gens()
2815
+ # Use that the trace is positive definite; we could also put this as an
2816
+ # extra condition when determining the endomorphism basis to speed up
2817
+ # that calculation slightly
2818
+ R = sum(gensA[i] * Rs[i].change_ring(A) for i in range(r))
2819
+ tr = (R * self.rosati_involution(R)).trace()
2820
+ # Condition tr = 2 g creates ellipsoid
2821
+ M = Matrix(
2822
+ ZZ,
2823
+ r,
2824
+ r,
2825
+ [tr.derivative(gen1).derivative(gen2) for gen1 in gensA for gen2 in gensA],
2826
+ )
2827
+ vs = M.__pari__().qfminim(4 * g)[2].sage().transpose()
2828
+ vs = [v for v in vs if v * M * v == 4 * g]
2829
+ vs += [-v for v in vs]
2830
+ RsIso = []
2831
+ for v in vs:
2832
+ R = sum(v[i] * Rs[i] for i in range(r))
2833
+ if R * self.rosati_involution(R) == 1:
2834
+ RsIso.append(R)
2835
+ return RsIso
2836
+
2837
+ def symplectic_automorphism_group(self, endo_basis=None, b=None, r=None):
2838
+ r"""
2839
+ Numerically compute the symplectic automorphism group as a permutation
2840
+ group.
2841
+
2842
+ INPUT:
2843
+
2844
+ - ``endo_basis`` -- (default: ``None``) a `\ZZ`-basis of the
2845
+ endomorphisms of ``self``, as obtained from
2846
+ :meth:`endomorphism_basis`. If you have already calculated this
2847
+ basis, it saves time to pass it via this keyword argument. Otherwise
2848
+ the method will calculate it.
2849
+
2850
+ - ``b`` -- integer (default provided); as for
2851
+ :meth:`homomorphism_basis`, and used in its invocation if
2852
+ (re)calculating said basis
2853
+
2854
+ - ``r`` -- integer (default: ``b/4``); as for
2855
+ :meth:`homomorphism_basis`, and used in its invocation if
2856
+ (re)calculating said basis
2857
+
2858
+ OUTPUT:
2859
+
2860
+ The symplectic automorphism group of the Jacobian of the Riemann
2861
+ surface. The automorphism group of the Riemann surface itself can be
2862
+ recovered from this; if the curve is hyperelliptic, then it is
2863
+ identical, and if not, then one divides out by the central element
2864
+ corresponding to multiplication by -1.
2865
+
2866
+ EXAMPLES::
2867
+
2868
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2869
+ sage: A.<x,y> = QQ[]
2870
+ sage: S = RiemannSurface(y^2 - (x^6 + 2*x^4 + 4*x^2 + 8), prec = 100)
2871
+ sage: G = S.symplectic_automorphism_group()
2872
+ sage: G.as_permutation_group().is_isomorphic(DihedralGroup(4))
2873
+ True
2874
+ """
2875
+ RsAut = self.symplectic_isomorphisms(hom_basis=endo_basis, b=b, r=r)
2876
+ return MatrixGroup(RsAut)
2877
+
2878
+ def __add__(self, other):
2879
+ r"""
2880
+ Return the disjoint union of the Riemann surface and the other argument.
2881
+
2882
+ EXAMPLES::
2883
+
2884
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
2885
+ sage: R.<x,y> = QQ[]
2886
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
2887
+ sage: S1+S1
2888
+ Riemann surface sum with period lattice of rank 4
2889
+ """
2890
+ return RiemannSurfaceSum([self, other])
2891
+
2892
+ def _integrate_differentials_iteratively(
2893
+ self, upstairs_edge, cutoff_individually=False, raise_errors=True, prec=None
2894
+ ):
2895
+ r"""
2896
+ Integrate the cohomology basis along a straight line edge.
2897
+
2898
+ The cohomology basis is integrated along a straight line using a version
2899
+ of the double exponential quadrature. This method of integrating the
2900
+ cohomology basis is especially useful when integrating out to infinity,
2901
+ or near roots of ``self._dfdw``. In order to aid with convergence of the
2902
+ method, two main modification to a standard integrator are made, most
2903
+ importantly of which is the truncation of the integral near branch points,
2904
+ where the first term in the Puiseux series of the integrands are used to
2905
+ approximately bound the integral. The ``cutoff_individually`` parameter
2906
+ allows the user to set whether that truncation is uniform over all the
2907
+ integrands, which improves the complexity of the algorithm, but loses
2908
+ the ability to gain benefits where integrands vanish to a high order at
2909
+ the branchpoint.
2910
+
2911
+ INPUT:
2912
+
2913
+ - ``upstairs_edge`` -- tuple. A tuple of complex numbers of the form
2914
+ ``((z_start, w_start), z_end)`` specifying the path to integrate
2915
+ along, where ``z_start`` may be infinite, in which case ``w_start``
2916
+ must be an integer specifying the branch.
2917
+
2918
+ - ``cutoff_individually`` -- boolean (default: ``False``); whether to
2919
+ truncate the integrand uniformly or not. If ``None``, then no
2920
+ truncation is applied
2921
+
2922
+ - ``raise_errors`` -- boolean (default: ``True``); by default the code
2923
+ uses convergence errors to ensure any answers returned are accurate.
2924
+ This can be turned off to return answers faster that are not
2925
+ necessarily correct.
2926
+
2927
+ - ``prec`` -- integer (default: ``self._prec``); the precision to try
2928
+ and achieve, defined as `2^{-\text{prec}+3}`
2929
+
2930
+ OUTPUT:
2931
+
2932
+ Tuple ``(I, gs)`` where ``I`` is the vector of integrals, and ``gs`` are
2933
+ the values of the differentials at ``z_end``.
2934
+
2935
+ EXAMPLES:
2936
+
2937
+ We know that for the surface given by `w^2-z^4-1` a cohomology basis is
2938
+ given by `\frac{dz}{2w}`. One can verify analytically that
2939
+ `\int_0^1 frac{dt}{\sqrt{1-t^4}}=\frac{\sqrt{\pi}\Gamma(5/4)}{\Gamma(3/4)}`,
2940
+ and we check this with the integrator, being careful with signs::
2941
+
2942
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
2943
+ sage: R.<z,w> = QQ[]
2944
+ sage: S = RiemannSurface(w^2 + z^4 - 1, prec=100)
2945
+ sage: branch = 0
2946
+ sage: eps = S._RR(2)**(-S._prec)
2947
+ sage: z_start = 1 - eps
2948
+ sage: z_end = 0
2949
+ sage: w_start = S.w_values(z_start)[0]
2950
+ sage: s = sign(w_start)
2951
+ sage: u_edge = ((z_start, w_start), z_end)
2952
+ sage: J, _ = S._integrate_differentials_iteratively(u_edge)
2953
+ sage: bool(J[0] + s*S._RR(sqrt(pi)*gamma(5/4)/gamma(3/4)/2) < 1e-10) # needs sage.symbolic
2954
+ True
2955
+
2956
+ .. NOTE::
2957
+
2958
+ The cutoff methodology is calculating the first term in the Puiseux
2959
+ series of the differentials about z_start. In future it may be
2960
+ desirable to extend this further and use the truncated Puiseux series
2961
+ entirely to integrate the differentials.
2962
+ """
2963
+ (z_start, w_start), z_end = upstairs_edge
2964
+ z_start = self._CC(z_start)
2965
+ z_end = self._CC(z_end)
2966
+
2967
+ if z_end == self._CC(Infinity):
2968
+ raise NotImplementedError
2969
+
2970
+ _, bounding_data_list = self._cohomology_basis_bounding_data
2971
+ mp_list = [bd[2] for bd in bounding_data_list]
2972
+
2973
+ # Parameterise so zbar=0 corresponds to z=z_start
2974
+ mp_list = [reparameterize_differential_minpoly(mp, z_start) for mp in mp_list]
2975
+
2976
+ # Depending on whether we have reparameterized about infinity or not,
2977
+ # we initialise some values we will need in the calculation, including
2978
+ # the function `initalize', which at a given value of zbar, calculates
2979
+ # the starting value for the i-th differential so it can be iterated
2980
+ # from via homotopy continuation.
2981
+ if z_start == self._CC(Infinity):
2982
+ CCzg = PolynomialRing(self._CC, ["zbar", "gbar"])
2983
+ mp_list = [CCzg(mp) for mp in mp_list]
2984
+ J = 1 / z_end
2985
+ endscale = -(z_end**(-2))
2986
+
2987
+ def initialise(z, i):
2988
+ DF = ComplexField(2 * self._prec)
2989
+ DFw = PolynomialRing(DF, "wbar")
2990
+ z = DF(z)
2991
+ R = DF(z**(-1))
2992
+ wR = DFw(self.f(R, DFw.gen(0))).roots(multiplicities=False)[w_start]
2993
+ newg = -(R**2) * self.cohomology_basis()[i](R, wR) / self._dfdw(R, wR)
2994
+ err = mp_list[i](z, newg).abs()
2995
+ if err > tau:
2996
+ rs = mp_list[i](z, DFw.gen(0)).roots(multiplicities=False)
2997
+ sb = find_closest_element(newg, rs)
2998
+ newg = rs[sb]
2999
+ return newg
3000
+
3001
+ else:
3002
+ CCzg = mp_list[0].parent()
3003
+ J = z_end - z_start
3004
+ endscale = 1
3005
+
3006
+ def initialise(z, i):
3007
+ newg = self.cohomology_basis()[i](z_start, w_start) / self._dfdw(
3008
+ z_start, w_start
3009
+ )
3010
+ err = mp_list[i](z, newg).abs()
3011
+ if err > tau:
3012
+ rs = mp_list[i](z, self._CCw.gen(0)).roots(multiplicities=False)
3013
+ sb = find_closest_element(newg, rs)
3014
+ newg = rs[sb]
3015
+ return newg
3016
+
3017
+ # As multiple calls of the minimal polynomial and it's derivative will
3018
+ # be required for the homotopy continuation, we create fast-callable
3019
+ # versions of these.
3020
+ fc_mp_list = [fast_callable(mp, domain=self._CC) for mp in mp_list]
3021
+ fc_dmp_list = [
3022
+ fast_callable(mp.derivative(CCzg.gen(1)), domain=self._CC) for mp in mp_list
3023
+ ]
3024
+
3025
+ if prec is None:
3026
+ prec = self._prec
3027
+ # tau here is playing the role of the desired error.
3028
+ tau = self._RR(2)**(-prec + 3)
3029
+ one = self._RR.one()
3030
+ la = self._RR.pi() / 2
3031
+
3032
+ # Cutoffs are used to allow us to not have to integrate as close into
3033
+ # a singularity as we might otherwise have to, by knowing that we can
3034
+ # truncate the integration interval and only introduce a finite error
3035
+ # that can be bounded by knowledge of the asymptotics of the integrands,
3036
+ # which we have from their minimal polynomials. This is really a
3037
+ # precursor to what would be ideal to implement eventually, namely
3038
+ # a method that uses Puiseux series to integrate into singularities.
3039
+ # We allow for cutoffs to be tailored to each integrand, or we take a
3040
+ # uniform value.
3041
+ if cutoff_individually is None:
3042
+ cutoffs = [0]
3043
+ cutoff_individually = False
3044
+ else:
3045
+ cutoffs = []
3046
+ A = PolynomialRing(self._CC, "xyz")
3047
+ aes = []
3048
+ for mp in mp_list:
3049
+ d = mp.monomial_coefficients()
3050
+ mp = sum(
3051
+ [
3052
+ d[k] * CCzg.gen(0)**k[0] * CCzg.gen(1)**k[1]
3053
+ for k in d.keys()
3054
+ if d[k].abs() > tau
3055
+ ]
3056
+ )
3057
+ cst = min([iz for (iz, ig) in d.keys() if ig == 0])
3058
+ a = QQ(max([(cst - iz) / ig for (iz, ig) in d.keys() if ig > 0]))
3059
+ sum_coeffs = sum(
3060
+ [
3061
+ d[k] * A.gen(0)**k[1]
3062
+ for k in d.keys()
3063
+ if ((k[1] == 0 and k[0] == cst) or k[1] * a + k[0] - cst == 0)
3064
+ ]
3065
+ )
3066
+ G = max([r.abs() for r in sum_coeffs.roots(multiplicities=False)])
3067
+ cutoffs.append(((a + 1) * tau / G)**(1 / self._CC(a + 1)) / J.abs())
3068
+ aes.append(a)
3069
+ cutoff_individually = bool(
3070
+ not all(ai <= 0 for ai in aes) and cutoff_individually
3071
+ )
3072
+
3073
+ # The `raise_errors' variable toggles what we do in the event that
3074
+ # newton iteration hasn't converged to the desired precision in a
3075
+ # fixed number of steps, here set to 100.
3076
+ # If the default value of True is taken, then the failure to converge
3077
+ # raises an error. If the value of False is taken, this failure to
3078
+ # converge happens silently, thus allowing the user to get *an*
3079
+ # answer out of the integration, but numerical imprecision is to be
3080
+ # expected. As such, we set the maximum number of steps in the sequence
3081
+ # of DE integrations to be lower in the latter case.
3082
+ if raise_errors:
3083
+ n_steps = self._prec - 1
3084
+ else:
3085
+ n_steps = 15
3086
+
3087
+ from sage.functions.log import lambert_w
3088
+
3089
+ V = VectorSpace(self._CC, self.genus)
3090
+ h = one
3091
+ Nh = (-lambert_w(-1, -tau / 2) / la).log().ceil()
3092
+ h0 = Nh * h
3093
+
3094
+ # Depending on how the cutoffs were defined, we now create the function
3095
+ # which calculates the integrand we want to integrate via double-
3096
+ # exponential methods. This will get the value at the next node by
3097
+ # homotopy-continuing from the last node value. There is also a slight
3098
+ # technical condition which implements the cutoffs.
3099
+ if cutoff_individually:
3100
+ z_fc_list = list(zip(fc_mp_list, fc_dmp_list))
3101
+
3102
+ def fv(hj, previous_estimate_and_validity):
3103
+ u2 = la * hj.sinh()
3104
+ t = 1 / (2 * u2.exp() * u2.cosh())
3105
+ z0 = J * t
3106
+ outg = []
3107
+ valid = self.genus * [True]
3108
+ previous_estimate, validity = previous_estimate_and_validity
3109
+ for i in range(self.genus):
3110
+ co = cutoffs[i]
3111
+ pv = validity[i]
3112
+ if t < co:
3113
+ outg.append(0)
3114
+ valid[i] = False
3115
+ elif not pv:
3116
+ outg.append(initialise(z0, i))
3117
+ else:
3118
+ F, dF = z_fc_list[i]
3119
+ oldg = previous_estimate[i]
3120
+ delta = F(z0, oldg) / dF(z0, oldg)
3121
+ Ndelta = delta.norm()
3122
+ newg = oldg - delta
3123
+ for j in range(100):
3124
+ new_delta = F(z0, newg) / dF(z0, newg)
3125
+ Nnew_delta = new_delta.norm()
3126
+ if (new_delta == 0) or (
3127
+ Nnew_delta >= Ndelta
3128
+ and (Ndelta.sign_mantissa_exponent()[2] + self._prec)
3129
+ < newg.norm().sign_mantissa_exponent()[2]
3130
+ ):
3131
+ outg.append(newg)
3132
+ break
3133
+ delta = new_delta
3134
+ Ndelta = Nnew_delta
3135
+ newg -= delta
3136
+ else:
3137
+ if raise_errors:
3138
+ raise ConvergenceError("Newton iteration fails to converge")
3139
+ else:
3140
+ outg.append(newg)
3141
+ fj = V(outg)
3142
+ u1 = la * hj.cosh()
3143
+ w = u1 / (2 * u2.cosh()**2)
3144
+ return (fj, valid), w * fj
3145
+
3146
+ f0, v0 = fv(h0, (self.genus * [0], self.genus * [False]))
3147
+ else:
3148
+ cutoffs.append(1)
3149
+ cutoff = min(cutoffs)
3150
+ cutoff_z = J * cutoff
3151
+ J -= cutoff_z
3152
+
3153
+ def fv(hj, previous_estimate):
3154
+ u2 = la * hj.sinh()
3155
+ t = 1 / (2 * u2.exp() * u2.cosh())
3156
+ z0 = cutoff_z + J * t
3157
+ outg = []
3158
+ for F, dF, oldg in zip(fc_mp_list, fc_dmp_list, previous_estimate):
3159
+ delta = F(z0, oldg) / dF(z0, oldg)
3160
+ Ndelta = delta.norm()
3161
+ newg = oldg - delta
3162
+ for j in range(100):
3163
+ new_delta = F(z0, newg) / dF(z0, newg)
3164
+ Nnew_delta = new_delta.norm()
3165
+ if (new_delta == 0) or (
3166
+ Nnew_delta >= Ndelta
3167
+ and (Ndelta.sign_mantissa_exponent()[2] + self._prec)
3168
+ < newg.norm().sign_mantissa_exponent()[2]
3169
+ ):
3170
+ outg.append(newg)
3171
+ break
3172
+ delta = new_delta
3173
+ Ndelta = Nnew_delta
3174
+ newg -= delta
3175
+ else:
3176
+ if raise_errors:
3177
+ raise ConvergenceError("Newton iteration fails to converge")
3178
+ else:
3179
+ outg.append(newg)
3180
+ fj = V(outg)
3181
+ u1 = la * hj.cosh()
3182
+ w = u1 / (2 * u2.cosh()**2)
3183
+ return fj, w * fj
3184
+
3185
+ u1, u2 = (la * h0.cosh(), la * h0.sinh())
3186
+ y, w = (1 / (2 * u2.exp() * u2.cosh()), u1 / (2 * u2.cosh() ** 2))
3187
+ z0 = cutoff_z + J * y
3188
+ f0 = [initialise(z0, i) for i in range(self.genus)]
3189
+ f0 = V(f0)
3190
+ v0 = w * f0
3191
+
3192
+ D3_over_tau = v0.norm(Infinity)
3193
+ D4 = D3_over_tau
3194
+ results = []
3195
+
3196
+ # we now calculate the integral via double-exponential methods
3197
+ # repeatedly halving the step size and then using a heuristic
3198
+ # convergence check. The maximum number of steps allowed is
3199
+ # currently set to make sure the step size does not fall below the
3200
+ # resolution set by the binary precision used.
3201
+ for k in range(n_steps):
3202
+ hj = h0
3203
+ val = v0
3204
+ fj = f0
3205
+ for j in range(2 * Nh):
3206
+ hj -= h
3207
+ try:
3208
+ fj, v = fv(hj, fj)
3209
+ except ConvergenceError:
3210
+ break
3211
+ D3_over_tau = max(v.norm(Infinity), D3_over_tau)
3212
+ val += v
3213
+ if j == 2 * Nh - 1:
3214
+ results.append(h * val)
3215
+ D4 = max(D4, v.norm(Infinity))
3216
+ if len(results) > 2:
3217
+ if results[-1] == results[-2] or results[2] == results[-3]:
3218
+ D = tau
3219
+ else:
3220
+ D1 = (results[-1] - results[-2]).norm(Infinity)
3221
+ D2 = (results[-1] - results[-3]).norm(Infinity)
3222
+ D = min(
3223
+ one,
3224
+ max(
3225
+ D1**(D1.log() / D2.log()),
3226
+ D2**2,
3227
+ tau * D3_over_tau,
3228
+ D4,
3229
+ tau,
3230
+ ),
3231
+ )
3232
+
3233
+ if D <= tau:
3234
+ if cutoff_individually:
3235
+ fj = fj[0]
3236
+ return J * results[-1], endscale * fj
3237
+ h /= 2
3238
+ Nh *= 2
3239
+ # Note that throughout this loop there is a return statement, intended
3240
+ # to be activated when the sequence of integral approximations is
3241
+ # deemed to have converged by the heuristic error. If this has no
3242
+ # happened by the time we have gone through the process n_steps times,
3243
+ # we have one final error handle. Again, this will throw an error if
3244
+ # the raise_errors flag is true, but will just return the answer otherwise.
3245
+ if raise_errors:
3246
+ raise ConvergenceError("Newton iteration fails to converge")
3247
+
3248
+ return (J * results[-1], endscale * fj)
3249
+
3250
+ def _aj_based(self, P):
3251
+ r"""
3252
+ Return the Abel-Jacobi map to ``P`` from ``self._basepoint``.
3253
+
3254
+ Computes a representative of the Abel-Jacobi map from ``self._basepoint``
3255
+ to ``P`` via a well-chosen vertex ``V``. The representative given will be
3256
+ dependent on the path chosen.
3257
+
3258
+ INPUT:
3259
+
3260
+ - ``P`` -- tuple. A pair giving the endpoint of the integral, either in
3261
+ the form ``(z, w)`` or ``(Infinity, branch)``, where in the latter case
3262
+ we are using the convention that the `w` value over `\infty` is given by
3263
+ the limit as ``z`` tends to `\infty` of ``self.w_values(z)[branch]``.
3264
+
3265
+ OUTPUT: a vector of length ``self.genus``
3266
+
3267
+ EXAMPLES:
3268
+
3269
+ As the output of ``_aj_based`` is difficult to interpret due to its path
3270
+ dependency, we look at the output of :meth:`abel_jacobi`. We check for
3271
+ two hyperelliptic curves that the Abel-Jacobi map between two branch
3272
+ points is a 2-torsion point over the lattice. Note we must remember to
3273
+ reduce over the period lattice, as results are path dependent::
3274
+
3275
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3276
+ sage: R.<x,y> = QQ[]
3277
+ sage: p = 100
3278
+ sage: S = RiemannSurface(y^2-x^3+1, prec=p)
3279
+ sage: divisor = [(-1, (Infinity, 0)), (1, (1, 0))]
3280
+ sage: AJ = S.abel_jacobi(divisor)
3281
+ sage: AJx2 = [2*z for z in AJ]
3282
+ sage: vector(AJx2).norm() # abs tol 1e-10
3283
+ 2.4286506478875809114000865640
3284
+ sage: bool(S.reduce_over_period_lattice(AJx2).norm() < 1e-10)
3285
+ True
3286
+ sage: S = RiemannSurface(y^2-x^4+1, prec=p)
3287
+ sage: divisor = [(-1, (-1, 0)), (1, (1, 0))]
3288
+ sage: AJ = S.abel_jacobi(divisor)
3289
+ sage: AJx2 = [2*z for z in AJ]
3290
+ sage: bool(S.reduce_over_period_lattice(AJx2).norm() < 1e-10)
3291
+ True
3292
+ """
3293
+ #####
3294
+ fcd = self._fastcall_cohomology_basis
3295
+
3296
+ if self._integration_method == "heuristic":
3297
+ line_int = lambda edge: self.simple_vector_line_integral(edge, fcd)
3298
+ else:
3299
+ bd = self._cohomology_basis_bounding_data
3300
+ line_int = lambda edge: self.rigorous_line_integral(edge, fcd, bd)
3301
+ #####
3302
+ B = self._basepoint
3303
+ zP, wP = P
3304
+
3305
+ try:
3306
+ Inf = bool(zP == zP.parent()(Infinity))
3307
+ except TypeError:
3308
+ Inf = False
3309
+
3310
+ if Inf:
3311
+ zV = self._vertices[B[0]]
3312
+ if zV == 0:
3313
+ zV += 1
3314
+ upstairs_edge = (P, zV)
3315
+ ci = bool(self._CC(Infinity) in self._differentials_branch_locus)
3316
+ AJ, endgs = self._integrate_differentials_iteratively(
3317
+ upstairs_edge, cutoff_individually=ci
3318
+ )
3319
+ AJ = -AJ
3320
+ g0e = endgs[0]
3321
+ ws = self.w_values(zV)
3322
+ g0s = [self.cohomology_basis()[0](zV, wi) / self._dfdw(zV, wi) for wi in ws]
3323
+ W_index = find_closest_element(g0e, g0s)
3324
+ if (
3325
+ g0e
3326
+ - self.cohomology_basis()[0](zV, ws[W_index])
3327
+ / self._dfdw(zV, ws[W_index])
3328
+ ).abs() > 1e-10:
3329
+ raise ConvergenceError(
3330
+ "Integrand continuation failed to get representative values, higher precision required."
3331
+ )
3332
+ V_index = B[0]
3333
+ else:
3334
+ zP = self._CC(zP)
3335
+ wP = self._CC(wP)
3336
+ V_index = find_closest_element(zP, self._vertices)
3337
+
3338
+ if zP == self._vertices[V_index]:
3339
+ W_index = find_closest_element(wP, self._wvalues[V_index])
3340
+ AJ = 0
3341
+ else:
3342
+ b_index = find_closest_element(zP, self.branch_locus)
3343
+ b = self.branch_locus[b_index]
3344
+ # bl = self.branch_locus+self._differentials_branch_locus
3345
+ # b_index = find_closest_element(zP, bl)
3346
+ # b = bl[b_index]
3347
+
3348
+ scale = max(b.abs() for b in self.branch_locus)
3349
+ d1 = self._CC(1e-2) * scale
3350
+
3351
+ # We choose the first vertex we want to go to.
3352
+ # If the closest vertex is closer than the nearest branch point, just take that vertex
3353
+ # otherwise we need something smarter.
3354
+ delta = self._RR(2)**(-self._prec + 1)
3355
+ if not (
3356
+ (zP - self._vertices[V_index]).abs() < (zP - b).abs()
3357
+ or (zP - b).abs() <= delta
3358
+ ):
3359
+ region = self.voronoi_diagram.regions[
3360
+ self.voronoi_diagram.point_region[b_index]
3361
+ ]
3362
+ args = [
3363
+ (self._vertices[i] - zP).argument() - (b - zP).argument()
3364
+ for i in region
3365
+ ]
3366
+ suitable_vertex_indices = [
3367
+ region[i]
3368
+ for i in range(len(region))
3369
+ if args[i].abs() - self._RR.pi() / 2 >= -self._RR(1e-15)
3370
+ ]
3371
+ suitable_vertices = [
3372
+ self._vertices[i] for i in suitable_vertex_indices
3373
+ ]
3374
+ if suitable_vertices == []:
3375
+ raise ValueError(
3376
+ "There is no satisfactory choice of V for zP={}".format(zP)
3377
+ )
3378
+ V_index = suitable_vertex_indices[
3379
+ find_closest_element(zP, suitable_vertices)
3380
+ ]
3381
+ #####
3382
+ zV = self._vertices[V_index]
3383
+
3384
+ if (zP - b).abs() >= d1 or b in self._differentials_branch_locus:
3385
+ wP_index = find_closest_element(wP, self.w_values(zP))
3386
+ d_edge = (zP, zV)
3387
+ u_edge = ((zP, wP_index), (zV,))
3388
+ initial_continuation = self.homotopy_continuation(d_edge)
3389
+ AJ = -line_int(u_edge)
3390
+
3391
+ w_end = initial_continuation[-1][1][wP_index]
3392
+ W_index = find_closest_element(w_end, self._wvalues[V_index])
3393
+ else:
3394
+ zs = zP
3395
+ ws = wP
3396
+
3397
+ #####
3398
+ # Here we need a block of code to change the vertex if the path
3399
+ # from zP to zV would go through a ramification point of the integrands
3400
+ fl = [
3401
+ c
3402
+ for c in self._differentials_branch_locus
3403
+ if not c == self._CC(Infinity)
3404
+ ]
3405
+ ts = [
3406
+ ((c - zP) * (zV - zP).conjugate()).real()
3407
+ / (zP - zV).norm()**2
3408
+ for c in fl
3409
+ ]
3410
+ ds = [
3411
+ (fl[i] - zP - ts[i] * (zV - zP)).abs()
3412
+ for i in range(len(ts))
3413
+ if (ts[i] >= 0 and ts[i] <= 1)
3414
+ ]
3415
+ while len(ds) >= 1 and min(ds) < delta:
3416
+ V_index = suitable_vertex_indices.pop()
3417
+ zV = self._vertices[V_index]
3418
+ ts = [
3419
+ ((c - zP) * (zV - zP).conjugate()).real()
3420
+ / (zP - zV).norm()**2
3421
+ for c in fl
3422
+ ]
3423
+ ds = [
3424
+ (fl[i] - zP - ts[i] * (zV - zP)).abs()
3425
+ for i in range(len(ts))
3426
+ if (ts[i] >= 0 and ts[i] <= 1)
3427
+ ]
3428
+ #####
3429
+
3430
+ while self._dfdw(zs, ws).abs() == 0:
3431
+ zs = zs + delta * (zV - zs) / (zV - zs).abs() / 2
3432
+ ws_list = self.w_values(zs)
3433
+ wP_index = find_closest_element(ws, ws_list)
3434
+ ws = ws_list[wP_index]
3435
+ upstairs_edge = ((zs, ws), zV)
3436
+ AJ, endgs = self._integrate_differentials_iteratively(
3437
+ upstairs_edge, cutoff_individually=False
3438
+ )
3439
+ AJ = -AJ
3440
+ g0e = endgs[0]
3441
+
3442
+ ws = self.w_values(zV)
3443
+ g0s = [
3444
+ self.cohomology_basis()[0](zV, wi) / self._dfdw(zV, wi)
3445
+ for wi in ws
3446
+ ]
3447
+ W_index = find_closest_element(g0e, g0s)
3448
+ if (
3449
+ g0e
3450
+ - self.cohomology_basis()[0](zV, ws[W_index])
3451
+ / self._dfdw(zV, ws[W_index])
3452
+ ).abs() > 1e-10:
3453
+ raise ConvergenceError(
3454
+ "Integrand continuation failed to get representative values, higher precision required."
3455
+ )
3456
+
3457
+ uV_index = (V_index, W_index)
3458
+ #####
3459
+ G = self.upstairs_graph()
3460
+ path = G.shortest_path(B, uV_index)
3461
+ edges = [(path[i], path[i + 1]) for i in range(len(path) - 1)]
3462
+ #####
3463
+ for e in edges:
3464
+ if e[1][0] > e[0][0]:
3465
+ s = 1
3466
+ else:
3467
+ s = -1
3468
+ e = tuple(reversed(e))
3469
+ try:
3470
+ AJ += s * self._integral_dict[e]
3471
+ except KeyError:
3472
+ Ie = line_int(e)
3473
+ self._integral_dict[e] = Ie
3474
+ AJ += s * Ie
3475
+ return AJ
3476
+
3477
+ def abel_jacobi(self, divisor, verbose=False):
3478
+ r"""
3479
+ Return the Abel-Jacobi map of ``divisor``.
3480
+
3481
+ Return a representative of the Abel-Jacobi map of a divisor with basepoint
3482
+ ``self._basepoint``.
3483
+
3484
+ INPUT:
3485
+
3486
+ - ``divisor`` -- list. A list with each entry a tuple of the form ``(v, P)``,
3487
+ where ``v`` is the valuation of the divisor at point ``P``, ``P`` as per
3488
+ the input to :meth:`_aj_based`.
3489
+
3490
+ - ``verbose`` -- logical (default: ``False``); whether to report the progress
3491
+ of the computation, in terms of how many elements of the list ``divisor``
3492
+ have been completed
3493
+
3494
+ OUTPUT: a vector of length ``self.genus``
3495
+
3496
+ EXAMPLES:
3497
+
3498
+ We can test that the Abel-Jacobi map between two branchpoints of a
3499
+ superelliptic curve of degree `p` is a `p`-torsion point in the Jacobian::
3500
+
3501
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3502
+ sage: R.<x,y> = QQ[]
3503
+ sage: p = 4
3504
+ sage: S = RiemannSurface(y^p-x^4+1, prec=100)
3505
+ sage: divisor = [(-1, (-1, 0)), (1, (1, 0))]
3506
+ sage: AJ = S.abel_jacobi(divisor) # long time (15 seconds)
3507
+ sage: AJxp = [p*z for z in AJ] # long time
3508
+ sage: bool(S.reduce_over_period_lattice(AJxp).norm()<1e-7) # long time
3509
+ True
3510
+ """
3511
+ if isinstance(divisor, FunctionFieldDivisor):
3512
+ divisor = self.divisor_to_divisor_list(divisor)
3513
+ ans = 0
3514
+ n = len(divisor)
3515
+ for i in range(n):
3516
+ v, p = divisor[i]
3517
+ if verbose:
3518
+ print("starting computation for p = {}".format(p))
3519
+ ans += v * self._aj_based(p)
3520
+ if verbose:
3521
+ print(
3522
+ "Done, {}% complete".format(numerical_approx(100 * (i + 1) / n, 11))
3523
+ )
3524
+ return ans
3525
+
3526
+ def reduce_over_period_lattice(
3527
+ self, vector, method='ip', b=None, r=None, normalised=False
3528
+ ):
3529
+ r"""
3530
+ Reduce a vector over the period lattice.
3531
+
3532
+ Given a vector of length ``self.genus``, this method returns a vector
3533
+ in the same orbit of the period lattice that is short. There are two
3534
+ possible methods, ``'svp'`` which returns a certified shortest vector,
3535
+ but can be much slower for higher genus curves, and ``'ip'``, which is
3536
+ faster but not guaranteed to return the shortest vector. In general the
3537
+ latter will perform well when the lattice basis vectors are of similar
3538
+ size.
3539
+
3540
+ INPUT:
3541
+
3542
+ - ``vector`` -- vector. A vector of length ``self.genus`` to reduce over
3543
+ the lattice
3544
+
3545
+ - ``method`` -- string (default: ``'ip'``) specifying the method
3546
+ to use to reduce the vector; the options are ``'ip'`` and ``'svp'``
3547
+
3548
+ - ``b`` -- integer (default provided); as for
3549
+ :meth:`homomorphism_basis`, and used in its invocation if
3550
+ (re)calculating said basis
3551
+
3552
+ - ``r`` -- integer (default: ``b/4``); as for
3553
+ :meth:`homomorphism_basis`, and used in its invocation if
3554
+ (re)calculating said basis
3555
+
3556
+ - ``normalised`` -- logical (default: ``False``); whether to use the
3557
+ period matrix with the differentials normalised s.t. the `A`-matrix
3558
+ is the identity.
3559
+
3560
+ OUTPUT:
3561
+
3562
+ Complex vector of length ``self.genus`` in the same orbit as ``vector``
3563
+ in the lattice.
3564
+
3565
+ EXAMPLES:
3566
+
3567
+ We can check that the lattice basis vectors themselves are reduced to
3568
+ zero::
3569
+
3570
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3571
+ sage: R.<x,y> = QQ[]
3572
+ sage: S = RiemannSurface(y^2 - x^5 + 1)
3573
+ sage: epsilon = S._RR(2)^(-S._prec+1)
3574
+ sage: for vector in S.period_matrix().columns():
3575
+ ....: print(bool(S.reduce_over_period_lattice(vector).norm()<epsilon))
3576
+ True
3577
+ True
3578
+ True
3579
+ True
3580
+
3581
+ We can also check that the method ``'svp'`` always gives a smaller norm
3582
+ than ``'ip'``::
3583
+
3584
+ sage: for vector in S.period_matrix().columns():
3585
+ ....: n1 = S.reduce_over_period_lattice(vector).norm()
3586
+ ....: n2 = S.reduce_over_period_lattice(vector, method='svp').norm()
3587
+ ....: print(bool(n2<=n1))
3588
+ True
3589
+ True
3590
+ True
3591
+ True
3592
+ """
3593
+ if not len(vector) == self.genus:
3594
+ raise ValueError("Input vector needs to be of length {}".format(self.genus))
3595
+
3596
+ VR = VectorSpace(self._RR, 2 * self.genus)
3597
+ VC = VectorSpace(self._CC, self.genus)
3598
+ I = self._CC(0, 1)
3599
+ PM = self.period_matrix()
3600
+
3601
+ if normalised:
3602
+ AM = PM[:, 0 : self.genus]
3603
+ AInv = numerical_inverse(AM)
3604
+ PM = AInv * PM
3605
+
3606
+ if method == "svp":
3607
+ H = max(
3608
+ max(z.real_part().abs() for z in vector),
3609
+ max(z.imag_part().abs() for z in vector),
3610
+ )
3611
+ if b is None:
3612
+ b = self._prec - 5 - H.log2().floor()
3613
+ if r is None:
3614
+ r = b // 4
3615
+ S = 2**b
3616
+ if H * S > 2**(self._prec - 4):
3617
+ raise ValueError("insufficient precision for b=%s" % b)
3618
+
3619
+ def C2Z(v):
3620
+ vR = [(S * z.real_part()).round() for z in v]
3621
+ vR += [(S * z.imag_part()).round() for z in v]
3622
+ return vR
3623
+
3624
+ M = Matrix(
3625
+ ZZ, 2 * self.genus, 2 * self.genus, [C2Z(c) for c in PM.columns()]
3626
+ )
3627
+ u = C2Z(vector)
3628
+ L = IntegerLattice(M)
3629
+ u = VR(u) - VR(L.closest_vector(u))
3630
+ reduced = VC(
3631
+ [self._CC(u[i] + I * u[i + self.genus]) / S for i in range(self.genus)]
3632
+ )
3633
+
3634
+ elif method == "ip":
3635
+
3636
+ def C2R(v):
3637
+ return VR([z.real_part() for z in v] + [z.imag_part() for z in v])
3638
+
3639
+ u = C2R(vector)
3640
+ basis_vecs = [C2R(c) for c in PM.columns()]
3641
+ M = Matrix([[ei.dot_product(ej) for ei in basis_vecs] for ej in basis_vecs])
3642
+ v_dot_e = VR([u.dot_product(e) for e in basis_vecs])
3643
+ coeffs = M.solve_right(v_dot_e)
3644
+ u -= sum([t.round() * e for t, e in zip(coeffs, basis_vecs)])
3645
+ reduced = VC(
3646
+ [self._CC(u[i] + I * u[i + self.genus]) for i in range(self.genus)]
3647
+ )
3648
+ else:
3649
+ raise ValueError("Must give a valid method.")
3650
+
3651
+ return reduced
3652
+
3653
+ def curve(self):
3654
+ r"""
3655
+ Return the curve from which this Riemann surface is obtained.
3656
+
3657
+ Riemann surfaces explicitly obtained from a curve return that same object.
3658
+ For others, the curve is constructed and cached, so that an identical curve is
3659
+ returned upon subsequent calls.
3660
+
3661
+ OUTPUT: curve from which Riemann surface is obtained
3662
+
3663
+ EXAMPLES::
3664
+
3665
+ sage: R.<x,y> = QQ[]
3666
+ sage: C = Curve( y^3+x^3-1)
3667
+ sage: S = C.riemann_surface()
3668
+ sage: S.curve() is C
3669
+ True
3670
+ """
3671
+ if self._curve is None:
3672
+ self._curve = Curve(self.f)
3673
+ return self._curve
3674
+
3675
+ def places_at_branch_locus(self):
3676
+ r"""
3677
+ Return the places above the branch locus.
3678
+
3679
+ Return a list of the of places above the branch locus. This must be
3680
+ done over the base ring, and so the places are given in terms of the
3681
+ factors of the discriminant. Currently, this method only works when
3682
+ ``self._R.base_ring() == QQ`` as for other rings, the function field
3683
+ for ``Curve(self.f)`` is not implemented. To go from these divisors to
3684
+ a divisor list, see :meth:`divisor_to_divisor_list`.
3685
+
3686
+ OUTPUT:
3687
+
3688
+ List of places of the functions field ``Curve(self.f).function_field()``.
3689
+
3690
+ EXAMPLES::
3691
+
3692
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3693
+ sage: R.<x,y> = QQ[]
3694
+ sage: S = RiemannSurface(25*(x^4+y^4+1) - 34*(x^2*y^2+x^2+y^2))
3695
+ sage: S.places_at_branch_locus()
3696
+ [Place (x - 2, (x - 2)*y, y^2 - 17/5, y^3 - 17/5*y),
3697
+ Place (x + 2, (x + 2)*y, y^2 - 17/5, y^3 - 17/5*y),
3698
+ Place (x - 1/2, (x - 1/2)*y, y^2 - 17/20, y^3 - 17/20*y),
3699
+ Place (x + 1/2, (x + 1/2)*y, y^2 - 17/20, y^3 - 17/20*y),
3700
+ Place (x^4 - 34/25*x^2 + 1, y, y^2, y^3),
3701
+ Place (x^4 - 34/25*x^2 + 1, (x^4 - 34/25*x^2 + 1)*y, y^2 - 34/25*x^2 - 34/25, y^3 + (-34/25*x^2 - 34/25)*y)]
3702
+ """
3703
+ BP = []
3704
+ K = self._R.base_ring()
3705
+ if K is not QQ:
3706
+ raise NotImplementedError
3707
+ C = self.curve()
3708
+ KC = C.function_field()
3709
+ g0, g1 = self._R.gens()
3710
+ Kb = FunctionField(K, str(g0))
3711
+ MO = Kb.maximal_order()
3712
+ BP = []
3713
+ for x in self._discriminant.factor():
3714
+ fac = x[0](g0, 0)
3715
+ p0 = MO.ideal(fac).place()
3716
+ BP += KC.places_above(p0)
3717
+ return BP
3718
+
3719
+ def strong_approximation(self, divisor, S):
3720
+ r"""
3721
+ Apply the method of strong approximation to a divisor.
3722
+
3723
+ As described in [Neu2018]_, apply the method of strong approximation to
3724
+ ``divisor`` with list of places to avoid ``S``. Currently, this method
3725
+ only works when ``self._R.base_ring() == QQ`` as for other rings, the function
3726
+ field for ``Curve(self.f)`` is not implemented.
3727
+
3728
+ INPUT:
3729
+
3730
+ - ``divisor`` -- an element of ``Curve(self.f).function_field().divisor_group()``
3731
+
3732
+ - ``S`` -- list of places to avoid
3733
+
3734
+ OUTPUT:
3735
+
3736
+ A tuple ``(D, B)``, where ``D`` is a new divisor, linearly equivalent
3737
+ to ``divisor``, but not intersecting ``S``, and ``B`` is a list of tuples
3738
+ ``(v, b)`` where ``b`` are the functions giving the linear equivalence,
3739
+ added with multiplicity ``v``.
3740
+
3741
+ EXAMPLES::
3742
+
3743
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3744
+ sage: R.<x,y> = QQ[]
3745
+ sage: S = RiemannSurface(y^2-x^3+1)
3746
+ sage: avoid = Curve(S.f).places_at_infinity()
3747
+ sage: D = 1*avoid[0]
3748
+ sage: S.strong_approximation(D, avoid)
3749
+ (- Place (x - 2, (x - 2)*y)
3750
+ + Place (x - 1, y)
3751
+ + Place (x^2 + x + 1, y),
3752
+ [(1, (1/(x - 2))*y)])
3753
+ """
3754
+ # One would standardly expect to run this with
3755
+ # S = Curve(self.f).places_at_infinity()
3756
+ # or
3757
+ # S = Curve(self.f).places_at_infinity()+self.places_at_branch_locus()
3758
+ #
3759
+ # To avoid current implementation issues with going between divisors
3760
+ # and divisor lists, we implement a method that handles only divisors
3761
+ K = self._R.base_ring()
3762
+ if not K == QQ:
3763
+ raise NotImplementedError
3764
+ C = self.curve()
3765
+ KC = C.function_field()
3766
+ g0, g1 = self._R.gens()
3767
+ Kb = FunctionField(K, str(g0))
3768
+ MO = Kb.maximal_order()
3769
+
3770
+ D_base = -sum(S)
3771
+
3772
+ rr = self._vertices[self._basepoint[0]].real()
3773
+ rr = rr.ceil()
3774
+ Fac = g0 - K(rr)
3775
+ p0 = MO.ideal(Fac).place()
3776
+ q0 = KC.places_above(p0)[0]
3777
+
3778
+ new_divisor = divisor
3779
+ B = []
3780
+ for p in divisor.support():
3781
+ if p in S:
3782
+ v = divisor.valuation(p)
3783
+ i = S.index(p)
3784
+ Q = S[i]
3785
+ D = D_base + Q
3786
+ if not D:
3787
+ ios = self.genus
3788
+ else:
3789
+ ios = len(D.basis_differential_space())
3790
+ while ios > 0:
3791
+ D += q0
3792
+ ios = len(D.basis_differential_space())
3793
+ LD = D.function_space()
3794
+ V = LD[0]
3795
+ a = LD[1]
3796
+ b = 0
3797
+ for s in S:
3798
+ LDps = (D + s).function_space()
3799
+ Vps = LDps[0]
3800
+ ebd = [LDps[2](a(g)) for g in V.gens()]
3801
+ U = Vps.span(ebd)
3802
+ Quot = Vps.quotient(U)
3803
+ bs = LDps[1](Quot.lift(Quot.basis()[0]))
3804
+ b += bs
3805
+ B.append((v, b))
3806
+ new_divisor += v * b.divisor()
3807
+ return new_divisor, B
3808
+
3809
+ def divisor_to_divisor_list(self, divisor, eps=None):
3810
+ r"""
3811
+ Turn a divisor into a list for :meth:`abel_jacobi`.
3812
+
3813
+ Given ``divisor`` in ``Curve(self.f).function_field().divisor_group()``,
3814
+ consisting of places above finite points in the base, return an equivalent
3815
+ divisor list suitable for input into :meth:`abel_jacboi`.
3816
+
3817
+ INPUT:
3818
+
3819
+ - ``divisor`` -- an element of ``Curve(self.f).function_field().divisor_group()``
3820
+ - ``eps`` -- real number (optional); tolerance used to determine whether a complex
3821
+ number is close enough to a root of a polynomial
3822
+
3823
+ OUTPUT:
3824
+
3825
+ A list with elements of the form ``(v, (z, w))`` representing the finite places.
3826
+
3827
+ EXAMPLES::
3828
+
3829
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface
3830
+ sage: R.<x,y> = QQ[]
3831
+ sage: S = RiemannSurface(y^2-x^3+1)
3832
+ sage: D = sum(S.places_at_branch_locus())
3833
+ sage: S.divisor_to_divisor_list(D)
3834
+ [(1, (1.00000000000000, 0.000000000000000)),
3835
+ (1, (-0.500000000000000 - 0.866025403784439*I, 0.000000000000000)),
3836
+ (1, (-0.500000000000000 + 0.866025403784439*I, 0.000000000000000))]
3837
+
3838
+ .. TODO::
3839
+
3840
+ Currently this method can only handle places above finite points in
3841
+ the base. It would be useful to extend this to allow for places at
3842
+ infinity.
3843
+ """
3844
+ # If this error bound is too restrictive, this method might fail and
3845
+ # not return. One might want to change the way this error is handled.
3846
+ if not eps:
3847
+ eps = self._RR(2)**(-self._prec + 3)
3848
+ dl = []
3849
+
3850
+ PZ = PolynomialRing(self._R.base(), "z").fraction_field()
3851
+ RF = PolynomialRing(PZ, "w")
3852
+
3853
+ for d in divisor.support():
3854
+ if d.is_infinite_place():
3855
+ raise NotImplementedError(
3856
+ "Conversion of infinite places not implemented yet."
3857
+ )
3858
+ v = divisor.valuation(d)
3859
+ gs = d._prime.gens()
3860
+
3861
+ g0 = self._R(gs[0])
3862
+ gis = [
3863
+ sum([PZ(gi.list()[i]) * RF.gen()**i for i in range(len(gi.list()))])
3864
+ for gi in gs[1:]
3865
+ ]
3866
+
3867
+ rs = self._CCz(g0).roots()
3868
+ rys = []
3869
+
3870
+ for r, m in rs:
3871
+ ys = []
3872
+ for gi in gis:
3873
+ # This test is a bit clunky, it surely can be made more efficient.
3874
+ if ys:
3875
+ ers = min(gi(y, r).abs() for y in ys)
3876
+ else:
3877
+ ers = 1
3878
+
3879
+ if not ers <= eps:
3880
+ poly = self._CCw(gi(self._CCw.gen(0), r))
3881
+ if poly == 0:
3882
+ nys = []
3883
+ else:
3884
+ nys = poly.roots()
3885
+ ys.extend(ny[0] for ny in nys)
3886
+ rys.extend((v * m * n, (r, y)) for y, n in nys)
3887
+
3888
+ if rys:
3889
+ dl.extend(rys)
3890
+ else:
3891
+ for r, m in rs:
3892
+ ys = self._CCw(self.f(r, self._CCw.gen(0))).roots()
3893
+ dl.extend([(v * m * n, (r, y)) for y, n in ys])
3894
+ if not sum([v[0] for v in dl]) == divisor.degree():
3895
+ raise ValueError(
3896
+ "numerical instability, list of wrong degree, returning list {}".format(
3897
+ dl
3898
+ )
3899
+ )
3900
+ return dl
3901
+
3902
+
3903
+ def integer_matrix_relations(M1, M2, b=None, r=None):
3904
+ r"""
3905
+ Determine integer relations between complex matrices.
3906
+
3907
+ Given two square matrices with complex entries of size `g`, `h` respectively,
3908
+ numerically determine an (approximate) `\ZZ`-basis for the `2g \times 2h` matrices
3909
+ with integer entries of the shape `(D, B; C, A)` such that `B+M_1*A=(D+M_1*C)*M2`.
3910
+ By considering real and imaginary parts separately we obtain `2gh` equations
3911
+ with real coefficients in `4gh` variables. We scale the coefficients by a
3912
+ constant `2^b` and round them to integers, in order to obtain an integer
3913
+ system of equations. Standard application of LLL allows us to determine near
3914
+ solutions.
3915
+
3916
+ The user can specify the parameter `b`, but by default the system will
3917
+ choose a `b` based on the size of the coefficients and the precision with
3918
+ which they are given.
3919
+
3920
+ INPUT:
3921
+
3922
+ - ``M1`` -- square complex valued matrix
3923
+
3924
+ - ``M2`` -- square complex valued matrix of same size as ``M1``
3925
+
3926
+ - ``b`` -- integer (default provided); the equation coefficients are scaled
3927
+ by `2^b` before rounding to integers
3928
+
3929
+ - ``r`` -- integer (default: ``b/4``); the vectors found by LLL that satisfy
3930
+ the scaled equations to within `2^r` are reported as solutions
3931
+
3932
+ OUTPUT:
3933
+
3934
+ A list of `2g \times 2h` integer matrices that, for large enough `r`, `b-r`,
3935
+ generate the `\ZZ`-module of relevant transformations.
3936
+
3937
+ EXAMPLES::
3938
+
3939
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import integer_matrix_relations
3940
+ sage: M1 = M2 = matrix(CC, 2, 2, [CC(d).sqrt() for d in [2,-3,-3,-6]])
3941
+ sage: T = integer_matrix_relations(M1,M2)
3942
+ sage: id = parent(M1)(1)
3943
+ sage: M1t = [id.augment(M1) * t for t in T]
3944
+ sage: [((m[:,:2]^(-1)*m)[:,2:]-M2).norm() < 1e-13 for m in M1t]
3945
+ [True, True]
3946
+ """
3947
+ if not (M1.is_square() and M2.is_square()):
3948
+ raise ValueError("matrices need to be square")
3949
+ prec = min(M1.base_ring().precision(), M2.base_ring().precision())
3950
+ H = max(
3951
+ max(abs(m.real_part()) for m in M1.list() + M2.list()),
3952
+ max(abs(m.imag_part()) for m in M1.list() + M2.list()),
3953
+ )
3954
+ if b is None:
3955
+ b = prec - 5 - H.log2().floor()
3956
+ if r is None:
3957
+ r = b // 4
3958
+ S = 2**b
3959
+ if H * S > 2**(prec - 4):
3960
+ raise ValueError("insufficient precision for b=%s" % b)
3961
+ g1 = M1.ncols()
3962
+ g2 = M2.ncols()
3963
+ CC = (
3964
+ M1.base_ring()
3965
+ if (M1.base_ring().precision() <= M2.base_ring().precision())
3966
+ else M2.base_ring()
3967
+ )
3968
+ V = ["%s%s" % (n, i) for n in ["a", "b", "c", "d"] for i in range(1, 1 + g1 * g2)]
3969
+ R = PolynomialRing(CC, V)
3970
+ vars = R.gens()
3971
+ A = Matrix(R, g1, g2, vars[: g1 * g2])
3972
+ B = Matrix(R, g1, g2, vars[g1 * g2 : 2 * g1 * g2])
3973
+ C = Matrix(R, g1, g2, vars[2 * g1 * g2 : 3 * g1 * g2])
3974
+ D = Matrix(R, g1, g2, vars[3 * g1 * g2 : 4 * g1 * g2])
3975
+ W = ((M1 * A + B) - (M1 * C + D) * M2).list()
3976
+ vars = R.gens()
3977
+ mt = Matrix(ZZ, [[1 if i == j else 0 for j in range(4 * g1 * g2)] +
3978
+ [(S * w.monomial_coefficient(vi).real_part()).round() for w in W] +
3979
+ [(S * w.monomial_coefficient(vi).imag_part()).round() for w in W]
3980
+ for i, vi in enumerate(vars)])
3981
+ # we compute an LLL-reduced basis of this lattice:
3982
+ mtL = mt.LLL()
3983
+
3984
+ def vectomat(v):
3985
+ A = Matrix(g1, g2, v[: g1 * g2].list())
3986
+ B = Matrix(g1, g2, v[g1 * g2 : 2 * g1 * g2].list())
3987
+ C = Matrix(g1, g2, v[2 * g1 * g2 : 3 * g1 * g2].list())
3988
+ D = Matrix(g1, g2, v[3 * g1 * g2 : 4 * g1 * g2].list())
3989
+ return D.augment(B).stack(C.augment(A))
3990
+
3991
+ c = 2**r
3992
+ return [vectomat(v) for v in mtL if all(a.abs() <= c for a in v[g1 * g2 :])]
3993
+
3994
+
3995
+ class RiemannSurfaceSum(RiemannSurface):
3996
+ r"""
3997
+ Represent the disjoint union of finitely many Riemann surfaces.
3998
+
3999
+ Rudimentary class to represent disjoint unions of Riemann surfaces. Exists
4000
+ mainly (and this is the only functionality actually implemented) to
4001
+ represents direct products of the complex tori that arise as analytic
4002
+ Jacobians of Riemann surfaces.
4003
+
4004
+ INPUT:
4005
+
4006
+ - ``L`` -- list of RiemannSurface objects
4007
+
4008
+ EXAMPLES::
4009
+
4010
+ sage: _.<x> = QQ[]
4011
+ sage: SC = HyperellipticCurve(x^6-2*x^4+3*x^2-7).riemann_surface(prec=60)
4012
+ sage: S1 = HyperellipticCurve(x^3-2*x^2+3*x-7).riemann_surface(prec=60)
4013
+ sage: S2 = HyperellipticCurve(1-2*x+3*x^2-7*x^3).riemann_surface(prec=60)
4014
+ sage: len(SC.homomorphism_basis(S1+S2))
4015
+ 2
4016
+ """
4017
+
4018
+ def __init__(self, L):
4019
+ r"""
4020
+ TESTS::
4021
+
4022
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
4023
+ sage: R.<x,y> = QQ[]
4024
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
4025
+ sage: S2 = RiemannSurface(y^2-x^3-x-5)
4026
+ sage: S = RiemannSurfaceSum([S1,S2])
4027
+ sage: S.riemann_matrix() == S1.riemann_matrix().block_sum(S2.riemann_matrix())
4028
+ True
4029
+ """
4030
+ if not all(isinstance(l, RiemannSurface) for l in L):
4031
+ raise ValueError("summands must be RiemannSurface objects")
4032
+ prec = min(l._prec for l in L)
4033
+ self._prec = prec
4034
+ self.genus = sum(s.genus for s in L)
4035
+ it = iter(L)
4036
+ s = next(it)
4037
+ g = s.genus
4038
+ PM = s.period_matrix()
4039
+ PM1 = PM[:g, :g]
4040
+ PM2 = PM[:g, g : 2 * g]
4041
+ tau = s.riemann_matrix()
4042
+ for s in it:
4043
+ g = s.genus
4044
+ PM = s.period_matrix()
4045
+ PM1 = PM1.block_sum(PM[:g, :g])
4046
+ PM2 = PM2.block_sum(PM[:g, g : 2 * g])
4047
+ tau = tau.block_sum(s.riemann_matrix())
4048
+ self.PM = block_matrix([[PM1, PM2]], subdivide=False)
4049
+ self.tau = tau
4050
+
4051
+ def period_matrix(self):
4052
+ r"""
4053
+ Return the period matrix of the surface.
4054
+
4055
+ This is just the diagonal block matrix constructed from the period
4056
+ matrices of the constituents.
4057
+
4058
+ EXAMPLES::
4059
+
4060
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
4061
+ sage: R.<x,y> = QQ[]
4062
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
4063
+ sage: S2 = RiemannSurface(y^2-x^3-x-5)
4064
+ sage: S = RiemannSurfaceSum([S1,S2])
4065
+ sage: S1S2 = S1.period_matrix().block_sum(S2.period_matrix())
4066
+ sage: S.period_matrix() == S1S2[[0,1],[0,2,1,3]]
4067
+ True
4068
+ """
4069
+ return self.PM
4070
+
4071
+ def riemann_matrix(self):
4072
+ r"""
4073
+ Return the normalized period matrix of the surface.
4074
+
4075
+ This is just the diagonal block matrix constructed from the Riemann
4076
+ matrices of the constituents.
4077
+
4078
+ EXAMPLES::
4079
+
4080
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
4081
+ sage: R.<x,y> = QQ[]
4082
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
4083
+ sage: S2 = RiemannSurface(y^2-x^3-x-5)
4084
+ sage: S = RiemannSurfaceSum([S1,S2])
4085
+ sage: S.riemann_matrix() == S1.riemann_matrix().block_sum(S2.riemann_matrix())
4086
+ True
4087
+ """
4088
+ return self.tau
4089
+
4090
+ def __repr__(self) -> str:
4091
+ r"""
4092
+ Return string describing Riemann surface sum.
4093
+
4094
+ EXAMPLES::
4095
+
4096
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
4097
+ sage: R.<x,y> = QQ[]
4098
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
4099
+ sage: S2 = RiemannSurface(y^2-x^3-x-5)
4100
+ sage: RiemannSurfaceSum([S1,S2])
4101
+ Riemann surface sum with period lattice of rank 4
4102
+ """
4103
+ return "Riemann surface sum with period lattice of rank " + str(2 * self.genus)
4104
+
4105
+ def __add__(self, other):
4106
+ r"""
4107
+ Return the disjoint union of the Riemann surface and the other argument.
4108
+
4109
+ EXAMPLES::
4110
+
4111
+ sage: from sage.schemes.riemann_surfaces.riemann_surface import RiemannSurface, RiemannSurfaceSum
4112
+ sage: R.<x,y> = QQ[]
4113
+ sage: S1 = RiemannSurface(y^2-x^3-x-1)
4114
+ sage: S1+S1+S1
4115
+ Riemann surface sum with period lattice of rank 6
4116
+ """
4117
+ return RiemannSurfaceSum([self, other])