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 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) sage: C.quadratic_transform() 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 of degree with only ordinary singularities, we can compute its geometric genus as , where denotes the multiplicity of a singularity, and 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)]]>

In the first week, I worked on trying to improve the efficiency of the ordinary model functionality in ticket #20895. The approach that’s being tested now is to use resultants to avoid needing to explicitly compute the points at which curve intersects the exceptional lines when testing whether the curve is in excellent position. It still needs some more testing, but if it’s working properly, it will allow the ordinary model functionality to work for curves defined over number fields, rather than requiring them to be defined over the algebraic closure of the rationals, , which is costly to work over in Sage. The function that computes the ordinary models still depends on some QQbar computations when finding singular points, and it seems it would be helpful to implement a function to compute a “splitting field” for a zero-dimensional subscheme over a number field, that is, an extension of the base field over which all rational points of the subscheme can be found.

However, the highest priority right now, and for the last two weeks, has been to implement functionality to compute nonsingular models of curves. The most practical approach to doing this seems to be to repeatedly blow up the singularities of a curve until they are resolved. This first requires that functionality for blowing up a curve at a point is implemented.

Blowing up a variety along a subvariety can be defined generally as follows: suppose is a variety, and is a subvariety. Suppose for some , where is the coordinate ring of . Define the map by for each . This is a rational map (regular when restricted to ). Define the blow up of along to be , that is, the closure of the graph of in the product space . This construction can be shown to be independent of the choice of generators for .

To illustrate this in a specific case, consider the example of blowing up affine space at the origin . Our map would be defined , and the closure of its graph in would be the zero locus of the polynomials for where the are homogeneous coordinates for . We can consider the projection map defined . When restricted to the graph of , the map is a bijection. Outside of the graph, contains a copy of , that is, . In general, for a blow up as defined earlier, this inverse image is known as the exceptional divisor of the blow up. Intuitively, blowing up at the origin has the effect of replacing the origin with a copy of .

Next consider the example of blowing up the curve in at the origin. We can describe the blow up as the zero locus of and in , where are homogeneous coordinates for . To visualize what’s happening, we can use the two standard affine charts of corresponding to and . When , we can set , and so we have that , and thus that . Factoring this we have , thus the blow up in this affine chart is the union of the curves and in with coordinates . The curve corresponds to the exceptional divisor. Repeating this for the affine chart yields the curves , in with coordinates . Note that both and are both nonsingular curves; thus blowing up had the effect of resolving the singularity that has at the origin.

This approach of using affine charts to represent the blow up of a curve at a point is the approach that’s we’re currently trying for resolution of singularities. The ticket for implementing blow ups for curves is #21085. Progress so far has been to implement the blow up functionality for affine curves only. Using the current implementation to blow up the curve at gives:

sage: A.<x,y> = AffineSpace(QQ, 2) sage: C = Curve([y^2 - x^3], A) sage: Q = A([0,0]) sage: C.blowup(Q) ([Affine Plane Curve over Rational Field defined by z1^2 - z0, Affine Plane Curve over Rational Field defined by -z0*z1^3 + 1], [[Scheme endomorphism of Affine Space of dimension 2 over Rational Field Defn: Defined on coordinates by sending (z0, z1) to (z0, z1), Scheme endomorphism of Affine Space of dimension 2 over Rational Field Defn: Defined on coordinates by sending (z0, z1) to (z0*z1, 1/z1)], [Scheme endomorphism of Affine Space of dimension 2 over Rational Field Defn: Defined on coordinates by sending (z0, z1) to (z0*z1, 1/z1), Scheme endomorphism of Affine Space of dimension 2 over Rational Field Defn: Defined on coordinates by sending (z0, z1) to (z0, z1)]])

The maps in the second element of the tuple that’s returned are the transition maps between the affine charts. These indicate how the charts should be glued together in order to get a single variety representing the blow up. This is the main issue that we currently face: what information is necessary to support multiple blow ups using this affine chart approach.

For projective curves, there’s another option for representing the blow up. The blow up of a projective variety can be represented as a subvariety of a product of projective spaces, and in turn can be realized as a projective variety by using the Segre embedding. However, doing this quickly becomes impractical for applying multiple blow ups since the dimension of the projective ambient space will grow quickly.

Other than resolution of singularities, the second major goal for the remaining time in GSoC is to implement functionality for computing rational parameterizations of rational curves. It can be proven that any projective (resp. affine) curve with geometric genus zero is birational to (resp. ). Such curves are called rational. In the case of projective curves, such a map from to the curve can be given by a tuple of homogeneous polynomials in two indeterminants, and these polynomials give a parameterization of the curve. For an affine curve, the map from can be given by a tuple of elements of the fraction field of a polynomial ring in one indeterminant.

