# GSoC project summary

GSoC 2016 has come to an end! It was a lot of fun working on this project and I feel that I learned a great deal since its onset. I’m very grateful to my project mentors Benjamin Hutz and Miguel Marco for all of their help and guidance during the program, and to Google and SageMath for providing this opportunity.

In this post I will summarize the project contributions and give some examples illustrating the new functionality that has been added to Sage. Contributions were made through the ticket system of the SageMath Trac website. The tickets that were opened for this project are #20676, #20697, #20698, #20774, #20790, #20811, #20839, #20848, #20895, #20930, #21085, #21137, #21167, and #21168. Of these, #20790, #20895, and #21085 still need to be merged, though are close to being finished.

Overall, the main goals of the project were met; the following breaks them down and describes the functionality that was implemented for them. Most of the details of the first few topics were also covered in the midterm summary blog post.

Improve class structure for algebraic curves (tickets #20697, #20698):

The first goal of the project was to create a more robust foundation for working with general algebraic curves in Sage at the class level. One improvement in this area was to rename the existing curve classes so that it’s clearer which classes represent plane algebraic curves, and which classes are more general and can represent space curves. This along with implementing a more straightforward inheritance structure between the curve classes was done in ticket #20697. Now to work with a general curve in Sage, a user works with an object of one of the classes AffineCurve, AffinePlaneCurve, AffinePlaneCurve_finite_field, AffinePlaneCurve_prime_finite_field (with the classes on the right inheriting from those on the left), or an object of the similarly-named projective curve classes (e.g. ProjectivePlaneCurve).

The initialization function Curve for objects of these classes was also improved in #20698 so that users can specify the ambient spaces the curves live in.

sage: P.<x,y,z,w,t,u> = ProjectiveSpace(QQ, 5)
sage: C = Curve([x^4*w - y^5, 7/2*t^2 - 3/4*u*y + z^2, x*y*z*u - w^4, y*w - u*t], P); C
Projective Curve over Rational Field defined by -y^5 + x^4*w, z^2 +
7/2*t^2 - 3/4*y*u, -w^4 + x*y*z*u, y*w - t*u
sage: Q.<a,b,c> = ProjectiveSpace(GF(17), 2)
sage: D = Curve([b^2*c - a^2*c - a^3], Q); D
Projective Plane Curve over Finite Field of size 17 defined by -a^3 -
a^2*c + b^2*c

Before this feature, each time a curve was initialized, a new ambient space object was created, even though the polynomials defining the curve could come from the coordinate ring of an existing ambient space.

sage: A.<x,y,z> = AffineSpace(QQ, 3)
sage: C = Curve([x^2 - y^2 + z^2 + 1, x - z])
sage: C.ambient_space() is A
False
sage: D = Curve([x^2 - y^2 + z^2 + 1, x - z], A)
sage: D.ambient_space() is A
True

A curve member function was also added to the affine/projective ambient space classes for extra convenience when defining a curve in a particular ambient space.

sage: K.<a> = QuadraticField(5)
sage: A.<x,y> = AffineSpace(K, 2)
sage: C = A.curve([y - a*x^3])
sage: C
Affine Plane Curve over Number Field in a with defining polynomial x^2 -
5 defined by (-a)*x^3 + y

Implement an interface between affine/projective curves: projective closure and affine patches (#20676)

The classes AffineCurve and ProjectiveCurve were made to inherit from the corresponding affine/projective subscheme classes in Sage so that curves would have access to some of the more general functionality. In particular, this gave curve objects access to the projective closure and affine patch functionality that affine/projective subschemes have in Sage. In #20676, overrides of these functions were implemented so that the objects returned would be curve objects. The projective_embedding function for affine subschemes was also improved so that in order to compute the projective closure of an affine subscheme, the generators of a Groebner basis of its defining ideal are first computed, and then the homogenizations of those generators will generate the ideal of the projective closure. Without computing a Groebner basis first, some examples such as the twisted cubic in $\mathbb{A}^3$ will fail.

sage: A.<x,y,z> = AffineSpace(QQ, 3)
sage: X = A.subscheme([y - x^2, z - x^3])
sage: X.projective_closure()
Closed subscheme of Projective Space of dimension 3 over Rational Field
defined by:
x0^2 - x1*x3,
x0*x1 - x2*x3,
x1^2 - x0*x2

Here as curves:

sage: A.<x,y,z> = AffineSpace(QQ, 3)
sage: C = A.curve([y - x^2, z - x^3])
sage: C.projective_closure()
Projective Curve over Rational Field defined by x1^2 - x0*x2, x1*x2 -
x0*x3, x2^2 - x1*x3
sage: C.projective_closure(i=1)
Projective Curve over Rational Field defined by x0^2 - x1*x2, x0*x2 -
x1*x3, x2^2 - x0*x3
sage: P.<a,b,c,d> = ProjectiveSpace(QQ, 3)
sage: C.projective_closure(PP=P)
Projective Curve over Rational Field defined by b^2 - a*c, b*c - a*d,
c^2 - b*d

The user can also specify a projective ambient space to compute the projective closure in. The affine patch functionality for curves works similarly. The user specifies the affine coordinate chart of the projective ambient space to compute the affine patch with respect to, and can also specify an affine ambient space for the affine patch to be defined in.

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = P.curve([y^2*z - x^3])
sage: C.affine_patch(0)
Affine Plane Curve over Rational Field defined by x0^2*x1 - 1
sage: A.<a,b> = AffineSpace(QQ, 2)
sage: C.affine_patch(1, A)
Affine Plane Curve over Rational Field defined by -a^3 + b

Arithmetic genus and degree for projective curves (ticket #20848)

Sage has the ability to compute the Hilbert polynomial of a homogeneous ideal using existing Singular functionality. For this project, we implemented functions to compute the degree and arithmetic genus of arbitrary projective curves. Both the degree and arithmetic genus of a projective variety are easily computable from its Hilbert polynomial. We also implemented degree for projective subschemes instead of just curves as it helped with testing some of the later intersection analysis functionality. Along with this, an is_irreducible function for subschemes was also implemented that just checks whether the defining ideal of the subscheme is a prime ideal (over the current base field).

sage: P.<x,y,z,w,t> = ProjectiveSpace(GF(7), 4)
sage: C = P.curve([t^3 - x*y*w, x^3 + y^3 + z^3, z - w])
sage: C.is_irreducible()
True
sage: C.arithmetic_genus()
10
sage: C.degree()
9

Singularity and intersection analysis for curves/subschemes (tickets #20774, #20811, #20839, #20930)

The interface between affine/projective curves established with the projective closure/affine patch functionality was especially useful for performing basic analysis of some local properties of curves and intersections of curves.

In #20774, functionality was added for computing the singular subscheme of a curve. This is the subscheme defined by the Jacobian ideal of the curve. Functionality for computing this ideal for algebraic subschemes already existed in Sage, so this was implemented as a simple function returning the subscheme defined by that ideal. Finding the singular points amounts to just computing the set of rational points of the singular subscheme. The singular subscheme of an irreducible curve always has dimension zero, as it is a proper closed subset of the curve, and so the existing functionality Sage has for finding the rational points of dimension zero subschemes can be used.

sage: P.<x,y,z,w> = ProjectiveSpace(QQ, 3)
sage: C = Curve([y^8 - x^2*z*w^5, w^2 - 2*y^2 - x*z], P)
sage: C.Jacobian()
Ideal (y^8 - x^2*z*w^5, -2*y^2 - x*z + w^2, -x^3*y*z^4 + 3*x^2*y*z^3*w^2
- 3*x*y*z^2*w^4 + 8*x*y*z*w^5 + y*z*w^6, x^2*z*w^5, -5*x^2*z^2*w^4 -
4*x*z*w^6, x^4*y*z^3 - 3*x^3*y*z^2*w^2 + 3*x^2*y*z*w^4 - 4*x^2*y*w^5 -
x*y*w^6, -2*x^3*y*z^3*w + 6*x^2*y*z^2*w^3 - 20*x^2*y*z*w^4 - 6*x*y*z*w^5
+ 2*y*w^7, -5*x^3*z*w^4 - 2*x^2*w^6) of Multivariate Polynomial Ring in
x, y, z, w over Rational Field
sage: C.singular_subscheme()
Closed subscheme of Projective Space of dimension 3 over Rational Field
defined by:
y^8 - x^2*z*w^5,
-2*y^2 - x*z + w^2,
-x^3*y*z^4 + 3*x^2*y*z^3*w^2 - 3*x*y*z^2*w^4 + 8*x*y*z*w^5 + y*z*w^6,
x^2*z*w^5,
-5*x^2*z^2*w^4 - 4*x*z*w^6,
x^4*y*z^3 - 3*x^3*y*z^2*w^2 + 3*x^2*y*z*w^4 - 4*x^2*y*w^5 - x*y*w^6,
-2*x^3*y*z^3*w + 6*x^2*y*z^2*w^3 - 20*x^2*y*z*w^4 - 6*x*y*z*w^5 +
2*y*w^7,
-5*x^3*z*w^4 - 2*x^2*w^6
sage: C.singular_points()
[(0 : 0 : 1 : 0), (1 : 0 : 0 : 0)]

Note that the singular_points function will only return the singular points defined over the base field of the curve; if that field is not algebraically closed there could be other singular points of the curve defined over an extension. The user may optionally specify such an extension over which to search:

sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = Curve([(x^2 + y^2 - y - 2)*(y - x^2 + 2) + y^5], A)
sage: C.singular_points()
[]
sage: K.<a> = QuadraticField(2)
sage: C.singular_points(F=K)
[(-a, 0), (a, 0)]

Next we implemented functionality for computing the tangents and multiplicity at a given point for plane curves. Using this, an is_ordinary_singularity function was also implemented which tests whether a singular point of a plane curve is ordinary (true when the tangent lines at that point are distinct).

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([y^3*z^2 - x^5], P)
sage: Q = P([0,0,1]) # Q is a cusp on C
sage: C.multiplicity(Q)
3
sage: C.tangents(Q)
[y]
sage: C.is_ordinary_singularity(Q)
False
sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = A.curve([y^2 - x^2 - x^3])
sage: Q = A([0,0]) # is an node on C
sage: C.multiplicity(Q)
2
sage: C.tangents(Q)
[x - y, x + y]
sage: C.is_ordinary_singularity(Q)
True

Multiplicity at a point can also be defined for space curves and subschemes in general as the multiplicity of the maximal ideal in the local ring corresponding to that point. Functionality to compute this already exists within Singular; this was used for curves in #20774, and later extended to general subschemes in #20930.

sage: A.<x,y,z,w> = AffineSpace(QQ, 4)
sage: X = A.subscheme([z*y - x^7, w - 2*z])
sage: Q1 = A([1,1/3,3,6])
sage: X.multiplicity(Q1)
1
sage: Q2 = A([0,0,0,0])
sage: X.multiplicity(Q2)
2
sage: K.<a> = CyclotomicField(3)
sage: P.<x,y,z,w> = ProjectiveSpace(K, 3)
sage: C = P.curve([(x^2 + y^2)^3 - 4*x^2*y^2*z^2 + z^6, z*x - w*y])
sage: Q = P([0,0,0,1])
sage: C.multiplicity(Q)
6

Functionality for computing intersection multiplicities was implemented in #20839. We used Serre’s Tor formula to do this as it turned out that Singular has enough homological algebra functionality to support the needed computations.

Some examples showing this implementation agrees with Bezout’s theorem:

sage: R.<a> = QQ[]
sage: K.<b> = NumberField(a^6 - 3*a^5 + 5*a^4 - 5*a^3 + 5*a^2 - 3*a + 1)
sage: P.<x,y,z> = ProjectiveSpace(K, 2)
sage: C = P.curve([x^4 + x^2*y^2 - 2*x^2*y*z - x*y^2*z + y^2*z^2])
sage: D = P.curve([y^2*z - x^3 - x^2*z])
sage: L = C.intersection(D).rational_points(); L
sage: C.degree()*D.degree()
12
sage: sum([C.intersection_multiplicity(D, Q) for Q in L])
12
sage: R.<a> = QQ[]
sage: K.<b> = NumberField(a^24 - 3*a^23 + 10*a^22 - 14*a^21 + 17*a^20 - 4*a^19 + 18*a^18 - 13*a^17 + 142*a^16 - 79*a^15 + 295*a^14 - 59*a^13 + 362*a^12 + 59*a^11 + 295*a^10 + 79*a^9 + 142*a^8 + 13*a^7 + 18*a^6 + 4*a^5 + 17*a^4 + 14*a^3 + 10*a^2 + 3*a + 1)
sage: P.<x,y,z,w> = ProjectiveSpace(K, 3)
sage: X = P.subscheme([x^2*z + y^3 - z*w^2])
sage: Y = P.subscheme([y^3 - x*y*z, w - z])
sage: X.degree()*Y.degree()
9
sage: sum([X.intersection_multiplicity(Y, Q) for Q in X.intersection(Y).rational_points()]) # takes around a minute
9

This also works for subschemes of products of projective spaces or affine spaces:

sage: R.<a> = QQ[]
sage: K.<b> = NumberField(a^6 - 3*a^5 + 5*a^4 - 5*a^3 + 5*a^2 - 3*a + 1)
sage: A.<x,y,z,w> = AffineSpace(K, 4)
sage: X = A.subscheme([x*y, y*z + 7, w^3 - x^3])
sage: Y = A.subscheme([x - z^3 + z + 1])
sage: Q = A([0, -7*b^5 + 21*b^4 - 28*b^3 + 21*b^2 - 21*b + 14, -b^5 + 2*b^4 - 3*b^3 + 2*b^2 - 2*b, 0])
sage: X.intersection_multiplicity(Y, Q)
3
sage: F.<a> = GF(4)
sage: PP.<x,y,z,u,v,w> = ProductProjectiveSpaces(F, [2,2])
sage: X = PP.subscheme([z^5 + 3*x*y^4 + 8*y^5, u^2 - v^2])
sage: Y = PP.subscheme([x^6 + z^6, w*z - v*y])
sage: Q = PP([a,a+1,1,a,a,1])
sage: X.intersection_multiplicity(Y, Q)
16

In #20811, a class for points on curves was implemented so that points could have access to the plane curve specific functionality.

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([y^3*z^2 - x^5], P)
sage: Q = C([0,0,1])
sage: Q.multiplicity()
3
sage: Q.tangents()
[y]
sage: Q.is_singular()
True
sage: Q.is_ordinary_singularity()
False

The subscheme point classes were also given functions to access the multiplicity/intersection multiplicity functionality in #20930. For example:

sage: PP.<x,y,z,u,v> = ProductProjectiveSpaces(QQ, [2,1])
sage: X = PP.subscheme([y^2*z^3*u - x^5*v])
sage: Y = PP.subscheme([u^3 - v^3, x - y])
sage: Q = X([0,0,1,1,1])
sage: Q.intersection_multiplicity(Y)
2

Plane curve models (ticket #20790)

Every algebraic curve is birationally equivalent to a plane curve. To find such a model for a curve, we compute a projection of the curve into a plane. Such projection functionality has been implemented for both projective and affine curves in #20790 as the functions projection and plane_projection. For projective curves, creation of a projection map relies on finding a point not on the curve. The projective curve projection function projects a given curve into projective space of dimension one less than the ambient space of the curve. The user can optionally specify the space to project into, and a point not on the curve:

sage: K.<a> = CyclotomicField(3)
sage: P.<x,y,z,w> = ProjectiveSpace(K, 3)
sage: C = Curve([y*w - x^2, z*w^2 - a*x^3], P)
sage: L.<a,b,c> = ProjectiveSpace(K, 2)
sage: C.projection(PS=L)
(Scheme morphism:
From: Projective Curve over Cyclotomic Field of order 3 and degree 2
defined by -x^2 + y*w, (-a)*x^3 + z*w^2
To: Projective Space of dimension 2 over Cyclotomic Field of order
3 and degree 2
Defn: Defined on coordinates by sending (x : y : z : w) to
(x : y : -z + w),
Projective Plane Curve over Cyclotomic Field of order 3 and degree 2
defined by a^6 + (-a)*a^3*b^3 - a^4*b*c)
sage: PP.<x,y,z,w> = ProjectiveSpace(QQ, 3)
sage: C = PP.curve([x^3 - z^2*y, w^2 - z*x])
sage: Q = PP([1,0,1,1])
sage: C.projection(P=Q)
(Scheme morphism:
From: Projective Curve over Rational Field defined by x^3 - y*z^2,
-x*z + w^2
To: Projective Space of dimension 2 over Rational Field
Defn: Defined on coordinates by sending (x : y : z : w) to
(y : -x + z : -x + w),
Projective Plane Curve over Rational Field defined by x0*x1^5 -
6*x0*x1^4*x2 + 14*x0*x1^3*x2^2 - 16*x0*x1^2*x2^3 + 9*x0*x1*x2^4 -
2*x0*x2^5 - x2^6)

The plane_projection function computes a plane curve model by repeatedly applying projection. The user can similarly specify a projective plane to project into.

sage: P.<x,y,z,w,u,v> = ProjectiveSpace(QQ, 5)
sage: C = P.curve([x*u - z*v, w - y, w*y - x^2, y^3*u*2*z - w^4*w])
sage: L.<a,b,c> = ProjectiveSpace(QQ, 2)
sage: C.plane_projection(PP=L)
(Scheme morphism:
From: Projective Curve over Rational Field defined by x*u - z*v, -y +
w, -x^2 + y*w, -w^5 + 2*y^3*z*u
To: Projective Space of dimension 2 over Rational Field
Defn: Defined on coordinates by sending (x : y : z : w : u : v) to
(x : -z + u : -z + v),
Projective Plane Curve over Rational Field defined by a^8 + 6*a^7*b +
4*a^5*b^3 - 4*a^7*c - 2*a^6*b*c - 4*a^5*b^2*c + 2*a^6*c^2)

Affine curves also have a projection function and a plane_projection function though the functionality works a bit differently. For affine curves, the user can specify an affine space of dimension between 2 and the dimension of the ambient space of the curve to project into. The projection map will just be the projection onto the coordinates specified by the user:

sage: A.<x,y,z,w> = AffineSpace(QQ, 4)
sage: C = A.curve([x*y - z^3, x*z - w^3, w^2 - x^3])
sage: C.projection([y,z])
(Scheme morphism:
From: Affine Curve over Rational Field defined by -z^3 + x*y, -w^3 +
x*z, -x^3 + w^2
To: Affine Space of dimension 2 over Rational Field
Defn: Defined on coordinates by sending (x, y, z, w) to
(y, z),
Affine Plane Curve over Rational Field defined by x1^23 - x0^7*x1^4)
sage: B.<x,y,z> = AffineSpace(QQ, 3)
sage: C.projection([x,y,z], AS=B)
(Scheme morphism:
From: Affine Curve over Rational Field defined by -z^3 + x*y, -w^3 +
x*z, -x^3 + w^2
To: Affine Space of dimension 3 over Rational Field
Defn: Defined on coordinates by sending (x, y, z, w) to
(x, y, z),
Affine Curve over Rational Field defined by z^3 - x*y, x^8 - x*z^2,
x^7*z^2 - x*y*z)

To get a curve back, sometimes some care is necessary when choosing the coordinates to project onto:

sage: A.<x,y,z,w> = AffineSpace(QQ, 4)
sage: C = Curve([x - 2, y - 3, z - 1], A)
sage: B.<a,b,c> = AffineSpace(QQ, 3)
sage: C.projection([0,1,2], AS=B)
(Scheme morphism:
From: Affine Curve over Rational Field defined by x - 2, y - 3, z - 1
To: Affine Space of dimension 3 over Rational Field
Defn: Defined on coordinates by sending (x, y, z, w) to
(x, y, z),
Closed subscheme of Affine Space of dimension 3 over Rational Field
defined by:
c - 1,
b - 3,
a - 2)

The affine plane_projection function works in the same way as the projective implementation:

sage: K.<b> = QuadraticField(-2)
sage: A.<x,y,z> = AffineSpace(K, 3)
sage: C = A.curve([x - b, y - 2])
sage: B.<a,b> = AffineSpace(K, 2)
sage: C.plane_projection(AP=B)
(Scheme morphism:
From: Affine Curve over Number Field in b with defining polynomial
x^2 + 2 defined by x + (-b), y - 2
To: Affine Space of dimension 2 over Number Field in b with
defining polynomial x^2 + 2
Defn: Defined on coordinates by sending (x, y, z) to
(x, z),
Affine Plane Curve over Number Field in b with defining polynomial x^2
+ 2 defined by a + (-b))

Ordinary plane curve models (ticket #20895)

While not every curve has a nonsingular model that lies in a plane, every curve is birationally equivalent to a plane curve with only ordinary singularities. Functionality to compute such models was implemented in ticket #20895. The idea behind one method of computing these models is described in this previous blog post. This functionality was implemented for projective curves in the function ordinary_model, and its two helper functions quadratic_transform and excellent_position. The function quadratic_transform computes the proper transform of a given plane curve with respect to the standard Cremona transformation.

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve(x^3*y - z^4 - z^2*x^2, P)
Scheme morphism:
From: Projective Plane Curve over Rational Field defined by x^3*y -
x^2*z^2 - z^4
To: Projective Plane Curve over Rational Field defined by -x^3*y -
x*y*z^2 + z^4
Defn: Defined on coordinates by sending (x : y : z) to
(y*z : x*z : x*y)

The excellent_position function moves a given plane curve into “excellent position” via a change of coordinates as defined in William Fulton’s Algebraic Curves text.

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([x*y - z^2], P)
sage: Q = P([1,1,1])
sage: C.excellent_position(Q)
Scheme morphism:
From: Projective Plane Curve over Rational Field defined by x*y - z^2
To: Projective Plane Curve over Rational Field defined by -x^2 -
3*x*y - 4*y^2 - x*z - 3*y*z
Defn: Defined on coordinates by sending (x : y : z) to
(-x + 1/2*y + 1/2*z : -1/2*y + 1/2*z : x + 1/2*y - 1/2*z)

Then for a given plane curve, the ordinary_model function repeatedly applies these functions in a way so that eventually a plane curve with only ordinary singularities is reached. Unfortunately, these transformations tend to increase the degree of the curve very quickly, and so far this functionality is only practical for small examples.

Here is an example testing the fact that for a projective plane curve $C$ of degree $d$ with only ordinary singularities, we can compute its geometric genus as $\mathrm{genus}(C) = \frac{1}{2}((d-1)(d-2) - \sum_{P\in\mathrm{sing(C)}}\mathrm{mult}_{P}(C)(\mathrm{mult}_{P}(C) - 1))$, where $\mathrm{mult}$ denotes the multiplicity of a singularity, and $\mathrm{sing}$ denotes the set of singular points. The genus function that’s used in this example computes the geometric genus of a curve using the implementation in Singular which works over finite fields and the rational field.

sage: set_verbose(-1)
sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([y^2*z^2 - x^4 - x^3*z], P)
sage: C.genus()
0
sage: D = C.ordinary_model(); D
Scheme morphism:
From: Projective Plane Curve over Rational Field defined by -x^4 -
x^3*z + y^2*z^2
To: Projective Plane Curve over Rational Field defined by 4*x^6*y^3
- 24*x^5*y^4 + 36*x^4*y^5 + 8*x^6*y^2*z - 40*x^5*y^3*z + 24*x^4*y^4*z +
72*x^3*y^5*z - 4*x^6*y*z^2 + 8*x^5*y^2*z^2 - 56*x^4*y^3*z^2 +
104*x^3*y^4*z^2 + 44*x^2*y^5*z^2 + 8*x^6*z^3 - 16*x^5*y*z^3 -
24*x^4*y^2*z^3 + 40*x^3*y^3*z^3 + 48*x^2*y^4*z^3 + 8*x*y^5*z^3 -
8*x^5*z^4 + 36*x^4*y*z^4 - 56*x^3*y^2*z^4 + 20*x^2*y^3*z^4 +
40*x*y^4*z^4 - 16*y^5*z^4
Defn: Defined on coordinates by sending (x : y : z) to
(-3/64*x^4 + 9/64*x^2*y^2 - 3/32*x*y^3 - 1/16*x^3*z -
1/8*x^2*y*z + 1/4*x*y^2*z - 1/16*y^3*z - 1/8*x*y*z^2 + 1/16*y^2*z^2 :
-1/64*x^4 + 3/64*x^2*y^2 - 1/32*x*y^3 + 1/16*x*y^2*z - 1/16*y^3*z +
1/16*y^2*z^2 : 3/64*x^4 - 3/32*x^3*y + 3/64*x^2*y^2 + 1/16*x^3*z -
3/16*x^2*y*z + 1/8*x*y^2*z - 1/8*x*y*z^2 + 1/16*y^2*z^2)
sage: D = D.codomain()
sage: sing = D.singular_points()
sage: t = 0
sage: for Q in sing:
....:     m = D.multiplicity(Q)
....:     t = t + m*(m - 1)/2
sage: D.arithmetic_genus() - t
0

Blow ups and resolution of singularities (ticket #21085)

Probably one of the biggest goals of the project was to implement functionality for computing nonsingular models for curves. Several approaches were considered, but the approach we settled on was to repeatedly blow up the singularities of a curve until they are all resolved. This approach was implemented in ticket #21085. For irreducible curves, the order in which the singularities are blown up doesn’t matter, and a nonsingular curve will be obtained after finitely many blow ups. However, the primary difficulty here was finding a useful way to represent the blow up and resolution of singularities data in Sage.

Some details about the resolution process we chose are explained in this post. One option to represent blow ups is to give them as subschemes of product spaces. However, while Sage currently can work with products of projective spaces, Sage doesn’t yet have support for mixed product spaces. Additionally, this representation can make applying multiple blow ups difficult.

Because of this, we restricted the functionality to just affine curves, and present the blow up and resolution of singularities data in terms of affine charts that cover the product spaces the blow ups are defined in. This culminated in two functions for affine curves: blowup and resolution_of_singularities.

The following example blows up a curve at the origin. The blow up is covered by two affine patches:

sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = Curve([y^2 - x^3], A)
sage: C.blowup()
((Affine Plane Curve over Rational Field defined by s1^2 - x,
Affine Plane Curve over Rational Field defined by y*s0^3 - 1),
([Scheme endomorphism of Affine Plane Curve over Rational Field defined
by s1^2 - x
Defn: Defined on coordinates by sending (x, s1) to
(x, s1), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by s1^2 - x
To: Affine Plane Curve over Rational Field defined by y*s0^3 - 1
Defn: Defined on coordinates by sending (x, s1) to
(x*s1, 1/s1)], [Scheme morphism:
From: Affine Plane Curve over Rational Field defined by y*s0^3 - 1
To: Affine Plane Curve over Rational Field defined by s1^2 - x
Defn: Defined on coordinates by sending (y, s0) to
(y*s0, 1/s0),
Scheme endomorphism of Affine Plane Curve over Rational Field defined
by y*s0^3 - 1
Defn: Defined on coordinates by sending (y, s0) to
(y, s0)]),
(Scheme morphism:
From: Affine Plane Curve over Rational Field defined by s1^2 - x
To: Affine Plane Curve over Rational Field defined by -x^3 + y^2
Defn: Defined on coordinates by sending (x, s1) to
(x, x*s1), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by y*s0^3 - 1
To: Affine Plane Curve over Rational Field defined by -x^3 + y^2
Defn: Defined on coordinates by sending (y, s0) to
(y*s0, y)))

Note that the single blow up actually resolves the singularity the curve has at the origin. The data returned consists of affine patches defined by the proper transform of the curve in each chart, transition maps between the patches, and maps back to the original curve. The transition maps are provided to specify how the patches could be glued together to get a single object representing the blowup (gluing schemes is currently unsupported in Sage), and the maps back to the original curve are the restrictions of the projection map from the blow up to the original curve to each patch.

The user can also specify to blow up a curve at a non-origin point:

sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = A.curve((y - 3/2)^3 - (x + 2)^5 - (x + 2)^6)
sage: Q = A([-2,3/2])
sage: C.blowup(Q)
((Affine Plane Curve over Rational Field defined by x^3 - s1^3 + 7*x^2 + 16*x + 12,
Affine Plane Curve over Rational Field defined by 8*y^3*s0^6 - 36*y^2*s0^6 + 8*y^2*s0^5 + 54*y*s0^6 - 24*y*s0^5 - 27*s0^6 + 18*s0^5 - 8),
([Scheme endomorphism of Affine Plane Curve over Rational Field defined by x^3 - s1^3 + 7*x^2 + 16*x + 12
Defn: Defined on coordinates by sending (x, s1) to (x, s1), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by x^3 - s1^3 + 7*x^2 + 16*x + 12
To: Affine Plane Curve over Rational Field defined by 8*y^3*s0^6 - 36*y^2*s0^6 + 8*y^2*s0^5 + 54*y*s0^6 - 24*y*s0^5 - 27*s0^6 + 18*s0^5 - 8
Defn: Defined on coordinates by sending (x, s1) to (x*s1 + 2*s1 + 3/2, 1/s1)], [Scheme morphism:
From: Affine Plane Curve over Rational Field defined by 8*y^3*s0^6 - 36*y^2*s0^6 + 8*y^2*s0^5 + 54*y*s0^6 - 24*y*s0^5 - 27*s0^6 + 18*s0^5 - 8
To: Affine Plane Curve over Rational Field defined by x^3 - s1^3 + 7*x^2 + 16*x + 12
Defn: Defined on coordinates by sending (y, s0) to (y*s0 - 3/2*s0 - 2, 1/s0),
Scheme endomorphism of Affine Plane Curve over Rational Field defined by 8*y^3*s0^6 - 36*y^2*s0^6 + 8*y^2*s0^5 + 54*y*s0^6 - 24*y*s0^5 - 27*s0^6 + 18*s0^5 - 8
Defn: Defined on coordinates by sending (y, s0) to (y, s0)]),
(Scheme morphism:
From: Affine Plane Curve over Rational Field defined by x^3 - s1^3 + 7*x^2 + 16*x + 12
To: Affine Plane Curve over Rational Field defined by -x^6 - 13*x^5 - 70*x^4 - 200*x^3 + y^3 - 320*x^2 - 9/2*y^2 - 272*x + 27/4*y - 795/8
Defn: Defined on coordinates by sending (x, s1) to (x, x*s1 + 2*s1 + 3/2), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by 8*y^3*s0^6 - 36*y^2*s0^6 + 8*y^2*s0^5 + 54*y*s0^6 - 24*y*s0^5 - 27*s0^6 + 18*s0^5 - 8
To: Affine Plane Curve over Rational Field defined by -x^6 - 13*x^5 - 70*x^4 - 200*x^3 + y^3 - 320*x^2 - 9/2*y^2 - 272*x + 27/4*y - 795/8
Defn: Defined on coordinates by sending (y, s0) to (y*s0 - 3/2*s0 - 2, y)))

This approach facilitates repeated blow ups, and in the function resolution_of_singularities the singular points of a given curve are recursively blown up using the blowup function. The transition maps between the patches are composed with each other and the restrictions of the projection maps so that the resolutions of singularities of the curve can be represented in a similar way as the blow up data:

sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = Curve([y^3 - x^5], A)
sage: B = C.resolution_of_singularities()
sage: B[0]
(Affine Plane Curve over Rational Field defined by x*ss1^3 - 1,
Affine Plane Curve over Rational Field defined by ss0^2 - s1,
Affine Plane Curve over Rational Field defined by y^2*s0^5 - 1)
sage: B[2]
(Scheme morphism:
From: Affine Plane Curve over Rational Field defined by x*ss1^3 - 1
To: Affine Plane Curve over Rational Field defined by -x^5 + y^3
Defn: Defined on coordinates by sending (x, ss1) to
(x, x^2*ss1), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by ss0^2 - s1
To: Affine Plane Curve over Rational Field defined by -x^5 + y^3
Defn: Defined on coordinates by sending (s1, ss0) to
(s1*ss0, s1^2*ss0), Scheme morphism:
From: Affine Plane Curve over Rational Field defined by y^2*s0^5 - 1
To: Affine Plane Curve over Rational Field defined by -x^5 + y^3
Defn: Defined on coordinates by sending (y, s0) to
(y*s0, y))

By default, only singularities defined over the base field of the curve are resolved, but if working over a number field the user can specify to extend the base field to ensure the resulting curve is nonsingular. However, the extension process can slow down computations.

sage: set_verbose(-1)
sage: K.<a> = QuadraticField(3)
sage: A.<x,y> = AffineSpace(K, 2)
sage: C = A.curve(x^4 + 2*x^2 + a*y^3 + 1)
sage: C.resolution_of_singularities(extend=True)[0]
(Affine Plane Curve over Number Field in a0 with defining polynomial y^4
- 4*y^2 + 16 defined by 24*x^2*ss1^3 + 24*ss1^3 + (a0^3 - 8*a0),
Affine Plane Curve over Number Field in a0 with defining polynomial y^4
- 4*y^2 + 16 defined by 24*s1^2*ss0 + (a0^3 - 8*a0)*ss0^2 + (6*a0^3)*s1,
Affine Plane Curve over Number Field in a0 with defining polynomial y^4
- 4*y^2 + 16 defined by 8*y^2*s0^4 + (-4*a0^3)*y*s0^3 - 32*s0^2 + (a0^3
- 8*a0)*y)

Rational parameterizations (tickets #21137, #21167, #21168)

The final goal of the project was to implement functions to compute rational parameterizations of rational (geometric genus zero) curves. Singular already has functionality for computing rational parameterizations of rational projective plane curves, and both affine and projective versions of this functionality were implemented in Sage in ticket #21137.

The parameterizations are returned as birational maps from either the affine or projective line to the given affine/projective plane curve. The curves given to these functions must be defined over the rational field and have geometric genus zero. When implementing this functionality, it was helpful to implement a curve-specific version of the change_ring function for algebraic subschemes. This was done in ticket #21168. Also there was a bug with the Singular interface in Sage which was addressed in #21167.

sage: A.<x,y> = AffineSpace(QQ, 2)
sage: C = Curve([(x^2 + y^2 - 2*x)^2 - x^2 - y^2], A)
sage: C.rational_parameterization()
Scheme morphism:
From: Affine Space of dimension 1 over Rational Field
To: Affine Plane Curve over Rational Field defined by x^4 +
2*x^2*y^2 + y^4 - 4*x^3 - 4*x*y^2 + 3*x^2 - y^2
Defn: Defined on coordinates by sending (t) to
((-12*t^4 + 6*t^3 + 4*t^2 - 2*t)/(-25*t^4 + 40*t^3 - 26*t^2 +
8*t - 1), (-9*t^4 + 12*t^3 - 4*t + 1)/(-25*t^4 + 40*t^3 - 26*t^2 + 8*t - 1))

The parameterization returned may sometimes be defined over a quadratic extension of the rationals:

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
sage: C = Curve([x^2 + y^2 + z^2], P)
sage: C.rational_parameterization()
Scheme morphism:
From: Projective Space of dimension 1 over Number Field in a with
defining polynomial a^2 + 1
To:   Projective Plane Curve over Number Field in a with defining
polynomial a^2 + 1 defined by x^2 + y^2 + z^2
Defn: Defined on coordinates by sending (s : t) to
(s^2 - t^2 : (a)*s^2 + (a)*t^2 : -2*s*t)