class GenericBTReductor(BasicInterface): """Generic Balanced Truncation reductor. Parameters ---------- fom The full-order |LTIModel| to reduce. """ def __init__(self, fom): assert isinstance(fom, LTIModel) self.fom = fom self.V = None self.W = None self._pg_reductor = None self._sv_U_V_cache = None def _gramians(self): """Return low-rank Cholesky factors of Gramians.""" raise NotImplementedError def _sv_U_V(self): """Return singular values and vectors.""" if self._sv_U_V_cache is None: cf, of = self._gramians() U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf), lapack_driver='gesvd') self._sv_U_V_cache = (sv, U.T, Vh) return self._sv_U_V_cache def error_bounds(self): """Returns error bounds for all possible reduced orders.""" raise NotImplementedError 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 reconstruct(self, u): """Reconstruct high-dimensional vector from reduced vector `u`.""" return self._pg_reductor.reconstruct(u)
class GenericBTReductor(BasicObject): """Generic Balanced Truncation reductor. Parameters ---------- fom The full-order |LTIModel| to reduce. mu |Parameter|. """ def __init__(self, fom, mu=None): assert isinstance(fom, LTIModel) self.fom = fom self.mu = fom.parse_parameter(mu) self.V = None self.W = None self._pg_reductor = None self._sv_U_V_cache = None def _gramians(self): """Return low-rank Cholesky factors of Gramians.""" raise NotImplementedError def _sv_U_V(self): """Return singular values and vectors.""" if self._sv_U_V_cache is None: cf, of = self._gramians() U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf, mu=self.mu), lapack_driver='gesvd') self._sv_U_V_cache = (sv, U.T, Vh) return self._sv_U_V_cache def error_bounds(self): """Returns error bounds for all possible reduced orders.""" raise NotImplementedError 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': 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.E, copy=False) # find reduced-order model if self.fom.parametric: fom_mu = 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_mu = self.fom self._pg_reductor = LTIPGReductor(fom_mu, self.W, self.V, projection in ('sr', 'biorth')) rom = self._pg_reductor.reduce() return rom def reconstruct(self, u): """Reconstruct high-dimensional vector from reduced vector `u`.""" return self._pg_reductor.reconstruct(u)
class GenericBTReductor(BasicInterface): """Generic Balanced Truncation reductor. Parameters ---------- fom The system which is to be reduced. """ def __init__(self, fom): assert isinstance(fom, LTIModel) self.fom = fom self.V = None self.W = None self.sv = None self.sU = None self.sV = None def gramians(self): """Return low-rank Cholesky factors of Gramians.""" raise NotImplementedError def sv_U_V(self): """Return singular values and vectors.""" if self.sv is None or self.sU is None or self.sV is None: cf, of = self.gramians() U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf), lapack_driver='gesvd') self.sv = sv self.sU = U.T self.sV = Vh return self.sv, self.sU, self.sV def error_bounds(self): """Returns error bounds for all possible reduced orders.""" raise NotImplementedError 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 system. """ 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 and find the reduced model 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) self.pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth')) rom = self.pg_reductor.reduce() return rom def reconstruct(self, u): """Reconstruct high-dimensional vector from reduced vector `u`.""" self.pg_reductor.reconstruct(u)