diff --git a/src/sage/rings/polynomial/msolve.py b/src/sage/rings/polynomial/msolve.py
new file mode 100644
index 00000000000..7f743f0f004
--- /dev/null
+++ b/src/sage/rings/polynomial/msolve.py
@@ -0,0 +1,220 @@
+# coding: utf-8
+r"""
+Solution of polynomial systems using msolve
+
+`msolve `_ is a multivariate polynomial system solver
+developed mainly by Jérémy Berthomieu (Sorbonne University), Christian Eder
+(TU Kaiserslautern), and Mohab Safey El Din (Sorbonne University).
+
+This module provide implementations of some operations on polynomial ideals
+based on msolve. Currently the only supported operation is the computation of
+the variety of zero-dimensional ideal over the rationals.
+
+Note that msolve is currently not part of Sage and must be installed
+separately. The present interface looks for a binary named ``msolve`` in the
+system program path (``$PATH``).
+
+.. SEEALSO:: :mod:`sage.rings.polynomial.multi_polynomial_ideal`
+"""
+
+import os
+import tempfile
+import subprocess
+
+import sage.structure.proof.proof
+
+from sage.misc.all import SAGE_TMP
+from sage.misc.converting_dict import KeyConvertingDict
+from sage.misc.sage_eval import sage_eval
+from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.rings.rational_field import QQ
+from sage.rings.real_arb import RealBallField
+from sage.rings.real_double import RealDoubleField_class
+from sage.rings.real_mpfr import RealField_class
+from sage.rings.real_mpfi import RealIntervalField_class, RealIntervalField
+
+
+def _variety(ideal, ring, proof):
+ r"""
+ Compute the variety of a zero-dimensional ideal using msolve.
+
+ Part of the initial implementation was loosely based on the example
+ interfaces available as part of msolve, with the authors' permission.
+
+ TESTS::
+
+ sage: K. = PolynomialRing(QQ, 2, order='lex')
+ sage: I = Ideal([ x*y - 1, (x-2)^2 + (y-1)^2 - 1])
+
+ sage: I.variety(algorithm='msolve', proof=False) # optional - msolve
+ [{x: 1, y: 1}]
+ sage: I.variety(RealField(100), algorithm='msolve', proof=False) # optional - msolve
+ [{x: 2.7692923542386314152404094643, y: 0.36110308052864737763464656216},
+ {x: 1.0000000000000000000000000000, y: 1.0000000000000000000000000000}]
+ sage: I.variety(RealIntervalField(100), algorithm='msolve', proof=False) # optional - msolve
+ [{x: 2.76929235423863141524040946434?, y: 0.361103080528647377634646562159?},
+ {x: 1, y: 1}]
+ sage: I.variety(RBF, algorithm='msolve', proof=False) # optional - msolve
+ [{x: [2.76929235423863 +/- 2.08e-15], y: [0.361103080528647 +/- 4.53e-16]},
+ {x: 1.000000000000000, y: 1.000000000000000}]
+ sage: I.variety(RDF, algorithm='msolve', proof=False) # optional - msolve
+ [{x: 2.7692923542386314, y: 0.36110308052864737}, {x: 1.0, y: 1.0}]
+ sage: I.variety(AA, algorithm='msolve', proof=False) # optional - msolve
+ [{x: 2.769292354238632?, y: 0.3611030805286474?},
+ {x: 1.000000000000000?, y: 1.000000000000000?}]
+ sage: I.variety(QQbar, algorithm='msolve', proof=False) # optional - msolve
+ [{x: 2.769292354238632?, y: 0.3611030805286474?},
+ {x: 1, y: 1},
+ {x: 0.11535382288068429? + 0.5897428050222055?*I, y: 0.3194484597356763? - 1.633170240915238?*I},
+ {x: 0.11535382288068429? - 0.5897428050222055?*I, y: 0.3194484597356763? + 1.633170240915238?*I}]
+ sage: I.variety(ComplexField(100))
+ [{y: 1.0000000000000000000000000000, x: 1.0000000000000000000000000000},
+ {y: 0.36110308052864737763464656216, x: 2.7692923542386314152404094643},
+ {y: 0.31944845973567631118267671892 - 1.6331702409152376561188467320*I, x: 0.11535382288068429237979526783 + 0.58974280502220550164728074602*I},
+ {y: 0.31944845973567631118267671892 + 1.6331702409152376561188467320*I, x: 0.11535382288068429237979526783 - 0.58974280502220550164728074602*I}]
+
+ sage: Ideal(x^2 + y^2 - 1, x - y).variety(RBF, algorithm='msolve', proof=False) # optional - msolve
+ [{x: [-0.707106781186547 +/- 6.29e-16], y: [-0.707106781186547 +/- 6.29e-16]},
+ {x: [0.707106781186547 +/- 6.29e-16], y: [0.707106781186547 +/- 6.29e-16]}]
+ sage: sorted(Ideal(x^2 - 1, y^2 - 1).variety(QQ, algorithm='msolve', proof=False), key=str) # optional - msolve
+ [{x: -1, y: -1}, {x: -1, y: 1}, {x: 1, y: -1}, {x: 1, y: 1}]
+ sage: Ideal(x^2-1, y^2-2).variety(CC, algorithm='msolve', proof=False) # optional - msolve
+ [{x: 1.00000000000000, y: 1.41421356237310},
+ {x: -1.00000000000000, y: 1.41421356237309},
+ {x: 1.00000000000000, y: -1.41421356237309},
+ {x: -1.00000000000000, y: -1.41421356237310}]
+
+ sage: Ideal([x, y, x + y]).variety(algorithm='msolve', proof=False) # optional - msolve
+ [{x: 0, y: 0}]
+
+ sage: Ideal([x, y, x + y - 1]).variety(algorithm='msolve', proof=False) # optional - msolve
+ []
+ sage: Ideal([x, y, x + y - 1]).variety(RR, algorithm='msolve', proof=False) # optional - msolve
+ []
+
+ sage: Ideal([x*y - 1]).variety(QQbar, algorithm='msolve', proof=False) # optional - msolve
+ Traceback (most recent call last):
+ ...
+ ValueError: positive-dimensional ideal
+
+ sage: K. = PolynomialRing(RR, 2, order='lex')
+ sage: Ideal(x, y).variety(algorithm='msolve', proof=False)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: unsupported base field: Real Field with 53 bits of precision
+
+ sage: K. = PolynomialRing(QQ, 2, order='lex')
+ sage: Ideal(x, y).variety(ZZ, algorithm='msolve', proof=False)
+ Traceback (most recent call last):
+ ...
+ ValueError: no coercion from base field Rational Field to output ring Integer Ring
+ """
+
+ # Normalize and check input
+
+ base = ideal.base_ring()
+ if ring is None:
+ ring = base
+ proof = sage.structure.proof.proof.get_flag(proof, "polynomial")
+ if proof:
+ raise ValueError("msolve relies on heuristics; please use proof=False")
+ # As of msolve 0.2.4, prime field seem to be supported, by I cannot
+ # make sense of msolve's output in the positive characteristic case.
+ # if not (base is QQ or isinstance(base, FiniteField) and
+ # base.is_prime_field() and base.characteristic() < 2**31):
+ if base is not QQ:
+ raise NotImplementedError(f"unsupported base field: {base}")
+ if not ring.has_coerce_map_from(base):
+ raise ValueError(f"no coercion from base field {base} to output ring {ring}")
+
+ # Run msolve
+
+ drlpolring = ideal.ring().change_ring(order='degrevlex')
+ polys = ideal.change_ring(drlpolring).gens()
+ msolve_in = tempfile.NamedTemporaryFile(dir=SAGE_TMP, mode='w',
+ encoding='ascii', delete=False)
+ command = ["msolve", "-f", msolve_in.name]
+ if isinstance(ring, (RealIntervalField_class, RealBallField,
+ RealField_class, RealDoubleField_class)):
+ parameterization = False
+ command += ["-p", str(ring.precision())]
+ else:
+ parameterization = True
+ command += ["-P", "1"]
+ try:
+ print(",".join(drlpolring.variable_names()), file=msolve_in)
+ print(base.characteristic(), file=msolve_in)
+ print(*(pol._repr_().replace(" ", "") for pol in polys),
+ sep=',\n', file=msolve_in)
+ msolve_in.close()
+ msolve_out = subprocess.run(command, capture_output=True)
+ except FileNotFoundError:
+ raise RuntimeError(
+ "could not find the msolve binary. Please install msolve "
+ "(https://msolve.lip6.fr/) and try again.")
+ finally:
+ os.unlink(msolve_in.name)
+ msolve_out.check_returncode()
+
+ # Interpret output
+
+ data = sage_eval(msolve_out.stdout[:-2].decode('ascii'))
+
+ dim = data[0]
+ if dim == -1:
+ return []
+ elif dim > 0:
+ raise ValueError("positive-dimensional ideal")
+ else:
+ assert dim.is_zero()
+
+ out_ring = ideal.ring().change_ring(ring)
+
+ if parameterization:
+
+ upol = PolynomialRing(base, 't')
+ t = upol.gen()
+ def to_poly(p):
+ assert len(p[1]) == p[0] + 1
+ return upol(p[1])
+
+ if len(data) != 3:
+ raise NotImplementedError(f"unsupported msolve output format: {data}")
+ [dim1, nvars, _, vars, _, [one, elim, den, param]] = data[1]
+ assert dim1.is_zero()
+ assert one.is_one()
+ assert len(vars) == nvars
+ ringvars = out_ring.variable_names()
+ assert sorted(vars[:len(ringvars)]) == sorted(ringvars)
+ vars = [out_ring(name) for name in vars[:len(ringvars)]]
+ elim = to_poly(elim)
+ den = to_poly(den)
+ param = [to_poly(f)/d for [f, d] in param]
+ elim_roots = elim.roots(ring, multiplicities=False)
+ variety = []
+ for rt in elim_roots:
+ den_of_rt = den(rt)
+ point = [-p(rt)/den_of_rt for p in param]
+ if len(param) != len(vars):
+ point.append(rt)
+ assert len(point) == len(vars)
+ variety.append(point)
+
+ else:
+
+ if len(data) != 2 or data[1][0] != 1:
+ raise NotImplementedError(
+ f"unsupported msolve output format: {data}")
+ _, [_, variety] = data
+ if isinstance(ring, (RealIntervalField_class, RealBallField)):
+ to_out_ring = ring
+ else:
+ assert isinstance(ring, (RealField_class, RealDoubleField_class))
+ myRIF = RealIntervalField(ring.precision())
+ to_out_ring = lambda iv: ring.coerce(myRIF(iv).center())
+ vars = out_ring.gens()
+ variety = [[to_out_ring(iv) for iv in point]
+ for point in variety]
+
+ return [KeyConvertingDict(out_ring, zip(vars, point)) for point in variety]
+
diff --git a/src/sage/rings/polynomial/multi_polynomial_ideal.py b/src/sage/rings/polynomial/multi_polynomial_ideal.py
index 5762b701e80..a32b9fe3d1f 100644
--- a/src/sage/rings/polynomial/multi_polynomial_ideal.py
+++ b/src/sage/rings/polynomial/multi_polynomial_ideal.py
@@ -232,6 +232,7 @@
# https://www.gnu.org/licenses/
# ****************************************************************************
+
from sage.interfaces.singular import singular as singular_default
from sage.interfaces.magma import magma as magma_default
@@ -2283,7 +2284,7 @@ def saturation(self, other):
return (R.ideal(ideal), ZZ(expo))
@require_field
- def variety(self, ring=None, *, algorithm="triangular_decomposition"):
+ def variety(self, ring=None, *, algorithm="triangular_decomposition", proof=True):
r"""
Return the variety of this ideal.
@@ -2317,6 +2318,8 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition"):
- ``ring`` - return roots in the ``ring`` instead of the base
ring of this ideal (default: ``None``)
+ - ``algorithm`` - algorithm or implementation to use; see below for
+ supported values
- ``proof`` - return a provably correct result (default: ``True``)
EXAMPLES::
@@ -2370,7 +2373,9 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition"):
sage: I.variety(ring=AA)
[{y: 1, x: 1},
{y: 0.3611030805286474?, x: 2.769292354238632?}]
-
+ sage: I.variety(RBF, algorithm='msolve', proof=False) # optional - msolve
+ [{x: [2.76929235423863 +/- 2.08e-15], y: [0.361103080528647 +/- 4.53e-16]},
+ {x: 1.000000000000000, y: 1.000000000000000}]
and a total of four intersections::
@@ -2434,10 +2439,21 @@ def variety(self, ring=None, *, algorithm="triangular_decomposition"):
ALGORITHM:
- Uses triangular decomposition.
+ - With ``algorithm`` = ``"triangular_decomposition"`` (default),
+ uses triangular decomposition, via Singular if possible, falling back
+ on a toy implementation otherwise.
+
+ - With ``algorithm`` = ``"msolve"``, calls the external program
+ `msolve `_ (if available in the system
+ program search path). Note that msolve uses heuristics and therefore
+ requires setting the ``proof`` flag to ``False``. See
+ :mod:`~sage.rings.polynomial.msolve` for more information.
"""
if algorithm == "triangular_decomposition":
return self._variety_triangular_decomposition(ring)
+ elif algorithm == "msolve":
+ from . import msolve
+ return msolve._variety(self, ring, proof)
else:
raise ValueError(f"unknown algorithm {algorithm!r}")