Our first order of business will be to define the setting in which we will be working. Since we will be dealing with univariate polynomials over the integers, we will define the ring $R$ to be precisely those univariate polynomials, with the following simple command:
Next, we let Sage know how to define the family of polynomials we will be working with. Courtesy of an elementary graph theoretic result governing independence polynomials, the following recursively-defined family gives set of independence polynomials of a particular family of path-like graphs:
max_n = 10
b(x) = (3*x+1)*(2*x+1)*(x+1)
c(x) = 2*x+1
def p(n):
if n == 1:
return b(x)+x*c(x)
else:
if n == 2:
return b(x)*p(1)+x*c(x)*b(x)
else:
return b(x)*(p(n-1)+x*c(x)*p(n-2))
first_few_ps = [p(n).expand() for n in range(1,max_n+1)]
first_few_ps
Note the structure of the "if/else" statements in the above block of code, as well as the structure of a method definition.
Now that we have our polynomials, let's do something with them! Each of the three blocks of code below defines a test to be performed on an input list of coefficients. The first tests for logarithmic concavity, a condition met if the terms $a_n$ in the input sequence all satisfy $a_n^2 \geq a_{n-1}a_{n+1}$:
def lc_test(L):
for n in range(1,len(L)-1):
if L[n-1]*L[n+1]>L[n]^2:
return False
return True
In the above code we've had an encounter with the "for" loop, as it appears in Python and Sage.
Just as easy to test is the condition of symmetry:
def symmetry_test(L):
for n in range(0,len(L)-1):
if L[n]!=L[len(L)-1-n]:
return False
return True
Checking for unimodality (the presence of a single peak in the input coefficients) is a little trickier, but still elementary. We introduce a third control structure in this block, Sage's version of the "while" loop:
def unimodality_test(L):
n = 0
while n<=len(L)-2 and L[n]<=L[n+1]:
n = n+1
if n == len(L)-1:
return True
else:
while n<=len(L)-2 and L[n]>=L[n+1]:
n = n+1
if n == len(L)-1:
return True
else:
return False
Let's put 'em to a road test! First we'll generate the first so-and-so many members of our family of polynomials. Note how Sage recognizes that the polynomials we're generating are indeed elements of the polynomial ring over the intgers, $\mathbb{Z}[x]$, and therefore we're able to make use of the built-in "coefficients()" method that such objects entail.
firstNpolys = [p(n) for n in range(maxN)]
coeffs = [firstNpolys[n].coefficients() for n in range(maxN)]
print firstNpolys
print coeffs
Just to show off Sage's intellectual prowess, let's ask it where the first element we generated lives:
Finally, let's run our combinatorial tests on the polynomials we've generated. Based upon the output, what sort of conjectures would you feel comfortable making at this point?
lc = [lc_test(coeffs[n]) for n in range(maxN)]
print lc
uni = [unimodality_test(coeffs[n]) for n in range(maxN)]
print uni
sym = [symmetry_test(coeffs[n]) for n in range(maxN)]
print sym