passagemath-schemes 10.6.40__cp314-cp314-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.
Potentially problematic release.
This version of passagemath-schemes might be problematic. Click here for more details.
- passagemath_schemes/.dylibs/libflint.22.0.dylib +0 -0
- passagemath_schemes/.dylibs/libgmp.10.dylib +0 -0
- passagemath_schemes/.dylibs/libgmpxx.4.dylib +0 -0
- passagemath_schemes/.dylibs/libmpfr.6.dylib +0 -0
- passagemath_schemes/__init__.py +3 -0
- passagemath_schemes-10.6.40.dist-info/METADATA +204 -0
- passagemath_schemes-10.6.40.dist-info/METADATA.bak +205 -0
- passagemath_schemes-10.6.40.dist-info/RECORD +314 -0
- passagemath_schemes-10.6.40.dist-info/WHEEL +6 -0
- passagemath_schemes-10.6.40.dist-info/top_level.txt +3 -0
- sage/all__sagemath_schemes.py +23 -0
- sage/databases/all__sagemath_schemes.py +7 -0
- sage/databases/cremona.py +1723 -0
- sage/dynamics/all__sagemath_schemes.py +2 -0
- sage/dynamics/arithmetic_dynamics/affine_ds.py +1083 -0
- sage/dynamics/arithmetic_dynamics/all.py +14 -0
- sage/dynamics/arithmetic_dynamics/berkovich_ds.py +1101 -0
- sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py +1543 -0
- sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +2426 -0
- sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +1169 -0
- sage/dynamics/arithmetic_dynamics/generic_ds.py +663 -0
- sage/dynamics/arithmetic_dynamics/product_projective_ds.py +339 -0
- sage/dynamics/arithmetic_dynamics/projective_ds.py +9558 -0
- sage/dynamics/arithmetic_dynamics/projective_ds_helper.cpython-314-darwin.so +0 -0
- sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +301 -0
- sage/dynamics/arithmetic_dynamics/wehlerK3.py +2576 -0
- sage/lfunctions/all.py +18 -0
- sage/lfunctions/dokchitser.py +745 -0
- sage/lfunctions/pari.py +818 -0
- sage/lfunctions/zero_sums.cpython-314-darwin.so +0 -0
- sage/lfunctions/zero_sums.pyx +1847 -0
- sage/modular/abvar/abvar.py +5135 -0
- sage/modular/abvar/abvar_ambient_jacobian.py +413 -0
- sage/modular/abvar/abvar_newform.py +244 -0
- sage/modular/abvar/all.py +8 -0
- sage/modular/abvar/constructor.py +186 -0
- sage/modular/abvar/cuspidal_subgroup.py +371 -0
- sage/modular/abvar/finite_subgroup.py +896 -0
- sage/modular/abvar/homology.py +720 -0
- sage/modular/abvar/homspace.py +998 -0
- sage/modular/abvar/lseries.py +415 -0
- sage/modular/abvar/morphism.py +935 -0
- sage/modular/abvar/torsion_point.py +274 -0
- sage/modular/abvar/torsion_subgroup.py +740 -0
- sage/modular/all.py +43 -0
- sage/modular/arithgroup/all.py +20 -0
- sage/modular/arithgroup/arithgroup_element.cpython-314-darwin.so +0 -0
- sage/modular/arithgroup/arithgroup_element.pyx +474 -0
- sage/modular/arithgroup/arithgroup_generic.py +1402 -0
- sage/modular/arithgroup/arithgroup_perm.py +2692 -0
- sage/modular/arithgroup/congroup.cpython-314-darwin.so +0 -0
- sage/modular/arithgroup/congroup.pyx +334 -0
- sage/modular/arithgroup/congroup_gamma.py +363 -0
- sage/modular/arithgroup/congroup_gamma0.py +692 -0
- sage/modular/arithgroup/congroup_gamma1.py +653 -0
- sage/modular/arithgroup/congroup_gammaH.py +1469 -0
- sage/modular/arithgroup/congroup_generic.py +628 -0
- sage/modular/arithgroup/congroup_sl2z.py +267 -0
- sage/modular/arithgroup/farey_symbol.cpython-314-darwin.so +0 -0
- sage/modular/arithgroup/farey_symbol.pyx +1066 -0
- sage/modular/arithgroup/tests.py +418 -0
- sage/modular/btquotients/all.py +4 -0
- sage/modular/btquotients/btquotient.py +3753 -0
- sage/modular/btquotients/pautomorphicform.py +2570 -0
- sage/modular/buzzard.py +100 -0
- sage/modular/congroup.py +29 -0
- sage/modular/congroup_element.py +13 -0
- sage/modular/cusps.py +1109 -0
- sage/modular/cusps_nf.py +1270 -0
- sage/modular/dims.py +569 -0
- sage/modular/dirichlet.py +3310 -0
- sage/modular/drinfeld_modform/all.py +2 -0
- sage/modular/drinfeld_modform/element.py +446 -0
- sage/modular/drinfeld_modform/ring.py +773 -0
- sage/modular/drinfeld_modform/tutorial.py +236 -0
- sage/modular/etaproducts.py +1065 -0
- sage/modular/hecke/algebra.py +746 -0
- sage/modular/hecke/all.py +20 -0
- sage/modular/hecke/ambient_module.py +1019 -0
- sage/modular/hecke/degenmap.py +119 -0
- sage/modular/hecke/element.py +325 -0
- sage/modular/hecke/hecke_operator.py +780 -0
- sage/modular/hecke/homspace.py +206 -0
- sage/modular/hecke/module.py +1767 -0
- sage/modular/hecke/morphism.py +174 -0
- sage/modular/hecke/submodule.py +989 -0
- sage/modular/hypergeometric_misc.cpython-314-darwin.so +0 -0
- sage/modular/hypergeometric_misc.pxd +4 -0
- sage/modular/hypergeometric_misc.pyx +166 -0
- sage/modular/hypergeometric_motive.py +2017 -0
- sage/modular/local_comp/all.py +2 -0
- sage/modular/local_comp/liftings.py +292 -0
- sage/modular/local_comp/local_comp.py +1071 -0
- sage/modular/local_comp/smoothchar.py +1825 -0
- sage/modular/local_comp/type_space.py +748 -0
- sage/modular/modform/all.py +30 -0
- sage/modular/modform/ambient.py +815 -0
- sage/modular/modform/ambient_R.py +177 -0
- sage/modular/modform/ambient_eps.py +306 -0
- sage/modular/modform/ambient_g0.py +124 -0
- sage/modular/modform/ambient_g1.py +204 -0
- sage/modular/modform/constructor.py +545 -0
- sage/modular/modform/cuspidal_submodule.py +708 -0
- sage/modular/modform/defaults.py +14 -0
- sage/modular/modform/eis_series.py +505 -0
- sage/modular/modform/eisenstein_submodule.py +663 -0
- sage/modular/modform/element.py +4131 -0
- sage/modular/modform/find_generators.py +59 -0
- sage/modular/modform/half_integral.py +154 -0
- sage/modular/modform/hecke_operator_on_qexp.py +247 -0
- sage/modular/modform/j_invariant.py +47 -0
- sage/modular/modform/l_series_gross_zagier.py +133 -0
- sage/modular/modform/l_series_gross_zagier_coeffs.cpython-314-darwin.so +0 -0
- sage/modular/modform/l_series_gross_zagier_coeffs.pyx +177 -0
- sage/modular/modform/notes.py +45 -0
- sage/modular/modform/numerical.py +514 -0
- sage/modular/modform/periods.py +14 -0
- sage/modular/modform/ring.py +1257 -0
- sage/modular/modform/space.py +1860 -0
- sage/modular/modform/submodule.py +118 -0
- sage/modular/modform/tests.py +64 -0
- sage/modular/modform/theta.py +110 -0
- sage/modular/modform/vm_basis.py +381 -0
- sage/modular/modform/weight1.py +220 -0
- sage/modular/modform_hecketriangle/abstract_ring.py +1932 -0
- sage/modular/modform_hecketriangle/abstract_space.py +2528 -0
- sage/modular/modform_hecketriangle/all.py +30 -0
- sage/modular/modform_hecketriangle/analytic_type.py +590 -0
- sage/modular/modform_hecketriangle/constructor.py +416 -0
- sage/modular/modform_hecketriangle/element.py +351 -0
- sage/modular/modform_hecketriangle/functors.py +752 -0
- sage/modular/modform_hecketriangle/graded_ring.py +541 -0
- sage/modular/modform_hecketriangle/graded_ring_element.py +2225 -0
- sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +3352 -0
- sage/modular/modform_hecketriangle/hecke_triangle_groups.py +1432 -0
- sage/modular/modform_hecketriangle/readme.py +1214 -0
- sage/modular/modform_hecketriangle/series_constructor.py +580 -0
- sage/modular/modform_hecketriangle/space.py +1037 -0
- sage/modular/modform_hecketriangle/subspace.py +423 -0
- sage/modular/modsym/all.py +17 -0
- sage/modular/modsym/ambient.py +3846 -0
- sage/modular/modsym/boundary.py +1420 -0
- sage/modular/modsym/element.py +336 -0
- sage/modular/modsym/g1list.py +178 -0
- sage/modular/modsym/ghlist.py +182 -0
- sage/modular/modsym/hecke_operator.py +73 -0
- sage/modular/modsym/manin_symbol.cpython-314-darwin.so +0 -0
- sage/modular/modsym/manin_symbol.pxd +5 -0
- sage/modular/modsym/manin_symbol.pyx +497 -0
- sage/modular/modsym/manin_symbol_list.py +1295 -0
- sage/modular/modsym/modsym.py +400 -0
- sage/modular/modsym/modular_symbols.py +384 -0
- sage/modular/modsym/p1list.cpython-314-darwin.so +0 -0
- sage/modular/modsym/p1list.pxd +29 -0
- sage/modular/modsym/p1list.pyx +1372 -0
- sage/modular/modsym/p1list_nf.py +1241 -0
- sage/modular/modsym/relation_matrix.py +591 -0
- sage/modular/modsym/relation_matrix_pyx.cpython-314-darwin.so +0 -0
- sage/modular/modsym/relation_matrix_pyx.pyx +108 -0
- sage/modular/modsym/space.py +2468 -0
- sage/modular/modsym/subspace.py +455 -0
- sage/modular/modsym/tests.py +375 -0
- sage/modular/multiple_zeta.py +2632 -0
- sage/modular/multiple_zeta_F_algebra.py +786 -0
- sage/modular/overconvergent/all.py +6 -0
- sage/modular/overconvergent/genus0.py +1878 -0
- sage/modular/overconvergent/hecke_series.py +1187 -0
- sage/modular/overconvergent/weightspace.py +778 -0
- sage/modular/pollack_stevens/all.py +4 -0
- sage/modular/pollack_stevens/distributions.py +874 -0
- sage/modular/pollack_stevens/fund_domain.py +1572 -0
- sage/modular/pollack_stevens/manin_map.py +859 -0
- sage/modular/pollack_stevens/modsym.py +1593 -0
- sage/modular/pollack_stevens/padic_lseries.py +417 -0
- sage/modular/pollack_stevens/sigma0.py +534 -0
- sage/modular/pollack_stevens/space.py +1076 -0
- sage/modular/quasimodform/all.py +3 -0
- sage/modular/quasimodform/element.py +845 -0
- sage/modular/quasimodform/ring.py +828 -0
- sage/modular/quatalg/all.py +3 -0
- sage/modular/quatalg/brandt.py +1642 -0
- sage/modular/ssmod/all.py +8 -0
- sage/modular/ssmod/ssmod.py +827 -0
- sage/rings/all__sagemath_schemes.py +1 -0
- sage/rings/polynomial/all__sagemath_schemes.py +1 -0
- sage/rings/polynomial/binary_form_reduce.py +585 -0
- sage/schemes/all.py +41 -0
- sage/schemes/berkovich/all.py +6 -0
- sage/schemes/berkovich/berkovich_cp_element.py +2582 -0
- sage/schemes/berkovich/berkovich_space.py +748 -0
- sage/schemes/curves/affine_curve.py +2928 -0
- sage/schemes/curves/all.py +33 -0
- sage/schemes/curves/closed_point.py +434 -0
- sage/schemes/curves/constructor.py +381 -0
- sage/schemes/curves/curve.py +542 -0
- sage/schemes/curves/plane_curve_arrangement.py +1283 -0
- sage/schemes/curves/point.py +463 -0
- sage/schemes/curves/projective_curve.py +3026 -0
- sage/schemes/curves/zariski_vankampen.py +1932 -0
- sage/schemes/cyclic_covers/all.py +2 -0
- sage/schemes/cyclic_covers/charpoly_frobenius.py +320 -0
- sage/schemes/cyclic_covers/constructor.py +137 -0
- sage/schemes/cyclic_covers/cycliccover_finite_field.py +1309 -0
- sage/schemes/cyclic_covers/cycliccover_generic.py +310 -0
- sage/schemes/elliptic_curves/BSD.py +1036 -0
- sage/schemes/elliptic_curves/Qcurves.py +592 -0
- sage/schemes/elliptic_curves/addition_formulas_ring.py +94 -0
- sage/schemes/elliptic_curves/all.py +49 -0
- sage/schemes/elliptic_curves/cardinality.py +609 -0
- sage/schemes/elliptic_curves/cm.py +1102 -0
- sage/schemes/elliptic_curves/constructor.py +1552 -0
- sage/schemes/elliptic_curves/ec_database.py +175 -0
- sage/schemes/elliptic_curves/ell_curve_isogeny.py +3972 -0
- sage/schemes/elliptic_curves/ell_egros.py +459 -0
- sage/schemes/elliptic_curves/ell_field.py +2836 -0
- sage/schemes/elliptic_curves/ell_finite_field.py +3359 -0
- sage/schemes/elliptic_curves/ell_generic.py +3760 -0
- sage/schemes/elliptic_curves/ell_local_data.py +1207 -0
- sage/schemes/elliptic_curves/ell_modular_symbols.py +775 -0
- sage/schemes/elliptic_curves/ell_number_field.py +4220 -0
- sage/schemes/elliptic_curves/ell_padic_field.py +107 -0
- sage/schemes/elliptic_curves/ell_point.py +4787 -0
- sage/schemes/elliptic_curves/ell_rational_field.py +7368 -0
- sage/schemes/elliptic_curves/ell_tate_curve.py +671 -0
- sage/schemes/elliptic_curves/ell_torsion.py +436 -0
- sage/schemes/elliptic_curves/ell_wp.py +352 -0
- sage/schemes/elliptic_curves/formal_group.py +760 -0
- sage/schemes/elliptic_curves/gal_reps.py +1459 -0
- sage/schemes/elliptic_curves/gal_reps_number_field.py +1669 -0
- sage/schemes/elliptic_curves/gp_simon.py +152 -0
- sage/schemes/elliptic_curves/heegner.py +7335 -0
- sage/schemes/elliptic_curves/height.py +2109 -0
- sage/schemes/elliptic_curves/hom.py +1406 -0
- sage/schemes/elliptic_curves/hom_composite.py +934 -0
- sage/schemes/elliptic_curves/hom_frobenius.py +522 -0
- sage/schemes/elliptic_curves/hom_scalar.py +531 -0
- sage/schemes/elliptic_curves/hom_sum.py +682 -0
- sage/schemes/elliptic_curves/hom_velusqrt.py +1290 -0
- sage/schemes/elliptic_curves/homset.py +271 -0
- sage/schemes/elliptic_curves/isogeny_class.py +1521 -0
- sage/schemes/elliptic_curves/isogeny_small_degree.py +2797 -0
- sage/schemes/elliptic_curves/jacobian.py +237 -0
- sage/schemes/elliptic_curves/kodaira_symbol.py +344 -0
- sage/schemes/elliptic_curves/kraus.py +1014 -0
- sage/schemes/elliptic_curves/lseries_ell.py +943 -0
- sage/schemes/elliptic_curves/mod5family.py +105 -0
- sage/schemes/elliptic_curves/mod_poly.py +197 -0
- sage/schemes/elliptic_curves/mod_sym_num.cpython-314-darwin.so +0 -0
- sage/schemes/elliptic_curves/mod_sym_num.pyx +3796 -0
- sage/schemes/elliptic_curves/modular_parametrization.py +305 -0
- sage/schemes/elliptic_curves/padic_lseries.py +1793 -0
- sage/schemes/elliptic_curves/padics.py +1816 -0
- sage/schemes/elliptic_curves/period_lattice.py +2234 -0
- sage/schemes/elliptic_curves/period_lattice_region.cpython-314-darwin.so +0 -0
- sage/schemes/elliptic_curves/period_lattice_region.pyx +722 -0
- sage/schemes/elliptic_curves/saturation.py +715 -0
- sage/schemes/elliptic_curves/sha_tate.py +1158 -0
- sage/schemes/elliptic_curves/weierstrass_morphism.py +1117 -0
- sage/schemes/elliptic_curves/weierstrass_transform.py +200 -0
- sage/schemes/hyperelliptic_curves/all.py +6 -0
- sage/schemes/hyperelliptic_curves/constructor.py +291 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +1914 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +192 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +954 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +1332 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +84 -0
- sage/schemes/hyperelliptic_curves/invariants.py +410 -0
- sage/schemes/hyperelliptic_curves/jacobian_endomorphism_utils.py +315 -0
- sage/schemes/hyperelliptic_curves/jacobian_g2.py +32 -0
- sage/schemes/hyperelliptic_curves/jacobian_generic.py +419 -0
- sage/schemes/hyperelliptic_curves/jacobian_homset.py +186 -0
- sage/schemes/hyperelliptic_curves/jacobian_morphism.py +875 -0
- sage/schemes/hyperelliptic_curves/kummer_surface.py +99 -0
- sage/schemes/hyperelliptic_curves/mestre.py +302 -0
- sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +3871 -0
- sage/schemes/jacobians/abstract_jacobian.py +277 -0
- sage/schemes/jacobians/all.py +2 -0
- sage/schemes/overview.py +161 -0
- sage/schemes/plane_conics/all.py +22 -0
- sage/schemes/plane_conics/con_field.py +1296 -0
- sage/schemes/plane_conics/con_finite_field.py +158 -0
- sage/schemes/plane_conics/con_number_field.py +456 -0
- sage/schemes/plane_conics/con_rational_field.py +406 -0
- sage/schemes/plane_conics/con_rational_function_field.py +580 -0
- sage/schemes/plane_conics/constructor.py +249 -0
- sage/schemes/plane_quartics/all.py +2 -0
- sage/schemes/plane_quartics/quartic_constructor.py +71 -0
- sage/schemes/plane_quartics/quartic_generic.py +73 -0
- sage/schemes/riemann_surfaces/all.py +1 -0
- sage/schemes/riemann_surfaces/riemann_surface.py +4117 -0
- sage_wheels/share/cremona/cremona_mini.db +0 -0
- sage_wheels/share/ellcurves/rank0 +30427 -0
- sage_wheels/share/ellcurves/rank1 +31871 -0
- sage_wheels/share/ellcurves/rank10 +6 -0
- sage_wheels/share/ellcurves/rank11 +6 -0
- sage_wheels/share/ellcurves/rank12 +1 -0
- sage_wheels/share/ellcurves/rank14 +1 -0
- sage_wheels/share/ellcurves/rank15 +1 -0
- sage_wheels/share/ellcurves/rank17 +1 -0
- sage_wheels/share/ellcurves/rank19 +1 -0
- sage_wheels/share/ellcurves/rank2 +2388 -0
- sage_wheels/share/ellcurves/rank20 +1 -0
- sage_wheels/share/ellcurves/rank21 +1 -0
- sage_wheels/share/ellcurves/rank22 +1 -0
- sage_wheels/share/ellcurves/rank23 +1 -0
- sage_wheels/share/ellcurves/rank24 +1 -0
- sage_wheels/share/ellcurves/rank28 +1 -0
- sage_wheels/share/ellcurves/rank3 +836 -0
- sage_wheels/share/ellcurves/rank4 +10 -0
- sage_wheels/share/ellcurves/rank5 +5 -0
- sage_wheels/share/ellcurves/rank6 +5 -0
- sage_wheels/share/ellcurves/rank7 +5 -0
- sage_wheels/share/ellcurves/rank8 +6 -0
- 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])
|