-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incremental DMD #279
base: master
Are you sure you want to change the base?
Incremental DMD #279
Conversation
It would be nice to include regression test for this example. |
@@ -27,6 +42,20 @@ namespace CAROM { | |||
class IncrementalSVDBrand : public IncrementalSVD | |||
{ | |||
public: | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are significant changes, especially in the cpp file, in the existing class IncrementalSVDBrand
, to accommodate incremental DMD. Can this PR be refactored to avoid changes to IncrementalSVDBrand
? It is understandable that the data from IncrementalSVDBrand
needs to be accessed for incremental DMD, so it makes sense to add access functions. Hopefully it is possible to avoid changing so much of the class's implementation, by using access functions, making a new class derived from IncrementalSVDBrand
, or some other way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've somehow fixed this problem in 06b4d6b. The lengthy IncrementalDMDInternal
things have been removed, but the thing is that we still need to intercept some of the intermediate matrices from IncrementalSVDBrand
for IncrementalDMD
. Specifically, matrices before IncrementalSVDBrand::addNewSample
or IncrementalSVDBrand::addLinearlyDependentSample
are accessed by IncrementalDMD
. For that, I have added extra pointers pointing to the matrices before being updated. (See Line 176-190 of lib/linalg/svd/IncrementalSVDBrand.h
. IncrementalDMD
is also declared as a friend class of IncrementalSVDBrand
so it can access those private pointers. I find that this makes minimal changes to IncrementalSVDBrand
, but if you have better ideas, please let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the improvement. There is still a lot of new code in IncrementalSVDBrand.cpp
, and it is hard to tell whether it affects the existing incremental SVD or just adds additional intermediate matrices needed by IncrementalDMD
. If it should not change the SVD algorithm, and if it only stores intermediate matrices, can this be done optionally by setting a flag? My concerns are (i) verifying that the old incremental SVD is not changed, and (ii) not adding extra computation or memory usage when IncrementalDMD
is not being used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way to separate the new code from the existing IncrementalSVDBrand
might be to make a class derived from IncrementalSVDBrand
, containing the additional data members. Functions like buildIncrementalSVD
, updateTemporalBasis
, and addNewSample
could become virtual. Some code from the class IncrementalSVDBrand
would be reused, while the new code is added only in the derived class.
Incremental DMD
Files added:
lib/algo/IncrementalDMD.h
andlib/algo/IncrementalDMD.cpp
(Incremental DMD class)examples/dmd/heat_conduction_otf.cpp
(Demo for heat equation solver w/ online DMD)Files modified:
lib/linalg/svd/IncrementalSVDBrand.h
andlib/linalg/svd/IncrementalSVDBrand.cpp
(save matrices to pass toIncrementalDMD
)lib/linalg/BasisGenerator.h
(addIncrementalDMD
as friend)CMakeList.txt
andlib/CMakeList.txt
(include demo and add to libraries)Background
IncrementalDMD constructs DMD in a fast, online fashion, leveraging the incremental nature of IncrementalSVD introduced by [Brand, 2006] (https://doi.org/10.1016/j.laa.2005.07.021). Starting from the original DMD formulation, let us first define snapshot matrices$$F^+ = [f^1 \vert f^2 \vert \cdots \vert f^m] \in \mathbb R^{N \times m} \quad \text{and} \quad F^- = [f^0 \vert f^1 \vert \cdots \vert f^{m-1}] \in \mathbb R^{N \times m}$$ where $f^i \in \mathbb R^N$ is a snapshot that has $N$ degrees of freedom at time $i$ for $i=0,1,\cdots,m$ . Then, we can construct the reduced DMD matrix as
$$\tilde A_r = U^* F^+ W S^{-1} \in \mathbb R^{r \times r},$$ where $F^- \approx USW^*$ is the truncated SVD of the snapshot matrix $F^-$ at rank $r$ . In other words, $$U \in \mathbb R^{N \times r}, \quad S \in \mathbb R^{r \times r},\quad \text{and} \quad W \in \mathbb R^{m \times r}.$$
In case new snapshots stream into the DMD and$F^\pm$ increments every iteration, it is efficient to update the SVD incrementally rather than constructing all the matrices from scratch repetitively. For a fast rank-1 update of the SVD matrices, IncrementalSVD first decomposes the matrices as $$USW^* = \bar U \bar U' S \bar W'^* \bar W^*$$ where $\bar U \in \mathbb R^{N \times r}$ , $\bar U' \in \mathbb R^{r \times r}$ , $\bar W \in \mathbb R^{m \times r}$ , and $\bar W' \in \mathbb R^{r \times r}$ . The trick is only to append the large matrices $\bar U$ and $\bar W$ , and all the matrix-matrix multiplications are done on small matrices $\bar U'$ and $\bar W'$ . The updating rules are as follows.
Given the error$p = f_\mathrm{new} - UU^* f_\mathrm{new}$ , first do the SVD
Then,
if$p$ is linearly independent from $U$ and
otherwise. The pseudo-inverse of$\bar W'$ can also be updated efficiently using the Shermann-Woodbury-Morrison formula. See [Brand, 2006] for more details.
Plugging these into our first equation for$\tilde A_r$ ,
if$p$ is linearly independent, and
otherwise.
In both cases, the matrix at the center that contains$\tilde A_{r,\mathrm{old}}$ is constructed most efficiently. $\tilde A_{r,\mathrm{old}} S_\mathrm{old}$ is $O(r^2)$ as $S_\mathrm{old}$ is diagonal. The two matrix-vector multiplications, $U'^*_{\mathrm{old}} U^*_{\mathrm{old}} f_{\mathrm{new}}$ and $p^* F^+ W_\mathrm{old} W'_\mathrm{old}$ , are $O(Nr+r^2) \approx O(Nr)$ and $O(Nm+rm+r^2) \approx O(Nm)$ given $N \gg m > r$ . The inner product $p^* f_\mathrm{new}$ is $O(N)$ . Once the central matrix is constructed, all the remaining matrix-matrix multiplications cost at most $O(r^3)$ .
IncrementalDMD::updateDMD
is a straightforward implementation of the prescribed algorithm. Some remarks:std::vector<Vector*> d_snapshots
instead.IncrementalSVDBrand
,IncrementalDMD
. As such, a private struct objectIncrementalDMDInternal
is defined inIncrementalSVDBrand.h
. The matrices can be accessed byIncrementalSVDBrand::getAllMatrices
.Demonstration
Incremental DMD can be used to construct a reduced-order model (ROM) along a full-order model (FOM) simulation. This has been demonstrated on a heat conduction simulation, which is already included in
examples/dmd/heat_conduction.cpp
. Whenever a new snapshot is added, it is fed into the DMD. Then DMD predicts the solution at the next time step and if that prediction is accurate enough, the DMD prediction is appended as the new snapshot. Whether the DMD prediction is accurate is checked by measuring the norm of the residual when the predicted solution is plugged into the governing equation of the FOM. This is problem-specific, and can be found inConductionOperator::Residual
inexamples/dmd/heat_conduction_otf.cpp
.