Our task is to, given a rational projective/affine curve, compute a parameterization for it. It turns out that an algorithm for doing so has already been implemented in Singular, and this implementation appears to be working well so the hard work has already been done. The goal of ticket #21137 is to add this functionality to Sage, and maybe implement some additional functionality for working with parameterizations, such as given a parameterization, compute the defining ideal of the curve it defines (this can be done with elimination theory).

Here’s some examples of the current projective/affine functionality in #21137:

sage: P.<x,y,z> = ProjectiveSpace(QQ, 2) sage: C = Curve([y^2*z - x^3], P) sage: C.rational_parameterization() (s^2*t, s^3, t^3)

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() ((-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))]]>

Progress in weeks 6, 7 has been to finish tickets #20811 and #20848, and to work on ticket #20895 which will implement functionality for computing ordinary models of plane curves. I also worked on #20930 which implements multiplicity for arbitrary affine/projective subschemes, and multiplicity/intersection multiplicity for subschemes of products of projective spaces. Helper functions in the corresponding point classes to access this functionality are being implemented as well.

Implementing the ordinary model functionality has proven to be somewhat tricky. Theoretically, there is a straightforward method for transforming a plane curve into one with only ordinary singularities that works in a finite number of steps. Before describing it, here’s some preliminaries:

We use the birational automorphism of known as the *standard Cremona transformation*, and will denote it as . This is the map defined for , and is undefined at the points which we will call the *fundamental points* of for convenience. We also call the lines the *exceptional lines* of . Next consider the open subset , the complement of the union of the exceptional lines. Note that is defined everywhere in , and that the exceptional lines are exactly the fibers of above the fundamental points, so the image of is contained in . We then also see that, when restricted to , is its own inverse: for , , since . So is an isomorphism of to itself, and thus is indeed a birational automorphism of .

Next, given a plane curve for some homogeneous polynomial , algebraically closed (our base field), a defining polynomial for the image of by is , which we can write as for a homogeneous and for some such that no positive power of or divides . We call the *quadratic transformation* of .

We also define the *apparent genus* of a plane curve as , where is the set of singular points of , and is the multiplicity of . Finally, if has degree , we say is in *excellent position* if is a point of multiplicity of , no exceptional line is tangent to at a fundamental point, the exceptional lines intersect each in distinct points aside from , transversally, and the exceptional line intersects transversally in distinct, non-fundamental points.

The main result is that if is a non-ordinary singularity of , and if is in excellent position, then its quadratic transformation either has one less non-ordinary singularity, or has a smaller apparent genus. The apparent genus can be shown to always be a nonnegative integer, thus we have the following algorithm to resolve the non-ordinary singularities of a plane curve:

Input: a plane curve

- while has non-ordinary singularities,
- pick a non-ordinary singularity of
- find a change of coordinates sending to , and putting into excellent position
- compute the quadratic transformation
- replace with

- return the plane curve

The output plane curve will be birational to the original curve, and will have only ordinary singularities. By the above note, this algorithm will always terminate. It isn’t difficult to prove that such change of coordinates maps exist, but this is where the difficulties of actual implementation begin.

Currently, the approach in #20895 to constructing a change of coordinates map to put a curve with non-ordinary singular point into excellent position is to incrementally generate two points such that are not collinear until the lines connecting the points satisfy the excellent position conditions. That is, ideally we would map to , and the curve would then be in excellent position.

With some care taken to incrementally choose the points the selection process will eventually terminate after finitely many choices. However, checking whether the transformation constructed from a choice of points will put the curve into excellent position can be an extremely expensive operation. Most of the cost is due to finding and analyzing the intersections of the lines connecting the points with . We currently work over the implementation of the algebraic closure of the rationals in Sage so that we can find all non-ordinary singular points and all intersections of lines with without needing to extend the field we start with, but computations over this field can be quite slow.

To get around this, our current approach is to bypass the explicit checking process; we just apply the transformation and repeat. To verify that the transformations will work, we instead check that the procedure terminates after steps, where is our starting curve, and is the number of non-ordinary singularities of . If the change of coordinate maps all put the curve into excellent position, then the algorithm should terminate in at most steps, otherwise something went wrong, and we choose points differently in the next attempt.

Most of the time, the first choices of points works, but the resulting transformations can be quite messy. Here is a simple example with only one non-ordinary singularity:

