forked from PyDMD/PyDMD
-
Notifications
You must be signed in to change notification settings - Fork 1
/
tutorial-9-spdmd.py
210 lines (121 loc) · 5.91 KB
/
tutorial-9-spdmd.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/env python
# coding: utf-8
# # Sparsity-Promoting Dynamic Mode Decomposition
# In this tutorial we present a variant of DMD called *Sparsity-Promoting* DMD. The algorithm differs from the original one in the way it computes DMD modes amplitudes, since SpDMD "promotes" combinations of amplitudes with an high number of amplitudes set to 0. Such a quality of DMD amplitudes (and of matrix in general) is called *sparsity*. The parameter $\gamma$ controls how much sparsity is promoted.
#
# SpDMD has been presented in *Sparsity-promoting dynamic mode decomposition* by *Jovanovic* et al. The algorithm uses ADMM (check *Distributed optimization and statistical learning via the alternating
# direction method of multipliers* by *Boyd* et al), an iterative algorithm used to solve optimization problems.
#
# First of all, we import the modules needed for this tutorial.
# In[1]:
import numpy as np
from pydmd import SpDMD, DMD
import matplotlib.pyplot as plt
# We consider several values of the parameter $\gamma$ in order to check how it affects the result:
# In[49]:
gammas = [1.0e-3, 2, 10, 20, 30, 50, 100, 1000]
# As $\gamma \to 0$, SpDMD approaches to standard DMD algorithm, which does not impose sparsity in the result.
#
# We now load a dataset related to an heat conductivity problem:
# In[37]:
X = np.load("data/heat_90.npy")
X.shape
# We use the dataset to train several instances of SpDMD (one for each $\gamma$ taken into account). We also create an instance of standard DMD for comparison:
# In[63]:
spdmd_list = [SpDMD(svd_rank=30, gamma=gm, rho=1.0e4).fit(X) for gm in gammas]
std_dmd = DMD(svd_rank=30).fit(X)
# As you can see each call to the method `SpDMD.fit()` prints the number of iterations of ADMM. You can suppress this output passing the flag `verbose=False` to the constructor of the class `SpDMD`:
# In[68]:
SpDMD(svd_rank=30, gamma=10, rho=1.0e2, verbose=False).fit(X)
# You can control the number of iterations with the parameter `rho` of the constructor of the class `SpDMD`. The optimal value for this parameter may differ for different problems:
# In[69]:
SpDMD(svd_rank=30, gamma=10, rho=1.0e4).fit(X)
SpDMD(svd_rank=30, gamma=10, rho=1).fit(X)
# The maximum number of iterations of ADMM is $10^4$ by default, and can be tuned with the parameter `max_iterations` of the constructor of the class `SpDMD`.
# ## Sparsity-promoting amplitudes
# We now examine the results of the experiment initialized above. First of all we plot the number of non-zero amplitudes in the algorithm as $\gamma$ increases. As you can see an high value of $\gamma$ "breaks" the algorithm, in the sense that all the DMD amplitudes are set to 0. By contrast, for low values of $\gamma$ SpDMD converges to standard DMD (all amplitudes are non-zero).
# In[65]:
plt.figure(figsize=(20, 5))
plt.plot(gammas, [np.count_nonzero(dmd.amplitudes) for dmd in spdmd_list])
plt.grid()
plt.xscale("log")
plt.title("Non-zero amplitudes")
plt.xlabel("$\gamma$")
plt.ylabel("#non-zero amplitudes")
plt.show()
# In order to visualize the situation we plot the DMD ampltiudes for the tested values of $\gamma$.
# In[66]:
# plot amplitudes
plt.figure(figsize=(20, 5))
for gm, a in zip(gammas, map(lambda dmd: dmd.amplitudes, spdmd_list)):
plt.plot(a.real, label="$\gamma = $ {}".format(gm))
plt.grid()
plt.legend()
plt.title("DMD amplitudes (real part)")
plt.show()
# ## Reconstruction and pointwise error
# We use the instances of `SpDMD` and `DMD` constructed above to reconstruct the dataset for a particular time instance. Then, we evaluate the pointwise error for that time instance, and we compare the results for different values of $\gamma$.
# In[60]:
time = 2
plt.figure(figsize=(20, 14.5))
plt.subplot(3, 4, 1)
plt.pcolormesh(X[:, time].reshape(21, 21))
plt.title("Original")
for idx, gm, dmd in zip(range(len(gammas)), gammas, spdmd_list):
plt.subplot(3, 4, idx + 2)
plt.pcolormesh(dmd.reconstructed_data.real[:, time].reshape(21, 21))
plt.title("$\gamma =$ {}".format(gm))
plt.subplot(3, 4, len(gammas) + 2)
plt.pcolormesh(std_dmd.reconstructed_data.real[:, time].reshape(21, 21))
plt.title("Standard DMD")
plt.show()
# In[61]:
time = 2
plt.figure(figsize=(20, 15))
for idx, gm, dmd in zip(range(len(gammas)), gammas, spdmd_list):
plt.subplot(3, 3, idx + 1)
plt.pcolormesh(
dmd.reconstructed_data.real[:, time].reshape(21, 21)
- X[:, time].real.reshape(21, 21)
)
plt.colorbar()
plt.title("$\gamma =$ {}".format(gm))
plt.subplot(3, 3, len(gammas) + 1)
plt.pcolormesh(
std_dmd.reconstructed_data.real[:, time].reshape(21, 21)
- X[:, time].real.reshape(21, 21)
)
plt.colorbar()
plt.title("Standard DMD")
plt.show()
# As you can see the errors provided by SpDMD and standard DMD are on similar magnitudes. The quality decreases as $\gamma$ grows, with the worst case happening when the number of non-zero amplitudes becomes 0.
# ## Comparison of the time evolution of absolute error
# To end the comparison, we plot alongside the evolution of the absolute error (see the code below for our implementation of the function `absolute_error`) for the tested values of $\gamma$.
# In[62]:
def relative_error(got, expected):
return np.linalg.norm(got - expected) / np.linalg.norm(expected)
def absolute_error(got, expected):
return np.linalg.norm(got - expected)
# gather data about absolute/relative error
errors = []
dmds = [*spdmd_list, std_dmd]
for dmd in dmds:
e = []
rec = dmd.reconstructed_data
for time in range(X.shape[1]):
e.append(absolute_error(rec[:, time], X[:, time]))
errors.append(e)
errors = np.array(errors)
# plot the results
plt.figure(figsize=(20, 5))
plt.plot(errors[-1], label="Standard DMD")
for gm, err in zip(gammas, errors):
plt.plot(err, label="$\gamma =$ {}".format(gm))
plt.legend()
plt.grid()
plt.yscale("log")
plt.ylim(bottom=1.0e-9)
plt.xlabel("Time")
plt.ylabel("Absolute error")
plt.title("Absolute error over time")
plt.show()