1717"""
1818
1919import warnings
20- from typing import Any , Dict , Tuple , Union , Optional
2120from types import MappingProxyType
21+ from typing import Any , Dict , Tuple , Union
22+
2223import numpy as np
23- from scipy .optimize import OptimizeResult , least_squares
2424from scipy .linalg import qr
25+ from scipy .optimize import OptimizeResult , least_squares
26+
2527from .dmd import DMDBase
2628from .dmdoperator import DMDOperator
2729from .snapshots import Snapshots
28-
30+ from . utils import compute_svd
2931
3032OPT_DEF_ARGS = MappingProxyType (
3133 {
4042)
4143
4244
43- def _svht (
44- sigma_svd : np .ndarray ,
45- rows : int ,
46- cols : int ,
47- sigma : Optional [float ] = None ,
48- ) -> int :
49- """
50- Determine optimal rank for svd matrix approximation,
51- based on https://arxiv.org/pdf/1305.5870.pdf.
52-
53- :param sigma_svd: Diagonal matrix of "enonomy" SVD as 1d array.
54- :type sigma_svd: np.ndarray
55- :param rows: Number of rows of original data matrix.
56- :type cols: int
57- :param cols: Number of columns of original data matrix.
58- :type cols: int
59- :param sigma: Signal noise level if known, defaults to None.
60- :type sigma: Optional[float], optional
61- :raises ValueError: sigma_svd must be a 1d-array else exception is raised.
62- :return: Optimal rank.
63- :rtype: int
64- """
65-
66- if len (sigma_svd .shape ) != 1 :
67- raise ValueError ("Expected 1d array for diagonal matrix!" )
68-
69- beta = (
70- float (cols ) / float (rows ) if rows > cols else float (rows ) / float (cols )
71- )
72-
73- tau_star = 0
74-
75- if sigma is not None :
76- sigma = abs (sigma )
77- lambda_star = np .sqrt (
78- 2 * (beta + 1 )
79- + (8 * beta / (beta + 1 + np .sqrt (beta * beta + 14 * beta + 1 )))
80- )
81- tau_star = (
82- lambda_star
83- * sigma
84- * np .sqrt (float (cols ) if cols > rows else float (rows ))
85- )
86- else :
87- median = np .median (sigma_svd )
88- beta_square = beta * beta
89- beta_cubic = beta * beta_square
90- omega = 0.56 * beta_cubic - 0.95 * beta_square + 1.82 * beta + 1.43
91- tau_star = median * omega
92-
93- rank = np .where (sigma_svd >= tau_star )[0 ]
94-
95- if rank .size == 0 :
96- return sigma_svd .shape [- 1 ]
97-
98- return rank [- 1 ] + 1
99-
100-
101- def _compute_rank (
102- sigma_x : np .ndarray ,
103- rows : int ,
104- cols : int ,
105- rank : Union [float , int ],
106- sigma : float = None ,
107- ) -> int :
108- r"""
109- Compute rank without duplicate SVD computation.
110-
111- :param sigma_x: Diagonal matrix of SVD as 1d array.
112- :type sigma_x: np.ndarray
113- :param rows: Number of rows of the original data matrix.
114- :type rows: int
115- :param cols: Number of columns of the original data matrix.
116- :type cols: int
117- :param rank: Desired input rank :math:`r`. If rank is an integer,
118- and :math:`r > 0`, the desired rank is used iff possible.
119- This depends on the size of parameter sigma_x. If the desired
120- rank exceeds the size of sigma_x, then the minimum of desired rank
121- and the size of sigma_x is used. If the rank is a float
122- and :math:`0 < r < 1`, than the rank is determined by investigating
123- the cumulative energy of the singular values.
124- :type rank: Union[float, int]
125- :param sigma: Noise level for SVHT algorithm. If it is unknown
126- keep it as None. Defaults to None.
127- :type sigma: float, optional
128- :raises ValueError: ValueError is raised if rank is negative
129- (:math:`r < 0`).
130- :return: Optimal rank.
131- :rtype: int
132- """
133-
134- if 0 < rank < 1 :
135- sigma_x_square = np .square (sigma_x )
136- cumulative_energy = np .cumsum (sigma_x_square / sigma_x_square .sum ())
137- __rank = np .searchsorted (cumulative_energy , rank ) + 1
138- elif rank == 0 :
139- __rank = _svht (sigma_x , rows , cols , sigma )
140- elif rank >= 1 :
141- __rank = int (rank )
142- else :
143- raise ValueError (f"Invalid rank specified, provided { rank } !" )
144- return min (__rank , sigma_x .size )
145-
146-
14745def _compute_dmd_ev (
14846 x_current : np .ndarray ,
14947 x_next : np .ndarray ,
@@ -167,22 +65,13 @@ def _compute_dmd_ev(
16765 :rtype: np.ndarray
16866 """
16967
170- u_x , sigma_x , v_x_t = np .linalg .svd (
171- x_current , full_matrices = False , hermitian = False
172- )
173- __rank = _compute_rank (
174- sigma_x , x_current .shape [0 ], x_current .shape [1 ], rank
175- )
68+ u_x , sigma_x , v_x = compute_svd (x_current , rank )
17669
17770 # columns of v need to be multiplicated with inverse sigma
178- sigma_inv_approx = np .reciprocal (sigma_x [: __rank ] )
71+ sigma_inv_approx = np .reciprocal (sigma_x )
17972
18073 a_approx = np .linalg .multi_dot (
181- [
182- u_x [:, :__rank ].conj ().T ,
183- x_next ,
184- sigma_inv_approx [None ] * v_x_t [:__rank , :].conj ().T ,
185- ]
74+ [u_x .conj ().T , x_next , sigma_inv_approx [None ] * v_x ]
18675 )
18776
18877 return np .linalg .eigvals (a_approx )
@@ -368,11 +257,7 @@ def _varpro_preprocessing(
368257 np.ndarray]
369258 """
370259
371- u_r , s_r , v_r_t = np .linalg .svd (data , full_matrices = False )
372- __rank = _compute_rank (s_r , data .shape [0 ], data .shape [1 ], rank )
373- u_r = u_r [:, :__rank ]
374- s_r = s_r [:__rank ]
375- v_r = v_r_t [:__rank ].conj ().T
260+ u_r , s_r , v_r = compute_svd (data , rank )
376261 data_out = v_r .conj ().T * s_r [:, None ] if use_proj else data
377262
378263 # trapezoidal derivative approximation
0 commit comments