Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
    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