kane.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. from sympy.core.backend import zeros, Matrix, diff, eye
  2. from sympy.core.sorting import default_sort_key
  3. from sympy.physics.vector import (ReferenceFrame, dynamicsymbols,
  4. partial_velocity)
  5. from sympy.physics.mechanics.method import _Methods
  6. from sympy.physics.mechanics.particle import Particle
  7. from sympy.physics.mechanics.rigidbody import RigidBody
  8. from sympy.physics.mechanics.functions import (
  9. msubs, find_dynamicsymbols, _f_list_parser, _validate_coordinates)
  10. from sympy.physics.mechanics.linearize import Linearizer
  11. from sympy.utilities.iterables import iterable
  12. __all__ = ['KanesMethod']
  13. class KanesMethod(_Methods):
  14. r"""Kane's method object.
  15. Explanation
  16. ===========
  17. This object is used to do the "book-keeping" as you go through and form
  18. equations of motion in the way Kane presents in:
  19. Kane, T., Levinson, D. Dynamics Theory and Applications. 1985 McGraw-Hill
  20. The attributes are for equations in the form [M] udot = forcing.
  21. Attributes
  22. ==========
  23. q, u : Matrix
  24. Matrices of the generalized coordinates and speeds
  25. bodies : iterable
  26. Iterable of Point and RigidBody objects in the system.
  27. loads : iterable
  28. Iterable of (Point, vector) or (ReferenceFrame, vector) tuples
  29. describing the forces on the system.
  30. auxiliary_eqs : Matrix
  31. If applicable, the set of auxiliary Kane's
  32. equations used to solve for non-contributing
  33. forces.
  34. mass_matrix : Matrix
  35. The system's dynamics mass matrix: [k_d; k_dnh]
  36. forcing : Matrix
  37. The system's dynamics forcing vector: -[f_d; f_dnh]
  38. mass_matrix_kin : Matrix
  39. The "mass matrix" for kinematic differential equations: k_kqdot
  40. forcing_kin : Matrix
  41. The forcing vector for kinematic differential equations: -(k_ku*u + f_k)
  42. mass_matrix_full : Matrix
  43. The "mass matrix" for the u's and q's with dynamics and kinematics
  44. forcing_full : Matrix
  45. The "forcing vector" for the u's and q's with dynamics and kinematics
  46. explicit_kinematics : bool
  47. Boolean whether the mass matrices and forcing vectors should use the
  48. explicit form (default) or implicit form for kinematics.
  49. See the notes for more details.
  50. Notes
  51. =====
  52. The mass matrices and forcing vectors related to kinematic equations
  53. are given in the explicit form by default. In other words, the kinematic
  54. mass matrix is $\mathbf{k_{k\dot{q}}} = \mathbf{I}$.
  55. In order to get the implicit form of those matrices/vectors, you can set the
  56. ``explicit_kinematics`` attribute to ``False``. So $\mathbf{k_{k\dot{q}}}$ is not
  57. necessarily an identity matrix. This can provide more compact equations for
  58. non-simple kinematics (see #22626).
  59. Examples
  60. ========
  61. This is a simple example for a one degree of freedom translational
  62. spring-mass-damper.
  63. In this example, we first need to do the kinematics.
  64. This involves creating generalized speeds and coordinates and their
  65. derivatives.
  66. Then we create a point and set its velocity in a frame.
  67. >>> from sympy import symbols
  68. >>> from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame
  69. >>> from sympy.physics.mechanics import Point, Particle, KanesMethod
  70. >>> q, u = dynamicsymbols('q u')
  71. >>> qd, ud = dynamicsymbols('q u', 1)
  72. >>> m, c, k = symbols('m c k')
  73. >>> N = ReferenceFrame('N')
  74. >>> P = Point('P')
  75. >>> P.set_vel(N, u * N.x)
  76. Next we need to arrange/store information in the way that KanesMethod
  77. requires. The kinematic differential equations need to be stored in a
  78. dict. A list of forces/torques must be constructed, where each entry in
  79. the list is a (Point, Vector) or (ReferenceFrame, Vector) tuple, where the
  80. Vectors represent the Force or Torque.
  81. Next a particle needs to be created, and it needs to have a point and mass
  82. assigned to it.
  83. Finally, a list of all bodies and particles needs to be created.
  84. >>> kd = [qd - u]
  85. >>> FL = [(P, (-k * q - c * u) * N.x)]
  86. >>> pa = Particle('pa', P, m)
  87. >>> BL = [pa]
  88. Finally we can generate the equations of motion.
  89. First we create the KanesMethod object and supply an inertial frame,
  90. coordinates, generalized speeds, and the kinematic differential equations.
  91. Additional quantities such as configuration and motion constraints,
  92. dependent coordinates and speeds, and auxiliary speeds are also supplied
  93. here (see the online documentation).
  94. Next we form FR* and FR to complete: Fr + Fr* = 0.
  95. We have the equations of motion at this point.
  96. It makes sense to rearrange them though, so we calculate the mass matrix and
  97. the forcing terms, for E.o.M. in the form: [MM] udot = forcing, where MM is
  98. the mass matrix, udot is a vector of the time derivatives of the
  99. generalized speeds, and forcing is a vector representing "forcing" terms.
  100. >>> KM = KanesMethod(N, q_ind=[q], u_ind=[u], kd_eqs=kd)
  101. >>> (fr, frstar) = KM.kanes_equations(BL, FL)
  102. >>> MM = KM.mass_matrix
  103. >>> forcing = KM.forcing
  104. >>> rhs = MM.inv() * forcing
  105. >>> rhs
  106. Matrix([[(-c*u(t) - k*q(t))/m]])
  107. >>> KM.linearize(A_and_B=True)[0]
  108. Matrix([
  109. [ 0, 1],
  110. [-k/m, -c/m]])
  111. Please look at the documentation pages for more information on how to
  112. perform linearization and how to deal with dependent coordinates & speeds,
  113. and how do deal with bringing non-contributing forces into evidence.
  114. """
  115. def __init__(self, frame, q_ind, u_ind, kd_eqs=None, q_dependent=None,
  116. configuration_constraints=None, u_dependent=None,
  117. velocity_constraints=None, acceleration_constraints=None,
  118. u_auxiliary=None, bodies=None, forcelist=None, explicit_kinematics=True):
  119. """Please read the online documentation. """
  120. if not q_ind:
  121. q_ind = [dynamicsymbols('dummy_q')]
  122. kd_eqs = [dynamicsymbols('dummy_kd')]
  123. if not isinstance(frame, ReferenceFrame):
  124. raise TypeError('An inertial ReferenceFrame must be supplied')
  125. self._inertial = frame
  126. self._fr = None
  127. self._frstar = None
  128. self._forcelist = forcelist
  129. self._bodylist = bodies
  130. self.explicit_kinematics = explicit_kinematics
  131. self._initialize_vectors(q_ind, q_dependent, u_ind, u_dependent,
  132. u_auxiliary)
  133. _validate_coordinates(self.q, self.u)
  134. self._initialize_kindiffeq_matrices(kd_eqs)
  135. self._initialize_constraint_matrices(configuration_constraints,
  136. velocity_constraints, acceleration_constraints)
  137. def _initialize_vectors(self, q_ind, q_dep, u_ind, u_dep, u_aux):
  138. """Initialize the coordinate and speed vectors."""
  139. none_handler = lambda x: Matrix(x) if x else Matrix()
  140. # Initialize generalized coordinates
  141. q_dep = none_handler(q_dep)
  142. if not iterable(q_ind):
  143. raise TypeError('Generalized coordinates must be an iterable.')
  144. if not iterable(q_dep):
  145. raise TypeError('Dependent coordinates must be an iterable.')
  146. q_ind = Matrix(q_ind)
  147. self._qdep = q_dep
  148. self._q = Matrix([q_ind, q_dep])
  149. self._qdot = self.q.diff(dynamicsymbols._t)
  150. # Initialize generalized speeds
  151. u_dep = none_handler(u_dep)
  152. if not iterable(u_ind):
  153. raise TypeError('Generalized speeds must be an iterable.')
  154. if not iterable(u_dep):
  155. raise TypeError('Dependent speeds must be an iterable.')
  156. u_ind = Matrix(u_ind)
  157. self._udep = u_dep
  158. self._u = Matrix([u_ind, u_dep])
  159. self._udot = self.u.diff(dynamicsymbols._t)
  160. self._uaux = none_handler(u_aux)
  161. def _initialize_constraint_matrices(self, config, vel, acc):
  162. """Initializes constraint matrices."""
  163. # Define vector dimensions
  164. o = len(self.u)
  165. m = len(self._udep)
  166. p = o - m
  167. none_handler = lambda x: Matrix(x) if x else Matrix()
  168. # Initialize configuration constraints
  169. config = none_handler(config)
  170. if len(self._qdep) != len(config):
  171. raise ValueError('There must be an equal number of dependent '
  172. 'coordinates and configuration constraints.')
  173. self._f_h = none_handler(config)
  174. # Initialize velocity and acceleration constraints
  175. vel = none_handler(vel)
  176. acc = none_handler(acc)
  177. if len(vel) != m:
  178. raise ValueError('There must be an equal number of dependent '
  179. 'speeds and velocity constraints.')
  180. if acc and (len(acc) != m):
  181. raise ValueError('There must be an equal number of dependent '
  182. 'speeds and acceleration constraints.')
  183. if vel:
  184. u_zero = {i: 0 for i in self.u}
  185. udot_zero = {i: 0 for i in self._udot}
  186. # When calling kanes_equations, another class instance will be
  187. # created if auxiliary u's are present. In this case, the
  188. # computation of kinetic differential equation matrices will be
  189. # skipped as this was computed during the original KanesMethod
  190. # object, and the qd_u_map will not be available.
  191. if self._qdot_u_map is not None:
  192. vel = msubs(vel, self._qdot_u_map)
  193. self._f_nh = msubs(vel, u_zero)
  194. self._k_nh = (vel - self._f_nh).jacobian(self.u)
  195. # If no acceleration constraints given, calculate them.
  196. if not acc:
  197. _f_dnh = (self._k_nh.diff(dynamicsymbols._t) * self.u +
  198. self._f_nh.diff(dynamicsymbols._t))
  199. if self._qdot_u_map is not None:
  200. _f_dnh = msubs(_f_dnh, self._qdot_u_map)
  201. self._f_dnh = _f_dnh
  202. self._k_dnh = self._k_nh
  203. else:
  204. if self._qdot_u_map is not None:
  205. acc = msubs(acc, self._qdot_u_map)
  206. self._f_dnh = msubs(acc, udot_zero)
  207. self._k_dnh = (acc - self._f_dnh).jacobian(self._udot)
  208. # Form of non-holonomic constraints is B*u + C = 0.
  209. # We partition B into independent and dependent columns:
  210. # Ars is then -B_dep.inv() * B_ind, and it relates dependent speeds
  211. # to independent speeds as: udep = Ars*uind, neglecting the C term.
  212. B_ind = self._k_nh[:, :p]
  213. B_dep = self._k_nh[:, p:o]
  214. self._Ars = -B_dep.LUsolve(B_ind)
  215. else:
  216. self._f_nh = Matrix()
  217. self._k_nh = Matrix()
  218. self._f_dnh = Matrix()
  219. self._k_dnh = Matrix()
  220. self._Ars = Matrix()
  221. def _initialize_kindiffeq_matrices(self, kdeqs):
  222. """Initialize the kinematic differential equation matrices.
  223. Parameters
  224. ==========
  225. kdeqs : sequence of sympy expressions
  226. Kinematic differential equations in the form of f(u,q',q,t) where
  227. f() = 0. The equations have to be linear in the generalized
  228. coordinates and generalized speeds.
  229. """
  230. if kdeqs:
  231. if len(self.q) != len(kdeqs):
  232. raise ValueError('There must be an equal number of kinematic '
  233. 'differential equations and coordinates.')
  234. u = self.u
  235. qdot = self._qdot
  236. kdeqs = Matrix(kdeqs)
  237. u_zero = {ui: 0 for ui in u}
  238. uaux_zero = {uai: 0 for uai in self._uaux}
  239. qdot_zero = {qdi: 0 for qdi in qdot}
  240. # Extract the linear coefficient matrices as per the following
  241. # equation:
  242. #
  243. # k_ku(q,t)*u(t) + k_kqdot(q,t)*q'(t) + f_k(q,t) = 0
  244. #
  245. k_ku = kdeqs.jacobian(u)
  246. k_kqdot = kdeqs.jacobian(qdot)
  247. f_k = kdeqs.xreplace(u_zero).xreplace(qdot_zero)
  248. # The kinematic differential equations should be linear in both q'
  249. # and u, so check for u and q' in the components.
  250. dy_syms = find_dynamicsymbols(k_ku.row_join(k_kqdot).row_join(f_k))
  251. nonlin_vars = [vari for vari in u[:] + qdot[:] if vari in dy_syms]
  252. if nonlin_vars:
  253. msg = ('The provided kinematic differential equations are '
  254. 'nonlinear in {}. They must be linear in the '
  255. 'generalized speeds and derivatives of the generalized '
  256. 'coordinates.')
  257. raise ValueError(msg.format(nonlin_vars))
  258. self._f_k_implicit = f_k.xreplace(uaux_zero)
  259. self._k_ku_implicit = k_ku.xreplace(uaux_zero)
  260. self._k_kqdot_implicit = k_kqdot
  261. # Solve for q'(t) such that the coefficient matrices are now in
  262. # this form:
  263. #
  264. # k_kqdot^-1*k_ku*u(t) + I*q'(t) + k_kqdot^-1*f_k = 0
  265. #
  266. # NOTE : Solving the kinematic differential equations here is not
  267. # necessary and prevents the equations from being provided in fully
  268. # implicit form.
  269. f_k_explicit = k_kqdot.LUsolve(f_k)
  270. k_ku_explicit = k_kqdot.LUsolve(k_ku)
  271. self._qdot_u_map = dict(zip(qdot, -(k_ku_explicit*u + f_k_explicit)))
  272. self._f_k = f_k_explicit.xreplace(uaux_zero)
  273. self._k_ku = k_ku_explicit.xreplace(uaux_zero)
  274. self._k_kqdot = eye(len(qdot))
  275. else:
  276. self._qdot_u_map = None
  277. self._f_k_implicit = self._f_k = Matrix()
  278. self._k_ku_implicit = self._k_ku = Matrix()
  279. self._k_kqdot_implicit = self._k_kqdot = Matrix()
  280. def _form_fr(self, fl):
  281. """Form the generalized active force."""
  282. if fl is not None and (len(fl) == 0 or not iterable(fl)):
  283. raise ValueError('Force pairs must be supplied in an '
  284. 'non-empty iterable or None.')
  285. N = self._inertial
  286. # pull out relevant velocities for constructing partial velocities
  287. vel_list, f_list = _f_list_parser(fl, N)
  288. vel_list = [msubs(i, self._qdot_u_map) for i in vel_list]
  289. f_list = [msubs(i, self._qdot_u_map) for i in f_list]
  290. # Fill Fr with dot product of partial velocities and forces
  291. o = len(self.u)
  292. b = len(f_list)
  293. FR = zeros(o, 1)
  294. partials = partial_velocity(vel_list, self.u, N)
  295. for i in range(o):
  296. FR[i] = sum(partials[j][i] & f_list[j] for j in range(b))
  297. # In case there are dependent speeds
  298. if self._udep:
  299. p = o - len(self._udep)
  300. FRtilde = FR[:p, 0]
  301. FRold = FR[p:o, 0]
  302. FRtilde += self._Ars.T * FRold
  303. FR = FRtilde
  304. self._forcelist = fl
  305. self._fr = FR
  306. return FR
  307. def _form_frstar(self, bl):
  308. """Form the generalized inertia force."""
  309. if not iterable(bl):
  310. raise TypeError('Bodies must be supplied in an iterable.')
  311. t = dynamicsymbols._t
  312. N = self._inertial
  313. # Dicts setting things to zero
  314. udot_zero = {i: 0 for i in self._udot}
  315. uaux_zero = {i: 0 for i in self._uaux}
  316. uauxdot = [diff(i, t) for i in self._uaux]
  317. uauxdot_zero = {i: 0 for i in uauxdot}
  318. # Dictionary of q' and q'' to u and u'
  319. q_ddot_u_map = {k.diff(t): v.diff(t) for (k, v) in
  320. self._qdot_u_map.items()}
  321. q_ddot_u_map.update(self._qdot_u_map)
  322. # Fill up the list of partials: format is a list with num elements
  323. # equal to number of entries in body list. Each of these elements is a
  324. # list - either of length 1 for the translational components of
  325. # particles or of length 2 for the translational and rotational
  326. # components of rigid bodies. The inner most list is the list of
  327. # partial velocities.
  328. def get_partial_velocity(body):
  329. if isinstance(body, RigidBody):
  330. vlist = [body.masscenter.vel(N), body.frame.ang_vel_in(N)]
  331. elif isinstance(body, Particle):
  332. vlist = [body.point.vel(N),]
  333. else:
  334. raise TypeError('The body list may only contain either '
  335. 'RigidBody or Particle as list elements.')
  336. v = [msubs(vel, self._qdot_u_map) for vel in vlist]
  337. return partial_velocity(v, self.u, N)
  338. partials = [get_partial_velocity(body) for body in bl]
  339. # Compute fr_star in two components:
  340. # fr_star = -(MM*u' + nonMM)
  341. o = len(self.u)
  342. MM = zeros(o, o)
  343. nonMM = zeros(o, 1)
  344. zero_uaux = lambda expr: msubs(expr, uaux_zero)
  345. zero_udot_uaux = lambda expr: msubs(msubs(expr, udot_zero), uaux_zero)
  346. for i, body in enumerate(bl):
  347. if isinstance(body, RigidBody):
  348. M = zero_uaux(body.mass)
  349. I = zero_uaux(body.central_inertia)
  350. vel = zero_uaux(body.masscenter.vel(N))
  351. omega = zero_uaux(body.frame.ang_vel_in(N))
  352. acc = zero_udot_uaux(body.masscenter.acc(N))
  353. inertial_force = (M.diff(t) * vel + M * acc)
  354. inertial_torque = zero_uaux((I.dt(body.frame) & omega) +
  355. msubs(I & body.frame.ang_acc_in(N), udot_zero) +
  356. (omega ^ (I & omega)))
  357. for j in range(o):
  358. tmp_vel = zero_uaux(partials[i][0][j])
  359. tmp_ang = zero_uaux(I & partials[i][1][j])
  360. for k in range(o):
  361. # translational
  362. MM[j, k] += M * (tmp_vel & partials[i][0][k])
  363. # rotational
  364. MM[j, k] += (tmp_ang & partials[i][1][k])
  365. nonMM[j] += inertial_force & partials[i][0][j]
  366. nonMM[j] += inertial_torque & partials[i][1][j]
  367. else:
  368. M = zero_uaux(body.mass)
  369. vel = zero_uaux(body.point.vel(N))
  370. acc = zero_udot_uaux(body.point.acc(N))
  371. inertial_force = (M.diff(t) * vel + M * acc)
  372. for j in range(o):
  373. temp = zero_uaux(partials[i][0][j])
  374. for k in range(o):
  375. MM[j, k] += M * (temp & partials[i][0][k])
  376. nonMM[j] += inertial_force & partials[i][0][j]
  377. # Compose fr_star out of MM and nonMM
  378. MM = zero_uaux(msubs(MM, q_ddot_u_map))
  379. nonMM = msubs(msubs(nonMM, q_ddot_u_map),
  380. udot_zero, uauxdot_zero, uaux_zero)
  381. fr_star = -(MM * msubs(Matrix(self._udot), uauxdot_zero) + nonMM)
  382. # If there are dependent speeds, we need to find fr_star_tilde
  383. if self._udep:
  384. p = o - len(self._udep)
  385. fr_star_ind = fr_star[:p, 0]
  386. fr_star_dep = fr_star[p:o, 0]
  387. fr_star = fr_star_ind + (self._Ars.T * fr_star_dep)
  388. # Apply the same to MM
  389. MMi = MM[:p, :]
  390. MMd = MM[p:o, :]
  391. MM = MMi + (self._Ars.T * MMd)
  392. self._bodylist = bl
  393. self._frstar = fr_star
  394. self._k_d = MM
  395. self._f_d = -msubs(self._fr + self._frstar, udot_zero)
  396. return fr_star
  397. def to_linearizer(self):
  398. """Returns an instance of the Linearizer class, initiated from the
  399. data in the KanesMethod class. This may be more desirable than using
  400. the linearize class method, as the Linearizer object will allow more
  401. efficient recalculation (i.e. about varying operating points)."""
  402. if (self._fr is None) or (self._frstar is None):
  403. raise ValueError('Need to compute Fr, Fr* first.')
  404. # Get required equation components. The Kane's method class breaks
  405. # these into pieces. Need to reassemble
  406. f_c = self._f_h
  407. if self._f_nh and self._k_nh:
  408. f_v = self._f_nh + self._k_nh*Matrix(self.u)
  409. else:
  410. f_v = Matrix()
  411. if self._f_dnh and self._k_dnh:
  412. f_a = self._f_dnh + self._k_dnh*Matrix(self._udot)
  413. else:
  414. f_a = Matrix()
  415. # Dicts to sub to zero, for splitting up expressions
  416. u_zero = {i: 0 for i in self.u}
  417. ud_zero = {i: 0 for i in self._udot}
  418. qd_zero = {i: 0 for i in self._qdot}
  419. qd_u_zero = {i: 0 for i in Matrix([self._qdot, self.u])}
  420. # Break the kinematic differential eqs apart into f_0 and f_1
  421. f_0 = msubs(self._f_k, u_zero) + self._k_kqdot*Matrix(self._qdot)
  422. f_1 = msubs(self._f_k, qd_zero) + self._k_ku*Matrix(self.u)
  423. # Break the dynamic differential eqs into f_2 and f_3
  424. f_2 = msubs(self._frstar, qd_u_zero)
  425. f_3 = msubs(self._frstar, ud_zero) + self._fr
  426. f_4 = zeros(len(f_2), 1)
  427. # Get the required vector components
  428. q = self.q
  429. u = self.u
  430. if self._qdep:
  431. q_i = q[:-len(self._qdep)]
  432. else:
  433. q_i = q
  434. q_d = self._qdep
  435. if self._udep:
  436. u_i = u[:-len(self._udep)]
  437. else:
  438. u_i = u
  439. u_d = self._udep
  440. # Form dictionary to set auxiliary speeds & their derivatives to 0.
  441. uaux = self._uaux
  442. uauxdot = uaux.diff(dynamicsymbols._t)
  443. uaux_zero = {i: 0 for i in Matrix([uaux, uauxdot])}
  444. # Checking for dynamic symbols outside the dynamic differential
  445. # equations; throws error if there is.
  446. sym_list = set(Matrix([q, self._qdot, u, self._udot, uaux, uauxdot]))
  447. if any(find_dynamicsymbols(i, sym_list) for i in [self._k_kqdot,
  448. self._k_ku, self._f_k, self._k_dnh, self._f_dnh, self._k_d]):
  449. raise ValueError('Cannot have dynamicsymbols outside dynamic \
  450. forcing vector.')
  451. # Find all other dynamic symbols, forming the forcing vector r.
  452. # Sort r to make it canonical.
  453. r = list(find_dynamicsymbols(msubs(self._f_d, uaux_zero), sym_list))
  454. r.sort(key=default_sort_key)
  455. # Check for any derivatives of variables in r that are also found in r.
  456. for i in r:
  457. if diff(i, dynamicsymbols._t) in r:
  458. raise ValueError('Cannot have derivatives of specified \
  459. quantities when linearizing forcing terms.')
  460. return Linearizer(f_0, f_1, f_2, f_3, f_4, f_c, f_v, f_a, q, u, q_i,
  461. q_d, u_i, u_d, r)
  462. # TODO : Remove `new_method` after 1.1 has been released.
  463. def linearize(self, *, new_method=None, **kwargs):
  464. """ Linearize the equations of motion about a symbolic operating point.
  465. Explanation
  466. ===========
  467. If kwarg A_and_B is False (default), returns M, A, B, r for the
  468. linearized form, M*[q', u']^T = A*[q_ind, u_ind]^T + B*r.
  469. If kwarg A_and_B is True, returns A, B, r for the linearized form
  470. dx = A*x + B*r, where x = [q_ind, u_ind]^T. Note that this is
  471. computationally intensive if there are many symbolic parameters. For
  472. this reason, it may be more desirable to use the default A_and_B=False,
  473. returning M, A, and B. Values may then be substituted in to these
  474. matrices, and the state space form found as
  475. A = P.T*M.inv()*A, B = P.T*M.inv()*B, where P = Linearizer.perm_mat.
  476. In both cases, r is found as all dynamicsymbols in the equations of
  477. motion that are not part of q, u, q', or u'. They are sorted in
  478. canonical form.
  479. The operating points may be also entered using the ``op_point`` kwarg.
  480. This takes a dictionary of {symbol: value}, or a an iterable of such
  481. dictionaries. The values may be numeric or symbolic. The more values
  482. you can specify beforehand, the faster this computation will run.
  483. For more documentation, please see the ``Linearizer`` class."""
  484. linearizer = self.to_linearizer()
  485. result = linearizer.linearize(**kwargs)
  486. return result + (linearizer.r,)
  487. def kanes_equations(self, bodies=None, loads=None):
  488. """ Method to form Kane's equations, Fr + Fr* = 0.
  489. Explanation
  490. ===========
  491. Returns (Fr, Fr*). In the case where auxiliary generalized speeds are
  492. present (say, s auxiliary speeds, o generalized speeds, and m motion
  493. constraints) the length of the returned vectors will be o - m + s in
  494. length. The first o - m equations will be the constrained Kane's
  495. equations, then the s auxiliary Kane's equations. These auxiliary
  496. equations can be accessed with the auxiliary_eqs property.
  497. Parameters
  498. ==========
  499. bodies : iterable
  500. An iterable of all RigidBody's and Particle's in the system.
  501. A system must have at least one body.
  502. loads : iterable
  503. Takes in an iterable of (Particle, Vector) or (ReferenceFrame, Vector)
  504. tuples which represent the force at a point or torque on a frame.
  505. Must be either a non-empty iterable of tuples or None which corresponds
  506. to a system with no constraints.
  507. """
  508. if bodies is None:
  509. bodies = self.bodies
  510. if loads is None and self._forcelist is not None:
  511. loads = self._forcelist
  512. if loads == []:
  513. loads = None
  514. if not self._k_kqdot:
  515. raise AttributeError('Create an instance of KanesMethod with '
  516. 'kinematic differential equations to use this method.')
  517. fr = self._form_fr(loads)
  518. frstar = self._form_frstar(bodies)
  519. if self._uaux:
  520. if not self._udep:
  521. km = KanesMethod(self._inertial, self.q, self._uaux,
  522. u_auxiliary=self._uaux)
  523. else:
  524. km = KanesMethod(self._inertial, self.q, self._uaux,
  525. u_auxiliary=self._uaux, u_dependent=self._udep,
  526. velocity_constraints=(self._k_nh * self.u +
  527. self._f_nh),
  528. acceleration_constraints=(self._k_dnh * self._udot +
  529. self._f_dnh)
  530. )
  531. km._qdot_u_map = self._qdot_u_map
  532. self._km = km
  533. fraux = km._form_fr(loads)
  534. frstaraux = km._form_frstar(bodies)
  535. self._aux_eq = fraux + frstaraux
  536. self._fr = fr.col_join(fraux)
  537. self._frstar = frstar.col_join(frstaraux)
  538. return (self._fr, self._frstar)
  539. def _form_eoms(self):
  540. fr, frstar = self.kanes_equations(self.bodylist, self.forcelist)
  541. return fr + frstar
  542. def rhs(self, inv_method=None):
  543. """Returns the system's equations of motion in first order form. The
  544. output is the right hand side of::
  545. x' = |q'| =: f(q, u, r, p, t)
  546. |u'|
  547. The right hand side is what is needed by most numerical ODE
  548. integrators.
  549. Parameters
  550. ==========
  551. inv_method : str
  552. The specific sympy inverse matrix calculation method to use. For a
  553. list of valid methods, see
  554. :meth:`~sympy.matrices.matrices.MatrixBase.inv`
  555. """
  556. rhs = zeros(len(self.q) + len(self.u), 1)
  557. kdes = self.kindiffdict()
  558. for i, q_i in enumerate(self.q):
  559. rhs[i] = kdes[q_i.diff()]
  560. if inv_method is None:
  561. rhs[len(self.q):, 0] = self.mass_matrix.LUsolve(self.forcing)
  562. else:
  563. rhs[len(self.q):, 0] = (self.mass_matrix.inv(inv_method,
  564. try_block_diag=True) *
  565. self.forcing)
  566. return rhs
  567. def kindiffdict(self):
  568. """Returns a dictionary mapping q' to u."""
  569. if not self._qdot_u_map:
  570. raise AttributeError('Create an instance of KanesMethod with '
  571. 'kinematic differential equations to use this method.')
  572. return self._qdot_u_map
  573. @property
  574. def auxiliary_eqs(self):
  575. """A matrix containing the auxiliary equations."""
  576. if not self._fr or not self._frstar:
  577. raise ValueError('Need to compute Fr, Fr* first.')
  578. if not self._uaux:
  579. raise ValueError('No auxiliary speeds have been declared.')
  580. return self._aux_eq
  581. @property
  582. def mass_matrix_kin(self):
  583. r"""The kinematic "mass matrix" $\mathbf{k_{k\dot{q}}}$ of the system."""
  584. return self._k_kqdot if self.explicit_kinematics else self._k_kqdot_implicit
  585. @property
  586. def forcing_kin(self):
  587. """The kinematic "forcing vector" of the system."""
  588. if self.explicit_kinematics:
  589. return -(self._k_ku * Matrix(self.u) + self._f_k)
  590. else:
  591. return -(self._k_ku_implicit * Matrix(self.u) + self._f_k_implicit)
  592. @property
  593. def mass_matrix(self):
  594. """The mass matrix of the system."""
  595. if not self._fr or not self._frstar:
  596. raise ValueError('Need to compute Fr, Fr* first.')
  597. return Matrix([self._k_d, self._k_dnh])
  598. @property
  599. def forcing(self):
  600. """The forcing vector of the system."""
  601. if not self._fr or not self._frstar:
  602. raise ValueError('Need to compute Fr, Fr* first.')
  603. return -Matrix([self._f_d, self._f_dnh])
  604. @property
  605. def mass_matrix_full(self):
  606. """The mass matrix of the system, augmented by the kinematic
  607. differential equations in explicit or implicit form."""
  608. if not self._fr or not self._frstar:
  609. raise ValueError('Need to compute Fr, Fr* first.')
  610. o, n = len(self.u), len(self.q)
  611. return (self.mass_matrix_kin.row_join(zeros(n, o))).col_join(
  612. zeros(o, n).row_join(self.mass_matrix))
  613. @property
  614. def forcing_full(self):
  615. """The forcing vector of the system, augmented by the kinematic
  616. differential equations in explicit or implicit form."""
  617. return Matrix([self.forcing_kin, self.forcing])
  618. @property
  619. def q(self):
  620. return self._q
  621. @property
  622. def u(self):
  623. return self._u
  624. @property
  625. def bodylist(self):
  626. return self._bodylist
  627. @property
  628. def forcelist(self):
  629. return self._forcelist
  630. @property
  631. def bodies(self):
  632. return self._bodylist
  633. @property
  634. def loads(self):
  635. return self._forcelist