system.py 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553
  1. from functools import wraps
  2. from sympy.core.basic import Basic
  3. from sympy.matrices.immutable import ImmutableMatrix
  4. from sympy.matrices.dense import Matrix, eye, zeros
  5. from sympy.core.containers import OrderedSet
  6. from sympy.physics.mechanics.actuator import ActuatorBase
  7. from sympy.physics.mechanics.body_base import BodyBase
  8. from sympy.physics.mechanics.functions import (
  9. Lagrangian, _validate_coordinates, find_dynamicsymbols)
  10. from sympy.physics.mechanics.joint import Joint
  11. from sympy.physics.mechanics.kane import KanesMethod
  12. from sympy.physics.mechanics.lagrange import LagrangesMethod
  13. from sympy.physics.mechanics.loads import _parse_load, gravity
  14. from sympy.physics.mechanics.method import _Methods
  15. from sympy.physics.mechanics.particle import Particle
  16. from sympy.physics.vector import Point, ReferenceFrame, dynamicsymbols
  17. from sympy.utilities.iterables import iterable
  18. from sympy.utilities.misc import filldedent
  19. __all__ = ['SymbolicSystem', 'System']
  20. def _reset_eom_method(method):
  21. """Decorator to reset the eom_method if a property is changed."""
  22. @wraps(method)
  23. def wrapper(self, *args, **kwargs):
  24. self._eom_method = None
  25. return method(self, *args, **kwargs)
  26. return wrapper
  27. class System(_Methods):
  28. """Class to define a multibody system and form its equations of motion.
  29. Explanation
  30. ===========
  31. A ``System`` instance stores the different objects associated with a model,
  32. including bodies, joints, constraints, and other relevant information. With
  33. all the relationships between components defined, the ``System`` can be used
  34. to form the equations of motion using a backend, such as ``KanesMethod``.
  35. The ``System`` has been designed to be compatible with third-party
  36. libraries for greater flexibility and integration with other tools.
  37. Attributes
  38. ==========
  39. frame : ReferenceFrame
  40. Inertial reference frame of the system.
  41. fixed_point : Point
  42. A fixed point in the inertial reference frame.
  43. x : Vector
  44. Unit vector fixed in the inertial reference frame.
  45. y : Vector
  46. Unit vector fixed in the inertial reference frame.
  47. z : Vector
  48. Unit vector fixed in the inertial reference frame.
  49. q : ImmutableMatrix
  50. Matrix of all the generalized coordinates, i.e. the independent
  51. generalized coordinates stacked upon the dependent.
  52. u : ImmutableMatrix
  53. Matrix of all the generalized speeds, i.e. the independent generealized
  54. speeds stacked upon the dependent.
  55. q_ind : ImmutableMatrix
  56. Matrix of the independent generalized coordinates.
  57. q_dep : ImmutableMatrix
  58. Matrix of the dependent generalized coordinates.
  59. u_ind : ImmutableMatrix
  60. Matrix of the independent generalized speeds.
  61. u_dep : ImmutableMatrix
  62. Matrix of the dependent generalized speeds.
  63. u_aux : ImmutableMatrix
  64. Matrix of auxiliary generalized speeds.
  65. kdes : ImmutableMatrix
  66. Matrix of the kinematical differential equations as expressions equated
  67. to the zero matrix.
  68. bodies : tuple of BodyBase subclasses
  69. Tuple of all bodies that make up the system.
  70. joints : tuple of Joint
  71. Tuple of all joints that connect bodies in the system.
  72. loads : tuple of LoadBase subclasses
  73. Tuple of all loads that have been applied to the system.
  74. actuators : tuple of ActuatorBase subclasses
  75. Tuple of all actuators present in the system.
  76. holonomic_constraints : ImmutableMatrix
  77. Matrix with the holonomic constraints as expressions equated to the zero
  78. matrix.
  79. nonholonomic_constraints : ImmutableMatrix
  80. Matrix with the nonholonomic constraints as expressions equated to the
  81. zero matrix.
  82. velocity_constraints : ImmutableMatrix
  83. Matrix with the velocity constraints as expressions equated to the zero
  84. matrix. These are by default derived as the time derivatives of the
  85. holonomic constraints extended with the nonholonomic constraints.
  86. eom_method : subclass of KanesMethod or LagrangesMethod
  87. Backend for forming the equations of motion.
  88. Examples
  89. ========
  90. In the example below a cart with a pendulum is created. The cart moves along
  91. the x axis of the rail and the pendulum rotates about the z axis. The length
  92. of the pendulum is ``l`` with the pendulum represented as a particle. To
  93. move the cart a time dependent force ``F`` is applied to the cart.
  94. We first need to import some functions and create some of our variables.
  95. >>> from sympy import symbols, simplify
  96. >>> from sympy.physics.mechanics import (
  97. ... mechanics_printing, dynamicsymbols, RigidBody, Particle,
  98. ... ReferenceFrame, PrismaticJoint, PinJoint, System)
  99. >>> mechanics_printing(pretty_print=False)
  100. >>> g, l = symbols('g l')
  101. >>> F = dynamicsymbols('F')
  102. The next step is to create bodies. It is also useful to create a frame for
  103. locating the particle with respect to the pin joint later on, as a particle
  104. does not have a body-fixed frame.
  105. >>> rail = RigidBody('rail')
  106. >>> cart = RigidBody('cart')
  107. >>> bob = Particle('bob')
  108. >>> bob_frame = ReferenceFrame('bob_frame')
  109. Initialize the system, with the rail as the Newtonian reference. The body is
  110. also automatically added to the system.
  111. >>> system = System.from_newtonian(rail)
  112. >>> print(system.bodies[0])
  113. rail
  114. Create the joints, while immediately also adding them to the system.
  115. >>> system.add_joints(
  116. ... PrismaticJoint('slider', rail, cart, joint_axis=rail.x),
  117. ... PinJoint('pin', cart, bob, joint_axis=cart.z,
  118. ... child_interframe=bob_frame,
  119. ... child_point=l * bob_frame.y)
  120. ... )
  121. >>> system.joints
  122. (PrismaticJoint: slider parent: rail child: cart,
  123. PinJoint: pin parent: cart child: bob)
  124. While adding the joints, the associated generalized coordinates, generalized
  125. speeds, kinematic differential equations and bodies are also added to the
  126. system.
  127. >>> system.q
  128. Matrix([
  129. [q_slider],
  130. [ q_pin]])
  131. >>> system.u
  132. Matrix([
  133. [u_slider],
  134. [ u_pin]])
  135. >>> system.kdes
  136. Matrix([
  137. [u_slider - q_slider'],
  138. [ u_pin - q_pin']])
  139. >>> [body.name for body in system.bodies]
  140. ['rail', 'cart', 'bob']
  141. With the kinematics established, we can now apply gravity and the cart force
  142. ``F``.
  143. >>> system.apply_uniform_gravity(-g * system.y)
  144. >>> system.add_loads((cart.masscenter, F * rail.x))
  145. >>> system.loads
  146. ((rail_masscenter, - g*rail_mass*rail_frame.y),
  147. (cart_masscenter, - cart_mass*g*rail_frame.y),
  148. (bob_masscenter, - bob_mass*g*rail_frame.y),
  149. (cart_masscenter, F*rail_frame.x))
  150. With the entire system defined, we can now form the equations of motion.
  151. Before forming the equations of motion, one can also run some checks that
  152. will try to identify some common errors.
  153. >>> system.validate_system()
  154. >>> system.form_eoms()
  155. Matrix([
  156. [bob_mass*l*u_pin**2*sin(q_pin) - bob_mass*l*cos(q_pin)*u_pin'
  157. - (bob_mass + cart_mass)*u_slider' + F],
  158. [ -bob_mass*g*l*sin(q_pin) - bob_mass*l**2*u_pin'
  159. - bob_mass*l*cos(q_pin)*u_slider']])
  160. >>> simplify(system.mass_matrix)
  161. Matrix([
  162. [ bob_mass + cart_mass, bob_mass*l*cos(q_pin)],
  163. [bob_mass*l*cos(q_pin), bob_mass*l**2]])
  164. >>> system.forcing
  165. Matrix([
  166. [bob_mass*l*u_pin**2*sin(q_pin) + F],
  167. [ -bob_mass*g*l*sin(q_pin)]])
  168. The complexity of the above example can be increased if we add a constraint
  169. to prevent the particle from moving in the horizontal (x) direction. This
  170. can be done by adding a holonomic constraint. After which we should also
  171. redefine what our (in)dependent generalized coordinates and speeds are.
  172. >>> system.add_holonomic_constraints(
  173. ... bob.masscenter.pos_from(rail.masscenter).dot(system.x)
  174. ... )
  175. >>> system.q_ind = system.get_joint('pin').coordinates
  176. >>> system.q_dep = system.get_joint('slider').coordinates
  177. >>> system.u_ind = system.get_joint('pin').speeds
  178. >>> system.u_dep = system.get_joint('slider').speeds
  179. With the updated system the equations of motion can be formed again.
  180. >>> system.validate_system()
  181. >>> system.form_eoms()
  182. Matrix([[-bob_mass*g*l*sin(q_pin)
  183. - bob_mass*l**2*u_pin'
  184. - bob_mass*l*cos(q_pin)*u_slider'
  185. - l*(bob_mass*l*u_pin**2*sin(q_pin)
  186. - bob_mass*l*cos(q_pin)*u_pin'
  187. - (bob_mass + cart_mass)*u_slider')*cos(q_pin)
  188. - l*F*cos(q_pin)]])
  189. >>> simplify(system.mass_matrix)
  190. Matrix([
  191. [bob_mass*l**2*sin(q_pin)**2, -cart_mass*l*cos(q_pin)],
  192. [ l*cos(q_pin), 1]])
  193. >>> simplify(system.forcing)
  194. Matrix([
  195. [-l*(bob_mass*g*sin(q_pin) + bob_mass*l*u_pin**2*sin(2*q_pin)/2
  196. + F*cos(q_pin))],
  197. [
  198. l*u_pin**2*sin(q_pin)]])
  199. """
  200. def __init__(self, frame=None, fixed_point=None):
  201. """Initialize the system.
  202. Parameters
  203. ==========
  204. frame : ReferenceFrame, optional
  205. The inertial frame of the system. If none is supplied, a new frame
  206. will be created.
  207. fixed_point : Point, optional
  208. A fixed point in the inertial reference frame. If none is supplied,
  209. a new fixed_point will be created.
  210. """
  211. if frame is None:
  212. frame = ReferenceFrame('inertial_frame')
  213. elif not isinstance(frame, ReferenceFrame):
  214. raise TypeError('Frame must be an instance of ReferenceFrame.')
  215. self._frame = frame
  216. if fixed_point is None:
  217. fixed_point = Point('inertial_point')
  218. elif not isinstance(fixed_point, Point):
  219. raise TypeError('Fixed point must be an instance of Point.')
  220. self._fixed_point = fixed_point
  221. self._fixed_point.set_vel(self._frame, 0)
  222. self._q_ind = ImmutableMatrix(1, 0, []).T
  223. self._q_dep = ImmutableMatrix(1, 0, []).T
  224. self._u_ind = ImmutableMatrix(1, 0, []).T
  225. self._u_dep = ImmutableMatrix(1, 0, []).T
  226. self._u_aux = ImmutableMatrix(1, 0, []).T
  227. self._kdes = ImmutableMatrix(1, 0, []).T
  228. self._hol_coneqs = ImmutableMatrix(1, 0, []).T
  229. self._nonhol_coneqs = ImmutableMatrix(1, 0, []).T
  230. self._vel_constrs = None
  231. self._bodies = []
  232. self._joints = []
  233. self._loads = []
  234. self._actuators = []
  235. self._eom_method = None
  236. @classmethod
  237. def from_newtonian(cls, newtonian):
  238. """Constructs the system with respect to a Newtonian body."""
  239. if isinstance(newtonian, Particle):
  240. raise TypeError('A Particle has no frame so cannot act as '
  241. 'the Newtonian.')
  242. system = cls(frame=newtonian.frame, fixed_point=newtonian.masscenter)
  243. system.add_bodies(newtonian)
  244. return system
  245. @property
  246. def fixed_point(self):
  247. """Fixed point in the inertial reference frame."""
  248. return self._fixed_point
  249. @property
  250. def frame(self):
  251. """Inertial reference frame of the system."""
  252. return self._frame
  253. @property
  254. def x(self):
  255. """Unit vector fixed in the inertial reference frame."""
  256. return self._frame.x
  257. @property
  258. def y(self):
  259. """Unit vector fixed in the inertial reference frame."""
  260. return self._frame.y
  261. @property
  262. def z(self):
  263. """Unit vector fixed in the inertial reference frame."""
  264. return self._frame.z
  265. @property
  266. def bodies(self):
  267. """Tuple of all bodies that have been added to the system."""
  268. return tuple(self._bodies)
  269. @bodies.setter
  270. @_reset_eom_method
  271. def bodies(self, bodies):
  272. bodies = self._objects_to_list(bodies)
  273. self._check_objects(bodies, [], BodyBase, 'Bodies', 'bodies')
  274. self._bodies = bodies
  275. @property
  276. def joints(self):
  277. """Tuple of all joints that have been added to the system."""
  278. return tuple(self._joints)
  279. @joints.setter
  280. @_reset_eom_method
  281. def joints(self, joints):
  282. joints = self._objects_to_list(joints)
  283. self._check_objects(joints, [], Joint, 'Joints', 'joints')
  284. self._joints = []
  285. self.add_joints(*joints)
  286. @property
  287. def loads(self):
  288. """Tuple of loads that have been applied on the system."""
  289. return tuple(self._loads)
  290. @loads.setter
  291. @_reset_eom_method
  292. def loads(self, loads):
  293. loads = self._objects_to_list(loads)
  294. self._loads = [_parse_load(load) for load in loads]
  295. @property
  296. def actuators(self):
  297. """Tuple of actuators present in the system."""
  298. return tuple(self._actuators)
  299. @actuators.setter
  300. @_reset_eom_method
  301. def actuators(self, actuators):
  302. actuators = self._objects_to_list(actuators)
  303. self._check_objects(actuators, [], ActuatorBase, 'Actuators',
  304. 'actuators')
  305. self._actuators = actuators
  306. @property
  307. def q(self):
  308. """Matrix of all the generalized coordinates with the independent
  309. stacked upon the dependent."""
  310. return self._q_ind.col_join(self._q_dep)
  311. @property
  312. def u(self):
  313. """Matrix of all the generalized speeds with the independent stacked
  314. upon the dependent."""
  315. return self._u_ind.col_join(self._u_dep)
  316. @property
  317. def q_ind(self):
  318. """Matrix of the independent generalized coordinates."""
  319. return self._q_ind
  320. @q_ind.setter
  321. @_reset_eom_method
  322. def q_ind(self, q_ind):
  323. self._q_ind, self._q_dep = self._parse_coordinates(
  324. self._objects_to_list(q_ind), True, [], self.q_dep, 'coordinates')
  325. @property
  326. def q_dep(self):
  327. """Matrix of the dependent generalized coordinates."""
  328. return self._q_dep
  329. @q_dep.setter
  330. @_reset_eom_method
  331. def q_dep(self, q_dep):
  332. self._q_ind, self._q_dep = self._parse_coordinates(
  333. self._objects_to_list(q_dep), False, self.q_ind, [], 'coordinates')
  334. @property
  335. def u_ind(self):
  336. """Matrix of the independent generalized speeds."""
  337. return self._u_ind
  338. @u_ind.setter
  339. @_reset_eom_method
  340. def u_ind(self, u_ind):
  341. self._u_ind, self._u_dep = self._parse_coordinates(
  342. self._objects_to_list(u_ind), True, [], self.u_dep, 'speeds')
  343. @property
  344. def u_dep(self):
  345. """Matrix of the dependent generalized speeds."""
  346. return self._u_dep
  347. @u_dep.setter
  348. @_reset_eom_method
  349. def u_dep(self, u_dep):
  350. self._u_ind, self._u_dep = self._parse_coordinates(
  351. self._objects_to_list(u_dep), False, self.u_ind, [], 'speeds')
  352. @property
  353. def u_aux(self):
  354. """Matrix of auxiliary generalized speeds."""
  355. return self._u_aux
  356. @u_aux.setter
  357. @_reset_eom_method
  358. def u_aux(self, u_aux):
  359. self._u_aux = self._parse_coordinates(
  360. self._objects_to_list(u_aux), True, [], [], 'u_auxiliary')[0]
  361. @property
  362. def kdes(self):
  363. """Kinematical differential equations as expressions equated to the zero
  364. matrix. These equations describe the coupling between the generalized
  365. coordinates and the generalized speeds."""
  366. return self._kdes
  367. @kdes.setter
  368. @_reset_eom_method
  369. def kdes(self, kdes):
  370. kdes = self._objects_to_list(kdes)
  371. self._kdes = self._parse_expressions(
  372. kdes, [], 'kinematic differential equations')
  373. @property
  374. def holonomic_constraints(self):
  375. """Matrix with the holonomic constraints as expressions equated to the
  376. zero matrix."""
  377. return self._hol_coneqs
  378. @holonomic_constraints.setter
  379. @_reset_eom_method
  380. def holonomic_constraints(self, constraints):
  381. constraints = self._objects_to_list(constraints)
  382. self._hol_coneqs = self._parse_expressions(
  383. constraints, [], 'holonomic constraints')
  384. @property
  385. def nonholonomic_constraints(self):
  386. """Matrix with the nonholonomic constraints as expressions equated to
  387. the zero matrix."""
  388. return self._nonhol_coneqs
  389. @nonholonomic_constraints.setter
  390. @_reset_eom_method
  391. def nonholonomic_constraints(self, constraints):
  392. constraints = self._objects_to_list(constraints)
  393. self._nonhol_coneqs = self._parse_expressions(
  394. constraints, [], 'nonholonomic constraints')
  395. @property
  396. def velocity_constraints(self):
  397. """Matrix with the velocity constraints as expressions equated to the
  398. zero matrix. The velocity constraints are by default derived from the
  399. holonomic and nonholonomic constraints unless they are explicitly set.
  400. """
  401. if self._vel_constrs is None:
  402. return self.holonomic_constraints.diff(dynamicsymbols._t).col_join(
  403. self.nonholonomic_constraints)
  404. return self._vel_constrs
  405. @velocity_constraints.setter
  406. @_reset_eom_method
  407. def velocity_constraints(self, constraints):
  408. if constraints is None:
  409. self._vel_constrs = None
  410. return
  411. constraints = self._objects_to_list(constraints)
  412. self._vel_constrs = self._parse_expressions(
  413. constraints, [], 'velocity constraints')
  414. @property
  415. def eom_method(self):
  416. """Backend for forming the equations of motion."""
  417. return self._eom_method
  418. @staticmethod
  419. def _objects_to_list(lst):
  420. """Helper to convert passed objects to a list."""
  421. if not iterable(lst): # Only one object
  422. return [lst]
  423. return list(lst[:]) # converts Matrix and tuple to flattened list
  424. @staticmethod
  425. def _check_objects(objects, obj_lst, expected_type, obj_name, type_name):
  426. """Helper to check the objects that are being added to the system.
  427. Explanation
  428. ===========
  429. This method checks that the objects that are being added to the system
  430. are of the correct type and have not already been added. If any of the
  431. objects are not of the correct type or have already been added, then
  432. an error is raised.
  433. Parameters
  434. ==========
  435. objects : iterable
  436. The objects that would be added to the system.
  437. obj_lst : list
  438. The list of objects that are already in the system.
  439. expected_type : type
  440. The type that the objects should be.
  441. obj_name : str
  442. The name of the category of objects. This string is used to
  443. formulate the error message for the user.
  444. type_name : str
  445. The name of the type that the objects should be. This string is used
  446. to formulate the error message for the user.
  447. """
  448. seen = set(obj_lst)
  449. duplicates = set()
  450. wrong_types = set()
  451. for obj in objects:
  452. if not isinstance(obj, expected_type):
  453. wrong_types.add(obj)
  454. if obj in seen:
  455. duplicates.add(obj)
  456. else:
  457. seen.add(obj)
  458. if wrong_types:
  459. raise TypeError(f'{obj_name} {wrong_types} are not {type_name}.')
  460. if duplicates:
  461. raise ValueError(f'{obj_name} {duplicates} have already been added '
  462. f'to the system.')
  463. def _parse_coordinates(self, new_coords, independent, old_coords_ind,
  464. old_coords_dep, coord_type='coordinates'):
  465. """Helper to parse coordinates and speeds."""
  466. # Construct lists of the independent and dependent coordinates
  467. coords_ind, coords_dep = old_coords_ind[:], old_coords_dep[:]
  468. if not iterable(independent):
  469. independent = [independent] * len(new_coords)
  470. for coord, indep in zip(new_coords, independent):
  471. if indep:
  472. coords_ind.append(coord)
  473. else:
  474. coords_dep.append(coord)
  475. # Check types and duplicates
  476. current = {'coordinates': self.q_ind[:] + self.q_dep[:],
  477. 'speeds': self.u_ind[:] + self.u_dep[:],
  478. 'u_auxiliary': self._u_aux[:],
  479. coord_type: coords_ind + coords_dep}
  480. _validate_coordinates(**current)
  481. return (ImmutableMatrix(1, len(coords_ind), coords_ind).T,
  482. ImmutableMatrix(1, len(coords_dep), coords_dep).T)
  483. @staticmethod
  484. def _parse_expressions(new_expressions, old_expressions, name,
  485. check_negatives=False):
  486. """Helper to parse expressions like constraints."""
  487. old_expressions = old_expressions[:]
  488. new_expressions = list(new_expressions) # Converts a possible tuple
  489. if check_negatives:
  490. check_exprs = old_expressions + [-expr for expr in old_expressions]
  491. else:
  492. check_exprs = old_expressions
  493. System._check_objects(new_expressions, check_exprs, Basic, name,
  494. 'expressions')
  495. for expr in new_expressions:
  496. if expr == 0:
  497. raise ValueError(f'Parsed {name} are zero.')
  498. return ImmutableMatrix(1, len(old_expressions) + len(new_expressions),
  499. old_expressions + new_expressions).T
  500. @_reset_eom_method
  501. def add_coordinates(self, *coordinates, independent=True):
  502. """Add generalized coordinate(s) to the system.
  503. Parameters
  504. ==========
  505. *coordinates : dynamicsymbols
  506. One or more generalized coordinates to be added to the system.
  507. independent : bool or list of bool, optional
  508. Boolean whether a coordinate is dependent or independent. The
  509. default is True, so the coordinates are added as independent by
  510. default.
  511. """
  512. self._q_ind, self._q_dep = self._parse_coordinates(
  513. coordinates, independent, self.q_ind, self.q_dep, 'coordinates')
  514. @_reset_eom_method
  515. def add_speeds(self, *speeds, independent=True):
  516. """Add generalized speed(s) to the system.
  517. Parameters
  518. ==========
  519. *speeds : dynamicsymbols
  520. One or more generalized speeds to be added to the system.
  521. independent : bool or list of bool, optional
  522. Boolean whether a speed is dependent or independent. The default is
  523. True, so the speeds are added as independent by default.
  524. """
  525. self._u_ind, self._u_dep = self._parse_coordinates(
  526. speeds, independent, self.u_ind, self.u_dep, 'speeds')
  527. @_reset_eom_method
  528. def add_auxiliary_speeds(self, *speeds):
  529. """Add auxiliary speed(s) to the system.
  530. Parameters
  531. ==========
  532. *speeds : dynamicsymbols
  533. One or more auxiliary speeds to be added to the system.
  534. """
  535. self._u_aux = self._parse_coordinates(
  536. speeds, True, self._u_aux, [], 'u_auxiliary')[0]
  537. @_reset_eom_method
  538. def add_kdes(self, *kdes):
  539. """Add kinematic differential equation(s) to the system.
  540. Parameters
  541. ==========
  542. *kdes : Expr
  543. One or more kinematic differential equations.
  544. """
  545. self._kdes = self._parse_expressions(
  546. kdes, self.kdes, 'kinematic differential equations',
  547. check_negatives=True)
  548. @_reset_eom_method
  549. def add_holonomic_constraints(self, *constraints):
  550. """Add holonomic constraint(s) to the system.
  551. Parameters
  552. ==========
  553. *constraints : Expr
  554. One or more holonomic constraints, which are expressions that should
  555. be zero.
  556. """
  557. self._hol_coneqs = self._parse_expressions(
  558. constraints, self._hol_coneqs, 'holonomic constraints',
  559. check_negatives=True)
  560. @_reset_eom_method
  561. def add_nonholonomic_constraints(self, *constraints):
  562. """Add nonholonomic constraint(s) to the system.
  563. Parameters
  564. ==========
  565. *constraints : Expr
  566. One or more nonholonomic constraints, which are expressions that
  567. should be zero.
  568. """
  569. self._nonhol_coneqs = self._parse_expressions(
  570. constraints, self._nonhol_coneqs, 'nonholonomic constraints',
  571. check_negatives=True)
  572. @_reset_eom_method
  573. def add_bodies(self, *bodies):
  574. """Add body(ies) to the system.
  575. Parameters
  576. ==========
  577. bodies : Particle or RigidBody
  578. One or more bodies.
  579. """
  580. self._check_objects(bodies, self.bodies, BodyBase, 'Bodies', 'bodies')
  581. self._bodies.extend(bodies)
  582. @_reset_eom_method
  583. def add_loads(self, *loads):
  584. """Add load(s) to the system.
  585. Parameters
  586. ==========
  587. *loads : Force or Torque
  588. One or more loads.
  589. """
  590. loads = [_parse_load(load) for load in loads] # Checks the loads
  591. self._loads.extend(loads)
  592. @_reset_eom_method
  593. def apply_uniform_gravity(self, acceleration):
  594. """Apply uniform gravity to all bodies in the system by adding loads.
  595. Parameters
  596. ==========
  597. acceleration : Vector
  598. The acceleration due to gravity.
  599. """
  600. self.add_loads(*gravity(acceleration, *self.bodies))
  601. @_reset_eom_method
  602. def add_actuators(self, *actuators):
  603. """Add actuator(s) to the system.
  604. Parameters
  605. ==========
  606. *actuators : subclass of ActuatorBase
  607. One or more actuators.
  608. """
  609. self._check_objects(actuators, self.actuators, ActuatorBase,
  610. 'Actuators', 'actuators')
  611. self._actuators.extend(actuators)
  612. @_reset_eom_method
  613. def add_joints(self, *joints):
  614. """Add joint(s) to the system.
  615. Explanation
  616. ===========
  617. This methods adds one or more joints to the system including its
  618. associated objects, i.e. generalized coordinates, generalized speeds,
  619. kinematic differential equations and the bodies.
  620. Parameters
  621. ==========
  622. *joints : subclass of Joint
  623. One or more joints.
  624. Notes
  625. =====
  626. For the generalized coordinates, generalized speeds and bodies it is
  627. checked whether they are already known by the system instance. If they
  628. are, then they are not added. The kinematic differential equations are
  629. however always added to the system, so you should not also manually add
  630. those on beforehand.
  631. """
  632. self._check_objects(joints, self.joints, Joint, 'Joints', 'joints')
  633. self._joints.extend(joints)
  634. coordinates, speeds, kdes, bodies = (OrderedSet() for _ in range(4))
  635. for joint in joints:
  636. coordinates.update(joint.coordinates)
  637. speeds.update(joint.speeds)
  638. kdes.update(joint.kdes)
  639. bodies.update((joint.parent, joint.child))
  640. coordinates = coordinates.difference(self.q)
  641. speeds = speeds.difference(self.u)
  642. kdes = kdes.difference(self.kdes[:] + (-self.kdes)[:])
  643. bodies = bodies.difference(self.bodies)
  644. self.add_coordinates(*tuple(coordinates))
  645. self.add_speeds(*tuple(speeds))
  646. self.add_kdes(*(kde for kde in tuple(kdes) if not kde == 0))
  647. self.add_bodies(*tuple(bodies))
  648. def get_body(self, name):
  649. """Retrieve a body from the system by name.
  650. Parameters
  651. ==========
  652. name : str
  653. The name of the body to retrieve.
  654. Returns
  655. =======
  656. RigidBody or Particle
  657. The body with the given name, or None if no such body exists.
  658. """
  659. for body in self._bodies:
  660. if body.name == name:
  661. return body
  662. def get_joint(self, name):
  663. """Retrieve a joint from the system by name.
  664. Parameters
  665. ==========
  666. name : str
  667. The name of the joint to retrieve.
  668. Returns
  669. =======
  670. subclass of Joint
  671. The joint with the given name, or None if no such joint exists.
  672. """
  673. for joint in self._joints:
  674. if joint.name == name:
  675. return joint
  676. def _form_eoms(self):
  677. return self.form_eoms()
  678. def form_eoms(self, eom_method=KanesMethod, **kwargs):
  679. """Form the equations of motion of the system.
  680. Parameters
  681. ==========
  682. eom_method : subclass of KanesMethod or LagrangesMethod
  683. Backend class to be used for forming the equations of motion. The
  684. default is ``KanesMethod``.
  685. Returns
  686. ========
  687. ImmutableMatrix
  688. Vector of equations of motions.
  689. Examples
  690. ========
  691. This is a simple example for a one degree of freedom translational
  692. spring-mass-damper.
  693. >>> from sympy import S, symbols
  694. >>> from sympy.physics.mechanics import (
  695. ... LagrangesMethod, dynamicsymbols, PrismaticJoint, Particle,
  696. ... RigidBody, System)
  697. >>> q = dynamicsymbols('q')
  698. >>> qd = dynamicsymbols('q', 1)
  699. >>> m, k, b = symbols('m k b')
  700. >>> wall = RigidBody('W')
  701. >>> system = System.from_newtonian(wall)
  702. >>> bob = Particle('P', mass=m)
  703. >>> bob.potential_energy = S.Half * k * q**2
  704. >>> system.add_joints(PrismaticJoint('J', wall, bob, q, qd))
  705. >>> system.add_loads((bob.masscenter, b * qd * system.x))
  706. >>> system.form_eoms(LagrangesMethod)
  707. Matrix([[-b*Derivative(q(t), t) + k*q(t) + m*Derivative(q(t), (t, 2))]])
  708. We can also solve for the states using the 'rhs' method.
  709. >>> system.rhs()
  710. Matrix([
  711. [ Derivative(q(t), t)],
  712. [(b*Derivative(q(t), t) - k*q(t))/m]])
  713. """
  714. # KanesMethod does not accept empty iterables
  715. loads = self.loads + tuple(
  716. load for act in self.actuators for load in act.to_loads())
  717. loads = loads if loads else None
  718. if issubclass(eom_method, KanesMethod):
  719. disallowed_kwargs = {
  720. "frame", "q_ind", "u_ind", "kd_eqs", "q_dependent",
  721. "u_dependent", "u_auxiliary", "configuration_constraints",
  722. "velocity_constraints", "forcelist", "bodies"}
  723. wrong_kwargs = disallowed_kwargs.intersection(kwargs)
  724. if wrong_kwargs:
  725. raise ValueError(
  726. f"The following keyword arguments are not allowed to be "
  727. f"overwritten in {eom_method.__name__}: {wrong_kwargs}.")
  728. kwargs = {"frame": self.frame, "q_ind": self.q_ind,
  729. "u_ind": self.u_ind, "kd_eqs": self.kdes,
  730. "q_dependent": self.q_dep, "u_dependent": self.u_dep,
  731. "configuration_constraints": self.holonomic_constraints,
  732. "velocity_constraints": self.velocity_constraints,
  733. "u_auxiliary": self.u_aux,
  734. "forcelist": loads, "bodies": self.bodies,
  735. "explicit_kinematics": False, **kwargs}
  736. self._eom_method = eom_method(**kwargs)
  737. elif issubclass(eom_method, LagrangesMethod):
  738. disallowed_kwargs = {
  739. "frame", "qs", "forcelist", "bodies", "hol_coneqs",
  740. "nonhol_coneqs", "Lagrangian"}
  741. wrong_kwargs = disallowed_kwargs.intersection(kwargs)
  742. if wrong_kwargs:
  743. raise ValueError(
  744. f"The following keyword arguments are not allowed to be "
  745. f"overwritten in {eom_method.__name__}: {wrong_kwargs}.")
  746. kwargs = {"frame": self.frame, "qs": self.q, "forcelist": loads,
  747. "bodies": self.bodies,
  748. "hol_coneqs": self.holonomic_constraints,
  749. "nonhol_coneqs": self.nonholonomic_constraints, **kwargs}
  750. if "Lagrangian" not in kwargs:
  751. kwargs["Lagrangian"] = Lagrangian(kwargs["frame"],
  752. *kwargs["bodies"])
  753. self._eom_method = eom_method(**kwargs)
  754. else:
  755. raise NotImplementedError(f'{eom_method} has not been implemented.')
  756. return self.eom_method._form_eoms()
  757. def rhs(self, inv_method=None):
  758. """Compute the equations of motion in the explicit form.
  759. Parameters
  760. ==========
  761. inv_method : str
  762. The specific sympy inverse matrix calculation method to use. For a
  763. list of valid methods, see
  764. :meth:`~sympy.matrices.matrixbase.MatrixBase.inv`
  765. Returns
  766. ========
  767. ImmutableMatrix
  768. Equations of motion in the explicit form.
  769. See Also
  770. ========
  771. sympy.physics.mechanics.kane.KanesMethod.rhs:
  772. KanesMethod's ``rhs`` function.
  773. sympy.physics.mechanics.lagrange.LagrangesMethod.rhs:
  774. LagrangesMethod's ``rhs`` function.
  775. """
  776. return self.eom_method.rhs(inv_method=inv_method)
  777. @property
  778. def mass_matrix(self):
  779. r"""The mass matrix of the system.
  780. Explanation
  781. ===========
  782. The mass matrix $M_d$ and the forcing vector $f_d$ of a system describe
  783. the system's dynamics according to the following equations:
  784. .. math::
  785. M_d \dot{u} = f_d
  786. where $\dot{u}$ is the time derivative of the generalized speeds.
  787. """
  788. return self.eom_method.mass_matrix
  789. @property
  790. def mass_matrix_full(self):
  791. r"""The mass matrix of the system, augmented by the kinematic
  792. differential equations in explicit or implicit form.
  793. Explanation
  794. ===========
  795. The full mass matrix $M_m$ and the full forcing vector $f_m$ of a system
  796. describe the dynamics and kinematics according to the following
  797. equation:
  798. .. math::
  799. M_m \dot{x} = f_m
  800. where $x$ is the state vector stacking $q$ and $u$.
  801. """
  802. return self.eom_method.mass_matrix_full
  803. @property
  804. def forcing(self):
  805. """The forcing vector of the system."""
  806. return self.eom_method.forcing
  807. @property
  808. def forcing_full(self):
  809. """The forcing vector of the system, augmented by the kinematic
  810. differential equations in explicit or implicit form."""
  811. return self.eom_method.forcing_full
  812. def validate_system(self, eom_method=KanesMethod, check_duplicates=False):
  813. """Validates the system using some basic checks.
  814. Explanation
  815. ===========
  816. This method validates the system based on the following checks:
  817. - The number of dependent generalized coordinates should equal the
  818. number of holonomic constraints.
  819. - All generalized coordinates defined by the joints should also be known
  820. to the system.
  821. - If ``KanesMethod`` is used as a ``eom_method``:
  822. - All generalized speeds and kinematic differential equations
  823. defined by the joints should also be known to the system.
  824. - The number of dependent generalized speeds should equal the number
  825. of velocity constraints.
  826. - The number of generalized coordinates should be less than or equal
  827. to the number of generalized speeds.
  828. - The number of generalized coordinates should equal the number of
  829. kinematic differential equations.
  830. - If ``LagrangesMethod`` is used as ``eom_method``:
  831. - There should not be any generalized speeds that are not
  832. derivatives of the generalized coordinates (this includes the
  833. generalized speeds defined by the joints).
  834. Parameters
  835. ==========
  836. eom_method : subclass of KanesMethod or LagrangesMethod
  837. Backend class that will be used for forming the equations of motion.
  838. There are different checks for the different backends. The default
  839. is ``KanesMethod``.
  840. check_duplicates : bool
  841. Boolean whether the system should be checked for duplicate
  842. definitions. The default is False, because duplicates are already
  843. checked when adding objects to the system.
  844. Notes
  845. =====
  846. This method is not guaranteed to be backwards compatible as it may
  847. improve over time. The method can become both more and less strict in
  848. certain areas. However a well-defined system should always pass all
  849. these tests.
  850. """
  851. msgs = []
  852. # Save some data in variables
  853. n_hc = self.holonomic_constraints.shape[0]
  854. n_vc = self.velocity_constraints.shape[0]
  855. n_q_dep, n_u_dep = self.q_dep.shape[0], self.u_dep.shape[0]
  856. q_set, u_set = set(self.q), set(self.u)
  857. n_q, n_u = len(q_set), len(u_set)
  858. # Check number of holonomic constraints
  859. if n_q_dep != n_hc:
  860. msgs.append(filldedent(f"""
  861. The number of dependent generalized coordinates {n_q_dep} should be
  862. equal to the number of holonomic constraints {n_hc}."""))
  863. # Check if all joint coordinates and speeds are present
  864. missing_q = set()
  865. for joint in self.joints:
  866. missing_q.update(set(joint.coordinates).difference(q_set))
  867. if missing_q:
  868. msgs.append(filldedent(f"""
  869. The generalized coordinates {missing_q} used in joints are not added
  870. to the system."""))
  871. # Method dependent checks
  872. if issubclass(eom_method, KanesMethod):
  873. n_kdes = len(self.kdes)
  874. missing_kdes, missing_u = set(), set()
  875. for joint in self.joints:
  876. missing_u.update(set(joint.speeds).difference(u_set))
  877. missing_kdes.update(set(joint.kdes).difference(
  878. self.kdes[:] + (-self.kdes)[:]))
  879. if missing_u:
  880. msgs.append(filldedent(f"""
  881. The generalized speeds {missing_u} used in joints are not added
  882. to the system."""))
  883. if missing_kdes:
  884. msgs.append(filldedent(f"""
  885. The kinematic differential equations {missing_kdes} used in
  886. joints are not added to the system."""))
  887. if n_u_dep != n_vc:
  888. msgs.append(filldedent(f"""
  889. The number of dependent generalized speeds {n_u_dep} should be
  890. equal to the number of velocity constraints {n_vc}."""))
  891. if n_q > n_u:
  892. msgs.append(filldedent(f"""
  893. The number of generalized coordinates {n_q} should be less than
  894. or equal to the number of generalized speeds {n_u}."""))
  895. if n_u != n_kdes:
  896. msgs.append(filldedent(f"""
  897. The number of generalized speeds {n_u} should be equal to the
  898. number of kinematic differential equations {n_kdes}."""))
  899. elif issubclass(eom_method, LagrangesMethod):
  900. not_qdots = set(self.u).difference(self.q.diff(dynamicsymbols._t))
  901. for joint in self.joints:
  902. not_qdots.update(set(
  903. joint.speeds).difference(self.q.diff(dynamicsymbols._t)))
  904. if not_qdots:
  905. msgs.append(filldedent(f"""
  906. The generalized speeds {not_qdots} are not supported by this
  907. method. Only derivatives of the generalized coordinates are
  908. supported. If these symbols are used in your expressions, then
  909. this will result in wrong equations of motion."""))
  910. if self.u_aux:
  911. msgs.append(filldedent(f"""
  912. This method does not support auxiliary speeds. If these symbols
  913. are used in your expressions, then this will result in wrong
  914. equations of motion. The auxiliary speeds are {self.u_aux}."""))
  915. else:
  916. raise NotImplementedError(f'{eom_method} has not been implemented.')
  917. if check_duplicates: # Should be redundant
  918. duplicates_to_check = [('generalized coordinates', self.q),
  919. ('generalized speeds', self.u),
  920. ('auxiliary speeds', self.u_aux),
  921. ('bodies', self.bodies),
  922. ('joints', self.joints)]
  923. for name, lst in duplicates_to_check:
  924. seen = set()
  925. duplicates = {x for x in lst if x in seen or seen.add(x)}
  926. if duplicates:
  927. msgs.append(filldedent(f"""
  928. The {name} {duplicates} exist multiple times within the
  929. system."""))
  930. if msgs:
  931. raise ValueError('\n'.join(msgs))
  932. class SymbolicSystem:
  933. """SymbolicSystem is a class that contains all the information about a
  934. system in a symbolic format such as the equations of motions and the bodies
  935. and loads in the system.
  936. There are three ways that the equations of motion can be described for
  937. Symbolic System:
  938. [1] Explicit form where the kinematics and dynamics are combined
  939. x' = F_1(x, t, r, p)
  940. [2] Implicit form where the kinematics and dynamics are combined
  941. M_2(x, p) x' = F_2(x, t, r, p)
  942. [3] Implicit form where the kinematics and dynamics are separate
  943. M_3(q, p) u' = F_3(q, u, t, r, p)
  944. q' = G(q, u, t, r, p)
  945. where
  946. x : states, e.g. [q, u]
  947. t : time
  948. r : specified (exogenous) inputs
  949. p : constants
  950. q : generalized coordinates
  951. u : generalized speeds
  952. F_1 : right hand side of the combined equations in explicit form
  953. F_2 : right hand side of the combined equations in implicit form
  954. F_3 : right hand side of the dynamical equations in implicit form
  955. M_2 : mass matrix of the combined equations in implicit form
  956. M_3 : mass matrix of the dynamical equations in implicit form
  957. G : right hand side of the kinematical differential equations
  958. Parameters
  959. ==========
  960. coord_states : ordered iterable of functions of time
  961. This input will either be a collection of the coordinates or states
  962. of the system depending on whether or not the speeds are also
  963. given. If speeds are specified this input will be assumed to
  964. be the coordinates otherwise this input will be assumed to
  965. be the states.
  966. right_hand_side : Matrix
  967. This variable is the right hand side of the equations of motion in
  968. any of the forms. The specific form will be assumed depending on
  969. whether a mass matrix or coordinate derivatives are given.
  970. speeds : ordered iterable of functions of time, optional
  971. This is a collection of the generalized speeds of the system. If
  972. given it will be assumed that the first argument (coord_states)
  973. will represent the generalized coordinates of the system.
  974. mass_matrix : Matrix, optional
  975. The matrix of the implicit forms of the equations of motion (forms
  976. [2] and [3]). The distinction between the forms is determined by
  977. whether or not the coordinate derivatives are passed in. If
  978. they are given form [3] will be assumed otherwise form [2] is
  979. assumed.
  980. coordinate_derivatives : Matrix, optional
  981. The right hand side of the kinematical equations in explicit form.
  982. If given it will be assumed that the equations of motion are being
  983. entered in form [3].
  984. alg_con : Iterable, optional
  985. The indexes of the rows in the equations of motion that contain
  986. algebraic constraints instead of differential equations. If the
  987. equations are input in form [3], it will be assumed the indexes are
  988. referencing the mass_matrix/right_hand_side combination and not the
  989. coordinate_derivatives.
  990. output_eqns : Dictionary, optional
  991. Any output equations that are desired to be tracked are stored in a
  992. dictionary where the key corresponds to the name given for the
  993. specific equation and the value is the equation itself in symbolic
  994. form
  995. coord_idxs : Iterable, optional
  996. If coord_states corresponds to the states rather than the
  997. coordinates this variable will tell SymbolicSystem which indexes of
  998. the states correspond to generalized coordinates.
  999. speed_idxs : Iterable, optional
  1000. If coord_states corresponds to the states rather than the
  1001. coordinates this variable will tell SymbolicSystem which indexes of
  1002. the states correspond to generalized speeds.
  1003. bodies : iterable of Body/Rigidbody objects, optional
  1004. Iterable containing the bodies of the system
  1005. loads : iterable of load instances (described below), optional
  1006. Iterable containing the loads of the system where forces are given
  1007. by (point of application, force vector) and torques are given by
  1008. (reference frame acting upon, torque vector). Ex [(point, force),
  1009. (ref_frame, torque)]
  1010. Attributes
  1011. ==========
  1012. coordinates : Matrix, shape(n, 1)
  1013. This is a matrix containing the generalized coordinates of the system
  1014. speeds : Matrix, shape(m, 1)
  1015. This is a matrix containing the generalized speeds of the system
  1016. states : Matrix, shape(o, 1)
  1017. This is a matrix containing the state variables of the system
  1018. alg_con : List
  1019. This list contains the indices of the algebraic constraints in the
  1020. combined equations of motion. The presence of these constraints
  1021. requires that a DAE solver be used instead of an ODE solver.
  1022. If the system is given in form [3] the alg_con variable will be
  1023. adjusted such that it is a representation of the combined kinematics
  1024. and dynamics thus make sure it always matches the mass matrix
  1025. entered.
  1026. dyn_implicit_mat : Matrix, shape(m, m)
  1027. This is the M matrix in form [3] of the equations of motion (the mass
  1028. matrix or generalized inertia matrix of the dynamical equations of
  1029. motion in implicit form).
  1030. dyn_implicit_rhs : Matrix, shape(m, 1)
  1031. This is the F vector in form [3] of the equations of motion (the right
  1032. hand side of the dynamical equations of motion in implicit form).
  1033. comb_implicit_mat : Matrix, shape(o, o)
  1034. This is the M matrix in form [2] of the equations of motion.
  1035. This matrix contains a block diagonal structure where the top
  1036. left block (the first rows) represent the matrix in the
  1037. implicit form of the kinematical equations and the bottom right
  1038. block (the last rows) represent the matrix in the implicit form
  1039. of the dynamical equations.
  1040. comb_implicit_rhs : Matrix, shape(o, 1)
  1041. This is the F vector in form [2] of the equations of motion. The top
  1042. part of the vector represents the right hand side of the implicit form
  1043. of the kinemaical equations and the bottom of the vector represents the
  1044. right hand side of the implicit form of the dynamical equations of
  1045. motion.
  1046. comb_explicit_rhs : Matrix, shape(o, 1)
  1047. This vector represents the right hand side of the combined equations of
  1048. motion in explicit form (form [1] from above).
  1049. kin_explicit_rhs : Matrix, shape(m, 1)
  1050. This is the right hand side of the explicit form of the kinematical
  1051. equations of motion as can be seen in form [3] (the G matrix).
  1052. output_eqns : Dictionary
  1053. If output equations were given they are stored in a dictionary where
  1054. the key corresponds to the name given for the specific equation and
  1055. the value is the equation itself in symbolic form
  1056. bodies : Tuple
  1057. If the bodies in the system were given they are stored in a tuple for
  1058. future access
  1059. loads : Tuple
  1060. If the loads in the system were given they are stored in a tuple for
  1061. future access. This includes forces and torques where forces are given
  1062. by (point of application, force vector) and torques are given by
  1063. (reference frame acted upon, torque vector).
  1064. Example
  1065. =======
  1066. As a simple example, the dynamics of a simple pendulum will be input into a
  1067. SymbolicSystem object manually. First some imports will be needed and then
  1068. symbols will be set up for the length of the pendulum (l), mass at the end
  1069. of the pendulum (m), and a constant for gravity (g). ::
  1070. >>> from sympy import Matrix, sin, symbols
  1071. >>> from sympy.physics.mechanics import dynamicsymbols, SymbolicSystem
  1072. >>> l, m, g = symbols('l m g')
  1073. The system will be defined by an angle of theta from the vertical and a
  1074. generalized speed of omega will be used where omega = theta_dot. ::
  1075. >>> theta, omega = dynamicsymbols('theta omega')
  1076. Now the equations of motion are ready to be formed and passed to the
  1077. SymbolicSystem object. ::
  1078. >>> kin_explicit_rhs = Matrix([omega])
  1079. >>> dyn_implicit_mat = Matrix([l**2 * m])
  1080. >>> dyn_implicit_rhs = Matrix([-g * l * m * sin(theta)])
  1081. >>> symsystem = SymbolicSystem([theta], dyn_implicit_rhs, [omega],
  1082. ... dyn_implicit_mat)
  1083. Notes
  1084. =====
  1085. m : number of generalized speeds
  1086. n : number of generalized coordinates
  1087. o : number of states
  1088. """
  1089. def __init__(self, coord_states, right_hand_side, speeds=None,
  1090. mass_matrix=None, coordinate_derivatives=None, alg_con=None,
  1091. output_eqns={}, coord_idxs=None, speed_idxs=None, bodies=None,
  1092. loads=None):
  1093. """Initializes a SymbolicSystem object"""
  1094. # Extract information on speeds, coordinates and states
  1095. if speeds is None:
  1096. self._states = Matrix(coord_states)
  1097. if coord_idxs is None:
  1098. self._coordinates = None
  1099. else:
  1100. coords = [coord_states[i] for i in coord_idxs]
  1101. self._coordinates = Matrix(coords)
  1102. if speed_idxs is None:
  1103. self._speeds = None
  1104. else:
  1105. speeds_inter = [coord_states[i] for i in speed_idxs]
  1106. self._speeds = Matrix(speeds_inter)
  1107. else:
  1108. self._coordinates = Matrix(coord_states)
  1109. self._speeds = Matrix(speeds)
  1110. self._states = self._coordinates.col_join(self._speeds)
  1111. # Extract equations of motion form
  1112. if coordinate_derivatives is not None:
  1113. self._kin_explicit_rhs = coordinate_derivatives
  1114. self._dyn_implicit_rhs = right_hand_side
  1115. self._dyn_implicit_mat = mass_matrix
  1116. self._comb_implicit_rhs = None
  1117. self._comb_implicit_mat = None
  1118. self._comb_explicit_rhs = None
  1119. elif mass_matrix is not None:
  1120. self._kin_explicit_rhs = None
  1121. self._dyn_implicit_rhs = None
  1122. self._dyn_implicit_mat = None
  1123. self._comb_implicit_rhs = right_hand_side
  1124. self._comb_implicit_mat = mass_matrix
  1125. self._comb_explicit_rhs = None
  1126. else:
  1127. self._kin_explicit_rhs = None
  1128. self._dyn_implicit_rhs = None
  1129. self._dyn_implicit_mat = None
  1130. self._comb_implicit_rhs = None
  1131. self._comb_implicit_mat = None
  1132. self._comb_explicit_rhs = right_hand_side
  1133. # Set the remainder of the inputs as instance attributes
  1134. if alg_con is not None and coordinate_derivatives is not None:
  1135. alg_con = [i + len(coordinate_derivatives) for i in alg_con]
  1136. self._alg_con = alg_con
  1137. self.output_eqns = output_eqns
  1138. # Change the body and loads iterables to tuples if they are not tuples
  1139. # already
  1140. if not isinstance(bodies, tuple) and bodies is not None:
  1141. bodies = tuple(bodies)
  1142. if not isinstance(loads, tuple) and loads is not None:
  1143. loads = tuple(loads)
  1144. self._bodies = bodies
  1145. self._loads = loads
  1146. @property
  1147. def coordinates(self):
  1148. """Returns the column matrix of the generalized coordinates"""
  1149. if self._coordinates is None:
  1150. raise AttributeError("The coordinates were not specified.")
  1151. else:
  1152. return self._coordinates
  1153. @property
  1154. def speeds(self):
  1155. """Returns the column matrix of generalized speeds"""
  1156. if self._speeds is None:
  1157. raise AttributeError("The speeds were not specified.")
  1158. else:
  1159. return self._speeds
  1160. @property
  1161. def states(self):
  1162. """Returns the column matrix of the state variables"""
  1163. return self._states
  1164. @property
  1165. def alg_con(self):
  1166. """Returns a list with the indices of the rows containing algebraic
  1167. constraints in the combined form of the equations of motion"""
  1168. return self._alg_con
  1169. @property
  1170. def dyn_implicit_mat(self):
  1171. """Returns the matrix, M, corresponding to the dynamic equations in
  1172. implicit form, M x' = F, where the kinematical equations are not
  1173. included"""
  1174. if self._dyn_implicit_mat is None:
  1175. raise AttributeError("dyn_implicit_mat is not specified for "
  1176. "equations of motion form [1] or [2].")
  1177. else:
  1178. return self._dyn_implicit_mat
  1179. @property
  1180. def dyn_implicit_rhs(self):
  1181. """Returns the column matrix, F, corresponding to the dynamic equations
  1182. in implicit form, M x' = F, where the kinematical equations are not
  1183. included"""
  1184. if self._dyn_implicit_rhs is None:
  1185. raise AttributeError("dyn_implicit_rhs is not specified for "
  1186. "equations of motion form [1] or [2].")
  1187. else:
  1188. return self._dyn_implicit_rhs
  1189. @property
  1190. def comb_implicit_mat(self):
  1191. """Returns the matrix, M, corresponding to the equations of motion in
  1192. implicit form (form [2]), M x' = F, where the kinematical equations are
  1193. included"""
  1194. if self._comb_implicit_mat is None:
  1195. if self._dyn_implicit_mat is not None:
  1196. num_kin_eqns = len(self._kin_explicit_rhs)
  1197. num_dyn_eqns = len(self._dyn_implicit_rhs)
  1198. zeros1 = zeros(num_kin_eqns, num_dyn_eqns)
  1199. zeros2 = zeros(num_dyn_eqns, num_kin_eqns)
  1200. inter1 = eye(num_kin_eqns).row_join(zeros1)
  1201. inter2 = zeros2.row_join(self._dyn_implicit_mat)
  1202. self._comb_implicit_mat = inter1.col_join(inter2)
  1203. return self._comb_implicit_mat
  1204. else:
  1205. raise AttributeError("comb_implicit_mat is not specified for "
  1206. "equations of motion form [1].")
  1207. else:
  1208. return self._comb_implicit_mat
  1209. @property
  1210. def comb_implicit_rhs(self):
  1211. """Returns the column matrix, F, corresponding to the equations of
  1212. motion in implicit form (form [2]), M x' = F, where the kinematical
  1213. equations are included"""
  1214. if self._comb_implicit_rhs is None:
  1215. if self._dyn_implicit_rhs is not None:
  1216. kin_inter = self._kin_explicit_rhs
  1217. dyn_inter = self._dyn_implicit_rhs
  1218. self._comb_implicit_rhs = kin_inter.col_join(dyn_inter)
  1219. return self._comb_implicit_rhs
  1220. else:
  1221. raise AttributeError("comb_implicit_mat is not specified for "
  1222. "equations of motion in form [1].")
  1223. else:
  1224. return self._comb_implicit_rhs
  1225. def compute_explicit_form(self):
  1226. """If the explicit right hand side of the combined equations of motion
  1227. is to provided upon initialization, this method will calculate it. This
  1228. calculation can potentially take awhile to compute."""
  1229. if self._comb_explicit_rhs is not None:
  1230. raise AttributeError("comb_explicit_rhs is already formed.")
  1231. inter1 = getattr(self, 'kin_explicit_rhs', None)
  1232. if inter1 is not None:
  1233. inter2 = self._dyn_implicit_mat.LUsolve(self._dyn_implicit_rhs)
  1234. out = inter1.col_join(inter2)
  1235. else:
  1236. out = self._comb_implicit_mat.LUsolve(self._comb_implicit_rhs)
  1237. self._comb_explicit_rhs = out
  1238. @property
  1239. def comb_explicit_rhs(self):
  1240. """Returns the right hand side of the equations of motion in explicit
  1241. form, x' = F, where the kinematical equations are included"""
  1242. if self._comb_explicit_rhs is None:
  1243. raise AttributeError("Please run .combute_explicit_form before "
  1244. "attempting to access comb_explicit_rhs.")
  1245. else:
  1246. return self._comb_explicit_rhs
  1247. @property
  1248. def kin_explicit_rhs(self):
  1249. """Returns the right hand side of the kinematical equations in explicit
  1250. form, q' = G"""
  1251. if self._kin_explicit_rhs is None:
  1252. raise AttributeError("kin_explicit_rhs is not specified for "
  1253. "equations of motion form [1] or [2].")
  1254. else:
  1255. return self._kin_explicit_rhs
  1256. def dynamic_symbols(self):
  1257. """Returns a column matrix containing all of the symbols in the system
  1258. that depend on time"""
  1259. # Create a list of all of the expressions in the equations of motion
  1260. if self._comb_explicit_rhs is None:
  1261. eom_expressions = (self.comb_implicit_mat[:] +
  1262. self.comb_implicit_rhs[:])
  1263. else:
  1264. eom_expressions = (self._comb_explicit_rhs[:])
  1265. functions_of_time = set()
  1266. for expr in eom_expressions:
  1267. functions_of_time = functions_of_time.union(
  1268. find_dynamicsymbols(expr))
  1269. functions_of_time = functions_of_time.union(self._states)
  1270. return tuple(functions_of_time)
  1271. def constant_symbols(self):
  1272. """Returns a column matrix containing all of the symbols in the system
  1273. that do not depend on time"""
  1274. # Create a list of all of the expressions in the equations of motion
  1275. if self._comb_explicit_rhs is None:
  1276. eom_expressions = (self.comb_implicit_mat[:] +
  1277. self.comb_implicit_rhs[:])
  1278. else:
  1279. eom_expressions = (self._comb_explicit_rhs[:])
  1280. constants = set()
  1281. for expr in eom_expressions:
  1282. constants = constants.union(expr.free_symbols)
  1283. constants.remove(dynamicsymbols._t)
  1284. return tuple(constants)
  1285. @property
  1286. def bodies(self):
  1287. """Returns the bodies in the system"""
  1288. if self._bodies is None:
  1289. raise AttributeError("bodies were not specified for the system.")
  1290. else:
  1291. return self._bodies
  1292. @property
  1293. def loads(self):
  1294. """Returns the loads in the system"""
  1295. if self._loads is None:
  1296. raise AttributeError("loads were not specified for the system.")
  1297. else:
  1298. return self._loads