sage: set_verbose(-1) sage: P.<x,y,z> = ProjectiveSpace(QQbar, 2) sage: C = Curve([y^2*z - x^3], P) sage: C.singular_points() [(0 : 0 : 1)] sage: C.tangents(P([0,0,1])) # higher multiplicity tangent [y] sage: D = C.ordinary_model() sage: D (Scheme morphism: From: Projective Plane Curve over Algebraic Field defined by -x^3 + y^2*z To: Projective Space of dimension 2 over Algebraic Field Defn: Defined on coordinates by sending (x : y : z) to ((-1/16)*x^2 + (-1/4)*x*y + (-1/4)*y^2 + 1/4*x*z + 1/2*y*z : 1/16*x^2 + (-1/4)*y^2 + (-1/4)*x*z + 1/2*y*z : (-1/16)*x^2 + 1/4*y^2), Projective Plane Curve over Algebraic Field defined by x^3*y + 2*x^2*y^2 + x*y^3 + (-7)*x^3*z + 26*x^2*y*z + (-23)*x*y^2*z + 8*y^3*z) D[1].singular_points() [(0 : 0 : 1)] D[1].tangents(P([0,0,1])) # singularity is now ordinary [x + (-2.630294284738199?)*y, x + (-0.5419957147737579? + 0.3751512615427921?*I)*y, x + (-0.5419957147737579? - 0.3751512615427921?*I)*y]]]>

The tickets that have been opened for this project as of now are #20676, #20697, #20698, #20774, #20811, #20790, #20839, and #20848. Of these, #20811, #20839, and #20848 are still open, and the goal is to finish them this week.

In #20697 and #20698, the class structure of general curves was revised. Among the revisions made, the curve class names and inheritance structure was changed to facilitate implementation of functionality that can work for arbitrary curves, not just plane curves. The main classes for curves are broken into projective curve classes, and affine curve classes. The projective curve classes are now `ProjectiveCurve`, `ProjectivePlaneCurve`, `ProjectivePlaneCurve_finite_field`, `ProjectivePlaneCurve_prime_finte_field`, listed by order of inheritance (`ProjectiveCurve` is the most general class). The same naming scheme is used for the affine curve classes. Another important part of these revisions was to change how curve objects are initialized.

Before the changes, the only way to initialize curves was to specify some elements of a polynomial ring:

sage: x,y,z,w = QQ['x,y,z,w'].gens() sage: C = Curve([x^3 + y^3 - z^3 - w^3, x^5 - y*z^4]); C Projective Curve over Rational Field defined by x^3 + y^3 - z^3 - w^3, x^5 - y*z^4

The constructor for the curve classes decides based on whether the given polynomials are homogeneous or not to create an affine or projective curve. This initialization is somewhat limiting, and also has the downside that the user cannot specify the space in which the curve is created; a new ambient space object is always created:

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

To revise this initialization, we added an optional parameter to the constructor that allows users to specify a space to initialize a curve in,

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

and also added some helper functions in the ambient space classes so users can define curves in this way as well:

sage: A.<x,y> = AffineSpace(QQ, 2) sage: C = A.curve([y - x^2]); C Affine Plane Curve over Rational Field defined by -x^2 + y

After these revisions, we were able to start working on implementing the next functionality goals, the first of which was to implement some interface between affine and projective curves in the form of projective closures and affine patches (done in #20676). Because of the class inheritance structure revisions for curves, and because of the increased control over curve initialization, we could make use of the existing affine/projective subscheme functionality for affine patches/projective closures.

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

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

The affine patch functionality is especially important for investigating local properties of projective curves, and was essential to implementing some of the basic singularity analysis functionality in ticket #20774. Given a curve, we can now compute the subscheme defining its singular locus, its set of singular points (if finite), and the multiplicities of its singular points. For plane curves, computation of tangents and testing whether a singularity is ordinary or not was also implemented:

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.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)] sage: Q = P([0,0,1,0]) sage: C.multiplicity(Q) 8

sage: R.<a> = QQ[] sage: K.<b> = NumberField(a^2 - 3) sage: A.<x,y> = AffineSpace(K, 2) sage: C = Curve([(x^2 + y^2 - 2*x)^2 - x^2 - y^2], A) sage: C.singular_points() [(0, 0)] sage: Q = A([0,0]) sage: C.multiplicity(Q) 2 sage: C.tangents(Q) [x + (-1/3*b)*y, x + (1/3*b)*y] sage: C.is_ordinary_singularity(Q) True

Functionality for finding plane curve models for curves in higher dimension ambient spaces was implemented in ticket #20790. Such models are constructed by projecting curves into planes. The main portion of the projection functionality is implemented in the `projection` affine/projective curve class member functions. These functions return a tuple consisting of two elements: a scheme morphism from the starting curve to an ambient space of dimension one less than the ambient space of the original curve, and the curve that is the image of that morphism (if the image isn’t a curve, a subscheme object is returned instead). For affine curves, the user can specify the coordinates on which to project by passing in a list/tuple of coordinate indices or variables:

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

