BalancedTruncation.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. # Read data containing all entries of the state-space matrices
  7. def ReadData(filepath):
  8. with open(filepath, 'r') as file:
  9. AllData = list(csv.reader(file, delimiter=';'))
  10. # Assign variables
  11. m = int(AllData[1][0]) #input order
  12. p = int(AllData[1][1]) #output order
  13. n = int(AllData[1][2]) #problem order
  14. A = np.matrix(AllData[3:n+3], dtype = np.double)
  15. B = np.matrix(AllData[n+4:2*n+4], dtype = np.double)
  16. C = np.matrix(AllData[2*n+5:3*n+5], dtype = np.double)
  17. D = np.matrix(AllData[3*n+6:3*n+p+6], dtype = np.double)
  18. return m, p, n, A, B, C, D
  19. # Assigns all needed variables from the provided data
  20. def AssignVars(reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
  21. r = reducedOrder
  22. if filepath != None:
  23. m, p, n, A, B, C, D = ReadData(filepath)
  24. else:
  25. if A.all() == None or B.all() == None or C.all() == None or D == None:
  26. print("Matrices not defined correctly.")
  27. A = np.matrix(A, dtype=np.double)
  28. B = np.matrix(B, dtype=np.double).transpose()
  29. C = np.matrix(C, dtype=np.double).transpose()
  30. D = np.matrix(D, dtype=np.double)
  31. m = B.shape[1]
  32. p = C.shape[1]
  33. n = A.shape[0]
  34. return r, m, p, n, A, B, C, D
  35. # Applies a similarity transform to a system
  36. def SimilarityTransform(A, B, C, T, T_inv):
  37. A_prime = np.matmul(np.matmul(T_inv, A), T)
  38. B_prime = np.matmul(T_inv, B)
  39. C_prime = np.matmul(C, T)
  40. return A_prime, B_prime, C_prime
  41. # This returns the sections of the matrices used in the reduced system based on reduction order r.
  42. def TruncateSystemMatrices(A, B, C, r, m, p):
  43. A11 = np.ndarray([r, r])
  44. for i in range(r):
  45. for j in range(r):
  46. A11[i,j] = A[i,j]
  47. B1 = np.ndarray([r, m])
  48. for i in range(r):
  49. for j in range(m):
  50. B1[i,j] = B[i,j]
  51. C1 = np.ndarray([p, r])
  52. for i in range(p):
  53. for j in range(r):
  54. C1[i,j] = C[i,j]
  55. return A11, B1, C1
  56. # Calculates the error-bound corresponding to a certain reduction.
  57. def CalculateBTErrorBound(HSV, r):
  58. result = 0.0
  59. for i in range(r, HSV.size):
  60. result += HSV[i]
  61. return 2*result
  62. # Plot the size of Hankel Singular Values and the error bound for each order of truncation.
  63. def PlotData(HSV):
  64. fig, (ax_1, ax_2) = plt.subplots(nrows=1, ncols=2, figsize=(8,4))
  65. ax_1.plot(HSV, 'bs')
  66. ax_1.set_title('Size of the Hankel Singular Values', fontsize='medium')
  67. ax_1.axis([0, HSV.size, 0, int(HSV[0]) + 1])
  68. ax_1.set_xlabel('order, n')
  69. ax_1.set_ylabel('HSV, $\sigma_i$')
  70. print('HSV')
  71. print(HSV)
  72. errorbounds = []
  73. for i in range(HSV.size):
  74. errorbounds.append(CalculateBTErrorBound(HSV, i))
  75. ax_2.plot(errorbounds, 'rs')
  76. ax_2.set_title('Size of the BT error bound', fontsize='medium')
  77. ax_2.axis([0, len(errorbounds), 0, int(errorbounds[0]) + 1])
  78. ax_2.set_xlabel('order of reduced model')
  79. ax_2.set_ylabel('Error Bound')
  80. print('Error bounds')
  81. print(errorbounds)
  82. fig.suptitle('Balanced Truncation', fontsize='medium')
  83. plt.show()
  84. # Applies Balanced Truncation to the matrices of a state-space system and reduces it to a given order.
  85. def ApplyBalancedTruncation(SamplingInterval, reducedOrder, A=None, B=None, C=None, D=None, filepath=None):
  86. r, m, p, n, A, B, C, D = AssignVars(reducedOrder, A, B, C, D, filepath)
  87. BBT = np.matmul(B, B.transpose())
  88. CT = C.transpose()
  89. DiscreteSystem = ct.StateSpace(A, B, CT, D, SamplingInterval)
  90. # We calculate the cholesky-factors of both Gramians of the system and the Gramians itself.
  91. Rc = ct.gram(DiscreteSystem, 'cf')
  92. Wc = np.matmul(Rc, Rc.transpose())
  93. Ro = ct.gram(DiscreteSystem, 'of')
  94. Wo = np.matmul(Ro.transpose(), Ro)
  95. T, T_inv, HSV = SquareRootAlgorithm(Rc, Wc, Ro, Wo)
  96. PlotData(HSV)
  97. A_prime, B_prime, C_prime = SimilarityTransform(A, B, CT, T, T_inv)
  98. A11, B1, C1 = TruncateSystemMatrices(A_prime, B_prime, C_prime, r, m, p)
  99. return A11, B1, C1
  100. #[1] This method produces a balancing transformation and the Hankel Singular Values
  101. # based on the square root algorithm as stated in Antoulas A.C., Sorensen D.C., "APPROXIMATION OF LARGE-SCALE
  102. # DYNAMICAL SYSTEMS: AN OVERVIEW", Int. J. Appl. Math. Comput. Sci., 2001, Vol.11, No.5, 1093–1121.
  103. def SquareRootAlgorithm(Rc, Wc, Ro, Wo):
  104. # Setting variables to follow the exact notation in [1].
  105. P = Wc
  106. U = Rc
  107. UT = Rc.transpose()
  108. Q = Wo
  109. L = Ro.transpose()
  110. LT = Ro
  111. UTL = UT@L
  112. Z, S, YT = np.linalg.svd(UTL)
  113. S12_inv = S.copy()
  114. for i in range(S.size):
  115. S12_inv[i] = 1/(np.sqrt(S[i]))
  116. Tb = np.diag(S12_inv) @ YT @ LT
  117. Tb_inv = U @ Z @ np.diag(S12_inv)
  118. HSV = S
  119. T = Tb_inv
  120. T_inv = Tb
  121. return T, T_inv, HSV