This paper was converted on www.awesomepapers.org from LaTeX by an anonymous user.
Want to know more? Visit the Converter page.

Nemo/Hecke: Computer Algebra and Number Theory Packages for the Julia Programming Language

Claus Fieker TU KaiserslauternFachbereich Mathematik, Postfach 304967653KaiserslauternGermany fieker@mathematik.uni-kl.de William Hart TU KaiserslauternFachbereich Mathematik, Postfach 304967653KaiserslauternGermany goodwillhart@gmail.com Tommy Hofmann TU KaiserslauternFachbereich Mathematik, Postfach 304967653KaiserslauternGermany thofmann@mathematik.uni-kl.de  and  Fredrik Johansson Inria Bordeaux & Institut de Mathématiques de Bordeaux200 Avenue de la Vieille Tour33400TalenceFrance fredrik.johansson@gmail.com
(2017)
Abstract.

We introduce two new packages, Nemo and Hecke, written in the Julia programming language for computer algebra and number theory. We demonstrate that high performance generic algorithms can be implemented in Julia, without the need to resort to a low-level C implementation. For specialised algorithms, we use Julia’s efficient native C interface to wrap existing C/C++ libraries such as Flint, Arb, Antic and Singular. We give examples of how to use Hecke and Nemo and discuss some algorithms that we have implemented to provide high performance basic arithmetic.

copyright: acmlicensedjournalyear: 2017conference: ISSAC ’17; July 25-28, 2017; Kaiserslautern, Germanyprice: 15.00doi: 10.1145/3087604.3087611isbn: 978-1-4503-5064-8/17/07

1. Introduction

Nemo111http://nemocas.org. Nemo and Hecke are BSD licensed. is a computer algebra package for the Julia programming language. The eventual aim is to provide highly performant commutative algebra, number theory, group theory and discrete geometry routines. Hecke is a Julia package that builds on Nemo to cover algebraic number theory.

Nemo consists of two parts: wrappers of specialised C/C++ libraries (Flint (flint, ), Arb (arb, ), Antic (antic, ) and Singular (singular, )), and implementations of generic algorithms and mathematical data structures in the Julia language. So far the fully recursive, generic constructions include univariate and multivariate polynomial rings, power series rings, residue rings (modulo principal ideals), fraction fields, and matrices.

We demonstrate that Julia is effective for implementing high performance computer algebra algorithms. Our implementations also include a number of improvements over the state of the art in existing computer algebra systems.

2. Computer algebra in Julia

Julia (julia, ) is a modern programming language designed to be both performant and flexible. Notable features include an innovative type system, multiple dispatch, just-in-time (JIT) compilation supported by dynamic type inference, automatic memory management (garbage collection), metaprogramming capabilities, high performance builtin collection types (dictionaries, arrays, etc.), a powerful standard library, and a familiar imperative syntax (like Python). Julia also has an efficient native C interface, and more recently a C++ interface. In addition, Julia provides an interactive console and can be used with Jupyter notebooks.

Julia was designed with high performance numerical algorithms in mind, and provides near C or Fortran performance for low-level arithmetic. This allows low-level algorithms to be implemented in Julia itself. However, for us, the main advantage of Julia is its ability to JIT compile generic algorithms for specific types, in cases where a specific implementation does not exist in C.

One of the most important features from a mathematical point of view is Julia’s parametric type system. For example, in Julia, a 1-dimensional array of integers has type Array{Int, 1}. We make heavy use of the parametric type system in Nemo and Hecke. Generic polynomials over a ring RR have type GenPoly{T} where TT is the type of elements of the ring RR. Parametric types bear some resemblance to C++ template classes, except that in Julia the type TT can be constrained to belong to a specified class of types.

2.1. Modelling domains in Julia

Julia provides two levels of types: abstract and concrete types. Concrete types are like types in Java, C or C++, etc. Abstract types can be thought of as collections of types. They are used when writing generic functions that should work for any type in a collection.

To write such a generic function, we first create an abstract type, then we create the individual concrete types that belong to that abstract type. The generic function is then specified with a type parameter, T say, belonging to the abstract type.

In Julia, the symbol ¡: is used to specify that a given type belongs to a given abstract type. For example the built-in Julia type Int64 for 64-bit machine integers belongs to the Julia abstract type Integer.

Abstract types in Julia can form a hierarchy. For example, the Nemo.Field abstract type belongs to the Nemo.Ring abstract type. An object representing a field in Nemo has type belonging to Nemo.Field. But because we define the inclusion Nemo.Field ¡: Nemo.Ring, the type of such an object also belongs to Nemo.Ring. This means that any generic function in Nemo which is designed to work with ring objects will also work with field objects.

Julia always picks the most specific function that applies to a given type. This allows one to implement a function at the most general level of a type hierarchy at which it applies. One can also write a version of a given function for specific concrete types. For example, one may wish to call a specific C implementation for a multiplication algorithm, say, when arguments with a certain very specific given type are passed.

Another way that we make use of Julia’s abstract type system in Nemo/Hecke is to distinguish between the type of elements of fields, and the fields themselves, and similarly for all other kinds of domains in Nemo.

Figure 1 shows the abstract type hierarchy in Nemo.

Refer to caption
Figure 1. The Nemo abstract type hierarchy

Naively, one may expect that specific mathematical domains in Nemo/Hecke can be modeled as types and their elements as objects of the given type. But there are various reasons why this is not a good model.

As an example, consider the ring R=/nR=\mathbb{Z}/n\mathbb{Z}. If we were to model the ring RR as a type, then the type would need to contain information about the modulus nn. This is not possible in Julia if nn is an object, e.g. a multiprecision integer. Further, Julia dispatches on type, and each time we call a generic function with values of a new type, the function is recompiled by the JIT compiler for that new type. This would result in very poor performance if we were writing a multimodular algorithm, say, as recompilation would be triggered for each distinct modulus nn. For this reason, the modulus nn needs to be attached to the elements of the ring, not to the type associated with those elements.

The way we deal with this is to have special (singleton) objects, known as parent objects, that act like types, but are in fact ordinary Julia objects. As ordinary objects, parents can contain arbitrary information, such as the modulus nn in /n\mathbb{Z}/n\mathbb{Z}. Each object representing an element of the ring then contains a pointer to this parent object.

This model of mathematical parent objects is taken from SageMath (sage, ) which in turn followed Magma (magma, ).

Julia allows ordinary objects to be made callable. We make use of the facility to write coercions and constructors for elements of mathematical domains in Nemo/Hecke. For example, the following code constructs a=3(mod7)a=3\pmod{7}.

