🎯 Solving a system with ZeroDimSolver¶
ZeroDimSolver is the algorithm for the most common task: given a polynomial system, find all of
its isolated complex solutions. You hand it a system; it forms a start system and a homotopy,
tracks the paths, and classifies the endpoints. (If you instead already have a homotopy and a list
of start points – the parameter-homotopy workflow – reach for HomotopySolver().)
This tutorial shows ZeroDimSolver on the three shapes of input you will meet: a well-posed
square system, an over-determined system (more equations than unknowns), and an
under-determined one (fewer). The point is that ZeroDimSolver does the right thing – and
tells you clearly – in each case.
A pleasant square system¶
A square system has exactly one equation per variable, and generically finitely many isolated solutions. Take the unit circle meeting the line \(x = y\):
import bertini
from bertini import ZeroDimSolver
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 1) # the unit circle
sys.add_function(x - y) # the line x = y
solver = ZeroDimSolver(sys, mptype='adaptive')
solver.solve()
There are two intersection points, \(\pm(1/\sqrt2,\, 1/\sqrt2)\):
assert solver.was_randomized() is False # already square -- nothing to do
assert len(solver.finite_solutions()) == 2
An over-determined system: extra equations, extraneous solutions filtered out¶
An over-determined system has more equations than variables. It can still have isolated
solutions – the points where all the equations vanish at once – but a start system needs a
square target to track. ZeroDimSolver handles this for you: it randomizes the system down
to square (replacing the \(N\) equations by \(n\) generic combinations of them), solves the
square system, and then filters out the extraneous solutions that the squaring introduces by
re-checking each one against your original equations.
Three equations in two variables, whose only common solutions are \((1, 1)\) and \((-1, -1)\):
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 2)
sys.add_function(x - y)
sys.add_function(x*y - 1)
solver = ZeroDimSolver(sys, mptype='adaptive')
assert solver.was_randomized() is True # ZeroDimSolver squared it up for you
solver.solve()
The squared system has more endpoints than there are genuine solutions (the extras satisfy the
random combinations but not the original equations). all_solutions() shows them all;
solutions() returns only the genuine roots, and the extraneous ones – flagged
is_nonsolution in the metadata – are surfaced separately by nonsolutions():
n_all = len(solver.all_solutions()) # the squared system's full path count
assert n_all > 2
assert len(solver.solutions()) == 2 # only the two true common solutions
# the remaining endpoints are nonsolutions (finite, not solutions) or diverged to infinity; every endpoint
# is exactly one of genuine solution / nonsolution / at-infinity:
assert len(solver.solutions()) + len(solver.nonsolutions()) + len(solver.infinite_solutions()) == n_all
solutions() is the general getter: by default the finite genuine solutions, with keywords to
filter by category – solutions(real=False) for the complex ones, solutions(singular=False)
for the nonsingular ones, solutions(infinite=True) to also include the at-infinity endpoints,
solutions(nonsolution=True) to opt the nonsolutions back in. You can inspect the exact combinations the
squaring used with solver.randomization_matrix().
An under-determined system: a helpful refusal¶
An under-determined system has fewer equations than variables, so its solution set is
positive-dimensional – a curve, a surface, … – not a finite set of points. ZeroDimSolver
computes isolated solutions only, so it declines, with an error that says why rather than quietly
returning garbage:
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 1) # one equation, two variables: a whole curve
try:
ZeroDimSolver(sys, mptype='adaptive')
raise AssertionError("expected ZeroDimSolver to refuse an under-determined system")
except RuntimeError as e:
assert 'under-determined' in str(e)
The same refusal protects you from a subtler case: a system that is square by equation count but
whose solution set is still positive-dimensional (for instance a repeated equation). ZeroDimSolver
checks the Jacobian rank at a generic point and raises if the solution set cannot be
zero-dimensional.
Complete example¶
The whole tutorial as one runnable script – assemble nothing, just run it:
"""Solving a system with ZeroDimSolver -- Bertini 2 tutorial.
Demonstrates ZeroDimSolver on a square, an over-determined, and an
under-determined polynomial system.
Run: python zerodim_solver.py
"""
import bertini
from bertini import ZeroDimSolver
def square_system():
"""A pleasant square system: the unit circle meeting the line x = y."""
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 1) # the unit circle
sys.add_function(x - y) # the line x = y
solver = ZeroDimSolver(sys, mptype='adaptive')
solver.solve()
assert solver.was_randomized() is False # already square -- nothing to do
assert len(solver.finite_solutions()) == 2
return solver
def over_determined_system():
"""An over-determined system: extra equations, extraneous solutions filtered out."""
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 2)
sys.add_function(x - y)
sys.add_function(x*y - 1)
solver = ZeroDimSolver(sys, mptype='adaptive')
assert solver.was_randomized() is True # ZeroDimSolver squared it up for you
solver.solve()
n_all = len(solver.all_solutions()) # the squared system's full path count
assert n_all > 2
assert len(solver.solutions()) == 2 # only the two true common solutions
# the remaining endpoints are nonsolutions (finite, not solutions) or diverged to infinity; every endpoint
# is exactly one of genuine solution / nonsolution / at-infinity:
assert len(solver.solutions()) + len(solver.nonsolutions()) + len(solver.infinite_solutions()) == n_all
return solver
def under_determined_system():
"""An under-determined system: a helpful refusal."""
x, y = bertini.Variable('x'), bertini.Variable('y')
sys = bertini.System()
sys.add_variable_group(bertini.VariableGroup([x, y]))
sys.add_function(x**2 + y**2 - 1) # one equation, two variables: a whole curve
try:
ZeroDimSolver(sys, mptype='adaptive')
raise AssertionError("expected ZeroDimSolver to refuse an under-determined system")
except RuntimeError as e:
assert 'under-determined' in str(e)
def main():
square_system()
over_determined_system()
under_determined_system()
if __name__ == '__main__':
main()