|
| 1 | +# Compute fast Walsh-Hadamard transforms (in natural, dyadic, or sequency order) |
| 2 | +# using FFTW, by interpreting them as 2x2x2...x2x2 DFTs. We follow Matlab's |
| 3 | +# convention in which ifwht is the unnormalized transform and fwht has a 1/N |
| 4 | +# normalization (as opposed to using a unitary normalization). |
| 5 | + |
| 6 | +module Hadamard |
| 7 | +export fwht, ifwht, fwht_natural, ifwht_natural, fwht_dyadic, ifwht_dyadic, hadamard |
| 8 | + |
| 9 | +importall Base.FFTW |
| 10 | +import FFTW.set_timelimit |
| 11 | +import FFTW.dims_howmany |
| 12 | +import FFTW.Plan |
| 13 | +import FFTW.execute |
| 14 | +import FFTW.complexfloat |
| 15 | +import FFTW.normalization |
| 16 | + |
| 17 | +power_of_two(n::Integer) = n > 0 && (n & (n - 1)) == 0 |
| 18 | + |
| 19 | +# A power-of-two dimension to be transformed is interpreted as a |
| 20 | +# 2x2x2x....x2x2 multidimensional DFT. This function transforms |
| 21 | +# each individual power-of-two dimension n into the corresponding log2(n) |
| 22 | +# DFT dimensions. If bitreverse is true, then the output is in |
| 23 | +# bit-reversed (transposed) order (which may not work in-place). |
| 24 | +function hadamardize(dims::Array{Int,2}, bitreverse::Bool) |
| 25 | + ntot = 0 |
| 26 | + for i = 1:size(dims,2) |
| 27 | + n = dims[1,i] |
| 28 | + if !power_of_two(n) |
| 29 | + throw(ArgumentError("non power-of-two Hadamard-transform length")) |
| 30 | + end |
| 31 | + ntot += trailing_zeros(n) |
| 32 | + end |
| 33 | + hdims = Array(Int,3,ntot) |
| 34 | + j = 0 |
| 35 | + for i = 1:size(dims,2) |
| 36 | + n = dims[1,i] |
| 37 | + is = dims[2,i] |
| 38 | + os = dims[3,i] |
| 39 | + log2n = trailing_zeros(n) |
| 40 | + krange = j+1:j+log2n |
| 41 | + for k in krange |
| 42 | + hdims[1,k] = 2 |
| 43 | + hdims[2,k] = is |
| 44 | + hdims[3,k] = os |
| 45 | + is *= 2 |
| 46 | + os *= 2 |
| 47 | + end |
| 48 | + if bitreverse |
| 49 | + hdims[3,krange] = fliplr(hdims[3,krange]) |
| 50 | + end |
| 51 | + j += log2n |
| 52 | + end |
| 53 | + return hdims |
| 54 | +end |
| 55 | + |
| 56 | +for (Tr,Tc,fftw,lib) in ((:Float64,:Complex128,"fftw",FFTW.libfftw), |
| 57 | + (:Float32,:Complex64,"fftwf",FFTW.libfftwf)) |
| 58 | + @eval function Plan_Hadamard(X::StridedArray{$Tc}, Y::StridedArray{$Tc}, |
| 59 | + region, flags::Unsigned, timelimit::Real, |
| 60 | + bitreverse::Bool) |
| 61 | + set_timelimit($Tr, timelimit) |
| 62 | + dims, howmany = dims_howmany(X, Y, [size(X)...], region) |
| 63 | + dims = hadamardize(dims, bitreverse) |
| 64 | + plan = ccall(($(string(fftw,"_plan_guru64_dft")),$lib), |
| 65 | + Ptr{Void}, |
| 66 | + (Int32, Ptr{Int}, Int32, Ptr{Int}, |
| 67 | + Ptr{$Tc}, Ptr{$Tc}, Int32, Uint32), |
| 68 | + size(dims,2), dims, size(howmany,2), howmany, |
| 69 | + X, Y, FFTW.FORWARD, flags) |
| 70 | + set_timelimit($Tr, NO_TIMELIMIT) |
| 71 | + if plan == C_NULL |
| 72 | + error("FFTW could not create plan") # shouldn't normally happen |
| 73 | + end |
| 74 | + return Plan(plan, X) |
| 75 | + end |
| 76 | + |
| 77 | + @eval function Plan_Hadamard(X::StridedArray{$Tr}, Y::StridedArray{$Tr}, |
| 78 | + region, flags::Unsigned, timelimit::Real, |
| 79 | + bitreverse::Bool) |
| 80 | + set_timelimit($Tr, timelimit) |
| 81 | + dims, howmany = dims_howmany(X, Y, [size(X)...], region) |
| 82 | + dims = hadamardize(dims, bitreverse) |
| 83 | + kind = Array(Int32, size(dims,2)) |
| 84 | + kind[:] = R2HC |
| 85 | + plan = ccall(($(string(fftw,"_plan_guru64_r2r")),$lib), |
| 86 | + Ptr{Void}, |
| 87 | + (Int32, Ptr{Int}, Int32, Ptr{Int}, |
| 88 | + Ptr{$Tr}, Ptr{$Tr}, Ptr{Int32}, Uint32), |
| 89 | + size(dims,2), dims, size(howmany,2), howmany, |
| 90 | + X, Y, kind, flags) |
| 91 | + set_timelimit($Tr, NO_TIMELIMIT) |
| 92 | + if plan == C_NULL |
| 93 | + error("FFTW could not create plan") # shouldn't normally happen |
| 94 | + end |
| 95 | + return Plan(plan, X) |
| 96 | + end |
| 97 | +end |
| 98 | + |
| 99 | +############################################################################ |
| 100 | +# Define ifwht (inverse/unnormalized) transforms for various orderings |
| 101 | + |
| 102 | +# Natural (Hadamard) ordering: |
| 103 | + |
| 104 | +function ifwht_natural{T<:fftwNumber}(X::StridedArray{T}, region) |
| 105 | + Y = similar(X) |
| 106 | + p = Plan_Hadamard(X, Y, region, ESTIMATE, NO_TIMELIMIT, false) |
| 107 | + execute(T, p.plan) |
| 108 | + return Y |
| 109 | +end |
| 110 | + |
| 111 | +function ifwht_natural{T<:Number}(X::StridedArray{T}, region) |
| 112 | + Y = T<:Complex ? complexfloat(X) : float(X) |
| 113 | + p = Plan_Hadamard(Y, Y, region, ESTIMATE, NO_TIMELIMIT, false) |
| 114 | + execute(p.plan, Y, Y) |
| 115 | + return Y |
| 116 | +end |
| 117 | + |
| 118 | +# Dyadic (Paley, bit-reversed) ordering: |
| 119 | + |
| 120 | +function ifwht_dyadic{T<:fftwNumber}(X::StridedArray{T}, region) |
| 121 | + Y = similar(X) |
| 122 | + p = Plan_Hadamard(X, Y, region, ESTIMATE, NO_TIMELIMIT, true) |
| 123 | + execute(T, p.plan) |
| 124 | + return Y |
| 125 | +end |
| 126 | + |
| 127 | +function ifwht_dyadic{T<:Number}(X::StridedArray{T}, region) |
| 128 | + X = T<:Complex ? complexfloat(X) : float(X) |
| 129 | + Y = similar(X) # FFTW cannot do bit-reversal in-place |
| 130 | + p = Plan_Hadamard(X, Y, region, ESTIMATE, NO_TIMELIMIT, true) |
| 131 | + execute(p.plan, X, Y) |
| 132 | + return Y |
| 133 | +end |
| 134 | + |
| 135 | +############################################################################ |
| 136 | +# Sequency (Walsh) ordering: |
| 137 | + |
| 138 | +# ifwht along a single dimension d of X |
| 139 | +function ifwht{T<:fftwNumber}(X::Array{T}, region) |
| 140 | + Y = ifwht_dyadic(X, region) |
| 141 | + |
| 142 | + # Perform Gray-code permutation of Y (TODO: in-place?) |
| 143 | + if isempty(region) |
| 144 | + return Y |
| 145 | + elseif ndims(Y) == 1 |
| 146 | + return [ Y[1 + ((i >> 1) $ i)] for i = 0:length(Y)-1 ] |
| 147 | + else |
| 148 | + sz = [size(Y)...] |
| 149 | + tmp = Array(T, max(sz[region])) # storage for out-of-place permutations |
| 150 | + for d in region |
| 151 | + # somewhat ugly loops to do 1d permutations along dimension d |
| 152 | + na = prod(sz[d+1:end]) |
| 153 | + n = sz[d] |
| 154 | + nb = prod(sz[1:d-1]) |
| 155 | + sa = nb * n |
| 156 | + for ia = 0:na-1 |
| 157 | + for ib = 1:nb |
| 158 | + i0 = ib + sa * ia |
| 159 | + for i = 0:n-1 |
| 160 | + tmp[i+1] = Y[i0 + nb * ((i >> 1) $ i)] |
| 161 | + end |
| 162 | + for i = 0:n-1 |
| 163 | + Y[i0 + nb * i] = tmp[i+1] |
| 164 | + end |
| 165 | + end |
| 166 | + end |
| 167 | + end |
| 168 | + return Y |
| 169 | + end |
| 170 | +end |
| 171 | + |
| 172 | +# handle 1d case of strided arrays (loops in multidim case are too annoying) |
| 173 | +function ifwht{T<:fftwNumber}(X::StridedVector{T}, region) |
| 174 | + Y = ifwht_dyadic(X, region) |
| 175 | + |
| 176 | + # Perform Gray-code permutation of Y (TODO: in-place?) |
| 177 | + if isempty(region) |
| 178 | + return Y |
| 179 | + else |
| 180 | + return [ Y[1 + ((i >> 1) $ i)] for i = 0:length(Y)-1 ] |
| 181 | + end |
| 182 | +end |
| 183 | + |
| 184 | +############################################################################ |
| 185 | +# Forward transforms (normalized by 1/N as in Matlab) and transforms |
| 186 | +# without the region argument: |
| 187 | + |
| 188 | +for f in (:ifwht_natural, :ifwht_dyadic, :ifwht) |
| 189 | + g = symbol(string(f)[2:end]) |
| 190 | + @eval begin |
| 191 | + $f(X) = $f(X, 1:ndims(X)) |
| 192 | + $g(X) = scale!($f(X), normalization(X)) |
| 193 | + $g(X,r) = scale!($f(X,r), normalization(X,r)) |
| 194 | + end |
| 195 | +end |
| 196 | + |
| 197 | +############################################################################ |
| 198 | +# create (floating-point) Hadamard matrix, similar to Matlab function, |
| 199 | +# in natural order. |
| 200 | + |
| 201 | +function hadamard(n::Integer) |
| 202 | + if power_of_two(n) |
| 203 | + return ifwht_natural(eye(n), 1) |
| 204 | + else |
| 205 | + # Punt. Matlab supports power-of-two multiples of 12 or 20, |
| 206 | + # but lots of other sizes are known as well (see e.g. Sloane's |
| 207 | + # web page http://neilsloane.com/hadamard/) and there is no |
| 208 | + # particularly efficient way to construct them other than |
| 209 | + # a look-up-table of known factors. [Given Hadamard matrices Hm |
| 210 | + # and Hn of sizes m and n, a Hadamard matrix of size mn is kron(Hm,Hn).] |
| 211 | + # It is not really clear what factors are important to support, |
| 212 | + # nor which of the (generally nonunique) Hadamard matrices to pick. |
| 213 | + throw(ArgumentError("non power-of-two Hadamard matrices aren't supported")) |
| 214 | + end |
| 215 | +end |
| 216 | + |
| 217 | +############################################################################ |
| 218 | + |
| 219 | +end # module |
0 commit comments