test_linearize.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. from sympy.core.backend import (symbols, Matrix, cos, sin, atan, sqrt,
  2. Rational, _simplify_matrix)
  3. from sympy.core.sympify import sympify
  4. from sympy.simplify.simplify import simplify
  5. from sympy.solvers.solvers import solve
  6. from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame, Point,\
  7. dot, cross, inertia, KanesMethod, Particle, RigidBody, Lagrangian,\
  8. LagrangesMethod
  9. from sympy.testing.pytest import slow
  10. @slow
  11. def test_linearize_rolling_disc_kane():
  12. # Symbols for time and constant parameters
  13. t, r, m, g, v = symbols('t r m g v')
  14. # Configuration variables and their time derivatives
  15. q1, q2, q3, q4, q5, q6 = q = dynamicsymbols('q1:7')
  16. q1d, q2d, q3d, q4d, q5d, q6d = qd = [qi.diff(t) for qi in q]
  17. # Generalized speeds and their time derivatives
  18. u = dynamicsymbols('u:6')
  19. u1, u2, u3, u4, u5, u6 = u = dynamicsymbols('u1:7')
  20. u1d, u2d, u3d, u4d, u5d, u6d = [ui.diff(t) for ui in u]
  21. # Reference frames
  22. N = ReferenceFrame('N') # Inertial frame
  23. NO = Point('NO') # Inertial origin
  24. A = N.orientnew('A', 'Axis', [q1, N.z]) # Yaw intermediate frame
  25. B = A.orientnew('B', 'Axis', [q2, A.x]) # Lean intermediate frame
  26. C = B.orientnew('C', 'Axis', [q3, B.y]) # Disc fixed frame
  27. CO = NO.locatenew('CO', q4*N.x + q5*N.y + q6*N.z) # Disc center
  28. # Disc angular velocity in N expressed using time derivatives of coordinates
  29. w_c_n_qd = C.ang_vel_in(N)
  30. w_b_n_qd = B.ang_vel_in(N)
  31. # Inertial angular velocity and angular acceleration of disc fixed frame
  32. C.set_ang_vel(N, u1*B.x + u2*B.y + u3*B.z)
  33. # Disc center velocity in N expressed using time derivatives of coordinates
  34. v_co_n_qd = CO.pos_from(NO).dt(N)
  35. # Disc center velocity in N expressed using generalized speeds
  36. CO.set_vel(N, u4*C.x + u5*C.y + u6*C.z)
  37. # Disc Ground Contact Point
  38. P = CO.locatenew('P', r*B.z)
  39. P.v2pt_theory(CO, N, C)
  40. # Configuration constraint
  41. f_c = Matrix([q6 - dot(CO.pos_from(P), N.z)])
  42. # Velocity level constraints
  43. f_v = Matrix([dot(P.vel(N), uv) for uv in C])
  44. # Kinematic differential equations
  45. kindiffs = Matrix([dot(w_c_n_qd - C.ang_vel_in(N), uv) for uv in B] +
  46. [dot(v_co_n_qd - CO.vel(N), uv) for uv in N])
  47. qdots = solve(kindiffs, qd)
  48. # Set angular velocity of remaining frames
  49. B.set_ang_vel(N, w_b_n_qd.subs(qdots))
  50. C.set_ang_acc(N, C.ang_vel_in(N).dt(B) + cross(B.ang_vel_in(N), C.ang_vel_in(N)))
  51. # Active forces
  52. F_CO = m*g*A.z
  53. # Create inertia dyadic of disc C about point CO
  54. I = (m * r**2) / 4
  55. J = (m * r**2) / 2
  56. I_C_CO = inertia(C, I, J, I)
  57. Disc = RigidBody('Disc', CO, C, m, (I_C_CO, CO))
  58. BL = [Disc]
  59. FL = [(CO, F_CO)]
  60. KM = KanesMethod(N, [q1, q2, q3, q4, q5], [u1, u2, u3], kd_eqs=kindiffs,
  61. q_dependent=[q6], configuration_constraints=f_c,
  62. u_dependent=[u4, u5, u6], velocity_constraints=f_v)
  63. (fr, fr_star) = KM.kanes_equations(BL, FL)
  64. # Test generalized form equations
  65. linearizer = KM.to_linearizer()
  66. assert linearizer.f_c == f_c
  67. assert linearizer.f_v == f_v
  68. assert linearizer.f_a == f_v.diff(t).subs(KM.kindiffdict())
  69. sol = solve(linearizer.f_0 + linearizer.f_1, qd)
  70. for qi in qdots.keys():
  71. assert sol[qi] == qdots[qi]
  72. assert simplify(linearizer.f_2 + linearizer.f_3 - fr - fr_star) == Matrix([0, 0, 0])
  73. # Perform the linearization
  74. # Precomputed operating point
  75. q_op = {q6: -r*cos(q2)}
  76. u_op = {u1: 0,
  77. u2: sin(q2)*q1d + q3d,
  78. u3: cos(q2)*q1d,
  79. u4: -r*(sin(q2)*q1d + q3d)*cos(q3),
  80. u5: 0,
  81. u6: -r*(sin(q2)*q1d + q3d)*sin(q3)}
  82. qd_op = {q2d: 0,
  83. q4d: -r*(sin(q2)*q1d + q3d)*cos(q1),
  84. q5d: -r*(sin(q2)*q1d + q3d)*sin(q1),
  85. q6d: 0}
  86. ud_op = {u1d: 4*g*sin(q2)/(5*r) + sin(2*q2)*q1d**2/2 + 6*cos(q2)*q1d*q3d/5,
  87. u2d: 0,
  88. u3d: 0,
  89. u4d: r*(sin(q2)*sin(q3)*q1d*q3d + sin(q3)*q3d**2),
  90. u5d: r*(4*g*sin(q2)/(5*r) + sin(2*q2)*q1d**2/2 + 6*cos(q2)*q1d*q3d/5),
  91. u6d: -r*(sin(q2)*cos(q3)*q1d*q3d + cos(q3)*q3d**2)}
  92. A, B = linearizer.linearize(op_point=[q_op, u_op, qd_op, ud_op], A_and_B=True, simplify=True)
  93. upright_nominal = {q1d: 0, q2: 0, m: 1, r: 1, g: 1}
  94. # Precomputed solution
  95. A_sol = Matrix([[0, 0, 0, 0, 0, 0, 0, 1],
  96. [0, 0, 0, 0, 0, 1, 0, 0],
  97. [0, 0, 0, 0, 0, 0, 1, 0],
  98. [sin(q1)*q3d, 0, 0, 0, 0, -sin(q1), -cos(q1), 0],
  99. [-cos(q1)*q3d, 0, 0, 0, 0, cos(q1), -sin(q1), 0],
  100. [0, Rational(4, 5), 0, 0, 0, 0, 0, 6*q3d/5],
  101. [0, 0, 0, 0, 0, 0, 0, 0],
  102. [0, 0, 0, 0, 0, -2*q3d, 0, 0]])
  103. B_sol = Matrix([])
  104. # Check that linearization is correct
  105. assert A.subs(upright_nominal) == A_sol
  106. assert B.subs(upright_nominal) == B_sol
  107. # Check eigenvalues at critical speed are all zero:
  108. assert sympify(A.subs(upright_nominal).subs(q3d, 1/sqrt(3))).eigenvals() == {0: 8}
  109. def test_linearize_pendulum_kane_minimal():
  110. q1 = dynamicsymbols('q1') # angle of pendulum
  111. u1 = dynamicsymbols('u1') # Angular velocity
  112. q1d = dynamicsymbols('q1', 1) # Angular velocity
  113. L, m, t = symbols('L, m, t')
  114. g = 9.8
  115. # Compose world frame
  116. N = ReferenceFrame('N')
  117. pN = Point('N*')
  118. pN.set_vel(N, 0)
  119. # A.x is along the pendulum
  120. A = N.orientnew('A', 'axis', [q1, N.z])
  121. A.set_ang_vel(N, u1*N.z)
  122. # Locate point P relative to the origin N*
  123. P = pN.locatenew('P', L*A.x)
  124. P.v2pt_theory(pN, N, A)
  125. pP = Particle('pP', P, m)
  126. # Create Kinematic Differential Equations
  127. kde = Matrix([q1d - u1])
  128. # Input the force resultant at P
  129. R = m*g*N.x
  130. # Solve for eom with kanes method
  131. KM = KanesMethod(N, q_ind=[q1], u_ind=[u1], kd_eqs=kde)
  132. (fr, frstar) = KM.kanes_equations([pP], [(P, R)])
  133. # Linearize
  134. A, B, inp_vec = KM.linearize(A_and_B=True, simplify=True)
  135. assert A == Matrix([[0, 1], [-9.8*cos(q1)/L, 0]])
  136. assert B == Matrix([])
  137. def test_linearize_pendulum_kane_nonminimal():
  138. # Create generalized coordinates and speeds for this non-minimal realization
  139. # q1, q2 = N.x and N.y coordinates of pendulum
  140. # u1, u2 = N.x and N.y velocities of pendulum
  141. q1, q2 = dynamicsymbols('q1:3')
  142. q1d, q2d = dynamicsymbols('q1:3', level=1)
  143. u1, u2 = dynamicsymbols('u1:3')
  144. u1d, u2d = dynamicsymbols('u1:3', level=1)
  145. L, m, t = symbols('L, m, t')
  146. g = 9.8
  147. # Compose world frame
  148. N = ReferenceFrame('N')
  149. pN = Point('N*')
  150. pN.set_vel(N, 0)
  151. # A.x is along the pendulum
  152. theta1 = atan(q2/q1)
  153. A = N.orientnew('A', 'axis', [theta1, N.z])
  154. # Locate the pendulum mass
  155. P = pN.locatenew('P1', q1*N.x + q2*N.y)
  156. pP = Particle('pP', P, m)
  157. # Calculate the kinematic differential equations
  158. kde = Matrix([q1d - u1,
  159. q2d - u2])
  160. dq_dict = solve(kde, [q1d, q2d])
  161. # Set velocity of point P
  162. P.set_vel(N, P.pos_from(pN).dt(N).subs(dq_dict))
  163. # Configuration constraint is length of pendulum
  164. f_c = Matrix([P.pos_from(pN).magnitude() - L])
  165. # Velocity constraint is that the velocity in the A.x direction is
  166. # always zero (the pendulum is never getting longer).
  167. f_v = Matrix([P.vel(N).express(A).dot(A.x)])
  168. f_v.simplify()
  169. # Acceleration constraints is the time derivative of the velocity constraint
  170. f_a = f_v.diff(t)
  171. f_a.simplify()
  172. # Input the force resultant at P
  173. R = m*g*N.x
  174. # Derive the equations of motion using the KanesMethod class.
  175. KM = KanesMethod(N, q_ind=[q2], u_ind=[u2], q_dependent=[q1],
  176. u_dependent=[u1], configuration_constraints=f_c,
  177. velocity_constraints=f_v, acceleration_constraints=f_a, kd_eqs=kde)
  178. (fr, frstar) = KM.kanes_equations([pP], [(P, R)])
  179. # Set the operating point to be straight down, and non-moving
  180. q_op = {q1: L, q2: 0}
  181. u_op = {u1: 0, u2: 0}
  182. ud_op = {u1d: 0, u2d: 0}
  183. A, B, inp_vec = KM.linearize(op_point=[q_op, u_op, ud_op], A_and_B=True,
  184. simplify=True)
  185. assert A.expand() == Matrix([[0, 1], [-9.8/L, 0]])
  186. assert B == Matrix([])
  187. def test_linearize_pendulum_lagrange_minimal():
  188. q1 = dynamicsymbols('q1') # angle of pendulum
  189. q1d = dynamicsymbols('q1', 1) # Angular velocity
  190. L, m, t = symbols('L, m, t')
  191. g = 9.8
  192. # Compose world frame
  193. N = ReferenceFrame('N')
  194. pN = Point('N*')
  195. pN.set_vel(N, 0)
  196. # A.x is along the pendulum
  197. A = N.orientnew('A', 'axis', [q1, N.z])
  198. A.set_ang_vel(N, q1d*N.z)
  199. # Locate point P relative to the origin N*
  200. P = pN.locatenew('P', L*A.x)
  201. P.v2pt_theory(pN, N, A)
  202. pP = Particle('pP', P, m)
  203. # Solve for eom with Lagranges method
  204. Lag = Lagrangian(N, pP)
  205. LM = LagrangesMethod(Lag, [q1], forcelist=[(P, m*g*N.x)], frame=N)
  206. LM.form_lagranges_equations()
  207. # Linearize
  208. A, B, inp_vec = LM.linearize([q1], [q1d], A_and_B=True)
  209. assert _simplify_matrix(A) == Matrix([[0, 1], [-9.8*cos(q1)/L, 0]])
  210. assert B == Matrix([])
  211. def test_linearize_pendulum_lagrange_nonminimal():
  212. q1, q2 = dynamicsymbols('q1:3')
  213. q1d, q2d = dynamicsymbols('q1:3', level=1)
  214. L, m, t = symbols('L, m, t')
  215. g = 9.8
  216. # Compose World Frame
  217. N = ReferenceFrame('N')
  218. pN = Point('N*')
  219. pN.set_vel(N, 0)
  220. # A.x is along the pendulum
  221. theta1 = atan(q2/q1)
  222. A = N.orientnew('A', 'axis', [theta1, N.z])
  223. # Create point P, the pendulum mass
  224. P = pN.locatenew('P1', q1*N.x + q2*N.y)
  225. P.set_vel(N, P.pos_from(pN).dt(N))
  226. pP = Particle('pP', P, m)
  227. # Constraint Equations
  228. f_c = Matrix([q1**2 + q2**2 - L**2])
  229. # Calculate the lagrangian, and form the equations of motion
  230. Lag = Lagrangian(N, pP)
  231. LM = LagrangesMethod(Lag, [q1, q2], hol_coneqs=f_c, forcelist=[(P, m*g*N.x)], frame=N)
  232. LM.form_lagranges_equations()
  233. # Compose operating point
  234. op_point = {q1: L, q2: 0, q1d: 0, q2d: 0, q1d.diff(t): 0, q2d.diff(t): 0}
  235. # Solve for multiplier operating point
  236. lam_op = LM.solve_multipliers(op_point=op_point)
  237. op_point.update(lam_op)
  238. # Perform the Linearization
  239. A, B, inp_vec = LM.linearize([q2], [q2d], [q1], [q1d],
  240. op_point=op_point, A_and_B=True)
  241. assert _simplify_matrix(A) == Matrix([[0, 1], [-9.8/L, 0]])
  242. assert B == Matrix([])
  243. def test_linearize_rolling_disc_lagrange():
  244. q1, q2, q3 = q = dynamicsymbols('q1 q2 q3')
  245. q1d, q2d, q3d = qd = dynamicsymbols('q1 q2 q3', 1)
  246. r, m, g = symbols('r m g')
  247. N = ReferenceFrame('N')
  248. Y = N.orientnew('Y', 'Axis', [q1, N.z])
  249. L = Y.orientnew('L', 'Axis', [q2, Y.x])
  250. R = L.orientnew('R', 'Axis', [q3, L.y])
  251. C = Point('C')
  252. C.set_vel(N, 0)
  253. Dmc = C.locatenew('Dmc', r * L.z)
  254. Dmc.v2pt_theory(C, N, R)
  255. I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
  256. BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
  257. BodyD.potential_energy = - m * g * r * cos(q2)
  258. Lag = Lagrangian(N, BodyD)
  259. l = LagrangesMethod(Lag, q)
  260. l.form_lagranges_equations()
  261. # Linearize about steady-state upright rolling
  262. op_point = {q1: 0, q2: 0, q3: 0,
  263. q1d: 0, q2d: 0,
  264. q1d.diff(): 0, q2d.diff(): 0, q3d.diff(): 0}
  265. A = l.linearize(q_ind=q, qd_ind=qd, op_point=op_point, A_and_B=True)[0]
  266. sol = Matrix([[0, 0, 0, 1, 0, 0],
  267. [0, 0, 0, 0, 1, 0],
  268. [0, 0, 0, 0, 0, 1],
  269. [0, 0, 0, 0, -6*q3d, 0],
  270. [0, -4*g/(5*r), 0, 6*q3d/5, 0, 0],
  271. [0, 0, 0, 0, 0, 0]])
  272. assert A == sol