diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index 7a5b298ce07..01e50f43ea0 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -13,7 +13,7 @@ choose several base points and a system of paths joining them that generate all the necessary loops around the points of the discriminant. The group is generated by the free groups over these points, and -braids over this paths gives relations between these generators. +braids over these paths give relations between these generators. This big group presentation is simplified at the end. AUTHORS: @@ -37,27 +37,27 @@ # (at your option) any later version. # https://www.gnu.org/licenses/ # **************************************************************************** +import itertools +from copy import copy +from sage.combinat.permutation import Permutation +from sage.geometry.voronoi_diagram import VoronoiDiagram +from sage.graphs.graph import Graph from sage.groups.braid import BraidGroup +from sage.groups.free_group import FreeGroup from sage.groups.perm_gps.permgroup_named import SymmetricGroup -from sage.rings.rational_field import QQ -from sage.rings.qqbar import QQbar -from sage.parallel.decorate import parallel +from sage.misc.cachefunc import cached_function from sage.misc.flatten import flatten -from sage.groups.free_group import FreeGroup from sage.misc.misc_c import prod +from sage.parallel.decorate import parallel +from sage.rings.complex_interval_field import ComplexIntervalField from sage.rings.complex_mpfr import ComplexField +from sage.rings.qqbar import QQbar +from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField -from sage.rings.complex_interval_field import ComplexIntervalField -from sage.combinat.permutation import Permutation -import itertools -from sage.geometry.voronoi_diagram import VoronoiDiagram -from sage.graphs.graph import Graph -from sage.misc.cachefunc import cached_function -from copy import copy -roots_interval_cache = dict() +roots_interval_cache = {} def braid_from_piecewise(strands): @@ -67,7 +67,8 @@ def braid_from_piecewise(strands): INPUT: - ``strands`` -- a list of lists of tuples ``(t, c1, c2)``, where ``t`` - is a number between 0 and 1, and ``c1`` and ``c2`` are rationals or algebraic reals. + is a number between 0 and 1, and ``c1`` and ``c2`` are rationals + or algebraic reals. OUTPUT: @@ -123,11 +124,13 @@ def sgn(x, y): l1 = [a[0] for a in M] l2 = [a[1] for a in M] cruces = [] - for j in range(len(l2)): + for j, l2j in enumerate(l2): + l1j = l1[j] for k in range(j): - if l2[j] < l2[k]: - t = (l1[j][0] - l1[k][0])/((l2[k][0]-l2[j][0]) + (l1[j][0] - l1[k][0])) - s = sgn(l1[k][1]*(1 - t) + t*l2[k][1], l1[j][1]*(1 - t) + t*l2[j][1]) + if l2j < l2[k]: + t = (l1j[0] - l1[k][0]) / ((l2[k][0] - l2j[0]) + (l1j[0] - l1[k][0])) + s = sgn(l1[k][1] * (1 - t) + t * l2[k][1], + l1j[1] * (1 - t) + t * l2j[1]) cruces.append([t, k, j, s]) if cruces: cruces.sort() @@ -141,10 +144,10 @@ def sgn(x, y): while crossesl: crossesl.sort() c = crossesl.pop(0) - braid.append(c[3]*min(map(P, [c[1] + 1, c[2] + 1]))) + braid.append(c[3] * min(map(P, [c[1] + 1, c[2] + 1]))) P = G(Permutation([(c[1] + 1, c[2] + 1)])) * P - crossesl = [(P(cr[2]+1) - P(cr[1]+1), cr[1], cr[2], cr[3]) - for cr in crossesl] + crossesl = [(P(cr[2] + 1) - P(cr[1] + 1), + cr[1], cr[2], cr[3]) for cr in crossesl] B = BraidGroup(len(L)) return B(braid) @@ -185,8 +188,9 @@ def discrim(f): @cached_function def corrected_voronoi_diagram(points): r""" - Compute a Voronoi diagram of a set of points with rational coordinates, such - that the given points are granted to lie one in each bounded region. + Compute a Voronoi diagram of a set of points with rational coordinates. + + The given points are granted to lie one in each bounded region. INPUT: @@ -215,18 +219,18 @@ def corrected_voronoi_diagram(points): P(1/1000000, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices, P(2, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 5 vertices, P(7, 0): A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 2 vertices and 2 rays} - """ prec = 53 point_coordinates = [(p.real(), p.imag()) for p in points] while True: RF = RealField(prec) - apprpoints = {(QQ(RF(p[0])), QQ(RF(p[1]))): p for p in point_coordinates} + apprpoints = {(QQ(RF(p[0])), QQ(RF(p[1]))): p + for p in point_coordinates} added_points = 3 * max(map(abs, flatten(apprpoints))) + 1 - configuration = list(apprpoints.keys())+[(added_points, 0), - (-added_points, 0), - (0, added_points), - (0, -added_points)] + configuration = list(apprpoints.keys()) + [(added_points, 0), + (-added_points, 0), + (0, added_points), + (0, -added_points)] V = VoronoiDiagram(configuration) valid = True for r in V.regions().items(): @@ -277,19 +281,18 @@ def segments(points): (-5/2*I + 5/2, 5/2*I + 5/2), (5/2*I + 5/2, 144713866144468523/66040650000519163*I + 167101179147960739/132081300001038326)] - """ V = corrected_voronoi_diagram(tuple(points)) - res = set([]) + res = set() for region in V.regions().values(): if region.rays(): continue - segments = region.facets() - for s in segments: + for s in region.facets(): t = tuple((tuple(v.vector()) for v in s.vertices())) if t not in res and not tuple(reversed(t)) in res: res.add(t) - return [(r[0]+QQbar.gen()*r[1], s[0]+QQbar.gen()*s[1]) for (r, s) in res] + return [(r[0] + QQbar.gen() * r[1], + s[0] + QQbar.gen() * s[1]) for r, s in res] def followstrand(f, factors, x0, x1, y0a, prec=53): @@ -336,7 +339,6 @@ def followstrand(f, factors, x0, x1, y0a, prec=53): (0.5303300858899107, -1.0076747107983448, -0.17588022709184917), (0.7651655429449553, -1.015686131039112, -0.25243563967299404), (1.0, -1.026166099551513, -0.3276894025360433)] - """ if f.degree() == 1: CF = ComplexField(prec) @@ -349,7 +351,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53): CIF = ComplexIntervalField(prec) CC = ComplexField(prec) G = f.change_ring(QQbar).change_ring(CIF) - (x, y) = G.parent().gens() + x, y = G.parent().gens() g = G.subs({x: (1 - x) * CIF(x0) + x * CIF(x1)}) coefs = [] deg = g.total_degree() @@ -407,7 +409,6 @@ def newton(f, x0, i0): The interval `x_0-\frac{f(x_0)}{f'(I_0)}` - EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import newton @@ -422,9 +423,8 @@ def newton(f, x0, i0): (-0.0147727272727274, 0.00982142857142862) sage: n.imag().endpoints() (0.000000000000000, -0.000000000000000) - """ - return x0 - f(x0)/f.derivative()(i0) + return x0 - f(x0) / f.derivative()(i0) @parallel @@ -441,8 +441,9 @@ def roots_interval(f, x0): The intervals are taken as big as possible to be able to detect when two approximate roots of `f(x_0, y)` correspond to the same exact root. - The result is given as a dictionary, where the keys are approximations to the roots - with rational real and imaginary parts, and the values are intervals containing them. + The result is given as a dictionary, where the keys are + approximations to the roots with rational real and imaginary + parts, and the values are intervals containing them. EXAMPLES:: @@ -467,32 +468,32 @@ def roots_interval(f, x0): -0.0669872981077806 + 1.29903810567666*I, -0.933012701892219 + 1.29903810567666*I, -0.0669872981077806 + 0.433012701892219*I)] - """ x, y = f.parent().gens() I = QQbar.gen() - fx = QQbar[y](f.subs({x: QQ(x0.real())+I*QQ(x0.imag())})) + fx = QQbar[y](f.subs({x: QQ(x0.real()) + I * QQ(x0.imag())})) roots = fx.roots(QQbar, multiplicities=False) result = {} - for i in range(len(roots)): - r = roots[i] + for i, r in enumerate(roots): prec = 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor = 4 - diam = min((CF(r)-CF(r0)).abs() for r0 in roots[:i]+roots[i+1:]) / divisor - envelop = IF(diam)*IF((-1, 1), (-1, 1)) - while not newton(fx, r, r+envelop) in r+envelop: + diam = min((CF(r) - CF(r0)).abs() + for r0 in roots[:i] + roots[i + 1:]) / divisor + envelop = IF(diam) * IF((-1, 1), (-1, 1)) + while not newton(fx, r, r + envelop) in r + envelop: prec += 53 IF = ComplexIntervalField(prec) CF = ComplexField(prec) divisor *= 2 - diam = min([(CF(r)-CF(r0)).abs() for r0 in roots[:i]+roots[i+1:]])/divisor - envelop = IF(diam)*IF((-1, 1), (-1, 1)) - qapr = QQ(CF(r).real())+QQbar.gen()*QQ(CF(r).imag()) - if qapr not in r+envelop: + diam = min((CF(r) - CF(r0)).abs() + for r0 in roots[:i] + roots[i + 1:]) / divisor + envelop = IF(diam) * IF((-1, 1), (-1, 1)) + qapr = QQ(CF(r).real()) + QQbar.gen() * QQ(CF(r).imag()) + if qapr not in r + envelop: raise ValueError("Could not approximate roots with exact values") - result[qapr] = r+envelop + result[qapr] = r + envelop return result @@ -500,7 +501,6 @@ def roots_interval_cached(f, x0): r""" Cached version of :func:`roots_interval`. - TESTS:: sage: from sage.schemes.curves.zariski_vankampen import roots_interval, roots_interval_cached, roots_interval_cache @@ -515,7 +515,6 @@ def roots_interval_cached(f, x0): 1: 1.? + 0.?*I} sage: (f, 1) in roots_interval_cache True - """ global roots_interval_cache try: @@ -602,16 +601,15 @@ def braid_in_segment(g, x0, x1): sage: B = zvk.braid_in_segment(g.factor(),CC(p1),CC(p2)) # optional - sirocco sage: B # optional - sirocco s5*s3^-1 - """ - (x, y) = g.value().parent().gens() + _, y = g.value().parent().gens() I = QQbar.gen() X0 = QQ(x0.real()) + I * QQ(x0.imag()) X1 = QQ(x1.real()) + I * QQ(x1.imag()) intervals = {} precision = {} y0s = [] - for (f, naux) in g: + for f, _ in g: if f.variables() == (y,): F0 = QQbar[y](f.base_ring()[y](f)) else: @@ -633,8 +631,8 @@ def braid_in_segment(g, x0, x1): initialintervals = roots_interval_cached(g.value(), X0) finalintervals = roots_interval_cached(g.value(), X1) for cs in complexstrands: - ip = cs[0][1] + I*cs[0][2] - fp = cs[-1][1] + I*cs[-1][2] + ip = cs[0][1] + I * cs[0][2] + fp = cs[-1][1] + I * cs[-1][2] matched = 0 for center, interval in initialintervals.items(): if ip in interval: @@ -652,7 +650,7 @@ def braid_in_segment(g, x0, x1): if matched == 0: raise ValueError("unable to match braid endpoint with root") if matched > 1: - raise ValueError("braid endpoint mathes more than one root") + raise ValueError("braid endpoint matches more than one root") initialbraid = braid_from_piecewise(initialstrands) finalbraid = braid_from_piecewise(finalstrands) @@ -661,7 +659,7 @@ def braid_in_segment(g, x0, x1): def orient_circuit(circuit): r""" - Reverses a circuit if it goes clockwise; otherwise leaves it unchanged. + Reverse a circuit if it goes clockwise; otherwise leave it unchanged. INPUT: @@ -711,16 +709,16 @@ def orient_circuit(circuit): """ prec = 53 - vectors = [v[1].vector()-v[0].vector() for v in circuit] + vectors = [v[1].vector() - v[0].vector() for v in circuit] while True: CIF = ComplexIntervalField(prec) - totalangle = sum((CIF(*vectors[i])/CIF(*vectors[i-1])).argument() for i in range(len(vectors))) + totalangle = sum((CIF(*vectors[i]) / CIF(*vectors[i - 1])).argument() + for i in range(len(vectors))) if totalangle < 0: return list(reversed([(c[1], c[0]) + c[2:] for c in circuit])) - elif totalangle > 0: + if totalangle > 0: return circuit - else: - prec *= 2 + prec *= 2 def geometric_basis(G, E, p): @@ -731,15 +729,15 @@ def geometric_basis(G, E, p): - ``G`` -- the graph of the bounded regions of a Voronoi Diagram - - ``E`` -- the subgraph of ``G`` formed by the edges that touch an unbounded - region + - ``E`` -- the subgraph of ``G`` formed by the edges that touch + an unbounded region - ``p`` -- a vertex of ``E`` OUTPUT: A geometric basis. It is formed by a list of sequences of paths. Each path is a list of vertices, that form a closed path in `G`, based at - `p`, that goes to a region, surrounds it, and comes back by the same path it - came. The concatenation of all these paths is equivalent to `E`. + `p`, that goes to a region, surrounds it, and comes back by the same + path it came. The concatenation of all these paths is equivalent to `E`. EXAMPLES:: @@ -790,11 +788,10 @@ def geometric_basis(G, E, p): A vertex at (-1/2, 1/2), A vertex at (-2, 2), A vertex at (-2, -2)]] - """ EC = [v[0] for v in orient_circuit(E.eulerian_circuit())] i = EC.index(p) - EC = EC[i:]+EC[:i+1] # A counterclockwise eulerian circuit on the boundary, based at p + EC = EC[i:] + EC[:i + 1] # A counterclockwise eulerian circuit on the boundary, based at p if G.size() == E.size(): if E.is_cycle(): return [EC] @@ -808,17 +805,18 @@ def geometric_basis(G, E, p): if len(E.neighbors(v)) > 2: I.add_vertex(v) - for i in range(len(EC)): # q and r are the points we will cut through + for i, ECi in enumerate(EC): # q and r are the points we will cut through if EC[i] in I: q = EC[i] connecting_path = EC[:i] break - elif EC[-i] in I: + if EC[-i] in I: q = EC[-i] connecting_path = list(reversed(EC[-i:])) break - distancequotients = [(E.distance(q, v)**2/I.distance(q, v), v) for v in E if v in I.connected_component_containing_vertex(q) and not v == q] + distancequotients = [(E.distance(q, v)**2 / I.distance(q, v), v) for v in E + if v in I.connected_component_containing_vertex(q) and not v == q] r = max(distancequotients)[1] cutpath = I.shortest_path(q, r) Gcut = copy(G) @@ -839,16 +837,16 @@ def geometric_basis(G, E, p): if n in G2 or n in cutpath: G2.add_edge(v, n, None) - if EC[EC.index(q)+1] in G2: + if EC[EC.index(q) + 1] in G2: G1, G2 = G2, G1 E1, E2 = Ecut.connected_components_subgraphs() - if EC[EC.index(q)+1] in E2: + if EC[EC.index(q) + 1] in E2: E1, E2 = E2, E1 - for i in range(len(cutpath)-1): - E1.add_edge(cutpath[i], cutpath[i+1], None) - E2.add_edge(cutpath[i], cutpath[i+1], None) + for i in range(len(cutpath) - 1): + E1.add_edge(cutpath[i], cutpath[i + 1], None) + E2.add_edge(cutpath[i], cutpath[i + 1], None) for v in [q, r]: for n in E.neighbors(v): @@ -860,14 +858,16 @@ def geometric_basis(G, E, p): gb1 = geometric_basis(G1, E1, q) gb2 = geometric_basis(G2, E2, q) - resul = [connecting_path + path + list(reversed(connecting_path)) for path in gb1 + gb2] + reverse_connecting = list(reversed(connecting_path)) + resul = [connecting_path + path + reverse_connecting + for path in gb1 + gb2] for r in resul: i = 0 - while i < len(r)-2: - if r[i] == r[i+2]: + while i < len(r) - 2: + if r[i] == r[i + 2]: r.pop(i) r.pop(i) - if i > 0: + if i: i -= 1 else: i += 1 @@ -876,12 +876,12 @@ def geometric_basis(G, E, p): def braid_monodromy(f): r""" - Compute the braid monodromy of a projection of the curve defined by a polynomial + Compute the braid monodromy of a projection of the curve defined by a polynomial. INPUT: - - ``f`` -- a polynomial with two variables, over a number field with an embedding - in the complex numbers. + - ``f`` -- a polynomial with two variables, over a number field + with an embedding in the complex numbers OUTPUT: @@ -904,10 +904,9 @@ def braid_monodromy(f): s1*s0*(s1*s2)^2*(s0*s2^-1*s1*s2*s1*s2^-1)^2*(s2^-1*s1^-1)^2*s0^-1*s1^-1, s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, s1*s0*s2*s0^-1*s2*s1^-1] - """ global roots_interval_cache - (x, y) = f.parent().gens() + x, y = f.parent().gens() F = f.base_ring() g = f.radical() d = g.degree(y) @@ -925,22 +924,24 @@ def braid_monodromy(f): E = E.union(reg.vertex_graph()) p = next(E.vertex_iterator()) geombasis = geometric_basis(G, E, p) - segs = set([]) + segs = set() for p in geombasis: for s in zip(p[:-1], p[1:]): if (s[1], s[0]) not in segs: segs.add((s[0], s[1])) I = QQbar.gen() - segs = [(a[0]+I*a[1], b[0]+I*b[1]) for (a, b) in segs] + segs = [(a[0] + I * a[1], b[0] + I * b[1]) for a, b in segs] vertices = list(set(flatten(segs))) tocacheverts = [(g, v) for v in vertices] populate_roots_interval_cache(tocacheverts) gfac = g.factor() try: - braidscomputed = list(braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) + braidscomputed = (braid_in_segment([(gfac, seg[0], seg[1]) + for seg in segs])) except ChildProcessError: # hack to deal with random fails first time - braidscomputed = list(braid_in_segment([(gfac, seg[0], seg[1]) for seg in segs])) - segsbraids = dict() + braidscomputed = (braid_in_segment([(gfac, seg[0], seg[1]) + for seg in segs])) + segsbraids = {} for braidcomputed in braidscomputed: seg = (braidcomputed[0][0][1], braidcomputed[0][0][2]) beginseg = (QQ(seg[0].real()), QQ(seg[0].imag())) @@ -952,9 +953,9 @@ def braid_monodromy(f): result = [] for path in geombasis: braidpath = B.one() - for i in range(len(path)-1): + for i in range(len(path) - 1): x0 = tuple(path[i].vector()) - x1 = tuple(path[i+1].vector()) + x1 = tuple(path[i + 1].vector()) braidpath = braidpath * segsbraids[(x0, x1)] result.append(braidpath) return result @@ -978,7 +979,8 @@ def fundamental_group(f, simplified=True, projective=False): of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - If ``simplified`` is ``False``, a Zariski-VanKampen presentation is returned. + If ``simplified`` is ``False``, a Zariski-VanKampen presentation + is returned. OUTPUT: @@ -1020,9 +1022,9 @@ def fundamental_group(f, simplified=True, projective=False): """ g = f if projective: - x,y = g.parent().gens() + x, y = g.parent().gens() while g.degree(y) < g.degree(): - g = g.subs({x : x + y}) + g = g.subs({x: x + y}) bm = braid_monodromy(g) n = bm[0].parent().strands() F = FreeGroup(n) @@ -1030,7 +1032,7 @@ def fundamental_group(f, simplified=True, projective=False): @parallel def relation(x, b): return x * b / x - relations = list(relation([(x, b) for x in F.gens() for b in bm])) + relations = (relation([(x, b) for x in F.gens() for b in bm])) R = [r[1] for r in relations] if projective: R.append(prod(F.gens()))