diff --git a/src/doc/en/reference/curves/index.rst b/src/doc/en/reference/curves/index.rst index 583b331ea28..fbe7626b4e5 100644 --- a/src/doc/en/reference/curves/index.rst +++ b/src/doc/en/reference/curves/index.rst @@ -13,6 +13,7 @@ Curves sage/schemes/curves/constructor sage/schemes/curves/curve sage/schemes/curves/affine_curve + sage/schemes/curves/plane_curve_arrangement sage/schemes/curves/projective_curve sage/schemes/curves/point sage/schemes/curves/closed_point diff --git a/src/doc/en/reference/discrete_geometry/index.rst b/src/doc/en/reference/discrete_geometry/index.rst index 7c0fb57bb1e..c64c6d9cd8a 100644 --- a/src/doc/en/reference/discrete_geometry/index.rst +++ b/src/doc/en/reference/discrete_geometry/index.rst @@ -12,6 +12,7 @@ Hyperplane arrangements :maxdepth: 1 sage/geometry/hyperplane_arrangement/arrangement + sage/geometry/hyperplane_arrangement/ordered_arrangement sage/geometry/hyperplane_arrangement/library sage/geometry/hyperplane_arrangement/hyperplane sage/geometry/hyperplane_arrangement/affine_subspace diff --git a/src/sage/geometry/all.py b/src/sage/geometry/all.py index 3b7dcf756d8..e4b4d933bc4 100644 --- a/src/sage/geometry/all.py +++ b/src/sage/geometry/all.py @@ -16,4 +16,5 @@ lazy_import('sage.geometry.voronoi_diagram', 'VoronoiDiagram') lazy_import('sage.geometry.ribbon_graph', 'RibbonGraph') lazy_import('sage.geometry.hyperplane_arrangement.arrangement', 'HyperplaneArrangements') +lazy_import('sage.geometry.hyperplane_arrangement.ordered_arrangement', 'OrderedHyperplaneArrangements') lazy_import('sage.geometry.hyperplane_arrangement.library', 'hyperplane_arrangements') diff --git a/src/sage/geometry/hyperplane_arrangement/arrangement.py b/src/sage/geometry/hyperplane_arrangement/arrangement.py index bb1e04efabe..19ae8698f8c 100644 --- a/src/sage/geometry/hyperplane_arrangement/arrangement.py +++ b/src/sage/geometry/hyperplane_arrangement/arrangement.py @@ -242,7 +242,7 @@ \chi(x) := \sum_{w\in P} \mu(w) x^{dim(w)} -where the sum is `P` is the +where `P` is the :meth:`~HyperplaneArrangementElement.intersection_poset` of the arrangement and `\mu` is the Möbius function of `P`:: @@ -335,7 +335,7 @@ arrangements. """ -#***************************************************************************** +# ***************************************************************************** # Copyright (C) 2013 David Perkinson # Volker Braun # @@ -344,25 +344,24 @@ # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ -#***************************************************************************** +# ***************************************************************************** # Possible extensions for hyperplane_arrangement.py: # - the big face lattice # - create ties with the Sage matroid methods # - hyperplane arrangements over other fields +from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane +from sage.matrix.constructor import matrix, vector +from sage.misc.cachefunc import cached_method +from sage.modules.free_module import VectorSpace +from sage.rings.integer_ring import ZZ +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.rational_field import QQ from sage.structure.parent import Parent from sage.structure.element import Element from sage.structure.richcmp import richcmp from sage.structure.unique_representation import UniqueRepresentation -from sage.rings.integer_ring import ZZ -from sage.rings.rational_field import QQ -from sage.misc.cachefunc import cached_method -from sage.matrix.constructor import matrix, vector -from sage.modules.free_module import VectorSpace -from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - -from sage.geometry.hyperplane_arrangement.hyperplane import AmbientVectorSpace, Hyperplane class HyperplaneArrangementElement(Element): @@ -385,10 +384,9 @@ def __init__(self, parent, hyperplanes, check=True, backend=None): - ``hyperplanes`` -- a tuple of hyperplanes - - ``check`` -- boolean (optional; default ``True``); whether - to check input + - ``check`` -- boolean (default: ``True``); whether to check input - - ``backend`` -- string (optional; default: ``None``); the backend to + - ``backend`` -- string (optional); the backend to use for the related polyhedral objects EXAMPLES:: @@ -499,7 +497,7 @@ def hyperplanes(self): OUTPUT: - An integer. + A tuple EXAMPLES:: @@ -659,10 +657,10 @@ def union(self, other): sage: H. = HyperplaneArrangements(QQ) sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0]) sage: B = H([1,1,1], [1,-1,1], [1,0,-1]) - sage: A.union(B) - Arrangement of 8 hyperplanes of dimension 2 and rank 2 - sage: A | B # syntactic sugar + sage: C = A.union(B); C Arrangement of 8 hyperplanes of dimension 2 and rank 2 + sage: C == A | B # syntactic sugar + True A single hyperplane is coerced into a hyperplane arrangement if necessary:: @@ -678,9 +676,10 @@ def union(self, other): Arrangement of 6 hyperplanes of dimension 2 and rank 2 """ P = self.parent() - other = P(other) - hyperplanes = self._hyperplanes + other._hyperplanes - return P(*hyperplanes, backend=self._backend) + other_h = P(other) + hyperplanes = self._hyperplanes + other_h._hyperplanes + result = P(*hyperplanes, backend=self._backend) + return result add_hyperplane = union @@ -713,9 +712,10 @@ def cone(self, variable='t'): OUTPUT: - A new hyperplane arrangement. Its equations consist of - `[0, -d, a_1, \ldots, a_n]` for each `[d, a_1, \ldots, a_n]` in the - original arrangement and the equation `[0, 1, 0, \ldots, 0]`. + A new hyperplane arrangement `L`. + Its equations consist of `[0, -d, a_1, \ldots, a_n]` for each + `[d, a_1, \ldots, a_n]` in the original arrangement and the + equation `[0, 1, 0, \ldots, 0]` (maybe not in this order). .. WARNING:: @@ -724,7 +724,8 @@ def cone(self, variable='t'): no guarantee that the order in which they appear in ``self.hyperplanes()`` will match the order in which their counterparts in ``self.cone()`` will appear in - ``self.cone().hyperplanes()``! + ``self.cone().hyperplanes()``! This warning does not apply + to ordered hyperplane arrangements. EXAMPLES:: @@ -758,7 +759,7 @@ def cone(self, variable='t'): hyperplanes.append([0, 1] + [0] * self.dimension()) P = self.parent() names = (variable,) + P._names - H = HyperplaneArrangements(self.parent().base_ring(), names=names) + H = type(P).__base__(P.base_ring(), names=names) return H(*hyperplanes, backend=self._backend) @cached_method @@ -853,16 +854,16 @@ def intersection_poset(self, element_label="int"): W = Vector space of dimension 2 over Rational Field] """ if element_label == "int": - def update(mapping, val, I): + def update(mapping, val, I0): mapping[val] = len(mapping) elif element_label == "subset": from sage.sets.set import Set - def update(mapping, val, I): + def update(mapping, val, I0): mapping[val] = Set(val) elif element_label == "subspace": - def update(mapping, val, I): - mapping[val] = I + def update(mapping, val, I0): + mapping[val] = I0 else: raise ValueError("invalid element label type") @@ -885,13 +886,13 @@ def update(mapping, val, I): for label, T in cur_level: edges = [] for i, H in enumerate(hyperplanes): - I = H.intersection(T) - if I is not None and I != T: + I0 = H.intersection(T) + if I0 is not None and I0 != T: try: - target = new_level[I] + target = new_level[I0] except KeyError: target = set(label) - new_level[I] = target + new_level[I0] = target target.add(i) edges.append(target) hasse[label] = edges @@ -1216,7 +1217,7 @@ def deletion(self, hyperplanes): raise ValueError('hyperplane is not in the arrangement') return parent(*planes, backend=self._backend) - def restriction(self, hyperplane): + def restriction(self, hyperplane, repetitions=False): r""" Return the restriction to a hyperplane. @@ -1224,10 +1225,13 @@ def restriction(self, hyperplane): - ``hyperplane`` -- a hyperplane of the hyperplane arrangement + - ``repetitions`` -- boolean (default: ``False``); eliminate + repetitions for ordered arrangements + OUTPUT: - The restriction of the hyperplane arrangement to the given - ``hyperplane``. + The restriction `\mathcal{A}_H` of the + hyperplane arrangement `\mathcal{A}` to the given ``hyperplane`` `H`. EXAMPLES:: @@ -1236,8 +1240,12 @@ def restriction(self, hyperplane): Arrangement of 6 hyperplanes of dimension 4 and rank 3 sage: H = A[0]; H Hyperplane 0*u + 0*x + y - z + 0 - sage: R = A.restriction(H); R + sage: R = A.restriction(H); R Arrangement + sage: A.add_hyperplane(z).restriction(z) + Arrangement of 6 hyperplanes of dimension 3 and rank 3 + sage: A.add_hyperplane(u).restriction(u) + Arrangement of 6 hyperplanes of dimension 3 and rank 3 sage: D = A.deletion(H); D Arrangement of 5 hyperplanes of dimension 4 and rank 3 sage: ca = A.characteristic_polynomial() @@ -1281,8 +1289,19 @@ def restriction(self, hyperplane): hyperplanes.append([A, b]) names = list(parent._names) names.pop(pivot) - H = HyperplaneArrangements(parent.base_ring(), names=tuple(names)) - return H(*hyperplanes, signed=False, backend=self._backend) + from sage.geometry.hyperplane_arrangement.ordered_arrangement import OrderedHyperplaneArrangements + if isinstance(parent, OrderedHyperplaneArrangements): + H = OrderedHyperplaneArrangements(parent.base_ring(), names=tuple(names)) + if not repetitions: + L = list(hyperplanes) + hyperplanes = () + for h in L: + if h not in hyperplanes: + hyperplanes += (h,) + else: + H = HyperplaneArrangements(parent.base_ring(), names=tuple(names)) + result = H(*hyperplanes, signed=False, backend=self._backend) + return result def change_ring(self, base_ring): """ @@ -1665,7 +1684,8 @@ def essentialization(self): OUTPUT: - The essentialization as a new hyperplane arrangement. + The essentialization `\mathcal{A}'` of `\mathcal{A}` as a + new hyperplane arrangement. EXAMPLES:: @@ -2091,7 +2111,7 @@ def regions(self): for hyperplane in self: ieq = vector(R, hyperplane.dense_coefficient_list()) - pos_half = Polyhedron(ieqs=[ ieq], base_ring=R, backend=be) + pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be) if not regions: # See comment above. @@ -2129,8 +2149,8 @@ def regions(self): else: # In this case, at least one of the vertices is not on the hyperplane. # So we check if any ray or line pokes the hyperplane. - if ( any(ieq[1:]*r[:]*direction < 0 for r in region.rays()) or - any(ieq[1:]*l[:] != 0 for l in region_lines)): + if (any(ieq[1:]*r[:]*direction < 0 for r in region.rays()) or + any(ieq[1:]*ll[:] != 0 for ll in region_lines)): splits = True if splits: @@ -2158,10 +2178,10 @@ def poset_of_regions(self, B=None, numbered_labels=True): INPUT: - - ``B`` -- a region (optional; default: ``None``); if ``None``, then + - ``B`` -- a region (optional); if ``None``, then an arbitrary region is chosen as the base region. - - ``numbered_labels`` -- bool (optional; default: ``True``); if ``True``, + - ``numbered_labels`` -- bool (default: ``True``); if ``True``, then the elements of the poset are numbered. Else they are labelled with the regions themselves. @@ -2345,12 +2365,13 @@ def closed_faces(self, labelled=True): ((-1, -1), A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 1 vertex and 2 rays, (-1, -2))] - sage: a = hyperplane_arrangements.braid(3) # needs sage.graphs - sage: a.hyperplanes() # needs sage.graphs + sage: # needs sage.graphs + sage: a = hyperplane_arrangements.braid(3) + sage: a.hyperplanes() (Hyperplane 0*t0 + t1 - t2 + 0, Hyperplane t0 - t1 + 0*t2 + 0, Hyperplane t0 + 0*t1 - t2 + 0) - sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()] # needs sage.graphs + sage: [(v, F, F.representative_point()) for v, F in a.closed_faces()] [((0, 0, 0), A 1-dimensional polyhedron in QQ^3 defined as the convex hull of 1 vertex and 1 line, (0, 0, 0)), ((0, 1, 1), A 2-dimensional polyhedron in QQ^3 defined @@ -2422,7 +2443,7 @@ def closed_faces(self, labelled=True): zero_half = Polyhedron(eqns=[ieq], base_ring=R, backend=be) # ``zero_half`` is the hyperplane ``hyperplane`` itself # (viewed as a polyhedron). - pos_half = Polyhedron(ieqs=[ ieq], base_ring=R, backend=be) + pos_half = Polyhedron(ieqs=[ieq], base_ring=R, backend=be) neg_half = Polyhedron(ieqs=[-ieq], base_ring=R, backend=be) subdivided = [] for signs, face in faces: @@ -2904,8 +2925,9 @@ def whitney_data(self): EXAMPLES:: + sage: # needs sage.combinat sage: A = hyperplane_arrangements.Shi(3) - sage: A.whitney_data() # needs sage.combinat + sage: A.whitney_data() ( [ 1 -6 9] [ 1 6 6] [ 0 6 -15] [ 0 6 15] @@ -3247,7 +3269,7 @@ def orlik_solomon_algebra(self, base_ring=None, ordering=None, **kwds): """ if base_ring is None: base_ring = self.base_ring() - return self.matroid().orlik_solomon_algebra(base_ring, ordering,**kwds) + return self.matroid().orlik_solomon_algebra(base_ring, ordering, **kwds) def orlik_terao_algebra(self, base_ring=None, ordering=None, **kwds): """ @@ -3394,9 +3416,10 @@ def derivation_module_free_chain(self): EXAMPLES:: - sage: W = WeylGroup(['A',3], prefix='s') # needs sage.combinat sage.groups - sage: A = W.long_element().inversion_arrangement() # needs sage.combinat sage.groups - sage: for M in A.derivation_module_free_chain(): print("%s\n"%M) # needs sage.combinat sage.groups + sage: # needs sage.combinat sage.groups + sage: W = WeylGroup(['A',3], prefix='s') + sage: A = W.long_element().inversion_arrangement() + sage: for M in A.derivation_module_free_chain(): print("%s\n"%M) [ 1 0 0] [ 0 1 0] [ 0 0 a3] @@ -3539,9 +3562,10 @@ def derivation_module_basis(self, algorithm="singular"): EXAMPLES:: - sage: W = WeylGroup(['A', 2], prefix='s') # needs sage.combinat sage.groups - sage: A = W.long_element().inversion_arrangement() # needs sage.combinat sage.groups - sage: A.derivation_module_basis() # needs sage.combinat sage.groups + sage: # needs sage.combinat sage.groups + sage: W = WeylGroup(['A', 2], prefix='s') + sage: A = W.long_element().inversion_arrangement() + sage: A.derivation_module_basis() [(a1, a2), (0, a1*a2 + a2^2)] TESTS: @@ -3560,7 +3584,7 @@ def derivation_module_basis(self, algorithm="singular"): ....: else: ....: assert exponents(B) == exponents(Bp) """ - alg = algorithm # prevent possible changes to a global variable + alg = algorithm # prevent possible changes to a global variable if alg == "singular": # import sage.libs.singular.function_factory # syz = sage.libs.singular.function_factory.ff.syz @@ -3575,7 +3599,7 @@ def derivation_module_basis(self, algorithm="singular"): # Check using Saito's criterion if det / f in f.parent().base_ring() and not det.is_zero(): return basis.rows() - except ValueError: # Non-square matrix or det = 0 + except ValueError: # Non-square matrix or det = 0 pass # Check if it is free if not self.is_free(algorithm=alg): @@ -3586,7 +3610,7 @@ def derivation_module_basis(self, algorithm="singular"): if alg == "BC": C = self.derivation_module_free_chain() if C is not None: - if not C: # C is an empty list + if not C: # C is an empty list S = self.parent().ambient_space().symmetric_space() return matrix.identity(S, self.dimension()).rows() from sage.misc.misc_c import prod @@ -3739,15 +3763,15 @@ def _element_constructor_(self, *args, **kwds): hyperplane; alternatively, a single polytope or a single hyperplane arrangement - - ``signed`` -- boolean (optional, default: ``True``); whether to + - ``signed`` -- boolean (default: ``True``); whether to preserve signs of hyperplane equations - - ``warn_duplicates`` -- boolean (optional, default: ``False``); + - ``warn_duplicates`` -- boolean (default: ``False``); whether to issue a warning if duplicate hyperplanes were passed -- note that duplicate hyperplanes are always removed, whether or not there is a warning shown - - ``check`` -- boolean (optional, default: ``True``); whether to + - ``check`` -- boolean (default: ``True``); whether to perform argument checking. EXAMPLES:: diff --git a/src/sage/geometry/hyperplane_arrangement/library.py b/src/sage/geometry/hyperplane_arrangement/library.py index cdfaca2faf0..98b03ceac70 100644 --- a/src/sage/geometry/hyperplane_arrangement/library.py +++ b/src/sage/geometry/hyperplane_arrangement/library.py @@ -156,7 +156,7 @@ def bigraphical(self, G, A=None, K=QQ, names=None): for u, v in G.edge_iterator(labels=False, sort_vertices=False): i = vertex_to_int[u] j = vertex_to_int[v] - hyperplanes.append( x[i] - x[j] - A[i][j]) + hyperplanes.append(x[i] - x[j] - A[i][j]) hyperplanes.append(-x[i] + x[j] - A[j][i]) return H(*hyperplanes) @@ -794,7 +794,7 @@ def Shi(self, data, K=QQ, names=None, m=1): hyperplanes = [] for a in PR: - for const in range(-m+1,m+1): + for const in range(-m + 1, m + 1): hyperplanes.append(sum(a[j]*x[j] for j in range(d))-const) A = H(*hyperplanes) x = polygen(QQ, 'x') diff --git a/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py b/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py new file mode 100644 index 00000000000..bb16768e13b --- /dev/null +++ b/src/sage/geometry/hyperplane_arrangement/ordered_arrangement.py @@ -0,0 +1,650 @@ +r""" +Ordered Hyperplane Arrangements + +The :class:`HyperplaneArrangements` orders the hyperplanes in a arrangement +independently of the way the hyperplanes are introduced. The class +:class:`OrderedHyperplaneArrangements` fixes an order specified by +the user. This can be needed for certain properties, e.g., fundamental group with +information about meridians, braid monodromy with information about the strands; +in the future, it may be useful for combinatorial properties. +There are no other differences with usual hyperplane arrangements. + +An ordered arrangement is an arrangement where the hyperplanes are sorted +by the user:: + + sage: H0. = HyperplaneArrangements(QQ) + sage: H0(t0 - t1, t1 - t2, t0 - t2) + Arrangement + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: H(t0 - t1, t1 - t2, t0 - t2) + Arrangement + +Some methods are adapted, e.g., :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.hyperplanes`, +and some new ones are created, regarding +hyperplane sections and fundamental groups:: + + sage: H. = HyperplaneArrangements(QQ) + sage: H1. = OrderedHyperplaneArrangements(QQ) + sage: A1 = H1(x, y); A = H(A1) + sage: A.hyperplanes() + (Hyperplane 0*x + y + 0, Hyperplane x + 0*y + 0) + sage: A1.hyperplanes() + (Hyperplane x + 0*y + 0, Hyperplane 0*x + y + 0) + +We see the differences in :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.union`:: + + sage: H. = HyperplaneArrangements(QQ) + sage: H1. = OrderedHyperplaneArrangements(QQ) + sage: A = H([1,2,3], [0,1,1], [0,1,-1], [1,-1,0], [1,1,0]) + sage: B = H([1,1,1], [1,-1,1], [1,0,-1]) + sage: C = A.union(B) + sage: A1 = H1(A); B1 = H1(B); C1 = A1.union(B1) + sage: [C1.hyperplanes().index(h) for h in C.hyperplanes()] + [0, 5, 6, 1, 2, 3, 7, 4] + +Also in meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.cone`:: + + sage: # needs sage.combinat + sage: a. = hyperplane_arrangements.semiorder(3) + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: a1 = H(a) + sage: b = a.cone(); b1 = a1.cone() + sage: [b1.hyperplanes().index(h) for h in b.hyperplanes()] + [0, 2, 4, 6, 1, 3, 5] + +And in :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.restriction`:: + + sage: # needs sage.graphs + sage: A. = hyperplane_arrangements.braid(4) + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: A1 = L(A) + sage: H = A[0]; H + Hyperplane 0*u + 0*x + y - z + 0 + sage: A.restriction(H) + Arrangement + sage: A1.restriction(H) + Arrangement + sage: A1.restriction(H, repetitions=True) + Arrangement of 5 hyperplanes of dimension 3 and rank 2 + +AUTHORS: + +- Enrique Artal (2023-12): initial version + +This module adds some features to the *unordered* one for some +properties which depend on the order. +""" + +# ***************************************************************************** +# Copyright (C) 2023 Enrique Artal +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangementElement +from sage.geometry.hyperplane_arrangement.arrangement import HyperplaneArrangements +from sage.geometry.hyperplane_arrangement.hyperplane import Hyperplane +from sage.matrix.constructor import matrix, vector +from sage.misc.misc_c import prod +from sage.groups.free_group import FreeGroup +from sage.rings.integer_ring import ZZ +from sage.rings.qqbar import QQbar +from sage.schemes.curves.plane_curve_arrangement import AffinePlaneCurveArrangements +from sage.schemes.curves.plane_curve_arrangement import ProjectivePlaneCurveArrangements + + +class OrderedHyperplaneArrangementElement(HyperplaneArrangementElement): + """ + An ordered hyperplane arrangement. + + .. WARNING:: + + You should never create + :class:`OrderedHyperplaneArrangementElement` instances directly, + always use the parent. + """ + def __init__(self, parent, hyperplanes, check=True, backend=None): + """ + Construct an ordered hyperplane arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`OrderedHyperplaneArrangements` + + - ``hyperplanes`` -- a tuple of hyperplanes + + - ``check`` -- boolean (default ``True``); whether + to check input + + - ``backend`` -- string (default: ``None``); the backend to + use for the related polyhedral objects + + EXAMPLES:: + + sage: H. = OrderedHyperplaneArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement + sage: TestSuite(elt).run() + """ + super().__init__(parent, hyperplanes, check=check, backend=backend) + self._affine_fundamental_group = None + self._affine_meridians = None + self._projective_fundamental_group = None + self._projective_meridians = None + + def hyperplane_section(self, proj=True): + r""" + Compute a generic hyperplane section of ``self``. + + INPUT: + + - ``proj`` -- (default: ``True``); if the + ambient space is affine or projective + + OUTPUT: + + An arrangement `\mathcal{A}` obtained by intersecting with a + generic hyperplane + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(x, y - 1, z).hyperplane_section() + Traceback (most recent call last): + ... + TypeError: the arrangement is not projective + + sage: # needs sage.graphs + sage: A0. = hyperplane_arrangements.braid(4); A0 + Arrangement of 6 hyperplanes of dimension 4 and rank 3 + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: A = L(A0) + sage: M = A.matroid() + sage: A1 = A.hyperplane_section() + sage: A1 + Arrangement of 6 hyperplanes of dimension 3 and rank 3 + sage: M1 = A1.matroid() + sage: A2 = A1.hyperplane_section(); A2 + Arrangement of 6 hyperplanes of dimension 2 and rank 2 + sage: M2 = A2.matroid() + sage: T1 = M1.truncation() + sage: T1.is_isomorphic(M2) + True + sage: T1.isomorphism(M2) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} + + sage: # needs sage.combinat + sage: a0 = hyperplane_arrangements.semiorder(3); a0 + Arrangement of 6 hyperplanes of dimension 3 and rank 2 + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: a = L(a0) + sage: ca = a.cone() + sage: m = ca.matroid() + sage: a1 = a.hyperplane_section(proj=False) + sage: a1 + Arrangement of 6 hyperplanes of dimension 2 and rank 2 + sage: ca1 = a1.cone() + sage: m1 = ca1.matroid() + sage: m.isomorphism(m1) + {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6} + sage: p0 = hyperplane_arrangements.Shi(4) + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: p = L(p0) + sage: a = p.hyperplane_section(proj=False); a + Arrangement of 12 hyperplanes of dimension 3 and rank 3 + sage: ca = a.cone() + sage: m = ca.matroid().truncation() + sage: a1 = a.hyperplane_section(proj=False); a1 + Arrangement of 12 hyperplanes of dimension 2 and rank 2 + sage: ca1 = a1.cone() + sage: m1 = ca1.matroid() + sage: m1.is_isomorphism(m, {j: j for j in range(13)}) + True + """ + if proj and not self.is_linear(): + raise TypeError('the arrangement is not projective') + n0 = self.dimension() + if not proj: + H = self.cone() + H1 = H.hyperplane_section() + mat = matrix(h.coefficients()[1:] for h in H1) + m = mat.nrows() + for j in range(mat.ncols()): + if mat[m - 1, j] != 0: + mat.swap_columns(0, j) + break + for j in range(1, mat.ncols()): + mat.add_multiple_of_column(j, 0, -mat[m - 1, j] / mat[m - 1, 0]) + vrs = H1.parent().variable_names()[1:] + A1 = OrderedHyperplaneArrangements(self.base_ring(), names=vrs) + mat_rows = mat.rows()[:-1] + H1b = A1(mat_rows) + return H1b + P = self.intersection_poset(element_label="subspace") + center = P.maximal_elements()[0].linear_part() + n1 = center.dimension() + U = [] + for p in P: + if p.dimension() == n1 + 1: + B = [u for u in p.linear_part().basis() if u not in center] + U.append(B[0]) + # U = [p.linear_part().basis()[0] for p in P if p.dimension() == n1 + 1] + U0 = sum(U) + # We look for a linear hyperplane with integer coefficients + # defining a transversal hyperplane + for v in ZZ**n0: + v1 = v + U0 + if 0 not in [w * v1 for w in U]: + break + h0 = self.parent()((0,) + tuple(v1)) + H1 = self.add_hyperplane(h0) + return H1.restriction(h0) + + def affine_fundamental_group(self): + r""" + Return the fundamental group of the complement of an affine + hyperplane arrangement in `\CC^n` whose equations have + coefficients in a subfield of `\QQbar`. + + OUTPUT: + + A finitely presented fundamental group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: L = [y + x, y + x - 1] + sage: H = A(L) + sage: H.affine_fundamental_group() + Finitely presented group < x0, x1 | > + sage: L = [x, y, x + 1, y + 1, x - y] + sage: A(L).affine_fundamental_group() + Finitely presented group + < x0, x1, x2, x3, x4 | x4*x0*x4^-1*x0^-1, + x0*x2*x3*x2^-1*x0^-1*x3^-1, + x1*x2*x4*x2^-1*x1^-1*x4^-1, + x2*x3*x0*x2^-1*x0^-1*x3^-1, + x2*x4*x1*x2^-1*x1^-1*x4^-1, + x4*x1*x4^-1*x3^-1*x2^-1*x1^-1*x2*x3 > + sage: H = A(x, y, x + y) + sage: H.affine_fundamental_group() + Finitely presented group + < x0, x1, x2 | x0*x1*x2*x1^-1*x0^-1*x2^-1, x1*x2*x0*x1^-1*x0^-1*x2^-1 > + sage: H.affine_fundamental_group() # repeat to use the attribute + Finitely presented group + < x0, x1, x2 | x0*x1*x2*x1^-1*x0^-1*x2^-1, x1*x2*x0*x1^-1*x0^-1*x2^-1 > + sage: T. = QQ[] + sage: K. = NumberField(t^3 + t + 1) + sage: L. = OrderedHyperplaneArrangements(K) + sage: H = L(a*x + y -1, x + a*y + 1, x - 1, y - 1) + sage: H.affine_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the base field is not in QQbar + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L([t - j for j in range(4)]).affine_fundamental_group() + Finitely presented group < x0, x1, x2, x3 | > + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(L.gens() + (x + y + z + 1,)).affine_fundamental_group().sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x3*x1, + x3^-1*x0^-1*x3*x0, x2^-1*x1^-1*x2*x1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A = OrderedHyperplaneArrangements(QQ, names=()) + sage: H = A(); H + Empty hyperplane arrangement of dimension 0 + sage: H.affine_fundamental_group() + Finitely presented group < | > + """ + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + if self._affine_fundamental_group: + return self._affine_fundamental_group + n = self.dimension() + r = len(self) + if n == 0: + return FreeGroup(0) / [] + if n == 1: + G = FreeGroup(r) / [] + dic = {j: G.gen(j) for j in range(r)} + dic[r] = [prod(G.gens()) ** -1] + self._affine_fundamental_group = G + self._affine_meridians = dic + return G + if n == 2: + S = self.parent().ambient_space().symmetric_space() + coord = vector((1,) + S.gens()) + Af = AffinePlaneCurveArrangements(K, names=self.parent().variable_names()) + L = Af([vector(line.coefficients()) * coord for line in self]) + G = L.fundamental_group() + self._affine_fundamental_group = G + self._affine_meridians = L._meridians_simpl_vertical + return G + H1 = self.hyperplane_section(proj=False) + G = H1.affine_fundamental_group() + self._affine_fundamental_group = G + self._affine_meridians = H1._affine_meridians + return G + + def affine_meridians(self): + r""" + Return the meridians of each hyperplane (including the one at infinity). + + OUTPUT: + + A dictionary + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: L = [y + x, y + x - 1] + sage: H = A(L) + sage: g = H.affine_fundamental_group() + sage: g + Finitely presented group < x0, x1 | > + sage: H.affine_meridians() + {0: [x0], 1: [x1], 2: [x1^-1*x0^-1]} + sage: H1 = H.add_hyperplane(y - x) + sage: H1.affine_meridians() + {0: [x0], 1: [x1], 2: [x2], 3: [x2^-1*x1^-1*x0^-1]} + """ + if self._affine_meridians is None: + self.affine_fundamental_group() + return dict(self._affine_meridians) + + def projective_fundamental_group(self): + r""" + Return the fundamental group of the complement of a projective + hyperplane arrangement. + + OUTPUT: + + The finitely presented group of the complement + in the projective space whose equations have + coefficients in a subfield of `\QQbar`. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(x, y, x + y) + sage: H.projective_fundamental_group() + Finitely presented group < x0, x1 | > + + sage: # needs sirocco sage.graphs + sage: A3. = OrderedHyperplaneArrangements(QQ) + sage: H = A3(hyperplane_arrangements.braid(4).essentialization()) + sage: G3 = H.projective_fundamental_group(); G3.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3, x4 | x4^-1*x3^-1*x2^-1*x3*x4*x0*x2*x0^-1, + x4^-1*x2^-1*x4*x2, x4^-1*x1^-1*x0^-1*x1*x4*x0, + x4^-1*x1^-1*x0^-1*x4*x0*x1, + x4^-1*x1^-1*x3*x0*x1*x3^-1*x2^-1*x4*x0^-1*x2, + x3^-1*x2^-1*x1^-1*x0^-1*x3*x0*x1*x2, + x3^-1*x1^-1*x3*x1 > + sage: G3.abelian_invariants() + (0, 0, 0, 0, 0) + sage: A4. = OrderedHyperplaneArrangements(QQ) + sage: H = A4(hyperplane_arrangements.braid(4)) + sage: G4 = H.projective_fundamental_group(); G4.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3, x4 | x4^-1*x3^-1*x2^-1*x3*x4*x0*x2*x0^-1, + x4^-1*x2^-1*x4*x2, x4^-1*x1^-1*x0^-1*x1*x4*x0, + x4^-1*x1^-1*x0^-1*x4*x0*x1, + x4^-1*x1^-1*x3*x0*x1*x3^-1*x2^-1*x4*x0^-1*x2, + x3^-1*x2^-1*x1^-1*x0^-1*x3*x0*x1*x2, + x3^-1*x1^-1*x3*x1 > + sage: G4.abelian_invariants() + (0, 0, 0, 0, 0) + + sage: # needs sirocco + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: H = hyperplane_arrangements.coordinate(5) + sage: H = L(H) + sage: g = H.projective_fundamental_group() + sage: g.is_abelian(), g.abelian_invariants() + (True, (0, 0, 0, 0)) + sage: L(t0, t1, t2, t3, t4, t0 - 1).projective_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the arrangement is not projective + sage: T. = QQ[] + sage: K. = NumberField(t^3 + t + 1) + sage: L. = OrderedHyperplaneArrangements(K) + sage: H = L(a*x + y - z, x + a*y + z, x - z, y - z) + sage: H.projective_fundamental_group() + Traceback (most recent call last): + ... + TypeError: the base field is not in QQbar + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(); H + Empty hyperplane arrangement of dimension 1 + sage: H.projective_fundamental_group() + Finitely presented group < | > + """ + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + if not self.is_linear(): + raise TypeError('the arrangement is not projective') + if self._projective_fundamental_group: + return self._projective_fundamental_group + n = self.dimension() + r = len(self) + if n == 1: + return FreeGroup(0) / [] + if n == 2: + G = FreeGroup(r - 1) / [] + dic = {j: G.gen(j) for j in range(r - 1)} + dic[r - 1] = [prod(G.gens()) ** -1] + self._projective_fundamental_group = G + self._projective_meridians = dic + return G + if n == 3: + S = self.parent().ambient_space().symmetric_space() + coord = vector(S.gens()) + Proj = ProjectivePlaneCurveArrangements(K, + names=self.parent().variable_names()) + L = Proj([vector(line.coefficients()[1:]) * coord for line in self]) + G = L.fundamental_group() + self._projective_fundamental_group = G + self._projective_meridians = L._meridians_simpl + return G + H1 = self.hyperplane_section() + G = H1.projective_fundamental_group() + self._projective_fundamental_group = G + self._projective_meridians = H1._projective_meridians + return G + + def projective_meridians(self): + r""" + Return the meridian of each hyperplane. + + OUTPUT: + + A dictionary + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: A. = OrderedHyperplaneArrangements(QQ) + sage: H = A(x, y, x + y) + sage: H.projective_meridians() + {0: x0, 1: x1, 2: [x1^-1*x0^-1]} + + sage: # needs sirocco sage.graphs + sage: A3. = OrderedHyperplaneArrangements(QQ) + sage: H = A3(hyperplane_arrangements.braid(4).essentialization()) + sage: H.projective_meridians() + {0: [x2^-1*x0^-1*x4^-1*x3^-1*x1^-1], + 1: [x3], 2: [x4], 3: [x1], 4: [x2], 5: [x0]} + sage: A4. = OrderedHyperplaneArrangements(QQ) + sage: H = A4(hyperplane_arrangements.braid(4)) + sage: H.projective_meridians() + {0: [x2^-1*x0^-1*x4^-1*x3^-1*x1^-1], 1: [x3], + 2: [x4], 3: [x0], 4: [x2], 5: [x1]} + + sage: # needs sirocco + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: H = hyperplane_arrangements.coordinate(5) + sage: H = L(H) + sage: H.projective_meridians() + {0: [x2], 1: [x3], 2: [x0], 3: [x3^-1*x2^-1*x1^-1*x0^-1], 4: [x1]} + """ + if self._projective_meridians is None: + self.projective_fundamental_group() + return dict(self._projective_meridians) + + +class OrderedHyperplaneArrangements(HyperplaneArrangements): + """ + Ordered Hyperplane arrangements. + + For more information on hyperplane arrangements, see + :mod:`sage.geometry.hyperplane_arrangement.arrangement`. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = HyperplaneArrangements(QQ) + sage: x + Hyperplane x + 0*y + 0 + sage: x + y + Hyperplane x + y + 0 + sage: H(x, y, x-1, y-1) + Arrangement + """ + Element = OrderedHyperplaneArrangementElement + + def _element_constructor_(self, *args, **kwds): + """ + Construct an element of ``self``. + + INPUT: + + - ``*args`` -- positional arguments, each defining a + hyperplane; alternatively, a single polytope or a single + hyperplane arrangement + + - ``signed`` -- boolean (default: ``True``); whether to + preserve signs of hyperplane equations + + - ``check`` -- boolean (default: ``True``); whether to + perform argument checking. + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ) + sage: L(x) + Arrangement + sage: L(x, y) + Arrangement + sage: L([x, y]) + Arrangement + sage: L([0, 1, 0], [0, 0, 1]) + Arrangement + sage: L([[0, 0, 1], [0, 1, 0]]) + Arrangement + + sage: L(polytopes.hypercube(2)) + Arrangement <-x + 1 | -y + 1 | x + 1 | y + 1> + + sage: L(-x, x + y - 1, signed=False) + Arrangement + + TESTS:: + + sage: L() + Empty hyperplane arrangement of dimension 2 + sage: L(0) # zero is equivalent to no argument, Issue #8648 + Empty hyperplane arrangement of dimension 2 + sage: L(0*x) # degenerate hyperplane is NOT allowed + Traceback (most recent call last): + ... + ValueError: linear expression must be non-constant to define a hyperplane + sage: L(0*x, y) # ditto + Traceback (most recent call last): + ... + ValueError: linear expression must be non-constant to define a hyperplane + """ + if len(args) == 1: + arg = args[0] + if isinstance(arg, HyperplaneArrangementElement) and arg.parent() is self: + # optimization if argument is already a hyperplane arrangement + return arg + if arg == 0 and not isinstance(arg, Hyperplane): + # zero = neutral element under addition = the empty hyperplane arrangement + args = [] + # process keyword arguments + not_char2 = (self.base_ring().characteristic() != 2) + signed = kwds.pop('signed', not_char2) + check = kwds.pop('check', True) + backend = kwds.pop('backend', None) + if kwds: + raise ValueError('unknown keyword argument') + # process positional arguments + AA = self.ambient_space() + try: + hyperplanes = [AA(a) for a in args] + except (TypeError, ValueError, AttributeError): + if len(args) > 1: + raise + arg = args[0] + if hasattr(arg, 'Hrepresentation'): + hyperplanes = [AA(h) for h in arg.Hrepresentation()] + else: + hyperplanes = [AA(a) for a in arg] + hyperplanes = [h.primitive(signed) for h in hyperplanes] + if check: + if signed and not not_char2: + raise ValueError('cannot be signed in characteristic 2') + for h in hyperplanes: + if h.A() == 0: + raise ValueError('linear expression must be non-constant to define a hyperplane') + if not_char2 and -h in hyperplanes: + raise ValueError('arrangement cannot simultaneously have h and -h as hyperplane') + return self.element_class(self, tuple(hyperplanes), backend=backend) + + def _repr_(self): + """ + Return a string representation. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: L. = OrderedHyperplaneArrangements(QQ); L + Ordered hyperplane arrangements in 2-dimensional linear space + over Rational Field with coordinates x, y + """ + return 'Ordered hyperplane arrangements in {0}'.format(self.ambient_space()) diff --git a/src/sage/schemes/curves/affine_curve.py b/src/sage/schemes/curves/affine_curve.py index 992cd528803..efbc690b543 100644 --- a/src/sage/schemes/curves/affine_curve.py +++ b/src/sage/schemes/curves/affine_curve.py @@ -174,10 +174,11 @@ class AffineCurve(Curve_generic, AlgebraicScheme_subscheme_affine): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -194,10 +195,11 @@ def __init__(self, A, X): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -456,23 +458,25 @@ def plot(self, *args, **kwds): sage: R. = QQ[] sage: C = Curve(x^3 - y^2) - sage: C.plot() # needs sage.plot + sage: C.plot() # needs sage.plot Graphics object consisting of 1 graphics primitive A 5-nodal curve of degree 11. This example also illustrates some of the optional arguments:: + sage: # needs sage.plot() sage: R. = ZZ[] sage: C = Curve(32*x^2 - 2097152*y^11 + 1441792*y^9 ....: - 360448*y^7 + 39424*y^5 - 1760*y^3 + 22*y - 1) - sage: C.plot((x, -1, 1), (y, -1, 1), plot_points=400) # needs sage.plot + sage: C.plot((x, -1, 1), (y, -1, 1), plot_points=400) Graphics object consisting of 1 graphics primitive A line over `\mathbf{RR}`:: + sage: # needs sage.symbolic sage.plot sage: R. = RR[] - sage: C = Curve(R(y - sqrt(2)*x)) # needs sage.symbolic - sage: C.plot() # needs sage.plot + sage: C = Curve(R(y - sqrt(2)*x)) + sage: C.plot() Graphics object consisting of 1 graphics primitive """ Id = self.defining_ideal() @@ -843,10 +847,11 @@ def __init__(self, A, X): EXAMPLES:: + sage: # needs sage.rings.number_field sage: R. = QQ[] - sage: K. = NumberField(v^2 + 3) # needs sage.rings.number_field - sage: A. = AffineSpace(K, 3) # needs sage.rings.number_field - sage: C = Curve([z - u*x^2, y^2], A); C # needs sage.rings.number_field + sage: K. = NumberField(v^2 + 3) + sage: A. = AffineSpace(K, 3) + sage: C = Curve([z - u*x^2, y^2], A); C Affine Curve over Number Field in u with defining polynomial v^2 + 3 defined by (-u)*x^2 + z, y^2 @@ -1293,9 +1298,10 @@ def blowup(self, P=None): :: - sage: A. = AffineSpace(QuadraticField(-1), 2) # needs sage.rings.number_field - sage: C = A.curve([y^2 + x^2]) # needs sage.rings.number_field - sage: C.blowup() # needs sage.rings.number_field + sage: # needs sage.rings.number_field + sage: A. = AffineSpace(QuadraticField(-1), 2) + sage: C = A.curve([y^2 + x^2]) + sage: C.blowup() Traceback (most recent call last): ... TypeError: this curve must be irreducible @@ -1725,7 +1731,6 @@ def tangent_line(self, p): defined by: -2*y + z + 1, x + y + z sage: _ == C.tangent_line(p) True - """ A = self.ambient_space() R = A.coordinate_ring() @@ -1752,8 +1757,48 @@ class AffinePlaneCurve_field(AffinePlaneCurve, AffineCurve_field): """ _point = AffinePlaneCurvePoint_field + def has_vertical_asymptote(self): + """ + Check if the curve is not a line and has vertical asymptotes. + + EXAMPLES:: + + sage: A2. = AffineSpace(2, QQ) + sage: Curve(x).has_vertical_asymptote() + False + sage: Curve(y^2 * x + x + y).has_vertical_asymptote() + True + """ + A = self.ambient_space() + R = A.coordinate_ring() + x, y = R.gens() + f = self.defining_polynomial().radical() + dy = f.degree(y) + dxy = f.coefficient({y: dy}).degree() + return dxy > 0 and f.degree() > 1 + + def is_vertical_line(self): + """ + Check if the curve is a vertical line. + + EXAMPLES:: + + sage: A2. = AffineSpace(2, QQ) + sage: Curve(x - 1).is_vertical_line() + True + sage: Curve(x - y).is_vertical_line() + False + sage: Curve(y^2 * x + x + y).is_vertical_line() + False + """ + A = self.ambient_space() + R = A.coordinate_ring() + x, y = R.gens() + f = self.defining_polynomial().radical() + return f.degree(y) == 0 and f.degree() == 1 + @cached_method - def fundamental_group(self, simplified=True, puiseux=False): + def fundamental_group(self, simplified=True, puiseux=True): r""" Return a presentation of the fundamental group of the complement of ``self``. @@ -1762,9 +1807,9 @@ def fundamental_group(self, simplified=True, puiseux=False): - ``simplified`` -- (default: ``True``) boolean to simplify the presentation. - - ``puiseux`` -- (default: ``False``) boolean to decide if the + - ``puiseux`` -- (default: ``True``) boolean to decide if the presentation is constructed in the classical way or using Puiseux - shortcut. If ``True``, ``simplified`` is set to ``False``. + shortcut. OUTPUT: @@ -1781,17 +1826,18 @@ def fundamental_group(self, simplified=True, puiseux=False): .. NOTE:: The curve must be defined over the rationals or a number field - with an embedding over `\QQbar`. + with an embedding over `\QQbar`. This functionality requires + the ``sirocco`` package to be installed. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: A. = AffineSpace(QQ, 2) sage: C = A.curve(y^2 - x^3 - x^2) - sage: C.fundamental_group() + sage: C.fundamental_group(puiseux=False) Finitely presented group < x0 | > sage: bm = C.braid_monodromy() - sage: g = C.fundamental_group(puiseux=True) + sage: g = C.fundamental_group(simplified=False) sage: g.sorted_presentation() Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0, x1^-1*x0 > sage: g.simplified() @@ -1808,15 +1854,28 @@ def fundamental_group(self, simplified=True, puiseux=False): Defining a sage: A. = AffineSpace(F, 2) sage: C = A.curve(y^2 - a*x^3 - x^2) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | > - - .. WARNING:: - - This functionality requires the sirocco package to be installed. + sage: C = A.curve(x * (x - 1)) + sage: C.fundamental_group() # needs sirocco + Finitely presented group < x0, x1 | > """ from sage.schemes.curves.zariski_vankampen import fundamental_group_from_braid_mon - return fundamental_group_from_braid_mon(self.braid_monodromy(), simplified=simplified, puiseux=puiseux) + bm = self.braid_monodromy() + if not bm: + f = self.defining_polynomial() + x, y = f.parent().gens() + d0 = f.degree(y) + f0 = f.coefficient({y: d0}) + d = d0 + f0.degree(x) + else: + d = bm[0].parent().strands() + G = fundamental_group_from_braid_mon(bm, degree=d, + simplified=simplified, + puiseux=puiseux) + if simplified: + G = G.simplified() + return G @cached_method def braid_monodromy(self): @@ -1829,20 +1888,31 @@ def braid_monodromy(self): each of this paths is the conjugated of a loop around one of the points in the discriminant of the projection of ``self``. - NOTE: + .. NOTE:: + + The projection over the `x` axis is used if there are no vertical asymptotes. + Otherwise, a linear change of variables is done to fall into the previous case. - The projection over the `x` axis is used if there are no vertical asymptotes. - Otherwise, a linear change of variables is done to fall into the previous case. + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. EXAMPLES:: sage: A. = AffineSpace(QQ, 2) sage: C = A.curve((x^2-y^3)*(x+3*y-5)) - sage: C.braid_monodromy() # optional - sirocco + sage: C.braid_monodromy() # needs sirocco [s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1, 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] + sage: T. = QQ[] + sage: K. = NumberField(t^3 + 2, 'a') + sage: A. = AffineSpace(K, 2) + sage: Curve(y^2 + a * x).braid_monodromy() + Traceback (most recent call last): + ... + NotImplementedError: the base field must have an embedding to the algebraic field """ from sage.schemes.curves.zariski_vankampen import braid_monodromy @@ -2047,9 +2117,10 @@ def function_field(self): :: - sage: A. = AffineSpace(GF(8), 2) # needs sage.rings.finite_rings - sage: C = Curve(x^5 + y^5 + x*y + 1) # needs sage.rings.finite_rings - sage: C.function_field() # needs sage.rings.finite_rings + sage: # needs sage.rings.finite_rings + sage: A. = AffineSpace(GF(8), 2) + sage: C = Curve(x^5 + y^5 + x*y + 1) + sage: C.function_field() Function field in y defined by y^5 + x*y + x^5 + 1 """ return self._function_field diff --git a/src/sage/schemes/curves/all.py b/src/sage/schemes/curves/all.py index 147c2e1e6fe..b34592457c6 100644 --- a/src/sage/schemes/curves/all.py +++ b/src/sage/schemes/curves/all.py @@ -23,3 +23,11 @@ from .constructor import Curve from .projective_curve import Hasse_bounds + +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'PlaneCurveArrangements') + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'AffinePlaneCurveArrangements') + +lazy_import('sage.schemes.curves.plane_curve_arrangement', 'ProjectivePlaneCurveArrangements') diff --git a/src/sage/schemes/curves/plane_curve_arrangement.py b/src/sage/schemes/curves/plane_curve_arrangement.py new file mode 100644 index 00000000000..df4ba63f4e6 --- /dev/null +++ b/src/sage/schemes/curves/plane_curve_arrangement.py @@ -0,0 +1,1304 @@ +r""" +Affine and Projective Plane Curve Arrangements + +We create classes :class:`AffinePlaneCurveArrangements` +and :class:`ProjectivePlaneCurveArrangements` +following the properties of :class:`HyperplaneArrangements`:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: C = H(3*x + 2*y - x^2 + y^3 - 7); C + Arrangement (y^3 - x^2 + 3*x + 2*y - 7) in Affine Space of dimension 2 over Rational Field + +The individual curves will be in :class:`AffinePlaneCurve` or in :class:`ProjectivePlaneCurve`:: + + sage: C[0].parent() + + +The default base field is `\QQ`, the rational numbers. +Number fields are also possible (also with fixed embeddings in +`\QQbar`):: + + sage: # needs sage.rings.number_field + sage: x = polygen(QQ, 'x') + sage: NF. = NumberField(x^4 - 5 * x^2 + 5, embedding=1.90) + sage: H. = AffinePlaneCurveArrangements(NF) + sage: A = H(y^2 - a * z, y^2 + a * z); A + Arrangement (y^2 + (-a)*z, y^2 + a*z) in Affine Space of dimension 2 + over Number Field in a with defining polynomial + x^4 - 5*x^2 + 5 with a = 1.902113032590308? + sage: A.base_ring() + Number Field in a with defining polynomial x^4 - 5*x^2 + 5 + with a = 1.902113032590308? + + +AUTHORS: + +- Enrique Artal (2023-10): initial version +""" + +# ***************************************************************************** +# Copyright (C) 2023 Enrique Artal +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +# ***************************************************************************** + +from itertools import combinations +from sage.categories.sets_cat import Sets +from sage.groups.free_group import FreeGroup +from sage.misc.abstract_method import abstract_method +from sage.misc.cachefunc import cached_method +from sage.misc.misc_c import prod +from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing +from sage.rings.qqbar import QQbar +from sage.rings.ring import _Fields +from sage.schemes.affine.affine_space import AffineSpace +from sage.schemes.curves.affine_curve import AffinePlaneCurve +from sage.schemes.curves.constructor import Curve +from sage.schemes.curves.projective_curve import ProjectiveSpace, ProjectivePlaneCurve +from sage.schemes.curves.zariski_vankampen import braid_monodromy, fundamental_group_arrangement +from sage.structure.category_object import normalize_names +from sage.structure.parent import Parent +from sage.structure.element import Element +from sage.structure.richcmp import richcmp +from sage.structure.unique_representation import UniqueRepresentation + + +class PlaneCurveArrangementElement(Element): + """ + An ordered plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct a plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`PlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Projective Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + super().__init__(parent) + self._curves = tuple(curves) + if check: + affine = all(isinstance(h, AffinePlaneCurve) for h in curves) + projective = all(isinstance(h, ProjectivePlaneCurve) for h in curves) + if not (affine or projective): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is parent.ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + + def __getitem__(self, i): + """ + Return the `i`-th curve. + + INPUT: + + - ``i`` -- integer + + OUTPUT: + + The `i`-th curve. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(y^2 - x, y^3 + 2 * x^2, x^4 + y^4 + 1) + Arrangement (y^2 - x, y^3 + 2*x^2, x^4 + y^4 + 1) + in Affine Space of dimension 2 over Rational Field + """ + return self._curves[i] + + def __hash__(self): + r""" + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H((x * y, x + y +1)).__hash__() # random + -4938643871296220686 + """ + return hash(self.curves()) + + def ncurves(self): + r""" + Return the number of curves in the arrangement. + + OUTPUT: + + An integer. + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: h = H((x * y, x + y + z)) + sage: h.ncurves() + 2 + sage: len(h) # equivalent + 2 + """ + return len(self._curves) + + __len__ = ncurves + + def curves(self): + r""" + Return the curves in the arrangement as a tuple. + + OUTPUT: + + A tuple. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H((x * y, x + y + 1)) + sage: h.curves() + (Affine Plane Curve over Rational Field defined by x*y, + Affine Plane Curve over Rational Field defined by x + y + 1) + + Note that the curves can be indexed as if they were a list:: + + sage: h[1] + Affine Plane Curve over Rational Field defined by x + y + 1 + """ + return self._curves + + def _repr_(self): + r""" + String representation for a curve arrangement. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: h + Arrangement of 5 curves in Affine Space of dimension 2 over Rational Field + sage: H(()) + Arrangement () in Affine Space of dimension 2 over Rational Field + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + z, x^3 * z^2 - y^5, x^2 * y^2 * z + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - z^3)^2]) + sage: h + Arrangement of 5 curves in Projective Space of dimension 2 over Rational Field + """ + if not self: + return 'Empty curve arrangement in {0}'.format(self.parent().ambient_space()) + elif len(self) < 5: + curves = ', '.join(h.defining_polynomial()._repr_() + for h in self._curves) + return 'Arrangement ({0}) in {1}'.format(curves, + self.parent().ambient_space()) + return 'Arrangement of {0} curves in {1}'.format(len(self), + self.parent().ambient_space()) + + def _richcmp_(self, other, op): + """ + Compare two curve arrangements. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x) == H(y) + False + sage: H(x) == H(2 * x) + True + + TESTS:: + + sage: H(x) == 0 + False + """ + return richcmp(self._curves, other._curves, op) + + def union(self, other): + r""" + The union of ``self`` with ``other``. + + INPUT: + + - ``other`` -- a curve arrangement or something that can + be converted into a curve arrangement + + OUTPUT: + + A new curve arrangement. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: C = Curve(x^8 - y^8 -x^4 * y^4) + sage: h1 = h.union(C); h1 + Arrangement of 6 curves in Affine Space of dimension 2 over Rational Field + sage: h1 == h1.union(C) + True + """ + P = self.parent() + other_h = P(other) + curves0 = self._curves + other_h._curves + curves = [] + for h in curves0: + if h not in curves: + curves.append(h) + result = P(*curves) + return result + + add_curves = union + + __or__ = union + + def deletion(self, curves): + r""" + Return the curve arrangement obtained by removing ``curves``. + + INPUT: + + - ``curves`` -- a curve or curve arrangement + + OUTPUT: + + A new curve arrangement with the given curve(s) + ``h`` removed. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: h = H([x * y, x + y + 1, x^3 - y^5, x^2 * y^2 + x^5 + y^5, (x^2 + y^2)^3 + (x^3 + y^3 - 1)^2]) + sage: C = h[-1] + sage: h.deletion(C) + Arrangement (x*y, x + y + 1, -y^5 + x^3, x^5 + y^5 + x^2*y^2) + in Affine Space of dimension 2 over Rational Field + sage: h.deletion(x) + Traceback (most recent call last): + ... + ValueError: curve is not in the arrangement + """ + parent = self.parent() + curves = parent(curves) + planes = list(self) + for curve in curves: + try: + planes.remove(curve) + except ValueError: + raise ValueError('curve is not in the arrangement') + return parent(planes) + + def change_ring(self, base_ring): + """ + Return curve arrangement over the new base ring. + + INPUT: + + - ``base_ring`` -- the new base ring; must be a field for + curve arrangements + + OUTPUT: + + The curve arrangement obtained by changing the base + field, as a new curve arrangement. + + EXAMPLES:: + + sage: # needs sage.rings.number_field + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 - x^3, x, y, y^2 + x * y + x^2) + sage: K. = CyclotomicField(3) + sage: A.change_ring(K) + Arrangement (-x^3 + y^2, x, y, x^2 + x*y + y^2) in Affine Space of + dimension 2 over Cyclotomic Field of order 3 and degree 2 + """ + parent = self.parent().change_ring(base_ring) + curves = tuple(c.change_ring(base_ring) for c in self) + return parent(curves) + + @cached_method + def coordinate_ring(self): + """ + Return the coordinate ring of ``self``. + + OUTPUT: + + The coordinate ring of the curve arrangement. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: C = L(x, y) + sage: C.coordinate_ring() + Multivariate Polynomial Ring in x, y over Rational Field + sage: P. = ProjectivePlaneCurveArrangements(QQ) + sage: C = P(x, y) + sage: C.coordinate_ring() + Multivariate Polynomial Ring in x, y, z over Rational Field + """ + return self._curves[0].defining_polynomial().parent() + + def defining_polynomials(self): + r""" + Return the defining polynomials of the elements of ``self``. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 - x^3, x, y, y^2 + x * y + x^2) + sage: A.defining_polynomials() + (-x^3 + y^2, x, y, x^2 + x*y + y^2) + """ + return tuple([h.defining_polynomial() for h in self._curves]) + + def defining_polynomial(self, simplified=True): + r""" + Return the defining polynomial of the union of the curves in ``self``. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y ** 2 + x ** 2, x, y) + sage: prod(A.defining_polynomials()) == A.defining_polynomial() + True + """ + return prod(self.defining_polynomials()) + + def have_common_factors(self): + r""" + Check if the curves have common factors. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(x * y, x^2 + x* y^3) + sage: A.have_common_factors() + True + sage: H(x * y, x + y^3).have_common_factors() + False + """ + L = [c.defining_polynomial() for c in self._curves] + C = combinations(L, 2) + return any(f1.gcd(f2).degree() > 0 for f1, f2 in C) + + def reduce(self, clean=False, verbose=False): + r""" + Replace the curves by their reduction. + + INPUT: + + - ``clean`` -- boolean (default: ``False``); if ``False`` + and there are common factors it returns ``None`` and + a warning message. If ``True``, the common factors are kept + only in the first occurance. + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2, (x + y)^3 * (x^2 + x * y + y^2)) + sage: A.reduce() + Arrangement (y, x^3 + 2*x^2*y + 2*x*y^2 + y^3) in Affine Space + of dimension 2 over Rational Field + sage: C = H(x*y, x*(y + 1)) + sage: C.reduce(verbose=True) + Some curves have common components + sage: C.reduce(clean=True) + Arrangement (x*y, y + 1) in Affine Space of dimension 2 + over Rational Field + sage: C = H(x*y, x) + sage: C.reduce(clean=True) + Arrangement (x*y) in Affine Space of dimension 2 over Rational Field + """ + P = self.parent() + L = [self._curves[0].defining_polynomial().radical()] + for c in self._curves[1:]: + g = c.defining_polynomial().radical() + for f in L: + d = g.gcd(f) + if d.degree() > 0 and not clean: + if verbose: + print("Some curves have common components") + return None + g //= d + if g.degree() > 0: + L.append(g) + return P(*L) + + +class AffinePlaneCurveArrangementElement(PlaneCurveArrangementElement): + """ + An ordered affine plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct an ordered affine plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`AffinePlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: elt = H(x, y); elt + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + Element.__init__(self, parent) + self._curves = tuple(curves) + if check: + if not all(isinstance(h, AffinePlaneCurve) for h in curves): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is self.parent().ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + self._braid_monodromy_non_vertical = None + self._braid_monodromy_vertical = None + self._strands_nonvertical = None + self._strands_vertical = None + self._fundamental_group_nonsimpl_nonvertical = None + self._fundamental_group_nonsimpl_vertical = None + self._fundamental_group_simpl_nonvertical = None + self._fundamental_group_simpl_vertical = None + self._meridians_nonsimpl_nonvertical = None + self._meridians_nonsimpl_vertical = None + self._meridians_simpl_nonvertical = None + self._meridians_simpl_vertical = None + self._vertical_lines_in_braid_mon = None + + def fundamental_group(self, simplified=True, vertical=True, + projective=False): + r""" + Return the fundamental group of the complement of the union + of affine plane curves in `\CC^2`. + + INPUT: + + - ``vertical`` -- boolean (default: ``True``); if ``True``, there + are no vertical asymptotes, and there are vertical lines, then a + simplified braid :func:`braid_monodromy` is used + + - ``simplified`` -- boolean (default: ``True``); if it is ``True``, the + group is simplified + + - ``projective`` -- boolean (default: ``False``); to be used in the + method for projective curves + + OUTPUT: + + A finitely presented group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2 | x2*x0*x2^-1*x0^-1, x1*x0*x1^-1*x0^-1, (x2*x1)^2*(x2^-1*x1^-1)^2 > + sage: A.meridians() + {0: [x1, x2*x1*x2^-1], 1: [x0], 2: [x2], + 3: [x1^-1*x2^-1*x1^-1*x0^-1]} + sage: G = A.fundamental_group(simplified=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x0*x1*x0^-1, + x3^-1*x1^-1*x3*x0*x1*x0^-1*x2^-1*x0^-1*(x2*x0)^2*x1^-1*x0^-1, + x3^-1*x0^-1*x3*x0*x1*x0^-1*x2^-1*x0*x2*x0*x1^-1*x0^-1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False) + {0: [x1, x2], 1: [x0], 2: [x3], 3: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: A.fundamental_group(vertical=False) + Finitely presented group + < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x1*x0*x1^-1*x0^-1, (x0*x2)^2*(x0^-1*x2^-1)^2 > + sage: A.meridians(vertical=False) + {0: [x2, x0*x2*x0^-1], 1: [x1], 2: [x0], 3: [x0*x2^-1*x0^-1*x2^-1*x1^-1*x0^-1]} + sage: G = A.fundamental_group(simplified=False, vertical=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + (x3^-1*x2^-1*x0^-1*x2)^2*(x3*x2^-1*x0*x2)^2, + x3^-1*x2^-1*x0^-1*x2*x3^-1*x2^-1*x0*x2*x3*x2, + x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False, vertical=False) + {0: [x2, x3], 1: [x1], 2: [x0], 3: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: A = H(x * y^2 + x + y, y + x -1, x, y) + sage: G = A.fundamental_group() + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x3*x1, + x3^-1*x0^-1*x3*x0, x2^-1*x1^-1*x2*x1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + """ + if simplified and vertical: + computed = self._fundamental_group_simpl_vertical + elif simplified and not vertical: + computed = self._fundamental_group_simpl_nonvertical + elif not simplified and vertical: + computed = self._fundamental_group_nonsimpl_vertical + else: + computed = self._fundamental_group_nonsimpl_nonvertical + if computed: + return computed + K = self.base_ring() + R = self.coordinate_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + C = self.reduce() + L = C.defining_polynomials() + if vertical: + bm = self._braid_monodromy_vertical + else: + bm = self._braid_monodromy_non_vertical + if bm is not None: # bm could be [] + if not vertical: + st = self._strands_nonvertical + d1 = prod(L).degree() + bd = (bm, st, dict(), d1) + else: + st = self._strands_vertical + d1 = prod(L).degree(R.gen(1)) + bd = (bm, st, self._vertical_lines_in_braid_mon, d1) + else: + bd = None + G, dic = fundamental_group_arrangement(L, simplified=simplified, + puiseux=True, + projective=projective, + vertical=vertical, + braid_data=bd) + if simplified and vertical: + self._fundamental_group_simpl_vertical = G + self._meridians_simpl_vertical = dic + elif simplified and not vertical: + self._fundamental_group_simpl_nonvertical = G + self._meridians_simpl_nonvertical = dic + elif not simplified and vertical: + self._fundamental_group_nonsimpl_vertical = G + self._meridians_nonsimpl_vertical = dic + else: + self._fundamental_group_nonsimpl_nonvertical = G + self._meridians_nonsimpl_nonvertical = dic + return G + + def meridians(self, simplified=True, vertical=True): + r""" + Return the meridians of each irreducible component. + + OUTPUT: + + A dictionary which associates the index of each curve with its meridians, + including the line at infinity if it can be omputed + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed + and :meth:`AffinePlaneCurveArrangements.fundamental_group` with the same options, + where some examples are shown. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(x-1, y, x, y - 1) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2, x3 | x2*x0*x2^-1*x0^-1, x2*x1*x2^-1*x1^-1, + x3*x0*x3^-1*x0^-1, x3*x1*x3^-1*x1^-1 > + sage: A.meridians() + {0: [x2], 1: [x0], 2: [x3], 3: [x1], 4: [x3^-1*x2^-1*x1^-1*x0^-1]} + """ + if simplified and vertical: + computed = self._meridians_simpl_vertical + elif simplified and not vertical: + computed = self._meridians_simpl_nonvertical + elif not simplified and vertical: + computed = self._meridians_nonsimpl_vertical + else: + computed = self._meridians_nonsimpl_nonvertical + if computed: + return dict(computed) + self.fundamental_group(simplified=simplified, vertical=vertical) + if simplified and vertical: + return dict(self._meridians_simpl_vertical) + elif simplified and not vertical: + return dict(self._meridians_group_simpl_nonvertical) + elif not simplified and vertical: + return dict(self._meridians_nonsimpl_vertical) + else: + return dict(self._meridians_nonsimpl_nonvertical) + + def braid_monodromy(self, vertical=True): + r""" + Return the braid monodromy of the complement of the union + of affine plane curves in `\CC^2`. If there are vertical + asymptotes a change of variable is done. + + INPUT: + + - ``vertical`` -- boolean (default: ``True``); if it is ``True``, there + are no vertical asymptotes, and there are vertical lines, then a + simplified :func:`braid_monodromy` is computed. + + OUTPUT: + + A braid monodromy with dictionnaries identifying strands with components + and braids with vertical lines. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.braid_monodromy(vertical=False) + [s1*s0*(s1*s2*s1)^2*s2*(s1^-1*s2^-1)^2*s1^-1*s0^-1*s1^-1, + s1*s0*(s1*s2)^2*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*(s1*s2^-1)^2*s0*s1*s2*s1*s0*s2^-1*s1^-3*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*s1*s2^-1*s1^4*s2*s1^-1*s2^-1*s1^-1*s0^-1*s1^-1, + s1*s0*s1*s2*s1*s2^-1*s1^-1*s2*s0^-1*s1^-1] + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if vertical: + computed = self._braid_monodromy_vertical + else: + computed = self._braid_monodromy_non_vertical + if computed is not None: + return computed + K = self.base_ring() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + L = self.defining_polynomials() + bm, dic, dv, d1 = braid_monodromy(prod(L), arrangement=L, + vertical=vertical) + if vertical: + self._braid_monodromy_vertical = bm + self._strands_vertical = dic + self._vertical_lines_in_braid_mon = dv + else: + self._braid_monodromy_non_vertical = bm + self._strands_nonvertical = dic + return bm + + def strands(self): + r""" + Return the strands for each member of the arrangement. + + OUTPUT: + + A dictionary which associates to the index of each strand + its associated component if the braid monodromy has been + calculated with ``vertical=False``. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: bm = A.braid_monodromy() + sage: A.strands() + {0: 2, 1: 1, 2: 0, 3: 0} + """ + if not self._strands_nonvertical: + self._braid_monodromy = self.braid_monodromy(vertical=False) + return self._strands_nonvertical + + def vertical_strands(self): + r""" + Return the strands if the braid monodromy has been computed with + the vertical option. + + OUTPUT: + + A dictionary which associates to the index of each strand + its associated component if the braid monodromy has been + calculated with ``vertical=True``. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.vertical_strands() + {0: 1, 1: 0, 2: 0} + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if not self._strands_vertical: + self.braid_monodromy(vertical=True) + return self._strands_vertical + + def vertical_lines_in_braid_monodromy(self): + r""" + Return the vertical lines in the arrangement. + + OUTPUT: + + A dictionary which associates the index of a braid + to the index of the vertical line associated to the braid. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x, y + x - 1, x) + sage: A.vertical_lines_in_braid_monodromy() + {1: 2} + sage: A.braid_monodromy(vertical=True) + [s1*s0*s1*s0^-1*s1^-1*s0, s0^-1*s1*s0*s1^-1*s0, s0^-1*s1^2*s0] + """ + if not self._vertical_lines_in_braid_mon: + self.braid_monodromy(vertical=True) + return self._vertical_lines_in_braid_mon + + +class ProjectivePlaneCurveArrangementElement(PlaneCurveArrangementElement): + """ + An ordered projective plane curve arrangement. + """ + def __init__(self, parent, curves, check=True): + """ + Construct an ordered projective plane curve arrangement. + + INPUT: + + - ``parent`` -- the parent :class:`ProjectivePlaneCurveArrangements` + + - ``curves`` -- a tuple of curves + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: elt = H(x, y, z); elt + Arrangement (x, y, z) in Projective Space of dimension 2 over Rational Field + sage: TestSuite(elt).run() + """ + Element.__init__(self, parent) + self._curves = tuple(curves) + if check: + if not all(isinstance(h, ProjectivePlaneCurve) for h in curves): + raise ValueError("not all elements are curves") + if not all(h.ambient_space() is self.parent().ambient_space() + for h in curves): + raise ValueError("not all curves are in the same ambient space") + self._fundamental_group_nonsimpl = None + self._fundamental_group_simpl = None + self._meridians_nonsimpl = None + self._meridians_simpl = None + + def fundamental_group(self, simplified=True): + r""" + Return the fundamental group of the complement of the union + of an arragnement of projective plane curves + in the projective plane. + + INPUT: + + - ``simplified`` -- boolean (default: True); set if the group + is simplified + + OUTPUT: + + A finitely presented group. + + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H(z).fundamental_group() + Finitely presented group < | > + sage: H(x*y).fundamental_group() + Finitely presented group < x | > + sage: A = H(y^2 + x*z, y + x - z, x) + sage: A.fundamental_group().sorted_presentation() + Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 > + sage: A.meridians() + {0: [x1], 1: [x0], 2: [x1^-1*x0^-1*x1^-1]} + sage: G = A.fundamental_group(simplified=False) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x0^-1, x3^-1*x2^-1*x3*x0*x1*x0^-1, + x3^-1*x1^-1*x3*x0*x1*x0^-1*x2^-1*x0^-1*(x2*x0)^2*x1^-1*x0^-1, + x3^-1*x0^-1*x3*x0*x1*x0^-1*x2^-1*x0*x2*x0*x1^-1*x0^-1, + x2^-1*x0^-1*x2*x0, x1^-1*x0^-1*x1*x0 > + sage: A.meridians(simplified=False) + {0: [x1, x2], 1: [x0], 2: [x3]} + sage: A = H(y^2 + x*z, z, x) + sage: A.fundamental_group() + Finitely presented group < x0, x1 | (x1*x0)^2*(x1^-1*x0^-1)^2 > + sage: A = H(y^2 + x*z, z*x, y) + sage: A.fundamental_group() + Finitely presented group + < x0, x1, x2 | x2*x0*x1*x0^-1*x2^-1*x1^-1, + x1*(x2*x0)^2*x2^-1*x1^-1*x0^-1*x2^-1*x0^-1 > + """ + if simplified: + computed = self._fundamental_group_simpl + else: + computed = self._fundamental_group_nonsimpl + if computed: + return computed + H = self.parent() + K = self.base_ring() + R = self.coordinate_ring() + x, y, z = R.gens() + if not K.is_subring(QQbar): + raise TypeError('the base field is not in QQbar') + C = self.reduce() + n = len(C) + infinity = Curve(z) + infinity_in_C = infinity in C + if infinity_in_C and n == 1: + G = FreeGroup(0) / [] + if simplified: + self._fundamental_group_simpl = G + self._meridians_simpl = {0: [G.one()]} + else: + self._fundamental_group_nonsimpl = G + self._meridians_nonsimpl = {0: [G.one()]} + return G + if infinity_in_C: + j = C.curves().index(infinity) + C = H(C.curves()[:j] + C.curves()[j + 1:]) + infinity_divides = False + for j, c in enumerate(C): + g = c.defining_polynomial() + infinity_divides = z.divides(g) + if infinity_divides: + h = R(g / z) + C = H(C.curves()[:j] + (h, ) + C.curves()[j + 1:]) + break + affine = AffinePlaneCurveArrangements(K, names=('u', 'v')) + u, v = affine.gens() + affines = [f.defining_polynomial().subs({x: u, y: v, z: 1}) for f in C] + changes = any(g.degree(v) < g.degree() > 1 for g in affines) + while changes: + affines = [f.subs({u: u + v}) for f in affines] + changes = any(g.degree(v) < g.degree() > 1 for g in affines) + C_affine = affine(affines) + proj = not (infinity_divides or infinity_in_C) + G = C_affine.fundamental_group(simplified=simplified, vertical=True, + projective=proj) + dic = C_affine.meridians(simplified=simplified, vertical=True) + if infinity_in_C: + dic1 = dict() + for k in range(j): + dic1[k] = dic[k] + dic1[j] = dic[n - 1] + for k in range(j + 1, n): + dic1[k] = dic[k - 1] + elif infinity_divides: + dic1 = {k: dic[k] for k in range(n)} + dic1[j] += dic[n] + else: + dic1 = dic + if simplified: + self._fundamental_group_simpl = G + self._meridians_simpl = dic1 + else: + self._fundamental_group_nonsimpl = G + self._meridians_nonsimpl = dic1 + return G + + def meridians(self, simplified=True): + r""" + Return the meridians of each irreducible component. + + OUTPUT: + + A dictionary which associates the index of each curve with + its meridians, including the line at infinity if it can be + computed + + .. NOTE:: + + This function requires the ``sirocco`` package to be installed and + :func:`ProjectivePlaneCurveArrangements.fundamental_group` + with the same options, where some examples are shown. + + EXAMPLES:: + + sage: # needs sirocco + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: A = H(y^2 + x*z, y + x - z, x) + sage: A.fundamental_group().sorted_presentation() + Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 > + sage: A.meridians() + {0: [x1], 1: [x0], 2: [x1^-1*x0^-1*x1^-1]} + sage: A = H(y^2 + x*z, z, x) + sage: A.fundamental_group() + Finitely presented group < x0, x1 | (x1*x0)^2*(x1^-1*x0^-1)^2 > + sage: A.meridians() + {0: [x0, x1*x0*x1^-1], 1: [x0^-1*x1^-1*x0^-1], 2: [x1]} + sage: A = H(y^2 + x*z, z*x, y) + sage: A.fundamental_group() + Finitely presented group < x0, x1, x2 | x2*x0*x1*x0^-1*x2^-1*x1^-1, + x1*(x2*x0)^2*x2^-1*x1^-1*x0^-1*x2^-1*x0^-1 > + sage: A.meridians() + {0: [x0, x2*x0*x2^-1], 1: [x2, x0^-1*x2^-1*x1^-1*x0^-1], 2: [x1]} + """ + if simplified: + computed = self._meridians_simpl + else: + computed = self._meridians_nonsimpl + if computed: + return dict(computed) + self._fundamental_group(simplified=simplified) + if simplified: + return dict(self._meridians_simpl) + else: + return dict(self._meridians_nonsimpl) + + +class PlaneCurveArrangements(UniqueRepresentation, Parent): + """ + Plane curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-1, y-1) + Arrangement (x, y^2, x - 1, y - 1) in Affine Space + of dimension 2 over Rational Field + """ + Element = PlaneCurveArrangementElement + + @staticmethod + def __classcall__(cls, base, names=()): + """ + Normalize the inputs to ensure a unique representation. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: K = AffinePlaneCurveArrangements(QQ, names=('x', 'y')) + sage: H is K + True + """ + names = normalize_names(len(names), names) + return super().__classcall__(cls, base, names) + + def __init__(self, base_ring, names=tuple()): + """ + Initialize ``self``. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: TestSuite(H).run() + """ + if base_ring not in _Fields: + raise ValueError('base ring must be a field') + super().__init__(base_ring, names=names, category=Sets()) + self._embedded = None + if len(names) == 2: + self._embedded = 'affine' + elif len(names) == 3: + self._embedded = 'projective' + + def coordinate_ring(self): + """ + Return the coordinate ring. + + OUTPUT: + + The coordinate ring of the curve arrangement. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.coordinate_ring() + Multivariate Polynomial Ring in x, y over Rational Field + """ + return PolynomialRing(self.base_ring(), self.variable_names()) + + def change_ring(self, base_ring): + """ + Return curve arrangements over a different base ring. + + INPUT: + + - ``base_ring`` -- a ring; the new base ring. + + OUTPUT: + + A new :class:`PlaneCurveArrangements` instance over the new + base ring + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.change_ring(RR).base_ring() + Real Field with 53 bits of precision + + TESTS:: + + sage: L.change_ring(QQ) is L + True + """ + return self.__reduce__()[1][0](base_ring, names=self.variable_names()) + + @abstract_method + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = PlaneCurveArrangements(QQ) + Traceback (most recent call last): + ... + NotImplementedError: + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Affine Space of dimension 2 over Rational Field + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Projective Space of dimension 2 over Rational Field + """ + + def _repr_(self): + """ + Return a string representation. + + OUTPUT: + + A string. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ); L + Curve arrangements in Affine Space of dimension 2 over Rational Field + """ + return 'Curve arrangements in {0}'.format(self.ambient_space()) + + def _element_constructor_(self, *args, **kwds): + """ + Construct an element of ``self``. + + INPUT: + + - ``*args`` -- positional arguments, each defining a curve + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: A = L(x, y); A + Arrangement (x, y) in Affine Space of dimension 2 over Rational Field + sage: L([x, y]) == A + True + sage: L(Curve(x), Curve(y)) == A + True + sage: L(y, x) == A + False + """ + if len(args) == 1: + if not isinstance(args[0], (tuple, list)): + arg = (args[0], ) + else: + arg = tuple(args[0]) + else: + arg = tuple(args) + ambient_space = self.ambient_space() + R = ambient_space.coordinate_ring() + curves = () + for h in arg: + try: + ambient = h.ambient_space() + if ambient == ambient_space: + curves += (h, ) + else: + raise TypeError('the curves do not have the same ambient space') + except AttributeError: + try: + h = R(h) + curves += (Curve(h), ) + except TypeError: + raise TypeError('elements are not curves') + return self.element_class(self, curves) + + def _an_element_(self): + """ + Return an element of ``self``. + + TESTS:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H._an_element_() + Arrangement (t) in Affine Space of dimension 2 over Rational Field + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H._an_element_() + Arrangement (t) in Projective Space of dimension 2 over Rational Field + """ + return self(self.gen(0)) + + @cached_method + def ngens(self): + """ + Return the number of variables, i.e. 2 or 3, kept for completness. + + OUTPUT: + + An integer, 2 or 3, depending if the arrangement is projective or affine. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ngens() + 2 + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ngens() + 3 + """ + return len(self.variable_names()) + + def gens(self): + """ + Return the coordinates. + + OUTPUT: + + A tuple of linear expressions, one for each linear variable. + + EXAMPLES:: + + sage: L = AffinePlaneCurveArrangements(QQ, ('x', 'y')) + sage: L.gens() + (x, y) + sage: L = ProjectivePlaneCurveArrangements(QQ, ('x', 'y', 'z')) + sage: L.gens() + (x, y, z) + """ + return self.ambient_space().gens() + + def gen(self, i): + """ + Return the `i`-th coordinate. + + INPUT: + + - ``i`` -- integer + + OUTPUT: + + A variable. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.gen(1) + y + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.gen(2) + z + """ + return self.gens()[i] + + +class AffinePlaneCurveArrangements(PlaneCurveArrangements): + """ + Affine curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = AffinePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-1, y-1) + Arrangement (x, y^2, x - 1, y - 1) in Affine Space + of dimension 2 over Rational Field + """ + Element = AffinePlaneCurveArrangementElement + + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = AffinePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Affine Space of dimension 2 over Rational Field + """ + return AffineSpace(self.base_ring(), 2, self._names) + + +class ProjectivePlaneCurveArrangements(PlaneCurveArrangements): + """ + Projective curve arrangements. + + INPUT: + + - ``base_ring`` -- ring; the base ring + + - ``names`` -- tuple of strings; the variable names + + EXAMPLES:: + + sage: H. = ProjectivePlaneCurveArrangements(QQ) + sage: H(x, y^2, x-z, y-z) + Arrangement (x, y^2, x - z, y - z) in Projective Space + of dimension 2 over Rational Field + """ + Element = ProjectivePlaneCurveArrangementElement + + def ambient_space(self): + """ + Return the ambient space. + + EXAMPLES:: + + sage: L. = ProjectivePlaneCurveArrangements(QQ) + sage: L.ambient_space() + Projective Space of dimension 2 over Rational Field + """ + return ProjectiveSpace(self.base_ring(), 2, self._names) diff --git a/src/sage/schemes/curves/projective_curve.py b/src/sage/schemes/curves/projective_curve.py index e1f3403b834..1cc7fce0906 100644 --- a/src/sage/schemes/curves/projective_curve.py +++ b/src/sage/schemes/curves/projective_curve.py @@ -487,12 +487,12 @@ def projection(self, P=None, PS=None): # defining polynomials of this curve with the polynomials defining the inverse of the change of coordinates invcoords = [Q[i]*PP.gens()[j] + PP.gens()[i] for i in range(n + 1)] invcoords[j] = Q[j]*PP.gens()[j] - I = PP.coordinate_ring().ideal([f(invcoords) for f in self.defining_polynomials()]) - J = I.elimination_ideal(PP.gens()[j]) + id = PP.coordinate_ring().ideal([f(invcoords) for f in self.defining_polynomials()]) + J = id.elimination_ideal(PP.gens()[j]) K = Hom(PP.coordinate_ring(), PP2.coordinate_ring()) - l = list(PP2.gens()) - l.insert(j, 0) - phi = K(l) + ll = list(PP2.gens()) + ll.insert(j, 0) + phi = K(ll) G = [phi(f) for f in J.gens()] C = PP2.curve(G) return (psi, C) @@ -781,32 +781,35 @@ def plot(self, *args, **kwds): The other affine patches of the same curve:: - sage: C.plot(patch=0) # needs sage.plot + sage: # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive An elliptic curve:: + sage: # needs sage.plot sage: E = EllipticCurve('101a') sage: C = Curve(E) - sage: C.plot() # needs sage.plot + sage: C.plot() Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=0) # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive A hyperelliptic curve:: + sage: # needs sage.plot sage: P. = QQ[] sage: f = 4*x^5 - 30*x^3 + 45*x - 22 sage: C = HyperellipticCurve(f) - sage: C.plot() # needs sage.plot + sage: C.plot() Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=0) # needs sage.plot + sage: C.plot(patch=0) Graphics object consisting of 1 graphics primitive - sage: C.plot(patch=1) # needs sage.plot + sage: C.plot(patch=1) Graphics object consisting of 1 graphics primitive """ # if user has not specified a favorite affine patch, take the @@ -862,16 +865,17 @@ def is_singular(self, P=None): Over `\CC`:: + sage: # needs sage.rings.function_field sage: F = CC sage: P2. = ProjectiveSpace(F, 2) sage: C = Curve(X) - sage: C.is_singular() # needs sage.rings.function_field + sage: C.is_singular() False sage: D = Curve(Y^2*Z - X^3) - sage: D.is_singular() # needs sage.rings.function_field + sage: D.is_singular() True sage: E = Curve(Y^2*Z - X^3 + Z^3) - sage: E.is_singular() # needs sage.rings.function_field + sage: E.is_singular() False Showing that :issue:`12187` is fixed:: @@ -883,10 +887,11 @@ def is_singular(self, P=None): :: + sage: # needs sage.fings.function_field sage: P. = ProjectiveSpace(CC, 2) sage: C = Curve([y^4 - x^3*z], P) sage: Q = P([0,0,1]) - sage: C.is_singular() # needs sage.rings.function_field + sage: C.is_singular() True """ if P is None: @@ -1277,11 +1282,11 @@ def excellent_position(self, Q): if j == 0: div_pow = min(e[1] for e in npoly.exponents()) npoly = PP.coordinate_ring()({(v0, v1 - div_pow, v2): g - for (v0, v1, v2), g in npoly.dict().items()}) + for (v0, v1, v2), g in npoly.dict().items()}) else: div_pow = min(e[0] for e in npoly.exponents()) npoly = PP.coordinate_ring()({(v0 - div_pow, v1, v2): g - for (v0, v1, v2), g in npoly.dict().items()}) + for (v0, v1, v2), g in npoly.dict().items()}) # check the degree again if npoly.degree() != d - r: need_continue = True @@ -1660,9 +1665,9 @@ def is_complete_intersection(self): False """ singular.lib("sing.lib") - I = singular.simplify(self.defining_ideal(), 10) - L = singular.is_ci(I).sage() - return len(self.ambient_space().gens()) - len(I.sage().gens()) == L[-1] + id = singular.simplify(self.defining_ideal(), 10) + L = singular.is_ci(id).sage() + return len(self.ambient_space().gens()) - len(id.sage().gens()) == L[-1] def tangent_line(self, p): """ @@ -1742,12 +1747,18 @@ def fundamental_group(self): The curve must be defined over the rationals or a number field with an embedding over `\QQbar`. + .. NOTE:: + + This functionality requires the ``sirocco`` package to be installed. + EXAMPLES:: sage: P. = ProjectiveSpace(QQ, 2) sage: C = P.curve(x^2*z - y^3) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | x0^3 > + sage: P.curve(z*(x^2*z - y^3)).fundamental_group() # needs sirocco + Finitely presented group < x0, x1 | x1*x0*x1*x0^-1*x1^-1*x0^-1 > In the case of number fields, they need to have an embedding into the algebraic field:: @@ -1761,13 +1772,9 @@ def fundamental_group(self): sage: F.inject_variables() Defining a sage: C = P.curve(x^2 + a * y^2) - sage: C.fundamental_group() # optional - sirocco + sage: C.fundamental_group() # needs sirocco Finitely presented group < x0 | > - .. WARNING:: - - This functionality requires the ``sirocco`` package to be installed. - TESTS:: sage: F. = FreeGroup() @@ -1778,11 +1785,13 @@ def fundamental_group(self): sage: C = P.curve(z^2*y^3 - z*(33*x*z+2*x^2+8*z^2)*y^2 ....: + (21*z^2+21*x*z-x^2)*(z^2+11*x*z-x^2)*y ....: + (x-18*z)*(z^2+11*x*z-x^2)^2) - sage: G0 = C.fundamental_group() # optional - sirocco - sage: G.is_isomorphic(G0) # optional - sirocco + sage: G0 = C.fundamental_group() # needs sirocco + sage: G.is_isomorphic(G0) # needs sirocco #I Forcing finiteness test True - + sage: C = P.curve(z) + sage: C.fundamental_group() # needs sirocco + Finitely presented group < | > """ from sage.schemes.curves.zariski_vankampen import fundamental_group F = self.base_ring() @@ -1790,7 +1799,11 @@ def fundamental_group(self): if QQbar.coerce_map_from(F) is None: raise NotImplementedError("the base field must have an embedding" " to the algebraic field") - f = self.affine_patch(2).defining_polynomial() + g = self.defining_polynomial() + ring = self.ambient_space().affine_patch(2).coordinate_ring() + if g.degree() == 1: + return fundamental_group(ring.one()) + f = ring(self.affine_patch(2).defining_polynomial()) if f.degree() == self.degree(): return fundamental_group(f, projective=True) else: # in this case, the line at infinity is part of the curve, so the complement lies in the affine patch diff --git a/src/sage/schemes/curves/zariski_vankampen.py b/src/sage/schemes/curves/zariski_vankampen.py index 0fc01b81347..c6b5ca66017 100644 --- a/src/sage/schemes/curves/zariski_vankampen.py +++ b/src/sage/schemes/curves/zariski_vankampen.py @@ -23,12 +23,12 @@ EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy sage: R. = QQ[] sage: f = y^3 + x^3 - 1 sage: braid_monodromy(f) - ([s1*s0, s1*s0, s1*s0], {0: 0, 1: 0, 2: 0}) + ([s1*s0, s1*s0, s1*s0], {0: 0, 1: 0, 2: 0}, {}, 3) sage: fundamental_group(f) Finitely presented group < x0 | > """ @@ -44,7 +44,7 @@ import itertools from copy import copy -from sage.combinat.combination import Combinations +from itertools import combinations from sage.combinat.permutation import Permutation from sage.functions.generalized import sign from sage.geometry.voronoi_diagram import VoronoiDiagram @@ -67,7 +67,7 @@ from sage.rings.qqbar import QQbar from sage.rings.rational_field import QQ from sage.rings.real_mpfr import RealField -# from sage.sets.set import Set +from .constructor import Curve roots_interval_cache = {} @@ -88,7 +88,7 @@ def braid_from_piecewise(strands): EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_from_piecewise sage: paths = [[(0, 0, 1), (0.2, -1, -0.5), (0.8, -1, 0), (1, 0, -1)], ....: [(0, -1, 0), (0.5, 0, -1), (1, 1, 0)], @@ -109,8 +109,8 @@ def braid_from_piecewise(strands): yauxi = val[indices[j]][2] aaux = val[indices[j] - 1][0] baux = val[indices[j]][0] - interpolar = xauxr + (yauxr - xauxr) * (i - aaux) / (baux - aaux) - interpolai = xauxi + (yauxi - xauxi) * (i - aaux) / (baux - aaux) + interpolar = xauxr + (yauxr - xauxr)*(i - aaux) / (baux - aaux) + interpolai = xauxi + (yauxi - xauxi)*(i - aaux) / (baux - aaux) totalpoints[j].append([interpolar, interpolai]) else: totalpoints[j].append([val[indices[j]][1], @@ -129,6 +129,7 @@ def sgn(x, y): if x > y: return -1 return 0 + for i in range(len(totalpoints[0]) - 1): l1 = [totalpoints[j][i] for j in range(len(L))] l2 = [totalpoints[j][i + 1] for j in range(len(L))] @@ -177,7 +178,7 @@ def discrim(pols) -> tuple: INPUT: - ``pols`` -- a list or tuple of polynomials in two variables with - coefficients in a number field with a fixed embedding in `\QQbar` + coefficients in a number field with a fixed embedding in `\QQbar`. OUTPUT: @@ -188,13 +189,13 @@ def discrim(pols) -> tuple: sage: from sage.schemes.curves.zariski_vankampen import discrim sage: R. = QQ[] sage: flist = (y^3 + x^3 - 1, 2 * x + y) - sage: discrim(flist) - (1, - -0.500000000000000? - 0.866025403784439?*I, - -0.500000000000000? + 0.866025403784439?*I, - -0.522757958574711?, - 0.2613789792873551? - 0.4527216721561923?*I, - 0.2613789792873551? + 0.4527216721561923?*I) + sage: sorted((discrim(flist))) + [-0.522757958574711?, + -0.500000000000000? - 0.866025403784439?*I, + -0.500000000000000? + 0.866025403784439?*I, + 0.2613789792873551? - 0.4527216721561923?*I, + 0.2613789792873551? + 0.4527216721561923?*I, + 1] """ x, y = pols[0].parent().gens() field = pols[0].base_ring() @@ -206,7 +207,8 @@ def discrim_pairs(f, g): return pol_ring(f.discriminant(y)) return pol_ring(f.resultant(g, y)) - pairs = [(f, None) for f in pols] + [tuple(t) for t in Combinations(pols, 2)] + pairs = [(f, None) for f in pols] + [tuple(t) for t + in combinations(pols, 2)] fdiscrim = discrim_pairs(pairs) rts = () poly = 1 @@ -226,7 +228,7 @@ def corrected_voronoi_diagram(points): INPUT: - - ``points`` -- a list of complex numbers + - ``points`` -- a tuple of complex numbers OUTPUT: @@ -266,7 +268,8 @@ def corrected_voronoi_diagram(points): V = VoronoiDiagram(configuration) valid = True for r in V.regions().items(): - if not r[1].rays() and not r[1].interior_contains(apprpoints[r[0].affine()]): + if (not r[1].rays() and + not r[1].interior_contains(apprpoints[r[0].affine()])): prec += 53 valid = False break @@ -284,12 +287,12 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): - ``circuit`` -- a circuit in the graph of a Voronoi Diagram, given by a list of edges - - ``convex`` -- boolean (default: `False`), if set to ``True`` a simpler + - ``convex`` -- boolean (default: ``False``); if set to ``True`` a simpler computation is made - ``precision`` -- bits of precision (default: 53) - - ``verbose`` -- boolean (default: ``False``) for testing purposes + - ``verbose`` -- boolean (default: ``False``); for testing purposes OUTPUT: @@ -353,7 +356,6 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): # return circuit return circuit_vertex elif pr < 0: - # return list(reversed([(c[1], c[0]) + c[2:] for c in circuit])) return tuple(reversed(circuit_vertex)) prec = precision while True: @@ -361,17 +363,15 @@ def orient_circuit(circuit, convex=False, precision=53, verbose=False): 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])) return tuple(reversed(circuit_vertex)) if totalangle > 0: - # return circuit return circuit_vertex prec *= 2 if verbose: print(prec) -def voronoi_cells(V): +def voronoi_cells(V, vertical_lines=frozenset()): r""" Compute the graph, the boundary graph, a base point, a positive orientation of the boundary graph, and the dual graph of a corrected Voronoi diagram. @@ -380,6 +380,9 @@ def voronoi_cells(V): - ``V`` -- a corrected Voronoi diagram + - ``vertical_lines`` -- frozenset (default: ``frozenset()``); indices of the + vertical lines + OUTPUT: - ``G`` -- the graph of the 1-skeleton of ``V`` @@ -389,13 +392,15 @@ def voronoi_cells(V): of ``E``) with identical first and last elements) - ``DG`` -- the dual graph of ``V``, where the vertices are labelled by the compact regions of ``V`` and the edges by their dual edges. + - ``vertical_regions`` -- dictionary for the regions associated + with vertical lines EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import corrected_voronoi_diagram, voronoi_cells sage: points = (2, I, 0.000001, 0, 0.000001*I) sage: V = corrected_voronoi_diagram(points) - sage: G, E, p, EC, DG = voronoi_cells(V) + sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((1,))) sage: Gv = G.vertices(sort=True) sage: Ge = G.edges(sort=True) sage: len(Gv), len(Ge) @@ -443,25 +448,38 @@ def voronoi_cells(V): (A vertex at (2000001/2000000, 500001/1000000), A vertex at (11/4, 4), None)) sage: edg[-1] in Ge True + sage: VR + {1: (A vertex at (-49000001/14000000, 1000001/2000000), + A vertex at (1000001/2000000, 1000001/2000000), + A vertex at (2000001/2000000, 500001/1000000), + A vertex at (11/4, 4), + A vertex at (-4, 4), + A vertex at (-49000001/14000000, 1000001/2000000))} """ - compact_regions = [_ for _ in V.regions().values() if _.is_compact()] - non_compact_regions = [_ for _ in V.regions().values() if not _.is_compact()] - G = Graph([u.vertices() for v in compact_regions for u in v.faces(1)], format='list_of_edges') - E = Graph([u.vertices() for v in non_compact_regions for u in v.faces(1) if u.is_compact()], format='list_of_edges') + regions = V.regions() + points = [p for p in V.regions().keys() if V.regions()[p].is_compact()] + compact_regions = [regions[p] for p in points] + vertical_regions = {} + non_compact_regions = [reg for reg in V.regions().values() + if not reg.is_compact()] + G = Graph([u.vertices() for v in compact_regions for u in v.faces(1)], + format='list_of_edges') + E = Graph([u.vertices() for v in non_compact_regions for u in v.faces(1) + if u.is_compact()], format='list_of_edges') p = next(E.vertex_iterator()) EC = orient_circuit(E.eulerian_circuit()) - # EC = [EC0[0][0]] + [e[1] for e in EC0] DG = Graph() for i, reg in enumerate(compact_regions): Greg0 = orient_circuit(reg.graph().eulerian_circuit(), convex=True) - # Greg = (Greg0[0][0],) + tuple(e[1] for e in Greg0) + if i in vertical_lines: + vertical_regions[i] = Greg0 DG.add_vertex((i, Greg0)) for e in G.edges(sort=True): a, b = e[:2] regs = [v for v in DG.vertices(sort=True) if a in v[1] and b in v[1]] if len(regs) == 2: DG.add_edge(regs[0], regs[1], e) - return (G, E, p, EC, DG) + return (G, E, p, EC, DG, vertical_regions) def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: @@ -492,7 +510,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import followstrand sage: R. = QQ[] sage: f = x^2 + y^3 @@ -548,7 +566,7 @@ def followstrand(f, factors, x0, x1, y0a, prec=53) -> list: ci = c.imag() coefsfactors += list(cr.endpoints()) coefsfactors += list(ci.endpoints()) - from sage.libs.sirocco import contpath, contpath_mp, contpath_comps, contpath_mp_comps + from sage.libs.sirocco import (contpath, contpath_mp, contpath_comps, contpath_mp_comps) try: if prec == 53: if factors: @@ -603,11 +621,11 @@ def fieldI(field): INPUT: - - ``field`` -- a number field with an embedding in ``QQbar``. + - ``field`` -- a number field with an embedding in `\QQbar`. OUTPUT: - The extension ``F`` of ``field`` containing ``I`` with an embedding in ``QQbar``. + The extension ``F`` of ``field`` containing ``I`` with an embedding in `\QQbar`. EXAMPLES:: @@ -616,9 +634,20 @@ def fieldI(field): sage: a0 = p.roots(QQbar, multiplicities=False)[0] sage: F0. = NumberField(p, embedding=a0) sage: fieldI(F0) - Number Field in b with defining polynomial + Number Field in prim with defining polynomial x^10 + 5*x^8 + 14*x^6 - 2*x^5 - 10*x^4 + 20*x^3 - 11*x^2 - 14*x + 10 - with b = 0.4863890359345430? + 1.000000000000000?*I + with prim = 0.4863890359345430? + 1.000000000000000?*I + sage: F0 = CyclotomicField(5) + sage: fieldI(F0) + Number Field in prim with defining polynomial + x^8 - 2*x^7 + 7*x^6 - 10*x^5 + 16*x^4 - 10*x^3 - 2*x^2 + 4*x + 1 + with prim = -0.3090169943749474? + 0.04894348370484643?*I + sage: fieldI(QuadraticField(3)) + Number Field in prim with defining polynomial x^4 - 4*x^2 + 16 + with prim = -1.732050807568878? + 1.000000000000000?*I + sage: fieldI(QuadraticField(-3)) + Number Field in prim with defining polynomial x^4 + 8*x^2 + 4 + with prim = 0.?e-18 - 0.732050807568878?*I If ``I`` is already in the field, the result is the field itself:: @@ -629,6 +658,8 @@ def fieldI(field): sage: F1 = fieldI(F0) sage: F0 == F1 True + sage: QuadraticField(-1) == fieldI(QuadraticField(-1)) + True """ I0 = QQbar.gen() if I0 in field: @@ -641,8 +672,8 @@ def fieldI(field): for h1 in qembd: b1 = h1(b0) b2 = h1(field_b(field_a.gen(0))) - b3 = field.gen(0) - F1 = NumberField(q, 'b', embedding=b1) + b3 = QQbar(field.gen(0)) + F1 = NumberField(q, 'prim', embedding=b1) if b3 in F1 and b2.imag() > 0: return F1 @@ -803,7 +834,7 @@ def braid_in_segment(glist, x0, x1, precision={}): - ``glist`` -- a tuple of polynomials in two variables - ``x0`` -- a Gauss rational - ``x1`` -- a Gauss rational - - ``precision`` -- a dictionary (default: `dict()`) which assigns a number + - ``precision`` -- a dictionary (default: `{}`) which assigns a number precision bits to each element of ``glist`` OUTPUT: @@ -812,7 +843,6 @@ def braid_in_segment(glist, x0, x1, precision={}): EXAMPLES:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_in_segment, fieldI sage: R. = QQ[] sage: K = fieldI(QQ) @@ -820,7 +850,7 @@ def braid_in_segment(glist, x0, x1, precision={}): sage: f = f.change_ring(K) sage: x0 = 1 sage: x1 = 1 + I / 2 - sage: braid_in_segment(tuple(_[0] for _ in f.factor()), x0, x1) + sage: braid_in_segment(tuple(_[0] for _ in f.factor()), x0, x1) # needs sirocco s1 TESTS: @@ -844,43 +874,44 @@ def braid_in_segment(glist, x0, x1, precision={}): sage: p2a = CC(p2) sage: p2b = QQ(p2a.real()) + I*QQ(p2a.imag()) sage: glist = tuple([_[0] for _ in g.factor()]) - sage: B = braid_in_segment(glist, p1b, p2b); B # optional - sirocco + sage: B = braid_in_segment(glist, p1b, p2b); B # needs sirocco s5*s3^-1 """ precision1 = precision.copy() g = prod(glist) F1 = g.base_ring() x, y = g.parent().gens() - X0 = F1(x0) - X1 = F1(x1) intervals = {} - if not precision1: # new - precision1 = {f: 53 for f in glist} # new + if not precision1: + precision1 = {f: 53 for f in glist} y0s = [] for f in glist: if f.variables() == (y,): f0 = F1[y](f) else: - f0 = F1[y](f.subs({x: X0})) + f0 = F1[y](f.subs({x: F1(x0)})) y0sf = f0.roots(QQbar, multiplicities=False) y0s += list(y0sf) while True: CIFp = ComplexIntervalField(precision1[f]) intervals[f] = [r.interval(CIFp) for r in y0sf] - if not any(a.overlaps(b) for a, b in itertools.combinations(intervals[f], 2)): + if not any(a.overlaps(b) for a, b in + itertools.combinations(intervals[f], 2)): break precision1[f] *= 2 strands = [] for f in glist: for i in intervals[f]: - aux = followstrand(f, [p for p in glist if p != f], x0, x1, i.center(), precision1[f]) + aux = followstrand(f, [p for p in glist if p != f], + x0, x1, i.center(), precision1[f]) strands.append(aux) - complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b] for b in strands] + complexstrands = [[(QQ(a[0]), QQ(a[1]), QQ(a[2])) for a in b] + for b in strands] centralbraid = braid_from_piecewise(complexstrands) initialstrands = [] finalstrands = [] - initialintervals = roots_interval_cached(g, X0) - finalintervals = roots_interval_cached(g, X1) + initialintervals = roots_interval_cached(g, x0) + finalintervals = roots_interval_cached(g, x1) I1 = QQbar.gen() for cs in complexstrands: ip = cs[0][1] + I1 * cs[0][2] @@ -888,27 +919,29 @@ def braid_in_segment(glist, x0, x1, precision={}): matched = 0 for center, interval in initialintervals.items(): if ip in interval: - initialstrands.append([(0, center.real(), center.imag()), (1, cs[0][1], cs[0][2])]) + initialstrands.append([(0, center.real(), center.imag()), + (1, cs[0][1], cs[0][2])]) matched += 1 if matched != 1: - precision1 = {f: precision1[f] * 2 for f in glist} # new - return braid_in_segment(glist, x0, x1, precision=precision1) # new + precision1 = {f: precision1[f] * 2 for f in glist} + return braid_in_segment(glist, x0, x1, precision=precision1) matched = 0 for center, interval in finalintervals.items(): if fp in interval: - finalstrands.append([(0, cs[-1][1], cs[-1][2]), (1, center.real(), center.imag())]) + finalstrands.append([(0, cs[-1][1], cs[-1][2]), + (1, center.real(), center.imag())]) matched += 1 if matched != 1: - precision1 = {f: precision1[f] * 2 for f in glist} # new - return braid_in_segment(glist, x0, x1, precision=precision1) # new + precision1 = {f: precision1[f] * 2 for f in glist} + return braid_in_segment(glist, x0, x1, precision=precision1) initialbraid = braid_from_piecewise(initialstrands) finalbraid = braid_from_piecewise(finalstrands) return initialbraid * centralbraid * finalbraid -def geometric_basis(G, E, EC0, p, dual_graph) -> list: +def geometric_basis(G, E, EC0, p, dual_graph, vertical_regions={}) -> list: r""" Return a geometric basis, based on a vertex. @@ -927,74 +960,76 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: ``E`` is the boundary of the non-bounded component of the complement. The edges are labelled as the dual edges and the vertices are labelled by a tuple whose first element is the an integer for the position and the - second one is the cyclic ordered list of vertices in the region. + second one is the cyclic ordered list of vertices in the region + + - ``vertical_regions`` -- dictionary (default: `{}`); its keys are + the vertices of ``dual_graph`` to fix regions associated with + vertical lines - 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``. + OUTPUT: A geometric basis and a dictionary. + + The geometric basis is formed by a list of sequences of paths. Each path is a + ist 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``. + + The dictionary associates to each vertical line the index of the generator + of the geometric basis associated to it. EXAMPLES:: - sage: from sage.schemes.curves.zariski_vankampen import geometric_basis, voronoi_cells - sage: points = [(-3,0),(3,0),(0,3),(0,-3)]+ [(0,0),(0,-1),(0,1),(1,0),(-1,0)] - sage: V = VoronoiDiagram(points) - sage: G, E, p, EC, DG = voronoi_cells(V) - sage: geometric_basis(G, E, EC, p, DG) - [[A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (2, 2), - A vertex at (1/2, 1/2), - A vertex at (1/2, -1/2), - A vertex at (2, -2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (1/2, -1/2), - A vertex at (1/2, 1/2), - A vertex at (-1/2, 1/2), - A vertex at (-1/2, -1/2), - A vertex at (1/2, -1/2), - A vertex at (2, -2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (2, -2), - A vertex at (1/2, -1/2), - A vertex at (-1/2, -1/2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (-1/2, -1/2), - A vertex at (-1/2, 1/2), - A vertex at (1/2, 1/2), - A vertex at (2, 2), - A vertex at (-2, 2), - A vertex at (-1/2, 1/2), - A vertex at (-1/2, -1/2), - A vertex at (-2, -2)], - [A vertex at (-2, -2), - A vertex at (-1/2, -1/2), - A vertex at (-1/2, 1/2), - A vertex at (-2, 2), - A vertex at (-2, -2)]] + sage: from sage.schemes.curves.zariski_vankampen import geometric_basis, corrected_voronoi_diagram, voronoi_cells + sage: points = (0, -1, I, 1, -I) + sage: V = corrected_voronoi_diagram(points) + sage: G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=frozenset((0 .. 4))) + sage: gb, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR) + sage: gb + [[A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2), + A vertex at (-1/2, 1/2), A vertex at (-1/2, -1/2), A vertex at (1/2, -1/2), + A vertex at (1/2, 1/2), A vertex at (-1/2, 1/2), A vertex at (-5/2, 5/2), + A vertex at (5/2, 5/2), A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (-5/2, 5/2), + A vertex at (-1/2, 1/2), A vertex at (1/2, 1/2), A vertex at (5/2, 5/2), + A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (5/2, 5/2), A vertex at (1/2, 1/2), + A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)], [A vertex at (5/2, -5/2), + A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), A vertex at (-1/2, 1/2), + A vertex at (-5/2, 5/2), A vertex at (-5/2, -5/2), A vertex at (-1/2, -1/2), + A vertex at (1/2, -1/2), A vertex at (5/2, -5/2)], + [A vertex at (5/2, -5/2), A vertex at (1/2, -1/2), A vertex at (-1/2, -1/2), + A vertex at (-5/2, -5/2), A vertex at (5/2, -5/2)]] + sage: vd + {0: 0, 1: 3, 2: 1, 3: 2, 4: 4} """ i = EC0.index(p) - EC = EC0[i:-1] + EC0[:i + 1] # A counterclockwise eulerian circuit on the boundary, starting and ending at p + EC = EC0[i:-1] + EC0[:i + 1] + # A counterclockwise eulerian circuit on the boundary, + # starting and ending at p if G.size() == E.size(): if E.is_cycle(): - return [EC] + j = next(dual_graph.vertex_iterator())[0] + if j in vertical_regions: + vd = {j: 0} + else: + vd = {} + return [EC], vd edges_E = E.edges(sort=True) InternalEdges = [e for e in G.edges(sort=True) if e not in edges_E] InternalVertices = [v for e in InternalEdges for v in e[:2]] Internal = G.subgraph(vertices=InternalVertices, edges=InternalEdges) for i, ECi in enumerate(EC): # q and r are the points we will cut through if ECi in Internal: - EI = [v for v in E if v in Internal.connected_component_containing_vertex(ECi, sort=True) and v != ECi] + EI = [v for v in E if v in + Internal.connected_component_containing_vertex(ECi, sort=True) + and v != ECi] if EI: q = ECi connecting_path = list(EC[:i]) break if EC[-i] in Internal: - EI = [v for v in E if v in Internal.connected_component_containing_vertex(EC[-i], sort=True) and v != EC[-i]] + EI = [v for v in E if v in + Internal.connected_component_containing_vertex(EC[-i], sort=True) + and v != EC[-i]] if EI: q = EC[-i] connecting_path = list(reversed(EC[-i:])) @@ -1003,7 +1038,6 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: E_dist_q = E.shortest_path_lengths(q) I_dist_q = Internal.shortest_path_lengths(q) distancequotients = [(E_dist_q[v]**2 / I_dist_q[v], v) for v in EI] - # distancequotients = [(E.distance(q, v)**2 / Internal.distance(q, v), v) for v in EI] r = max(distancequotients)[1] cutpath = Internal.shortest_path(q, r) for i, v in enumerate(cutpath): @@ -1037,7 +1071,8 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: E1.add_edge(cutpath[i], cutpath[i + 1], None) E2.add_edge(cutpath[i], cutpath[i + 1], None) Gd = copy(dual_graph) - to_delete = [e for e in Gd.edges(sort=True) if e[2][0] in cutpath and e[2][1] in cutpath] + to_delete = [e for e in Gd.edges(sort=True) if e[2][0] in cutpath and + e[2][1] in cutpath] Gd.delete_edges(to_delete) Gd1, Gd2 = Gd.connected_components_subgraphs() edges_2 = [] @@ -1045,16 +1080,20 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: for reg in Gd2.vertices(sort=True): vertices_2 += reg[1][:-1] reg_circuit = reg[1] - edges_2 += [(v1, reg_circuit[i + 1]) for i, v1 in enumerate(reg_circuit[:-1])] - edges_2 += [(v1, reg_circuit[i - 1]) for i, v1 in enumerate(reg_circuit[1:])] + edges_2 += [(v1, reg_circuit[i + 1]) + for i, v1 in enumerate(reg_circuit[:-1])] + edges_2 += [(v1, reg_circuit[i - 1]) + for i, v1 in enumerate(reg_circuit[1:])] G2 = G.subgraph(vertices=vertices_2, edges=edges_2) edges_1 = [] vertices_1 = [] for reg in Gd1.vertices(sort=True): vertices_1 += reg[1] reg_circuit = reg[1] + (reg[1][0],) - edges_1 += [(v1, reg_circuit[i + 1]) for i, v1 in enumerate(reg_circuit[:-1])] - edges_1 += [(v1, reg_circuit[i - 1]) for i, v1 in enumerate(reg_circuit[1:])] + edges_1 += [(v1, reg_circuit[i + 1]) + for i, v1 in enumerate(reg_circuit[:-1])] + edges_1 += [(v1, reg_circuit[i - 1]) + for i, v1 in enumerate(reg_circuit[1:])] G1 = G.subgraph(vertices=vertices_1, edges=edges_1) if EC[qi + 1] in G2: G1, G2 = G2, G1 @@ -1067,9 +1106,13 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: EC1 = list(EC[qi:] + EC[1:ri]) + list(reversed(cutpath)) EC2 = cutpath + list(EC[ri + 1:qi + 1]) - gb1 = geometric_basis(G1, E1, EC1, q, Gd1) - gb2 = geometric_basis(G2, E2, EC2, q, Gd2) + gb1, vd1 = geometric_basis(G1, E1, EC1, q, Gd1, vertical_regions=vertical_regions) + gb2, vd2 = geometric_basis(G2, E2, EC2, q, Gd2, vertical_regions=vertical_regions) + vd = {j: vd1[j] for j in vd1} + m = len(gb1) + for j in vd2.keys(): + vd[j] = vd2[j] + m reverse_connecting = list(reversed(connecting_path)) resul = [connecting_path + path + reverse_connecting for path in gb1 + gb2] @@ -1083,10 +1126,52 @@ def geometric_basis(G, E, EC0, p, dual_graph) -> list: i -= 1 else: i += 1 - return resul + return (resul, vd) -def strand_components(f, flist, p1): +def vertical_lines_in_braidmon(pols) -> list: + r""" + Return the vertical lines in ``pols``, unless + one of the other components has a vertical asymptote. + + INPUT: + + - ``pols`` -- a list of polynomials with two variables whose + product equals ``f`` + + OUTPUT: + + A list with the indices of the vertical lines in ``flist`` if there is + no other componnet with vertical asymptote; otherwise it returns an empty + list. + + EXAMPLES:: + + sage: from sage.schemes.curves.zariski_vankampen import vertical_lines_in_braidmon + sage: R. = QQ[] + sage: flist = [x^2 - y^3, x, x + 3 * y - 5, 1 - x] + sage: vertical_lines_in_braidmon(flist) + [1, 3] + sage: flist += [x * y - 1] + sage: vertical_lines_in_braidmon(flist) + [] + sage: vertical_lines_in_braidmon([]) + [] + """ + if not pols: + return [] + res = [] + for j, f in enumerate(pols): + C = Curve(f) + vertical_asymptote = C.has_vertical_asymptote() + if vertical_asymptote: + return [] + if C.is_vertical_line(): + res.append(j) + return res + + +def strand_components(f, pols, p1): r""" Compute only the assignment from strands to elements of ``flist``. @@ -1095,7 +1180,7 @@ def strand_components(f, flist, p1): - ``f`` -- a reduced polynomial with two variables, over a number field with an embedding in the complex numbers - - ``flist`` -- a list of polynomials with two variables whose + - ``pols`` -- a list of polynomials with two variables whose product equals ``f`` - ``p1`` -- a Gauss rational @@ -1103,13 +1188,13 @@ def strand_components(f, flist, p1): OUTPUT: - A list and a dictionary. The first one is an ordered list of pairs - consisting of ``(z,i)`` where ``z`` is a root of ``f(p_1,y)`` and `i` is the position - of the polynomial in the list whose root is ``z``. The second one attaches - a number `i` (strand) to a number `j` (a polynomial in the list). + consisting of ``(z,i)`` where ``z`` is a root of ``f(p_1,y)`` + and `i` is the position of the polynomial in the list whose root + is ``z``. The second one attaches a number `i` (strand) to a + number `j` (a polynomial in the list). EXAMPLES:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import strand_components sage: R. = QQ[] sage: flist = [x^2 - y^3, x + 3 * y - 5] @@ -1119,20 +1204,20 @@ def strand_components(f, flist, p1): (1, 0), (1.333333333333334?, 1)], {0: 0, 1: 0, 2: 0, 3: 1}) """ x, y = f.parent().gens() - F = flist[0].base_ring() + F = pols[0].base_ring() strands = {} roots_base = [] - for i, h in enumerate(flist): + for i, h in enumerate(pols): h0 = h.subs({x: p1}) h1 = F[y](h0) rt = h1.roots(QQbar, multiplicities=False) roots_base += [(r, i) for r in rt] roots_base.sort() - strands = {i: par[1] for i, par in enumerate(roots_base)} # quitar +1 despues de revision + strands = {i: par[1] for i, par in enumerate(roots_base)} return (roots_base, strands) -def braid_monodromy(f, arrangement=()): +def braid_monodromy(f, arrangement=(), vertical=False): r""" Compute the braid monodromy of a projection of the curve defined by a polynomial. @@ -1142,31 +1227,45 @@ def braid_monodromy(f, arrangement=()): - ``f`` -- a polynomial with two variables, over a number field with an embedding in the complex numbers - - ``arrangement`` -- an optional tuple of polynomials whose product - equals ``f``. + - ``arrangement`` -- tuple (default: ``()``); an optional tuple + of polynomials whose product equals ``f`` + + - ``vertical`` -- boolean (default: ``False`); if set to ``True``, + ``arrangements`` contains more than one polynomial, some of them + are of degree `1` in `x` and degree `0` in `y`, and none of + the other components have vertical asymptotes, then these + components are marked as *vertical* and not used for the computation + of the braid monodromy. The other ones are marked as *horizontal*. If + a vertical component does not pass through a singular points of the + projection of the horizontal components a trivial braid is added + to the list. OUTPUT: - A list of braids and a dictionary. - The braids correspond to paths based in the same point; - each of these paths is the conjugated of a loop around one of the points - in the discriminant of the projection of ``f``. The dictionary assigns each - strand to the index of the corresponding factor in ``arrangement``. + - A list of braids, images by the braid monodromy of a geometric + basis of the complement of the discriminant of `f` in `\CC`. + + - A dictionary: ``i``, index of a strand is sent to the index of + the corresponding factor in ``arrangement``. + + - Another dictionary ``dv``, only relevant if ``vertical`` is ``True``. + If ``j`` is the index + of a braid corresponding to a vertical line with index ``i`` + in ``arrangement``, then ``dv[j] = i``. + + - A non-negative integer: the number of strands of the braids, + only necessary if the list of braids is empty. .. NOTE:: The projection over the `x` axis is used if there are no vertical asymptotes. Otherwise, a linear change of variables is done to fall - into the previous case. - - .. TODO:: - - Create a class ``arrangements_of_curves`` with a ``braid_monodromy`` - method; it can be also a method for affine line arrangements. + into the previous case except if the only vertical asymptotes are lines + and ``vertical=True``. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy sage: R. = QQ[] sage: f = (x^2 - y^3) * (x + 3*y - 5) @@ -1174,7 +1273,7 @@ def braid_monodromy(f, arrangement=()): ([s1*s0*(s1*s2)^2*s0*s2^2*s0^-1*(s2^-1*s1^-1)^2*s0^-1*s1^-1, 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], {0: 0, 1: 0, 2: 0, 3: 0}) + s1*s0*s2*s0^-1*s2*s1^-1], {0: 0, 1: 0, 2: 0, 3: 0}, {}, 4) sage: flist = (x^2 - y^3, x + 3*y - 5) sage: bm1 = braid_monodromy(f, arrangement=flist) sage: bm1[0] == bm[0] @@ -1182,42 +1281,86 @@ def braid_monodromy(f, arrangement=()): sage: bm1[1] {0: 0, 1: 1, 2: 0, 3: 0} sage: braid_monodromy(R(1)) - ([], {}) + ([], {}, {}, 0) sage: braid_monodromy(x*y^2 - 1) - ([s0*s1*s0^-1*s1*s0*s1^-1*s0^-1, s0*s1*s0^-1, s0], {0: 0, 1: 0, 2: 0}) + ([s0*s1*s0^-1*s1*s0*s1^-1*s0^-1, s0*s1*s0^-1, s0], {0: 0, 1: 0, 2: 0}, {}, 3) + sage: L = [x, y, x - 1, x -y] + sage: braid_monodromy(prod(L), arrangement=L, vertical=True) + ([s^2, 1], {0: 1, 1: 3}, {0: 0, 1: 2}, 2) """ global roots_interval_cache F = fieldI(f.base_ring()) I1 = F(QQbar.gen()) f = f.change_ring(F) - if arrangement == (): + if not arrangement: arrangement1 = (f,) else: - arrangement1 = tuple(_.change_ring(F) for _ in arrangement) + arrangement1 = tuple(g.change_ring(F) for g in arrangement) x, y = f.parent().gens() - glist = tuple(_[0] for f0 in arrangement1 for _ in f0.factor()) + if vertical: + indices_v = vertical_lines_in_braidmon(arrangement1) + else: + indices_v = [] + arrangement_h = tuple(f0 for j, f0 in enumerate(arrangement1) + if j not in indices_v) + arrangement_v = tuple(f0 for j, f0 in enumerate(arrangement1) + if j in indices_v) + glist = tuple(fc[0] for f0 in arrangement_h for fc in f0.factor()) g = f.parent()(prod(glist)) d = g.degree(y) - while not g.coefficient(y**d) in F: - g = g.subs({x: x + y}) - d = g.degree(y) - arrangement1 = tuple(f1.subs({x: x + y}) for f1 in arrangement1) - glist = tuple(f1.subs({x: x + y}) for f1 in glist) + if not arrangement_v: # change of coordinates only if indices_v is empty + while not g.coefficient(y**d) in F: + g = g.subs({x: x + y}) + d = g.degree(y) + arrangement_h = tuple(f1.subs({x: x + y}) for f1 in arrangement_h) + arrangement1 = arrangement_h + glist = tuple(f1.subs({x: x + y}) for f1 in glist) if d > 0: disc = discrim(glist) else: disc = [] + vertical_braid = {} + transversal = {} + vl = [] + for f0 in arrangement_v: + pt = [j for j, t in enumerate(disc) if f0.subs({x: t}) == 0] + if pt: + vertical_braid[f0] = (pt[0], arrangement1.index(f0)) + vl.append(pt[0]) + else: + transversal[f0] = arrangement1.index(f0) + vl.sort() + vl = frozenset(vl) if not disc: - result = [] + vertical_braids = {i: transversal[f0] + for i, f0 in enumerate(transversal)} + if d > 1: + result = [BraidGroup(d).one() for p in transversal] + else: + G = FreeGroup(0) / [] + result = [G.one() for p in transversal] p1 = F(0) - roots_base, strands = strand_components(g, arrangement1, p1) - return ([], strands) + if d > 0: + roots_base, strands = strand_components(g, arrangement_h, p1) + strands1 = {} + for j in range(d): + i = strands[j] + k = arrangement1.index(arrangement_h[i]) + strands1[j] = k + else: + strands1 = {} + return (result, strands1, vertical_braids, d) V = corrected_voronoi_diagram(tuple(disc)) - G, E, p, EC, DG = voronoi_cells(V) + G, E, p, EC, DG, VR = voronoi_cells(V, vertical_lines=vl) p0 = (p[0], p[1]) p1 = p0[0] + I1 * p0[1] - roots_base, strands = strand_components(g, arrangement1, p1) - geombasis = geometric_basis(G, E, EC, p, DG) + roots_base, strands = strand_components(g, arrangement_h, p1) + strands1 = {} + for j in range(d): + i = strands[j] + k = arrangement1.index(arrangement_h[i]) + strands1[j] = k + geombasis, vd = geometric_basis(G, E, EC, p, DG, vertical_regions=VR) segs = set() for p in geombasis: for s in zip(p[:-1], p[1:]): @@ -1253,7 +1396,18 @@ def braid_monodromy(f, arrangement=()): x1 = tuple(path[i + 1].vector()) braidpath = braidpath * segsbraids[(x0, x1)] result.append(braidpath) - return (result, strands) + vertical_braids = {} + r = len(result) + t = 0 + for f0 in arrangement_v: + if f0 in vertical_braid.keys(): + k, j = vertical_braid[f0] + vertical_braids[vd[k]] = j + else: + vertical_braids[r + t] = transversal[f0] + t += 1 + result.append(B.one()) + return (result, strands1, vertical_braids, d) def conjugate_positive_form(braid): @@ -1263,7 +1417,7 @@ def conjugate_positive_form(braid): INPUT: - - ``braid`` -- a braid ``\sigma``. + - ``braid`` -- a braid `\sigma` OUTPUT: @@ -1316,10 +1470,7 @@ def conjugate_positive_form(braid): A1 = rightnormalform(sg) par = A1[-1][0] % 2 A1 = [B(a) for a in A1[:-1]] - if not A1: - b = B.one() - else: - b = prod(A1) + b = prod(A1, B.one()) b1 = len(b.Tietze()) / (len(A1) + 1) if res is None or b1 < res[3]: res = [tau, A1, par, b1] @@ -1373,13 +1524,13 @@ def braid2rels(L): k = min(T1) - 1 B0 = BraidGroup(m) F0 = FreeGroup(m) - br0 = B0([_-k for _ in T]) + br0 = B0([j-k for j in T]) br0_left = leftnormalform(br0) q, r = ZZ(br0_left[0][0]).quo_rem(2) - br1 = B0.delta()**r * B0(prod(B0(_) for _ in br0_left[1:])) + br1 = B0.delta()**r * prod(map(B0, br0_left[1:]), B0.one()) cox = prod(F0.gens()) U0 = [cox**q * (f0 * br1) / cox**q / f0 for f0 in F0.gens()[:-1]] - U = [tuple(sign(k1) * (abs(k1) + k) for k1 in _.Tietze()) for _ in U0] + U = [tuple(sign(k1) * (abs(k1) + k) for k1 in br.Tietze()) for br in U0] pasos = [B.one()] + list(reversed(L1)) for C in pasos: U = [(F(a) * C.inverse()).Tietze() for a in U] @@ -1392,7 +1543,7 @@ def braid2rels(L): P.TzGoGo() P.TzGoGo() gb = wrap_FpGroup(P.FpGroupPresentation()) - U = [_.Tietze() for _ in gb.relations()] + U = [rel.Tietze() for rel in gb.relations()] return U @@ -1406,7 +1557,9 @@ def relation(x, b): return x * b / x -def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projective=False, puiseux=False, vertical=[]): +def fundamental_group_from_braid_mon(bm, degree=None, + simplified=True, projective=False, + puiseux=True, vertical=[]): r""" Return a presentation of the fundamental group computed from a braid monodromy. @@ -1426,16 +1579,15 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, - ``simplified`` is set to ``False``, and + - ``puiseux`` -- boolean (default: ``True``); if set to ``True`` a presentation of the fundamental group with the homotopy type of the complement of the affine curve will be computed, adding one relation if ``projective`` is set to ``True``. - ``vertical`` -- list of integers (default: ``[]``); the indices in - ``[1..r]`` of the braids that surround a vertical line + ``[0 .. r - 1]`` of the braids that surround a vertical line - If ``simplified` and ``projective``` are ``False`` and ``puiseux`` is + If ``projective``` is ``False`` and ``puiseux`` is ``True``, a Zariski-VanKampen presentation is returned. OUTPUT: @@ -1450,37 +1602,45 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv sage: bm = [s1*s2*s0*s1*s0^-1*s1^-1*s0^-1, ....: s0*s1^2*s0*s2*s1*(s0^-1*s1^-1)^2*s0^-1, ....: (s0*s1)^2] - sage: g = fundamental_group_from_braid_mon(bm, projective=True); g - Finitely presented group < x0, x1 | x1*x0^2*x1, x0^-1*x1^-1*x0^-1*x1*x0^-1*x1^-1 > - sage: print (g.order(), g.abelian_invariants()) + sage: g = fundamental_group_from_braid_mon(bm, projective=True); g # needs sirocco + Finitely presented group + < x1, x3 | x3^2*x1^2, x1^-1*x3^-1*x1*x3^-1*x1^-1*x3^-1 > + sage: print(g.order(), g.abelian_invariants()) # needs sirocco 12 (4,) sage: B2 = BraidGroup(2) sage: bm = [B2(3 * [1])] - sage: g = fundamental_group_from_braid_mon(bm, vertical=[1]); g - Finitely presented group < x0, x1, x2 | x2*x0*x1*x2^-1*x1^-1*x0^-1, - x2*x0*x1*x0*x1^-1*x0^-1*x2^-1*x1^-1 > - sage: fundamental_group_from_braid_mon([]) is None # optional - sirocco + sage: g = fundamental_group_from_braid_mon(bm, vertical=[0]); g # needs sirocco + Finitely presented group + < x0, x1, x2 | x2*x0*x1*x2^-1*x1^-1*x0^-1, + x2*x0*x1*x0*x1^-1*x0^-1*x2^-1*x1^-1 > + sage: fundamental_group_from_braid_mon([]) is None # needs sirocco True - sage: fundamental_group_from_braid_mon([], degree=2) # optional - sirocco + sage: fundamental_group_from_braid_mon([], degree=2) # needs sirocco Finitely presented group < x0, x1 | > + sage: fundamental_group_from_braid_mon([SymmetricGroup(1).one()]) # needs sirocco + Finitely presented group < x | > """ vertical0 = sorted(vertical) v = len(vertical0) if not bm: d = degree + elif bm[0].parent().order() == 1: + d = 1 else: d = bm[0].parent().strands() if d is None: return None F = FreeGroup(d) Fv = FreeGroup(d + v) - bmh = [br for j, br in enumerate(bm) if j + 1 not in vertical0] + if d == 0: + return Fv / [] + if d == 1: + return Fv / [(1, j, -1, -j) for j in range(2, d + v + 1)] + bmh = [br for j, br in enumerate(bm) if j not in vertical0] if not puiseux: relations_h = (relation([(x, b) for x in F.gens() for b in bmh])) rel_h = [r[1] for r in relations_h] - simplified0 = simplified else: - simplified0 = False conjugate_desc = conjugate_positive_form_p(bmh) trenzas_desc = [b1[-1] for b1 in conjugate_desc] trenzas_desc_1 = flatten(trenzas_desc, max_level=1) @@ -1490,7 +1650,7 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv rel_v = [] for j, k in enumerate(vertical0): l1 = d + j + 1 - br = bm[k - 1] + br = bm[k] for gen in F.gens(): j0 = gen.Tietze()[0] rl = (l1,) + (gen * br).Tietze() + (-l1, -j0) @@ -1499,12 +1659,12 @@ def fundamental_group_from_braid_mon(bm, degree=None, simplified=True, projectiv if projective: rel.append(prod(Fv.gens()).Tietze()) G = Fv / rel - if simplified0: + if simplified: return G.simplified() return G -def fundamental_group(f, simplified=True, projective=False, puiseux=False): +def fundamental_group(f, simplified=True, projective=False, puiseux=True): r""" Return a presentation of the fundamental group of the complement of the algebraic set defined by the polynomial ``f``. @@ -1522,12 +1682,14 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, + - ``puiseux`` -- boolean (default: ``True``); if set to ``True``, a presentation of the fundamental group with the homotopy type - of the complement of the affine curve is computed, ``simplified`` is - ignored. One relation is added if ``projective`` is set to ``True``. + of the complement of the affine curve is computed. If the Euler + characteristic does not match, the homotopy type is obtained + with a wedge of 2-spheres. One relation is added if ``projective`` + is set to ``True``. - If ``simplified` and ``projective``` are ``False`` and ``puiseux`` is + If ``projective``` is ``False`` and ``puiseux`` is ``True``, a Zariski-VanKampen presentation is returned. OUTPUT: @@ -1537,13 +1699,13 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group, braid_monodromy sage: R. = QQ[] sage: f = x^2 + y^3 sage: fundamental_group(f) - Finitely presented group < x1, x2 | x1*x2*x1^-1*x2^-1*x1^-1*x2 > - sage: fundamental_group(f, simplified=False).sorted_presentation() + Finitely presented group < x0, x1 | x0*x1^-1*x0^-1*x1^-1*x0*x1 > + sage: fundamental_group(f, simplified=False, puiseux=False).sorted_presentation() Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0*x1, x2^-1*x0*x1*x0^-1, x1^-1*x0^-1*x1^-1*x0*x1*x0 > @@ -1554,17 +1716,17 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): :: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: R. = QQ[] sage: f = y^3 + x^3 - sage: fundamental_group(f) - Finitely presented group < x0, x1, x2 | x0*x1*x2*x0^-1*x2^-1*x1^-1, x2*x0*x1*x2^-1*x1^-1*x0^-1 > + sage: fundamental_group(f).sorted_presentation() + Finitely presented group < x0, x1, x2 | x2^-1*x1^-1*x0^-1*x2*x0*x1, + x2^-1*x1^-1*x2*x0*x1*x0^-1 > It is also possible to have coefficients in a number field with a fixed embedding in `\QQbar`:: - sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: zeta = QQbar['x']('x^2 + x+ 1').roots(multiplicities=False)[0] sage: zeta @@ -1574,22 +1736,19 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): Defining zeta sage: R. = F[] sage: f = y^3 + x^3 + zeta * x + 1 - sage: fundamental_group(f) + sage: fundamental_group(f) # needs sirocco Finitely presented group < x0 | > - We compute the fundamental group of the complement of a quartic using the ``puiseux`` option:: + We compute the fundamental group of the complement of a + quartic using the ``puiseux`` option:: sage: # optional - sirocco sage: from sage.schemes.curves.zariski_vankampen import fundamental_group sage: R. = QQ[] sage: f = x^2 * y^2 + x^2 + y^2 - 2 * x * y * (x + y + 1) - sage: g = fundamental_group(f, puiseux=True); g.sorted_presentation() - Finitely presented group - < x0, x1, x2, x3 | x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2, - x3^-1*x2^-1*x1*x2, x2^-1*x1^-1*x0^-1*x1*x2*x1, x2^-1*x0 > - sage: g.simplified().sorted_presentation() + sage: g = fundamental_group(f); g.sorted_presentation() Finitely presented group < x0, x1 | x1^-2*x0^2, (x1^-1*x0)^3 > - sage: g = fundamental_group(f, puiseux=True, projective=True) + sage: g = fundamental_group(f, projective=True) sage: g.order(), g.abelian_invariants() (12, (4,)) sage: fundamental_group(y * (y - 1)) @@ -1610,10 +1769,15 @@ def fundamental_group(f, simplified=True, projective=False, puiseux=False): d = g.degree(y) else: d = bm[0].parent().strands() - return fundamental_group_from_braid_mon(bm, degree=d, simplified=simplified, projective=projective, puiseux=puiseux) + return fundamental_group_from_braid_mon(bm, degree=d, + simplified=simplified, + projective=projective, + puiseux=puiseux) -def fundamental_group_arrangement(flist, simplified=True, projective=False, puiseux=False): +def fundamental_group_arrangement(flist, simplified=True, projective=False, + puiseux=True, vertical=False, + braid_data=None): r""" Compute the fundamental group of the complement of a curve defined by a list of polynomials with the extra information @@ -1633,27 +1797,34 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed - - ``puiseux`` -- boolean (default: ``False``); if set to ``True``, - ``simplified`` is set to ``False``, and + - ``puiseux`` -- boolean (default: ``True``); if set to ``True`` a presentation of the fundamental group with the homotopy type of the complement of the affine curve will be computed, adding one relation if ``projective`` is set to ``True``. + - ``vertical`` -- boolean (default: ``False``); if set to ``True``, + whenever no curve has vertical asymptotes the computation of braid + monodromy is simpler if some lines are vertical + + - ``braid_data`` -- tuple (default: ``None``); if it is not the default + it is the output of ``fundamental_group_from_braid_mon`` previously + computed + OUTPUT: - A list of braids. The braids correspond to paths based in the same point; each of this paths is the conjugated of a loop around one of the points in the discriminant of the projection of ``f``. - - A dictionary attaching a tuple ``(i,)`` (generator) to a number ``j`` - (a polynomial in the list). If ``simplified`` is set to ``True``, - a longer key may appear for either the meridian of the line at infinity, - if ``projective`` is ``True``, or a simplified generator, - if ``projective`` is ``False`` + - A dictionary attaching to ``j`` a tuple a list of elements + of the group which are meridians of the curve in position ``j``. + If ``projective`` is ``False`` and the `y`-degree of the horizontal + components coincide with the total degree, another key is added + to give a meridian of the line at infinity. EXAMPLES:: - sage: # optional - sirocco + sage: # needs sirocco sage: from sage.schemes.curves.zariski_vankampen import braid_monodromy sage: from sage.schemes.curves.zariski_vankampen import fundamental_group_arrangement sage: R. = QQ[] @@ -1661,36 +1832,48 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis sage: g, dic = fundamental_group_arrangement(flist) sage: g.sorted_presentation() Finitely presented group - < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x2^-1*x0^-1*x2^-1*x0*x2*x0, x1^-1*x0^-1*x1*x0 > + < x0, x1, x2 | x2^-1*x1^-1*x2*x1, x2^-1*x0^-1*x2^-1*x0*x2*x0, + x1^-1*x0^-1*x1*x0 > sage: dic - {0: [x0, x2, x0], 1: [x1], 2: [x0^-1*x2^-1*x1^-1*x0^-1]} - sage: g, dic = fundamental_group_arrangement(flist, simplified=False) + {0: [x0, x2], 1: [x1], 2: [x0^-1*x2^-1*x1^-1*x0^-1]} + sage: g, dic = fundamental_group_arrangement(flist, simplified=False, puiseux=False) sage: g.sorted_presentation(), dic (Finitely presented group - < x0, x1, x2, x3 | 1, 1, 1, 1, 1, 1, 1, x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + < x0, x1, x2, x3 | 1, 1, 1, 1, 1, 1, 1, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x3*x2, x3^-1*x2^-1*x1^-1*x0^-1*x1*x2*x1^-1*x0*x1*x2, - x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, x3^-1*x1^-1*x0*x1, - x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, - x1^-1*x0^-1*x1*x0 >, + x3^-1*x2^-1*x1^-1*x2*x3*x2^-1*x1*x2, + x3^-1*x1^-1*x0*x1, + x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0, + x1^-1*x0^-1*x1*x0, x1^-1*x0^-1*x1*x0 >, {0: [x0, x2, x3], 1: [x1], 2: [x3^-1*x2^-1*x1^-1*x0^-1]}) sage: fundamental_group_arrangement(flist, projective=True) - (Finitely presented group < x | >, {0: [x0, x0, x0], 1: [x0^-3]}) + (Finitely presented group < x | >, {0: [x], 1: [x^-3]}) sage: fundamental_group_arrangement([]) (Finitely presented group < | >, {}) sage: g, dic = fundamental_group_arrangement([x * y]) sage: g.sorted_presentation(), dic (Finitely presented group < x0, x1 | x1^-1*x0^-1*x1*x0 >, - {0: [x0, x1], 1: [x1^-1*x0^-1]}) - sage: fundamental_group_arrangement([y + x^2], projective=True) - (Finitely presented group < x | x^2 >, {0: [x0, x0]}) - - .. TODO:: - - Create a class ``arrangements_of_curves`` with a ``fundamental_group`` - method it can be also a method for affine or projective line - arrangements, even for hyperplane arrangements defined over a number - subfield of ``QQbar`` after applying a generic line section. + {0: [x0, x1], 1: [x1^-1*x0^-1]}) + sage: fundamental_group_arrangement([y + x^2]) + (Finitely presented group < x | >, {0: [x]}) + sage: fundamental_group_arrangement([y^2 + x], projective=True) + (Finitely presented group < x | x^2 >, {0: [x]}) + sage: L = [x, y, x - 1, x -y] + sage: G, dic =fundamental_group_arrangement(L) + sage: G.sorted_presentation() + Finitely presented group + < x0, x1, x2, x3 | x3^-1*x2^-1*x3*x2, x3^-1*x1^-1*x0^-1*x1*x3*x0, + x3^-1*x1^-1*x3*x0*x1*x0^-1, x2^-1*x0^-1*x2*x0 > + sage: dic + {0: [x1], 1: [x3], 2: [x2], 3: [x0], 4: [x3^-1*x2^-1*x1^-1*x0^-1]} + sage: fundamental_group_arrangement(L, vertical=True) + (Finitely presented group + < x0, x1, x2, x3 | x3*x0*x3^-1*x0^-1, x3*x1*x3^-1*x1^-1, + x1*x2*x0*x2^-1*x1^-1*x0^-1, + x1*x2*x0*x1^-1*x0^-1*x2^-1 >, + {0: [x2], 1: [x0], 2: [x3], 3: [x1], 4: [x3^-1*x2^-1*x1^-1*x0^-1]}) """ if flist: f = prod(flist) @@ -1699,23 +1882,32 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis R = PolynomialRing(QQ, ('x', 'y')) f = R(1) x, y = R.gens() - F = R.base_ring() - flist1 = list(flist) - d = f.degree(y) - while not f.coefficient(y**d) in F: - flist1 = [g.subs({x: x + y}) for g in flist1] - f = prod(flist1) - d = f.degree(y) - if projective: - while f.degree(y) < f.degree(): - flist1 = [g.subs({x: x + y}) for g in flist] - f = prod(flist1) - if not flist1: + flist1 = tuple(flist) + if vertical and vertical_lines_in_braidmon(flist1): + infinity = all([Curve(g).is_vertical_line() or + g.degree(y) == g.degree() for g in flist1]) + else: + infinity = any([Curve(g).has_vertical_asymptote() or + Curve(g).is_vertical_line() for g in flist1]) + if not infinity: + infinity = all([g.degree(y) == g.degree() for g in flist1]) + if braid_data: + bm, dic, dv, d1 = braid_data + elif not flist: bm = [] dic = {} + dv = {j: j for j, f in flist1} + d1 = 0 else: - bm, dic = braid_monodromy(f, flist1) - g = fundamental_group_from_braid_mon(bm, degree=d, simplified=False, projective=projective, puiseux=puiseux) + bm, dic, dv, d1 = braid_monodromy(f, flist1, vertical=vertical) + vert_lines = list(dv) + vert_lines.sort() + for i, j in enumerate(vert_lines): + dic[d1 + i] = dv[j] + g = fundamental_group_from_braid_mon(bm, degree=d1, simplified=False, + projective=projective, + puiseux=puiseux, + vertical=vert_lines) if simplified: hom = g.simplification_isomorphism() else: @@ -1725,12 +1917,13 @@ def fundamental_group_arrangement(flist, simplified=True, projective=False, puis return (g1, {}) dic1 = {} for i in range(len(flist1)): - L = [j1 for j1 in dic.keys() if dic[j1] == i] + L = [j1 for j1 in dic if dic[j1] == i] dic1[i] = [hom(g.gen(j)) for j in L] - if not projective and f.degree(y) == f.degree(): - t = prod(hom(x) for x in g.gens()).inverse() + if not projective and infinity: + t = prod(hom(a) for a in g.gens()).inverse() dic1[len(flist1)] = [t] n = g1.ngens() - rels = [_.Tietze() for _ in g1.relations()] + rels = [rel.Tietze() for rel in g1.relations()] g1 = FreeGroup(n) / rels + dic1 = {i: list(set([g1(el.Tietze()) for el in dic1[i]])) for i in dic1} return (g1, dic1)