R = ResidueRing(ZZ, 7)
a = R(3)

We also make use of the parent object system to encode information such as context objects needed by C libraries. As Julia objects can have precisely the same bit representation as native C objects, parent objects can be passed directly to C functions. Additional fields in these objects can be safely appended if it is desirable to retain more information at the Julia level than the C/C++ level.

Nemo wraps C types provided by libraries such as Flint for ground domains. For example, Nemo’s ZZ wraps Flint’s fmpz integer type. Nemo also uses specialised C implementations for nested structures where available. For instance, PolynomialRing(R, "x") which constructs the univariate polynomial ring R[x]R[x] automatically switches to a wrapper of Flint’s fmpz_poly when R=R=\mathbb{Z} instead of using the Julia implementation designed for generic RR.

2.2. In-place operations

C libraries such as Flint allow mutating objects in-place, which improves performance especially when making incremental updates to large objects. Nemo objects are ostensibly immutable, but special mutation methods are provided for use in critical spots. Instead of writing

s += a * b

in Nemo, which creates an object for a * b and then replaces s with yet another new object, we create a reusable scratch variable t with the same type as a, b and s and use

mul!(t, a, b)
addeq!(s, t)

which creates no new objects and avoids making a copy of the data in s that is not being modified.

In Julia, in-place operators save not only memory allocation and copying overheads, but also garbage collection costs, which may be substantial in the worst case.

3. Nemo examples

We present a few Nemo code examples which double as benchmark problems.

3.1. Multivariate polynomials

The Fateman benchmark tests arithmetic in [x1,,xk]\mathbb{Z}[x_{1},\ldots,x_{k}] by computing f(f+1)f\cdot(f+1) where f=(1+x+y+z+t)nf=(1+x+y+z+t)^{n}. In Nemo, this is expressed by the following Julia code:

R,x,y,z,t = PolynomialRing(ZZ, ["x","y","z","t"])
f = (1+x+y+z+t)^30
f*(f+1)

Table 1 shows timing results compared to SageMath 7.4, Magma V2.22-5 and Giac-1.2.2 on a 2.2 GHz AMD Opteron 6174. We used sparse multivariate polynomials in all cases (this benchmark could also be performed using nested dense univariate arithmetic, i.e. in [x][y][z][t]\mathbb{Z}[x][y][z][t]).

Nemo (generic) is our Julia implementation of Johnson’s sparse multiplication algorithm which handles R[x1,,xk]R[x_{1},\ldots,x_{k}] for generic coefficient rings RR. Flint (no asm) is a reimplementation of the same algorithm in C for the special case R=R=\mathbb{Z}, and Flint (asm) is an assembly optimised version of this code. Finally, Flint (array) and Giac implement an algorithm designed for dense polynomials, in which terms are accumulated into a big array covering all possible exponents.

Table 2 shows timing results on the Pearce benchmark, which consists of computing fgf\cdot g where f=(1+x+y+2z2+3t3+5u5)nf=(1+x+y+2z^{2}+3t^{3}+5u^{5})^{n}, g=(1+u+t+2z2+3y3+5x5)ng=(1+u+t+2z^{2}+3y^{3}+5x^{5})^{n}. This problem is too sparse to use the big array method, so only the Johnson algorithm is used.

Table 1. Time (s) on the Fateman benchmark.
nn Sage- Magma Nemo Flint Flint Flint Giac
Math (generic) (no asm) (asm) (array)
5 0.008 \sim0.01 0.004 0.002 0.002 0.0003 0.0002
10 0.53 0.12 0.11 0.04 0.02 0.005 0.006
15 10 1.9 1.6 0.53 0.30 0.08 0.11
20 76 16 14.3 6.3 2.8 0.53 0.62
25 426 98 82 39 17.4 2.5 2.8
30 1814 439 362 168 82 11 14

By default, Nemo uses Flint for multivariate polynomials over R=R=\mathbb{Z} instead of the generic Julia code 222The new Flint type fmpz_mpoly is presently available in the git version of Flint. The mul_johnson (with and without asm enabled) and mul_array methods were timed via Nemo.. We have timed both versions here to provide a comparison between Julia and C. It is somewhat remarkable that the generic Julia code comes within a factor two of our C version in Flint (without asm), and even runs slightly faster than the C code in Magma.

Table 2. Time (s) on the Pearce benchmark.
nn SageMath Magma Nemo Flint Flint Giac
(generic) (no asm) (asm)
4 0.01 \sim0.01 0.008 0.003 0.002 0.004
6 0.20 0.08 0.07 0.03 0.03 0.03
8 2.0 0.68 0.56 0.16 0.15 0.28
10 11 3.0 2.2 0.77 0.71 1.45
12 57 11.3 8.8 3.7 3.2 4.8
14 214 36.8 32.3 16 12 14
16 785 94.0 85.5 44 32 39

3.2. Generic resultant

The following Nemo example code computes a resultant in the ring ((GF(1711)[y])/(y3+3xy+1))[z]((\operatorname{GF}(17^{11})[y])/(y^{3}+3xy+1))[z], demonstrating generic recursive rings and polynomial arithmetic. Flint is used for univariate polynomials over a finite field.

R, x = FiniteField(17, 11, "x")
S, y = PolynomialRing(R, "y")
T = ResidueRing(S, y^3 + 3x*y + 1)
U, z = PolynomialRing(T, "z")

f = (3y^2+y+x)*z^2 + ((x+2)*y^2+x+1)*z + 4x*y + 3
g = (7y^2-y+2x+7)*z^2 + (3y^2+4x+1)*z + (2x+1)*y + 1
s = f^12
t = (s + g)^12
resultant(s, t)

This example takes 179907 s in SageMath 6.8, 82 s in Magma V2.21-4 and 0.2 s in Nemo 0.4.

3.3. Generic linear algebra

We compute the determinant of a random 80×8080\times 80 matrix with entries in a cubic number field. This benchmark tests generic linear algebra over a field with coefficient blowup. The number field arithmetic is provided by Antic.

QQx, x = PolynomialRing(QQ, "x")
K, a = NumberField(x^3 + 3*x + 1, "a")
M = MatrixSpace(K, 80, 80)()

for i in 1:80
 for j in 1:80
   for k in 0:2
     M[i, j] = M[i, j] + a^k * (rand(-100:100))
   end
 end
end

det(M)

