โ™ป๏ธ Evaluation of cyclic-\(n\) polynomialsยถ

Bertini is software for algebraic geometry. This means we work with systems of polynomials, a critical component of which is system and function evaluation.

Bertini2 allows us to set up many kinds of functions, and thus systems, by exploting operator overloading.

Make some symbolsยถ

Letโ€™s start by making some variables, programmatically [1].

import bertini
import numpy

num_vars = 10
x = [None] * num_vars # preallocate the list
for ii in range(num_vars):
    x[ii] = bertini.Variable('x' + str(ii))

Huzzah, we have num_vars variables! This was hard to do in Bertini 1โ€™s classic style input files. Now we can do it directly! ๐ŸŽฏ

Write a function to produce the cyclic \(n\) polynomials [DKK03].

def cyclic(vars):
    n = len(vars)
    f = [None] * len(vars)
    y = []
    for ii in range(2):
        for x in vars:
            y.append(x)

    for ii in range(n-1):
        f[ii] = numpy.sum( [numpy.prod(y[jj:jj+ii+1]) for jj in range(n)] )

    # the last one is minus one
    f[-1] = numpy.prod(vars)-1
    return f

Now we will make a System, and put the cyclic polynomials into it.

sys = bertini.System()

for f in cyclic(x):
    sys.add_function(f)

print(sys) # long screen output, i know
...
10 functions:
  f_0 = x0+x1+x2+x3+x4+x5+x6+x7+x8+x9
...
  f_9 = x0*x1*x2*x3*x4*x5*x6*x7*x8*x9-1
...

We also need to associate the variables with the system. Unassociated variables are left unknown, and retain their value until elsewhere set.

vg = bertini.VariableGroup()
for var in x:
        vg.append(var)
sys.add_variable_group(vg)

Letโ€™s simplify this. It will modify elements of the constructed function tree, even those held externally โ€“ Bertini uses shared pointers under the hood, so pay attention to where you re-use parts of your functions, because later modification of them without deep cloning will cause โ€ฆ modification elsewhere, too.

bertini.system.simplify(sys)

Now, letโ€™s evaluate it at the origin โ€“ all zeroโ€™s (0 is the default value for multiprecision complex numbers in Bertini2). The returned value should be all zeroโ€™s except the last entry, which should be -1.

s = numpy.zeros((10,), dtype=bertini.multiprec.complex_mp)
result = sys.eval(s)
assert complex(result[-1]) == -1                       # last cyclic function is (prod x) - 1
assert all(complex(v) == 0 for v in result[:-1])       # the rest vanish at the origin

Yay, all zeros, except the last one is -1. Huzzah.

Letโ€™s change the values of our vector, and re-evaluate.

for ii in range(num_vars):
        s[ii] = bertini.multiprec.complex_mp(ii)
result = sys.eval(s)

There is much more one can do, too! Please write the authors, particularly Silviana, for more.

Complete exampleยถ

The whole tutorial as one runnable script โ€“ assemble nothing, just run it:

evaluation_cyclic.pyยถ
"""Bertini 2 tutorial: Evaluation of cyclic-n polynomials.

Assembles all code fragments from the tutorial into one runnable program.
Run:  python evaluation_cyclic.py
"""

import bertini
import numpy


def make_symbols(num_vars=10):
    """Programmatically create num_vars variables x0..x{n-1}."""
    x = [None] * num_vars  # preallocate the list
    for ii in range(num_vars):
        x[ii] = bertini.Variable('x' + str(ii))
    return x


def cyclic(vars):
    """Produce the cyclic-n polynomials from a list of variables."""
    n = len(vars)
    f = [None] * len(vars)
    y = []
    for ii in range(2):
        for x in vars:
            y.append(x)

    for ii in range(n - 1):
        f[ii] = numpy.sum([numpy.prod(y[jj:jj + ii + 1]) for jj in range(n)])

    # the last one is minus one
    f[-1] = numpy.prod(vars) - 1
    return f


def build_system(x):
    """Make a System and put the cyclic polynomials into it."""
    sys = bertini.System()

    for f in cyclic(x):
        sys.add_function(f)

    print(sys)  # long screen output, i know
    return sys


def associate_variables(sys, x):
    """Associate the variables with the system via a VariableGroup."""
    vg = bertini.VariableGroup()
    for var in x:
        vg.append(var)
    sys.add_variable_group(vg)


def simplify_system(sys):
    """Simplify the system in place (modifies the shared function tree)."""
    bertini.system.simplify(sys)


def evaluate_at_origin(sys):
    """Evaluate at the origin; all zeros except the last entry, which is -1."""
    s = numpy.zeros((10,), dtype=bertini.multiprec.complex_mp)
    result = sys.eval(s)
    assert complex(result[-1]) == -1                   # last cyclic function is (prod x) - 1
    assert all(complex(v) == 0 for v in result[:-1])   # the rest vanish at the origin
    return s


def evaluate_at_new_point(sys, s, num_vars=10):
    """Change the values of the vector and re-evaluate."""
    for ii in range(num_vars):
        s[ii] = bertini.multiprec.complex_mp(ii)
    result = sys.eval(s)
    return result


def main():
    num_vars = 10
    x = make_symbols(num_vars)
    sys = build_system(x)
    associate_variables(sys, x)
    simplify_system(sys)
    s = evaluate_at_origin(sys)
    evaluate_at_new_point(sys, s, num_vars)


if __name__ == '__main__':
    main()