def test_properties(self): # Test setters/getters for cross class properties. # This implicitly tests to_tf() and to_zpk() # Getters s = StateSpace(1, 1, 1, 1, dt=0.05) assert_equal(s.poles, [1]) assert_equal(s.zeros, [0]) with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) assert_equal(s.gain, 1) assert_equal(s.num, [1, 0]) assert_equal(s.den, [1, -1]) # transfer function setters s2 = StateSpace(2, 2, 2, 2, dt=0.05) with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) s2.num = [1, 0] s2.den = [1, -1] self._compare_systems(s, s2) # zpk setters s2 = StateSpace(2, 2, 2, 2, dt=0.05) with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) s2.poles = 1 s2.zeros = 0 s2.gain = 1 self._compare_systems(s, s2)
def test_initialization(self): # Check that all initializations work dt = 0.05 s = StateSpace(1, 1, 1, 1, dt=dt) s = StateSpace([1], [2], [3], [4], dt=dt) s = StateSpace(np.array([[1, 2], [3, 4]]), np.array([[1], [2]]), np.array([[1, 0]]), np.array([[0]]), dt=dt) s = StateSpace(1, 1, 1, 1, dt=True)
def test_conversion(self): # Check the conversion functions s = StateSpace(1, 2, 3, 4) assert_(isinstance(s.to_ss(), StateSpace)) assert_(isinstance(s.to_tf(), TransferFunction)) assert_(isinstance(s.to_zpk(), ZerosPolesGain)) # Make sure copies work assert_(StateSpace(s) is not s) assert_(s.to_ss() is not s)
def tfSim(inputType, mag, wn, zeta, init_xdot, init_x, step_increment, endtime): num = [0, 0, wn**2] den = [1, 2 * zeta * wn, wn**2] timesteps = np.arange(0, endtime, step_increment) if inputType == "sine": u = mag * np.sin(2 * np.pi * timesteps) elif inputType == "step": u = mag * np.ones(timesteps.size) else: raise ValueException("unrecognized input type") [A, B, C, D] = tf2ss(num, den) sys = StateSpace(A, B, C, D) print("C: {} ; shape: {}".format(C, C.shape)) init_xdot = init_xdot / C[0][1] init_x = init_x / C[0][1] init_cond = np.array([init_xdot, init_x]) print(init_cond) print("initial conditions shape: {}".format(init_cond.shape)) y = lsim(sys, u, timesteps, init_cond) return y
def test_properties(self): # Test setters/getters for cross class properties. # This implicitly tests to_tf() and to_zpk() # Getters s = StateSpace(1, 1, 1, 1, dt=0.05) assert_equal(s.poles, [1]) assert_equal(s.zeros, [0])
def test_conversion(self): # Check the conversion functions s = StateSpace(1, 2, 3, 4, dt=0.05) assert_(isinstance(s.to_ss(), StateSpace)) assert_(isinstance(s.to_tf(), TransferFunction)) assert_(isinstance(s.to_zpk(), ZerosPolesGain)) # Make sure copies work assert_(StateSpace(s) is not s) assert_(s.to_ss() is not s)
def get_model(): # Build model: R3C3 # ================= # Constants Vi = 400. # Indoor volume [m3] # Parameters Ri = 0.25 / (3. * Vi ** (2./3.)) # Int. wall resistance [K/W] Re1 = 1.00 / (3. * Vi ** (2./3.)) # Ext. wall resistance [K/W] Re2 = 0.25 / (3. * Vi ** (2./3.)) # Ext. wall resistance [K/W] Ci = 30.0 * Vi * 1.2 * 1005. # Int. wall capacitance [J/K] Ce = 20.0 * Vi * 1.2 * 1005. # Ext. wall capacitance [J/K] Cr = 10.0 * Vi * 1.2 * 1005. # Zone capacitance [J/K] fsol = 1.8 # Solar coeff. qmet = 100. # Metabolic heat gain [W/person] # States, inputs and outputs: # n (states) : [Tr, Ti, Te] # p (inputs) : [Tout, Hglo, qhvac, nocc] # q (outputs) : [Tr, Ti, Te, qhvac] # State matrix (n x n) A = np.array([ [-1/Ri/Cr - 1/Re1/Cr, 1/Ri/Cr, 1/Re1/Cr], [1/Ri/Ci, -1/Ri/Ci, 0], [1/Re1/Ce, 0, -1/Re1/Ce - 1/Re2/Ce] ]) # [Tr, Ti, Te]^T # Input matrix (n x p) B = np.array([ [0., fsol/Cr, 1/Cr, qmet/Cr], [0., 0., 0., 0.], [1/Re2/Ce, 0., 0., 0.] ]) # Output matrix (q x n) C = np.array([ [1., 0., 0.], # Tr [0., 1., 0.], # Ti [0., 0., 1.], # Te [0., 0., 0.] # qhvac ]) # Feedthrough matrix (q x p) D = np.array([ [0., 0., 0., 0.], # Tr [0., 0., 0., 0.], # Ti [0., 0., 0., 0.], # Te [0., 0., 1., 0.] # qhvac ]) return StateSpace(A, B, C, D)
def maxent(P,A,B,omega=None): assert P.ndim==2 and P.shape[0] == P.shape[1], 'P must be a square matrix' if omega is None: omega=linspace(0,2*pi,200) N = omega.size iP = inv(P) e = inv(B.T @ iP @ B) C1 = e @ B.T @ iP n,m = B.shape # Phi=C1*G(z); max entropy spectrum = Phi^{-1} e Phi^{-1}^* rA = A.real iA = A.imag AA = array([[rA, -iA],[iA, rA]]) rB = B.real iB = B.imag BB = array([[rB, -iB],[iB, rB]]) rC = C1.real iC = C1.imag CC1= array([[rC, -iC],[iC, rC]]) Phi_inv = StateSpace(AA-BB*CC1*AA, BB, -CC1*AA, eye(2*m,2*m)); HH = freqresp(Phi_inv,omega) H = HH[:m,:m,:] + 1j*HH[m:2*m,:m,:] if m==1: h = H.squeeze() spectrum = diag(h @ e @ h.T).real.T else: print('The spectrum is matricial m*m, where m=',m) spectrum = zeros((m,N*m)) halfspectrum = zeros((m,N*m)) [Ue,svdOmega] = svd(e) sqrtsvdOmega = sqrt(svdOmega) for i in range(N): temp=H[:,:,i] #H(:,:,i)=temp*e*temp.T spectrum[:,(i-1)*m:i*m] = temp @ e @ temp.T halfspectrum[:,(i-1)*m:i*m] = temp @ Ue @ sqrtsvdOmega return spectrum, halfspectrum, omega
# In[14]: Cp = C print(Cp) # In[15]: Ep = array([E, dot((C1 - C2), X) + dot((E1 - E2), U)]).transpose() print(Ep) # Com as matrizes ``Ap``, ``Bp``, ``Cp``, ``Ep`` calculadas o modelo de espaço de estado é montado (a partir daqui o notebook assume o que o ``buckboost.mdl`` no Simulink faz) # In[16]: ss_buckboost = StateSpace(Ap, Bp, Cp, Ep) # Um sinal de tempo (``t_in``) e os steps de entrada (``u`` e ``d``) são gerados utilizando a função [step](#step_function) criada. # In[17]: t_in = arange(0, t_final, t_step) u = step(t_in, .03, 0, v_f, '$\hat{u}$') d = step(t_in, .03, 0, d_f, '$\hat{d}$') up = array([u, d]).transpose() # Nesse momento, com as entradas definidas e o modelo pronto podemos evolui-lo no espaço de estados utilizando a função ``lsim``: # In[18]:
C = np.matrix([ [1, 0, 0, 0], [0, 1, 0, 0], ]) #C = np.concatenate((C,np.zeros((4,4))),axis=1) D = np.zeros((2, 2)) top = np.concatenate((A, np.zeros((4, 2))), axis=1) bottom = np.concatenate((-C, np.zeros((2, 2))), axis=1) Ahat = np.concatenate((top, bottom), axis=0) Bhat = np.concatenate((B, np.zeros((2, 2))), axis=0) Q = np.diag([1, 1, .0000001, .0000001, 1, 1]) * np.eye(6) R = np.eye(2) sys = StateSpace(Ahat, Bhat, np.concatenate((C, np.zeros((2, 2))), axis=1), D) #sys_d = sys.to_discrete(1) #Ahat, Bhat = sys_d.A, sys_d.B K, S, E = lqr(Ahat, Bhat, Q, R) """ Control """ x = np.matrix([[1], [-1], [2], [3], [0], [0]]) # initial state ref = np.matrix([[0], [0]]) cont = 0 record_x = [] record_u = [] record_SP = [] y = [[0], [0]] e_dot = [[0], [0], [0], [0]] xsi = 0
# This is the code for Exercise 5. import numpy as np from scipy.signal import StateSpace, dlsim A = np.asarray([[0.,1.], [1.,1.]]) B = np.asarray([[0.],[0.]]) C = np.asarray([0.,0.]) D = np.asarray([0.]) plane_sys = StateSpace(A, B, C, D, dt = 1) t = np.arange(0, 20, 1) input = np.zeros(len(t)) _, y, x = dlsim(plane_sys, input, t, x0=[0, 1]) F20 = int(x[19, 1]) print("F20 is:", F20)
def test_initialization(self): # Check that all initializations work s = StateSpace(1, 1, 1, 1) s = StateSpace([1], [2], [3], [4]) s = StateSpace(np.array([[1, 2], [3, 4]]), np.array([[1], [2]]), np.array([[1, 0]]), np.array([[0]]))
def test_operators(self): # Test +/-/* operators on systems class BadType(object): pass s1 = StateSpace(np.array([[-0.5, 0.7], [0.3, -0.8]]), np.array([[1], [0]]), np.array([[1, 0]]), np.array([[0]]), ) s2 = StateSpace(np.array([[-0.2, -0.1], [0.4, -0.1]]), np.array([[1], [0]]), np.array([[1, 0]]), np.array([[0]]) ) s_discrete = s1.to_discrete(0.1) s2_discrete = s2.to_discrete(0.2) # Impulse response t = np.linspace(0, 1, 100) u = np.zeros_like(t) u[0] = 1 # Test multiplication for typ in six.integer_types + (float, complex, np.float32, np.complex128, np.array): assert_allclose(lsim(typ(2) * s1, U=u, T=t)[1], typ(2) * lsim(s1, U=u, T=t)[1]) assert_allclose(lsim(s1 * typ(2), U=u, T=t)[1], lsim(s1, U=u, T=t)[1] * typ(2)) assert_allclose(lsim(s1 / typ(2), U=u, T=t)[1], lsim(s1, U=u, T=t)[1] / typ(2)) with assert_raises(TypeError): typ(2) / s1 assert_allclose(lsim(s1 * 2, U=u, T=t)[1], lsim(s1, U=2 * u, T=t)[1]) assert_allclose(lsim(s1 * s2, U=u, T=t)[1], lsim(s1, U=lsim(s2, U=u, T=t)[1], T=t)[1], atol=1e-5) with assert_raises(TypeError): s1 / s1 with assert_raises(TypeError): s1 * s_discrete with assert_raises(TypeError): # Check different discretization constants s_discrete * s2_discrete with assert_raises(TypeError): s1 * BadType() with assert_raises(TypeError): BadType() * s1 with assert_raises(TypeError): s1 / BadType() with assert_raises(TypeError): BadType() / s1 # Test addition assert_allclose(lsim(s1 + 2, U=u, T=t)[1], 2 * u + lsim(s1, U=u, T=t)[1]) # Check for dimension mismatch with assert_raises(ValueError): s1 + np.array([1, 2]) with assert_raises(ValueError): np.array([1, 2]) + s1 with assert_raises(TypeError): s1 + s_discrete with assert_raises(ValueError): s1 / np.array([[1, 2], [3, 4]]) with assert_raises(TypeError): # Check different discretization constants s_discrete + s2_discrete with assert_raises(TypeError): s1 + BadType() with assert_raises(TypeError): BadType() + s1 assert_allclose(lsim(s1 + s2, U=u, T=t)[1], lsim(s1, U=u, T=t)[1] + lsim(s2, U=u, T=t)[1]) # Test substraction assert_allclose(lsim(s1 - 2, U=u, T=t)[1], -2 * u + lsim(s1, U=u, T=t)[1]) assert_allclose(lsim(2 - s1, U=u, T=t)[1], 2 * u + lsim(-s1, U=u, T=t)[1]) assert_allclose(lsim(s1 - s2, U=u, T=t)[1], lsim(s1, U=u, T=t)[1] - lsim(s2, U=u, T=t)[1]) with assert_raises(TypeError): s1 - BadType() with assert_raises(TypeError): BadType() - s1
def test_operators(self): # Test +/-/* operators on systems class BadType(object): pass s1 = StateSpace( np.array([[-0.5, 0.7], [0.3, -0.8]]), np.array([[1], [0]]), np.array([[1, 0]]), np.array([[0]]), ) s2 = StateSpace(np.array([[-0.2, -0.1], [0.4, -0.1]]), np.array([[1], [0]]), np.array([[1, 0]]), np.array([[0]])) s_discrete = s1.to_discrete(0.1) s2_discrete = s2.to_discrete(0.2) s3_discrete = s2.to_discrete(0.1) # Impulse response t = np.linspace(0, 1, 100) u = np.zeros_like(t) u[0] = 1 # Test multiplication for typ in (int, float, complex, np.float32, np.complex128, np.array): assert_allclose( lsim(typ(2) * s1, U=u, T=t)[1], typ(2) * lsim(s1, U=u, T=t)[1]) assert_allclose( lsim(s1 * typ(2), U=u, T=t)[1], lsim(s1, U=u, T=t)[1] * typ(2)) assert_allclose( lsim(s1 / typ(2), U=u, T=t)[1], lsim(s1, U=u, T=t)[1] / typ(2)) with assert_raises(TypeError): typ(2) / s1 assert_allclose(lsim(s1 * 2, U=u, T=t)[1], lsim(s1, U=2 * u, T=t)[1]) assert_allclose(lsim(s1 * s2, U=u, T=t)[1], lsim(s1, U=lsim(s2, U=u, T=t)[1], T=t)[1], atol=1e-5) with assert_raises(TypeError): s1 / s1 with assert_raises(TypeError): s1 * s_discrete with assert_raises(TypeError): # Check different discretization constants s_discrete * s2_discrete with assert_raises(TypeError): s1 * BadType() with assert_raises(TypeError): BadType() * s1 with assert_raises(TypeError): s1 / BadType() with assert_raises(TypeError): BadType() / s1 # Test addition assert_allclose( lsim(s1 + 2, U=u, T=t)[1], 2 * u + lsim(s1, U=u, T=t)[1]) # Check for dimension mismatch with assert_raises(ValueError): s1 + np.array([1, 2]) with assert_raises(ValueError): np.array([1, 2]) + s1 with assert_raises(TypeError): s1 + s_discrete with assert_raises(ValueError): s1 / np.array([[1, 2], [3, 4]]) with assert_raises(TypeError): # Check different discretization constants s_discrete + s2_discrete with assert_raises(TypeError): s1 + BadType() with assert_raises(TypeError): BadType() + s1 assert_allclose( lsim(s1 + s2, U=u, T=t)[1], lsim(s1, U=u, T=t)[1] + lsim(s2, U=u, T=t)[1]) # Test subtraction assert_allclose( lsim(s1 - 2, U=u, T=t)[1], -2 * u + lsim(s1, U=u, T=t)[1]) assert_allclose( lsim(2 - s1, U=u, T=t)[1], 2 * u + lsim(-s1, U=u, T=t)[1]) assert_allclose( lsim(s1 - s2, U=u, T=t)[1], lsim(s1, U=u, T=t)[1] - lsim(s2, U=u, T=t)[1]) with assert_raises(TypeError): s1 - BadType() with assert_raises(TypeError): BadType() - s1 s = s_discrete + s3_discrete assert_(s.dt == 0.1) s = s_discrete * s3_discrete assert_(s.dt == 0.1) s = 3 * s_discrete assert_(s.dt == 0.1) s = -s_discrete assert_(s.dt == 0.1)
import numpy as np from scipy.signal import StateSpace, lsim, dlsim from scipy.linalg import expm import matplotlib.pyplot as plt # Build the CT system. A = np.asarray([[0., 1.], [-2., -2.]]) B = np.asarray([[1.],[1.]]) C = np.asarray([2.,3.]) D = np.asarray([0.]) t_CT = np.arange(0, 5.01, 0.01) input_CT = np.ones(len(t_CT)) sys_CT = StateSpace(A, B, C, D) _, y_CT, x_CT = lsim(sys_CT, input_CT, t_CT, X0=[0., 0.]) y5_CT = y_CT[-1] print("In the CT system, y(5) is:", y5_CT) # Calculate the DT system. G = expm(A); H = np.asarray([[0.,0.],[0.,0.]]) step = np.arange(0,1.001,0.001) for i in step: H += expm(A*i) * 0.001 H = np.dot(H, B) # Build the DT system. sys_DT = StateSpace(G, H, C, D, dt = 1) print("The discretized state space representation of this system is:") print(sys_DT) t_DT = np.arange(0, 6, 1);
def __init__(self, Y0, Y1, U0, q=10, u_nominal=1, dt=1): if Y0.shape[1] != U0.shape[1]: raise ValueError( "The number of snapshot pairs Y0, U0 should be equal to the number of training inputs." ) if Y1.shape[1] != U0.shape[1]: raise ValueError( "The number of snapshot pairs Y1, U0 should be equal to the number of training inputs." ) # Parameters self.ny = Y0.shape[0] # Number of outputs self.nu = U0.shape[0] # Number of inputs self.q = q # Number of POD modes kept self.p = Y0.shape[1] # Number of training snapshots available self.dt = dt # Time step # Save snapshots self.Y0 = Y0 self.Y1 = Y1 self.U0 = U0 # Compute POD Modes self.Uq = self.compute_POD_basis(Y0, self.q) # Project snapshots H0 = self.Uq.conj().T @ Y0 H1 = self.Uq.conj().T @ Y1 # POD projection error pod_error = np.linalg.norm((Y0 - self.Uq @ H0), ord='fro')/ \ np.linalg.norm(Y0, ord='fro')*100 print(' POD projection error = ', pod_error, '%') # Run DMDc self.F, self.G = self.DMDc(H0, H1, U0) # Eigendecomposition of A lamb, self.W = np.linalg.eig(self.F) self.Lambda = np.diag(lamb) self.Gamma = np.linalg.inv(self.W) @ self.G # DMD modes self.Phi = self.Uq @ self.W # Setup matrices for sparsity-promoting optimization #self.R = np.zeros((self.q, self.p), dtype=np.complex) R = self.Lambda @ ( np.linalg.pinv(self.Phi) @ self.Y0) + self.Gamma @ U0 L = self.Phi #Y1 = self.Y[:,1:] # Now cast into quadratic form self.P = np.multiply(L.conj().T @ L, (R @ R.conj().T).conj()) self.d = np.diag(R @ Y1.conj().T @ L).conj() self.s = np.trace(Y1.conj().T @ Y1) print('s.shape = ', self.s.shape) self.sys_pod = StateSpace(self.F, self.G, self.Uq) self.sys_dmd = StateSpace(self.Lambda, self.Gamma, self.Phi) print('DMD model error:') self.sys_dmd.error(Y0, Y1, U0)
class DMDcsp(object): """ Sparsity-Promoting Dynamic Mode Decomposition with Control Class Initialization: Simply provide data matrices Y0, Y1, U0 """ def __init__(self, Y0, Y1, U0, q=10, u_nominal=1, dt=1): if Y0.shape[1] != U0.shape[1]: raise ValueError( "The number of snapshot pairs Y0, U0 should be equal to the number of training inputs." ) if Y1.shape[1] != U0.shape[1]: raise ValueError( "The number of snapshot pairs Y1, U0 should be equal to the number of training inputs." ) # Parameters self.ny = Y0.shape[0] # Number of outputs self.nu = U0.shape[0] # Number of inputs self.q = q # Number of POD modes kept self.p = Y0.shape[1] # Number of training snapshots available self.dt = dt # Time step # Save snapshots self.Y0 = Y0 self.Y1 = Y1 self.U0 = U0 # Compute POD Modes self.Uq = self.compute_POD_basis(Y0, self.q) # Project snapshots H0 = self.Uq.conj().T @ Y0 H1 = self.Uq.conj().T @ Y1 # POD projection error pod_error = np.linalg.norm((Y0 - self.Uq @ H0), ord='fro')/ \ np.linalg.norm(Y0, ord='fro')*100 print(' POD projection error = ', pod_error, '%') # Run DMDc self.F, self.G = self.DMDc(H0, H1, U0) # Eigendecomposition of A lamb, self.W = np.linalg.eig(self.F) self.Lambda = np.diag(lamb) self.Gamma = np.linalg.inv(self.W) @ self.G # DMD modes self.Phi = self.Uq @ self.W # Setup matrices for sparsity-promoting optimization #self.R = np.zeros((self.q, self.p), dtype=np.complex) R = self.Lambda @ ( np.linalg.pinv(self.Phi) @ self.Y0) + self.Gamma @ U0 L = self.Phi #Y1 = self.Y[:,1:] # Now cast into quadratic form self.P = np.multiply(L.conj().T @ L, (R @ R.conj().T).conj()) self.d = np.diag(R @ Y1.conj().T @ L).conj() self.s = np.trace(Y1.conj().T @ Y1) print('s.shape = ', self.s.shape) self.sys_pod = StateSpace(self.F, self.G, self.Uq) self.sys_dmd = StateSpace(self.Lambda, self.Gamma, self.Phi) print('DMD model error:') self.sys_dmd.error(Y0, Y1, U0) def DMDc(self, H0, H1, U0): """ Inputs: H0 : first snapshot matrix H1 : second snapshot matrix (after one time step) U0 : corresponding input Outputs: A, B : state and output matrices fitted to the reduced-order data """ q = H0.shape[0] nu = U0.shape[0] p = U0.shape[1] U, Sig, VT = np.linalg.svd(np.vstack((H0, U0)), full_matrices=False) thres = 1.0e-10 rtil = np.min((np.sum(np.diag(Sig) > thres), q)) print(' rtil = ', rtil) Util = U[:, :rtil] Sigtil = np.diag(Sig)[:rtil, :rtil] Vtil = VT.T[:, :rtil] U_F = Util[:q, :] U_G = Util[q:q + nu, :] F = H1 @ Vtil @ np.linalg.inv(Sigtil) @ U_F.conj().T G = H1 @ Vtil @ np.linalg.inv(Sigtil) @ U_G.conj().T dmdc_error = np.linalg.norm((H1 - (F @ H0 + G @ U0)), ord='fro')/ \ np.linalg.norm(H1, ord='fro')*100 print(' DMDc error = ', dmdc_error, '%') print(' Maximum eigenvalue: ', np.max(np.abs(np.linalg.eig(F)[0]))) return F, G def sparse(self, gamma, niter): zero_thres = 1.e-6 print("gamma = %e | number of modes = " % (gamma)) # Define and solve the sparsity-promoting optimization problem # Weighted L1 norm is updated iteratively alpha = cp.Variable(self.q) weights = np.ones(self.q) for i in range(niter): objective_sparse = cp.Minimize( cp.quad_form(alpha, self.P) - 2. * cp.real(self.d.conj().T @ alpha) + self.s + gamma * cp.pnorm(np.diag(weights) @ alpha, p=1)) prob_sparse = cp.Problem(objective_sparse) sol_sparse = prob_sparse.solve(verbose=False, solver=cp.SCS) alpha_sp = alpha.value # Sparse solution if alpha_sp is None: alpha_sp = np.ones(self.q) # Update weights weights = 1.0 / (np.abs(alpha_sp) + np.finfo(float).eps) nonzero = np.abs(alpha_sp) > zero_thres # Nonzero modes print(" %d of %d" % (np.sum(nonzero), self.q)) J_sp = np.real(alpha_sp.conj().T @ self.P @ alpha_sp - 2 * np.real(self.d.conj().T @ alpha_sp) + self.s) # Square error nx = np.sum( nonzero ) # Number of nonzero modes - order of the sparse/reduced model Ez = np.eye(self.q)[:, ~nonzero] # Define and solve the refinement optimization problem #alpha = cp.Variable(self.q) objective_refine = cp.Minimize( cp.quad_form(alpha, self.P) - 2. * cp.real(self.d.conj().T @ alpha) + self.s) if np.sum(~nonzero): constraint_refine = [Ez.T @ alpha == 0] prob_refine = cp.Problem(objective_refine, constraint_refine) else: prob_refine = cp.Problem(objective_refine) sol_refine = prob_refine.solve() alpha_ref = alpha.value J_ref = np.real(alpha_ref.conj().T @ self.P @ alpha_ref - 2 * np.real(self.d.conj().T @ alpha_ref) + self.s) # Square error P_loss = 100 * np.sqrt(J_ref / self.s) # Truncate modes E = np.eye(self.q)[:, nonzero] Lambda_bar = E.T @ self.Lambda @ E Beta_bar = E.T @ self.Gamma Phi_bar = self.Phi @ np.diag(alpha_ref) @ E # Save data stats = {} stats["nx"] = nx stats["alpha_sp"] = alpha_sp stats["alpha_ref"] = alpha_ref stats["z_0"] = (np.linalg.pinv(self.Phi) @ self.Y0[:, 0])[nonzero] stats["E"] = E stats["J_sp"] = J_sp stats["J_ref"] = J_ref stats["P_loss"] = P_loss if nx != 0: print("Rank of controllability matrix: %d of %d" % (np.linalg.matrix_rank(control.ctrb(Lambda_bar, Beta_bar)), nx)) # Convert system from complex modal form to real block-modal form lambda_bar = np.diag(Lambda_bar) A = np.zeros((nx, nx)) B = np.zeros((nx, self.nu)) Theta = np.zeros((self.ny, nx)) i = 0 while i < nx: if np.isreal(lambda_bar[i]): A[i, i] = lambda_bar[i] B[i] = Beta_bar[i] Theta[:, i] = Phi_bar[:, i] i += 1 elif i == nx - 1: # In case only one of the conjugate pairs is truncated - this rarely happens A[i, i] = np.real(lambda_bar[i]) B[i] = np.real(Beta_bar[i]) Theta[:, i] = np.real(Phi_bar[:, i]) i += 1 elif np.isreal(np.isreal(lambda_bar[i] + lambda_bar[i + 1])): A[i:i + 2, i:i + 2] = np.array( [[np.real(lambda_bar[i]), np.imag(lambda_bar[i])], [-np.imag(lambda_bar[i]), np.real(lambda_bar[i])]]) B[i] = np.real(Beta_bar[i]) B[i + 1] = -np.imag(Beta_bar[i]) Theta[:, i] = 2 * np.real(Phi_bar[:, i]) Theta[:, i + 1] = 2 * np.imag(Phi_bar[:, i]) i += 2 else: raise ValueError( "Eigenvalues are not grouped in conjugate pairs") #return StateSpace(Lambda_bar, Beta_bar, Phi_bar), lambda_bar, stats return StateSpace(A, B, Theta), lambda_bar, stats def sparse_batch(self, gamma, niter): num = gamma.shape[0] self.rsys = num * [None] self.sys_eig = num * [None] stats = {} stats["alpha_sp"] = num * [None] stats["alpha_ref"] = num * [None] stats["z_0"] = num * [None] stats["E"] = num * [None] stats['nx'] = np.zeros(num) stats["J_sp"] = np.zeros(num) stats["J_ref"] = np.zeros(num) stats["P_loss"] = np.zeros(num) for i in range(num): print('Model # %d' % i) self.rsys[i], self.sys_eig[i], stats_tmp = self.sparse( gamma[i], niter) # Save stats stats["alpha_sp"][i] = stats_tmp["alpha_sp"] stats["alpha_ref"][i] = stats_tmp["alpha_ref"] stats["z_0"][i] = stats_tmp["z_0"] stats["E"][i] = stats_tmp["E"] stats['nx'][i] = stats_tmp['nx'] stats["J_sp"][i] = stats_tmp["J_sp"] stats["J_ref"][i] = stats_tmp["J_ref"] stats["P_loss"][i] = stats_tmp["P_loss"] self.sp_stats = stats return stats def compute_noise_cov(self, sys_i, sens): # Measurement output matrix C = self.rsys[sys_i].C[sens, :] ThetaInv = np.linalg.pinv(self.rsys[sys_i].C) Qe = np.cov(ThetaInv @ self.Y1 - self.rsys[sys_i].A @ (ThetaInv @ self.Y0 - self.rsys[sys_i].B @ self.U0)) Re = np.cov(self.Y1[sens] - C @ (ThetaInv @ self.Y1)) return C, Qe, Re def compute_POD_basis(self, Y, q): return np.linalg.svd(Y, full_matrices=False)[0][:, :q] """ Plot functions """ def plot_dmd_modes(self, grid, E=None): if E is None: E = np.eye(self.q) nlevels = 41 wymin = -0.1 wymax = 0.1 nx = E.shape[1] X = grid.X() Z = grid.Z() xmax = np.max(X) def Phi(k): return np.real(self.Phi @ E)[:, k].reshape((grid.npx, grid.npz)) fig, axs = plt.subplots(1, figsize=(10, 5), facecolor='w', edgecolor='k') cont = axs.contourf(X, Z, Phi(0), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) cbar = fig.colorbar(cont, ax=axs, orientation='vertical') cbar.set_ticks(np.linspace(wymin, wymax, num=6, endpoint=True)) axs.set_title('$Phi_0$') axs.set_xlim([1, xmax]) # Flat plate patch delx = 5.0 / 768.0 delz = 2.0 / 384.0 xc = 249 * delx zc = 192 * delz alpha = 20.0 * np.pi / 180.0 DL = 80 * delx DT = 6 * delz flat_plate = patches.Rectangle( (xc - DL * np.cos(alpha) / 2. - DT * np.sin(alpha) / 2., zc + DL * np.sin(alpha) / 2. - DT * np.cos(alpha) / 2.), DL, DT, angle=-(alpha * 180.0 / np.pi), linewidth=1, edgecolor='black', facecolor='black') axs.add_patch(flat_plate) def animate(k): axs.clear() cont = axs.contourf(X, Z, Phi(k), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) axs.set_xlabel('$x$') axs.set_ylabel('$z$') axs.set_title('$Phi_{%d}$' % k) axs.set_aspect('equal', 'box') axs.add_patch(flat_plate) axs.set_xlim([1, xmax]) return cont anim = animation.FuncAnimation(fig, animate, frames=range(0, nx, 1), interval=500) return anim def plot_model_response(self, sys, grid): x0 = np.linalg.pinv(sys.C) @ self.Y0[:, 0] xdmd, ydmd = sys.lsim(x0, self.U0) nlevels = 41 wymin = -10 wymax = 10 X = grid.X() Z = grid.Z() xmax = np.max(X) def WY(k): return self.Y0[:, k].reshape((grid.npx, grid.npz)) def WY_dmd(k): return ydmd[:, k].reshape((grid.npx, grid.npz)) fig, axs = plt.subplots(2, figsize=(10, 8), facecolor='w', edgecolor='k') cont = axs[0].contourf(X, Z, WY(0), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) cont = axs[1].contourf(X, Z, WY_dmd(0), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) #plt.clim(wymin, wymax) cbar = fig.colorbar(cont, ax=axs, orientation='vertical') #cbar.ax.set_autoscale_on(True) cbar.set_ticks(np.linspace(wymin, wymax, num=6, endpoint=True)) # Flat plate patch delx = 5.0 / 768.0 delz = 2.0 / 384.0 xc = 249 * delx zc = 192 * delz alpha = 20.0 * np.pi / 180.0 DL = 80 * delx DT = 6 * delz flat_plate = patches.Rectangle( (xc - DL * np.cos(alpha) / 2. - DT * np.sin(alpha) / 2., zc + DL * np.sin(alpha) / 2. - DT * np.cos(alpha) / 2.), DL, DT, angle=-(alpha * 180.0 / np.pi), linewidth=1, edgecolor='black', facecolor='black') flat_plate_2 = patches.Rectangle( (xc - DL * np.cos(alpha) / 2. - DT * np.sin(alpha) / 2., zc + DL * np.sin(alpha) / 2. - DT * np.cos(alpha) / 2.), DL, DT, angle=-(alpha * 180.0 / np.pi), linewidth=1, edgecolor='black', facecolor='black') axs[0].add_patch(flat_plate) axs[1].add_patch(flat_plate_2) axs[0].set_xlim([1, xmax]) axs[1].set_xlim([1, xmax]) axs[0].set_title('Time step $k = %d$' % (0)) #cbar.ax.locator_params(nbins=6) def animate(k): # Clear axes axs[0].clear() axs[1].clear() # Training Data cont = axs[0].contourf(X, Z, WY(k), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) axs[0].set_xlabel('$x$') axs[0].set_ylabel('$z$') axs[0].set_aspect('equal', 'box') axs[0].add_patch(flat_plate) axs[0].set_xlim([1, xmax]) axs[0].set_title('Time step $k = %d$' % (k)) # Simulation Results cont = axs[1].contourf(X, Z, WY_dmd(k), nlevels, cmap='coolwarm', vmin=wymin, vmax=wymax) axs[1].set_xlabel('$x$') axs[1].set_ylabel('$z$') axs[1].set_aspect('equal', 'box') axs[1].add_patch(flat_plate_2) axs[1].set_xlim([1, xmax]) return cont anim = animation.FuncAnimation(fig, animate, frames=range(0, self.p, 4), interval=200) return anim
def sparse(self, gamma, niter): zero_thres = 1.e-6 print("gamma = %e | number of modes = " % (gamma)) # Define and solve the sparsity-promoting optimization problem # Weighted L1 norm is updated iteratively alpha = cp.Variable(self.q) weights = np.ones(self.q) for i in range(niter): objective_sparse = cp.Minimize( cp.quad_form(alpha, self.P) - 2. * cp.real(self.d.conj().T @ alpha) + self.s + gamma * cp.pnorm(np.diag(weights) @ alpha, p=1)) prob_sparse = cp.Problem(objective_sparse) sol_sparse = prob_sparse.solve(verbose=False, solver=cp.SCS) alpha_sp = alpha.value # Sparse solution if alpha_sp is None: alpha_sp = np.ones(self.q) # Update weights weights = 1.0 / (np.abs(alpha_sp) + np.finfo(float).eps) nonzero = np.abs(alpha_sp) > zero_thres # Nonzero modes print(" %d of %d" % (np.sum(nonzero), self.q)) J_sp = np.real(alpha_sp.conj().T @ self.P @ alpha_sp - 2 * np.real(self.d.conj().T @ alpha_sp) + self.s) # Square error nx = np.sum( nonzero ) # Number of nonzero modes - order of the sparse/reduced model Ez = np.eye(self.q)[:, ~nonzero] # Define and solve the refinement optimization problem #alpha = cp.Variable(self.q) objective_refine = cp.Minimize( cp.quad_form(alpha, self.P) - 2. * cp.real(self.d.conj().T @ alpha) + self.s) if np.sum(~nonzero): constraint_refine = [Ez.T @ alpha == 0] prob_refine = cp.Problem(objective_refine, constraint_refine) else: prob_refine = cp.Problem(objective_refine) sol_refine = prob_refine.solve() alpha_ref = alpha.value J_ref = np.real(alpha_ref.conj().T @ self.P @ alpha_ref - 2 * np.real(self.d.conj().T @ alpha_ref) + self.s) # Square error P_loss = 100 * np.sqrt(J_ref / self.s) # Truncate modes E = np.eye(self.q)[:, nonzero] Lambda_bar = E.T @ self.Lambda @ E Beta_bar = E.T @ self.Gamma Phi_bar = self.Phi @ np.diag(alpha_ref) @ E # Save data stats = {} stats["nx"] = nx stats["alpha_sp"] = alpha_sp stats["alpha_ref"] = alpha_ref stats["z_0"] = (np.linalg.pinv(self.Phi) @ self.Y0[:, 0])[nonzero] stats["E"] = E stats["J_sp"] = J_sp stats["J_ref"] = J_ref stats["P_loss"] = P_loss if nx != 0: print("Rank of controllability matrix: %d of %d" % (np.linalg.matrix_rank(control.ctrb(Lambda_bar, Beta_bar)), nx)) # Convert system from complex modal form to real block-modal form lambda_bar = np.diag(Lambda_bar) A = np.zeros((nx, nx)) B = np.zeros((nx, self.nu)) Theta = np.zeros((self.ny, nx)) i = 0 while i < nx: if np.isreal(lambda_bar[i]): A[i, i] = lambda_bar[i] B[i] = Beta_bar[i] Theta[:, i] = Phi_bar[:, i] i += 1 elif i == nx - 1: # In case only one of the conjugate pairs is truncated - this rarely happens A[i, i] = np.real(lambda_bar[i]) B[i] = np.real(Beta_bar[i]) Theta[:, i] = np.real(Phi_bar[:, i]) i += 1 elif np.isreal(np.isreal(lambda_bar[i] + lambda_bar[i + 1])): A[i:i + 2, i:i + 2] = np.array( [[np.real(lambda_bar[i]), np.imag(lambda_bar[i])], [-np.imag(lambda_bar[i]), np.real(lambda_bar[i])]]) B[i] = np.real(Beta_bar[i]) B[i + 1] = -np.imag(Beta_bar[i]) Theta[:, i] = 2 * np.real(Phi_bar[:, i]) Theta[:, i + 1] = 2 * np.imag(Phi_bar[:, i]) i += 2 else: raise ValueError( "Eigenvalues are not grouped in conjugate pairs") #return StateSpace(Lambda_bar, Beta_bar, Phi_bar), lambda_bar, stats return StateSpace(A, B, Theta), lambda_bar, stats