This takes 5893 s in SageMath 6.8, 21.9 s in Pari/GP 2.7.4 (pari, ), 5.3 s in Magma V2.21-4, and 2.4 s in Nemo 0.4.

4. Generic algorithms in Nemo

Many high level algorithms in number theory and computer algebra rely on the efficient implementation of fundamental algorithms. We next discuss some of the algorithms used in Nemo for generic polynomials and matrices.

4.1. Polynomial algorithms

For generic coefficient rings, Nemo implements dense univariate polynomials, truncated power series (supporting both relative and absolute precision models), and multivariate polynomials in sparse distributed format.

The generic polynomial resultant code in Nemo uses subresultant pseudoremainder sequences. This code can even be called for polynomials over a ring with zero divisors, so long as impossible inverses are caught. The exception can be caught using a try/catch block in Julia and a fallback method called, e.g. the resultant can be computed using Sylvester matrices. This allows an algorithm with quadratic complexity to be called generically, with an algorithm of cubic complexity only called as backup.

We make use of variants of the sparse algorithms described by Monagan and Pearce for heap-based multiplication (heapmul, ), exact division and division with remainder (heapdiv, ) and powering (heappow, ). For powering of polynomials with few terms, we make use of the multinomial formula. For multivariate polynomials over \mathbb{Z}, we used the generic Julia code as a template to implement a more optimised C version in Flint.

In the case of pseudodivision for sparse, multivariate polynomials, we extract one of the variables and then use a heap-based, univariate pseudoremainder algorithm inspired by an unpublished manuscript of Monagan and Pearce.

We make use of this pseudoremainder implementation to implement the subresultant GCD algorithm. To speed the latter up, we make use of a number of tricks employed by the Giac/XCAS system (giac, ), as taught to us by Bernard Parisse. The most important of these is cheap removal of the content that accumulates during the subresultant algorithm by analysing the input polynomials to see what form the leading coefficient of the GCD can take.

We also use heuristics to determine which permutation of the variables will lead to the GCD being computed in the fastest time. This heuristic favours the variable with the lowest degree as the main variable for the computation, with the other variables following in increasing order of degree thereafter. But our heuristic heavily favours variables in which the polynomial is monic.

4.2. Matrix algorithms

For computing the determinant over a generic commutative ring, we implemented a generic algorithm making use of Clow sequences (clow, ) which uses only O(n4)O(n^{4}) ring operations in the dimension nn of the matrix. However, two other approaches seem to always outperform it. The first approach makes use of a generic fraction free LU decomposition. This algorithm may fail if an impossible inverse is encountered. However, as a fallback, we make use of a division free determinant algorithm. This computes the characteristic polynomial and then extracts the determinant from that.

For determinants of matrices over polynomial rings, we use an interpolation approach.

We implemented an algorithm for computing the characteristic polynomial due to Danilevsky (see below) and an algorithm that is based on computing the Hessenberg form. We also make use of a generic implementation of the algorithm of Berkowitz which is division free.

As is well known, fraction free algorithms often improve the performance of matrix algorithms over an integral domain. For example, fraction free Gaussian elimination for LU decomposition, inverse, determinant and reduced row echelon form are well-known.

We have also been able to use this strategy in the computation of the characteristic polynomial. We have adapted the well-known 1937 method of Danilevsky (danilevsky, ) for computing the characteristic polynomial, into a fraction-free version.

Danilevsky’s method works by applying similarity transforms to reduce the matrix to Frobenius form. Normally such computations are done over a field, however each of the outer iterations in the algorithm introduce only a single denominator. Scaling by this denominator allows us to avoid fractions. The entries become larger as the algorithm proceeds because of the scaling, but conveniently it is possible to prove that the introduced scaling factor can be removed one step later in the algorithm. This is an exact division and does not lead to denominators.

Removing such a factor has to be done in a way that respects the similarity transforms. We achieve this by making two passes over the matrix to remove the common factor.

4.2.1. Minimal polynomial

Next we describe an algorithm we have implemented for efficient computation of the minimal polynomial of an n×nn\times n integer matrix MM. A standard approach to this problem is known as “spinning” (steel, ). We provide a brief summary of the main ideas, to fix notation and then describe our contribution, which is a variant making use of Chinese remaindering in a provably correct way.

We first summarise the theory for matrices over a field KK. The theory relies on the following result, e.g. (vinberg, ).

Theorem 4.1.

Suppose MM is a linear operator on a KK-vector space VV, and that V=W1+W2++WnV=W_{1}+W_{2}+\cdots+W_{n} for invariant subspaces WiW_{i}. Then the minimal polynomial of MM is LCM(m1,m2,,mn)(m_{1},m_{2},\ldots,m_{n}), where mim_{i} is the minimal polynomial of MM restricted to WiW_{i}.

The subspaces WiW_{i} we have in mind are the following.

Definition 4.2.

Given a vector vv in a vector space VV the Krylov subspace K(V,v)K(V,v) associated to vv is the linear subspace spanned by {v,Mv,M2v,}\{v,Mv,M^{2}v,\ldots\}.

Krylov subspaces are invariant subspaces and so these results lead to an algorithm for computing the minimal polynomial as follows.

Start with v1=e1=(1,0,,0)v_{1}=e_{1}=(1,0,\ldots,0) and let W1=K(V,v1)W_{1}=K(V,v_{1}). For each i>1i>1 let viv_{i} be the first standard basis vector eje_{j} that is linearly independent of W1+W2++Wi1W_{1}+W_{2}+\cdots+W_{i-1}. Set Wi=K(V,vi)W_{i}=K(V,v_{i}). By construction, V=W1+W2++WnV=W_{1}+W_{2}+\cdots+W_{n}, and the minimal polynomial of MM is the least common multiple of the minimal polynomials mim_{i} of MM restricted to the WiW_{i}.

The minimal polynomials mim_{i} are easy to compute. For, if vv, MvMv, M2v,,Md1vM^{2}v,\ldots,M^{d-1}v is a basis for WiW_{i}, there is a linear relation

Mdv+cd1Md1v++c1Mv+c0v=0,M^{d}v+c_{d-1}M^{d-1}v+\cdots+c_{1}Mv+c_{0}v=0,

for some ciKc_{i}\in K, and no smaller such relation. Letting

mi(T)=c0+c1T++Td,m_{i}(T)=c_{0}+c_{1}T+\cdots+T^{d},

