def test_gram_schmidt_biorth_with_product(operator_with_arrays_and_products): _, _, U, _, p, _ = operator_with_arrays_and_products if U.dim < 2: return l = len(U) // 2 l = min((l, U.dim - 1)) if l < 1: return U1 = U[:l].copy() U2 = U[l:2 * l].copy() V1 = U1.copy() V2 = U2.copy() A1, A2 = gram_schmidt_biorth(U1, U2, product=p, copy=True) assert np.all(almost_equal(U1, V1)) assert np.all(almost_equal(U2, V2)) assert np.allclose(p.apply2(A2, A1), np.eye(len(A1))) c = np.linalg.cond(A1.to_numpy()) * np.linalg.cond(p.apply(A2).to_numpy()) assert np.all(almost_equal(U1, A1.lincomb(p.apply2(U1, A2)), rtol=c * 1e-14)) assert np.all(almost_equal(U2, A2.lincomb(p.apply2(U2, A1)), rtol=c * 1e-14)) B1, B2 = gram_schmidt_biorth(U1, U2, product=p, copy=False) assert np.all(almost_equal(A1, B1)) assert np.all(almost_equal(A2, B2)) assert np.all(almost_equal(A1, U1)) assert np.all(almost_equal(A2, U2))
def test_gram_schmidt_biorth(vector_array): U = vector_array if U.dim < 2: return l = len(U) // 2 l = min((l, U.dim - 1)) if l < 1: return U1 = U[:l].copy() U2 = U[l:2 * l].copy() V1 = U1.copy() V2 = U2.copy() A1, A2 = gram_schmidt_biorth(U1, U2, copy=True) assert np.all(almost_equal(U1, V1)) assert np.all(almost_equal(U2, V2)) assert np.allclose(A2.dot(A1), np.eye(len(A1))) c = np.linalg.cond(A1.to_numpy()) * np.linalg.cond(A2.to_numpy()) assert np.all(almost_equal(U1, A1.lincomb(U1.dot(A2)), rtol=c * 1e-14)) assert np.all(almost_equal(U2, A2.lincomb(U2.dot(A1)), rtol=c * 1e-14)) B1, B2 = gram_schmidt_biorth(U1, U2, copy=False) assert np.all(almost_equal(A1, B1)) assert np.all(almost_equal(A2, B2)) assert np.all(almost_equal(A1, U1)) assert np.all(almost_equal(A2, U2))
def test_gram_schmidt_biorth(vector_arrays): U1, U2 = vector_arrays V1 = U1.copy() V2 = U2.copy() # this is the default used in gram_schmidt_biorth check_tol = 1e-3 with log_levels( {'pymor.algorithms.gram_schmidt.gram_schmidt_biorth': 'ERROR'}): A1, A2 = gram_schmidt_biorth(U1, U2, copy=True, check_tol=check_tol) assert np.all(almost_equal(U1, V1)) assert np.all(almost_equal(U2, V2)) assert np.allclose(A2.dot(A1), np.eye(len(A1)), atol=check_tol) c = np.linalg.cond(A1.to_numpy()) * np.linalg.cond(A2.to_numpy()) assert np.all(almost_equal(U1, A1.lincomb(A2.dot(U1).T), rtol=c * 1e-14)) assert np.all(almost_equal(U2, A2.lincomb(A1.dot(U2).T), rtol=c * 1e-14)) with log_levels( {'pymor.algorithms.gram_schmidt.gram_schmidt_biorth': 'ERROR'}): B1, B2 = gram_schmidt_biorth(U1, U2, copy=False) assert np.all(almost_equal(A1, B1)) assert np.all(almost_equal(A2, B2)) assert np.all(almost_equal(A1, U1)) assert np.all(almost_equal(A2, U2))
def test_gram_schmidt_biorth_with_product(operator_with_arrays_and_products): _, _, U, _, p, _ = operator_with_arrays_and_products if U.dim < 2: return l = len(U) // 2 l = min((l, U.dim - 1)) if l < 1: return U1 = U[:l].copy() U2 = U[l:2 * l].copy() V1 = U1.copy() V2 = U2.copy() A1, A2 = gram_schmidt_biorth(U1, U2, product=p, copy=True) assert np.all(almost_equal(U1, V1)) assert np.all(almost_equal(U2, V2)) assert np.allclose(p.apply2(A2, A1), np.eye(len(A1))) c = np.linalg.cond(A1.to_numpy()) * np.linalg.cond(p.apply(A2).to_numpy()) assert np.all( almost_equal(U1, A1.lincomb(p.apply2(A2, U1).T), rtol=c * 1e-14)) assert np.all( almost_equal(U2, A2.lincomb(p.apply2(A1, U2).T), rtol=c * 1e-14)) B1, B2 = gram_schmidt_biorth(U1, U2, product=p, copy=False) assert np.all(almost_equal(A1, B1)) assert np.all(almost_equal(A2, B2)) assert np.all(almost_equal(A1, U1)) assert np.all(almost_equal(A2, U2))
def test_gram_schmidt_biorth(vector_array): U = vector_array if U.dim < 2: return l = len(U) // 2 l = min((l, U.dim - 1)) if l < 1: return U1 = U[:l].copy() U2 = U[l:2 * l].copy() V1 = U1.copy() V2 = U2.copy() A1, A2 = gram_schmidt_biorth(U1, U2, copy=True) assert np.all(almost_equal(U1, V1)) assert np.all(almost_equal(U2, V2)) assert np.allclose(A2.dot(A1), np.eye(len(A1))) c = np.linalg.cond(A1.to_numpy()) * np.linalg.cond(A2.to_numpy()) assert np.all(almost_equal(U1, A1.lincomb(U1.dot(A2)), rtol=c * 1e-14)) assert np.all(almost_equal(U2, A2.lincomb(U2.dot(A1)), rtol=c * 1e-14)) B1, B2 = gram_schmidt_biorth(U1, U2, copy=False) assert np.all(almost_equal(A1, B1)) assert np.all(almost_equal(A2, B2)) assert np.all(almost_equal(A1, U1)) assert np.all(almost_equal(A2, U2))
def reduce(self, r, projection='bfsr'): """Reduce using GenericSOBTpv. Parameters ---------- r Order of the reduced model. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices Returns ------- rom Reduced-order |SecondOrderModel|. """ assert 0 < r < self.fom.order assert projection in ('sr', 'bfsr', 'biorth') # compute all necessary Gramian factors gramians = self._gramians() if r > min(len(g) for g in gramians): raise ValueError( 'r needs to be smaller than the sizes of Gramian factors.') # compute projection matrices self.V, self.W, singular_values = self._projection_matrices_and_singular_values( r, gramians) if projection == 'sr': alpha = 1 / np.sqrt(singular_values[:r]) self.V.scal(alpha) self.W.scal(alpha) elif projection == 'bfsr': gram_schmidt(self.V, atol=0, rtol=0, copy=False) gram_schmidt(self.W, atol=0, rtol=0, copy=False) elif projection == 'biorth': gram_schmidt_biorth(self.V, self.W, product=self.fom.M, copy=False) # find the reduced model if self.fom.parametric: fom_mu = self.fom.with_( **{ op: getattr(self.fom, op).assemble(mu=self.mu) for op in ['M', 'E', 'K', 'B', 'Cp', 'Cv'] }) else: fom_mu = self.fom self._pg_reductor = SOLTIPGReductor(fom_mu, self.W, self.V, projection == 'biorth') rom = self._pg_reductor.reduce() return rom
def _projection_matrices(self, rom, projection): if self.fom.parametric: fom = self.fom.with_(**{ op: getattr(self.fom, op).assemble(mu=self.mu) for op in ['A', 'B', 'C', 'D', 'E'] }, parameter_space=None) else: fom = self.fom self.V, self.W = solve_sylv_schur(fom.A, rom.A, E=fom.E, Er=rom.E, B=fom.B, Br=rom.B, C=fom.C, Cr=rom.C) if projection == 'orth': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E) self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')
def reduce(self, r=None, tol=None, projection='bfsr'): """Generic Balanced Truncation. Parameters ---------- r Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified. tol Tolerance for the error bound if `r` is `None`. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`) Returns ------- rom Reduced-order model. """ assert r is not None or tol is not None assert r is None or 0 < r < self.fom.order assert projection in ('sr', 'bfsr', 'biorth') cf, of = self._gramians() sv, sU, sV = self._sv_U_V() # find reduced order if tol is specified if tol is not None: error_bounds = self.error_bounds() r_tol = np.argmax(error_bounds <= tol) + 1 r = r_tol if r is None else min(r, r_tol) if r > min(len(cf), len(of)): raise ValueError( 'r needs to be smaller than the sizes of Gramian factors.') # compute projection matrices self.V = cf.lincomb(sV[:r]) self.W = of.lincomb(sU[:r]) if projection == 'sr': alpha = 1 / np.sqrt(sv[:r]) self.V.scal(alpha) self.W.scal(alpha) elif projection == 'bfsr': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.E) # find reduced-order model self._pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth')) rom = self._pg_reductor.reduce() return rom
def reduce(self, r=None, tol=None, projection='bfsr'): """Generic Balanced Truncation. Parameters ---------- r Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified. tol Tolerance for the error bound if `r` is `None`. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`) Returns ------- rom Reduced-order model. """ assert r is not None or tol is not None assert r is None or 0 < r < self.fom.order assert projection in ('sr', 'bfsr', 'biorth') cf, of = self._gramians() sv, sU, sV = self._sv_U_V() # find reduced order if tol is specified if tol is not None: error_bounds = self.error_bounds() r_tol = np.argmax(error_bounds <= tol) + 1 r = r_tol if r is None else min(r, r_tol) if r > min(len(cf), len(of)): raise ValueError('r needs to be smaller than the sizes of Gramian factors.') # compute projection matrices self.V = cf.lincomb(sV[:r]) self.W = of.lincomb(sU[:r]) if projection == 'sr': alpha = 1 / np.sqrt(sv[:r]) self.V.scal(alpha) self.W.scal(alpha) elif projection == 'bfsr': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.E) # find reduced-order model self._pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth')) rom = self._pg_reductor.reduce() return rom
def reduce(self, r, projection='bfsr'): """Reduce using GenericSOBTpv. Parameters ---------- r Order of the reduced model. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices Returns ------- rom Reduced system. """ assert 0 < r < self.fom.order assert projection in ('sr', 'bfsr', 'biorth') # compute all necessary Gramian factors gramians = self._gramians() if r > min(len(g) for g in gramians): raise ValueError( 'r needs to be smaller than the sizes of Gramian factors.') # compute projection matrices and find the reduced model self.V, self.W, singular_values = self._projection_matrices_and_singular_values( r, gramians) if projection == 'sr': alpha = 1 / np.sqrt(singular_values[:r]) self.V.scal(alpha) self.W.scal(alpha) elif projection == 'bfsr': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.M) self.pg_reductor = SOLTIPGReductor(self.fom, self.W, self.V, projection == 'biorth') rom = self.pg_reductor.reduce() return rom
def _set_V_W_reductor(self, rom, projection): fom = ( self.fom.with_( **{op: getattr(self.fom, op).assemble(mu=self.mu) for op in ['A', 'B', 'C', 'D', 'E']} ) if self.fom.parametric else self.fom ) self.V, self.W = solve_sylv_schur(fom.A, rom.A, E=fom.E, Er=rom.E, B=fom.B, Br=rom.B, C=fom.C, Cr=rom.C) if projection == 'orth': gram_schmidt(self.V, atol=0, rtol=0, copy=False) gram_schmidt(self.W, atol=0, rtol=0, copy=False) elif projection == 'biorth': gram_schmidt_biorth(self.V, self.W, product=fom.E, copy=False) self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')
def _projection_matrices(self, rom, projection): fom = self.fom self.V, self.W = solve_sylv_schur(fom.A, rom.A, E=fom.E, Er=rom.E, B=fom.B, Br=rom.B, C=fom.C, Cr=rom.C) if projection == 'orth': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E) self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')
def reduce(self, r, projection='bfsr'): """Reduce using SOBTp. Parameters ---------- r Order of the reduced model. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices Returns ------- rd Reduced system. """ assert 0 < r < self.d.n assert projection in ('sr', 'bfsr', 'biorth') # compute all necessary Gramian factors gramians = self.gramians() if r > min(len(g) for g in gramians): raise ValueError('r needs to be smaller than the sizes of Gramian factors.') # compute projection matrices and find the reduced model self.V, self.W, singular_values = self.projection_matrices_and_singular_values(r, gramians) if projection == 'sr': alpha = 1 / np.sqrt(singular_values[:r]) self.V.scal(alpha) self.W.scal(alpha) elif projection == 'bfsr': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.d.M) self.pg_reductor = GenericPGReductor(self.d, self.W, self.V, projection == 'biorth', product=self.d.M) rd = self.pg_reductor.reduce() return rd
def _projection_matrices(self, rom, projection): fom = self.fom self.V, self.W = solve_sylv_schur(fom.A, rom.A, E=fom.E, Er=rom.E, B=fom.B, Br=rom.B, C=fom.C, Cr=rom.C) if projection == 'orth': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E) self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')
def reduce(self, sigma, b, c, projection='orth'): """Bitangential Hermite interpolation. Parameters ---------- sigma Interpolation points (closed under conjugation), list of length `r`. b Right tangential directions, |VectorArray| of length `r` from `self.fom.input_space`. c Left tangential directions, |VectorArray| of length `r` from `self.fom.output_space`. projection Projection method: - `'orth'`: projection matrices are orthogonalized with respect to the Euclidean inner product - `'biorth'`: projection matrices are biorthogolized with respect to the E product Returns ------- rom Reduced-order model. """ r = len(sigma) assert b in self.fom.input_space and len(b) == r assert c in self.fom.output_space and len(c) == r assert projection in ('orth', 'biorth') # rescale tangential directions (to avoid overflow or underflow) if b.dim > 1: b.scal(1 / b.l2_norm()) else: b = self.fom.input_space.ones(r) if c.dim > 1: c.scal(1 / c.l2_norm()) else: c = self.fom.output_space.ones(r) # compute projection matrices self.V = self.fom.solution_space.empty(reserve=r) self.W = self.fom.solution_space.empty(reserve=r) for i in range(r): if sigma[i].imag == 0: Bb = self._B_apply(sigma[i].real, b.real[i]) self.V.append(self._K_apply_inverse(sigma[i].real, Bb)) CTc = self._C_apply_adjoint(sigma[i].real, c.real[i]) self.W.append(self._K_apply_inverse_adjoint( sigma[i].real, CTc)) elif sigma[i].imag > 0: Bb = self._B_apply(sigma[i], b[i]) v = self._K_apply_inverse(sigma[i], Bb) self.V.append(v.real) self.V.append(v.imag) CTc = self._C_apply_adjoint(sigma[i], c[i].conj()) w = self._K_apply_inverse_adjoint(sigma[i], CTc) self.W.append(w.real) self.W.append(w.imag) if projection == 'orth': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self._product) # find reduced-order model self._pg_reductor = self._PGReductor(self._fom_assemble(), self.W, self.V, projection == 'biorth') rom = self._pg_reductor.reduce() return rom
def reduce(self, sigma, b, c, projection='orth'): """Bitangential Hermite interpolation. Parameters ---------- sigma Interpolation points (closed under conjugation), list of length `r`. b Right tangential directions, |VectorArray| of length `r` from `self._B_source`. c Left tangential directions, |VectorArray| of length `r` from `self._C_range`. projection Projection method: - `'orth'`: projection matrices are orthogonalized with respect to the Euclidean inner product - `'biorth'`: projection matrices are biorthogolized with respect to the E product Returns ------- rd Reduced discretization. """ r = len(sigma) assert b in self._B_source and len(b) == r assert c in self._C_range and len(c) == r assert projection in ('orth', 'biorth') # rescale tangential directions (to avoid overflow or underflow) if b.dim > 1: b.scal(1 / b.l2_norm()) else: b = self._B_source.from_numpy(np.ones((r, 1))) if c.dim > 1: c.scal(1 / c.l2_norm()) else: c = self._C_range.from_numpy(np.ones((r, 1))) # compute projection matrices self.V = self._K_source.empty(reserve=r) self.W = self._K_source.empty(reserve=r) for i in range(r): if sigma[i].imag == 0: Bb = self._B_apply(sigma[i].real, b.real[i]) self.V.append(self._K_apply_inverse(sigma[i].real, Bb)) CTc = self._C_apply_adjoint(sigma[i].real, c.real[i]) self.W.append(self._K_apply_inverse_adjoint(sigma[i].real, CTc)) elif sigma[i].imag > 0: Bb = self._B_apply(sigma[i], b[i]) v = self._K_apply_inverse(sigma[i], Bb) self.V.append(v.real) self.V.append(v.imag) CTc = self._C_apply_adjoint(sigma[i], c[i].conj()) w = self._K_apply_inverse_adjoint(sigma[i], CTc) self.W.append(w.real) self.W.append(w.imag) if projection == 'orth': self.V = gram_schmidt(self.V, atol=0, rtol=0) self.W = gram_schmidt(self.W, atol=0, rtol=0) elif projection == 'biorth': self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self._product) self.pg_reductor = GenericPGReductor(self.d, self.W, self.V, projection == 'biorth', product=self._product) rd = self.pg_reductor.reduce() return rd
def reduce(self, sigma, b, c, projection='orth'): """Bitangential Hermite interpolation. Parameters ---------- sigma Interpolation points (closed under conjugation), sequence of length `r`. b Right tangential directions, |NumPy array| of shape `(r, fom.dim_input)`. c Left tangential directions, |NumPy array| of shape `(r, fom.dim_output)`. projection Projection method: - `'orth'`: projection matrices are orthogonalized with respect to the Euclidean inner product - `'biorth'`: projection matrices are biorthogolized with respect to the E product Returns ------- rom Reduced-order model. """ r = len(sigma) assert b.shape == (r, self.fom.dim_input) assert c.shape == (r, self.fom.dim_output) assert projection in ('orth', 'biorth') # rescale tangential directions (to avoid overflow or underflow) b = b * (1 / np.linalg.norm(b)) if b.shape[1] > 1 else np.ones((r, 1)) c = c * (1 / np.linalg.norm(c)) if c.shape[1] > 1 else np.ones((r, 1)) b = self.fom.D.source.from_numpy(b) c = self.fom.D.range.from_numpy(c) # compute projection matrices self.V = self.fom.solution_space.empty(reserve=r) self.W = self.fom.solution_space.empty(reserve=r) for i in range(r): if sigma[i].imag == 0: Bb = self._B_apply(sigma[i].real, b.real[i]) self.V.append(self._K_apply_inverse(sigma[i].real, Bb)) CTc = self._C_apply_adjoint(sigma[i].real, c.real[i]) self.W.append(self._K_apply_inverse_adjoint( sigma[i].real, CTc)) elif sigma[i].imag > 0: Bb = self._B_apply(sigma[i], b[i]) v = self._K_apply_inverse(sigma[i], Bb) self.V.append(v.real) self.V.append(v.imag) CTc = self._C_apply_adjoint(sigma[i], c[i].conj()) w = self._K_apply_inverse_adjoint(sigma[i], CTc) self.W.append(w.real) self.W.append(w.imag) if projection == 'orth': gram_schmidt(self.V, atol=0, rtol=0, copy=False) gram_schmidt(self.W, atol=0, rtol=0, copy=False) elif projection == 'biorth': gram_schmidt_biorth(self.V, self.W, product=self._product, copy=False) # find reduced-order model self._pg_reductor = self._PGReductor(self._fom_assemble(), self.W, self.V, projection == 'biorth') rom = self._pg_reductor.reduce() return rom
def reduce(self, r, projection='bfsr'): """Reduce using SOBT. Parameters ---------- r Order of the reduced model. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices Returns ------- rd Reduced system. """ assert 0 < r < self.d.n assert projection in ('sr', 'bfsr', 'biorth') # compute all necessary Gramian factors pcf = self.d.gramian('pcf') pof = self.d.gramian('pof') vcf = self.d.gramian('vcf') vof = self.d.gramian('vof') if r > min(len(pcf), len(pof), len(vcf), len(vof)): raise ValueError('r needs to be smaller than the sizes of Gramian factors.') # find necessary SVDs Up, sp, Vp = spla.svd(pof.inner(pcf)) Up = Up.T Uv, sv, Vv = spla.svd(vof.inner(vcf, product=self.d.M)) Uv = Uv.T # compute projection matrices and find the reduced model self.V1 = pcf.lincomb(Vp[:r]) self.W1 = pof.lincomb(Up[:r]) self.V2 = vcf.lincomb(Vv[:r]) self.W2 = vof.lincomb(Uv[:r]) if projection == 'sr': alpha1 = 1 / np.sqrt(sp[:r]) self.V1.scal(alpha1) self.W1.scal(alpha1) alpha2 = 1 / np.sqrt(sv[:r]) self.V2.scal(alpha2) self.W2.scal(alpha2) W1TV1invW1TV2 = self.W1.inner(self.V2) projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r, self.d.state_space.id))} elif projection == 'bfsr': self.V1 = gram_schmidt(self.V1, atol=0, rtol=0) self.W1 = gram_schmidt(self.W1, atol=0, rtol=0) self.V2 = gram_schmidt(self.V2, atol=0, rtol=0) self.W2 = gram_schmidt(self.W2, atol=0, rtol=0) W1TV1invW1TV2 = spla.solve(self.W1.inner(self.V1), self.W1.inner(self.V2)) projected_ops = {'M': project(self.d.M, range_basis=self.W2, source_basis=self.V2)} elif projection == 'biorth': self.V1, self.W1 = gram_schmidt_biorth(self.V1, self.W1) self.V2, self.W2 = gram_schmidt_biorth(self.V2, self.W2, product=self.d.M) W1TV1invW1TV2 = self.W1.inner(self.V2) projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r, self.d.state_space.id))} projected_ops.update({'E': project(self.d.E, range_basis=self.W2, source_basis=self.V2), 'K': project(self.d.K, range_basis=self.W2, source_basis=self.V1.lincomb(W1TV1invW1TV2.T)), 'B': project(self.d.B, range_basis=self.W2, source_basis=None), 'Cp': project(self.d.Cp, range_basis=None, source_basis=self.V1.lincomb(W1TV1invW1TV2.T)), 'Cv': project(self.d.Cv, range_basis=None, source_basis=self.V2)}) rd = self.d.with_(operators=projected_ops, visualizer=None, estimator=None, cache_region=None, name=self.d.name + '_reduced') rd.disable_logging() return rd
def reduce(self, r, projection='bfsr'): """Reduce using SOBT. Parameters ---------- r Order of the reduced model. projection Projection method used: - `'sr'`: square root method - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by singular values and orthogonalizes the projection matrices, which might make it more accurate than the square root method) - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the projection matrices Returns ------- rom Reduced-order |SecondOrderModel|. """ assert 0 < r < self.fom.order assert projection in ('sr', 'bfsr', 'biorth') # compute all necessary Gramian factors pcf = self.fom.gramian('pc_lrcf', mu=self.mu) pof = self.fom.gramian('po_lrcf', mu=self.mu) vcf = self.fom.gramian('vc_lrcf', mu=self.mu) vof = self.fom.gramian('vo_lrcf', mu=self.mu) if r > min(len(pcf), len(pof), len(vcf), len(vof)): raise ValueError( 'r needs to be smaller than the sizes of Gramian factors.') # find necessary SVDs Up, sp, Vp = spla.svd(pof.inner(pcf), lapack_driver='gesvd') Up = Up.T Uv, sv, Vv = spla.svd(vof.inner(vcf, product=self.fom.M), lapack_driver='gesvd') Uv = Uv.T # compute projection matrices and find the reduced model self.V1 = pcf.lincomb(Vp[:r]) self.W1 = pof.lincomb(Up[:r]) self.V2 = vcf.lincomb(Vv[:r]) self.W2 = vof.lincomb(Uv[:r]) if projection == 'sr': alpha1 = 1 / np.sqrt(sp[:r]) self.V1.scal(alpha1) self.W1.scal(alpha1) alpha2 = 1 / np.sqrt(sv[:r]) self.V2.scal(alpha2) self.W2.scal(alpha2) W1TV1invW1TV2 = self.W1.inner(self.V2) projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r))} elif projection == 'bfsr': gram_schmidt(self.V1, atol=0, rtol=0, copy=False) gram_schmidt(self.W1, atol=0, rtol=0, copy=False) gram_schmidt(self.V2, atol=0, rtol=0, copy=False) gram_schmidt(self.W2, atol=0, rtol=0, copy=False) W1TV1invW1TV2 = spla.solve(self.W1.inner(self.V1), self.W1.inner(self.V2)) projected_ops = { 'M': project(self.fom.M, range_basis=self.W2, source_basis=self.V2) } elif projection == 'biorth': gram_schmidt_biorth(self.V1, self.W1, copy=False) gram_schmidt_biorth(self.V2, self.W2, product=self.fom.M, copy=False) W1TV1invW1TV2 = self.W1.inner(self.V2) projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r))} projected_ops.update({ 'E': project(self.fom.E.assemble(mu=self.mu), range_basis=self.W2, source_basis=self.V2), 'K': project(self.fom.K.assemble(mu=self.mu), range_basis=self.W2, source_basis=self.V1.lincomb(W1TV1invW1TV2.T)), 'B': project(self.fom.B.assemble(mu=self.mu), range_basis=self.W2, source_basis=None), 'Cp': project(self.fom.Cp.assemble(mu=self.mu), range_basis=None, source_basis=self.V1.lincomb(W1TV1invW1TV2.T)), 'Cv': project(self.fom.Cv.assemble(mu=self.mu), range_basis=None, source_basis=self.V2), 'D': self.fom.D.assemble(mu=self.mu), }) rom = SecondOrderModel(name=self.fom.name + '_reduced', **projected_ops) rom.disable_logging() return rom