def mimo_block_arnoldi(self, frequency, r): n = self.ss.states A = self.ss.A B = self.ss.B C = self.ss.C for i in range(self.nfreq): if self.frequency[i] == np.inf: F = A G = B else: lu_a = krylovutils.lu_factor(frequency[i], A) F = krylovutils.lu_solve(lu_a, np.eye(n)) G = krylovutils.lu_solve(lu_a, B) if i == 0: V = krylovutils.block_arnoldi_krylov(r, F, G) else: Vi = krylovutils.block_arnoldi_krylov(r, F, G) V = np.block([V, Vi]) self.V = V Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) return Ar, Br, Cr
def two_sided_arnoldi(self, frequency, r): r""" Two-sided projection with a single interpolation point following the Arnoldi procedure. Very similar to the one-sided method available, but it adds the projection :math:`\mathbf{W}` built using the Krylov space for the :math:`\mathbf{c}` vector: .. math:: \mathcal{K}_r((\sigma\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{c}^T)\subseteq\mathcal{W}=\text{range}(\mathbf{W}) The oblique projection :math:`\mathbf{VW}^T` matches twice as many moments as the single sided projection. The resulting system takes the form: .. math:: \hat{\Sigma} : \left(\begin{array}{c|c} \hat{A} & \hat{B} \\ \hline \hat{C} & {D}\end{array}\right) \text{with } \begin{cases}\hat{A}=W^TAV\in\mathbb{R}^{k\times k},\,\\ \hat{B}=W^TB\in\mathbb{R}^{k\times m},\,\\ \hat{C}=CV\in\mathbb{R}^{p\times k},\,\\ \hat{D}=D\in\mathbb{R}^{p\times m}\end{cases} Args: frequency (complex): Interpolation point :math:`\sigma \in \mathbb{C}` r (int): Number of moments to match on each side. The resulting ROM will be of order :math:`2r`. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r`. """ A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] if frequency != np.inf and frequency is not None: lu_A = krylovutils.lu_factor(frequency, A) V = krylovutils.construct_krylov(r, lu_A, B, 'Pade', 'b') W = krylovutils.construct_krylov(r, lu_A, C.T, 'Pade', 'c') else: V = krylovutils.construct_krylov(r, A, B, 'partial_realisation', 'b') W = krylovutils.construct_krylov(r, A, C.T, 'partial_realisation', 'c') T = W.T.dot(V) Tinv = sclalg.inv(T) reduction_checks(T, Tinv) self.W = W self.V = V # Reduced state space model Ar = W.T.dot(self.ss.A.dot(V.dot(Tinv))) Br = W.T.dot(self.ss.B) Cr = self.ss.C.dot(V.dot(Tinv)) return Ar, Br, Cr
def one_sided_arnoldi(self, frequency, r): r""" One-sided Arnoldi method expansion about a single interpolation point, :math:`\sigma`. The projection matrix :math:`\mathbf{V}` is constructed using an order :math:`r` Krylov space. The space for a single finite interpolation point known as a Pade approximation is described by: .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r((\sigma\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{b}) In the case of an interpolation about infinity, the problem is known as partial realisation and the Krylov space is .. math:: \text{range}(\textbf{V}) = \mathcal{K}_r(\mathbf{A}, \mathbf{b}) The resulting orthogonal projection leads to the following reduced order system: .. math:: \hat{\Sigma} : \left(\begin{array}{c|c} \hat{A} & \hat{B} \\ \hline \hat{C} & {D}\end{array}\right) \text{with } \begin{cases}\hat{A}=V^TAV\in\mathbb{R}^{k\times k},\,\\ \hat{B}=V^TB\in\mathbb{R}^{k\times m},\,\\ \hat{C}=CV\in\mathbb{R}^{p\times k},\,\\ \hat{D}=D\in\mathbb{R}^{p\times m}\end{cases} Args: frequency (complex): Interpolation point :math:`\sigma \in \mathbb{C}` r (int): Number of moments to match. Equivalent to Krylov space order and order of the ROM. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r` """ A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] if frequency != np.inf and frequency is not None: lu_A = krylovutils.lu_factor(frequency, A) V = krylovutils.construct_krylov(r, lu_A, B, 'Pade', 'b') else: V = krylovutils.construct_krylov(r, A, B, 'partial_realisation', 'b') # Reduced state space model Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) self.V = V self.W = V return Ar, Br, Cr
def dual_rational_arnoldi(self, frequency, r): r""" Dual Rational Arnoli Interpolation for SISO sytems [1] and MIMO systems through tangential interpolation [2]. Effectively the same as the two_sided_arnoldi and the resulting V matrices for each interpolation point are concatenated .. math:: \bigcup\limits_{k = 1}^K\mathcal{K}_{b_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{b})\subseteq\mathcal{V}&=\text{range}(\mathbf{V}) \\ \bigcup\limits_{k = 1}^K\mathcal{K}_{c_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{c}^T)\subseteq\mathcal{Z}&=\text{range}(\mathbf{Z}) For MIMO systems, tangential interpolation is used through the right and left tangential direction vectors :math:`\mathbf{r}_i` and :math:`\mathbf{l}_i`. .. math:: \bigcup\limits_{k = 1}^K\mathcal{K}_{b_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-1}\mathbf{Br}_i)\subseteq\mathcal{V}&=\text{range}(\mathbf{V}) \\ \bigcup\limits_{k = 1}^K\mathcal{K}_{c_k}((\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}, (\sigma_i\mathbf{I}_n - \mathbf{A})^{-T}\mathbf{C}^T\mathbf{l}_i)\subseteq\mathcal{Z}&=\text{range}(\mathbf{Z}) Args: frequency (np.ndarray): Array containing the interpolation points :math:`\sigma = \{\sigma_1, \dots, \sigma_K\}\in\mathbb{C}` r (int): Krylov space order :math:`b_k` and :math:`c_k`. At the moment, different orders for the controllability and observability constructions are not supported. right_tangent (np.ndarray): Matrix containing the right tangential direction interpolation vector for each interpolation point in column form, i.e. :math:`\mathbf{r}\in\mathbb{R}^{m \times K}`. left_tangent (np.ndarray): Matrix containing the left tangential direction interpolation vector for each interpolation point in column form, i.e. :math:`\mathbf{l}\in\mathbb{R}^{p \times K}`. Returns: tuple: The reduced order model matrices: :math:`\mathbf{A}_r`, :math:`\mathbf{B}_r` and :math:`\mathbf{C}_r`. References: [1] Grimme [2] Gallivan """ A = self.ss.A B = self.ss.B C = self.ss.C nx = self.ss.states nu = self.ss.inputs ny = self.ss.outputs B.shape = (nx, nu) if nu != 1: left_tangent, right_tangent, rc, ro, fc, fo = self.load_tangent_vectors( ) assert right_tangent is not None and left_tangent is not None, 'Missing interpolation vectors for MIMO case' else: fc = np.array(frequency) fo = np.array(frequency) left_tangent = np.zeros((1, len(fo))) right_tangent = np.zeros((1, len(fc))) rc = np.array([r] * len(fc)) ro = np.array([r] * len(fc)) right_tangent[0, :] = 1 left_tangent[0, :] = 1 try: nfreq = frequency.shape[0] except AttributeError: nfreq = 1 t0 = time.time() # # Tangential interpolation for MIMO systems # if right_tangent is None: # right_tangent = np.eye((nu, nfreq)) # # else: # # assert right_tangent.shape == (nu, nfreq), 'Right Tangential Direction vector not the correct shape' # # if left_tangent is None: # left_tangent = np.eye((ny, nfreq)) # # else: # # assert left_tangent.shape == (ny, nfreq), 'Left Tangential Direction vector not the correct shape' rom_dim = max(np.sum(rc), np.sum(ro)) V = np.zeros((nx, rom_dim), dtype=complex) W = np.zeros((nx, rom_dim), dtype=complex) we = 0 dict_of_luas = dict() for i in range(len(fc)): sigma = fc[i] if sigma == np.inf: approx_type = 'partial_realisation' lu_A = A else: approx_type = 'Pade' try: lu_A = dict_of_luas[sigma] except KeyError: lu_A = krylovutils.lu_factor(sigma, A) dict_of_luas[sigma] = lu_A V[:, we:we + rc[i]] = krylovutils.construct_krylov( rc[i], lu_A, B.dot(right_tangent[:, i:i + 1]), approx_type, 'b') we += rc[i] we = 0 for i in range(len(fo)): sigma = fo[i] if sigma == np.inf: approx_type = 'partial_realisation' lu_A = A else: approx_type = 'Pade' try: lu_A = dict_of_luas[sigma] except KeyError: lu_A = krylovutils.lu_factor(sigma, A) dict_of_luas[sigma] = lu_A W[:, we:we + ro[i]] = krylovutils.construct_krylov( ro[i], lu_A, C.T.dot(left_tangent[:, i:i + 1]), approx_type, 'c') we += ro[i] T = W.T.dot(V) Tinv = sclalg.inv(T) reduction_checks(T, Tinv) self.W = W self.V = V # Reduced state space model Ar = W.T.dot(self.ss.A.dot(V.dot(Tinv))) Br = W.T.dot(self.ss.B) Cr = self.ss.C.dot(V.dot(Tinv)) del dict_of_luas self.cpu_summary['algorithm'] = time.time() - t0 return Ar, Br, Cr
def real_rational_arnoldi(self, frequency, r): """ When employing complex frequencies, the projection matrix can be normalised to be real Following Algorithm 1b in Lee(2006) Args: frequency: r: Returns: """ raise NotImplementedError( 'Real valued rational Arnoldi Method Work in progress - use mimo_rational_arnoldi' ) ### Not working, having trouble with the last column of H. need to investigate the background behind the creation of H and see hwat can be done A = self.ss.A B = self.ss.B C = self.ss.C nx = A.shape[0] nfreq = frequency.shape[0] # Columns of matrix v v_ncols = 2 * np.sum(r) # Output projection matrices V = np.zeros((nx, v_ncols), dtype=float) H = np.zeros((v_ncols, v_ncols), dtype=float) res = np.zeros((nx, v_ncols + 2), dtype=float) # lu_A = krylovutils.lu_factor(frequency[0] * np.eye(nx) - A) v_res = sclalg.lu_solve(lu_A, B) H[0, 0] = np.linalg.norm(v_res) V[:, 0] = v_res.real / H[0, 0] k = 0 for i in range(nfreq): for j in range(r[i]): # k = 2*(i*r[i] + j) print("i = %g\t j = %g\t k = %g" % (i, j, k)) # res[:, k] = np.imag(v_res) # if k > 0: # res[:, k-1] = np.real(v_res) # # # Working on the last finished column i.e. k-1 only when k>0 # if k > 0: # for t in range(k): # H[t, k-1] = V[:, t].T.dot(res[:, k-1]) # res[:, k-1] -= res[:, k-1] - H[t, k-1] * V[:, t] # # H[k, k-1] = np.linalg.norm(res[:, k-1]) # V[:, k] = res[:, k-1] / H[k, k-1] # # # Normalise working column k # for t in range(k+1): # H[t, k] = V[:, t].T.dot(res[:, k]) # res[:, k] -= H[t, k] * V[:, t] # # # Subdiagonal term # H[k+1, k] = np.linalg.norm(res[:, k]) # V[:, k + 1] = res[:, k] / np.linalg.norm(res[:, k]) # # if j == r[i] - 1 and i < nfreq - 1: # lu_A = krylovutils.lu_factor(frequency[i+1] * np.eye(nx) - A) # v_res = sclalg.lu_solve(lu_A, B) # else: # v_res = - sclalg.lu_solve(lu_A, V[:, k+1]) if k == 0: V[:, 0] = v_res.real / np.linalg.norm(v_res.real) else: res[:, k] = np.imag(v_res) res[:, k - 1] = np.real(v_res) for t in range(k): H[t, k - 1] = np.linalg.norm(res[:, k - 1]) res[:, k - 1] -= H[t, k - 1] * V[:, t] H[k, k - 1] = np.linalg.norm(res[:, k - 1]) V[:, k] = res[:, k - 1] / H[k, k - 1] if k == 0: H[0, 0] = V[:, 0].T.dot(v_res.imag) res[:, 0] -= H[0, 0] * V[:, 0] else: for t in range(k + 1): H[t, k] = V[:, t].T.dot(res[:, k]) res[:, k] -= H[t, k] * V[:, t] H[k + 1, k] = np.linalg.norm(res[:, k]) V[:, k + 1] = res[:, k] / H[k + 1, k] if j == r[i] - 1 and i < nfreq - 1: lu_A = krylovutils.lu_factor(frequency[i + 1], A) v_res = sclalg.lu_solve(lu_A, B) else: v_res = -sclalg.lu_solve(lu_A, V[:, k + 1]) k += 2 # Add last column of H print(k) res[:, k - 1] = -sclalg.lu_solve(lu_A, V[:, k - 1]) for t in range(k - 1): H[t, k - 1] = V[:, t].T.dot(res[:, k - 1]) res[:, k - 1] -= H[t, k - 1] * V[:, t] self.V = V self.H = H Ar = V.T.dot(A.dot(V)) Br = V.T.dot(B) Cr = C.dot(V) return Ar, Br, Cr