we have mi(M)(v)=0m_{i}(M)(v)=0. One sees that mi(M)(f(M)v)=0m_{i}(M)(f(M)v)=0 for all polynomials f(T)K[T]f(T)\in K[T] and so mi(M)m_{i}(M) annihilates all of WiW_{i}. Thus mim_{i} is the required minimal polynomial.

To efficiently implement this algorithm, we keep track of and (incrementally) reduce a matrix BB whose rows consist of all the linearly independent vectors from the Krylov sequences computed so far. Any column without a pivot in this reduced matrix BB corresponds to a standard basis vector independent of the Krylov subspaces computed so far. As each new Krylov subspace is computed, we append the corresponding vectors to the matrix BB, and reduce them.

It is also possible to define the minimal polynomial of a matrix MM over \mathbb{Z}, since the null ideal ND(M)={f(T)D[T]|f(M)=0}N_{D}(M)=\{f(T)\in D[T]\;|\;f(M)=0\} of a matrix MM over an integrally closed domain DD is principal (brown, ).

In the case D=D=\mathbb{Z}, we can define the minimal polynomial m(T)m(T) of MM to be a (primitive) generator of this ideal. We have that m(T)m(T) is monic since the characteristic polynomial of MM is monic and an element of the null ideal. By Gauss’ Lemma for polynomials, this argument also shows that the minimal polynomial of MM is the same as that of MM considered as a matrix over \mathbb{Q}.

We have been informed by personal communication that the algorithm used by Magma (magma, ) for minimal polynomial computation over \mathbb{Z} uses spinning, however it uses a pp-adic approach. Unfortunately we have been unable to find a publication outlining their method.

We describe a multimodular variant of the spinning approach. The idea is to reduce the matrix MM modulo many small primes pp and apply the method described above over the field /p\mathbb{Z}/p\mathbb{Z}, for each prime pp. We then combine the minimal polynomials modulo the various primes pp using Chinese remaindering.

The minimal polynomial of the reduction M(p)M_{(p)} of MM modulo pp is the reduction modulo pp of the minimal polynomial m(T)m(T) of MM for all but finitely many “bad” primes (see (giesbrecht, ) Lemma 2.3). Bad primes are detected if the degrees of the minimal polynomials modulo pp change at any point during the algorithm.

Whilst bounds on the number of primes required in the Chinese remaindering exist, e.g. based on Ovals of Cassini, these bounds are typically extremely pessimistic. It is also unfortunately too expensive to simply evaluate the minimal polynomial m(T)m(T) at the matrix MM and compare with zero.

We obtain a better termination criterion by allowing a small amount of information to ’leak’ from the modulo pp minimal polynomial computations. Namely, for one of the (good) primes pp, we record which standard basis vectors viv_{i} were used to generate the Krylov subspaces WiW_{i} when computing the minimal polynomial of M(p)M_{(p)}. Recall that V=W1+W2++WnV=W_{1}+W_{2}+\cdots+W_{n}, where M(p)M_{(p)} is thought of as a linear operator on VV.

Thinking of MM as a linear operator on a \mathbb{Q}-vector space VV^{\prime}, and letting Wi=K(V,vi)W^{\prime}_{i}=K(V^{\prime},v^{\prime}_{i}), where viv^{\prime}_{i} is the lift of viv_{i} to \mathbb{Q}, it is clear that V=W1+W2++WnV^{\prime}=W^{\prime}_{1}+W^{\prime}_{2}+\cdots+W^{\prime}_{n}.

Thus, if m(T)m(T) is believed to be the minimal polynomial of MM, e.g. because the Chinese remaindering has stabilised, then if m(M)vi=0m(M)v^{\prime}_{i}=0 for all ii, then m(T)m(T) is the minimal polynomial of MM over \mathbb{Q}. This follows because if m(M)vi=0m(M)v^{\prime}_{i}=0 then m(M)m(M) annihilates all of WiW^{\prime}_{i} for each ii. Thus it annihilates all of VV^{\prime}.

The cost of computing the m(M)vim(M)v^{\prime}_{i} is usually low compared to computing m(M)m(M) directly, since it consists of matrix-vector rather than matrix-matrix multiplications.

In the worst case this algorithm requires O(n4)O(n^{4}) operations over \mathbb{Z} (once we encounter a good prime), but this is far from the generic case, even when the minimal polynomial is not the characteristic polynomial.

In practice our multimodular algorithm seems to slightly outperform Magma on the examples we have tried, including matrices with minimal polynomial smaller than the characteristic polynomial. A generic version of the algorithm over fields is implemented in Julia code in Nemo, and an efficient version over \mathbb{Z} using the Chinese remaindering trick is implemented in Flint and made available in Nemo.

5. C/C++ wrappers in Nemo

5.1. Flint: arithmetic and number theory

Flint provides arithmetic over \mathbb{Z}, \mathbb{Q}, /n\mathbb{Z}/n\mathbb{Z}, GF(q),p\operatorname{GF}(q),\mathbb{Q}_{p} as well as matrices, polynomials and power series over most of these ground rings. Nemo implements elements of these rings as thin wrappers of the Flint C types.

Flint uses a number of specialised techniques for each domain. For example, Flint’s multiplication in [x]\mathbb{Z}[x] uses a best-of-breed algorithm which selects classical multiplication, Karatsuba multiplication, Kronecker segmentation or a Schönhage-Strassen FFT, with accurate cutoffs between algorithms, depending on the degrees and coefficient sizes.

In some cases, Flint provides separate implementations for word-size and arbitrary-size coefficients. Nemo wraps both versions and transparently selects the optimised word-size version when possible.

Additional Flint algorithms wrapped by Nemo include primality testing, polynomial factorisation, LLL, and Smith and Hermite normal forms of integer matrices.

5.2. Arb: arbitrary precision ball arithmetic

Computing over \mathbb{R} and \mathbb{C} requires using some approximate model for these fields. The most common model is floating-point arithmetic. However, for many computer algebra algorithms, the error analysis necessary to guarantee correct results with floating-point arithmetic becomes impractical. Interval arithmetic solves this problem by effectively making error analysis automatic.

Nemo includes wrapper code for the C library Arb, which implements real numbers as arbitrary-precision midpoint-radius intervals (balls) [m±r][m\pm r] and complex numbers as rectangular boxes [a±r1][a\pm r_{1}] + [b±r2]i[b\pm r_{2}]i. Nemo stores the precision in the parent object. For example, R = ArbField(53) creates a field of Arb real numbers with 53-bit precision. Arb also supplies types for polynomials, power series and matrices over \mathbb{R} and \mathbb{C}, as well as transcendental functions. Like Flint, Arb systematically uses asymptotically fast algorithms for operations such as polynomial multiplication, with tuning for different problem sizes.

