Skip to content

Commit 02dae7c

Browse files
author
chris-langfield
committed
add gsp example
1 parent 95c3af8 commit 02dae7c

File tree

1 file changed

+267
-0
lines changed

1 file changed

+267
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
import scipy
4+
5+
import pygsp
6+
from pygsp import utils
7+
8+
from hexfft import HexArray, ifft
9+
from hexfft.plot import hexshow
10+
from hexfft.array import generate_grid
11+
12+
# ---------------------------------------------------
13+
W = np.array([[0., 0., 1., 1., 0.],
14+
[0., 0., 1., 1., 1.],
15+
[1., 1., 0., 0., 1.],
16+
[1., 1., 0., 0., 0.],
17+
[0., 1., 1., 0., 0.]])
18+
19+
g = pygsp.graphs.Graph(W)
20+
g.set_coordinates()
21+
g.plot_signal(np.ones(5))
22+
23+
24+
f = np.random.randn(5)
25+
print(f)
26+
fig, ax = plt.subplots()
27+
g.plot_signal(f, ax=ax)
28+
ax.set_title("Random signal")
29+
30+
# ---------------------------------------------------
31+
# 1D (ring graph)
32+
33+
N = 32
34+
rg = pygsp.graphs.Ring(N)
35+
sine = np.sin(2*np.pi*np.arange(N)/N)
36+
rg.plot_signal(sine)
37+
38+
rg.compute_fourier_basis()
39+
fig, ax = plt.subplots()
40+
rg.plot_signal(rg.U[:, 3], ax=ax)
41+
ax.set_title("3rd graph Laplacian eigenvector")
42+
43+
# adjacency matrix
44+
fig, ax = plt.subplots()
45+
ax.matshow(rg.W.toarray())
46+
ax.set_title("N=32 Ring graph adjacency matrix")
47+
48+
fig, axs = plt.subplots(4, 8)
49+
for i, ax in enumerate(axs.flat):
50+
ax.plot(rg.U[:, i])
51+
fig.suptitle("N=32 Ring Graph: Eigenvector modes")
52+
53+
Uf = np.zeros((N, N), np.complex128)
54+
for i in range(N):
55+
Uf[:, i] = np.exp(-2.j * np.pi * i * np.arange(N) / N)
56+
fig, axs = plt.subplots(4, 8)
57+
for i, ax in enumerate(axs.flat):
58+
ax.plot(Uf[:, i], "red")
59+
fig.suptitle("N=32 Ring Graph: Fourier modes")
60+
61+
fig, axs = plt.subplots(6, 3)
62+
axs[0, 0].plot(np.sqrt(32)*rg.U[:, 0])
63+
axs[0, 0].set_yticks([0.95, 1, 1.05])
64+
axs[0, 2].plot(Uf[:, 0], "red")
65+
axs[0, 0].set_title("Eigenvector modes", color="blue")
66+
axs[0, 2].set_title("Fourier modes (real part)", color="red")
67+
for i in range(5):
68+
axs[i+1, 0].plot(4*rg.U[:, 2*i+1])
69+
axs[i+1, 1].plot(4*rg.U[:, 2*i + 2])
70+
axs[i+1, 2].plot(Uf[:, i+1], "red")
71+
for i, ax in enumerate(axs.flat):
72+
ax.set_xticks([0, 16])
73+
if i % 3 ==1:
74+
ax.set_yticklabels([])
75+
ax.yaxis.set_visible(False)
76+
if i % 3 == 2:
77+
ax.yaxis.tick_right()
78+
ax.yaxis.set_label_position("right")
79+
ax.set_ylabel(f" k={i//3}", rotation=0,fontdict=dict(weight='bold'))
80+
if i not in [17, 16, 15]:
81+
ax.set_xticklabels([])
82+
83+
fig.suptitle("Ring graph: Graph Fourier basis vs 1D Fourier basis\n\n")
84+
85+
# eigenvalues
86+
eigs = np.sort(np.linalg.eigvals(rg.L.toarray()))
87+
eigs_analytic = np.sort(np.array([2*(1-np.cos(2*np.pi * k / N)) for k in range(N)]))
88+
89+
fig, ax = plt.subplots()
90+
ax.plot(np.sort(eigs)[::-1])
91+
ax.set_ylabel("λi ", rotation=0, fontsize=16)
92+
ax.set_xlabel("i", fontsize=16)
93+
fig.suptitle("Graph Laplacian Eigenvalues: N=32 Ring Graph")
94+
95+
96+
# ---------------------------------------------------
97+
# 2D (square graph)
98+
99+
N1, N2 = 12, 12
100+
sg = pygsp.graphs.Grid2d(N1, N2)
101+
sg.plot_signal(np.ones(N1*N2))
102+
103+
fig, ax = plt.subplots()
104+
ax.matshow(sg.W.toarray())
105+
ax.set_title("Adjacency matrix for 6x6 square lattice graph")
106+
107+
N1, N2 = 12, 12
108+
circ = np.zeros(N1*N2)
109+
circ[[1, N2, N1*N2-N2, N1*N2-1]] = 1
110+
adj = scipy.linalg.circulant(circ).T
111+
112+
psg = pygsp.graphs.Graph(W=adj)
113+
psg.set_coordinates()
114+
fig, ax = plt.subplots()
115+
psg.plot_signal(np.ones(N1*N2), ax=ax)
116+
ax.set_title("6x6 toroid graph (square lattice with periodic boundary conditions)")
117+
118+
fig, ax = plt.subplots()
119+
ax.matshow(adj)
120+
ax.set_title("Adjacency matrix for 6x6 square lattice graph\n[periodic boundary conditions]")
121+
122+
sg.compute_fourier_basis()
123+
124+
analytic_U = np.zeros((N1*N2, N1*N2))
125+
for k in range((N1*N2)):
126+
analytic_U[:, k] = np.exp(2.j * np.pi * k * np.arange(N1*N2)/(N1*N2))
127+
128+
fig, axs = plt.subplots(4, 4)
129+
for i, ax in enumerate(axs.flat):
130+
ax.matshow(sg.U[:, i].reshape(N1, N2))
131+
fig.suptitle("12x12 Square Graph: Eigenvector modes (no periodic boundary conditions)")
132+
133+
Uf = np.zeros((N1, N2, N1, N2), np.complex128)
134+
x, y = np.meshgrid(np.arange(N1), np.arange(N2), indexing="xy")
135+
for i in range(N1):
136+
for j in range(N2):
137+
Uf[i, j, :, :] = np.exp(2.j * np.pi * (i*x/N1+j*y/N2))
138+
fig, axs = plt.subplots(4, 4)
139+
for i in range(4):
140+
for j in range(4):
141+
axs[i, j].matshow(np.real(Uf[i, j, :, :]))
142+
fig.suptitle("12x12 Square Graph: Fourier modes")
143+
144+
eigs_noboundary = np.linalg.eigvals(sg.L.toarray())
145+
eigs_boundary = np.linalg.eigvals(psg.L.toarray())
146+
eigs_analytic = np.sort(
147+
np.array(
148+
[4-2*np.cos(2*np.pi*k/(N1**2)) - 2*np.cos(2*np.pi*k/N1)
149+
for k in range(N1**2)]
150+
)
151+
)
152+
153+
fig, ax = plt.subplots()
154+
ax.plot(np.sort(eigs_noboundary)[::-1], label="numerical - no boundary conditions")
155+
ax.plot(np.sort(eigs_boundary)[::-1], label="numerical - with periodic boundary conditions")
156+
ax.plot(np.sort(eigs_analytic)[::-1], label="analytic - with periodic boundary conditions")
157+
fig.suptitle("Graph Laplacian eigenvalues: 12x12 Square Grid Graph")
158+
ax.legend()
159+
160+
# ---------------------------------------------------
161+
# 2D (triangle graph)
162+
163+
164+
def TriangleGrid2d(N1, N2, periodic=True):
165+
W = np.zeros((N1*N2, N1*N2))
166+
167+
for i in range(N1*N2-1):
168+
j = i + 1
169+
if j % N2 == 0:
170+
continue
171+
else:
172+
W[i, j] = 1.
173+
W[j, i] = 1.
174+
175+
for i in range(N1):
176+
if i == 0:
177+
W[i, N2] = 1.
178+
for j in range(1, N2):
179+
W[i + j, N2 + j - 1: N2 + j + 1] = 1.
180+
elif i == N1 - 1 and i % 2 == 0:
181+
W[i*N2, i*N2 - N2] = 1.
182+
for j in range(1, N2):
183+
W[i*N2 + j, -N2 + i*N2 + j - 1: -N2 + i*N2 + j + 1] = 1.
184+
elif i % 2 == 0:
185+
W[i*N2, i*N2 + N2] = 1.
186+
W[i*N2, i*N2 - N2] = 1.
187+
for j in range(1, N2):
188+
W[i*N2 + j, N2 + i*N2 + j - 1: N2 + i*N2 + j + 1] = 1.
189+
W[i*N2 + j, -N2 + i*N2 + j - 1: -N2 + i*N2 + j + 1] = 1.
190+
191+
if periodic:
192+
for i in range(N1):
193+
# side to side
194+
W[i*N2, (i+1)*N2 - 1] = 1.
195+
# slant right connections at sides
196+
if i % 2 == 0:
197+
W[i*N2, (i+2)*N2 -1] = 1.
198+
# slant left connections at sides
199+
W[i*N2-1, i*N2] = 1.
200+
for i in range(N2):
201+
# slant right connections at top
202+
W[i, N1*N2 - N2 + i] = 1.
203+
# slant left connections at top
204+
if i == 0:
205+
W[i, N1*N2-1] = 1.
206+
else:
207+
W[i, N1*N2 - N2 + i -1] = 1.
208+
209+
# if periodic:
210+
# c0 = np.zeros(N1*N2)
211+
# c0[[1, N2-1, N2, N1*N2-N2, N1*N2-N2+1, N1*N2 - 1]] = 1.
212+
# c1 = np.zeros(N1*N2)
213+
# c1[[1, N2, N2+1, N1*N2-N2-1, N1*N2-N2, N1*N2-1]] = 1.
214+
215+
# even_block = scipy.linalg.circulant(c0).T
216+
# odd_block = scipy.linalg.circulant(c1).T
217+
218+
# for i in range(N1):
219+
# idx = slice(i*N1,(i+1)*N1)
220+
# if i % 2 == 0:
221+
# W[idx, :] = even_block[idx, :]
222+
# else:
223+
# W[idx, :] = odd_block[idx]
224+
225+
W = W + W.T
226+
W[W > 0] = 1.
227+
228+
# if periodic:
229+
# assert np.all(np.sum(W, 0) == 6)
230+
231+
x, y = generate_grid((N1, N2), "offset")
232+
coords = np.stack([x.flatten(), y.flatten()]).T
233+
return pygsp.graphs.Graph(W=W, coords=coords)
234+
235+
N1, N2 = 12, 12
236+
tg = TriangleGrid2d(N1, N2, periodic=True)
237+
tg.plot_signal(np.ones(N1*N2))
238+
fig, ax = plt.subplots()
239+
ax.matshow(tg.W.toarray())
240+
fig.suptitle("Adjacency matrix for 6x6 hex grid\n")
241+
fig.tight_layout()
242+
# block circulant https://math.stackexchange.com/questions/4022364/eigenvalues-of-a-particular-block-circulant-matrix
243+
244+
tg.compute_fourier_basis()
245+
246+
fig, axs = plt.subplots(4, 4, figsize=(8, 8))
247+
for i, ax in enumerate(axs.flat):
248+
hexshow(HexArray(tg.U[:, i+1].reshape(N1, N2)), ax=ax)
249+
fig.suptitle("12x12 Triange Graph: Eigenvector modes (with periodic boundary conds)\n")
250+
fig.tight_layout()
251+
252+
fig, axs = plt.subplots(4, 4, figsize=(8, 8))
253+
for i in range(4):
254+
for j in range(4):
255+
H = HexArray(np.zeros((N1, N2)))
256+
H[i, j] = 1.
257+
hexshow(np.real(ifft(H)), ax=axs[i, j])
258+
fig.suptitle("12x12 Triangle Graph: Hexagonal Fourier modes\n")
259+
fig.tight_layout()
260+
261+
262+
eigs = np.linalg.eigvals(tg.L.toarray())
263+
fig, ax = plt.subplots()
264+
ax.plot(np.sort(eigs)[::-1])
265+
ax.set_ylabel("magnitude")
266+
ax.set_xlabel("eigenvalue idx (sorted)")
267+
fig.suptitle("Graph Laplacian eigenvalues: 12x12 Triangle Graph")

0 commit comments

Comments
 (0)