LACE for Maple
The Maple package LACE computes, with certified results:
- the real roots of univariate polynomials, isolated in intervals, with constraints or through a parameterization;
- the Rational Univariate Representation (RUR) and the real solutions of zero-dimensional polynomial systems;
- the resultant, the decomposition and the RUR of bivariate systems.
Its functions are exported by the module LACE, which is also available
under the names RealSolving and RS4, so that code written for those
packages runs unchanged. Maple’s own fgbrs package, used by
RootFinding, is left untouched.
Installation
Download the archive
LACE-Maple2026-Darwin_arm64.tar.gz,
for Maple 2026 on macOS 12 or later, Apple silicon; other platforms and
versions of Maple are not available yet. It holds a directory LACE with
README.md and lib/ (LACE.mla, the Maple code and its help pages
LACE.help, and liblace_mpl.so, the compiled library). It needs nothing
but Maple: the arithmetic libraries GMP, MPFR and MPFI (LGPL) are included.
Quit Maple, then install the package as a Maple toolbox, in a terminal, from the directory that holds the archive:
mkdir -p ~/maple/toolbox/2026
tar -C ~/maple/toolbox/2026 -xzf LACE-Maple2026-Darwin_arm64.tar.gz
xattr -cr ~/maple/toolbox/2026/LACE
xattr removes the quarantine that macOS puts on files received from the
Internet or by AirDrop; without it, Maple may be refused the library.
Maple adds ~/maple/toolbox/2026/LACE/lib to libname, and reads the
help pages, when it starts: start Maple and check:
LACE:-library(); # the file .../maple/toolbox/2026/LACE/lib/liblace_mpl.so
LACE:-backend(); # "native"
?LACE
To update, extract the new archive over the old one, in a closed Maple
session. To uninstall, remove the directory ~/maple/toolbox/2026/LACE.
The archive must match the Maple that loads it: an archive for Maple 2026 does not load in another version of Maple.
Troubleshooting
LACE:-library()returns only a file name, or the first call fails with an error about an external library: the toolbox is not onlibname. Checklibnamein a new session and the location of the directoryLACE(the year in the path must be the version of Maple).- The library is found but refused: remove the quarantine with
xattr -cras above. ?LACEfinds nothing: check thatLACE.helpis in~/maple/toolbox/2026/LACE/lib, then quit Maple entirely and start it again; Maple reads the help databases when it starts.- The library can be taken elsewhere:
LACEBinDir := "/some/dir":before the first call, or the environment variableMAPLELDPATH, take precedence over the toolbox. - Call
LACE:-finish()beforequitin scripts: Maple does not always release the objects held by the library at exit.
First steps
f := x^3 - 7*x + 6:
LACE:-rs_isolate_uni_classical(f, precision = 64); # the roots -3, 1, 2 in intervals
LACE:-rur([x^2 + y^2 - 5, x - y - 1], [x, y]); # a RUR of the system
LACE:-real_solutions([x^2 + y^2 - 5, x - y - 1], [x, y]); # its two real solutions
with(LACE): # the short names
Long computations can be interrupted as usual in Maple.
Conventions
- Polynomials are ordinary Maple polynomials with rational coefficients; each one is scaled to integer coefficients by a positive factor before it is passed to the library.
- An interval is a list
[lo, hi]of rationals. Two equal bounds denote an exact value; otherwise the open interval contains exactly one root, neither bound being a root. - Intervals are returned in increasing order of the root (of the eliminant, for systems).
precisionis a number of significant bits (a relative precision), except fordescartes_isolate, where it is an absolute width.threadsis the number of workers; 0, the default, takes the library’s default:ACE_NB_THREADS(read when the library is loaded), elseOMP_NUM_THREADS, else the performance cores of the machine.- Errors of the library are raised as Maple errors.
Module functions
| function | effect |
|---|---|
LACE:-backend() |
the arithmetic the library was built with ("native", "flint") |
LACE:-library() |
the file the external functions are bound to |
LACE:-set_verbose(v) |
0: silent; 1: a summary of each computation; 2: details |
LACE:-reinit() |
initialises the library if needed |
LACE:-finish() |
releases the objects held by the library; call it before quit |
Univariate polynomials
rs_isolate_uni_classical
LACE:-rs_isolate_uni_classical(f, precision = 2, intervalLeft = -infinity,
intervalRight = infinity, constraints = [],
verbose = 0, threads = 0, squarefree = false)
The real roots of the univariate polynomial f, each in an interval of
precision significant bits. The square-free part of f is isolated
unless squarefree = true says that f is square-free; a multiple root is
returned once.
intervalLeft,intervalRightkeep the roots of that range only (a root lying on a bound is returned as the point[q, q]).constraintsis a list of polynomials in the variable off. The intervals are then such that each constraint has a constant sign on them, and the result is the pair[roots, values],values[i][j]an interval containing the value of constraintjat rooti(the point[0, 0]when the constraint vanishes at that root).
f := x^3 - 7*x + 6: # (x - 1)(x - 2)(x + 3)
LACE:-rs_isolate_uni_classical(f, precision = 64); # three intervals, around -3, 1, 2
LACE:-rs_isolate_uni_classical(f, intervalLeft = 0, precision = 64); # the roots 1 and 2
roots, vals := op(LACE:-rs_isolate_uni_classical(f, precision = 64,
constraints = [x - 1]));
# vals[i][1] encloses the value of x - 1 at root i; it is [0, 0] at x = 1
descartes_isolate
LACE:-descartes_isolate(f, precision = 16, threads = 0, squarefree = false)
The real roots of f by the Descartes method alone, in intervals with
dyadic bounds of width at most 2^(-precision). It is the cheapest way to
count the real roots of a polynomial.
nops(LACE:-descartes_isolate(x^3 - 7*x + 6)); # 3
rs_isolate_rur
LACE:-rs_isolate_rur(rext, rden, [num_1, ..., num_m], precision = 64,
exactinterval = FAIL, verbose = 0, threads = 0,
squarefree = false)
The real points of a parameterization in one variable T:
rext(T) = 0, x_k = num_k(T) / rden(T), where rden is coprime with
rext (a constant is allowed). The result has one element per real root
of rext, the list of the intervals of x_1, ..., x_m, each with
precision significant bits. exactinterval = [lo, hi] keeps only the
root of rext that this interval isolates.
LACE:-rs_isolate_rur(T^2 - 2, 1, [T, T^2], precision = 64);
# two points: x_1 around -sqrt(2) and sqrt(2), x_2 an interval containing 2
LACE:-rs_isolate_rur(T^2 - 2, 1, [T, T^2], exactinterval = [1, 2]);
# the point with x_1 = sqrt(2) only
Zero-dimensional systems
rur
LACE:-rur(sys, vars, tvar = _T, sep = 0, strategy = 0, nbits = 28,
maxprimes = 0, threads = 0)
The Rational Univariate Representation of a zero-dimensional system sys
with rational coefficients in the variables vars. The Groebner bases are computed by the library. The
result is a list of components
[f, g, [n_1, ..., n_n], tvar = form, mult]
with vars[k] = n_k(tvar) / g(tvar) at the roots of f(tvar), form the
separating linear form in vars and mult the multiplicity. The result
is [] when the system has no solution; a system that is not
zero-dimensional raises an error.
| option | meaning |
|---|---|
tvar |
the name of the parameter |
sep |
0: the library chooses the separating element; a name of vars, or its rank, imposes that variable |
strategy |
the search of the separating element: 0 sparse (default), 1 random, 2 variables only, 3 incremental |
nbits |
the primes are taken below 2^nbits |
maxprimes |
the lifting fails beyond that many primes; 0: 200000 |
LACE:-rur([x^2 + y^2 - 5, x - y - 1], [x, y]);
# [[_T^2 + _T - 2, 2*_T + 1, [_T + 5, 2*_T^2 + _T], _T = y, 1]]
that is _T = y, _T^2 + _T - 2 = 0, x = (_T + 5) / (2 _T + 1): the
points (2, 1) and (-1, -2).
When no variable separates the solutions, the form is a genuine linear combination:
R := LACE:-rur([x^2 - 1, y^2 - 1], [x, y]):
R[1][4]; # _T = -2*x + y
sep imposes a variable:
LACE:-rur([x^2 - 2, y - x - 1/3, z - x*y], [x, y, z], sep = z)[1][4]; # _T = z
real_solutions
LACE:-real_solutions(sys, vars, precision = 53, sep = 0, strategy = 0,
nbits = 28, maxprimes = 0, threads = 0)
The real solutions of the zero-dimensional system sys: one list of
intervals [lo, hi] per solution, in the order of vars, each with
precision significant bits; [] when there is none. The options are
those of rur, which is computed first.
B := LACE:-real_solutions([x^2 + y^2 - 5, x - y - 1], [x, y], precision = 64):
nops(B); # 2
map(s -> map(b -> evalf((b[1] + b[2]) / 2), s), B);
# [[-1., -2.], [2., 1.]]
Bivariate systems
The functions below take two polynomials p1, p2 in exactly two
variables and the variable v1 to eliminate; the other variable, v2, is
found from the polynomials. The polynomials in the results are
polynomials in v2 (or in the parameter).
bz_biv_resultant
LACE:-bz_biv_resultant(p1, p2, v1, threads = 0)
The resultant of p1 and p2 with respect to v1, a polynomial in
v2 (defined up to a constant factor).
bz_biv_decomposition
res, asymp, res_gen, res_non_gen := LACE:-bz_biv_decomposition(p1, p2, v1,
nbits = 62, threads = 0)
The solutions split by level, the level k of a value u of v2 being
the degree of gcd(p1(v1, u), p2(v1, u)). With
S_k = s_0 + s_1 v1 + ... + s_k v1^k the subresultant of index k:
res: the resultant with respect tov1;asymp: the polynomial of the values ofv2above which there are solutions at infinity,1when there are none;res_gen[k] = [s_(k-1) + k s_k v1, univ]: at the roots ofunivthere is one solution,v1being the root of the first polynomial, which is linear inv1;res_non_gen[k] = [S_k, rest]: at the roots ofrest, the values ofv1are the roots ofS_k.
An empty component is [0, 0].
c := g^2 + o^2 - 1:
res, asymp, gen, nongen := LACE:-bz_biv_decomposition(c, diff(c, g), g):
# gen[1] = [2*g, o^2 - 1] up to constant factors: g = 0 above o = -1 and o = 1
bz_biv_is_generic(res_non_gen) is true when every non-generic
component is empty; bz_biv_is_solved(res, res_gen, res_non_gen, v2) is
true when, in addition, the generic components describe the solutions.
bz_biv_rur
LACE:-bz_biv_rur(p1, p2, v1, nbits = 62, threads = 0)
A RUR with parameter v2: a list of components
[k, rext, rden, num_v1, num_v2], k the multiplicity, rext(v2) = 0,
v1 = num_v1 / rden and num_v2 = v2 * rden. An error is raised when v2
does not separate the solutions; use bz_biv_rur_shear then.
bz_biv_rur_shear
LACE:-bz_biv_rur_shear(p1, p2, v1, tvar = _T, nbits = 62, threads = 0,
sep = 0, a = FAIL)
A RUR with parameter tvar = v2 + a v1, the integer a being chosen by
the library. The result is [a, tvar, comps], each component
[mult, rext, rden, num_v1, num_v2] with rext(tvar) = 0,
v1 = num_v1 / rden, v2 = num_v2 / rden. a = 0 imposes tvar = v2;
another imposed value of a is refused.
c2 := g^2 - o^3:
a, T, comps := op(LACE:-bz_biv_rur_shear(c2, diff(c2, g), g, tvar = _T)):
bz_biv_decomposition_shear
a, t, res, asymp, res_gen, res_non_gen :=
LACE:-bz_biv_decomposition_shear(p1, p2, v1, a = FAIL, tvar = T,
nbits = 62, threads = 0)
The decomposition in the coordinates (v1, t), v2 = t - a v1. Without
an imposed a: when bz_biv_decomposition already solves the system,
a = 0 and t = v2; otherwise a is the shear of bz_biv_rur_shear and
t = tvar.