Many problems can be solved using lazy evaluation: the user can try a computation with some tentative precision pp and restart with precision 2p2p if that fails. The precision can be set optimally when a good estimate for the minimal required pp is available; that is, the intervals can be used as if they were plain floating-point numbers, and the automatic error bounds simply provide a certificate.

Alternative implementations of \mathbb{R} and \mathbb{C} may be added to Nemo in the future. For example, it would sometimes be more convenient to use a lazy bit stream abstraction in which individual numbers store a symbolic DAG representation to allow automatically increasing their own precision.

5.3. Antic: algebraic number fields

Antic is a C library, depending on Flint, for doing efficient computations in algebraic number fields. Nemo uses Antic for number field element arithmetic. We briefly describe some of the techniques Antic uses for fast arithmetic, but refer the reader to the article (antic, ) for full details.

Antic represents number field elements as Flint polynomials thereby benefiting from the highly optimised polynomial arithmetic in Flint. However, a few more tricks are used.

Firstly, Antic makes a number of precomputations which are stored in a context object to speed up subsequent computations with number field elements. Number field parent objects in Nemo consist precisely of these context objects.

The first is a precomputed inverse of the leading coefficient of the defining polynomial of the number field. This helps speed up reduction modulo the defining polynomial.

The second is an array of precomputed powers xix^{i} modulo the defining polynomial f(x)f(x). This allows fast reduction of polynomials whose degree exceeds that of the defining polynomial, e.g. in multiplication of number field elements gg and hh where one wants wants to compute g(x)h(x)(modf(x))g(x)h(x)\pmod{f(x)}.

The third precomputation is of Newton sums Sk=i=1nθikS_{k}=\sum_{i=1}^{n}\theta_{i}^{k} where the θi\theta_{i} are the roots of the defining polynomial f(x)f(x). These Newton sums are precomputed using recursive formulae as described in (Cohen1993, ). They are used to speed up the computation of traces of elements of the number field.

Norms of elements of number fields are computed using resultants, for which we use the fast polynomial resultant code in Flint. Inverses of elements are computed using the fast polynomial extended GCD implementation in Flint.

Antic also offers the ability to do multiplication of number field elements without reduction. This is useful for speeding up the dot products that occur in matrix multiplication, for example. Instead of reducing after every multiplication, the unreduced products are first accumulated and their sum can be reduced at the end of the dot product.

To facilitate delayed reduction, all Antic number field elements are allocated with sufficient space to store a full polynomial product, rather than the reduction of such a product.

5.4. Singular: commutative algebra

Singular (singular, ) is a C/C++ package for polynomial systems and algebraic geometry. Recently, we helped prepare a Julia package called Singular.jl, which is compatible with Nemo. It will be described in a future article.

6. Hecke: algebraic number theory in Julia

Hecke is a tool for algebraic number theory, written in Julia. Hecke includes the following functionality:

  • element and ideal arithmetic in orders of number fields,

  • class group and unit group computation,

  • verified computations with embeddings and

  • verified residue computation of Dedekind zeta functions.

Hecke is written purely in Julia, though it makes use of Flint, Arb and Antic through the interface provided by Nemo. Hecke also applies the generic constructions and algorithms provided by Nemo to its own types. This allows for example to work efficiently with polynomials or matrices over rings of integers without the necessity to define special types for these objects.

For most computational challenges we rely on well known techniques as described in (Cohen1993, ; Pohst1997, ; Belabas2004, ) (with a few modifications). However, we use new strategies for ideal arithmetic and computations with approximations.

6.1. Fast ideal arithmetic in rings of integers

A classical result is that in Dedekind domains, as for the ring of integers of a number field, any ideal can be generated using only two elements, one of which can be chosen at random in the ideal. This representation is efficient in terms of space compared to storing a \mathbb{Z}-basis. However, the key problem is the lack of efficient algorithms for working with two generators. We remark that one operation is always efficient using two generators: powering of ideals.

A refinement of this idea, due to Pohst and Zassenhaus (Pohst1997, , p. 400), is a normal presentation. Here a finite set SS of prime numbers is fixed, typically containing at least all prime divisors of the norm of the ideal. Then, based on this set, the two generators are chosen: The first as an integer having only divisors in SS—but typically with too high multiplicities. The second generator then has the correct multiplicity at all prime ideals over primes in SS—but is allowed to have other divisors as well.

For the remainder of this section, let K/K/\mathbb{Q} be a number field of degree nn and 𝒪K\mathcal{O}_{K} be its ring of integers.

Definition 6.1.

Let SS be a finite set of prime numbers and AA a nonzero ideal such that all primes pp dividing the norm N(A)=|𝒪K/A|N(A)=|\mathcal{O}_{K}/A| are in SS. A tuple (a,α)(a,\alpha) is an SS-normal presentation for AA if and only if

  • we have aAa\in A\cap\mathbb{Z}, αA\alpha\in A,

  • for all prime ideals QQ over pSp\not\in S we have vQ(a)=vQ(A)=0v_{Q}(a)=v_{Q}(A)=0 for the exponential valuation vQv_{Q} associated to QQ,

  • for all prime ideals QQ over pSp\in S we have vQ(α)=vQ(A)v_{Q}(\alpha)=v_{Q}(A).

A direct application of the Chinese remainder theorem shows the existence of such normal presentations. The algorithmic importance comes from the following result.

Theorem 6.2.

Let A=a,αA=\langle a,\alpha\rangle be an ideal in SS-normal presentation. Then the following hold:

  1. (1)

    We have N(A)=gcd(an,N(α))N(A)=\gcd(a^{n},N(\alpha)).

  2. (2)

    If B=b,βB=\langle b,\beta\rangle is a second ideal in SS-normal presentation for the same set SS, then AB=ab,αβAB=\langle ab,\alpha\beta\rangle is also a SS-normal presentation.

  3. (3)

    Let γ=1/α\gamma=1/\alpha, g=α𝒪K\langle g\rangle=\alpha\mathcal{O}_{K}\cap\mathbb{Z}, g=g1g2g=g_{1}g_{2} with coprime g1g_{1}, g2g_{2} and g2g_{2} only divisible by primes not in SS. Then A1=1,g2γA^{-1}=\langle 1,g_{2}\gamma\rangle is an SS-normal presentation.

  4. (4)

    If pp is a prime number and P=p,θP=\langle p,\theta\rangle a prime ideal over pp in {p}\{p\}-normal presentation, then the {p}\{p\}-normal presentation of P1=1,γP^{-1}=\langle 1,\gamma\rangle yields a valuation element, that is, vP(β)=max{kγkβ𝒪K}v_{P}(\beta)=\max\{k\mid\gamma^{k}\beta\in\mathcal{O}_{K}\} for all β𝒪K\beta\in\mathcal{O}_{K}.

