|
| 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) |
0 commit comments