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, and points of plane curves;
- interval Newton iterations and Kantorovich tests on square systems;
- for discriminant varieties of parametric systems, over Q: elimination ideals, ideals at infinity and saturations by a polynomial, Groebner bases included.
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. Every function of the Julia package
LACE.jl is there too, with the same name, arguments, options and results
(see The interface of LACE.jl).
Installation
Download the archive for Maple 2026 and your system:
LACE-Maple2026-Darwin_arm64.tar.gz
(macOS 12 or later, Apple silicon),
LACE-Maple2026-Linux_x86_64.tar.gz
(Linux x86-64) or
LACE-Maple2026-Windows_x86_64.zip
(Windows x86-64); other 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: on macOS and Linux it uses the GMP, MPFR and FLINT of Maple and includes
MPFI (LGPL); on Windows GMP, MPFR, MPFI and FLINT (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
On Linux, the same without xattr. On Windows, extract the zip archive into
%USERPROFILE%\maple\toolbox\2026 (the folder LACE directly under it).
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
LACE:-elim([a*x^2 + a + 1, 2*a*x], [x], [a]); # [a + 1]: the critical values (a double root in x)
LACE:-w_infty([a*x^2 + a + 1], [x], [a]); # [true, [[a]]]: where a root in x goes to infinity
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 (except by the Newton functions).
- An interval is a list
[lo, hi]of rationals. Two equal bounds denote an exact value; otherwise the open interval(lo, hi)contains exactly one root, the bounds excluded (a bound may be another root, given as its own exact value). The functions that take intervals (real_solution_at,rur_coords,signs_at) read them the same way. - Intervals are returned in increasing order of the root (of the eliminant, for systems).
precisionis a number of significant bits (a relative precision), except forisolate_rootsandrefine_roots, where it is an absolute width, and for the Newton functions, where it is the precision of the interval arithmetic.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. The exact results (RUR, ideals, polynomials) do not depend on it, nor do the primes the lifts over Q use (last_nprimes,last_biv_nprimes; forelim,w_infty,saturateup to a rare change of learn prime). The intervals do: the isolation of a univariate polynomial steals work between the threads, so that its isolating intervals, and the intervals and boxes refined from them, depend on the number of threads and, from two threads on, may differ from one run to the next. What they enclose does not: the number of roots or solutions, their order, the exact roots; refined to the same width, the intervals of the same rank overlap. Usethreads = 1for reproducible intervals.- Errors of the library are raised as Maple errors.
- The functions of LACE.jl take and return Maple objects: polynomials
rather than coefficient vectors, an interval
[lo, hi]of rationals rather than aRootor aBigInterval, a list rather than a tuple or a named tuple (its fields in the same order),FAILfornothing, a name (sparse) for a symbol (:sparse). A Julia name ending in!loses it, withset_in front for a setter:refine!isrefine,gb_backend!isset_gb_backend. A Julia keyword whose name is also an export ofLACE(pmv,refine) is to be quoted afterwith(LACE):'pmv' = true.
The functions of LACE.jl and their Maple names:
| LACE.jl | Maple |
|---|---|
zdim_parameterization(sys; ...) |
zdim_parameterization(sys, vars, ...) |
rur_modp(sys; p, ...), rur_core(sys) |
rur_modp(sys, vars, p, ...), rur_core(sys, vars, p) |
real_solutions(...) (four forms) |
real_solutions |
real_solution_at, rur_coords, signs_at, eval_bfi |
the same, see below |
guess_infos, guess_lowest_input_precision |
the same, with vars |
biv_parameterization, biv_decomposition, biv_psc, biv_resultant, pmv |
the same, with vars = [x_1, x_2] |
biv_decomposition_modp(sys; p, ...) |
biv_decomposition_modp(sys, vars, p, ...) |
isolate, Root, lo, hi, width, isexact |
isolate and the same on [lo, hi] |
isolation, refine!, isolated_root, refine_root!, set_root! |
isolation, refine, isolated_root, refine_root, set_root |
squarefree_part, dyadic_outward, exact_rational |
the same |
elim(F, vars), w_infty, saturate |
the same (params optional) |
tci_points, tci_columns, tci_track |
the same, with vars |
interval_newton, interval_classic_newton, kantorovich_test |
the same, with vars |
gb_backend, gb_backend!, rur_engine!, gb_time, gb_time_reset!, real_times_reset! |
gb_backend, set_gb_backend, set_rur_engine, gb_time, gb_time_reset, real_times_reset |
last_search, last_nprimes, last_biv_nprimes, nb_threads, set_verbose |
the same |
RS.jl: rs_isolate, rs_rur, rs_tci_points, rs_newton |
rs_isolate and the same |
RS4.jl: bz_biv_*, set_nb_threads, get_nb_threads |
the bz_biv_* above (the forms of Maple’s RS4), bz_biv_rur_all, set_nb_threads, get_nb_threads |
The random strategies of the separating search draw their forms from a generator of their own: the forms they find may differ from those of Julia, not their validity.
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 or false: silent; 1 or true: 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 |
LACE:-nb_threads() |
the default number of workers |
LACE:-set_nb_threads(n) |
sets it; n <= 0: the performance cores |
LACE:-perf_cores() |
the physical performance cores the process may use |
LACE:-memory_budget() |
the memory, in MB, the multi-modular loops may occupy (0: the available memory) |
LACE:-set_memory_budget(mb) |
sets it |
LACE:-rur_engine() |
the engine of the RUR modulo a prime, table (default) or fglm |
LACE:-set_rur_engine(e) |
sets it |
LACE:-real_times() |
the seconds of the real solving since the last reset: [square-free part, isolation, coordinates] |
LACE:-real_times_reset() |
resets them |
LACE:-last_nprimes() |
the primes of the last rur, real_solutions, elim, w_infty or saturate (-1 if none) |
LACE:-last_biv_nprimes() |
the primes of the last bivariate decomposition (-1 if none) |
LACE:-last_search() |
what the last separating search found: [quotient, minpoly, sep_lin, nvars] (all -1 before any) |
LACE:-gb_backend() |
the engine of the Groebner bases: always lace, the F4 of the library |
LACE:-set_gb_backend(b) |
lace (or f4c); groebner, Groebner.jl, exists in Julia only |
LACE:-gb_time() |
[seconds, calls] of the Groebner bases computed modulo a prime since the last reset |
LACE:-gb_time_reset() |
resets them |
LACE:-get_nb_threads() |
the same as nb_threads() (the name of RS4.jl) |
LACE:-KANTO_HESSIAN, KANTO_PRECOND, KANTO_SCHULZ |
1, 2, 4: the flags of the Kantorovich test |
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
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
isolate_roots
LACE:-isolate_roots(f, precision = 0, threads = 0, max_roots = 0,
newton = true, squarefree = false)
The isolating intervals of the real roots of f, with dyadic bounds
num / 2^k, refined to a width below 2^(-precision) when precision >
0. max_roots > 0 stops after that many roots; newton = false turns
off the Newton steps of the isolation, which help on clustered roots and
may cost on small polynomials.
refine_roots
LACE:-refine_roots(f, roots, precision, threads = 0, squarefree = false)
The intervals roots of f, as isolate_roots returns them, refined to
a width below 2^(-precision).
f := expand((x^2 - 2)^2 * (x - 1)):
r := LACE:-isolate_roots(f): # three intervals
LACE:-refine_roots(f, r, 100)[3]; # sqrt(2) to 100 bits
squarefree_part
LACE:-squarefree_part(f)
The primitive square-free part of f, with integer coefficients.
LACE:-squarefree_part(expand((x^2 - 2)^2 * (x - 1))); # x^3 - x^2 - 2*x + 2
Zero-dimensional systems
rur
LACE:-rur(sys, vars, tvar = _T, sep = 0, strategy = 0, nbits = 28,
maxprimes = 0, threads = 0, multiplicities = false)
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, a name or its number (sparse = 0, the default), see zdim_parameterization |
nbits |
the primes are taken below 2^nbits |
maxprimes |
the lifting fails beyond that many primes; 0: 200000 |
multiplicities |
true: the multiplicities of the solutions as well |
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
With multiplicities = true the RUR does not change and each component
gets a sixth element [[j, h_j], ...]: h_j(tvar) is the monic
polynomial whose roots are the values of the separating form at the
solutions of multiplicity exactly j; the h_j are pairwise coprime and
their product is f made monic.
R := LACE:-rur([(x^2 - 2)^2 * (x - 1), y^2 - x], [x, y], multiplicities = true):
R[1][4], R[1][6];
# _T = y, [[1, _T^2 - 1], [2, _T^4 - 2]]
real_solutions
LACE:-real_solutions(sys, vars, prec = 64, threads = 0, nn = 28, verbose = false,
sep = 0, strategy = 0, maxprimes = 0)
LACE:-real_solutions(rur, prec = 64, sep = FAIL, threads = 0, squarefree = false)
LACE:-real_solutions(rext, rden, nums, prec = 64, sep = FAIL, threads = 0, squarefree = false)
LACE:-real_solutions(f, prec = 64, constraints = [], constraint_values = false,
ensure_derivative_nonzero = false, search_interval = FAIL,
threads = 0, squarefree = false)
The real solutions of the zero-dimensional system sys: one list of
intervals [lo, hi] per solution, in the order of vars, each with
prec significant bits; [] when there is none. nn is the bit size of
the primes; the other options are those of rur, which is computed first.
precision and nbits are accepted for prec and nn.
The other forms are those of LACE.jl:
rur = [f, n_1, ..., n_n], a parameterization in the format ofzdim_parameterization:x_k = n_k(T) / f'(T)at the roots off, rational coefficients.sep, the separating vector, may be given: when it is the unit vector of a variable, that variable isTitself and its numerator is not evaluated.squarefree = truesays thatfis square-free.rext, rden, nums:rext(T) = 0,x_k = nums[k](T) / rden(T),rdenany polynomial prime torext.funivariate (RS.jl’srs_isolate): every root offas a one-element list[box], on which every polynomial ofconstraints(in the variable off) has a constant sign; withconstraint_values = true, the pair[roots, values],values[i][j]an interval of constraintjat rooti(the point[0, 0]where it vanishes), the constraints being scaled to integer coefficients by a positive factor.ensure_derivative_nonzeroappendsf'to the constraints.search_interval = [l, r]keeps the roots of[l, r](rational or infinite bounds; a root on a bound is the point); with finite dyadic bounds and no constraint they are isolated in[l, r]only.
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.]]
rur_modp
LACE:-rur_modp(sys, vars, p, sep = 0, search = true, strategy = sparse, tvar = _T,
multiplicities = false)
The RUR of sys modulo the prime p (below 2^30), the Groebner basis
being computed by the library: [flag, [f, n_1, ...], sep_lin, sep_var],
the polynomials in tvar with coefficients in 0 .. p-1. f is the
eliminant, then come the numerators of every variable after a search of
the separating form (search = true), of the variables other than
x_sep otherwise. sep_lin is the separating form, sep_var the
separating variable (from 1, 0 for a genuine form) and flag is 1 on
success. strategy is a strategy of
zdim_parameterization, by its name or its number. With multiplicities = true the result has a fifth element, the
multiplicity factors [[j, h_j], ...] of rur, modulo p.
guess_infos
LACE:-guess_infos(sys, vars, nn = 28, verbose = true, search_strategy = sparse)
The structure of sys modulo one prime of nn bits (nbits, strategy
accepted for nn, search_strategy; the strategies of
zdim_parameterization):
[found, type, quotient, minpoly, sep_lin]. type is shape when the
last variable separates and the ideal is radical, cyclic when the
ideal is radical but another form is needed, general otherwise;
quotient is the dimension of the quotient and minpoly the degree of
the minimal polynomial of the separating form sep_lin. The answer is
right for all but finitely many primes; it is not certified.
guess_lowest_input_precision
LACE:-guess_lowest_input_precision(sys, vars, nn = 28, verbose = true, lowest_prec = 2)
The fewest decimal digits the coefficients of sys can be rounded to
without changing quotient and minpoly of guess_infos: the pair
[rounded system, digits].
Discriminant varieties
sys is a list of polynomials with rational coefficients in the unknowns
vars (eliminated) and the parameters params. Each result is a reduced
Groebner basis for the degree reverse lexicographic order on params,
in that order, made of primitive integer polynomials: [1] for the whole
ring, [] for the zero ideal. params may be left out: the other names
of the polynomials, in lexicographic order. last_nprimes()
gives the primes used.
elim
LACE:-elim(sys, vars, params = <the other names>, threads = 0)
The elimination ideal <sys> ∩ Q[params]. For the critical values, add
to sys the maximal minors of its Jacobian matrix with respect to vars.
F := [a*x^2 + a + 1]:
LACE:-elim([op(F), diff(F[1], x)], [x], [a]);
# [a + 1]
w_infty
LACE:-w_infty(sys, vars, params = <the other names>, threads = 0)
[zerodim, [I_1, ..., I_k]]: whether sys is generically
zero-dimensional in vars and, when it is,
for each vars[j], the ideal of Q[params] generated by the leading
coefficients of the elements of a Groebner basis for
DRL(vars) > DRL(params) whose leading monomial in vars is a power of
vars[j]. The list is empty when zerodim is false.
LACE:-w_infty([a*x^6 + b*y^2 - 1, x^2 - a*y - b], [x, y], [a, b]);
# [true, [[1], [a^4]]]
saturate
LACE:-saturate(sys, g, vars, params = <the other names>, threads = 0)
(<sys> : g^∞) ∩ Q[params]; vars may be [] (the
saturation itself, in params).
G := [(a - 1)*(s - a), (a - 1)*(a - 2)*s]:
LACE:-elim(G, [s], [a]); # [a^3 - 3*a^2 + 2*a]
LACE:-saturate(G, a - 1, [s], [a]); # [a^2 - 2*a]
Plane curves
tci_points
LACE:-tci_points(curve, [x, y], a1, b1, a2, b2, nb, prec = 63, precision = 0,
threads = 0, verbose = 0)
Points of the curve curve(x, y) = 0 in the box [a1, b1] x [a2, b2]
(x in [a1, b1]), computed on a grid of nb lines per direction: the
pair [X, Y] of the lists of their coordinates, as floats. Each point is
exact to the width of the grid, enough to draw the curve, or to
2^(-precision) when that is finer. The eighth argument prec, as in
LACE.jl, is accepted and ignored.
P := LACE:-tci_points(x^2 + y^2 - 1, [x, y], -2, 2, -2, 2, 50):
plots:-pointplot(zip((a, b) -> [a, b], P[1], P[2]));
Interval Newton and the Kantorovich test
These functions take a square system sys in the variables vars, with
rational or float coefficients (floats are read exactly and the
coefficients are not scaled), and a starting point x0: a list of
numbers, or a list of intervals [lo, hi] for a box; one equation and
one number for one variable. Their arguments are those of LACE.jl, after
vars: input_precision (accepted: the coefficients are read exactly and
made intervals of work_precision bits), work_precision, the number of
bits of the interval arithmetic, output_precision, the significant bits
the results are rounded outward to, and for the iterations
target_precision and max_nb_loops. verbose sets the messages of the
library.
The Kantorovich test certifies that Newton’s method converges from x0
to a zero of sys, unique in the ball of radius 2 eta around x0. Its
classical bound depends on how the equations are scaled; flags, a set
among {hessian, precond, schulz} (or the integer sum of KANTO_HESSIAN
= 1, KANTO_PRECOND = 2, KANTO_SCHULZ = 4), selects
bounds that depend less, or not at all, on that scaling:
| flag | bound |
|---|---|
hessian |
a bound independent of a diagonal scaling of the equations |
precond |
a bound on a preconditioned system, independent of the scaling of the equations |
schulz |
with precond, a better preconditioner when the first one is poor |
The constants are [2 eta, mu] without flags, else
[2 eta, mu, mu1, mu2, delta], each an interval: mu is the best bound
computed (the point is certified when mu < 1), mu1 the one of
hessian, mu2 the one of precond (infinity when not computed), and
delta the defect of the preconditioner (-1 without precond).
kantorovich_test
LACE:-kantorovich_test(sys, vars, x0, input_precision = 63, work_precision = 63,
output_precision = 2, verbose = 1, flags = 0)
The constants, rounded outward to output_precision bits; the point is
certified when mu < 1, that is when constants[2][2] < 1.
precision is accepted for work_precision.
S := [x^2 + y, x^2 + y + x/1024]:
LACE:-kantorovich_test(S, [x, y], [1/256, 1/256], 63, 128, 128);
# not certified: mu = 64
LACE:-kantorovich_test(S, [x, y], [1/256, 1/256], 63, 128, 128, flags = {hessian, precond});
# certified: mu1 = 32, mu2 = 1/64
interval_newton
LACE:-interval_newton(sys, vars, x0, input_precision = 63, work_precision = 63,
target_precision = 2, output_precision = 2, max_nb_loops = 10,
refine = 1, verbose = 1, flags = 0)
The interval Newton method from x0, after the Kantorovich test:
[status, box, constants], box a list of intervals, one per variable,
rounded outward to output_precision bits. Each step is intersected with
the current box (refine = 1), or only when the step contains 0
(refine = 0). status is 0 for a singular Jacobian, 1 when a zero is
proved to lie in the box and every interval is narrower than
2^(-target_precision), 2 when the box no longer shrinks, 3 after
max_nb_loops steps, 4 when the starting box is proved to hold no zero,
5 when the width is reached without a proof that the box holds a zero. From a point (x0 a list
of numbers), the point is first inflated into a box proved to hold a zero
(epsilon-inflation): the status 1 then means that
the box returned holds a zero, and any other status gives no enclosure.
From a box, the status 1 also proves a zero in the box returned. The names of the previous
versions are accepted: precision, target, maxsteps for
work_precision, target_precision, max_nb_loops, and
always_intersect = true for refine = 1.
LACE:-interval_newton([x^2 - 2, y - x], [x, y], [1.4, 1.4], 63, 128, 100, 128);
interval_classic_newton
LACE:-interval_classic_newton(sys, vars, x0, input_precision = 63, work_precision = 63,
target_precision = 2, output_precision = 2,
max_nb_loops = 10, verbose = 1, flags = 0)
The same without intersections, for a start that may not enclose the solution.
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).
Each of them, and biv_parameterization, biv_decomposition and
biv_psc below, takes certified = false: the fast version, whose lift
stops when it reduces to the image of a witness prime. certified =
true lifts to the bound and checks the decomposition over the integers
by exact divisions, the primes all drawn again when a check fails
(rs_biv_set_certify).
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.
The interface of LACE.jl
The functions of the Julia package LACE.jl, with its arguments, options
and results, the conventions above turning its objects into Maple ones.
A system is given with its variables, which Julia takes from the ring of
the polynomials; the polynomials of a result are in tvar (_T by
default) when they are univariate in a new variable.
zdim_parameterization
LACE:-zdim_parameterization(sys, vars, nn = 28, verbose = true, threads = 0,
get_separating_element = false, max_primes = 200000,
search_strategy = sparse, eliminate_linear = true,
parallelism = default, composite = 4,
multiplicities = false, characteristic = 0, tvar = _T)
The RUR of a zero-dimensional system in the format of
RationalUnivariateRepresentation.jl: the list [f, n_1, ..., n_n], f
the eliminant and n_k the numerator of vars[k] (T f'(T) for the
separating variable), vars[k] = n_k(T) / f'(T) at the roots of f,
with rational coefficients; with characteristic = p, the same modulo
the prime p, coefficients in 0 .. p-1. nn is the bit size of the
primes, max_primes bounds their number. get_separating_element = true
returns [rur, lin], lin the separating linear form.
search_strategy, a name or its number: sparse (0, the free
variables, then the sparse escalation on them), random_free (1, the
free variables, then random forms on them), variables (2, the free
variables only), incremental (3, T_1 = y_1, then T_k = T_(k-1) + a
y_k with a = 0, 1, -1, 2, -2, ... until y_k is a function of T_k),
adaptive (4, the free variables, then random forms with small
coefficients), random_last (5, the last variable, then the forms of
random_free), random_direct (6, the forms of random_free only),
l0_norm / mron_0l (7 / 8, the free variables, then supports of
increasing size, x_1 / x_n first), deterministic (9, the last
variable, then the forms (1, i, i^2, ...) on the free variables). All
the forms are on the free variables, every other linear form taking the
same values at the solutions as one of them; the whole search runs in the
library. random, the former name of random_free, is still
accepted, with a warning.
eliminate_linear = false keeps the linear variables in the engine;
parallelism = serial forces one thread; composite is accepted and
ignored.
multiplicities = true asks for the multiplicities of the solutions as
well: the RUR does not change and the result becomes [rur, mult]
([rur, lin, mult] with get_separating_element), mult the list of
the pairs [j, h_j] of rur. A system without solution raises an error.
R := LACE:-zdim_parameterization([x^2 + y^2 - 5, x*y - 2], [x, y], verbose = false);
# [_T^4 - 5*_T^2 + 4, ..., 4*_T^4 - 10*_T^2]: T = y, x = R[2] / diff(R[1], _T)
rur_core
LACE:-rur_core(sys, vars, p, verbose = true, tvar = _T)
The RUR modulo p with the last variable as separating element:
[flag, [f, n_1, ...]], as rur_modp(..., search = false).
biv_parameterization
LACE:-biv_parameterization([p1, p2], [x_1, x_2], nbits = 62, threads = 0, sep = 0,
verbose = true, tvar = _T)
The RUR of two polynomials in two variables by the subresultants over Z
(no Groebner basis): one component per multiplicity, each the list
[rext, rden, num_X, num_Y, mult, lin], rext(T) = 0, x_1 = num_Y /
rden, x_2 = num_X / rden at the roots of rext, T = x_2 + lin[1] x_1
(sep = 2 imposes T = x_2).
biv_decomposition
LACE:-biv_decomposition([p1, p2], [x_1, x_2], nbits = 62, threads = 0,
verbose = true, psc = false)
The decomposition of bz_biv_decomposition, as
a list [res, asymp, res_gen, res_non_gen], x_1 eliminated; a constant
resultant raises an error. With psc = true a fifth element, the signed
principal subresultant coefficients [sRes_0, ..., sRes_p] of the pair
with respect to x_1, polynomials in x_2; for (P, diff(P, x_1)) it is
the Sturm-Habicht sequence of P: above a value u of x_2 where
sRes_p(u) <> 0, the number of distinct real roots of P(x_1, u) is
pmv([sRes_p(u), ..., sRes_0(u)]). Without psc = true these
coefficients are not computed. biv_psc computes that
sequence alone.
biv_decomposition_modp
LACE:-biv_decomposition_modp([p1, p2], [x_1, x_2], p, verbose = true, psc = false)
The decomposition of biv_decomposition modulo the
prime p < 2^63, in the same format, the coefficients in [0, p): the
resultant and the S_k are those of the pair reduced modulo p, the
other factors monic. p must exceed the degree in x_1 and that of the
resultant, and leave enough evaluation points for the subresultants. Where
two roots of the resultant meet modulo p, or a leading coefficient
vanishes at one of them, the result is the decomposition of the reduced
pair, not the reduction of the decomposition over Q.
LACE:-biv_decomposition_modp([x^2 + y^2 - 5, x*y - 2], [x, y], 101);
[y^4 + 96*y^2 + 4, 1, [[x*y + 99, y^4 + 96*y^2 + 4]], [[0, 0]]]
biv_psc
LACE:-biv_psc([p1, p2], [x_1, x_2], nbits = 62, threads = 0, verbose = true)
The signed principal subresultant coefficients [sRes_0, ..., sRes_p] of
p1, p2 with respect to x_1, polynomials in x_2, lifted
multi-modularly without the decomposition. For [P, diff(P, x_1)] it is
the Sturm-Habicht sequence of P.
SH := LACE:-biv_psc([x^2 + y^2 - 1, 2*x], [x, y]); # [4 - 4*y^2, 2, 1]
LACE:-pmv(ListTools:-Reverse(eval(SH, y = 0))); # 2 real roots in x above y = 0
biv_resultant
LACE:-biv_resultant([p1, p2], [x_1, x_2], threads = 0)
The resultant with respect to x_1, primitive, a polynomial in x_2.
pmv
LACE:-pmv(s)
Permanences minus variations of s = [s_p, ..., s_0] (decreasing indices,
s_p <> 0): the sum, over the consecutive nonzero entries s_i, s_j
(i > j, zeros between) with i - j odd, of eps_(i-j) signum(s_i s_j),
eps_k = (-1)^(k(k-1)/2).
isolate
LACE:-isolate(f, prec = 0, threads = 0, max_roots = 0, newton = true, trunc = true,
squarefree = false)
LACE:-isolate(f, [l, r], prec = 0, threads = 0, newton = true, trunc = true,
squarefree = false)
LACE:-isolate([[lo_0, hi_0], ..., [lo_n, hi_n]], [l, r], precision = 64)
LACE:-isolate(iso)
The real roots of the univariate f, sorted increasing, each an interval
[lo, hi] with dyadic bounds (an exact root when lo = hi), refined to a
width below 2^(-prec) when prec > 0. The square-free part is isolated
unless squarefree = true; max_roots > 0 stops after that many roots (a
budget, not the smallest ones); newton, trunc are the Newton jump and
the truncated frame of the isolation.
- With
[l, r](dyadic or infinite bounds), the roots of the closed interval[l, r]only, isolated there; a root on a bound is the point. - A list of intervals is a polynomial with interval coefficients
(constant term first, the bounds rounded outward to
precisionbits):[roots, nb_undecided], the roots in the open interval(l, r), simple, of every polynomial of the box, and what the width of the coefficients left undecided. An error when the leading coefficient holds 0. - On an
isolation, its current intervals.
Root(num_lo, den_lo, num_hi, den_hi) is [num_lo / 2^den_lo, num_hi /
2^den_hi]; lo(r), hi(r), width(r) and isexact(r) are its bounds,
its width and lo = hi. dyadic_outward(q, up, k) is q when it is
dyadic, else q rounded up (up = true) or down to a multiple of
2^(-k); exact_rational(x) is the rational of the number x, every
digit of a float kept.
LACE:-isolate(6 - 11*x + 6*x^2 - x^3); # [[0, 2], [2, 2], [2, 4]]
LACE:-isolate(6 - 11*x + 6*x^2 - x^3, [3/2, 3]); # [[2, 2], [3, 3]]
isolation
LACE:-isolation(f, threads = 0, max_roots = 0, newton = true, trunc = true,
squarefree = false)
An isolation kept to be tightened later, as a record: sf the square-free
part isolated, nb the number of roots, prec the width reached (0:
isolation only), reached whether the last refinement reached its
precision on every interval, threads, t_squarefree, t_isolation the
seconds of the square-free part and of the isolation, t_refinements the
pairs [bits, seconds] of the refinements. Its intervals live in the
library until finish().
refine(iso, prec, threads = iso:-threads)tightens every interval below2^(-prec), in place, from where it stands, and returns the seconds; a coarser request costs nothing.isolate(iso)gives the intervals,isolated_root(iso, k)intervalk.refine_root(iso, k, prec)refines intervalkalone and returns it.set_root(iso, k, lo, hi)replaces intervalk(dyadic bounds, not checked); the next refinement starts from there.reached_width(iso): the bits reached at least by the widest interval,infinitywhen they are all exact.
iso := LACE:-isolation(x^2 - 2):
LACE:-refine(iso, 40):
LACE:-isolate(iso); # the two roots to 2^-40
LACE:-refine_root(iso, 2, 200); # sqrt(2) to 2^-200
real_solution_at
LACE:-real_solution_at(f, g, nums, [lo, hi], prec = 64)
The values nums[k](T) / g(T) at the one root of the square-free f in
the interval [lo, hi] (dyadic bounds; open when lo < hi, a point when
lo = hi), each with prec
significant bits, a vanishing one the point 0: [root, values], the root
refined, or FAIL when [lo, hi] does not hold exactly one root of f.
g = 1 is allowed.
rur_coords
LACE:-rur_coords(f, g, nums, intervals, prec = 64, threads = 0)
The same at the roots of f isolated by intervals, a list of [lo,
hi] with dyadic bounds, exactly one root in each (open when lo < hi, the
bounds excluded; a point when lo = hi; an interval where f does not
change sign is an error): one
[root, values] per interval, in their order. The roots are refined, not
isolated again; for a parameterization with a separating form t, the
interval t(B) of a box B will do.
signs_at
LACE:-signs_at(f, polys, intervals, nonzero = [], pmv = false, threads = 0)
The exact signs of polys at the roots of the square-free f isolated by
intervals: [roots, signs], signs[i][k] the sign of polys[k] at root
i, each root refined only as far as the signs need (roots[i] its
interval then). A zero is decided by a gcd; nonzero lists the k known
not to vanish. With pmv = true an entry between two nonzero ones of
opposite signs is left undecided, as 0: it changes nothing to pmv of the
sequence.
LACE:-signs_at(x^2 - 2, [x - 1, x^2 - 2, 3], [[-2, -1], [1, 2]])[2];
# [[-1, 0, 1], [1, 0, 1]]
eval_bfi
LACE:-eval_bfi(polys, [lo, hi], prec = 64)
LACE:-eval_bfi([[lo_0, hi_0], ..., [lo_n, hi_n]], [lo, hi], prec = 64)
The univariate polys at the interval [lo, hi] (its bounds rounded
outward to prec bits), each enclosed at precision prec by the
evaluation of the library: hardware intervals when they suffice, MPFI
otherwise; or the polynomial with interval coefficients at [lo, hi].
LACE:-eval_bfi([1 + 2*x + 3*x^2, 5*x^3 - 1], [1/4, 1/2]); # [[27/16, 11/4], [-59/64, -3/8]]
tci_columns
LACE:-tci_columns(P, [x_1, x_2], a, b, nb, prec, ymin = -infinity, ymax = infinity,
squarefree = false, threads = 0)
The roots of P(x_1, X_i) in [ymin, ymax] for X_i = a + i (b - a) /
nb, i = 0 .. nb - 1 (x_2 the abscissa, a, b dyadic), refined to a
width at most 2^(-prec), and the number of roots under ymin above each
X_i: [roots, below]. ymin, ymax must be doubles exactly;
squarefree = true says every P(x_1, X_i) is square-free. prec is
required.
tci_track
LACE:-tci_track(P, [x_1, x_2], arcs, crit = 0, window, eps, tol, ycrit = [],
maxsteps = 0, threads = 0)
The arcs of the curve P = 0 followed step by step, x_2 the abscissa,
x_1 the ordinate. Each arc is [x0, ylo, yhi, dir, kind, e1, e2, e3, e4,
inf] (dyadic numbers and integers): it starts at the root of P(x_1, x0)
isolated by [ylo, yhi] (refined as the other points: the interval need
only isolate it) and goes in the direction dir (1 or -1) up to
e1 (kind = 0, a side of the window) or up to the root of crit (a
polynomial in x_2) isolated by [e1, e2] (kind = 1), where it ends at
the point of ordinate in [e3, e4] (inf = 0), goes to -infinity,
infinity (inf = -1, 1), or crosses a simple root of P (inf = 2:
the arc ends at the middle of [e1, e2], at the root of P there).
crit and ycrit come from the caller: tci_track neither computes nor
checks them.
window = [xmin, xmax, ymin, ymax]; the boxes have a diagonal below
eps, the points a width below tol; ycrit the intervals of the
abscissae of the horizontal tangents. Returns, for each arc, [x, y,
certified, steps, last]: its points as floats, whether every piece is
certified, the steps made, and the abscissa where it was given up
(FAIL otherwise).
rs_isolate
LACE:-rs_isolate(f, output_precision = 2, constraints = [], constraint_values = false,
search_interval = FAIL, ensure_derivative_nonzero = false, threads = 0,
verbose = 0, hybrid_limit = 2^30)
LACE:-rs_isolate(sys, vars, output_precision = 2, threads = 0, nn = 28, verbose = 0)
LACE:-rs_isolate(rext, rden, nums, sepvector, output_precision = 2, threads = 0)
LACE:-rs_isolate(rur, sepvector, output_precision = 2, threads = 0)
RS.jl’s isolation: the forms of real_solutions with
output_precision for prec; sepvector is checked against the
numerators before it is trusted, hybrid_limit is ignored.
rs_rur(sys, vars, verbose = 0, threads = 0, nn = 28) is RS.jl’s RUR,
[ext, den, coords, sep_vect] with integer coefficients, den a multiple
of ext'; rs_tci_points is tci_points;
rs_get_last_sep_vector() the separating form of the last search;
rs_newton raises an error (use kantorovich_test and
interval_newton).
bz_biv_rur_all
LACE:-bz_biv_rur_all(p1, p2, v1, nbits = 62, threads = 0)
RS4.jl’s form of bz_biv_rur: every component as
[rext, rden, num_X, num_Y, mult], num_X the numerator of v1,
num_Y = T rden.