It should be noted that (almost all) prime ideal are naturally computed in {p}\{p\}-normal presentation. The key problem in using the theorem for multiplication is that both ideals need to be given by an SS-normal presentation for the same set SS, which traditionally is achieved by recomputing a suitable presentation from scratch, taking random candidates for the second generator until it works. Based on the following lemma, we have developed a new algorithm that manages it at the cost of a few integer operations only.

Lemma 6.3.

Let SS be a finite set of prime numbers and A=a,αA=\langle a,\alpha\rangle an ideal in SS-normal presentation. Let TT be a second set of primes and set s=xSxs=\prod_{x\in S}x as well as t=xTSxt=\prod_{x\in T\setminus S}x. If 1=gcd(as,t)=uas+vt1=\gcd(as,t)=uas+vt, then a,β\langle a,\beta\rangle is an STS\cup T-normal presentation, where β=vtα+uas\beta=vt\alpha+uas.

Proof.

By definition, aa has only prime divisors in SS, so asas and tt are coprime. Also, aa is a possible first generator for the STS\cup T-normal presentation. Let PP be a prime ideal over a prime pSp\in S, then vP(β)=vP(α)=vP(A)v_{P}(\beta)=v_{P}(\alpha)=v_{P}(A) since vP(A)<vP(s)+vP(a)v_{P}(A)<v_{P}(s)+v_{P}(a) due to vP(s)1v_{P}(s)\geq 1 and vP(a)vP(A)v_{P}(a)\geq v_{P}(A) by definition. For PP coming from TST\setminus S, we have vP(β)=0v_{P}(\beta)=0 since vP(t)1v_{P}(t)\geq 1 and vP(uas)=0v_{P}(uas)=0 as well. ∎

Using this lemma on both input ideals, we can obtain compatible SS-normal presentations at the cost of two gcd\gcd computations, a few integer multiplications and two products of integers with algebraic numbers. The final ideal multiplication is then a single integer product and a product of algebraic numbers. Thus the asymptotic cost is that of a single multiplication of two algebraic numbers. In contrast, all previous algorithms require a linear algebra step.

Finally, we improve on the computation of an SS-normal presentation.

Lemma 6.4.

If 0α𝒪K0\neq\alpha\in\mathcal{O}_{K}, then α=d\langle\alpha\rangle\cap\mathbb{Z}=\langle d\rangle where d>0d>0 is minimal such that d/α𝒪Kd/\alpha\in\mathcal{O}_{K}.

Theorem 6.5 ((Pohst1997, )).

Let AA be a nonzero ideal. Then the tuple (a,α)(a,\alpha) is an {p|p divides a}\{p\,|\,p\text{ divides }a\}-normal presentation of AA if and only if gcd(a,d/gcd(a,d))=1\gcd(a,{d}/{\gcd(a,d)})=1, where d=min(α𝒪K)d=\min(\alpha\mathcal{O}_{K}\cap\mathbb{N}).

Together with the above lemma, this allows for the rapid computation of a normal presentation: Choose αA\alpha\in A at random until a normal presentation is obtained. It can be shown that, unless aa involves many ideals of small norm, this is very efficient.

To illustrate the speed of our algorithm, we created a list of ideals of bounded norm (here: 400) and took random products of 100 ideals in the field defined by the polynomial Xn+2X^{n}+2 for n=16,32,64,128n=16,32,64,128. The results are presented in Table 3. Times are given in seconds.

Table 3. Time (s) on ideal multiplication.
nn Magma Pari Hecke
16 8.44 0.05 0.02
32 235.82 0.18 0.04
64 905.44 0.96 0.06
128 7572.19 5.40 0.08

6.2. The use of interval arithmetic

One is often forced to study algebraic number fields in a real or complex setting, e.g. embeddings into an algebraically closed field, or computing Dedekind zeta functions. This can be due to an intrinsic property of the problem, or it may be the fastest (known) way to solve the problem. Either way, the price is working with approximations. We give a few examples of this and show how the ball arithmetic provided by Arb is employed in Hecke for this purpose.

6.2.1. Computing conjugates

Let K=[X]/(f)K=\mathbb{Q}[X]/(f) be an algebraic number field, where f[X]f\in\mathbb{Q}[X] is an irreducible polynomial of degree dd. We represent the elements of KK as polynomials of degree less than dd. Denoting by α1,,αd\alpha_{1},\dotsc,\alpha_{d}\in\mathbb{C} the roots of ff in \mathbb{C}, the distinct embeddings KK\to\mathbb{C} are given by σi:K,X¯αi\sigma_{i}\colon K\to\mathbb{C},\bar{X}\mapsto\alpha_{i}. For an element α\alpha of KK the complex numbers σi(α)\sigma_{i}(\alpha), 1id1\leq i\leq d are called the conjugates of α\alpha. Since for αK\alpha\in K\setminus\mathbb{Q} the conjugates are irrational, it is clear that we must rely on approximations.

In Hecke this is done using ball arithmetic. Let α=ajXj\alpha=\sum a_{j}X^{j} be an element of KK. Assume that we want to find σ^i(α)\hat{\sigma}_{i}(\alpha)\in\mathbb{R} with |σi(α)σ^i(α)|2p\lvert\sigma_{i}(\alpha)-\hat{\sigma}_{i}(\alpha)\rvert\leq 2^{-p} for some precision p1p\in\mathbb{Z}_{\geq 1}. Using Arb’s polynomial root finding functionality and some initial precision ppp^{\prime}\geq p we find balls Bp(i)\smash{B_{p^{\prime}}^{(i)}\subseteq\mathbb{R}} such that αiBp(i)\smash{\alpha_{i}\in B_{p^{\prime}}^{(i)}} and diam(Bp(i))2p\smash{\operatorname{diam}(B_{p^{\prime}}^{(i)})\leq 2^{-p^{\prime}}}. Ball arithmetic then yields balls Bα(i)B_{\alpha}^{(i)}\subseteq\mathbb{R} with

