def run(self, ss): A, B, C, D = ss.get_mats() try: if ss.dt is not None: dtsystem = True else: dtsystem = False except AttributeError: dtsystem = False S, T, Tinv = librom.balreal_direct_py(A, B, C, DLTI=dtsystem) Ar = T.dot(A.dot(Tinv)) Br = T.dot(B) Cr = C.dot(Tinv) if self.dtsystem: ss_bal = libss.ss(Ar, Br, Cr, self.ss.D, dt=self.ss.dt) else: ss_bal = libss.ss(Ar, Br, Cr, self.ss.D) if self.settings['tune']: kv = np.linspace(self.settings['rom_tune_freq_range'][0], self.settings['rom_tune_freq_range'][1]) ssrom = librom.tune_rom(ss_bal, kv=kv, tol=self.settings['rom_tolerance'], gv=S, convergence=self.settings['convergence'], method=self.settings['reduction_method']) return ssrom else: return ss_bal
def run(self, ss): if self.print_info: cout.cout_wrap( 'Reducing system using a Direct balancing method...') t0 = time.time() A, B, C, D = ss.get_mats() try: if ss.dt is not None: dtsystem = True else: dtsystem = False except AttributeError: dtsystem = False S, T, Tinv = librom.balreal_direct_py(A, B, C, DLTI=dtsystem, Schur=self.settings['use_schur']) Ar = T.dot(A.dot(Tinv)) Br = T.dot(B) Cr = C.dot(Tinv) if dtsystem: ss_bal = libss.ss(Ar, Br, Cr, D, dt=ss.dt) else: ss_bal = libss.ss(Ar, Br, Cr, D) t1 = time.time() if self.print_info: cout.cout_wrap('\t...completed balancing in %.2fs' % (t1 - t0), 1) if self.settings['tune']: cout.cout_wrap('\t\tTuning ROM to specified tolerance...', 2) kv = np.linspace(self.settings['rom_tune_freq_range'][0], self.settings['rom_tune_freq_range'][1]) ssrom = librom.tune_rom(ss_bal, kv=kv, tol=self.settings['rom_tolerance'], gv=S, convergence=self.settings['convergence'], method=self.settings['reduction_method']) if librom.check_stability(ssrom.A, dt=True): if self.settings['print_info']: cout.cout_wrap('ROM by direct balancing is stable') t2 = time.time() cout.cout_wrap('\t...completed reduction in %.2fs' % (t2 - t0), 1) return ssrom else: return ss_bal
def assemble(self, track_body=False): r""" Assembles the linearised UVLM system, removes the desired inputs and adds linearised control surfaces (if present). With all possible inputs present, these are ordered as .. math:: \mathbf{u} = [\boldsymbol{\zeta},\,\dot{\boldsymbol{\zeta}},\,\mathbf{w},\,\delta] Control surface inputs are ordered last as: .. math:: [\delta_1, \delta_2, \dots, \dot{\delta}_1, \dot{\delta_2}] """ self.sys.assemble_ss() if self.scaled: self.sys.nondimss() self.ss = self.sys.SS self.C_to_vertex_forces = self.ss.C.copy() nzeta = 3 * self.sys.Kzeta if self.settings['remove_inputs']: self.remove_inputs(self.settings['remove_inputs']) if self.gust_assembler is not None: A, B, C, D = self.gust_assembler.generate(self.sys, aero=None) ss_gust = libss.ss(A, B, C, D, dt=self.ss.dt) self.gust_assembler.ss_gust = ss_gust self.ss = libss.series(ss_gust, self.ss) if self.control_surface is not None: Kzeta_delta, Kdzeta_ddelta = self.control_surface.generate() n_zeta, n_ctrl_sfc = Kzeta_delta.shape # Modify the state space system with a gain at the input side # such that the control surface deflections are last if self.sys.use_sparse: gain_cs = sp.eye(self.ss.inputs, self.ss.inputs + 2 * self.control_surface.n_control_surfaces, format='lil') gain_cs[:n_zeta, self.ss.inputs:self.ss.inputs + n_ctrl_sfc] = Kzeta_delta gain_cs[n_zeta:2 * n_zeta, self.ss.inputs + n_ctrl_sfc:self.ss.inputs + 2 * n_ctrl_sfc] = Kdzeta_ddelta gain_cs = libsp.csc_matrix(gain_cs) else: gain_cs = np.eye( self.ss.inputs, self.ss.inputs + 2 * self.control_surface.n_control_surfaces) gain_cs[:n_zeta, self.ss.inputs:self.ss.inputs + n_ctrl_sfc] = Kzeta_delta gain_cs[n_zeta:2 * n_zeta, self.ss.inputs + n_ctrl_sfc:self.ss.inputs + 2 * n_ctrl_sfc] = Kdzeta_ddelta self.ss.addGain(gain_cs, where='in') self.gain_cs = gain_cs
def load_uvlm(filename): import sharpy.utils.h5utils as h5 cout.cout_wrap('Loading UVLM state space system projected onto structural DOFs from file') read_data = h5.readh5(filename).ss # uvlm_ss_read = read_data.linear.linear_system.uvlm.ss uvlm_ss_read = read_data return libss.ss(uvlm_ss_read.A, uvlm_ss_read.B, uvlm_ss_read.C, uvlm_ss_read.D, dt=uvlm_ss_read.dt)
def assemble(self): qinf = 0.5 * self.rho * self.u_inf ** 2 e = 0.8 L0 = 318*9.81 * np.cos(2*np.pi / 180) # qinf * self.S * self.CLa * self.alpha0 CL0 = self.CLa * self.alpha0 D0 = qinf * self.S * (0.005 + CL0 ** 2 / e / np.pi / self.AR) D = np.zeros((3, 3)) D[0, 0] = 2 * L0 / self.u_inf D[0, 1] = qinf * self.S / self.u_inf * self.CLa D[0, 2] = qinf * self.St * self.Clat * self.lt / self.u_inf D[1, 0] = 2 * D0 / self.u_inf D[1, 1] = qinf * self.S * 2 * self.S*CL0*self.CLa/np.pi/self.b**2/e/self.u_inf D[2, 1] = qinf * self.S * self.c * (self.lw/self.c*self.CLa - self.St/self.S*self.lt/self.c*self.Clat)/self.u_inf D[2, 2] = qinf * self.S * self.c * self.St * self.lt / self.S / self.c * self.Clat * self.lt / self.u_inf self.ss = libss.ss(np.zeros((3, 3)), np.zeros((3, 3)), np.zeros((3, 3)), D)
def setUp(self): # allocate some state-space model (dense and sparse) dt = 0.3 Ny, Nx, Nu = 4, 3, 2 A = np.random.rand(Nx, Nx) B = np.random.rand(Nx, Nu) C = np.random.rand(Ny, Nx) D = np.random.rand(Ny, Nu) self.SS = libss.ss(A, B, C, D, dt=dt)
def modred(SSb, N, method='residualisation'): """ Produces a reduced order model with N states from balanced or modal system SSb. Both "truncation" and "residualisation" methods are employed. Note: - this method is designed for small size systems, i.e. a deep copy of SSb is produced by default. """ assert method in ['residualisation', 'realisation', 'truncation'], \ "method must be equal to 'residualisation' or 'truncation'!" assert SSb.dt is not None, 'SSb is not a DLTI!' Nb = SSb.A.shape[0] if Nb == N: SSrom = libss.ss(SSb.A, SSb.B, SSb.C, SSb.D, dt=SSb.dt) return SSrom A11 = SSb.A[:N, :N] B11 = SSb.B[:N, :] C11 = SSb.C[:, :N] D = SSb.D if method is 'truncation': SSrom = libss.ss(A11, B11, C11, D, dt=SSb.dt) else: Nb = SSb.A.shape[0] IA22inv = -SSb.A[N:, N:].copy() eevec = range(Nb - N) IA22inv[eevec, eevec] += 1. IA22inv = scalg.inv(IA22inv, overwrite_a=True) SSrom = libss.ss( A11 + np.dot(SSb.A[:N, N:], np.dot(IA22inv, SSb.A[N:, :N])), B11 + np.dot(SSb.A[:N, N:], np.dot(IA22inv, SSb.B[N:, :])), C11 + np.dot(SSb.C[:, N:], np.dot(IA22inv, SSb.A[N:, :N])), D + np.dot(SSb.C[:, N:], np.dot(IA22inv, SSb.B[N:, :])), dt=SSb.dt) return SSrom
def setUp(self): # This particular system is a good test as it requires a few iterations of the Hinf # solver. In addition it is known that its SVD peak occurs between 0.1 and 1.0 rad/s # allowing us to increase the resolution in the graphical method around that vicinity. a = np.load(self.test_dir + '/src/a.npy') b = np.load(self.test_dir + '/src/b.npy') c = np.load(self.test_dir + '/src/c.npy') d = np.load(self.test_dir + '/src/d.npy') self.sys = libss.ss(a, b, c, d, dt=None)
def run(self, ss): A, B, C, D = ss.get_mats() s, T, Tinv, rcmax, romax = librom.balreal_iter(A, B, C, lowrank=self.settings['lowrank'], tolSmith=self.settings['smith_tol'].value, tolSVD=self.settings['tolSVD'].value) Ar = Tinv.dot(A.dot(T)) Br = Tinv.dot(B) Cr = C.dot(T) ssrom = libss.ss(Ar, Br, Cr, D, dt=ss.dt) return ssrom
def run(self, ss): self.ss = ss A, B, C, D = self.ss.get_mats() if self.ss.dt: self.dtsystem = True else: self.dtsystem = False out = self.algorithm.run(ss) if type(out) == libss.ss: self.ssrom = out else: Ar, Br, Cr = out if self.dtsystem: self.ssrom = libss.ss(Ar, Br, Cr, D, dt=self.ss.dt) else: self.ssrom = libss.ss(Ar, Br, Cr, D) return self.ssrom
def setUp(self): cout.cout_wrap.initialise(False, False) A = scio.loadmat(TestKrylov.test_dir + '/src/' + 'A.mat') B = scio.loadmat(TestKrylov.test_dir + '/src/' + 'B.mat') C = scio.loadmat(TestKrylov.test_dir + '/src/' + 'C.mat') A = libsp.csc_matrix(A['A']) B = B['B'] C = C['C'] D = np.zeros((B.shape[1], C.shape[0])) A = A.todense() self.ss = libss.ss(A, B, C, D) self.rom = krylov.Krylov() if not os.path.exists(self.test_dir + '/figs/'): os.makedirs(self.test_dir + '/figs/')
def __call__(self, wv): """ Evaluate interpolated model using weights wv. """ assert self.Projected, ( 'You must project the state-space models over' + ' a common basis before interpolating') Aint = np.zeros_like(self.AA[0]) Bint = np.zeros_like(self.BB[0]) Cint = np.zeros_like(self.CC[0]) Dint = np.zeros_like(self.DD[0]) for ii in range(len(self.AA)): Aint += wv[ii] * self.AA[ii] Bint += wv[ii] * self.BB[ii] Cint += wv[ii] * self.CC[ii] Dint += wv[ii] * self.DD[ii] return libss.ss(Aint, Bint, Cint, Dint, self.SS[0].dt)
def balfreq(SS, DictBalFreq): """ Method for frequency limited balancing. The Observability and controllability Gramians over the frequencies kv are solved in factorised form. Balanced modes are then obtained with a square-root method. Details: * Observability and controllability Gramians are solved in factorised form through explicit integration. The number of integration points determines both the accuracy and the maximum size of the balanced model. * Stability over all (Nb) balanced states is achieved if: a. one of the Gramian is integrated through the full Nyquist range b. the integration points are enough. Input: - DictBalFreq: dictionary specifying integration method with keys: - ``frequency``: defines limit frequencies for balancing. The balanced model will be accurate in the range ``[0,F]``, where ``F`` is the value of this key. Note that ``F`` units must be consistent with the units specified in the ``self.ScalingFacts`` dictionary. - ``method_low``: ``['gauss','trapz']`` specifies whether to use gauss quadrature or trapezoidal rule in the low-frequency range ``[0,F]``. - ``options_low``: options to use for integration in the low-frequencies. These depend on the integration scheme (See below). - ``method_high``: method to use for integration in the range [F,F_N], where F_N is the Nyquist frequency. See 'method_low'. - ``options_high``: options to use for integration in the high-frequencies. - ``check_stability``: if True, the balanced model is truncated to eliminate unstable modes - if any is found. Note that very accurate balanced model can still be obtained, even if high order modes are unstable. Note that this option is overridden if "" - ``get_frequency_response``: if True, the function also returns the frequency response evaluated at the low-frequency range integration points. If True, this option also allows to automatically tune the balanced model. Future options: - Ncpu: for parallel run The following integration schemes are available: - ``trapz``: performs integration over equally spaced points using trapezoidal rule. It accepts options dictionaries with keys: - ``points``: number of integration points to use (including domain boundary) - ``gauss`` performs gauss-lobotto quadrature. The domain can be partitioned in Npart sub-domain in which the gauss-lobotto quadrature of order Ord can be applied. A total number of Npart*Ord points is required. It accepts options dictionaries of the form: - ``partitions``: number of partitions - ``order``: quadrature order. Examples: The following dictionary >>> DictBalFreq={'frequency': 1.2, >>> 'method_low': 'trapz', >>> 'options_low': {'points': 12}, >>> 'method_high': 'gauss', >>> 'options_high': {'partitions': 2, 'order': 8}, >>> 'check_stability': True } balances the state-space model in the frequency range [0, 1.2] using: a. 12 equally-spaced points integration of the Gramians in the low-frequency range [0,1.2] and b. A 2 Gauss-Lobotto 8-th order quadratures of the controllability Gramian in the high-frequency range. A total number of 28 integration points will be required, which will result into a balanced model with number of states >>> min{ 2*28* number_inputs, 2*28* number_outputs } The model is finally truncated so as to retain only the first Ns stable modes. """ ### check input dictionary if 'frequency' not in DictBalFreq: raise NameError('Solution dictionary must include the "frequency" key') if 'method_low' not in DictBalFreq: warnings.warn('Setting default options for low-frequency integration') DictBalFreq['method_low'] = 'trapz' DictBalFreq['options_low'] = {'points': 12} if 'method_high' not in DictBalFreq: warnings.warn('Setting default options for high-frequency integration') DictBalFreq['method_high'] = 'gauss' DictBalFreq['options_high'] = {'partitions': 2, 'order': 8} if 'check_stability' not in DictBalFreq: DictBalFreq['check_stability'] = True if 'output_modes' not in DictBalFreq: DictBalFreq['output_modes'] = True if 'get_frequency_response' not in DictBalFreq: DictBalFreq['get_frequency_response'] = False ### get integration points and weights # Nyquist frequency kn = np.pi / SS.dt Opt = DictBalFreq['options_low'] if DictBalFreq['method_low'] == 'trapz': kv_low, wv_low = get_trapz_weights(0., DictBalFreq['frequency'], Opt['points'], False) elif DictBalFreq['method_low'] == 'gauss': kv_low, wv_low = get_gauss_weights(0., DictBalFreq['frequency'], Opt['partitions'], Opt['order']) else: raise NameError('Invalid value %s for key "method_low"' % DictBalFreq['method_low']) Opt = DictBalFreq['options_high'] if DictBalFreq['method_high'] == 'trapz': if Opt['points'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = get_trapz_weights(DictBalFreq['frequency'], kn, Opt['points'], True) elif DictBalFreq['method_high'] == 'gauss': if Opt['order'] * Opt['partitions'] == 0: warnings.warn('You have chosen no points in high frequency range!') kv_high, wv_high = [], [] else: kv_high, wv_high = get_gauss_weights(DictBalFreq['frequency'], kn, Opt['partitions'], Opt['order']) else: raise NameError('Invalid value %s for key "method_high"' % DictBalFreq['method_high']) ### -------------------------------------------------- loop frequencies ### merge vectors Nk_low = len(kv_low) kvdt = np.concatenate((kv_low, kv_high)) * SS.dt wv = np.concatenate((wv_low, wv_high)) * SS.dt zv = np.cos(kvdt) + 1.j * np.sin(kvdt) Eye = libsp.eye_as(SS.A) Zc = np.zeros((SS.states, 2 * SS.inputs * len(kvdt)), ) Zo = np.zeros((SS.states, 2 * SS.outputs * Nk_low), ) if DictBalFreq['get_frequency_response']: Yfreq = np.empty(( SS.outputs, SS.inputs, Nk_low, ), dtype=np.complex_) kv = kv_low for kk in range(len(kvdt)): zval = zv[kk] Intfact = wv[kk] # integration factor Qctrl = Intfact * libsp.solve(zval * Eye - SS.A, SS.B) kkvec = range(2 * kk * SS.inputs, 2 * (kk + 1) * SS.inputs) Zc[:, kkvec[:SS.inputs]] = Qctrl.real Zc[:, kkvec[SS.inputs:]] = Qctrl.imag ### ----- frequency response if DictBalFreq['get_frequency_response'] and kk < Nk_low: Yfreq[:, :, kk] = (1. / Intfact) * \ libsp.dot(SS.C, Qctrl, type_out=np.ndarray) + SS.D ### ----- observability if kk >= Nk_low: continue Qobs = Intfact * libsp.solve(np.conj(zval) * Eye - SS.A.T, SS.C.T) kkvec = range(2 * kk * SS.outputs, 2 * (kk + 1) * SS.outputs) Zo[:, kkvec[:SS.outputs]] = Intfact * Qobs.real Zo[:, kkvec[SS.outputs:]] = Intfact * Qobs.imag # delete full matrices Kernel = None Qctrl = None Qobs = None # LRSQM (optimised) U, hsv, Vh = scalg.svd(np.dot(Zo.T, Zc), full_matrices=False) sinv = hsv**(-0.5) T = np.dot(Zc, Vh.T * sinv) Ti = np.dot((U * sinv).T, Zo.T) # Zc,Zo=None,None ### build frequency balanced model Ab = libsp.dot(Ti, libsp.dot(SS.A, T)) Bb = libsp.dot(Ti, SS.B) Cb = libsp.dot(SS.C, T) SSb = libss.ss(Ab, Bb, Cb, SS.D, dt=SS.dt) ### Eliminate unstable modes - if any: if DictBalFreq['check_stability']: for nn in range(1, len(hsv) + 1): eigs_trunc = scalg.eigvals(SSb.A[:nn, :nn]) eigs_trunc_max = np.max(np.abs(eigs_trunc)) if eigs_trunc_max > 1. - 1e-16: SSb.truncate(nn - 1) hsv = hsv[:nn - 1] T = T[:, :nn - 1] Ti = Ti[:nn - 1, :] break outs = (SSb, hsv) if DictBalFreq['output_modes']: outs += (T, Ti, Zc, Zo, U, Vh) return outs
cpubal=time.time() kmin= 512#(2*gebm.Nmodes)*(2**6) # speed-up and better accuracy tolSVD=1e-8 print('Balanced realisation started (with A sparsity)...') print('kmin: %s tolSVD: %s'%(kmin,tolSVD)) gv,T,Ti,rc,ro=librom.balreal_iter(Sol.linuvlm.SS.A,Sol.linuvlm.SS.B,Sol.linuvlm.SS.C, lowrank=True,tolSmith=1e-10,tolSVD=tolSVD,kmin=kmin,tolAbs=False,Print=True) cpubal=time.time()-cpubal print('\t\tdone in %.2f sec'%cpubal ) ### define balaned system Ab=libsp.dot(Ti,libsp.dot(Sol.linuvlm.SS.A,T)) Bb=libsp.dot(Ti,Sol.linuvlm.SS.B) Cb=libsp.dot(Sol.linuvlm.SS.C,T) SSb=libss.ss(Ab,Bb,Cb,Sol.linuvlm.SS.D,dt=Sol.linuvlm.SS.dt) Nxbal=Ab.shape[0] ### tune ROM # ROM is tuned under the assumption that the balanced system freq. response # is exact. ds=Sol.linuvlm.SS.dt fs=1./ds fn=fs/2. ks=2.*np.pi*fs kn=2.*np.pi*fn # build freq. range kmin,kmax,Nk=0.001,.5,30 kv=np.linspace(kmin,kmax,Nk)
A = scio.loadmat('A.mat') B = scio.loadmat('B.mat') C = scio.loadmat('C.mat') A = A['A'] B = B['B'] C = C['C'] D = np.zeros((B.shape[1], C.shape[0])) # Convert A to dense As = sc.sparse.csc_matrix(A) Ad = As.todense() A = libsp.csc_matrix(As) # Assemble continuous time system fom_ss = libss.ss(A, B, C, D) fom_sc = sig.lti(Ad, B, C, D) # Compare frequency response wv = np.logspace(-1, 3, 1000) wvsc, mag_fom_sc, ph_fom_sc = fom_sc.bode(wv) Y_fom_ss = fom_ss.freqresp(wv)[0, 0, :] mag_fom_ss = 20 * np.log10(np.abs(Y_fom_ss)) ph_fom_ss = np.angle(Y_fom_ss) * 180 / np.pi print(np.max(np.abs(mag_fom_sc - mag_fom_ss))) # Build rom rom = ROM.KrylovReducedOrderModel() rom.initialise(data=None, ss=fom_ss) algorithm = "dual_rational_arnoldi"
else: b = np.zeros((2 * N, )) b[-1] = 1. c = np.zeros((1, 2 * N)) c[0, N - 1] = 1 d = np.zeros(1) # Plant matrix Minv = np.linalg.inv(m) MinvK = Minv.dot(k) A = np.zeros((2 * N, 2 * N)) A[:N, N:] = np.eye(N) A[N:, :N] = -MinvK A[N:, N:] = -Minv.dot(C) system_CT = libss.ss(A, b, c, d, dt=None) evals_ss = np.linalg.eigvals(system_CT.A) # Discrete time system dt = 1e-2 Adt, Bdt, Cdt, Ddt = lingebm.newmark_ss(Minv, C, k, dt=dt, num_damp=0) system_DT = libss.ss(Adt, Bdt, Cdt, Ddt, dt=dt) # SISO Gains for DT system if system_type == 'SISO': b_dt = np.zeros((N)) b_dt[-1] = 1 system_DT.addGain(b_dt, 'in')
# Create system to transmit a vertical gust across the chord in time A_gust = np.zeros((M + 1, M + 1)) A_gust[1:, :-1] = np.eye(M) B_gust = np.zeros((M + 1, 1)) B_gust[0] = 1 C_gust = np.eye(M + 1) D_gust = np.zeros((C_gust.shape[0], 1)) print(D_gust.shape) if use_sparse: A_gust = libsp.csc_matrix(A_gust) B_gust = libsp.csc_matrix(B_gust) C_gust = libsp.csc_matrix(C_gust) D_gust = libsp.csc_matrix(D_gust) print(D_gust.shape) ss_gust = libss.ss(A_gust, B_gust, C_gust, D_gust, dt=ws.dt) # print(ss_gust_airfoil.A.shape) # print(ss_gust_airfoil.B.shape) # print(ss_gust_airfoil.C.shape) # print(ss_gust_airfoil.D.shape) # B_temp = B_gust.copy() # B_temp.shape = (M+1, 1) # D_temp = D_gust.copy() # D_temp.shape = (M+1, 1) # sc_gust_airfoil = sc.signal.dlti(A_gust, B_temp, C_gust, D_temp, dt=dt) # Gain to get uz at single chordwise position across entire span K_lattice_gust = np.zeros((uvlm.SS.inputs, ss_gust.outputs)) for i in range(M + 1): K_lattice_gust[i * (N + 1):(i + 1) * (N + 1), i] = np.ones((N + 1, ))
def build_system(self, system_inputs, system_time): N = 5 # Number of masses/springs/dampers k_db = np.linspace(1, 10, N) # Stiffness database m_db = np.logspace(2, 0, N) # Mass database C_db = np.ones(N) * 1e-1 # Damping database # Build mass matrix m = np.zeros((N, N)) k = np.zeros((N, N)) C = np.zeros((N, N)) m[0, 0] = m_db[0] k[0, 0:2] = [k_db[0] + k_db[1], -k_db[1]] C[0, 0:2] = [C_db[0] + C_db[1], -C_db[1]] for i in range(1, N - 1): k[i, i - 1:i + 2] = [-k_db[i - 1], k_db[i] + k_db[i + 1], -k_db[i + 1]] C[i, i - 1:i + 2] = [-C_db[i - 1], C_db[i] + C_db[i + 1], -C_db[i + 1]] m[i, i] = m_db[i] m[-1, -1] = m_db[-1] k[-1, -2:] = [-k_db[-1], k_db[-1]] C[-1, -2:] = [-C_db[-1], C_db[-1]] # Input: Forces, Output: Displacements if system_inputs == 'MIMO': b = np.zeros((2 * N, N)) b[N:, :] = np.eye(N) # Output rn c = np.zeros((N, 2 * N)) c[:, :N] = np.eye(N) d = np.zeros((N, N)) else: b = np.zeros((2 * N, )) b[-1] = 1. c = np.zeros((1, 2 * N)) c[0, N - 1] = 1 d = np.zeros(1) # Plant matrix Minv = np.linalg.inv(m) MinvK = Minv.dot(k) A = np.zeros((2 * N, 2 * N)) A[:N, N:] = np.eye(N) A[N:, :N] = -MinvK A[N:, N:] = -Minv.dot(C) # Build State Space if system_time == 'ct': system = libss.ss(A, b, c, d, dt=None) else: # Discrete time system dt = 1e-2 Adt, Bdt, Cdt, Ddt = lingebm.newmark_ss(Minv, C, k, dt=dt, num_damp=0) system = libss.ss(Adt, Bdt, Cdt, Ddt, dt=dt) # SISO Gains for DT system if system_inputs == 'SISO': b_dt = np.zeros((N)) b_dt[-1] = 1 system.addGain(b_dt, 'in') system.addGain(c, where='out') return system
def run(self, ss): """ Performs Model Order Reduction employing Krylov space projection methods. Supported methods include: ========================= ==================== ========================================================== Algorithm Interpolation Points Systems ========================= ==================== ========================================================== ``one_sided_arnoldi`` 1 SISO Systems ``two_sided_arnoldi`` 1 SISO Systems ``dual_rational_arnoldi`` K SISO systems and Tangential interpolation for MIMO systems ``mimo_rational_arnoldi`` K MIMO systems. Uses vector-wise construction (more robust) ``mimo_block_arnoldi`` K MIMO systems. Uses block Arnoldi methods (more efficient) ========================= ==================== ========================================================== Args: ss (sharpy.linear.src.libss.ss): State space to reduce Returns: (libss.ss): Reduced state space system """ self.ss = ss if self.settings['print_info']: cout.cout_wrap('Model Order Reduction in progress...') self.print_header() if self.ss.dt is None: self.sstype = 'ct' else: self.sstype = 'dt' self.frequency = np.exp(self.frequency * ss.dt) t0 = time.time() Ar, Br, Cr = self.__getattribute__(self.algorithm)(self.frequency, self.r) self.ssrom = libss.ss(Ar, Br, Cr, self.ss.D, self.ss.dt) self.stable = self.check_stability( restart_arnoldi=self.restart_arnoldi) if not self.stable: pass warn.warn('Reduced Order Model Unstable') # Under development # TL, TR = self.restart() # Wtr, Vr = self.restart() # TL, TR = self.stable_realisation() # self.ssrom = libss.ss(TL.T.dot(Ar.dot(TR)), TL.T.dot(Br), Cr.dot(TR), self.ss.D, self.ss.dt) # self.ssrom = libss.ss(Wtr.dot(self.ssrom.A.dot(Vr)), Wtr.dot(self.ssrom.B), self.ssrom.C.dot(Vr), self.ss.D, self.ss.dt) # self.stable = self.check_stability(restart_arnoldi=self.restart_arnoldi) t_rom = time.time() - t0 self.cpu_summary['run'] = t_rom if self.settings['print_info']: cout.cout_wrap('System reduced from order %d to ' % self.ss.states) cout.cout_wrap('\tn = %d states' % self.ssrom.states, 1) cout.cout_wrap('...Completed Model Order Reduction in %.2f s' % t_rom) return self.ssrom
def FLB_transfer_function(SS_list, wv, U_list, VT_list, hsv_list=None, M_list=None): r""" Returns an interpolatory state-space model based on the transfer function method [1]. This method is applicable to frequency limited balanced state-space models only. Features: - stability preserved - the interpolated state-space model has the same size than the tabulated ones - all state-space models, need to have the same size and the same numbers of hankel singular values. - suitable for any ROM Args: SS_list (list): List of state-space models instances of :class:`sharpy.linear.src.libss.ss` class. wv (list): list of interpolatory weights. U_list (list): small size, thin SVD factors of Gramians square roots of each state space model (:math:`\mathbf{U}`). VT_list (list): small size, thin SVD factors of Gramians square roots of each state space model (:math:`\mathbf{V}^\top`). hsv_list (list): small size, thin SVD factors of Gramians square roots of each state space model. If ``None``, it is assumed that ``U_list = [ U_i sqrt(hsv_i) ]`` ``VT_list = [ sqrt(hsv_i) V_i.T ]`` where ``U_i`` and ``V_i.T`` are square matrices and hsv is an array. M_list (list): for fast on-line evaluation. Small size product of Gramians factors of each state-space model. Each element of this list is equal to: ``M_i = U_i hsv_i V_i.T`` Notes: Message for future generations: - the implementation is divided into an offline and online part. References: Maraniello S. and Palacios R., Frequency-limited balanced truncation for parametric reduced-order modelling of the UVLM. Only in the best theaters. See Also: Frequency-Limited Balanced ROMs may be obtained from SHARPy using :class:`sharpy.rom.balanced.FrequencyLimited`. """ # ----------------------------------------------------------------- offline ### checks sizes N_interp = len(SS_list) states = SS_list[0].states inputs = SS_list[0].inputs outputs = SS_list[0].outputs for ss_here in SS_list: assert ss_here.states == states, \ 'State-space models must have the same number of states!' assert ss_here.inputs == inputs, \ 'State-space models must have the same number of states!' assert ss_here.outputs == outputs, \ 'State-space models must have the same number of states!' ### case of unbalanced state-space models # in this case, U_list and VT_list contain the full-rank Gramians factors # of each ROM if U_list is None and VT_list is None: raise NameError('apply FLB before calling this routine') # hsv_list = None # M_list, U_list, VT_list = [], [], [] # for ii in range(N_interp): # # # avoid direct # # hsv,U,Vh,Zc,Zo = librom.balreal_direct_py( # # SS_list[ii].A, SS_list[ii].B, SS_list[ii].C, # # DLTI=True,full_outputs=True) # # iterative also fails # hsv,Zc,Zo = librom.balreal_iter(SS_list[ii].A, SS_list[ii].B, SS_list[ii].C, # lowrank=True,tolSmith=1e-10,tolSVD=1e-10, # kmin=None, tolAbs=False, Print=True, outFacts=True) # # M_list.append( np.dot( np.dot(U,np.diag(hsv)), Vh) ) # M_list.append( np.dot( Zo.T,Zc ) ) # U_list.append(Zo.T) # VT_list.append(Zc) # calculate small size product of Gramians factors elif M_list is None: if hsv_list is None: M_list = [np.dot(U, VT) for U, VT in zip(U_list, VT_list)] else: M_list = [ np.dot(U * hsv, VT) for U, hsv, VT in zip(U_list, hsv_list, VT_list) ] # ------------------------------------------------------------------ online ### balance interpolated model M_int = np.zeros_like(M_list[0]) for ii in range(N_interp): M_int += wv[ii] * M_list[ii] U_int, hsv_int, Vh_int = scalg.svd(M_int, full_matrices=False) sinv_int = hsv_int**(-0.5) ### build projection matrices sinvUT_int = (U_int * sinv_int).T Vsinv_int = Vh_int.T * sinv_int if hsv_list is None: Ti_int_list = [np.dot(sinvUT_int, U) for U in U_list] T_int_list = [np.dot(VT, Vsinv_int) for VT in VT_list] else: Ti_int_list = [np.dot(sinvUT_int, U * np.sqrt(hsv)) \ for U, hsv in zip(U_list, hsv_list)] T_int_list = [np.dot(np.dot(np.diag(np.sqrt(hsv)), VT), Vsinv_int) \ for hsv, VT in zip(hsv_list, VT_list)] ### assemble interp state-space model A_int = np.zeros((states, states)) B_int = np.zeros((states, inputs)) C_int = np.zeros((outputs, states)) D_int = np.zeros((outputs, inputs)) for ii in range(N_interp): # in A and B the weigths come from Ti A_int += wv[ii] * np.dot(Ti_int_list[ii], np.dot(SS_list[ii].A, T_int_list[ii])) B_int += wv[ii] * np.dot(Ti_int_list[ii], SS_list[ii].B) # in C and D the weights come from the interp system expression C_int += wv[ii] * np.dot(SS_list[ii].C, T_int_list[ii]) D_int += wv[ii] * SS_list[ii].D return libss.ss(A_int, B_int, C_int, D_int, dt=SS_list[0].dt), hsv_int