Skip to content

Commit 17a076c

Browse files
committed
generalize hadamard(n) with a lookup table of known Hadamard matrices (fix #2)
1 parent 0014201 commit 17a076c

File tree

5 files changed

+267
-39
lines changed

5 files changed

+267
-39
lines changed

README.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,22 @@ and sums them to recover the signal, with no `n` factor.
4848
functions take the same arguments as `fwht` and `ifwht` and have the
4949
same normalizations, respectively.
5050

51-
* Also provided is a function `hadamard(n)` which returns the
52-
(natural-order) Hadamard matrix of order `n`, similar to the Matlab
53-
function of the same name. Currently, `n` must be a power of two,
54-
in which case this function is equivalent to `ifwht_natural(eye(n), 1)`.
51+
## Hadamard matrices
52+
53+
We also provide a a function `hadamard(n)` which returns a Hadamard
54+
matrix of order `n`, similar to the Matlab function of the same name.
55+
The known Hadamard matrices up to size 256 are currently supported
56+
(via a lookup table), along with products of these sizes and powers of
57+
two.
58+
59+
The return value of `hadamard(n)` is a matrix of `Int8` values. If
60+
you are planning to do matrix computations with this matrix, you may
61+
want to convert to `Int` or `Float64` first via `int(hadamard(n))` or
62+
`float(hadamard(n))`, respectively.
63+
64+
For many sizes, the Hadamard matrix is not unique; the `hadamard`
65+
function returns an arbitrary choice. For power-of-two sizes, the
66+
choice is equivalent to `ifwht_natural(eye(n), 1)`.
5567

5668
## Author
5769

src/Hadamard.jl

+77-14
Original file line numberDiff line numberDiff line change
@@ -201,23 +201,86 @@ for f in (:ifwht_natural, :ifwht_dyadic, :ifwht)
201201
end
202202

203203
############################################################################
204-
# create (floating-point) Hadamard matrix, similar to Matlab function,
205-
# in natural order.
204+
# Utilities to work with a precomputed cache of known Hadamard matrices
205+
# of various sizes, produced by util/fetchhadamard.jl from Sloane's web page
206+
# and stored as BitMatrices
207+
208+
function readcache(cachefile::String)
209+
B = BitMatrix[]
210+
open(cachefile, "r") do io
211+
while !eof(io)
212+
k = int(ntoh(read(io, Int64)))
213+
b = BitArray(k, k)
214+
# Hack: use internal binary data from BitArray for efficiency
215+
bits = read(io, eltype(b.chunks), length(b.chunks))
216+
for i = 1:length(bits)
217+
b.chunks[i] = ntoh(bits[i])
218+
end
219+
push!(B, b)
220+
end
221+
end
222+
return B
223+
end
224+
readcache() = readcache(joinpath(Pkg.dir("Hadamard"), "src", "cache.dat"))
225+
226+
function printsigns{T<:Real}(io::IO, A::AbstractMatrix{T})
227+
m, n = size(A)
228+
println(io, m, "x", n, " sign matrix from ", typeof(A))
229+
for i = 1:m
230+
for j = 1:n
231+
print(io, A[i,j] > 0 ? "+" : "-")
232+
end
233+
println(io)
234+
end
235+
end
236+
printsigns(A) = printsigns(STDOUT, A)
237+
238+
function frombits(B::BitMatrix)
239+
A = convert(Matrix{Int8}, B)
240+
for i = 1:length(A)
241+
A[i] = A[i] == 0 ? -1 : 1
242+
end
243+
return A
244+
end
245+
246+
const hadamards = BitMatrix[] # read lazily below
247+
248+
############################################################################
249+
# Create Int8 order-n Hadamard matrix (in natural order), by factorizing
250+
# n into a product of known Hadamard sizes (if possible)
251+
252+
const H2 = Int8[1 1; 1 -1]
206253

207254
function hadamard(n::Integer)
208-
if power_of_two(n)
209-
return ifwht_natural(eye(n), 1)
210-
else
211-
# Punt. Matlab supports power-of-two multiples of 12 or 20,
212-
# but lots of other sizes are known as well (see e.g. Sloane's
213-
# web page http://neilsloane.com/hadamard/) and there is no
214-
# particularly efficient way to construct them other than
215-
# a look-up-table of known factors. [Given Hadamard matrices Hm
216-
# and Hn of sizes m and n, a Hadamard matrix of size mn is kron(Hm,Hn).]
217-
# It is not really clear what factors are important to support,
218-
# nor which of the (generally nonunique) Hadamard matrices to pick.
219-
throw(ArgumentError("non power-of-two Hadamard matrices aren't supported"))
255+
n < 1 && throw(ArgumentError("size n=$n should be positive"))
256+
n0 = n
257+
H = reshape(Int8[1], 1,1)
258+
if !ispow2(n)
259+
isempty(hadamards) && append!(hadamards, readcache())
260+
for i = length(hadamards):-1:1
261+
k = size(hadamards[i], 1)
262+
if rem(n, k) == 0
263+
Hk = frombits(hadamards[i])
264+
while true
265+
H = kron(H, Hk)
266+
n = div(n, k)
267+
rem(n, k) != 0 && break
268+
end
269+
end
270+
end
271+
end
272+
if !ispow2(n)
273+
n >>= trailing_zeros(n)
274+
throw(ArgumentError("unknown Hadamard factor $n of $n0"))
275+
end
276+
# Note: it would be faster to do a "power-by-squaring" like algorithm
277+
# here, where we repeatedly double the order via kron(H, H), but
278+
# it's not clear to me that we care much about performance here.
279+
while n > 1
280+
H = kron(H, H2)
281+
n >>= 1
220282
end
283+
return H
221284
end
222285

223286
############################################################################

src/cache.dat

146 KB
Binary file not shown.

test/test.jl test/runtests.jl

+37-21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Hadamard
2+
using Base.Test
23

34
H8 = [ 1 1 1 1 1 1 1 1
45
1 1 1 1 -1 -1 -1 -1
@@ -29,19 +30,19 @@ H8d = [ 1 1 1 1 1 1 1 1
2930

3031
ieye(n) = int8(eye(n))
3132

32-
@assert ifwht(eye(8),1) == H8
33-
@assert ifwht(eye(8),2)' == H8
34-
@assert ifwht_natural(eye(8),1) == H8n
35-
@assert ifwht_natural(eye(8),2)' == H8n
36-
@assert ifwht_dyadic(eye(8),1) == H8d
37-
@assert ifwht_dyadic(eye(8),2)' == H8d
33+
@test ifwht(eye(8),1) == H8
34+
@test ifwht(eye(8),2)' == H8
35+
@test ifwht_natural(eye(8),1) == H8n
36+
@test ifwht_natural(eye(8),2)' == H8n
37+
@test ifwht_dyadic(eye(8),1) == H8d
38+
@test ifwht_dyadic(eye(8),2)' == H8d
3839

39-
@assert ifwht(ieye(8),1) == H8
40-
@assert ifwht(ieye(8),2)' == H8
41-
@assert ifwht_natural(ieye(8),1) == H8n
42-
@assert ifwht_natural(ieye(8),2)' == H8n
43-
@assert ifwht_dyadic(ieye(8),1) == H8d
44-
@assert ifwht_dyadic(ieye(8),2)' == H8d
40+
@test ifwht(ieye(8),1) == H8
41+
@test ifwht(ieye(8),2)' == H8
42+
@test ifwht_natural(ieye(8),1) == H8n
43+
@test ifwht_natural(ieye(8),2)' == H8n
44+
@test ifwht_dyadic(ieye(8),1) == H8d
45+
@test ifwht_dyadic(ieye(8),2)' == H8d
4546

4647
H32 = [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
4748
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
@@ -76,23 +77,38 @@ H32 = [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
7677
1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1
7778
1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 1 -1 ]
7879

79-
@assert ifwht(eye(32),1) == H32
80-
@assert ifwht(eye(32),2)' == H32
80+
@test ifwht(eye(32),1) == H32
81+
@test ifwht(eye(32),2)' == H32
8182

82-
@assert ifwht(ieye(32),1) == H32
83-
@assert ifwht(ieye(32),2)' == H32
83+
@test ifwht(ieye(32),1) == H32
84+
@test ifwht(ieye(32),2)' == H32
8485

8586
X = reshape(sin([1:1024*32]), 1024,32);
8687
norminf(A) = maximum(abs(A))
8788

8889
for f in (:fwht, :fwht_natural, :fwht_dyadic)
8990
fi = symbol(string("i", f))
9091
@eval begin
91-
@assert norminf(X - $fi($f(X))) < 1e-14
92-
@assert norminf(X - $fi($f(X,1),1)) < 1e-14
93-
@assert norminf(X - $fi($f(X,2),2)) < 1e-14
94-
@assert norminf($f($f(X,1),2) - $f(X)) < 1e-14
95-
@assert norminf($f(X',1)' - $f(X,2)) < 1e-14
92+
@test norminf(X - $fi($f(X))) < 1e-14
93+
@test norminf(X - $fi($f(X,1),1)) < 1e-14
94+
@test norminf(X - $fi($f(X,2),2)) < 1e-14
95+
@test norminf($f($f(X,1),2) - $f(X)) < 1e-14
96+
@test norminf($f(X',1)' - $f(X,2)) < 1e-14
9697
end
9798
end
9899

100+
@test hadamard(8) == H8n
101+
@test ifwht_natural(eye(32), 1) == hadamard(32)
102+
@test ifwht_natural(eye(2), 1) == hadamard(2)
103+
104+
for i = 4:4:1000
105+
H = int(try
106+
hadamard(i)
107+
catch
108+
Int[]
109+
end)
110+
if !isempty(H)
111+
println("checking unitarity of hadamard($i)")
112+
@test vecnorm(H'*H - size(H,1)*I) == 0
113+
end
114+
end

util/fetchhadamard.jl

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Fetch a bunch of precomputed Hadamard matrices from neilsloane.com/hadamard/
2+
# and convert them into BitArrays. Then print the sizes and bitarray 64-bit
3+
# chunks to give us a compact representation of the data for inlining.
4+
5+
const sloane = "http://neilsloane.com/hadamard/had." # URL prefix
6+
7+
# There are multiple inequivalent Hadamard matrices for many sizes;
8+
# we just pick one, semi-arbitrarily.
9+
10+
const files = [
11+
# powers of 2 (and 1) are handled specially
12+
# 1 => "1",
13+
# 2 => "2",
14+
12 => "12",
15+
20 => "20.1",
16+
28 => "28.pal2",
17+
36 => "36.pal2",
18+
44 => "44.pal",
19+
52 => "52.will",
20+
60 => "60.pal",
21+
68 => "68.pal",
22+
72 => "72.pal",
23+
76 => "76.pal2",
24+
84 => "84.pal",
25+
88 => "88.tpal",
26+
92 => "92.will",
27+
96 => "96.tpal",
28+
100 => "100.will",
29+
104 => "104.pal",
30+
108 => "108.pal",
31+
116 => "116.will",
32+
120 => "120.tpal",
33+
124 => "124.pal2",
34+
132 => "132.pal",
35+
136 => "136.tpal",
36+
140 => "140.pal",
37+
148 => "148.pal2",
38+
152 => "152.pal",
39+
156 => "156.will",
40+
164 => "164.pal",
41+
168 => "168.pal",
42+
172 => "172.will",
43+
180 => "180.pal",
44+
184 => "184.twill",
45+
188 => "188.tur",
46+
196 => "196.pal2",
47+
204 => "204.pal2",
48+
212 => "212.pal",
49+
216 => "216.tpal",
50+
220 => "220.pal2",
51+
228 => "228.pal",
52+
232 => "232.twill",
53+
236 => "236.od",
54+
244 => "244.will",
55+
248 => "248.twill",
56+
252 => "252.pal",
57+
]
58+
59+
# the inverse of the printsigns function: parse +-+-... data (Sloane's format)
60+
# into a bitarray.
61+
function parsesigns(io::IO)
62+
lines = BitVector[]
63+
linelen = -1
64+
for s in eachline(io)
65+
s = chomp(s)
66+
line = BitVector(length(s))
67+
for (i,c) in enumerate(s)
68+
line[i] = c=='+' ? true : c=='-' ? false : error("invalid char $c")
69+
end
70+
if linelen == -1
71+
linelen = length(line)
72+
elseif linelen != length(line)
73+
error("line ", length(lines)+1, " has length ", length(line),
74+
" != ", linelen)
75+
end
76+
push!(lines, line)
77+
length(lines) == linelen && break
78+
end
79+
B = BitArray(length(lines), linelen)
80+
for i = 1:length(lines)
81+
for j = 1:linelen
82+
B[i,j] = lines[i][j]
83+
end
84+
end
85+
B
86+
end
87+
parsesigns(s::String) = parsesigns(IOBuffer(s))
88+
89+
open("cache.dat", "w") do io
90+
f = tempname()
91+
for k in sort!(collect(keys(files)))
92+
println("FETCHING order $k...\n")
93+
B = try
94+
download(sloane*files[k]*".txt", f)
95+
open(parsesigns, f, "r")
96+
catch
97+
println("# ERROR for size $k: ", sloane*files[k]*".txt", " -> ", f)
98+
rethrow()
99+
end
100+
size(B) != (k,k) && error("size $k mismatch for ", files[k])
101+
write(io, hton(int64(k)), map(hton, B.chunks))
102+
end
103+
104+
# size 428 is in a different format, grr
105+
k = 428
106+
println("FETCHING order $k...\n")
107+
B = try
108+
download(sloane*"428.txt", f)
109+
s = readall(f)
110+
s = replace(s, r" +1", "+")
111+
s = replace(s, r" *-1", "-")
112+
parsesigns(s)
113+
catch
114+
println("# ERROR for size $k: ", sloane*"428.txt", " -> ", f)
115+
rethrow()
116+
end
117+
size(B) != (k,k) && error("size $k mismatch for 428.txt")
118+
write(io, hton(int64(k)), map(hton, B.chunks))
119+
120+
# others not on Sloane's page can be found at http://www.iasri.res.in/webhadamard/
121+
#= ... the site seems to be broken (truncated output) at the moment ...
122+
for k in [260,268,276,284,288,292,300,308,316,324,332,340,348,356,364,372,380,388,396,404,412,420,436,444,452,460,468,476,484,492,500,508,516,520,524,532,536,540,548,552,556,564,568,572,576,580,584,588,596,600,604,612,616,620,628,632,636,644,648,652,660,664,676,680,684,692,696,700,708,712,724,728,732,740,744,748,756,760,764,772,776,780,788,792,796,804,808,812,820,824,828,836,840,844,852,860,868,872,884,888,900,904,908,916,920,924,932,936,940,948,952,956,964,968,972,980,984,988,996,1000]
123+
B = try
124+
download("http://www.iasri.res.in/webhadamard/Validate.dll?MfcISAPICommand=Hadamard&Order=$k&Normalization=3&output=2", f)
125+
s = readall(f)
126+
s = replace(s, r"</p><p> *", "\n")
127+
s = replace(s, r"<html>.*<p> *"s, "")
128+
parsesigns(s)
129+
catch
130+
println("# ERROR for size $k from www.iasri.res.in/webhadamard")
131+
rethrow()
132+
end
133+
size(B) != (k,k) && error("size $k mismatch for webhadamard")
134+
end
135+
write(io, hton(int64(k)), map(hton, B.chunks))
136+
=#
137+
end

0 commit comments

Comments
 (0)