@@ -1351,17 +1351,31 @@ def _check_eig_constraints(self, eig_constraints):
13511351 msg = "Invalid eigenvalue constraint combination provided."
13521352 raise ValueError (msg )
13531353
1354- def _initialize_alpha (self ):
1354+ def _initialize_alpha (self , s = None , V = None ):
13551355 """
13561356 Uses projected trapezoidal rule to approximate the eigenvalues of A in
13571357 z' = Az.
13581358 The computed eigenvalues will serve as our initial guess for alpha.
1359-
1359+ If the singular values and right singular vectors are provided, they
1360+ will be used to compute the projection. Otherwise, the snapshot data
1361+ will be projected onto the projection basis.
1362+
1363+ :param s: the singular values of the snapshot matrix. If provided, they
1364+ will be used along with V to compute the projection.
1365+ :type s: numpy.ndarray
1366+ :param V: the right singular vectors of the snapshot matrix. If
1367+ provided, they will be used along with s to compute the projection.
1368+ :type V: numpy.ndarray
13601369 :return: Approximated eigenvalues of the matrix A.
13611370 :rtype: numpy.ndarray
13621371 """
1363- # Project the snapshot data onto the projection basis.
1364- ux = self ._proj_basis .conj ().T .dot (self .snapshots )
1372+ if s is not None and V is not None :
1373+ # If the singular values and right singular vectors are provided,
1374+ # use them to compute the projection.
1375+ ux = np .diag (s ).dot (V )
1376+ else :
1377+ # Project the snapshot data onto the projection basis.
1378+ ux = self ._proj_basis .conj ().T .dot (self .snapshots )
13651379 ux1 = ux [:, :- 1 ]
13661380 ux2 = ux [:, 1 :]
13671381
@@ -1461,6 +1475,105 @@ def fit(self, X, t):
14611475
14621476 return self
14631477
1478+ def fit_econ (self , s , V , t ):
1479+ """
1480+ Compute the Optimized Dynamic Mode Decomposition using the
1481+ SVD results of the snapshot matrix.
1482+ Use this method if you have pre-computed the SVD and the
1483+ original snapshot matrix is not avaialble or too large to
1484+ store in memory.
1485+ The left singular vectors (U) must be specified as the projection
1486+ basis when initializing the BOPDMD object.
1487+
1488+ :param s: the singular values.
1489+ :type s: array_like
1490+ :param V: matrix of right singular vectors, where each row is a
1491+ different singular vector.
1492+ :type V: numpy.ndarray
1493+ :param t: the input time vector.
1494+ :type t: numpy.ndarray
1495+ """
1496+ self ._reset ()
1497+ self ._time = np .asarray (t ).squeeze ()
1498+ s = np .asarray (s ).squeeze ()
1499+
1500+ if self ._proj_basis is None or not self ._use_proj :
1501+ msg = """
1502+ proj_basis must be provided when using fit_econ,
1503+ and use_proj must be set to True.
1504+ """
1505+ raise ValueError (msg )
1506+ if (
1507+ not isinstance (self ._proj_basis , np .ndarray )
1508+ or self ._proj_basis .ndim != 2
1509+ or self ._proj_basis .shape [1 ] != self ._svd_rank
1510+ ):
1511+ msg = (
1512+ "proj_basis must be a 2D np.ndarray with "
1513+ f"{ self ._svd_rank } columns."
1514+ )
1515+ raise ValueError (msg )
1516+
1517+ if self ._time .ndim > 1 :
1518+ msg = "Input time vector t must be one-dimensional."
1519+ raise ValueError (msg )
1520+
1521+ if s .ndim != 1 or len (s ) != self ._svd_rank :
1522+ msg = f"s must be one-dimensional and of length { self ._svd_rank } ."
1523+ raise ValueError (msg )
1524+
1525+ if (
1526+ not isinstance (V , np .ndarray )
1527+ or V .ndim != 2
1528+ or V .shape [0 ] != self ._svd_rank
1529+ or V .shape [1 ] != len (self ._time )
1530+ ):
1531+ msg = (
1532+ "V must be a 2D numpy.ndarray with shape "
1533+ f"({ self ._svd_rank } , { len (self ._time )} )."
1534+ )
1535+ raise ValueError (msg )
1536+
1537+ # Set/check the initial guess for the continuous-time DMD eigenvalues.
1538+ if self ._init_alpha is None :
1539+ self ._init_alpha = self ._initialize_alpha (s = s , V = V )
1540+ elif (
1541+ not isinstance (self ._init_alpha , np .ndarray )
1542+ or self ._init_alpha .ndim > 1
1543+ or len (self ._init_alpha ) != self ._svd_rank
1544+ ):
1545+ msg = "init_alpha must be a 1D np.ndarray with {} entries."
1546+ raise ValueError (msg .format (self ._svd_rank ))
1547+
1548+ # Build the BOP-DMD operator now that the initial alpha and
1549+ # the projection basis have been defined.
1550+ self ._Atilde = BOPDMDOperator (
1551+ self ._compute_A ,
1552+ self ._use_proj ,
1553+ self ._init_alpha ,
1554+ self ._proj_basis ,
1555+ self ._num_trials ,
1556+ self ._trial_size ,
1557+ self ._eig_sort ,
1558+ self ._eig_constraints ,
1559+ self ._mode_prox ,
1560+ self ._remove_bad_bags ,
1561+ self ._bag_warning ,
1562+ self ._bag_maxfail ,
1563+ ** self ._varpro_opts_dict ,
1564+ )
1565+
1566+ # Define the snapshots that will be used for fitting.
1567+ snp = np .diag (s ).dot (V )
1568+
1569+ # Fit the data.
1570+ self ._b = self .operator .compute_operator (snp .T , self ._time )
1571+
1572+ # Store the singular values
1573+ self ._singular_values = s
1574+
1575+ return self
1576+
14641577 def forecast (self , t ):
14651578 """
14661579 Predict the output X given the input time t using the fitted DMD model.
0 commit comments