σi(α)=1jdajαij1jdaj(Bp(i))jBα(i).\sigma_{i}(\alpha)=\sum_{1\leq j\leq d}a_{j}\alpha_{i}^{j}\in\sum_{1\leq j\leq d}a_{j}(B_{p^{\prime}}^{(i)})^{j}\subseteq B_{\alpha}^{(i)}.

Finally we check whether diam(Bα(i))2p\smash{\operatorname{diam}(B_{\alpha}^{(i)})\leq 2^{-p}}. If not, we increase the working precision pp^{\prime} and repeat. When finished, the midpoints of the balls Bα(i)\smash{B_{\alpha}^{(i)}} are approximations σ^i(α)\hat{\sigma}_{i}(\alpha) with the desired property. The following Hecke code illustrates this.

QQx, x = PolynomialRing(QQ, "x")
K, a = NumberField(x^3 + 3*x + 1, "a")
alpha = a^2 + a + 1
p = 128; p’ = p

while true
  CCy, y = PolynomialRing(AcbField(p’), "y")
  g = CCy(x^3 + 3*x + 1)
  rts = roots(g)
  z = map(x^2 + x + 1, rts)
  if all([ radiuslttwopower(u, -p) for u in z])
    break
  else
    p’ = 2*p’
  end
end

To perform the same task using floating point arithmetic would require a priori error analysis. Arb’s ball arithmetic allows the error analysis to be carried along with the computation. This allows for fast development, while at the same time maintaining guaranteed results.

6.2.2. Torsion units

For an algebraic number field KK of degree dd, the set {αK×n1:αn=1}\{\alpha\in K^{\times}\mid\exists n\in\mathbb{Z}_{\geq 1}\colon\alpha^{n}=1\} of torsion units is a finite cyclic subgroup of K×K^{\times}, which plays an important role when investigating the unit group of the ring of integers of KK.

Given αK×\alpha\in K^{\times} it is important to quickly check if it is a torsion unit. A list of possible integers nn with αn=1\alpha^{n}=1 can be obtained as follows: If α\alpha is a torsion unit, then α\alpha is a primitive kk-th root of unity for some kk. In particular KK contains the kk-th cyclotomic field and φ(k)\varphi(k) divides nn. Since there are only finitely many kk with the property that φ(k)\varphi(k) divides nn, for every such kk we can test αk=1\alpha^{k}=1 and in this way decide whether α\alpha is a torsion unit. While this works well for small nn, for large nn this quickly becomes inefficient.

A second approach rests on the fact that torsion units are characterized by their conjugates: An element α\alpha is a torsion unit if and only if |σi(α)|=1\lvert\sigma_{i}(\alpha)\rvert=1 for 1id1\leq i\leq d. Although we can compute approximations of conjugates with arbitrary precision, since conjugates are in general irrational, it is not possible test whether they (or their absolute value) are exactly equal to 11.

We make use of the following result of Dobrowolski (Dobrowolski1978, ), which bounds the modulus of conjugates of non-torsion units.

Lemma 6.6.

If α\alpha is not a torsion unit, then there exists some 1ik1\leq i\leq k such that

(1+16log(d)d2)<|σk(α)|.\left(1+\frac{1}{6}\frac{\log(d)}{d^{2}}\right)<\lvert\sigma_{k}(\alpha)\rvert.

We can rephrase this using approximation as follows.

Lemma 6.7.

Let (Bk(i))k1(\!B^{(i)}_{k})_{k\geq 1}, 1id\!1\!\!\leq\!i\!\!\leq\!\!d, be sequences of real balls with |σi(α)|Bk(i)\lvert\sigma_{i}(\alpha)\rvert\in B^{(i)}_{k} and maxidiam(Bk(i))0\max_{i}\operatorname{diam}(B_{k}^{(i)})\to 0 for kk\to\infty.

  1. (1)

    If the element α\alpha is torsion, there exists k1k\geq 1 such that 1<Bk(i)\smash{1<B_{k}^{(i)}} for some 1id1\leq i\leq d.

  2. (2)

    If the element α\alpha is non-torsion, there exists k1k\geq 1 such that Bk(i)<1+log(d)/(6d2)\smash{B_{k}^{(i)}<1+\log(d)/(6d^{2})} for all 1id1\leq i\leq d.

It is straightforward to turn this into an algorithm to test whether α\alpha is torsion: Approximate the conjugates of α\alpha using balls as in 6.2.1 for some starting precision p1p\geq 1 and check if one of the statements of Lemma 6.7 hold. If not, increase the precision and start over. Since the radii of the balls tend to 0, by Lemma 6.7 the algorithm will eventually terminate.

6.2.3. Residues of Dedekind zeta functions

For a number field KK and ss\in\mathbb{C}, Re(s)>1\operatorname{Re}(s)>1, the Dedekind zeta function of KK is defined as

ζK(s)={0}I𝒪K1N(I)s,\zeta_{K}(s)=\sum_{\{0\}\neq I\subseteq\mathcal{O}_{K}}\frac{1}{N(I)^{s}},

where the sum is over all nonzero ideals of the ring of integers 𝒪K\mathcal{O}_{K} of KK. This function has an analytic continuation to the whole complex plane with a simple pole at s=1s=1. The analytic class number formula (Cohen1993, ) states that the residue at that pole encodes important arithmetic data of KK:

Res(ζK,1)=lims1(s1)ζK(s)=hKregKcK,\operatorname{Res}(\zeta_{K},1)=\lim_{s\to 1}(s-1)\zeta_{K}(s)=h_{K}\cdot\mathrm{reg}_{K}\cdot c_{K},

where hKh_{K} is the class number, regK\mathrm{reg}_{K} is the regulator and cKc_{K} is another (easy to determine) constant depending on KK.

The analytic class number formula is an important tool during class group computations. By approximating the residue, it allows one to certify that tentative class and unit groups are in fact correct (see (Biasse2014, )).

We describe the approximation under GRH using an algorithm of Belabas and Friedmann (Belabas2015, ), but it is also possible with results from Schoof (Schoof1982, ) or Bach (Bach1995, ). Since the aim is to illustrate the use of ball arithmetic, we will not give detailed formulas for the approximation or the error.

Theorem 6.8 (Belabas-Friedmann).

There exist functions gKg_{K}, εK:\varepsilon_{K}\colon\mathbb{R}\to\mathbb{R}, with

|log(Res(ζK,1))gK(x)|εK(x)\lvert\log(\operatorname{Res}(\zeta_{K},1))-g_{K}(x)\rvert\leq\varepsilon_{K}(x)