If the user wishes, they may also specify to keep the same variable names when creating the ambient space for the projected curve:

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], False) (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 z^23 - y^7*z^4)

For projective curves, the implementation and user options of `projection` are slightly different. Creating a well-defined projection map defined on a projective curve relies on the ability to find a point that’s not on the curve. The user may specify such a point, or by default, the method will attempt to construct one. This is always possible when working over a characteristic zero field, but when working over finite field, it is possible to have a curve that contains all of the points of its ambient space (infinite fields of positive characteristic have not yet been implemented well enough in Sage to be able to consider much functionality for curves defined over them). Once a point not on the given curve is found, a projection map can easily be created.

sage: P.<x,y,z,w,a,b,c> = ProjectiveSpace(QQ, 6) sage: C = Curve([y - x, z - a - b, w^2 - c^2, z - x - a, x^2 - w*z], P) sage: C.projection() (Scheme morphism: From: Projective Curve over Rational Field defined by -x + y, z - a - b, w^2 - c^2, -x + z - a, x^2 - z*w To: Projective Space of dimension 5 over Rational Field Defn: Defined on coordinates by sending (x : y : z : w : a : b : c) to (x : y : -z + w : a : b : c), Projective Curve over Rational Field defined by x1 - x4, x0 - x4, x2*x3 + x3^2 + x2*x4 + 2*x3*x4, x2^2 - x3^2 - 2*x3*x4 + x4^2 - x5^2, x2*x4^2 + x3*x4^2 + x4^3 - x3*x5^2 - x4*x5^2, x4^4 - x3^2*x5^2 - 2*x3*x4*x5^2 - x4^2*x5^2)

In addition to the `projection` function, projective and affine curves also now have `plane_projection` functions which just repeatedly call the corresponding `projection` functions until a projection map into a plane is constructed. Some care is taken to ensure that the image of the starting curve by the resulting projection map will still be a curve.

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

Tickets #20839 and #20848 are currently close to being finished. The goal of #20839 is to implement some basic intersection theory for curves. This includes computing intersection multiplicity and computing the set of intersection points between two curves. For plane curves, there is also a check now to determine whether two curves intersect transversely at a given point (this happens when the point is a nonsingular point of both curves, and the tangents of the curves there are distinct):

sage: A.<x,y> = AffineSpace(QQ, 2) sage: C = Curve([y - x^3], A) sage: D = Curve([y + x], A) sage: C.intersection_points(D) [(0,0)] sage: Q = A([0,0]) sage: C.intersects_at(D, Q) True sage: C.intersection_multiplicity(D, Q) 1 sage: print C.tangents(Q) [y] sage: print D.tangents(Q) [x + y] sage: C.is_transverse(D, Q) True

We are currently experimenting with implementing intersection multiplicity in general for projective/affine subschemes by using Serre’s Tor formula. To apply the formula, some restrictions on the dimensions of the subschemes and their intersection are needed, but plane curves that do not share common components already satisfy these conditions.

sage: P.<x,y,z,w> = ProjectiveSpace(QQ, 3) sage: X = P.subscheme([x^2 - z^2 + x*y, y^2 - w*x]) sage: Y = P.subscheme([w^2 + x*y + z^2]) sage: Q = P([1,-1,0,1]) sage: X.intersection_multiplicity(Y, Q) 2

In ticket #20848 we have implemented degree computation for projective subschemes. The degree of a subscheme is computed from the Hilbert polynomial of that subscheme. In fact, it is just the leading coefficient of the Hilbert polynomial scaled by the factorial of the dimension of the subscheme:

sage: P.<x,y,z,w> = ProjectiveSpace(GF(13), 3) sage: X = P.subscheme([y^2 - w^2]) sage: X.defining_ideal().hilbert_polynomial() t^2 + 2*t + 1 sage: X.degree() 2

We have also implemented arithmetic genus computation for arbitrary (not necessarily plane) irreducible projective curves. This again can easily be computed from the Hilbert polynomial of the curve; if is the Hilbert polynomial of a projective curve, then the curve’s arithmetic genus is :

sage: P.<x,y,z,w> = ProjectiveSpace(QQ, 3) sage: C = P.curve([w*z - x^2, w^2 + y^2 + z^2]) sage: C.defining_ideal().hilbert_polynomial() 4*t sage: C.arithmetic_genus() 1

