test_kane.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. from sympy import solve
  2. from sympy.core.backend import (cos, expand, Matrix, sin, symbols, tan, sqrt, S,
  3. zeros, eye)
  4. from sympy.simplify.simplify import simplify
  5. from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
  6. RigidBody, KanesMethod, inertia, Particle,
  7. dot)
  8. from sympy.testing.pytest import raises
  9. from sympy.core.backend import USE_SYMENGINE
  10. def test_invalid_coordinates():
  11. # Simple pendulum, but use symbols instead of dynamicsymbols
  12. l, m, g = symbols('l m g')
  13. q, u = symbols('q u') # Generalized coordinate
  14. kd = [q.diff(dynamicsymbols._t) - u]
  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. F = (P.point, -m * g * N.y)
  20. raises(ValueError, lambda: KanesMethod(N, [q], [u], kd, bodies=[P],
  21. forcelist=[F]))
  22. def test_one_dof():
  23. # This is for a 1 dof spring-mass-damper case.
  24. # It is described in more detail in the KanesMethod docstring.
  25. q, u = dynamicsymbols('q u')
  26. qd, ud = dynamicsymbols('q u', 1)
  27. m, c, k = symbols('m c k')
  28. N = ReferenceFrame('N')
  29. P = Point('P')
  30. P.set_vel(N, u * N.x)
  31. kd = [qd - u]
  32. FL = [(P, (-k * q - c * u) * N.x)]
  33. pa = Particle('pa', P, m)
  34. BL = [pa]
  35. KM = KanesMethod(N, [q], [u], kd)
  36. KM.kanes_equations(BL, FL)
  37. assert KM.bodies == BL
  38. assert KM.loads == FL
  39. MM = KM.mass_matrix
  40. forcing = KM.forcing
  41. rhs = MM.inv() * forcing
  42. assert expand(rhs[0]) == expand(-(q * k + u * c) / m)
  43. assert simplify(KM.rhs() -
  44. KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(2, 1)
  45. assert (KM.linearize(A_and_B=True, )[0] == Matrix([[0, 1], [-k/m, -c/m]]))
  46. def test_two_dof():
  47. # This is for a 2 d.o.f., 2 particle spring-mass-damper.
  48. # The first coordinate is the displacement of the first particle, and the
  49. # second is the relative displacement between the first and second
  50. # particles. Speeds are defined as the time derivatives of the particles.
  51. q1, q2, u1, u2 = dynamicsymbols('q1 q2 u1 u2')
  52. q1d, q2d, u1d, u2d = dynamicsymbols('q1 q2 u1 u2', 1)
  53. m, c1, c2, k1, k2 = symbols('m c1 c2 k1 k2')
  54. N = ReferenceFrame('N')
  55. P1 = Point('P1')
  56. P2 = Point('P2')
  57. P1.set_vel(N, u1 * N.x)
  58. P2.set_vel(N, (u1 + u2) * N.x)
  59. # Note we multiply the kinematic equation by an arbitrary factor
  60. # to test the implicit vs explicit kinematics attribute
  61. kd = [q1d/2 - u1/2, 2*q2d - 2*u2]
  62. # Now we create the list of forces, then assign properties to each
  63. # particle, then create a list of all particles.
  64. FL = [(P1, (-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2) * N.x), (P2, (-k2 *
  65. q2 - c2 * u2) * N.x)]
  66. pa1 = Particle('pa1', P1, m)
  67. pa2 = Particle('pa2', P2, m)
  68. BL = [pa1, pa2]
  69. # Finally we create the KanesMethod object, specify the inertial frame,
  70. # pass relevant information, and form Fr & Fr*. Then we calculate the mass
  71. # matrix and forcing terms, and finally solve for the udots.
  72. KM = KanesMethod(N, q_ind=[q1, q2], u_ind=[u1, u2], kd_eqs=kd)
  73. KM.kanes_equations(BL, FL)
  74. MM = KM.mass_matrix
  75. forcing = KM.forcing
  76. rhs = MM.inv() * forcing
  77. assert expand(rhs[0]) == expand((-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2)/m)
  78. assert expand(rhs[1]) == expand((k1 * q1 + c1 * u1 - 2 * k2 * q2 - 2 *
  79. c2 * u2) / m)
  80. # Check that the explicit form is the default and kinematic mass matrix is identity
  81. assert KM.explicit_kinematics
  82. assert KM.mass_matrix_kin == eye(2)
  83. # Check that for the implicit form the mass matrix is not identity
  84. KM.explicit_kinematics = False
  85. assert KM.mass_matrix_kin == Matrix([[S(1)/2, 0], [0, 2]])
  86. # Check that whether using implicit or explicit kinematics the RHS
  87. # equations are consistent with the matrix form
  88. for explicit_kinematics in [False, True]:
  89. KM.explicit_kinematics = explicit_kinematics
  90. assert simplify(KM.rhs() -
  91. KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(4, 1)
  92. # Make sure an error is raised if nonlinear kinematic differential
  93. # equations are supplied.
  94. kd = [q1d - u1**2, sin(q2d) - cos(u2)]
  95. raises(ValueError, lambda: KanesMethod(N, q_ind=[q1, q2],
  96. u_ind=[u1, u2], kd_eqs=kd))
  97. def test_pend():
  98. q, u = dynamicsymbols('q u')
  99. qd, ud = dynamicsymbols('q u', 1)
  100. m, l, g = symbols('m l g')
  101. N = ReferenceFrame('N')
  102. P = Point('P')
  103. P.set_vel(N, -l * u * sin(q) * N.x + l * u * cos(q) * N.y)
  104. kd = [qd - u]
  105. FL = [(P, m * g * N.x)]
  106. pa = Particle('pa', P, m)
  107. BL = [pa]
  108. KM = KanesMethod(N, [q], [u], kd)
  109. KM.kanes_equations(BL, FL)
  110. MM = KM.mass_matrix
  111. forcing = KM.forcing
  112. rhs = MM.inv() * forcing
  113. rhs.simplify()
  114. assert expand(rhs[0]) == expand(-g / l * sin(q))
  115. assert simplify(KM.rhs() -
  116. KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(2, 1)
  117. def test_rolling_disc():
  118. # Rolling Disc Example
  119. # Here the rolling disc is formed from the contact point up, removing the
  120. # need to introduce generalized speeds. Only 3 configuration and three
  121. # speed variables are need to describe this system, along with the disc's
  122. # mass and radius, and the local gravity (note that mass will drop out).
  123. q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1 q2 q3 u1 u2 u3')
  124. q1d, q2d, q3d, u1d, u2d, u3d = dynamicsymbols('q1 q2 q3 u1 u2 u3', 1)
  125. r, m, g = symbols('r m g')
  126. # The kinematics are formed by a series of simple rotations. Each simple
  127. # rotation creates a new frame, and the next rotation is defined by the new
  128. # frame's basis vectors. This example uses a 3-1-2 series of rotations, or
  129. # Z, X, Y series of rotations. Angular velocity for this is defined using
  130. # the second frame's basis (the lean frame).
  131. N = ReferenceFrame('N')
  132. Y = N.orientnew('Y', 'Axis', [q1, N.z])
  133. L = Y.orientnew('L', 'Axis', [q2, Y.x])
  134. R = L.orientnew('R', 'Axis', [q3, L.y])
  135. w_R_N_qd = R.ang_vel_in(N)
  136. R.set_ang_vel(N, u1 * L.x + u2 * L.y + u3 * L.z)
  137. # This is the translational kinematics. We create a point with no velocity
  138. # in N; this is the contact point between the disc and ground. Next we form
  139. # the position vector from the contact point to the disc's center of mass.
  140. # Finally we form the velocity and acceleration of the disc.
  141. C = Point('C')
  142. C.set_vel(N, 0)
  143. Dmc = C.locatenew('Dmc', r * L.z)
  144. Dmc.v2pt_theory(C, N, R)
  145. # This is a simple way to form the inertia dyadic.
  146. I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
  147. # Kinematic differential equations; how the generalized coordinate time
  148. # derivatives relate to generalized speeds.
  149. kd = [dot(R.ang_vel_in(N) - w_R_N_qd, uv) for uv in L]
  150. # Creation of the force list; it is the gravitational force at the mass
  151. # center of the disc. Then we create the disc by assigning a Point to the
  152. # center of mass attribute, a ReferenceFrame to the frame attribute, and mass
  153. # and inertia. Then we form the body list.
  154. ForceList = [(Dmc, - m * g * Y.z)]
  155. BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
  156. BodyList = [BodyD]
  157. # Finally we form the equations of motion, using the same steps we did
  158. # before. Specify inertial frame, supply generalized speeds, supply
  159. # kinematic differential equation dictionary, compute Fr from the force
  160. # list and Fr* from the body list, compute the mass matrix and forcing
  161. # terms, then solve for the u dots (time derivatives of the generalized
  162. # speeds).
  163. KM = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3], kd_eqs=kd)
  164. KM.kanes_equations(BodyList, ForceList)
  165. MM = KM.mass_matrix
  166. forcing = KM.forcing
  167. rhs = MM.inv() * forcing
  168. kdd = KM.kindiffdict()
  169. rhs = rhs.subs(kdd)
  170. rhs.simplify()
  171. assert rhs.expand() == Matrix([(6*u2*u3*r - u3**2*r*tan(q2) +
  172. 4*g*sin(q2))/(5*r), -2*u1*u3/3, u1*(-2*u2 + u3*tan(q2))]).expand()
  173. assert simplify(KM.rhs() -
  174. KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(6, 1)
  175. # This code tests our output vs. benchmark values. When r=g=m=1, the
  176. # critical speed (where all eigenvalues of the linearized equations are 0)
  177. # is 1 / sqrt(3) for the upright case.
  178. A = KM.linearize(A_and_B=True)[0]
  179. A_upright = A.subs({r: 1, g: 1, m: 1}).subs({q1: 0, q2: 0, q3: 0, u1: 0, u3: 0})
  180. import sympy
  181. assert sympy.sympify(A_upright.subs({u2: 1 / sqrt(3)})).eigenvals() == {S.Zero: 6}
  182. def test_aux():
  183. # Same as above, except we have 2 auxiliary speeds for the ground contact
  184. # point, which is known to be zero. In one case, we go through then
  185. # substitute the aux. speeds in at the end (they are zero, as well as their
  186. # derivative), in the other case, we use the built-in auxiliary speed part
  187. # of KanesMethod. The equations from each should be the same.
  188. q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1 q2 q3 u1 u2 u3')
  189. q1d, q2d, q3d, u1d, u2d, u3d = dynamicsymbols('q1 q2 q3 u1 u2 u3', 1)
  190. u4, u5, f1, f2 = dynamicsymbols('u4, u5, f1, f2')
  191. u4d, u5d = dynamicsymbols('u4, u5', 1)
  192. r, m, g = symbols('r m g')
  193. N = ReferenceFrame('N')
  194. Y = N.orientnew('Y', 'Axis', [q1, N.z])
  195. L = Y.orientnew('L', 'Axis', [q2, Y.x])
  196. R = L.orientnew('R', 'Axis', [q3, L.y])
  197. w_R_N_qd = R.ang_vel_in(N)
  198. R.set_ang_vel(N, u1 * L.x + u2 * L.y + u3 * L.z)
  199. C = Point('C')
  200. C.set_vel(N, u4 * L.x + u5 * (Y.z ^ L.x))
  201. Dmc = C.locatenew('Dmc', r * L.z)
  202. Dmc.v2pt_theory(C, N, R)
  203. Dmc.a2pt_theory(C, N, R)
  204. I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
  205. kd = [dot(R.ang_vel_in(N) - w_R_N_qd, uv) for uv in L]
  206. ForceList = [(Dmc, - m * g * Y.z), (C, f1 * L.x + f2 * (Y.z ^ L.x))]
  207. BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
  208. BodyList = [BodyD]
  209. KM = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3, u4, u5],
  210. kd_eqs=kd)
  211. (fr, frstar) = KM.kanes_equations(BodyList, ForceList)
  212. fr = fr.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
  213. frstar = frstar.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
  214. KM2 = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3], kd_eqs=kd,
  215. u_auxiliary=[u4, u5])
  216. (fr2, frstar2) = KM2.kanes_equations(BodyList, ForceList)
  217. fr2 = fr2.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
  218. frstar2 = frstar2.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
  219. frstar.simplify()
  220. frstar2.simplify()
  221. assert (fr - fr2).expand() == Matrix([0, 0, 0, 0, 0])
  222. assert (frstar - frstar2).expand() == Matrix([0, 0, 0, 0, 0])
  223. def test_parallel_axis():
  224. # This is for a 2 dof inverted pendulum on a cart.
  225. # This tests the parallel axis code in KanesMethod. The inertia of the
  226. # pendulum is defined about the hinge, not about the center of mass.
  227. # Defining the constants and knowns of the system
  228. gravity = symbols('g')
  229. k, ls = symbols('k ls')
  230. a, mA, mC = symbols('a mA mC')
  231. F = dynamicsymbols('F')
  232. Ix, Iy, Iz = symbols('Ix Iy Iz')
  233. # Declaring the Generalized coordinates and speeds
  234. q1, q2 = dynamicsymbols('q1 q2')
  235. q1d, q2d = dynamicsymbols('q1 q2', 1)
  236. u1, u2 = dynamicsymbols('u1 u2')
  237. u1d, u2d = dynamicsymbols('u1 u2', 1)
  238. # Creating reference frames
  239. N = ReferenceFrame('N')
  240. A = ReferenceFrame('A')
  241. A.orient(N, 'Axis', [-q2, N.z])
  242. A.set_ang_vel(N, -u2 * N.z)
  243. # Origin of Newtonian reference frame
  244. O = Point('O')
  245. # Creating and Locating the positions of the cart, C, and the
  246. # center of mass of the pendulum, A
  247. C = O.locatenew('C', q1 * N.x)
  248. Ao = C.locatenew('Ao', a * A.y)
  249. # Defining velocities of the points
  250. O.set_vel(N, 0)
  251. C.set_vel(N, u1 * N.x)
  252. Ao.v2pt_theory(C, N, A)
  253. Cart = Particle('Cart', C, mC)
  254. Pendulum = RigidBody('Pendulum', Ao, A, mA, (inertia(A, Ix, Iy, Iz), C))
  255. # kinematical differential equations
  256. kindiffs = [q1d - u1, q2d - u2]
  257. bodyList = [Cart, Pendulum]
  258. forceList = [(Ao, -N.y * gravity * mA),
  259. (C, -N.y * gravity * mC),
  260. (C, -N.x * k * (q1 - ls)),
  261. (C, N.x * F)]
  262. km = KanesMethod(N, [q1, q2], [u1, u2], kindiffs)
  263. (fr, frstar) = km.kanes_equations(bodyList, forceList)
  264. mm = km.mass_matrix_full
  265. assert mm[3, 3] == Iz
  266. def test_input_format():
  267. # 1 dof problem from test_one_dof
  268. q, u = dynamicsymbols('q u')
  269. qd, ud = dynamicsymbols('q u', 1)
  270. m, c, k = symbols('m c k')
  271. N = ReferenceFrame('N')
  272. P = Point('P')
  273. P.set_vel(N, u * N.x)
  274. kd = [qd - u]
  275. FL = [(P, (-k * q - c * u) * N.x)]
  276. pa = Particle('pa', P, m)
  277. BL = [pa]
  278. KM = KanesMethod(N, [q], [u], kd)
  279. # test for input format kane.kanes_equations((body1, body2, particle1))
  280. assert KM.kanes_equations(BL)[0] == Matrix([0])
  281. # test for input format kane.kanes_equations(bodies=(body1, body 2), loads=(load1,load2))
  282. assert KM.kanes_equations(bodies=BL, loads=None)[0] == Matrix([0])
  283. # test for input format kane.kanes_equations(bodies=(body1, body 2), loads=None)
  284. assert KM.kanes_equations(BL, loads=None)[0] == Matrix([0])
  285. # test for input format kane.kanes_equations(bodies=(body1, body 2))
  286. assert KM.kanes_equations(BL)[0] == Matrix([0])
  287. # test for input format kane.kanes_equations(bodies=(body1, body2), loads=[])
  288. assert KM.kanes_equations(BL, [])[0] == Matrix([0])
  289. # test for error raised when a wrong force list (in this case a string) is provided
  290. raises(ValueError, lambda: KM._form_fr('bad input'))
  291. # 1 dof problem from test_one_dof with FL & BL in instance
  292. KM = KanesMethod(N, [q], [u], kd, bodies=BL, forcelist=FL)
  293. assert KM.kanes_equations()[0] == Matrix([-c*u - k*q])
  294. # 2 dof problem from test_two_dof
  295. q1, q2, u1, u2 = dynamicsymbols('q1 q2 u1 u2')
  296. q1d, q2d, u1d, u2d = dynamicsymbols('q1 q2 u1 u2', 1)
  297. m, c1, c2, k1, k2 = symbols('m c1 c2 k1 k2')
  298. N = ReferenceFrame('N')
  299. P1 = Point('P1')
  300. P2 = Point('P2')
  301. P1.set_vel(N, u1 * N.x)
  302. P2.set_vel(N, (u1 + u2) * N.x)
  303. kd = [q1d - u1, q2d - u2]
  304. FL = ((P1, (-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2) * N.x), (P2, (-k2 *
  305. q2 - c2 * u2) * N.x))
  306. pa1 = Particle('pa1', P1, m)
  307. pa2 = Particle('pa2', P2, m)
  308. BL = (pa1, pa2)
  309. KM = KanesMethod(N, q_ind=[q1, q2], u_ind=[u1, u2], kd_eqs=kd)
  310. # test for input format
  311. # kane.kanes_equations((body1, body2), (load1, load2))
  312. KM.kanes_equations(BL, FL)
  313. MM = KM.mass_matrix
  314. forcing = KM.forcing
  315. rhs = MM.inv() * forcing
  316. assert expand(rhs[0]) == expand((-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2)/m)
  317. assert expand(rhs[1]) == expand((k1 * q1 + c1 * u1 - 2 * k2 * q2 - 2 *
  318. c2 * u2) / m)
  319. def test_implicit_kinematics():
  320. # Test that implicit kinematics can handle complicated
  321. # equations that explicit form struggles with
  322. # See https://github.com/sympy/sympy/issues/22626
  323. # Inertial frame
  324. NED = ReferenceFrame('NED')
  325. NED_o = Point('NED_o')
  326. NED_o.set_vel(NED, 0)
  327. # body frame
  328. q_att = dynamicsymbols('lambda_0:4', real=True)
  329. B = NED.orientnew('B', 'Quaternion', q_att)
  330. # Generalized coordinates
  331. q_pos = dynamicsymbols('B_x:z')
  332. B_cm = NED_o.locatenew('B_cm', q_pos[0]*B.x + q_pos[1]*B.y + q_pos[2]*B.z)
  333. q_ind = q_att[1:] + q_pos
  334. q_dep = [q_att[0]]
  335. kinematic_eqs = []
  336. # Generalized velocities
  337. B_ang_vel = B.ang_vel_in(NED)
  338. P, Q, R = dynamicsymbols('P Q R')
  339. B.set_ang_vel(NED, P*B.x + Q*B.y + R*B.z)
  340. B_ang_vel_kd = (B.ang_vel_in(NED) - B_ang_vel).simplify()
  341. # Equating the two gives us the kinematic equation
  342. kinematic_eqs += [
  343. B_ang_vel_kd & B.x,
  344. B_ang_vel_kd & B.y,
  345. B_ang_vel_kd & B.z
  346. ]
  347. B_cm_vel = B_cm.vel(NED)
  348. U, V, W = dynamicsymbols('U V W')
  349. B_cm.set_vel(NED, U*B.x + V*B.y + W*B.z)
  350. # Compute the velocity of the point using the two methods
  351. B_ref_vel_kd = (B_cm.vel(NED) - B_cm_vel)
  352. # taking dot product with unit vectors to get kinematic equations
  353. # relating body coordinates and velocities
  354. # Note, there is a choice to dot with NED.xyz here. That makes
  355. # the implicit form have some bigger terms but is still fine, the
  356. # explicit form still struggles though
  357. kinematic_eqs += [
  358. B_ref_vel_kd & B.x,
  359. B_ref_vel_kd & B.y,
  360. B_ref_vel_kd & B.z,
  361. ]
  362. u_ind = [U, V, W, P, Q, R]
  363. # constraints
  364. q_att_vec = Matrix(q_att)
  365. config_cons = [(q_att_vec.T*q_att_vec)[0] - 1] #unit norm
  366. kinematic_eqs = kinematic_eqs + [(q_att_vec.T * q_att_vec.diff())[0]]
  367. try:
  368. KM = KanesMethod(NED, q_ind, u_ind,
  369. q_dependent= q_dep,
  370. kd_eqs = kinematic_eqs,
  371. configuration_constraints = config_cons,
  372. velocity_constraints= [],
  373. u_dependent= [], #no dependent speeds
  374. u_auxiliary = [], # No auxiliary speeds
  375. explicit_kinematics = False # implicit kinematics
  376. )
  377. except Exception as e:
  378. # symengine struggles with these kinematic equations
  379. if USE_SYMENGINE and 'Matrix is rank deficient' in str(e):
  380. return
  381. else:
  382. raise e
  383. # mass and inertia dyadic relative to CM
  384. M_B = symbols('M_B')
  385. J_B = inertia(B, *[S(f'J_B_{ax}')*(1 if ax[0] == ax[1] else -1)
  386. for ax in ['xx', 'yy', 'zz', 'xy', 'yz', 'xz']])
  387. J_B = J_B.subs({S('J_B_xy'): 0, S('J_B_yz'): 0})
  388. RB = RigidBody('RB', B_cm, B, M_B, (J_B, B_cm))
  389. rigid_bodies = [RB]
  390. # Forces
  391. force_list = [
  392. #gravity pointing down
  393. (RB.masscenter, RB.mass*S('g')*NED.z),
  394. #generic forces and torques in body frame(inputs)
  395. (RB.frame, dynamicsymbols('T_z')*B.z),
  396. (RB.masscenter, dynamicsymbols('F_z')*B.z)
  397. ]
  398. KM.kanes_equations(rigid_bodies, force_list)
  399. # Expecting implicit form to be less than 5% of the flops
  400. n_ops_implicit = sum(
  401. [x.count_ops() for x in KM.forcing_full] +
  402. [x.count_ops() for x in KM.mass_matrix_full]
  403. )
  404. # Save implicit kinematic matrices to use later
  405. mass_matrix_kin_implicit = KM.mass_matrix_kin
  406. forcing_kin_implicit = KM.forcing_kin
  407. KM.explicit_kinematics = True
  408. n_ops_explicit = sum(
  409. [x.count_ops() for x in KM.forcing_full] +
  410. [x.count_ops() for x in KM.mass_matrix_full]
  411. )
  412. forcing_kin_explicit = KM.forcing_kin
  413. assert n_ops_implicit / n_ops_explicit < .05
  414. # Ideally we would check that implicit and explicit equations give the same result as done in test_one_dof
  415. # But the whole raison-d'etre of the implicit equations is to deal with problems such
  416. # as this one where the explicit form is too complicated to handle, especially the angular part
  417. # (i.e. tests would be too slow)
  418. # Instead, we check that the kinematic equations are correct using more fundamental tests:
  419. #
  420. # (1) that we recover the kinematic equations we have provided
  421. assert (mass_matrix_kin_implicit * KM.q.diff() - forcing_kin_implicit) == Matrix(kinematic_eqs)
  422. # (2) that rate of quaternions matches what 'textbook' solutions give
  423. # Note that we just use the explicit kinematics for the linear velocities
  424. # as they are not as complicated as the angular ones
  425. qdot_candidate = forcing_kin_explicit
  426. quat_dot_textbook = Matrix([
  427. [0, -P, -Q, -R],
  428. [P, 0, R, -Q],
  429. [Q, -R, 0, P],
  430. [R, Q, -P, 0],
  431. ]) * q_att_vec / 2
  432. # Again, if we don't use this "textbook" solution
  433. # sympy will struggle to deal with the terms related to quaternion rates
  434. # due to the number of operations involved
  435. qdot_candidate[-1] = quat_dot_textbook[0] # lambda_0, note the [-1] as sympy's Kane puts the dependent coordinate last
  436. qdot_candidate[0] = quat_dot_textbook[1] # lambda_1
  437. qdot_candidate[1] = quat_dot_textbook[2] # lambda_2
  438. qdot_candidate[2] = quat_dot_textbook[3] # lambda_3
  439. # sub the config constraint in the candidate solution and compare to the implicit rhs
  440. lambda_0_sol = solve(config_cons[0], q_att_vec[0])[1]
  441. lhs_candidate = simplify(mass_matrix_kin_implicit * qdot_candidate).subs({q_att_vec[0]: lambda_0_sol})
  442. assert lhs_candidate == forcing_kin_implicit