def action_merge_low_rank_operators(self, ops): low_rank = [] not_low_rank = [] for op, coeff in zip(ops, self.coefficients): if isinstance(op, LowRankOperator): low_rank.append((op, coeff)) else: not_low_rank.append((op, coeff)) inverted = [op.inverted for op, _ in low_rank] if len(inverted) >= 2 and any(inverted) and any(not _ for _ in inverted): return None inverted = inverted[0] left = cat_arrays([op.left for op, _ in low_rank]) right = cat_arrays([op.right for op, _ in low_rank]) core = [] for op, coeff in low_rank: core.append(op.core) if inverted: core[-1] /= coeff else: core[-1] *= coeff core = spla.block_diag(*core) new_low_rank_op = LowRankOperator(left, core, right, inverted=inverted) if len(not_low_rank) == 0: return new_low_rank_op else: new_ops, new_coeffs = zip(*not_low_rank) return assemble_lincomb(chain(new_ops, [new_low_rank_op]), chain(new_coeffs, [1]), solver_options=self.solver_options, name=self.name)
def create_cl_fom(Re=110, level=2, palpha=1e-3, control='bc'): """Create model which is used to evaluate the H2-Gap norm.""" setup_str = 'lvl_' + str(level) + ('_' + control if control is not None else '') \ + '_re_' + str(Re) + ('_palpha_' + str(palpha) if control == 'bc' else '') fom = load_fom(Re, level, palpha, control) Bra = fom.B.as_range_array() Cva = fom.C.as_source_array() Z = solve_ricc_lrcf(fom.A, fom.E, Bra, Cva, trans=False) K = fom.E.apply(Z).lincomb(Z.dot(Cva).T) KC = LowRankOperator(K, np.eye(len(K)), Cva) mKB = cat_arrays([-K, Bra]).to_numpy().T mKBop = NumpyMatrixOperator(mKB) mKBop_proj = LerayProjectedOperator(mKBop, fom.A.source.G, fom.A.source.E, projection_space='range') cl_fom = LTIModel(fom.A - KC, mKBop_proj, fom.C, None, fom.E) with open(setup_str + '/cl_fom', 'wb') as cl_fom_file: pickle.dump({'cl_fom': cl_fom}, cl_fom_file)
def projection_shifts(A, E, V, prev_shifts): """Find further shift parameters for low-rank ADI iteration using Galerkin projection on spaces spanned by LR-ADI iterates. See [PK16]_, pp. 92-95. Parameters ---------- A The |Operator| A from the corresponding Lyapunov equation. E The |Operator| E from the corresponding Lyapunov equation. V A |VectorArray| representing the currently computed iterate. prev_shifts A |NumPy array| containing the set of all previously used shift parameters. Returns ------- shifts A |NumPy array| containing a set of stable shift parameters. """ if prev_shifts[-1].imag != 0: Q = gram_schmidt(cat_arrays([V.real, V.imag]), atol=0, rtol=0) else: Q = gram_schmidt(V, atol=0, rtol=0) Ap = A.apply2(Q, Q) Ep = E.apply2(Q, Q) shifts = spla.eigvals(Ap, Ep) shifts.imag[abs(shifts.imag) < np.finfo(float).eps] = 0 shifts = shifts[np.real(shifts) < 0] if shifts.size == 0: return prev_shifts else: if np.any(shifts.imag != 0): shifts = shifts[np.abs(shifts).argsort()] else: shifts.sort() return shifts
def solve_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=None): """Compute an approximate low-rank solution of a Riccati equation. See :func:`pymor.algorithms.riccati.solve_ricc_lrcf` for a general description. This function is an implementation of Algorithm 2 in [BBKS18]_. Parameters ---------- A The |Operator| A. E The |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. C The operator C as a |VectorArray| from `A.source`. R The operator R as a 2D |NumPy array| or `None`. S The operator S as a |VectorArray| from `A.source` or `None`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use. (see :func:`ricc_lrcf_solver_options`) Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, None, None, trans) options = _parse_options(options, ricc_lrcf_solver_options(), 'lrradi', None, False) logger = getLogger('pymor.algorithms.lrradi.solve_ricc_lrcf') shift_options = options['shift_options'][options['shifts']] if shift_options['type'] == 'hamiltonian_shifts': init_shifts = hamiltonian_shifts_init iteration_shifts = hamiltonian_shifts else: raise ValueError('Unknown lrradi shift strategy.') if E is None: E = IdentityOperator(A.source) if S is not None: raise NotImplementedError if R is not None: Rc = spla.cholesky(R) # R = Rc^T * Rc Rci = spla.solve_triangular(Rc, np.eye( Rc.shape[0])) # R^{-1} = Rci * Rci^T if not trans: C = C.lincomb(Rci.T) # C <- Rci^T * C = (C^T * Rci)^T else: B = B.lincomb(Rci.T) # B <- B * Rci if not trans: B, C = C, B Z = A.source.empty(reserve=len(C) * options['maxiter']) Y = np.empty((0, 0)) K = A.source.zeros(len(B)) RF = C.copy() j = 0 j_shift = 0 shifts = init_shifts(A, E, B, C, shift_options) res = np.linalg.norm(RF.gramian(), ord=2) init_res = res Ctol = res * options['tol'] while res > Ctol and j < options['maxiter']: if not trans: AsE = A + shifts[j_shift] * E else: AsE = A + np.conj(shifts[j_shift]) * E if j == 0: if not trans: V = AsE.apply_inverse(RF) * np.sqrt(-2 * shifts[j_shift].real) else: V = AsE.apply_inverse_adjoint(RF) * np.sqrt( -2 * shifts[j_shift].real) else: if not trans: LN = AsE.apply_inverse(cat_arrays([RF, K])) else: LN = AsE.apply_inverse_adjoint(cat_arrays([RF, K])) L = LN[:len(RF)] N = LN[-len(K):] ImBN = np.eye(len(K)) - B.dot(N) ImBNKL = spla.solve(ImBN, B.dot(L)) V = (L + N.lincomb(ImBNKL.T)) * np.sqrt(-2 * shifts[j_shift].real) if np.imag(shifts[j_shift]) == 0: Z.append(V) VB = V.dot(B) Yt = np.eye(len(C)) - (VB @ VB.T) / (2 * shifts[j_shift].real) Y = spla.block_diag(Y, Yt) if not trans: EVYt = E.apply(V).lincomb(np.linalg.inv(Yt)) else: EVYt = E.apply_adjoint(V).lincomb(np.linalg.inv(Yt)) RF.axpy(np.sqrt(-2 * shifts[j_shift].real), EVYt) K += EVYt.lincomb(VB.T) j += 1 else: Z.append(V.real) Z.append(V.imag) Vr = V.real.dot(B) Vi = V.imag.dot(B) sa = np.abs(shifts[j_shift]) F1 = np.vstack((-shifts[j_shift].real / sa * Vr - shifts[j_shift].imag / sa * Vi, shifts[j_shift].imag / sa * Vr - shifts[j_shift].real / sa * Vi)) F2 = np.vstack((Vr, Vi)) F3 = np.vstack((shifts[j_shift].imag / sa * np.eye(len(C)), shifts[j_shift].real / sa * np.eye(len(C)))) Yt = spla.block_diag(np.eye(len(C)), 0.5 * np.eye(len(C))) \ - (F1 @ F1.T) / (4 * shifts[j_shift].real) \ - (F2 @ F2.T) / (4 * shifts[j_shift].real) \ - (F3 @ F3.T) / 2 Y = spla.block_diag(Y, Yt) EVYt = E.apply(cat_arrays([V.real, V.imag])).lincomb(np.linalg.inv(Yt)) RF.axpy(np.sqrt(-2 * shifts[j_shift].real), EVYt[:len(C)]) K += EVYt.lincomb(F2.T) j += 2 j_shift += 1 res = np.linalg.norm(RF.gramian(), ord=2) logger.info(f'Relative residual at step {j}: {res/init_res:.5e}') if j_shift >= shifts.size: shifts = iteration_shifts(A, E, B, RF, K, Z, shift_options) j_shift = 0 # transform solution to lrcf cf = spla.cholesky(Y) Z_cf = Z.lincomb(spla.solve_triangular(cf, np.eye(len(Z))).T) return Z_cf