You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# We create the input data by summing two different functions:<br>
@@ -77,7 +77,7 @@ def f2(x, t):
77
77
plt.title(title)
78
78
plt.xlabel("Space")
79
79
plt.ylabel("Time")
80
-
plt.colorbar()
80
+
plt.colorbar()
81
81
plt.show()
82
82
83
83
@@ -96,47 +96,79 @@ def f2(x, t):
96
96
plt.title(title)
97
97
plt.xlabel("Space")
98
98
plt.ylabel("Time")
99
-
plt.colorbar()
99
+
plt.colorbar()
100
+
plt.show()
101
+
102
+
103
+
# ## DMD with perfect data (i.e. clean simulation data)
104
+
#
105
+
# **We begin by presenting the following results so that we know what we should be expecting when we apply DMD to this data set. We will go more in depth about the individual steps on how to use PyDMD later.**
106
+
#
107
+
# Notice that by construction, our data set is completely real (i.e. it doesn't possess imaginary components) and it contains 2 distinct spatiotemporal features that oscillate in time. Hence a successful DMD model should not only be able to faithfully reconstruct the input data, but it should also be able to disambiguate the 2 spatial modes, as well as recover their respective frequencies of oscillation $\omega_1=2.3$ and $\omega_2=2.8$.
108
+
#
109
+
# To capture such oscillations from real data sets, we will need 2 DMD eigenvalues for each oscillation: one to capture the frequency of the oscillation and one to capture its complex conjugate. Hence for our particular data set, we need at least 4 DMD eigenvalues / modes in order to capture the full extent of our data. We will also need time-delay data preprocessing in order to recover this rank-4 structure, but more on that later.
110
+
#
111
+
# Since our data is evenly-spaced in time and sufficiently high-dimensional after we appropriately preprocess our data, exact DMD (implemented by `DMD`) is able to successfully extract the 2 spatiotemporal modes that make up our system as long as we use clean data.
112
+
#
113
+
# **The results presented below are essentially perfect results.**
114
+
115
+
# In[5]:
116
+
117
+
118
+
d=2# we will use this number of delays throughout the tutorial
# ## DMD steps for handling real data (i.e. data with noise)
104
135
#
105
-
# Step 1: Do a time-delay embedding (`d` is number of delay embeddings).
136
+
# **Step 1:** Do a time-delay embedding (`d` is number of delay embeddings).
106
137
#
107
-
# Step 2: Apply BOP-DMD (`num_trials` is number of statistical bags).
138
+
# **Step 2:** Apply BOP-DMD (`num_trials` is number of statistical bags).
108
139
#
109
-
# Step 3: OPTIONAL -- Constrain the eigenvalues (i) left-half plane, (ii) imaginary axis, (iii) complex conjugate pairs.
140
+
# **Step 3:** OPTIONAL -- Constrain the eigenvalues (i) left-half plane, (ii) imaginary axis, (iii) complex conjugate pairs.
110
141
111
-
# ## Steps 1 and 2:
142
+
# ## Steps 1 and 2: Using PyDMD on real data
112
143
#
113
144
# We currently have the temporal snapshots in the input matrix rows. We can easily create a new DMD instance and exploit it in order to compute DMD on the data. Since the snapshots must be arranged by columns, we need to transpose the data matrix in this case.
114
145
#
115
-
# Starting with Step 1, we apply a time-delay embedding to our data before applying our DMD method of choice. In order to do that, we wrap our DMD instance in the `hankel_preprocessing` routine and provide our desired number of delays `d`. We will dive more into *why* we need the time-delay embedding later in the tutorial.
146
+
# Starting with **Step 1**, we apply a time-delay embedding to our data before applying our DMD method of choice. In order to do that, we wrap our DMD instance in the `hankel_preprocessing` routine and provide our desired number of delays `d`. We will dive more into *why* we need the time-delay embeddings later in the tutorial.
116
147
#
117
-
# Continuing on to Step 2, we note that in order to apply the BOP-DMD method in particular, all we need to do is build `BOPDMD` model as our particular DMD instance. Once the instance is wrapped, we can go ahead with the fit.
148
+
# Continuing on to **Step 2**, we note that in order to apply the BOP-DMD method in particular, all we need to do is build `BOPDMD` model as our particular DMD instance. Once the instance is wrapped, we can go ahead with the fit.
118
149
#
119
150
# A summary of the DMD results can then be plotted using the `plot_summary` function.
151
+
#
152
+
# Notice that from this process alone, we are able to obtain fairly accurate spatial modes, a good approximation of the temporal frequencies, and a good reconstruction of our data, even in the presence of high amounts of noise.
120
153
121
-
# In[5]:
154
+
# In[6]:
122
155
123
156
124
157
# Build the Optimized DMD model.
125
158
# num_trials=0 gives Optimized DMD, without bagging.
# Note: BOPDMD models need the data X and the times of data collection t for fitting.
134
166
# Hence if we apply time-delay, we must adjust the length of our time vector accordingly.
135
-
num_t=len(t) -delays+1
136
-
delay_optdmd.fit(Xn.T, t=t[:num_t])
167
+
delay_t=t[: -d+1]
168
+
delay_optdmd.fit(Xn.T, t=delay_t)
137
169
138
170
# Plot a summary of the DMD results.
139
-
plot_summary(delay_optdmd, d=delays)
171
+
plot_summary(delay_optdmd, x=x, d=d)
140
172
141
173
# Print computed eigenvalues (frequencies are given by imaginary components).
142
174
# Also plot the resulting data reconstruction.
@@ -146,7 +178,7 @@ def f2(x, t):
146
178
plt.title("Reconstructed Data")
147
179
plt.imshow(delay_optdmd.reconstructed_data.real)
148
180
plt.show()
149
-
plt.title("Ground Truth Data")
181
+
plt.title("Clean Ground Truth Data")
150
182
plt.imshow(X.T)
151
183
plt.show()
152
184
@@ -160,7 +192,7 @@ def f2(x, t):
160
192
#
161
193
# Although these attributes may be accessed directly from a fitted DMD object as demonstrated below, we note that the `plot_summary` function plots a summarizing view of many of these attributes automatically.
# `BOPDMD` models also have the option to specify the structure of the eigenvalues that they compute. More specifically, users can impose the following constraints, as well as any valid combination of them.
198
230
#
199
-
# - Stable: constrain eigenvalues to have non-positive real parts.
200
-
# - Imaginary: constrain eigenvalues to be purely imaginary.
201
-
# - Conjugate pairs: constrain eigenvalues to always appear with their complex conjugate.
231
+
# - **Stable:** constrain eigenvalues to have non-positive real parts.
232
+
# - **Imaginary:** constrain eigenvalues to be purely imaginary.
233
+
# - **Conjugate pairs:** constrain eigenvalues to always appear with their complex conjugate.
202
234
#
203
235
# This can be especially helpful for dealing with noise and preventing growth/decay of your dynamics.
236
+
#
237
+
# Notice that by taking this extra step, the eigenvalues computed by BOP-DMD are now perfectly imaginary, and they now come in perfect complex conjugate pairs. Also notice that this is automatically detected by `plot_summary`, which now plots the complex conjugate eigenvalue pairs and their respective mode in the same color.
# Put simply, **BOP-DMD is extremely robust to measurement noise, hence making it the preferred method when dealing with real-world data.** By contrast, the results of exact DMD (which is implemented by the `DMD` module) are extremely sensitive to measurement noise, as we demonstrate here. Note the decay of the dynamics onset by the bias in the eigenvalues. Also note how when we previously performed this fit but with BOP-DMD instead, we did not observe such decay, but rather we recovered the true oscillations.
243
-
244
-
# ### This is what happens when we use exact DMD instead of BOP-DMD:
# Notice that by construction, our data set is completely real (i.e. it doesn't possess imaginary components) and it contains 2 distinct spatiotemporal features that oscillate in time. To capture such oscillations from real data sets, we need 2 DMD eigenvalues for each oscillation: one to capture the frequency of the oscillation and one to capture its complex conjugate. Hence for our particular data set, we need at least 4 DMD eigenvalues / modes in order to capture the full extent of our data. You may have noticed this as we consistently used `svd_rank=4`.
276
+
# **Because our data is real *and* because the underlying spatial modes are stationary, we cannot always obtain correct results if we apply DMD directly to our data set, even if we use the proper rank truncation.**
277
+
#
278
+
# Time-delay helps mitigate this by giving us more observations to work with. As you will see below, our clean data reveals 2 dominant singular values, and if we try to apply DMD without time-delay, we obtain nonsensical results.
268
279
#
269
-
# However, **because our data is real *and* because the underlying spatial modes are stationary, we cannot always obtain correct results if we apply DMD directly to our data set, even if we use the proper rank truncation.** Time-delay helps mitigate this by giving us more observations to work with. As you will see below, our clean data reveals 2 dominant singular values, but applying any number of time-delay embeddingswill lift this number of singular values from 2 to 4, hence allowing us to more-consistently extract the rank-4 structure that we would expect. This is also why we use `d=2` -- any number of delays greater than 1 suffices.
280
+
# However, if we apply any number of time-delay embeddings, it will lift this number of singular values from 2 to 4, hence allowing us to moreconsistently extract the rank-4 structure that we expect. This is why we use `d=2` throughout this tutorial. Any number of delays greater than 1 suffice, granted we stil have enough temporal snapshots.
270
281
#
271
282
# Note that this preprocessing step may or may not be necessary depending on your particular data set. Hence the most practical thing to do during any DMD application is to **examine the singular value spectrum of you data as you apply time-delay embeddings.**
272
283
@@ -285,28 +296,34 @@ def f2(x, t):
285
296
plt.title("Reconstructed Data")
286
297
plt.imshow(dmd.reconstructed_data.real)
287
298
plt.show()
288
-
plt.title("Ground Truth Data")
299
+
plt.title("Clean Ground Truth Data")
289
300
plt.imshow(X.T)
290
301
plt.show()
291
302
292
303
293
-
# ### This is what happens with time-delay (using clean data and exact DMD):
304
+
# ## Why do we use BOP-DMD?
305
+
#
306
+
# **BOP-DMD is extremely robust to measurement noise, which is why it the preferred method when dealing with real-world data.** By contrast, the results of exact DMD are extremely sensitive to measurement noise.
307
+
#
308
+
# Below, we demonstrate what might happen if you apply exact DMD to non-perfect data. Note the decay of the dynamics onset by the bias in the eigenvalues. Also notice how when we previously performed this fit but with BOP-DMD instead, we did not observe such decay, but rather we recovered the expected oscillatory dynamics.
309
+
310
+
# ### This is what happens when we use exact DMD instead of BOP-DMD:
0 commit comments