123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import numpy as np
- import scipy as sp
- import control as ct
- import csv
- import matplotlib.pyplot as plt
- #[1] These methods provide functionality for implementing Modal Truncation as described in
- # Antoulas, A.C. "Model order reduction : methods, concepts and properties". (CASA-report; Vol. 1507). Eindhoven: Technische Universiteit Eindhoven.(2015).
- # Read data containing all entries of the state-space matrices
- def ReadData(filepath):
- with open(filepath, 'r') as file:
- AllData = list(csv.reader(file, delimiter=';'))
- # Assign variables
- m = int(AllData[1][0]) #input order
- p = int(AllData[1][1]) #output order
- n = int(AllData[1][2]) #problem order
-
- A = np.matrix(AllData[3:n+3], dtype = np.double)
- B = np.matrix(AllData[n+4:2*n+4], dtype = np.double)
- C = np.matrix(AllData[2*n+5:3*n+5], dtype = np.double)
- D = np.matrix(AllData[3*n+6:3*n+p+6], dtype = np.double)
-
- return m, p, n, A, B, C, D
-
- # Assigns all needed variables from the provided data
- def AssignVars(reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
- r = reducedOrder
- if filepath != None:
- m, p, n, A, B, C, D = ReadData(filepath)
- else:
- if A.all() == None or B.all() == None or C.all() == None or D == None:
- print("Matrices not defined correctly.")
- A = np.matrix(A, dtype=np.double)
- B = np.matrix(B, dtype=np.double)
- C = np.matrix(C, dtype=np.double)
- D = np.matrix(D, dtype=np.double)
- m = B.shape[1]
- p = C.shape[1]
- n = A.shape[0]
- return r, m, p, n, A, B, C, D
- # This methods creates an array of poles and residues of the transfer function of a given state-space model in pole-residue form.
- def CalculatePoleResidue(A, B, C):
- # Following [1], we calculate the eigenvalues and left and right eigenvectors.
- labda, v, w = sp.linalg.eig(A, left = True, right = True)
- residues = []
- scaledLeft = v.copy()
- scaledRight = w.copy()
-
- for i in range(labda.size):
- left = v[:,i]
- right = w[:,i]
- prod = np.vdot(right, left)
- scale = np.sqrt(1/prod)
- scaleconj = np.conj(scale)
-
- newright = np.array(scaleconj * right)
- newleft = np.array(scale * left)
- newB = np.asarray(B).reshape(-1)
- newC = np.asarray(C).reshape(-1)
- R_i = np.vdot(newC, newright) * np.vdot(newleft, newB)
- residues.append(R_i)
- scaledLeft[:,i] = newleft
- scaledRight[:,i] = newright
-
- poles = labda
- return poles, residues, scaledLeft, scaledRight
- # Calculates the dominance of each pole as defined in [1].
- def CalculateDominance(poles, residues):
- dominances = []
- for i in range(poles.size):
- dominances.append(np.abs(residues[i])/np.abs(np.real(poles[i])))
- return dominances
- # Sorts the poles, residues and dominances of each pole based on the size of the dominances in reverse order.
- def SortForDominance(poles, residues, dominances, leftEigVec, rightEigVec):
- poles_sorted = []
- residues_sorted = []
- dominances_sorted = []
- leftEigVec_sorted = leftEigVec.copy()
- rightEigVec_sorted = rightEigVec.copy()
-
- order = np.argsort(dominances)
- for j in range(1, len(poles) + 1):
- poles_sorted.append(poles[order[len(order)- j]])
- residues_sorted.append(residues[order[len(order)- j]])
- dominances_sorted.append(dominances[order[len(order)- j]])
- leftEigVec_sorted[:,j-1] = leftEigVec[:, order[len(order)- j]]
- rightEigVec_sorted[:,j-1] = rightEigVec[:, order[len(order)- j]]
- return poles_sorted, residues_sorted, dominances_sorted, leftEigVec_sorted, rightEigVec_sorted
- # Produces the numerator and denominator of the transfer functions reduced to order r.
- def ReduceTransferFunction(poles_sorted, residues_sorted, D, r):
- residues_reduced = []
- poles_reduced = []
-
- for i in range(r):
- residues_reduced.append(residues_sorted[i])
- poles_reduced.append(poles_sorted[i])
-
- return residues_reduced, poles_reduced, D
- def ReduceEigenVectorMatrices(leftEigVec_sorted, rightEigVec_sorted, n, r):
- leftEigVec_reduced = np.zeros((n, r), dtype = complex)
- rightEigVec_reduced = np.zeros((n, r), dtype = complex)
-
- for i in range(r):
- leftEigVec_reduced[:,i] = leftEigVec_sorted[:,i]
- rightEigVec_reduced[:,i] = rightEigVec_sorted[:,i]
-
- return leftEigVec_reduced, rightEigVec_reduced
- def ReduceSystem(A, B, C, leftEigVec_reduced, rightEigVec_reduced):
- A_reduced = leftEigVec_reduced.conj().transpose() @ A @ rightEigVec_reduced
- B_reduced = leftEigVec_reduced.conj().transpose() @ np.asarray(B).reshape(-1)
- C_reduced = C @ rightEigVec_reduced
- return A_reduced, B_reduced, C_reduced
- # Calculates the error bound associated with the reduction of the system to order r.
- def CalculateMTErrorBound(dominances_sorted, r):
- result = 0
- for i in range(r, len(dominances_sorted)):
- result += dominances_sorted[i]
- return result
-
- # Plot the size of the dominances of each pole and the error bound for each order of truncation.
- def PlotData(dominances_sorted):
- fig, (ax_1, ax_2) = plt.subplots(nrows=1, ncols=2, figsize=(8,4))
-
- ax_1.plot(dominances_sorted, 'bs')
- ax_1.set_title('Size of the dominance of each pole', fontsize='medium')
- ax_1.axis([0, len(dominances_sorted) - 1, 0, int(dominances_sorted[0]) + 1])
- ax_1.set_xlabel('Pole index, i')
- ax_1.set_ylabel('Dominance')
- print('Dominance')
- print(dominances_sorted)
-
- errorbounds = []
- for i in range(len(dominances_sorted)):
- errorbounds.append(CalculateMTErrorBound(dominances_sorted, i))
-
- ax_2.plot(errorbounds, 'rs')
- ax_2.set_title('Size of the MT error bound', fontsize='medium')
- ax_2.axis([0, len(errorbounds) - 1, 0, int(errorbounds[0]) + 1])
- ax_2.set_xlabel('order of reduced model')
- ax_2.set_ylabel('Error Bound')
- print('Error bounds')
- print(errorbounds)
-
- fig.suptitle('Modal Truncation', fontsize='medium')
- plt.show()
-
- # Applies Modal Truncation to the Transfer Function of a state-space model
- # and returns a reduced order transfer function numerator and denominator
- def ApplyModalTruncation(reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
- r, m, p, n, A, B, C, D = AssignVars(reducedOrder, A, B, C, D, filepath)
-
- poles, residues, leftEigVec, rightEigVec = CalculatePoleResidue(A, B, C)
- dominances = CalculateDominance(poles, residues)
-
- poles_sorted, residues_sorted, dominances_sorted, leftEigVec_sorted, rightEigVec_sorted = SortForDominance(poles, residues, dominances, leftEigVec, rightEigVec)
-
- PlotData(dominances_sorted)
-
- leftEigVec_reduced, rightEigVec_reduced = ReduceEigenVectorMatrices(leftEigVec_sorted, rightEigVec_sorted, n, r)
- residues_reduced, poles_reduced, D = ReduceTransferFunction(poles_sorted, residues_sorted, D, r)
-
- A_reduced, B_reduced, C_reduced = ReduceSystem(A, B, C, leftEigVec_reduced, rightEigVec_reduced)
-
- return A_reduced, B_reduced, C_reduced, residues_reduced, poles_reduced, D
|