test_lagrange.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
  2. RigidBody, LagrangesMethod, Particle,
  3. inertia, Lagrangian)
  4. from sympy.core.function import (Derivative, Function)
  5. from sympy.core.numbers import pi
  6. from sympy.core.symbol import symbols
  7. from sympy.functions.elementary.trigonometric import (cos, sin, tan)
  8. from sympy.matrices.dense import Matrix
  9. from sympy.simplify.simplify import simplify
  10. from sympy.testing.pytest import raises
  11. def test_invalid_coordinates():
  12. # Simple pendulum, but use symbol instead of dynamicsymbol
  13. l, m, g = symbols('l m g')
  14. q = symbols('q') # Generalized coordinate
  15. N, O = ReferenceFrame('N'), Point('O')
  16. O.set_vel(N, 0)
  17. P = Particle('P', Point('P'), m)
  18. P.point.set_pos(O, l * (sin(q) * N.x - cos(q) * N.y))
  19. P.potential_energy = m * g * P.point.pos_from(O).dot(N.y)
  20. L = Lagrangian(N, P)
  21. raises(ValueError, lambda: LagrangesMethod(L, [q], bodies=P))
  22. def test_disc_on_an_incline_plane():
  23. # Disc rolling on an inclined plane
  24. # First the generalized coordinates are created. The mass center of the
  25. # disc is located from top vertex of the inclined plane by the generalized
  26. # coordinate 'y'. The orientation of the disc is defined by the angle
  27. # 'theta'. The mass of the disc is 'm' and its radius is 'R'. The length of
  28. # the inclined path is 'l', the angle of inclination is 'alpha'. 'g' is the
  29. # gravitational constant.
  30. y, theta = dynamicsymbols('y theta')
  31. yd, thetad = dynamicsymbols('y theta', 1)
  32. m, g, R, l, alpha = symbols('m g R l alpha')
  33. # Next, we create the inertial reference frame 'N'. A reference frame 'A'
  34. # is attached to the inclined plane. Finally a frame is created which is attached to the disk.
  35. N = ReferenceFrame('N')
  36. A = N.orientnew('A', 'Axis', [pi/2 - alpha, N.z])
  37. B = A.orientnew('B', 'Axis', [-theta, A.z])
  38. # Creating the disc 'D'; we create the point that represents the mass
  39. # center of the disc and set its velocity. The inertia dyadic of the disc
  40. # is created. Finally, we create the disc.
  41. Do = Point('Do')
  42. Do.set_vel(N, yd * A.x)
  43. I = m * R**2/2 * B.z | B.z
  44. D = RigidBody('D', Do, B, m, (I, Do))
  45. # To construct the Lagrangian, 'L', of the disc, we determine its kinetic
  46. # and potential energies, T and U, respectively. L is defined as the
  47. # difference between T and U.
  48. D.potential_energy = m * g * (l - y) * sin(alpha)
  49. L = Lagrangian(N, D)
  50. # We then create the list of generalized coordinates and constraint
  51. # equations. The constraint arises due to the disc rolling without slip on
  52. # on the inclined path. We then invoke the 'LagrangesMethod' class and
  53. # supply it the necessary arguments and generate the equations of motion.
  54. # The'rhs' method solves for the q_double_dots (i.e. the second derivative
  55. # with respect to time of the generalized coordinates and the lagrange
  56. # multipliers.
  57. q = [y, theta]
  58. hol_coneqs = [y - R * theta]
  59. m = LagrangesMethod(L, q, hol_coneqs=hol_coneqs)
  60. m.form_lagranges_equations()
  61. rhs = m.rhs()
  62. rhs.simplify()
  63. assert rhs[2] == 2*g*sin(alpha)/3
  64. def test_simp_pen():
  65. # This tests that the equations generated by LagrangesMethod are identical
  66. # to those obtained by hand calculations. The system under consideration is
  67. # the simple pendulum.
  68. # We begin by creating the generalized coordinates as per the requirements
  69. # of LagrangesMethod. Also we created the associate symbols
  70. # that characterize the system: 'm' is the mass of the bob, l is the length
  71. # of the massless rigid rod connecting the bob to a point O fixed in the
  72. # inertial frame.
  73. q, u = dynamicsymbols('q u')
  74. qd, ud = dynamicsymbols('q u ', 1)
  75. l, m, g = symbols('l m g')
  76. # We then create the inertial frame and a frame attached to the massless
  77. # string following which we define the inertial angular velocity of the
  78. # string.
  79. N = ReferenceFrame('N')
  80. A = N.orientnew('A', 'Axis', [q, N.z])
  81. A.set_ang_vel(N, qd * N.z)
  82. # Next, we create the point O and fix it in the inertial frame. We then
  83. # locate the point P to which the bob is attached. Its corresponding
  84. # velocity is then determined by the 'two point formula'.
  85. O = Point('O')
  86. O.set_vel(N, 0)
  87. P = O.locatenew('P', l * A.x)
  88. P.v2pt_theory(O, N, A)
  89. # The 'Particle' which represents the bob is then created and its
  90. # Lagrangian generated.
  91. Pa = Particle('Pa', P, m)
  92. Pa.potential_energy = - m * g * l * cos(q)
  93. L = Lagrangian(N, Pa)
  94. # The 'LagrangesMethod' class is invoked to obtain equations of motion.
  95. lm = LagrangesMethod(L, [q])
  96. lm.form_lagranges_equations()
  97. RHS = lm.rhs()
  98. assert RHS[1] == -g*sin(q)/l
  99. def test_nonminimal_pendulum():
  100. q1, q2 = dynamicsymbols('q1:3')
  101. q1d, q2d = dynamicsymbols('q1:3', level=1)
  102. L, m, t = symbols('L, m, t')
  103. g = 9.8
  104. # Compose World Frame
  105. N = ReferenceFrame('N')
  106. pN = Point('N*')
  107. pN.set_vel(N, 0)
  108. # Create point P, the pendulum mass
  109. P = pN.locatenew('P1', q1*N.x + q2*N.y)
  110. P.set_vel(N, P.pos_from(pN).dt(N))
  111. pP = Particle('pP', P, m)
  112. # Constraint Equations
  113. f_c = Matrix([q1**2 + q2**2 - L**2])
  114. # Calculate the lagrangian, and form the equations of motion
  115. Lag = Lagrangian(N, pP)
  116. LM = LagrangesMethod(Lag, [q1, q2], hol_coneqs=f_c,
  117. forcelist=[(P, m*g*N.x)], frame=N)
  118. LM.form_lagranges_equations()
  119. # Check solution
  120. lam1 = LM.lam_vec[0, 0]
  121. eom_sol = Matrix([[m*Derivative(q1, t, t) - 9.8*m + 2*lam1*q1],
  122. [m*Derivative(q2, t, t) + 2*lam1*q2]])
  123. assert LM.eom == eom_sol
  124. # Check multiplier solution
  125. lam_sol = Matrix([(19.6*q1 + 2*q1d**2 + 2*q2d**2)/(4*q1**2/m + 4*q2**2/m)])
  126. assert simplify(LM.solve_multipliers(sol_type='Matrix')) == simplify(lam_sol)
  127. def test_dub_pen():
  128. # The system considered is the double pendulum. Like in the
  129. # test of the simple pendulum above, we begin by creating the generalized
  130. # coordinates and the simple generalized speeds and accelerations which
  131. # will be used later. Following this we create frames and points necessary
  132. # for the kinematics. The procedure isn't explicitly explained as this is
  133. # similar to the simple pendulum. Also this is documented on the pydy.org
  134. # website.
  135. q1, q2 = dynamicsymbols('q1 q2')
  136. q1d, q2d = dynamicsymbols('q1 q2', 1)
  137. q1dd, q2dd = dynamicsymbols('q1 q2', 2)
  138. u1, u2 = dynamicsymbols('u1 u2')
  139. u1d, u2d = dynamicsymbols('u1 u2', 1)
  140. l, m, g = symbols('l m g')
  141. N = ReferenceFrame('N')
  142. A = N.orientnew('A', 'Axis', [q1, N.z])
  143. B = N.orientnew('B', 'Axis', [q2, N.z])
  144. A.set_ang_vel(N, q1d * A.z)
  145. B.set_ang_vel(N, q2d * A.z)
  146. O = Point('O')
  147. P = O.locatenew('P', l * A.x)
  148. R = P.locatenew('R', l * B.x)
  149. O.set_vel(N, 0)
  150. P.v2pt_theory(O, N, A)
  151. R.v2pt_theory(P, N, B)
  152. ParP = Particle('ParP', P, m)
  153. ParR = Particle('ParR', R, m)
  154. ParP.potential_energy = - m * g * l * cos(q1)
  155. ParR.potential_energy = - m * g * l * cos(q1) - m * g * l * cos(q2)
  156. L = Lagrangian(N, ParP, ParR)
  157. lm = LagrangesMethod(L, [q1, q2], bodies=[ParP, ParR])
  158. lm.form_lagranges_equations()
  159. assert simplify(l*m*(2*g*sin(q1) + l*sin(q1)*sin(q2)*q2dd
  160. + l*sin(q1)*cos(q2)*q2d**2 - l*sin(q2)*cos(q1)*q2d**2
  161. + l*cos(q1)*cos(q2)*q2dd + 2*l*q1dd) - lm.eom[0]) == 0
  162. assert simplify(l*m*(g*sin(q2) + l*sin(q1)*sin(q2)*q1dd
  163. - l*sin(q1)*cos(q2)*q1d**2 + l*sin(q2)*cos(q1)*q1d**2
  164. + l*cos(q1)*cos(q2)*q1dd + l*q2dd) - lm.eom[1]) == 0
  165. assert lm.bodies == [ParP, ParR]
  166. def test_rolling_disc():
  167. # Rolling Disc Example
  168. # Here the rolling disc is formed from the contact point up, removing the
  169. # need to introduce generalized speeds. Only 3 configuration and 3
  170. # speed variables are need to describe this system, along with the
  171. # disc's mass and radius, and the local gravity.
  172. q1, q2, q3 = dynamicsymbols('q1 q2 q3')
  173. q1d, q2d, q3d = dynamicsymbols('q1 q2 q3', 1)
  174. r, m, g = symbols('r m g')
  175. # The kinematics are formed by a series of simple rotations. Each simple
  176. # rotation creates a new frame, and the next rotation is defined by the new
  177. # frame's basis vectors. This example uses a 3-1-2 series of rotations, or
  178. # Z, X, Y series of rotations. Angular velocity for this is defined using
  179. # the second frame's basis (the lean frame).
  180. N = ReferenceFrame('N')
  181. Y = N.orientnew('Y', 'Axis', [q1, N.z])
  182. L = Y.orientnew('L', 'Axis', [q2, Y.x])
  183. R = L.orientnew('R', 'Axis', [q3, L.y])
  184. # This is the translational kinematics. We create a point with no velocity
  185. # in N; this is the contact point between the disc and ground. Next we form
  186. # the position vector from the contact point to the disc's center of mass.
  187. # Finally we form the velocity and acceleration of the disc.
  188. C = Point('C')
  189. C.set_vel(N, 0)
  190. Dmc = C.locatenew('Dmc', r * L.z)
  191. Dmc.v2pt_theory(C, N, R)
  192. # Forming the inertia dyadic.
  193. I = inertia(L, m/4 * r**2, m/2 * r**2, m/4 * r**2)
  194. BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
  195. # Finally we form the equations of motion, using the same steps we did
  196. # before. Supply the Lagrangian, the generalized speeds.
  197. BodyD.potential_energy = - m * g * r * cos(q2)
  198. Lag = Lagrangian(N, BodyD)
  199. q = [q1, q2, q3]
  200. q1 = Function('q1')
  201. q2 = Function('q2')
  202. q3 = Function('q3')
  203. l = LagrangesMethod(Lag, q)
  204. l.form_lagranges_equations()
  205. RHS = l.rhs()
  206. RHS.simplify()
  207. t = symbols('t')
  208. assert (l.mass_matrix[3:6] == [0, 5*m*r**2/4, 0])
  209. assert RHS[4].simplify() == (
  210. (-8*g*sin(q2(t)) + r*(5*sin(2*q2(t))*Derivative(q1(t), t) +
  211. 12*cos(q2(t))*Derivative(q3(t), t))*Derivative(q1(t), t))/(10*r))
  212. assert RHS[5] == (-5*cos(q2(t))*Derivative(q1(t), t) + 6*tan(q2(t)
  213. )*Derivative(q3(t), t) + 4*Derivative(q1(t), t)/cos(q2(t))
  214. )*Derivative(q2(t), t)