test_kane2.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. from sympy.core.backend import cos, Matrix, sin, zeros, tan, pi, symbols
  2. from sympy.simplify.simplify import simplify
  3. from sympy.simplify.trigsimp import trigsimp
  4. from sympy.solvers.solvers import solve
  5. from sympy.physics.mechanics import (cross, dot, dynamicsymbols,
  6. find_dynamicsymbols, KanesMethod, inertia,
  7. inertia_of_point_mass, Point,
  8. ReferenceFrame, RigidBody)
  9. def test_aux_dep():
  10. # This test is about rolling disc dynamics, comparing the results found
  11. # with KanesMethod to those found when deriving the equations "manually"
  12. # with SymPy.
  13. # The terms Fr, Fr*, and Fr*_steady are all compared between the two
  14. # methods. Here, Fr*_steady refers to the generalized inertia forces for an
  15. # equilibrium configuration.
  16. # Note: comparing to the test of test_rolling_disc() in test_kane.py, this
  17. # test also tests auxiliary speeds and configuration and motion constraints
  18. #, seen in the generalized dependent coordinates q[3], and depend speeds
  19. # u[3], u[4] and u[5].
  20. # First, manual derivation of Fr, Fr_star, Fr_star_steady.
  21. # Symbols for time and constant parameters.
  22. # Symbols for contact forces: Fx, Fy, Fz.
  23. t, r, m, g, I, J = symbols('t r m g I J')
  24. Fx, Fy, Fz = symbols('Fx Fy Fz')
  25. # Configuration variables and their time derivatives:
  26. # q[0] -- yaw
  27. # q[1] -- lean
  28. # q[2] -- spin
  29. # q[3] -- dot(-r*B.z, A.z) -- distance from ground plane to disc center in
  30. # A.z direction
  31. # Generalized speeds and their time derivatives:
  32. # u[0] -- disc angular velocity component, disc fixed x direction
  33. # u[1] -- disc angular velocity component, disc fixed y direction
  34. # u[2] -- disc angular velocity component, disc fixed z direction
  35. # u[3] -- disc velocity component, A.x direction
  36. # u[4] -- disc velocity component, A.y direction
  37. # u[5] -- disc velocity component, A.z direction
  38. # Auxiliary generalized speeds:
  39. # ua[0] -- contact point auxiliary generalized speed, A.x direction
  40. # ua[1] -- contact point auxiliary generalized speed, A.y direction
  41. # ua[2] -- contact point auxiliary generalized speed, A.z direction
  42. q = dynamicsymbols('q:4')
  43. qd = [qi.diff(t) for qi in q]
  44. u = dynamicsymbols('u:6')
  45. ud = [ui.diff(t) for ui in u]
  46. ud_zero = dict(zip(ud, [0.]*len(ud)))
  47. ua = dynamicsymbols('ua:3')
  48. ua_zero = dict(zip(ua, [0.]*len(ua))) # noqa:F841
  49. # Reference frames:
  50. # Yaw intermediate frame: A.
  51. # Lean intermediate frame: B.
  52. # Disc fixed frame: C.
  53. N = ReferenceFrame('N')
  54. A = N.orientnew('A', 'Axis', [q[0], N.z])
  55. B = A.orientnew('B', 'Axis', [q[1], A.x])
  56. C = B.orientnew('C', 'Axis', [q[2], B.y])
  57. # Angular velocity and angular acceleration of disc fixed frame
  58. # u[0], u[1] and u[2] are generalized independent speeds.
  59. C.set_ang_vel(N, u[0]*B.x + u[1]*B.y + u[2]*B.z)
  60. C.set_ang_acc(N, C.ang_vel_in(N).diff(t, B)
  61. + cross(B.ang_vel_in(N), C.ang_vel_in(N)))
  62. # Velocity and acceleration of points:
  63. # Disc-ground contact point: P.
  64. # Center of disc: O, defined from point P with depend coordinate: q[3]
  65. # u[3], u[4] and u[5] are generalized dependent speeds.
  66. P = Point('P')
  67. P.set_vel(N, ua[0]*A.x + ua[1]*A.y + ua[2]*A.z)
  68. O = P.locatenew('O', q[3]*A.z + r*sin(q[1])*A.y)
  69. O.set_vel(N, u[3]*A.x + u[4]*A.y + u[5]*A.z)
  70. O.set_acc(N, O.vel(N).diff(t, A) + cross(A.ang_vel_in(N), O.vel(N)))
  71. # Kinematic differential equations:
  72. # Two equalities: one is w_c_n_qd = C.ang_vel_in(N) in three coordinates
  73. # directions of B, for qd0, qd1 and qd2.
  74. # the other is v_o_n_qd = O.vel(N) in A.z direction for qd3.
  75. # Then, solve for dq/dt's in terms of u's: qd_kd.
  76. w_c_n_qd = qd[0]*A.z + qd[1]*B.x + qd[2]*B.y
  77. v_o_n_qd = O.pos_from(P).diff(t, A) + cross(A.ang_vel_in(N), O.pos_from(P))
  78. kindiffs = Matrix([dot(w_c_n_qd - C.ang_vel_in(N), uv) for uv in B] +
  79. [dot(v_o_n_qd - O.vel(N), A.z)])
  80. qd_kd = solve(kindiffs, qd) # noqa:F841
  81. # Values of generalized speeds during a steady turn for later substitution
  82. # into the Fr_star_steady.
  83. steady_conditions = solve(kindiffs.subs({qd[1] : 0, qd[3] : 0}), u)
  84. steady_conditions.update({qd[1] : 0, qd[3] : 0})
  85. # Partial angular velocities and velocities.
  86. partial_w_C = [C.ang_vel_in(N).diff(ui, N) for ui in u + ua]
  87. partial_v_O = [O.vel(N).diff(ui, N) for ui in u + ua]
  88. partial_v_P = [P.vel(N).diff(ui, N) for ui in u + ua]
  89. # Configuration constraint: f_c, the projection of radius r in A.z direction
  90. # is q[3].
  91. # Velocity constraints: f_v, for u3, u4 and u5.
  92. # Acceleration constraints: f_a.
  93. f_c = Matrix([dot(-r*B.z, A.z) - q[3]])
  94. f_v = Matrix([dot(O.vel(N) - (P.vel(N) + cross(C.ang_vel_in(N),
  95. O.pos_from(P))), ai).expand() for ai in A])
  96. v_o_n = cross(C.ang_vel_in(N), O.pos_from(P))
  97. a_o_n = v_o_n.diff(t, A) + cross(A.ang_vel_in(N), v_o_n)
  98. f_a = Matrix([dot(O.acc(N) - a_o_n, ai) for ai in A]) # noqa:F841
  99. # Solve for constraint equations in the form of
  100. # u_dependent = A_rs * [u_i; u_aux].
  101. # First, obtain constraint coefficient matrix: M_v * [u; ua] = 0;
  102. # Second, taking u[0], u[1], u[2] as independent,
  103. # taking u[3], u[4], u[5] as dependent,
  104. # rearranging the matrix of M_v to be A_rs for u_dependent.
  105. # Third, u_aux ==0 for u_dep, and resulting dictionary of u_dep_dict.
  106. M_v = zeros(3, 9)
  107. for i in range(3):
  108. for j, ui in enumerate(u + ua):
  109. M_v[i, j] = f_v[i].diff(ui)
  110. M_v_i = M_v[:, :3]
  111. M_v_d = M_v[:, 3:6]
  112. M_v_aux = M_v[:, 6:]
  113. M_v_i_aux = M_v_i.row_join(M_v_aux)
  114. A_rs = - M_v_d.inv() * M_v_i_aux
  115. u_dep = A_rs[:, :3] * Matrix(u[:3])
  116. u_dep_dict = dict(zip(u[3:], u_dep))
  117. # Active forces: F_O acting on point O; F_P acting on point P.
  118. # Generalized active forces (unconstrained): Fr_u = F_point * pv_point.
  119. F_O = m*g*A.z
  120. F_P = Fx * A.x + Fy * A.y + Fz * A.z
  121. Fr_u = Matrix([dot(F_O, pv_o) + dot(F_P, pv_p) for pv_o, pv_p in
  122. zip(partial_v_O, partial_v_P)])
  123. # Inertia force: R_star_O.
  124. # Inertia of disc: I_C_O, where J is a inertia component about principal axis.
  125. # Inertia torque: T_star_C.
  126. # Generalized inertia forces (unconstrained): Fr_star_u.
  127. R_star_O = -m*O.acc(N)
  128. I_C_O = inertia(B, I, J, I)
  129. T_star_C = -(dot(I_C_O, C.ang_acc_in(N)) \
  130. + cross(C.ang_vel_in(N), dot(I_C_O, C.ang_vel_in(N))))
  131. Fr_star_u = Matrix([dot(R_star_O, pv) + dot(T_star_C, pav) for pv, pav in
  132. zip(partial_v_O, partial_w_C)])
  133. # Form nonholonomic Fr: Fr_c, and nonholonomic Fr_star: Fr_star_c.
  134. # Also, nonholonomic Fr_star in steady turning condition: Fr_star_steady.
  135. Fr_c = Fr_u[:3, :].col_join(Fr_u[6:, :]) + A_rs.T * Fr_u[3:6, :]
  136. Fr_star_c = Fr_star_u[:3, :].col_join(Fr_star_u[6:, :])\
  137. + A_rs.T * Fr_star_u[3:6, :]
  138. Fr_star_steady = Fr_star_c.subs(ud_zero).subs(u_dep_dict)\
  139. .subs(steady_conditions).subs({q[3]: -r*cos(q[1])}).expand()
  140. # Second, using KaneMethod in mechanics for fr, frstar and frstar_steady.
  141. # Rigid Bodies: disc, with inertia I_C_O.
  142. iner_tuple = (I_C_O, O)
  143. disc = RigidBody('disc', O, C, m, iner_tuple)
  144. bodyList = [disc]
  145. # Generalized forces: Gravity: F_o; Auxiliary forces: F_p.
  146. F_o = (O, F_O)
  147. F_p = (P, F_P)
  148. forceList = [F_o, F_p]
  149. # KanesMethod.
  150. kane = KanesMethod(
  151. N, q_ind= q[:3], u_ind= u[:3], kd_eqs=kindiffs,
  152. q_dependent=q[3:], configuration_constraints = f_c,
  153. u_dependent=u[3:], velocity_constraints= f_v,
  154. u_auxiliary=ua
  155. )
  156. # fr, frstar, frstar_steady and kdd(kinematic differential equations).
  157. (fr, frstar)= kane.kanes_equations(bodyList, forceList)
  158. frstar_steady = frstar.subs(ud_zero).subs(u_dep_dict).subs(steady_conditions)\
  159. .subs({q[3]: -r*cos(q[1])}).expand()
  160. kdd = kane.kindiffdict()
  161. assert Matrix(Fr_c).expand() == fr.expand()
  162. assert Matrix(Fr_star_c.subs(kdd)).expand() == frstar.expand()
  163. assert (simplify(Matrix(Fr_star_steady).expand()) ==
  164. simplify(frstar_steady.expand()))
  165. syms_in_forcing = find_dynamicsymbols(kane.forcing)
  166. for qdi in qd:
  167. assert qdi not in syms_in_forcing
  168. def test_non_central_inertia():
  169. # This tests that the calculation of Fr* does not depend the point
  170. # about which the inertia of a rigid body is defined. This test solves
  171. # exercises 8.12, 8.17 from Kane 1985.
  172. # Declare symbols
  173. q1, q2, q3 = dynamicsymbols('q1:4')
  174. q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
  175. u1, u2, u3, u4, u5 = dynamicsymbols('u1:6')
  176. u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
  177. a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
  178. Q1, Q2, Q3 = symbols('Q1, Q2 Q3')
  179. IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
  180. # Reference Frames
  181. F = ReferenceFrame('F')
  182. P = F.orientnew('P', 'axis', [-theta, F.y])
  183. A = P.orientnew('A', 'axis', [q1, P.x])
  184. A.set_ang_vel(F, u1*A.x + u3*A.z)
  185. # define frames for wheels
  186. B = A.orientnew('B', 'axis', [q2, A.z])
  187. C = A.orientnew('C', 'axis', [q3, A.z])
  188. B.set_ang_vel(A, u4 * A.z)
  189. C.set_ang_vel(A, u5 * A.z)
  190. # define points D, S*, Q on frame A and their velocities
  191. pD = Point('D')
  192. pD.set_vel(A, 0)
  193. # u3 will not change v_D_F since wheels are still assumed to roll without slip.
  194. pD.set_vel(F, u2 * A.y)
  195. pS_star = pD.locatenew('S*', e*A.y)
  196. pQ = pD.locatenew('Q', f*A.y - R*A.x)
  197. for p in [pS_star, pQ]:
  198. p.v2pt_theory(pD, F, A)
  199. # masscenters of bodies A, B, C
  200. pA_star = pD.locatenew('A*', a*A.y)
  201. pB_star = pD.locatenew('B*', b*A.z)
  202. pC_star = pD.locatenew('C*', -b*A.z)
  203. for p in [pA_star, pB_star, pC_star]:
  204. p.v2pt_theory(pD, F, A)
  205. # points of B, C touching the plane P
  206. pB_hat = pB_star.locatenew('B^', -R*A.x)
  207. pC_hat = pC_star.locatenew('C^', -R*A.x)
  208. pB_hat.v2pt_theory(pB_star, F, B)
  209. pC_hat.v2pt_theory(pC_star, F, C)
  210. # the velocities of B^, C^ are zero since B, C are assumed to roll without slip
  211. kde = [q1d - u1, q2d - u4, q3d - u5]
  212. vc = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
  213. # inertias of bodies A, B, C
  214. # IA22, IA23, IA33 are not specified in the problem statement, but are
  215. # necessary to define an inertia object. Although the values of
  216. # IA22, IA23, IA33 are not known in terms of the variables given in the
  217. # problem statement, they do not appear in the general inertia terms.
  218. inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
  219. inertia_B = inertia(B, K, K, J)
  220. inertia_C = inertia(C, K, K, J)
  221. # define the rigid bodies A, B, C
  222. rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
  223. rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
  224. rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
  225. km = KanesMethod(F, q_ind=[q1, q2, q3], u_ind=[u1, u2], kd_eqs=kde,
  226. u_dependent=[u4, u5], velocity_constraints=vc,
  227. u_auxiliary=[u3])
  228. forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
  229. bodies = [rbA, rbB, rbC]
  230. fr, fr_star = km.kanes_equations(bodies, forces)
  231. vc_map = solve(vc, [u4, u5])
  232. # KanesMethod returns the negative of Fr, Fr* as defined in Kane1985.
  233. fr_star_expected = Matrix([
  234. -(IA + 2*J*b**2/R**2 + 2*K +
  235. mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
  236. -(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
  237. 0])
  238. t = trigsimp(fr_star.subs(vc_map).subs({u3: 0})).doit().expand()
  239. assert ((fr_star_expected - t).expand() == zeros(3, 1))
  240. # define inertias of rigid bodies A, B, C about point D
  241. # I_S/O = I_S/S* + I_S*/O
  242. bodies2 = []
  243. for rb, I_star in zip([rbA, rbB, rbC], [inertia_A, inertia_B, inertia_C]):
  244. I = I_star + inertia_of_point_mass(rb.mass,
  245. rb.masscenter.pos_from(pD),
  246. rb.frame)
  247. bodies2.append(RigidBody('', rb.masscenter, rb.frame, rb.mass,
  248. (I, pD)))
  249. fr2, fr_star2 = km.kanes_equations(bodies2, forces)
  250. t = trigsimp(fr_star2.subs(vc_map).subs({u3: 0})).doit()
  251. assert (fr_star_expected - t).expand() == zeros(3, 1)
  252. def test_sub_qdot():
  253. # This test solves exercises 8.12, 8.17 from Kane 1985 and defines
  254. # some velocities in terms of q, qdot.
  255. ## --- Declare symbols ---
  256. q1, q2, q3 = dynamicsymbols('q1:4')
  257. q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
  258. u1, u2, u3 = dynamicsymbols('u1:4')
  259. u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
  260. a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
  261. IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
  262. Q1, Q2, Q3 = symbols('Q1 Q2 Q3')
  263. # --- Reference Frames ---
  264. F = ReferenceFrame('F')
  265. P = F.orientnew('P', 'axis', [-theta, F.y])
  266. A = P.orientnew('A', 'axis', [q1, P.x])
  267. A.set_ang_vel(F, u1*A.x + u3*A.z)
  268. # define frames for wheels
  269. B = A.orientnew('B', 'axis', [q2, A.z])
  270. C = A.orientnew('C', 'axis', [q3, A.z])
  271. ## --- define points D, S*, Q on frame A and their velocities ---
  272. pD = Point('D')
  273. pD.set_vel(A, 0)
  274. # u3 will not change v_D_F since wheels are still assumed to roll w/o slip
  275. pD.set_vel(F, u2 * A.y)
  276. pS_star = pD.locatenew('S*', e*A.y)
  277. pQ = pD.locatenew('Q', f*A.y - R*A.x)
  278. # masscenters of bodies A, B, C
  279. pA_star = pD.locatenew('A*', a*A.y)
  280. pB_star = pD.locatenew('B*', b*A.z)
  281. pC_star = pD.locatenew('C*', -b*A.z)
  282. for p in [pS_star, pQ, pA_star, pB_star, pC_star]:
  283. p.v2pt_theory(pD, F, A)
  284. # points of B, C touching the plane P
  285. pB_hat = pB_star.locatenew('B^', -R*A.x)
  286. pC_hat = pC_star.locatenew('C^', -R*A.x)
  287. pB_hat.v2pt_theory(pB_star, F, B)
  288. pC_hat.v2pt_theory(pC_star, F, C)
  289. # --- relate qdot, u ---
  290. # the velocities of B^, C^ are zero since B, C are assumed to roll w/o slip
  291. kde = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
  292. kde += [u1 - q1d]
  293. kde_map = solve(kde, [q1d, q2d, q3d])
  294. for k, v in list(kde_map.items()):
  295. kde_map[k.diff(t)] = v.diff(t)
  296. # inertias of bodies A, B, C
  297. # IA22, IA23, IA33 are not specified in the problem statement, but are
  298. # necessary to define an inertia object. Although the values of
  299. # IA22, IA23, IA33 are not known in terms of the variables given in the
  300. # problem statement, they do not appear in the general inertia terms.
  301. inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
  302. inertia_B = inertia(B, K, K, J)
  303. inertia_C = inertia(C, K, K, J)
  304. # define the rigid bodies A, B, C
  305. rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
  306. rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
  307. rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
  308. ## --- use kanes method ---
  309. km = KanesMethod(F, [q1, q2, q3], [u1, u2], kd_eqs=kde, u_auxiliary=[u3])
  310. forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
  311. bodies = [rbA, rbB, rbC]
  312. # Q2 = -u_prime * u2 * Q1 / sqrt(u2**2 + f**2 * u1**2)
  313. # -u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2) = R / Q1 * Q2
  314. fr_expected = Matrix([
  315. f*Q3 + M*g*e*sin(theta)*cos(q1),
  316. Q2 + M*g*sin(theta)*sin(q1),
  317. e*M*g*cos(theta) - Q1*f - Q2*R])
  318. #Q1 * (f - u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2)))])
  319. fr_star_expected = Matrix([
  320. -(IA + 2*J*b**2/R**2 + 2*K +
  321. mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
  322. -(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
  323. 0])
  324. fr, fr_star = km.kanes_equations(bodies, forces)
  325. assert (fr.expand() == fr_expected.expand())
  326. assert ((fr_star_expected - trigsimp(fr_star)).expand() == zeros(3, 1))
  327. def test_sub_qdot2():
  328. # This test solves exercises 8.3 from Kane 1985 and defines
  329. # all velocities in terms of q, qdot. We check that the generalized active
  330. # forces are correctly computed if u terms are only defined in the
  331. # kinematic differential equations.
  332. #
  333. # This functionality was added in PR 8948. Without qdot/u substitution, the
  334. # KanesMethod constructor will fail during the constraint initialization as
  335. # the B matrix will be poorly formed and inversion of the dependent part
  336. # will fail.
  337. g, m, Px, Py, Pz, R, t = symbols('g m Px Py Pz R t')
  338. q = dynamicsymbols('q:5')
  339. qd = dynamicsymbols('q:5', level=1)
  340. u = dynamicsymbols('u:5')
  341. ## Define inertial, intermediate, and rigid body reference frames
  342. A = ReferenceFrame('A')
  343. B_prime = A.orientnew('B_prime', 'Axis', [q[0], A.z])
  344. B = B_prime.orientnew('B', 'Axis', [pi/2 - q[1], B_prime.x])
  345. C = B.orientnew('C', 'Axis', [q[2], B.z])
  346. ## Define points of interest and their velocities
  347. pO = Point('O')
  348. pO.set_vel(A, 0)
  349. # R is the point in plane H that comes into contact with disk C.
  350. pR = pO.locatenew('R', q[3]*A.x + q[4]*A.y)
  351. pR.set_vel(A, pR.pos_from(pO).diff(t, A))
  352. pR.set_vel(B, 0)
  353. # C^ is the point in disk C that comes into contact with plane H.
  354. pC_hat = pR.locatenew('C^', 0)
  355. pC_hat.set_vel(C, 0)
  356. # C* is the point at the center of disk C.
  357. pCs = pC_hat.locatenew('C*', R*B.y)
  358. pCs.set_vel(C, 0)
  359. pCs.set_vel(B, 0)
  360. # calculate velocites of points C* and C^ in frame A
  361. pCs.v2pt_theory(pR, A, B) # points C* and R are fixed in frame B
  362. pC_hat.v2pt_theory(pCs, A, C) # points C* and C^ are fixed in frame C
  363. ## Define forces on each point of the system
  364. R_C_hat = Px*A.x + Py*A.y + Pz*A.z
  365. R_Cs = -m*g*A.z
  366. forces = [(pC_hat, R_C_hat), (pCs, R_Cs)]
  367. ## Define kinematic differential equations
  368. # let ui = omega_C_A & bi (i = 1, 2, 3)
  369. # u4 = qd4, u5 = qd5
  370. u_expr = [C.ang_vel_in(A) & uv for uv in B]
  371. u_expr += qd[3:]
  372. kde = [ui - e for ui, e in zip(u, u_expr)]
  373. km1 = KanesMethod(A, q, u, kde)
  374. fr1, _ = km1.kanes_equations([], forces)
  375. ## Calculate generalized active forces if we impose the condition that the
  376. # disk C is rolling without slipping
  377. u_indep = u[:3]
  378. u_dep = list(set(u) - set(u_indep))
  379. vc = [pC_hat.vel(A) & uv for uv in [A.x, A.y]]
  380. km2 = KanesMethod(A, q, u_indep, kde,
  381. u_dependent=u_dep, velocity_constraints=vc)
  382. fr2, _ = km2.kanes_equations([], forces)
  383. fr1_expected = Matrix([
  384. -R*g*m*sin(q[1]),
  385. -R*(Px*cos(q[0]) + Py*sin(q[0]))*tan(q[1]),
  386. R*(Px*cos(q[0]) + Py*sin(q[0])),
  387. Px,
  388. Py])
  389. fr2_expected = Matrix([
  390. -R*g*m*sin(q[1]),
  391. 0,
  392. 0])
  393. assert (trigsimp(fr1.expand()) == trigsimp(fr1_expected.expand()))
  394. assert (trigsimp(fr2.expand()) == trigsimp(fr2_expected.expand()))