Skip to content

Commit 800e54c

Browse files
committed
Added an implementation of formal power series
1 parent eb19f66 commit 800e54c

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

src/FormalPowerSeries.jl

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
#!/usr/bin/env julia
2+
#
3+
# Excursions in formal power series (fps)
4+
#
5+
# Jiahao Chen <[email protected]> 2013
6+
#
7+
# The primary reference is
8+
# [H] Peter Henrici, "Applied and Computational Complex Analysis", Volume I:
9+
# Power Series---Integration---Conformal Mapping---Location of Zeros,
10+
# Wiley-Interscience: New York, 1974
11+
12+
import Base.inv, Base.length
13+
14+
# Definition [H, p.9] - we limit this to finitely many coefficients
15+
type FormalPowerSeries{Coefficients}
16+
#TODO this really should be a sparse vector
17+
c :: Vector{Coefficients}
18+
FormalPowerSeries{Coefficients <: Number}(c::Vector{Coefficients}) = new(c)
19+
end
20+
21+
#Comparisons allowed between fps and Vector
22+
function =={T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
23+
a, b = P.c, Q.c
24+
n1, n2 = length(a), length(b)
25+
#Pad shorter coefficient list with zeros
26+
(n1 > n2) ? (b = [b; zeros(T, n1 - n2)]) : (a = [a; zeros(T, n2 - n1)])
27+
return all([a[i] == b[i] for i=1:max(n1, n2)])
28+
end
29+
30+
function =={T}(P::Vector{T}, Q::FormalPowerSeries{T})
31+
a, b = P, Q.c
32+
n1, n2 = length(a), length(b)
33+
#Pad shorter coefficient list with zeros
34+
(n1 > n2) ? (b = [b; zeros(T, n1 - n2)]) : (a = [a; zeros(T, n2 - n1)])
35+
return all([a[i] == b[i] for i=1:max(n1, n2)])
36+
end
37+
38+
function =={T}(P::FormalPowerSeries{T}, Q::Vector{T})
39+
a, b = P.c, Q
40+
n1, n2 = length(a), length(b)
41+
#Pad shorter coefficient list with zeros
42+
(n1 > n2) ? (b = [b; zeros(T, n1 - n2)]) : (a = [a; zeros(T, n2 - n1)])
43+
return all([a[i] == b[i] for i=1:max(n1, n2)])
44+
end
45+
46+
# Basic housekeeping and properties
47+
48+
# Remove extraneous zeros
49+
function trim{T}(P::FormalPowerSeries{T})
50+
NumExtraZeros::Integer = 0
51+
for k=length(P.c):-1:2
52+
P.c[k]==convert(T, 0) ? (NumExtraZeros+=1) : break
53+
end
54+
return FormalPowerSeries{T}(copy(P.c[1:end-NumExtraZeros]))
55+
end
56+
57+
length{T}(P::FormalPowerSeries{T})=length(P.c)
58+
59+
# Basic arithmetic [H, p.10]
60+
function +{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
61+
a, b = P.c, Q.c
62+
n1, n2 = length(a), length(b)
63+
#Pad shorter coefficient list with zeros
64+
(n1 > n2) ? (b = [b; zeros(T, n1 - n2)]) : (a = [a; zeros(T, n2 - n1)])
65+
FormalPowerSeries{T}(a + b)
66+
end
67+
68+
function -{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
69+
a, b = P.c, Q.c
70+
n1, n2 = length(a), length(b)
71+
#Pad shorter coefficient list with zeros
72+
(n1 > n2) ? (b = [b; zeros(T, n1 - n2)]) : (a = [a; zeros(T, n2 - n1)])
73+
FormalPowerSeries{T}(a - b)
74+
end
75+
76+
#negation
77+
-{T}(P::FormalPowerSeries{T}) = FormalPowerSeries{T}(-P.c)
78+
79+
#multiplication by scalar
80+
*{T}(P::FormalPowerSeries{T}, k::Number) = FormalPowerSeries{T}(P.c * k)
81+
*{T}(k::Number, P::FormalPowerSeries{T}) = FormalPowerSeries{T}(P.c * k)
82+
83+
#Cauchy product [H, p.10]
84+
#
85+
#TODO This is actually a discrete convolution so it should be amenable
86+
# to an FFT algorithm
87+
*{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T}) = CauchyProduct(P, Q)
88+
function CauchyProduct{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
89+
a, b = P.c, Q.c
90+
n1, n2 = length(a), length(b)
91+
c = zeros(T, n1+n2-1)
92+
for i1=1:n1
93+
for i2=1:n2
94+
c[i1+i2-1] += a[i1]*b[i2]
95+
end
96+
end
97+
FormalPowerSeries{T}(c)
98+
end
99+
100+
#Hadamard product [H, p.10] - the elementwise product
101+
.*{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T}) = HadamardProduct(P, Q)
102+
function HadamardProduct{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
103+
a, b = P.c, Q.c
104+
n1, n2 = length(a), length(b)
105+
n1>n2 ? (a=a[1:n2]) : (b=b[1:n1])
106+
FormalPowerSeries{T}(a.*b)
107+
end
108+
109+
#The identity element over the complex numbers
110+
function eye{T <: Number}(P::FormalPowerSeries{T})
111+
FormalPowerSeries{T}([convert(T, 0), convert(T, 1)])
112+
end
113+
114+
isunit{T <: Number}(P::FormalPowerSeries{T}) = P==eye(P)
115+
116+
# [H, p.12]
117+
isnonunit{T}(P::FormalPowerSeries{T}) = P.c[1]==0 && !isunit(P)
118+
119+
#Constructs the top left m x m block of the (infinite) semicirculant matrix
120+
#associated with the fps [H, Sec.1.3, p.14]
121+
#[H] calls it the semicirculant, but in contemporary nomenclature this is an
122+
#upper triangular Toeplitz matrix
123+
#This constructs the dense matrix - Toeplitz matrices don't exist in Julia yet
124+
function MatrixForm{T}(P::FormalPowerSeries{T}, m :: Integer)
125+
m<0 ? error(sprintf("Invalid matrix dimension %d requested", m)) : true
126+
127+
a = P.c
128+
n = length(a)
129+
M = sum([diagm(repmat(ones(T,1,1)*a[i], 1, m+1-i), i-1) for i=1:min(m, n)])
130+
end
131+
132+
#Reciprocal of power series [H, Sec.1.3, p.15]
133+
#
134+
#This algorithm is NOT that in [H], since it uses determinantal inversion
135+
#formulae for the inverse. It is much quicker to find the inverse of the
136+
#associated infinite triangular Toeplitz matrix!
137+
#
138+
#Every fps is isomorphic to such a triangular Toeplitz matrix [H. Sec.1.3, p.15]
139+
#
140+
#As most reciprocals of finite series are actually infinite,
141+
#allow the inverse to have a finite truncation
142+
#
143+
#TODO implement the FFT-based algorithm of one of the following
144+
# doi:10.1109/TAC.1984.1103499
145+
# https://cs.uwaterloo.ca/~glabahn/Papers/tamir-george-1.pdf
146+
function inv{T}(P::FormalPowerSeries{T}, Size :: Integer)
147+
Size<0 ? error(sprintf("Invalid inverse truncation length %d requested", Size)) : true
148+
149+
a = Size>=length(P) ? [P.c; zeros(T, Size-length(P))] : P.c[1:Size]
150+
n = length(a)
151+
a[1] == 0 ? (error("Inverse does not exist")) : true
152+
inv_a1 = T<:Number ? 1.0/a[1] : inv(a[1])
153+
154+
b = zeros(n) #Inverse
155+
b[1] = inv_a1
156+
for k=2:n
157+
b[k] = -inv_a1 * sum([b[k+1-i] * a[i] for i=2:k])
158+
end
159+
FormalPowerSeries{T}(b)
160+
end
161+
162+
163+
#Derivative of fps [H. Sec.1.4, p.18]
164+
function derivative{T}(P::FormalPowerSeries{T})
165+
a = P.c
166+
n = length(a)
167+
FormalPowerSeries{T}(n==1 ? [convert(T, 0)]: [(k-1)*a[k] for k=2:n])
168+
end
169+
170+
function isconstant{T <: Number}(P::FormalPowerSeries{T})
171+
length(P.c)==1 || all([c==0 for c in P.c[2:end]])
172+
end
173+
174+
#[H, Sec.1.4, p.18]
175+
isconst{T}(P::FormalPowerSeries{T}) = length(P)==1 || all([x==0 for x in P[2:end]])
176+
177+
178+
# Power of fps [H, Sec.1.5, p.35]
179+
function ^{T}(P::FormalPowerSeries{T}, n :: Integer)
180+
if n == 0
181+
return FormalPowerSeries{T}([convert(T, 1)])
182+
elseif n > 1
183+
#The straightforward recursive way
184+
return P^(n-1) * P
185+
#TODO implement the non-recursive formula
186+
elseif n==1
187+
return P
188+
else
189+
error(@sprintf("I don't know what to do for power n = %d", n))
190+
end
191+
end
192+
193+
194+
# Composition of fps [H, Sec.1.5, p.35]
195+
#TODO this doesn't work in infix notation yet
196+
#(⋅){T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T}) = compose(P, Q)
197+
function compose{T}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T})
198+
a, b = P.c, Q.c
199+
n, m = length(a), length(b)
200+
201+
l = (m-1)*(n-1)+1
202+
bb = zeros(T, n, l)
203+
QQ = FormalPowerSeries{T}([convert(T, 1)])
204+
for i=1:n
205+
bb[i, 1:length(QQ.c)] = QQ.c
206+
QQ *= Q
207+
end
208+
for i=1:n
209+
bb[i, :] *= a[i]
210+
end
211+
212+
return FormalPowerSeries{T}(sum(bb, 1)'[:,1])
213+
end
214+
215+
216+
#[H, p.45]
217+
isalmostunit{T}(P::FormalPowerSeries{T}) = length(P) > 1 && P.c[1]==0 && P.c[2] != 0
218+
###########
219+
# Sandbox #
220+
###########
221+
222+
MaxSeriesSize=50
223+
MaxRange = 1000
224+
MatrixSize=150
225+
226+
(n1, n2, n3) = int(rand(3)*MaxSeriesSize)
227+
228+
X = FormalPowerSeries{Int64}(int(rand(n1)*MaxRange))
229+
Y = FormalPowerSeries{Int64}(int(rand(n2)*MaxRange))
230+
Z = FormalPowerSeries{Int64}(int(rand(n3)*MaxRange))
231+
232+
c = int(rand()*MatrixSize) #Size of matrix representation to generate
233+
234+
235+
#Test very basic things
236+
@assert length(X) == length(X.c)
237+
@assert length(Y) == length(Y.c)
238+
239+
TT = typeof(X.c[1])
240+
nzeros = int(rand()*MaxSeriesSize)
241+
@assert trim(FormalPowerSeries{TT}([X.c; zeros(TT, nzeros)])) == X
242+
@assert trim(FormalPowerSeries{TT}([Y.c; zeros(TT, nzeros)])) == Y
243+
244+
#padded vectors for later testing
245+
Xv = [X.c; zeros(TT, max(0, length(Y.c)-length(X.c)))]
246+
Yv = [Y.c; zeros(TT, max(0, length(X.c)-length(Y.c)))]
247+
248+
#Test addition, p.15, (1.3-4)
249+
@assert (X+X).c == 2X.c
250+
@assert (X+Y).c == Xv + Yv
251+
@assert (Y+X).c == Yv + Xv
252+
@assert (Y+Y).c == 2Y.c
253+
@assert MatrixForm(X+Y,c) == MatrixForm(X,c)+MatrixForm(Y,c)
254+
255+
#Test subtraction, p.15, (1.3-4)
256+
@assert (X-X).c == 0X.c
257+
@assert (X-Y).c == Xv - Yv
258+
@assert (Y-X).c == Yv - Xv
259+
@assert (Y-Y).c == 0Y.c
260+
@assert MatrixForm(X-Y,c) == MatrixForm(X,c)-MatrixForm(Y,c)
261+
262+
#Test multiplication, p.15, (1.3-5)
263+
@assert MatrixForm(X*X,c) == MatrixForm(X,c)*MatrixForm(X,c)
264+
@assert MatrixForm(X*Y,c) == MatrixForm(X,c)*MatrixForm(Y,c)
265+
@assert MatrixForm(Y*X,c) == MatrixForm(Y,c)*MatrixForm(X,c)
266+
@assert MatrixForm(Y*Y,c) == MatrixForm(Y,c)*MatrixForm(Y,c)
267+
268+
@assert X.*X == Xv.*Xv
269+
@assert X.*Y == Xv.*Yv
270+
@assert Y.*X == Yv.*Xv
271+
@assert Y.*Y == Yv.*Yv
272+
273+
#Test reversion of series
274+
#The reciprocal series has associated matrix that is the matrix inverse
275+
#of the original series
276+
#@assert inv(float(MatrixForm(X,c)))[1, :]'[:, 1] == inv(X, c)
277+
278+
#Test differentiation
279+
XX = X
280+
for i =1:length(X)-1
281+
XX = derivative(XX)
282+
end
283+
@assert XX == [X.c[end]*factorial(length(X)-1)]
284+
285+
#Test product rule [H, Sec.1.4, p.19]
286+
@assert derivative(X*Y) == derivative(X)*Y + X*derivative(Y)
287+
288+
#Test composition against the one-liner
289+
compose_quickanddirty{T <: Number}(P::FormalPowerSeries{T}, Q::FormalPowerSeries{T}) = sum([P.c[i] * Q^(i-1) for i=1:length(P.c)])
290+
291+
#@assert ⋅(X,Y) == compose(X, Y)
292+
@assert compose(X, X) == compose_quickanddirty(X, X)
293+
@assert compose(X, Y) == compose_quickanddirty(X, Y)
294+
@assert compose(Y, X) == compose_quickanddirty(Y, X)
295+
@assert compose(Y, Y) == compose_quickanddirty(Y, Y)
296+
297+
#Test right distributive law of composition [H, Sec.1.6, p.38]
298+
@assert compose(X,Z)*compose(Y,Z) == compose(X*Y, Z)
299+
300+
#Test chain rule [H, Sec.1.6, p.40]
301+
@assert derivative(compose(X,Y)) == compose(derivative(X),Y)*derivative(Y)

src/RandomMatrices.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ module RandomMatrices
77
include("GaussianEnsembleSamples.jl")
88
#Classical univariate distributions
99
include("GaussianDensities.jl")
10+
#Tracy-Widom distribution
11+
include("TracyWidom.jl")
1012

1113
#Fast histogrammer for matrix eigenvalues
1214
include("FastHistogram.jl")
15+
16+
#Formal power series
17+
include("FormalPowerSeries.jl")
1318
end

0 commit comments

Comments
 (0)