(* "Copyright (c) 1996 The Regents of the University of California. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without written agreement is hereby granted, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS." Author: OMER EGECIOGLU (omer@cs.ucsb.edu) Version: 1 Creation Date: May 1996 Filename: DiophantineGF.m *) DiophantineGF[m_, b_, c_] := (* Input is of the form a, b, c where a is the rxs input matrix, b and c are rx1 vectors, and the triple corresponds to the diophantine systems ax = bn+c, x>=0. *) ( s = Length[m[[1]]]; (* s is the number of variables = the number of columns of the nonaugmented a. *) r = Length[Transpose[m][[1]]]; (* r is the number of equations = the number of rows of the nonaugmented a. *) temp = Transpose[Append[Transpose[m], -b]]; (* Add another column to m, originally rxs. This is the s+1 column, and consists of -b. *) texps = Table[0, {s + 1}]; texps[[-1]] = 1; a = Prepend[temp, texps]; (* Add an initial row marking t's: 0 0 0 ... 0 1. *) lambda = Array[l, r]; (* l[1], ... , l[r] are the auxilary variables. *) fudgefactor = Product[lambda[[i]]^(-c[[i]]), {i, 1, r}]; IterationM = Max[Abs[c]]; (* When power series involving mixed terms (l[i]s and possibly t's) are expanded, the series can be truncated past power IterationM. IterationM is only used as an upper bound, not the exact cutoff point. *) gf = 0; sign = +1; CheckAbort[recurse[a, sign], gf= Infinity]; (* Ordinarily, this statement would be "recurse[a, sign];" only to start the computation at the root of the tree. However, when infinite number of solutions are detected via the statement "If[checkinfiniteQ[a, u, v], Abort[ ] ];" in procedure recurse, all computation/recursion is aborted, and the control returns to assign gf= Infinity in this statement. *) gf = Factor[gf] (* Final output line. *) ) checkinfiniteQ[a_, u_, v_ ]:= (a[[1]][[u]] == a[[1]][[v]] == 0) && (!zerocolumnQ[a, u]) && (zerocolumnQ[ addcolumn[a, u, v], u ]) (* checkinfiniteQ: returns True if the u-th and the v-th columns of a have opposite signs (and nonzero). The t terms should not be there, this means the entries in the first row must be zero. The rows that are processed by the called functions are in question are 2 through r+1. *) prunetreeQ[a_] := Module[{i}, prun = False; For[i = 2, i <= r+1, i++, If[ (Max[ a[[i]] ] < 0 && c[[i-1]] > 0) || (Min[ a[[i]] ] > 0 && c[[i-1]] > 0), prun = True] ]; prun ] (* prunetreeQ: returns True if the recursion can be stopped at a. *) truncateexp[a_, j_] := Module[{i}, minimum= IterationM; For[i = 2, i <= r + 1, i++, If[a[[i]][[j]] != 0 && minimum > Floor[c[[i-1]]/a[[i]][[j]] ], minimum = Floor[ c[[i-1]]/ a[[i]][[j]] ] ] ];minimum] (* truncateexp: returns the exponent past which the term coming from column j of the leaf matrix a can have no contribution. This exponent is the minimum of lambda_i/c_i. Returned in minimum. *) zerocolumnQ[a_, j_] := Module[{i}, ans := True; For[i = 2, i <= r + 1, i++, If[a[[i]][[j]] != 0, ans := False]]; ans]; (* zerocolumnQ: checks to see if the entries in the j-th column of the augmented matrix a are all zeros, excepting the first row. Thus the row indices run from 2 to r+1. *) cleanrowQ[a_, i_] := Max[a[[i]]] <= 0 || Min[a[[i]]] >= 0; (* cleanrowQ: returns True iff the i-th row of a is all nonnegative or all nonpositive (i.e. "clean"). All zeros return True. *) uindex[a_, i_]:= Min[ Position [ a[[i]], Min[a[[i]]] ] ]; vindex[a_, i_]:= Min[ Position [ a[[i]], Max[a[[i]]] ] ]; (* uindex: scan the i-th row. Return the first index of a minimum element of the row, vindex: scan the i-th row. Return the first index of a maximum element of the row. The elements returned are distinct if cleanrowQ[a,i] = False. *) leafnode[a_] := Module[{i}, i = 2; While[i <= r + 1 && cleanrowQ[a, i], i++]; dirtyrow = i]; (* leafnode: returns the index of the first dirty row in the augmented matrix a. The relevant rows of a examined are 2 through r+1. The function returns r+2 if all the rows are clean, i.e. a is a leaf node of the recursion. *) addcolumn[a_, u_, v_] := (at = Transpose[a]; at[[u]] = at[[u]] + at[[v]]; Transpose[at]); (* addcolumn: returns a matrix which is obtained from a by adding its v-th column to its u-the column. *) zapcolumn[a_, v_] := Transpose[Drop[Transpose[a], {v, v}]]; (* zapcolumn: returns the matrix obtained by omitting the v-th column of a. *) help:= ( Print[" "]; Print["DiophantineGF[a, b, c]: Calculates the rational generating function"]; Print[" and the formula for the number of solutions to the linear "]; Print[" Diophantine system ax= bn+c, where the right hand side is "]; Print[" parametrized by n = 0, 1, 2, ... "]; Print[" "]; Print[" Inputs: a : rxs integral matrix, "]; Print[" b and c : rx1 integral vectors."]; Print[" "]; Print[" The unknowns x_1, x_2, ..., x_s are nonnegative integers."]; Print[" "]; Print[" After the computation of the generating function, typing"]; Print[" \"formula\" will generate the formula for the coefficient of "]; Print[" t^n in terms of the binomial coefficients C[x, k] where "]; Print[" "]; Print[" C[x, k] = Binomial[x, k] if x is a positive integer,"]; Print[" = zero otherwise."]; Print[" "]; Print[" A power basis expansion is also given if no argument x"]; Print[" is fractional in the formula found."]; Print[" "]; Print[" After the formula has been calculated, \"formulaN[n]\""]; Print[" will evaluate it for a specific argument n."]; ) example:= ( Print[" "]; Print["EXAMPLE I:"]; Print["---------- "]; Print[" "]; Print[" In[2]:= a = {{1, 1, 1, 1, 1}}; b = {2}; c = {-1};"]; Print[" "]; Print[" In[3]:= DiophantineGF[a, b, c]"]; Print[" "]; Print[" 2"]; Print[" t (5 + 10 t + t )"]; Print[" Out[3]= -----------------"]; Print[" 5"]; Print[" (1 - t)"]; Print[" "]; Print[" In[4]:= formula"]; Print[" "]; Print[" Binomial Formula : C[1 + n, 4] + 10 C[2 + n, 4] + 5 C[3 + n, 4]"]; Print[" "]; Print[" n (1 + n) (1 + 2 n) (3 + 2 n)"]; Print[" Power Formula : -----------------------------"]; Print[" 6"]; Print[" "]; Print[" In[5]:= formulaN[3]"]; Print[" Out[5]= 126"]; Print[" "]; Print["-----------"]; Print[" Type \"example2\" for another example."]; ) example2:= ( Print[" "]; Print["EXAMPLE II: "]; Print["-----------"]; Print[" "]; Print[" In[2]:= a = {{1, 0, 0, 1, 0, 0}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 1}};"]; Print[" In[3]:= b = {1, 1, 1}; c = {-1, -1, -1};"]; Print[" "]; Print[" In[4]:= DiophantineGF[a, b, c]"]; Print[" "]; Print[" 2"]; Print[" t (1 + 4 t + t )"]; Print[" Out[4]= ----------------"]; Print[" 4"]; Print[" (-1 + t)"]; Print[" "]; Print[" In[5]:= formula"]; Print[" "]; Print[" Binomial Formula : C[n, 3] + 4 C[1 + n, 3] + C[2 + n, 3]"]; Print[" "]; Print[" 3"]; Print[" Power Formula : n"]; Print[" "]; Print[" In[6]:= formulaN[5]"]; Print[" Out[6]= 125"]; Print[" "]; Print["-----------"]; Print[" Type \"example3\" for another example."]; ) example3:= ( Print[" "]; Print["EXAMPLE III:"]; Print["------------ "]; Print[" "]; Print[" In[2]:= a = {{1, -1, 2}, {1, 0, 3}}; b = {1, 2}; c = {1, -2};"]; Print[" "]; Print[" In[3]:= DiophantineGF[a, b, c]"]; Print[" "]; Print[" 3 2 3 "]; Print[" t (1 + t ) (1 + t - t )"]; Print[" Out[3]= ------------------------"]; Print[" 2 2 "]; Print[" (-1 + t) (1 + t + t ) "]; Print[" "]; Print[" In[4]:= Series[%, {t, 0, 9}]"]; Print[" "]; Print[" 3 4 5 6 7 8 9 10"]; Print[" Out[4]= t + 2 t + 3 t + 4 t + 5 t + 5 t + 6 t + O[t]"]; Print[" "]; Print[" In[5]:= formula"]; Print[" "]; Print[" Binomial Formula : "]; Print[" "]; Print[" -10 + n -8 + n -7 + n"]; Print[" > (C[-------, 0] - C[------, 0] + C[-8 + n, 1] - C[------, 0] - "]; Print[" 3 3 3"]; Print[" "]; Print[" -6 + n -4 + n"]; Print[" > 2 C[-7 + n, 1] - C[------, 0] - C[-5 + n, 1] + C[------, 0] + "]; Print[" 3 3"]; Print[" "]; Print[" -3 + n"]; Print[" > C[-4 + n, 1] + C[------, 0] + C[-3 + n, 1] + 2 C[-2 + n, 1]) / 3"]; Print[" 3"]; Print[" "]; Print[" In[6]:= formulaN[7]"]; Print[" Out[6]= 5"]; Print[" "]; Print[" In[7]:= formulaN[9]"]; Print[" Out[7]= 6"]; Print[" "]; Print[" In[8]:= formulaN[10^20 - 3]"]; Print[" Out[8]= 66666666666666666665"]; Print[" "]; Print[" In[9]:= formulaN[10^20 - 2]"]; Print[" Out[9]= 66666666666666666665"]; Print[" "]; Print[" In[10]:= formulaN[10^20 - 1]"]; Print[" Out[10]= 66666666666666666666"]; ) recurse[a_, sign_] := Module[{a1, a2, a3, u, v, dirtyrow}, If[ !prunetreeQ[a], dirtyrow = leafnode[a]; If[dirtyrow == r + 2, updategf[a, sign], (u = uindex[a, dirtyrow]; v = vindex[a, dirtyrow]; If[checkinfiniteQ[a, u, v], Abort[ ] ]; a1 = addcolumn[a, u, v]; a2 = addcolumn[a, v, u]; a3 = zapcolumn[a1, v]; recurse[a1, sign]; recurse[a2, sign]; recurse[a3, -sign]; )] ] ] updategf[a_, sign_] := ( tpart = mixedpart = 1; For[j = 1, j <= Length[a[[1]]], j++, If[zerocolumnQ[a, j], tpart = tpart/(1 - t^a[[1]][[j]]), ls = Product[lambda[[k - 1]]^a[[k]][[j]], {k, 2, r + 1}]; mixedmono = t^a[[1]][[j]]*ls; maxexp = truncateexp[a, j]; mixedpart = mixedpart*Sum[mixedmono^k, {k, 0, maxexp}]; ]; ]; mixedpart = mixedpart*fudgefactor; For[k = 1, k <= r, k++, mixedpart = Coefficient[mixedpart, lambda[[k]], 0] ]; gf = gf + tpart*mixedpart*sign; (* If the form of how the gf is accumulated needs to be controlled, then this is the line where the changes can be made. *) ); (* updategf: updates the gf for a leaf matrix a. The contribution of a column with d in the first row and zeros below is 1/(1-t^d). The product of such terms over the whole matrix is tpart. Otherwise raise l[i] to the power of the i-th entry in the row, multiply these out, call it ls. Note the indexing of l[] and the rows are shifted by one, since a is augmented. If W = mixedpart denotes this product multiplied further by t^d (which may or may not be 1), then mixedmono is 1+ W + .. + W^maxexp. The product of these terms over the whole matrix is mixedpart. After that adjust mixedpart by fudgefactor. The final For loop discards all the terms in mixedpart that contains some power of a l[], and leaves a polynomial in t. This polynomial multiplied by tpart is the contribution of this leaf matrix to the gf. *) formula := (* Compute the formula as a function of n for from the expansion of the generating function gf. C[x,k] stands for Binomial[x,k] if x is an integer, and zero otherwise. *) Module[ {i,j, gfden, gfnum, pi, ei, temp, partnum, expn, maxexpn }, gfden = Denominator[gf]; gfnum = Numerator[gf]; binomialformula = 0; powerformula = 0; maxexpn = 0; factors = Complement[FactorList[gfden], FactorList[1]]; (* This trick is to fix an incompatibility between versions 2.2 and 3.0 of Mathematica. In V2.2 FactorList[(-1 + t)^2]={{-1 + t, 2}} (what is used here) whereas in V3.0 FactorList[(-1 + t)^2] = {{1, 1}, {-1 + t, 2}}. The Complement function removes {1,1} if it is produced in FactorList[gfden] if running V3.0. *) nooffactors= Length[ factors ]; partfrac = Apart[ 1/gfden ]; For[i=1, i <= nooffactors, i++, pi = factors[[i]][[1]]; ei = factors[[i]][[2]]; (* i-th factor of the denominator and its exponent *) temp = Select[partfrac, PolynomialQ[ Factor[# pi^ei], t]&]; (* This function picks all terms in the partial fraction expansion of 1/gfden where the denominator is a divisor of the i-th factor in gfden. This factor is pi^ei, where pi(t) is a cyclotomic polynomial *) partnum = gfnum * pi^ei * temp; (* partnum is the numerator of 1/pi^ei in the expansion of the whole generating function. Now multiply by an appropriate factor to make sure that it is of the form 1/(1-t^di)^ei. *) For[expn= 1, PolynomialRemainder[1-t^expn, pi, t] =!= 0, expn++ ]; If[ expn > maxexpn, maxexpn = expn]; partnum = Factor[ partnum * ((1-t^expn)/pi)^ei ]; partnum = Expand[ partnum]; (* This last partnum is the actual numerator polynomial. The corresponding denominator is (1-t^expn)^ei . *) numdegree = Exponent[ partnum, t]; For[j=0, j <= numdegree, j++, binomialformula = binomialformula + Coefficient[partnum, t, j]*C[ei + (n -j)/expn -1, ei-1]; powerformula = powerformula + Coefficient[partnum, t, j]*Binomial[ei + (n -j)/expn -1, ei-1]; ]; ]; binomialformula = Factor[binomialformula]; Print[" "];Print["Binomial Formula : ", binomialformula]; If[maxexpn == 1, powerformula = Factor[powerformula]; Print[" "]; Print["Power Formula : ", powerformula]; ] ] B[x_, y_] := If[ Positive[x] && IntegerQ[x], Binomial[x, y], 0]; formulaN[v_] := (substitute = binomialformula /. {n -> v, C -> B}; Simplify[substitute]); Print["Loaded. Type \"help\" for instructions, \"example\" for examples."];