ModalTruncation.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import numpy as np
  2. import scipy as sp
  3. import control as ct
  4. import csv
  5. import matplotlib.pyplot as plt
  6. #[1] These methods provide functionality for implementing Modal Truncation as described in
  7. # Antoulas, A.C. "Model order reduction : methods, concepts and properties". (CASA-report; Vol. 1507). Eindhoven: Technische Universiteit Eindhoven.(2015).
  8. # Read data containing all entries of the state-space matrices
  9. def ReadData(filepath):
  10. with open(filepath, 'r') as file:
  11. AllData = list(csv.reader(file, delimiter=';'))
  12. # Assign variables
  13. m = int(AllData[1][0]) #input order
  14. p = int(AllData[1][1]) #output order
  15. n = int(AllData[1][2]) #problem order
  16. A = np.matrix(AllData[3:n+3], dtype = np.double)
  17. B = np.matrix(AllData[n+4:2*n+4], dtype = np.double)
  18. C = np.matrix(AllData[2*n+5:3*n+5], dtype = np.double)
  19. D = np.matrix(AllData[3*n+6:3*n+p+6], dtype = np.double)
  20. return m, p, n, A, B, C, D
  21. # Assigns all needed variables from the provided data
  22. def AssignVars(reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
  23. r = reducedOrder
  24. if filepath != None:
  25. m, p, n, A, B, C, D = ReadData(filepath)
  26. else:
  27. if A.all() == None or B.all() == None or C.all() == None or D == None:
  28. print("Matrices not defined correctly.")
  29. A = np.matrix(A, dtype=np.double)
  30. B = np.matrix(B, dtype=np.double)
  31. C = np.matrix(C, dtype=np.double)
  32. D = np.matrix(D, dtype=np.double)
  33. m = B.shape[1]
  34. p = C.shape[1]
  35. n = A.shape[0]
  36. return r, m, p, n, A, B, C, D
  37. # This methods creates an array of poles and residues of the transfer function of a given state-space model in pole-residue form.
  38. def CalculatePoleResidue(A, B, C):
  39. # Following [1], we calculate the eigenvalues and left and right eigenvectors.
  40. labda, v, w = sp.linalg.eig(A, left = True, right = True)
  41. residues = []
  42. scaledLeft = v.copy()
  43. scaledRight = w.copy()
  44. for i in range(labda.size):
  45. left = v[:,i]
  46. right = w[:,i]
  47. prod = np.vdot(right, left)
  48. scale = np.sqrt(1/prod)
  49. scaleconj = np.conj(scale)
  50. newright = np.array(scaleconj * right)
  51. newleft = np.array(scale * left)
  52. newB = np.asarray(B).reshape(-1)
  53. newC = np.asarray(C).reshape(-1)
  54. R_i = np.vdot(newC, newright) * np.vdot(newleft, newB)
  55. residues.append(R_i)
  56. scaledLeft[:,i] = newleft
  57. scaledRight[:,i] = newright
  58. poles = labda
  59. return poles, residues, scaledLeft, scaledRight
  60. # Calculates the dominance of each pole as defined in [1].
  61. def CalculateDominance(poles, residues):
  62. dominances = []
  63. for i in range(poles.size):
  64. dominances.append(np.abs(residues[i])/np.abs(np.real(poles[i])))
  65. return dominances
  66. # Sorts the poles, residues and dominances of each pole based on the size of the dominances in reverse order.
  67. def SortForDominance(poles, residues, dominances, leftEigVec, rightEigVec):
  68. poles_sorted = []
  69. residues_sorted = []
  70. dominances_sorted = []
  71. leftEigVec_sorted = leftEigVec.copy()
  72. rightEigVec_sorted = rightEigVec.copy()
  73. order = np.argsort(dominances)
  74. for j in range(1, len(poles) + 1):
  75. poles_sorted.append(poles[order[len(order)- j]])
  76. residues_sorted.append(residues[order[len(order)- j]])
  77. dominances_sorted.append(dominances[order[len(order)- j]])
  78. leftEigVec_sorted[:,j-1] = leftEigVec[:, order[len(order)- j]]
  79. rightEigVec_sorted[:,j-1] = rightEigVec[:, order[len(order)- j]]
  80. return poles_sorted, residues_sorted, dominances_sorted, leftEigVec_sorted, rightEigVec_sorted
  81. # Produces the numerator and denominator of the transfer functions reduced to order r.
  82. def ReduceTransferFunction(poles_sorted, residues_sorted, D, r):
  83. residues_reduced = []
  84. poles_reduced = []
  85. for i in range(r):
  86. residues_reduced.append(residues_sorted[i])
  87. poles_reduced.append(poles_sorted[i])
  88. return residues_reduced, poles_reduced, D
  89. def ReduceEigenVectorMatrices(leftEigVec_sorted, rightEigVec_sorted, n, r):
  90. leftEigVec_reduced = np.zeros((n, r), dtype = complex)
  91. rightEigVec_reduced = np.zeros((n, r), dtype = complex)
  92. for i in range(r):
  93. leftEigVec_reduced[:,i] = leftEigVec_sorted[:,i]
  94. rightEigVec_reduced[:,i] = rightEigVec_sorted[:,i]
  95. return leftEigVec_reduced, rightEigVec_reduced
  96. def ReduceSystem(A, B, C, leftEigVec_reduced, rightEigVec_reduced):
  97. A_reduced = leftEigVec_reduced.conj().transpose() @ A @ rightEigVec_reduced
  98. B_reduced = leftEigVec_reduced.conj().transpose() @ np.asarray(B).reshape(-1)
  99. C_reduced = C @ rightEigVec_reduced
  100. return A_reduced, B_reduced, C_reduced
  101. # Calculates the error bound associated with the reduction of the system to order r.
  102. def CalculateMTErrorBound(dominances_sorted, r):
  103. result = 0
  104. for i in range(r, len(dominances_sorted)):
  105. result += dominances_sorted[i]
  106. return result
  107. # Plot the size of the dominances of each pole and the error bound for each order of truncation.
  108. def PlotData(dominances_sorted):
  109. fig, (ax_1, ax_2) = plt.subplots(nrows=1, ncols=2, figsize=(8,4))
  110. ax_1.plot(dominances_sorted, 'bs')
  111. ax_1.set_title('Size of the dominance of each pole', fontsize='medium')
  112. ax_1.axis([0, len(dominances_sorted) - 1, 0, int(dominances_sorted[0]) + 1])
  113. ax_1.set_xlabel('Pole index, i')
  114. ax_1.set_ylabel('Dominance')
  115. print('Dominance')
  116. print(dominances_sorted)
  117. errorbounds = []
  118. for i in range(len(dominances_sorted)):
  119. errorbounds.append(CalculateMTErrorBound(dominances_sorted, i))
  120. ax_2.plot(errorbounds, 'rs')
  121. ax_2.set_title('Size of the MT error bound', fontsize='medium')
  122. ax_2.axis([0, len(errorbounds) - 1, 0, int(errorbounds[0]) + 1])
  123. ax_2.set_xlabel('order of reduced model')
  124. ax_2.set_ylabel('Error Bound')
  125. print('Error bounds')
  126. print(errorbounds)
  127. fig.suptitle('Modal Truncation', fontsize='medium')
  128. plt.show()
  129. # Applies Modal Truncation to the Transfer Function of a state-space model
  130. # and returns a reduced order transfer function numerator and denominator
  131. def ApplyModalTruncation(reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
  132. r, m, p, n, A, B, C, D = AssignVars(reducedOrder, A, B, C, D, filepath)
  133. poles, residues, leftEigVec, rightEigVec = CalculatePoleResidue(A, B, C)
  134. dominances = CalculateDominance(poles, residues)
  135. poles_sorted, residues_sorted, dominances_sorted, leftEigVec_sorted, rightEigVec_sorted = SortForDominance(poles, residues, dominances, leftEigVec, rightEigVec)
  136. PlotData(dominances_sorted)
  137. leftEigVec_reduced, rightEigVec_reduced = ReduceEigenVectorMatrices(leftEigVec_sorted, rightEigVec_sorted, n, r)
  138. residues_reduced, poles_reduced, D = ReduceTransferFunction(poles_sorted, residues_sorted, D, r)
  139. A_reduced, B_reduced, C_reduced = ReduceSystem(A, B, C, leftEigVec_reduced, rightEigVec_reduced)
  140. return A_reduced, B_reduced, C_reduced, residues_reduced, poles_reduced, D