test_kane2.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. from sympy 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. # These Matrices have some Integer(0) and some Float(0). Running under
  164. # SymEngine gives different types of zero.
  165. assert (simplify(Matrix(Fr_star_steady).expand()).xreplace({0:0.0}) ==
  166. simplify(frstar_steady.expand()).xreplace({0:0.0}))
  167. syms_in_forcing = find_dynamicsymbols(kane.forcing)
  168. for qdi in qd:
  169. assert qdi not in syms_in_forcing
  170. def test_non_central_inertia():
  171. # This tests that the calculation of Fr* does not depend the point
  172. # about which the inertia of a rigid body is defined. This test solves
  173. # exercises 8.12, 8.17 from Kane 1985.
  174. # Declare symbols
  175. q1, q2, q3 = dynamicsymbols('q1:4')
  176. q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
  177. u1, u2, u3, u4, u5 = dynamicsymbols('u1:6')
  178. u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
  179. a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
  180. Q1, Q2, Q3 = symbols('Q1, Q2 Q3')
  181. IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
  182. # Reference Frames
  183. F = ReferenceFrame('F')
  184. P = F.orientnew('P', 'axis', [-theta, F.y])
  185. A = P.orientnew('A', 'axis', [q1, P.x])
  186. A.set_ang_vel(F, u1*A.x + u3*A.z)
  187. # define frames for wheels
  188. B = A.orientnew('B', 'axis', [q2, A.z])
  189. C = A.orientnew('C', 'axis', [q3, A.z])
  190. B.set_ang_vel(A, u4 * A.z)
  191. C.set_ang_vel(A, u5 * A.z)
  192. # define points D, S*, Q on frame A and their velocities
  193. pD = Point('D')
  194. pD.set_vel(A, 0)
  195. # u3 will not change v_D_F since wheels are still assumed to roll without slip.
  196. pD.set_vel(F, u2 * A.y)
  197. pS_star = pD.locatenew('S*', e*A.y)
  198. pQ = pD.locatenew('Q', f*A.y - R*A.x)
  199. for p in [pS_star, pQ]:
  200. p.v2pt_theory(pD, F, A)
  201. # masscenters of bodies A, B, C
  202. pA_star = pD.locatenew('A*', a*A.y)
  203. pB_star = pD.locatenew('B*', b*A.z)
  204. pC_star = pD.locatenew('C*', -b*A.z)
  205. for p in [pA_star, pB_star, pC_star]:
  206. p.v2pt_theory(pD, F, A)
  207. # points of B, C touching the plane P
  208. pB_hat = pB_star.locatenew('B^', -R*A.x)
  209. pC_hat = pC_star.locatenew('C^', -R*A.x)
  210. pB_hat.v2pt_theory(pB_star, F, B)
  211. pC_hat.v2pt_theory(pC_star, F, C)
  212. # the velocities of B^, C^ are zero since B, C are assumed to roll without slip
  213. kde = [q1d - u1, q2d - u4, q3d - u5]
  214. vc = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
  215. # inertias of bodies A, B, C
  216. # IA22, IA23, IA33 are not specified in the problem statement, but are
  217. # necessary to define an inertia object. Although the values of
  218. # IA22, IA23, IA33 are not known in terms of the variables given in the
  219. # problem statement, they do not appear in the general inertia terms.
  220. inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
  221. inertia_B = inertia(B, K, K, J)
  222. inertia_C = inertia(C, K, K, J)
  223. # define the rigid bodies A, B, C
  224. rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
  225. rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
  226. rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
  227. km = KanesMethod(F, q_ind=[q1, q2, q3], u_ind=[u1, u2], kd_eqs=kde,
  228. u_dependent=[u4, u5], velocity_constraints=vc,
  229. u_auxiliary=[u3])
  230. forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
  231. bodies = [rbA, rbB, rbC]
  232. fr, fr_star = km.kanes_equations(bodies, forces)
  233. vc_map = solve(vc, [u4, u5])
  234. # KanesMethod returns the negative of Fr, Fr* as defined in Kane1985.
  235. fr_star_expected = Matrix([
  236. -(IA + 2*J*b**2/R**2 + 2*K +
  237. mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
  238. -(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
  239. 0])
  240. t = trigsimp(fr_star.subs(vc_map).subs({u3: 0})).doit().expand()
  241. assert ((fr_star_expected - t).expand() == zeros(3, 1))
  242. # define inertias of rigid bodies A, B, C about point D
  243. # I_S/O = I_S/S* + I_S*/O
  244. bodies2 = []
  245. for rb, I_star in zip([rbA, rbB, rbC], [inertia_A, inertia_B, inertia_C]):
  246. I = I_star + inertia_of_point_mass(rb.mass,
  247. rb.masscenter.pos_from(pD),
  248. rb.frame)
  249. bodies2.append(RigidBody('', rb.masscenter, rb.frame, rb.mass,
  250. (I, pD)))
  251. fr2, fr_star2 = km.kanes_equations(bodies2, forces)
  252. t = trigsimp(fr_star2.subs(vc_map).subs({u3: 0})).doit()
  253. assert (fr_star_expected - t).expand() == zeros(3, 1)
  254. def test_sub_qdot():
  255. # This test solves exercises 8.12, 8.17 from Kane 1985 and defines
  256. # some velocities in terms of q, qdot.
  257. ## --- Declare symbols ---
  258. q1, q2, q3 = dynamicsymbols('q1:4')
  259. q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
  260. u1, u2, u3 = dynamicsymbols('u1:4')
  261. u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
  262. a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
  263. IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
  264. Q1, Q2, Q3 = symbols('Q1 Q2 Q3')
  265. # --- Reference Frames ---
  266. F = ReferenceFrame('F')
  267. P = F.orientnew('P', 'axis', [-theta, F.y])
  268. A = P.orientnew('A', 'axis', [q1, P.x])
  269. A.set_ang_vel(F, u1*A.x + u3*A.z)
  270. # define frames for wheels
  271. B = A.orientnew('B', 'axis', [q2, A.z])
  272. C = A.orientnew('C', 'axis', [q3, A.z])
  273. ## --- define points D, S*, Q on frame A and their velocities ---
  274. pD = Point('D')
  275. pD.set_vel(A, 0)
  276. # u3 will not change v_D_F since wheels are still assumed to roll w/o slip
  277. pD.set_vel(F, u2 * A.y)
  278. pS_star = pD.locatenew('S*', e*A.y)
  279. pQ = pD.locatenew('Q', f*A.y - R*A.x)
  280. # masscenters of bodies A, B, C
  281. pA_star = pD.locatenew('A*', a*A.y)
  282. pB_star = pD.locatenew('B*', b*A.z)
  283. pC_star = pD.locatenew('C*', -b*A.z)
  284. for p in [pS_star, pQ, pA_star, pB_star, pC_star]:
  285. p.v2pt_theory(pD, F, A)
  286. # points of B, C touching the plane P
  287. pB_hat = pB_star.locatenew('B^', -R*A.x)
  288. pC_hat = pC_star.locatenew('C^', -R*A.x)
  289. pB_hat.v2pt_theory(pB_star, F, B)
  290. pC_hat.v2pt_theory(pC_star, F, C)
  291. # --- relate qdot, u ---
  292. # the velocities of B^, C^ are zero since B, C are assumed to roll w/o slip
  293. kde = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
  294. kde += [u1 - q1d]
  295. kde_map = solve(kde, [q1d, q2d, q3d])
  296. for k, v in list(kde_map.items()):
  297. kde_map[k.diff(t)] = v.diff(t)
  298. # inertias of bodies A, B, C
  299. # IA22, IA23, IA33 are not specified in the problem statement, but are
  300. # necessary to define an inertia object. Although the values of
  301. # IA22, IA23, IA33 are not known in terms of the variables given in the
  302. # problem statement, they do not appear in the general inertia terms.
  303. inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
  304. inertia_B = inertia(B, K, K, J)
  305. inertia_C = inertia(C, K, K, J)
  306. # define the rigid bodies A, B, C
  307. rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
  308. rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
  309. rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
  310. ## --- use kanes method ---
  311. km = KanesMethod(F, [q1, q2, q3], [u1, u2], kd_eqs=kde, u_auxiliary=[u3])
  312. forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
  313. bodies = [rbA, rbB, rbC]
  314. # Q2 = -u_prime * u2 * Q1 / sqrt(u2**2 + f**2 * u1**2)
  315. # -u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2) = R / Q1 * Q2
  316. fr_expected = Matrix([
  317. f*Q3 + M*g*e*sin(theta)*cos(q1),
  318. Q2 + M*g*sin(theta)*sin(q1),
  319. e*M*g*cos(theta) - Q1*f - Q2*R])
  320. #Q1 * (f - u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2)))])
  321. fr_star_expected = Matrix([
  322. -(IA + 2*J*b**2/R**2 + 2*K +
  323. mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
  324. -(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
  325. 0])
  326. fr, fr_star = km.kanes_equations(bodies, forces)
  327. assert (fr.expand() == fr_expected.expand())
  328. assert ((fr_star_expected - trigsimp(fr_star)).expand() == zeros(3, 1))
  329. def test_sub_qdot2():
  330. # This test solves exercises 8.3 from Kane 1985 and defines
  331. # all velocities in terms of q, qdot. We check that the generalized active
  332. # forces are correctly computed if u terms are only defined in the
  333. # kinematic differential equations.
  334. #
  335. # This functionality was added in PR 8948. Without qdot/u substitution, the
  336. # KanesMethod constructor will fail during the constraint initialization as
  337. # the B matrix will be poorly formed and inversion of the dependent part
  338. # will fail.
  339. g, m, Px, Py, Pz, R, t = symbols('g m Px Py Pz R t')
  340. q = dynamicsymbols('q:5')
  341. qd = dynamicsymbols('q:5', level=1)
  342. u = dynamicsymbols('u:5')
  343. ## Define inertial, intermediate, and rigid body reference frames
  344. A = ReferenceFrame('A')
  345. B_prime = A.orientnew('B_prime', 'Axis', [q[0], A.z])
  346. B = B_prime.orientnew('B', 'Axis', [pi/2 - q[1], B_prime.x])
  347. C = B.orientnew('C', 'Axis', [q[2], B.z])
  348. ## Define points of interest and their velocities
  349. pO = Point('O')
  350. pO.set_vel(A, 0)
  351. # R is the point in plane H that comes into contact with disk C.
  352. pR = pO.locatenew('R', q[3]*A.x + q[4]*A.y)
  353. pR.set_vel(A, pR.pos_from(pO).diff(t, A))
  354. pR.set_vel(B, 0)
  355. # C^ is the point in disk C that comes into contact with plane H.
  356. pC_hat = pR.locatenew('C^', 0)
  357. pC_hat.set_vel(C, 0)
  358. # C* is the point at the center of disk C.
  359. pCs = pC_hat.locatenew('C*', R*B.y)
  360. pCs.set_vel(C, 0)
  361. pCs.set_vel(B, 0)
  362. # calculate velocites of points C* and C^ in frame A
  363. pCs.v2pt_theory(pR, A, B) # points C* and R are fixed in frame B
  364. pC_hat.v2pt_theory(pCs, A, C) # points C* and C^ are fixed in frame C
  365. ## Define forces on each point of the system
  366. R_C_hat = Px*A.x + Py*A.y + Pz*A.z
  367. R_Cs = -m*g*A.z
  368. forces = [(pC_hat, R_C_hat), (pCs, R_Cs)]
  369. ## Define kinematic differential equations
  370. # let ui = omega_C_A & bi (i = 1, 2, 3)
  371. # u4 = qd4, u5 = qd5
  372. u_expr = [C.ang_vel_in(A) & uv for uv in B]
  373. u_expr += qd[3:]
  374. kde = [ui - e for ui, e in zip(u, u_expr)]
  375. km1 = KanesMethod(A, q, u, kde)
  376. fr1, _ = km1.kanes_equations([], forces)
  377. ## Calculate generalized active forces if we impose the condition that the
  378. # disk C is rolling without slipping
  379. u_indep = u[:3]
  380. u_dep = list(set(u) - set(u_indep))
  381. vc = [pC_hat.vel(A) & uv for uv in [A.x, A.y]]
  382. km2 = KanesMethod(A, q, u_indep, kde,
  383. u_dependent=u_dep, velocity_constraints=vc)
  384. fr2, _ = km2.kanes_equations([], forces)
  385. fr1_expected = Matrix([
  386. -R*g*m*sin(q[1]),
  387. -R*(Px*cos(q[0]) + Py*sin(q[0]))*tan(q[1]),
  388. R*(Px*cos(q[0]) + Py*sin(q[0])),
  389. Px,
  390. Py])
  391. fr2_expected = Matrix([
  392. -R*g*m*sin(q[1]),
  393. 0,
  394. 0])
  395. assert (trigsimp(fr1.expand()) == trigsimp(fr1_expected.expand()))
  396. assert (trigsimp(fr2.expand()) == trigsimp(fr2_expected.expand()))