Finally, in #20811 I will add classes to Sage for points on curves and give objects of those classes access to the singularity analysis functionality.

In the next half of GSoC 2016, my main goal is to implement resolution of singularities for curves. First up, in week 6 (June 27 – July 3) I will implement a function to compute ordinary plane curve models of curves. It can be proven that a plane curve can be transformed, via a finite sequence of change of coordinate maps and repeated application of the standard quadratic transformation map of , into a plane curve with only ordinary singularities. Combined with the plane curve model functionality implemented already, this provides a means of finding an ordinary plane curve birational to a given, arbitrary curve. The motivation for doing this is that ordinary singularities are nice singularities to work with (an ordinary singularity is a singular point at which the tangents of the plane curve there are distinct).

For example, once this is implemented, a possible application is an alternate (though probably not the most efficient) means of computing the geometric genus of a curve. Geometric genus is a birational invariant, and for a projective plane curve of degree with only ordinary singularities, we can compute its geometric genus as , where denotes the multiplicity of a singularity, and denotes the set of singular points.

So far, participating in GSoC 2016 has been a lot of fun. I really appreciate this chance to contribute to Sage and the guidance of my project mentors Dr. Benjamin Hutz and Dr. Miguel Marco, who have already helped me so much to understand the needed concepts and improve my coding technique. I’ve been able to keep close to the progress timeline set for this project so far, and will do my best to stay on pace and implement the remaining project functionality goals in the second half of GSoC!

]]>The aim of ticket #20774 is to implement functionality to analyze the singular points of a curve. The first objective here is to actually compute the set of singular points of a curve. This can be done by finding the rational points on the subscheme defined by the Jacobian ideal of the curve. For a curve in affine or projective space of dimension this is the ideal generated by the determinants of the minors of the Jacobian matrix of the curve. When the corresponding subscheme is zero-dimensional (which will be the case if the curve is irreducible/reduced), there are finitely many singular points, and we are able to find all of them. Once we can compute the set of singular points of a curve, we can implement functionality to classify them. An important tool for this classification is the multiplicity of a singular point. For a general affine variety , we can compute the multiplicity of a singular point of by computing the multiplicity of the local ring . This in turn is defined in terms of the Hilbert-Samuel polynomial corresponding to the local ring. To do this in Sage, I used the existing functionality in Singular for working with local rings. For projective varieties, a way to compute the multiplicity of a singular point is to compute the multiplicity of the corresponding point on any affine patch of the variety containing the point. For plane curves, we can also compute the tangents of the curves at points. These can be used to determine if a singular point is an ordinary singularity, which is defined to a be a singular point at which the tangents are distinct.

Ticket #20774 only implements singularity analysis functionality at the curve level. That is, all of the functions belong to the curve classes. So even if you have a point P that is on a curve C, things like P.is_singular() currently are not defined, but would be helpful to have. To address this, I opened ticket #20811 to create classes for points on curves, and to give objects of those classes access to the singularity functionality in #20774.

One of the main goals to complete before the midterm evaluation week is the implementation of functionality for computing plane curve models of space curves. This will allow us to reduce problems such as computing rational parameterizations (when they exist) of space curves to that of plane curves. This will be done in ticket #20790. So far, I have implemented a first attempt at projecting projective space curves into lower dimensional ambient spaces. My approach for doing this is to first pick a point not on the curve, then apply an automorphism of projective space that moves that point to , and then apply the projection map , which will be well-defined when the domain is restricted to the translated curve. The closure of the image of this map can then be computed via elimination theory.

This week my goal is to finish the plane curve model functionality, and implement that of computing the degree/arithmetic genus of general curves and intersection analysis.

]]>We also worked on implementing functionality for computing the projective closure and the affine patches of affine/projective curves. This was done in ticket #20676. In #20697, the curve classes were made to inherit from the respective projective/affine subscheme classes in Sage, and those classes already have functionality for projective closure and affine patch computation. However, the existing projective closure computation for affine subschemes worked by, given a set of generators for the defining ideal of the subscheme, homogenizing each generator, and then using the resulting set of homogeneous polynomials to generate the defining ideal of the projective closure. This method does not always work though; not all generating sets give a generating set for the defining ideal of the projective closure after homogenization. An example of where this fails is the twisted cubic curve in . Part of the work of #20676 is to fix this by instead homogenizing the elements of a Groebner basis (with respect to a graded monomial order) of the defining ideal of the affine subscheme, which does give a generating set for the defining ideal of its projective closure.

The task for this week (May 30-June 5) is to start working on implementing other basic functionality such as genus and degree computation, changes of coordinates, and singularity/intersection analysis.

]]>