for all x69x\geq 69. The evaluation of gK(x)g_{K}(x) involves only prime powers pmxp^{m}\leq x and prime ideal powers 𝔭m\mathfrak{p}^{m} with N(𝔭m)xN(\mathfrak{p}^{m})\leq x. Moreover the function εK\varepsilon_{K} is strictly decreasing and depends only on the degree and the discriminant of KK.

Assume that ε>0\varepsilon>0 and we want to find zz\in\mathbb{R} such that

|log(Res(ζK,1))z|ε.\lvert\log(\operatorname{Res}(\zeta_{K},1))-z\rvert\leq\varepsilon.

We first compute an x0x_{0} such that εK(x0)<ε/2\varepsilon_{K}(x_{0})<\varepsilon/2. Note that since εK\varepsilon_{K} is strictly decreasing, this can be done using ordinary floating point arithmetic with appropriate rounding modes. Once we have this value we compute a real ball BB with gK(x0)Bg_{K}(x_{0})\in B and diam(B)ε/2\operatorname{diam}(B)\leq\varepsilon/2 (as usual we progressively increase precision until the radius of the ball is small enough). By the choice of x0x_{0}, the midpoint of BB is an approximation to the logarithm of the residue with error at most ε\varepsilon. Again this yields a guaranteed result, but avoids the tedious error analysis due to the form of gKg_{K}.

7. Acknowledgments

This work was supported by DFG priority project SPP 1489, DFG SFB-TRR 195 and ERC Starting Grant ANTICS 278537.

References

  • (1) E. Bach, Improved approximations for Euler products, Number theory (Halifax, NS, 1994), vol. 15, CMS Conference Proceedings, pages 13–28. Amer. Math. Soc., Providence, RI, 1995.
  • (2) K. Belabas, Topics in computational algebraic number theory, J. Théor. Nombres Bordeaux, 16(1):19–63, 2004.
  • (3) K. Belabas, E. Friedman. Computing the residue of the Dedekind zeta function, Math. Comp., 84(291):357–369, 2015.
  • (4) J. Bezanson, A. Edelman, S. Karpinski, V. B. Shah, Julia: A fresh approach to numerical computing, https://arxiv.org/abs/1411.1607
  • (5) J.-F. Biasse, C. Fieker, Subexponential class group and unit group computation in large degree number fields, LMS J. Comput. Math., 17(suppl. A):385–403, 2014.
  • (6) W. Bosma, J. J. Cannon, C. Fieker, A. Steel (Eds.), Handbook of Magma Functions, Edition 2.22 (2016).
  • (7) W. Brown, Null ideals and spanning ranks of matrices, Comm. Algebra 26, (1998), pp. 2401–2417.
  • (8) H. Cohen, A course in computational algebraic number theory, Graduate Texts in Mathematics, vol. 138, Springer-Verlag, Berlin, 1993.
  • (9) A. Danilevsky, The numerical solution of the secular equation, (Russian), Matem. Sbornik, 44(2), 1937. pp. 169–171.
  • (10) W. Decker, G. M. Greuel, G. Pfister and H. Schönemann, Singular – A computer algebra system for polynomial computations, http://www.singular.uni-kl.de
  • (11) E. Dobrowolski, On the maximal modulus of conjugates of an algebraic integer, Bull. Acad. Polon. Sci. Sér. Sci. Math. Astronom. Phys., 26(4):291–292, 1978.
  • (12) L. Fousse, G. Hanrot, V. Lefèvre, P. Pélissier, P. Zimmermann, MPFR: A multiple-precision binary floating-point library with correct rounding, ACM Trans. Math. Soft., vol. 33, (2), 13, (2007), pp. 1–15.
  • (13) J. von zur Gathen, J. Gerhard, Modern Computer Algebra, Cambridge University Press, 1999.
  • (14) M. Giesbrecht, A. Storjohann, Computing rational forms of integer matrices. Journal of Symbolic Computation, 2002, v. 34(3), pp. 157–172.
  • (15) T. Granlund and the GMP development team, The GNU Multi Precision Arithmetic Library, http://gmplib.org/
  • (16) W. Hart, ANTIC: Algebraic Number Theory in C, Computeralgebra Rundbrief 56, Fachgruppe Computeralgebra, 2015, http://www.fachgruppe-computeralgebra.de/data/CA-Rundbrief/car56.pdf
  • (17) W. Hart, F. Johansson, S. Pancratz, FLINT: Fast Library for Number Theory, http://www.flintlib.org
  • (18) F. Johansson, Arb: A C library for ball arithmetic, http://arblib.org
  • (19) M. Monagan, R. Pearce, Sparse polynomial division using a heap, Journal of Symbolic Computation, vol. 46, (7), July 2011, pp. 807–822.
  • (20) M. Monagan, R. Pearce, Parallel sparse polynomial multiplication using heaps, ISSAC’09, ACM Press (2009), pp. 263–269.
  • (21) M. Monagan, R. Pearce, Sparse polynomial powering using heaps, Gerdt V.P., Koepf W., Mayr E.W., Vorozhtsov E.V. (eds) Computer Algebra in Scientific Computing. CASC 2012. Lecture Notes in Computer Science, vol 7442. Springer, Berlin, Heidelberg (2012), pp. 236–247.
  • (22) PARI Group, PARI/GP version 2.9.0, Univ. Bordeaux, 2016, http://pari.math.u-bordeaux.fr/
  • (23) B. Parisse, R. De Graeve, Giac/Xcas version 1.2.2, 2016, http://www-fourier.ujf-grenoble.fr/~parisse/giac.html
  • (24) M. Pohst, H. Zassenhaus, Algorithmic algebraic number theory, Cambridge University Press, Cambridge, 1997.
  • (25) Sage Developers, SageMath, the Sage Mathematics Software System (Version 7.4), 2016, http://www.sagemath.org
  • (26) R. J. Schoof, Quadratic fields and factorization, In Computational methods in number theory, Part II, volume 155 of Math. Centre Tracts, pages 235–286. Math. Centrum, Amsterdam, 1982.
  • (27) M. Soltys, Berkowitz’s algorithm and clow sequences, The Electronic Journal of Linear Algebra, Int. Linear Algebra Society, vol 9, Apr. 2002, pp. 42–54.
  • (28) A. Steel, A new algorithm for the computation of canonical forms of matrices over fields, J. Symbolic Computation (1997) 24, pp. 409–432.
  • (29) E. Vinberg, A Course in Algebra, American Mathematical Society, 2003, pp. 229.