test_system_class.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. import pytest
  2. from sympy.core.symbol import symbols
  3. from sympy.core.sympify import sympify
  4. from sympy.functions.elementary.trigonometric import cos, sin
  5. from sympy.matrices.dense import eye, zeros
  6. from sympy.matrices.immutable import ImmutableMatrix
  7. from sympy.physics.mechanics import (
  8. Force, KanesMethod, LagrangesMethod, Particle, PinJoint, Point,
  9. PrismaticJoint, ReferenceFrame, RigidBody, Torque, TorqueActuator, System,
  10. dynamicsymbols)
  11. from sympy.simplify.simplify import simplify
  12. from sympy.solvers.solvers import solve
  13. t = dynamicsymbols._t # type: ignore
  14. q = dynamicsymbols('q:6') # type: ignore
  15. qd = dynamicsymbols('q:6', 1) # type: ignore
  16. u = dynamicsymbols('u:6') # type: ignore
  17. ua = dynamicsymbols('ua:3') # type: ignore
  18. class TestSystemBase:
  19. @pytest.fixture()
  20. def _empty_system_setup(self):
  21. self.system = System(ReferenceFrame('frame'), Point('fixed_point'))
  22. def _empty_system_check(self, exclude=()):
  23. matrices = ('q_ind', 'q_dep', 'q', 'u_ind', 'u_dep', 'u', 'u_aux',
  24. 'kdes', 'holonomic_constraints', 'nonholonomic_constraints')
  25. tuples = ('loads', 'bodies', 'joints', 'actuators')
  26. for attr in matrices:
  27. if attr not in exclude:
  28. assert getattr(self.system, attr)[:] == []
  29. for attr in tuples:
  30. if attr not in exclude:
  31. assert getattr(self.system, attr) == ()
  32. if 'eom_method' not in exclude:
  33. assert self.system.eom_method is None
  34. def _create_filled_system(self, with_speeds=True):
  35. self.system = System(ReferenceFrame('frame'), Point('fixed_point'))
  36. u = dynamicsymbols('u:6') if with_speeds else qd
  37. self.bodies = symbols('rb1:5', cls=RigidBody)
  38. self.joints = (
  39. PinJoint('J1', self.bodies[0], self.bodies[1], q[0], u[0]),
  40. PrismaticJoint('J2', self.bodies[1], self.bodies[2], q[1], u[1]),
  41. PinJoint('J3', self.bodies[2], self.bodies[3], q[2], u[2])
  42. )
  43. self.system.add_joints(*self.joints)
  44. self.system.add_coordinates(q[3], independent=[False])
  45. self.system.add_speeds(u[3], independent=False)
  46. if with_speeds:
  47. self.system.add_kdes(u[3] - qd[3])
  48. self.system.add_auxiliary_speeds(ua[0], ua[1])
  49. self.system.add_holonomic_constraints(q[2] - q[0] + q[1])
  50. self.system.add_nonholonomic_constraints(u[3] - qd[1] + u[2])
  51. self.system.u_ind = u[:2]
  52. self.system.u_dep = u[2:4]
  53. self.q_ind, self.q_dep = self.system.q_ind[:], self.system.q_dep[:]
  54. self.u_ind, self.u_dep = self.system.u_ind[:], self.system.u_dep[:]
  55. self.kdes = self.system.kdes[:]
  56. self.hc = self.system.holonomic_constraints[:]
  57. self.vc = self.system.velocity_constraints[:]
  58. self.nhc = self.system.nonholonomic_constraints[:]
  59. @pytest.fixture()
  60. def _filled_system_setup(self):
  61. self._create_filled_system(with_speeds=True)
  62. @pytest.fixture()
  63. def _filled_system_setup_no_speeds(self):
  64. self._create_filled_system(with_speeds=False)
  65. def _filled_system_check(self, exclude=()):
  66. assert 'q_ind' in exclude or self.system.q_ind[:] == q[:3]
  67. assert 'q_dep' in exclude or self.system.q_dep[:] == [q[3]]
  68. assert 'q' in exclude or self.system.q[:] == q[:4]
  69. assert 'u_ind' in exclude or self.system.u_ind[:] == u[:2]
  70. assert 'u_dep' in exclude or self.system.u_dep[:] == u[2:4]
  71. assert 'u' in exclude or self.system.u[:] == u[:4]
  72. assert 'u_aux' in exclude or self.system.u_aux[:] == ua[:2]
  73. assert 'kdes' in exclude or self.system.kdes[:] == [
  74. ui - qdi for ui, qdi in zip(u[:4], qd[:4])]
  75. assert ('holonomic_constraints' in exclude or
  76. self.system.holonomic_constraints[:] == [q[2] - q[0] + q[1]])
  77. assert ('nonholonomic_constraints' in exclude or
  78. self.system.nonholonomic_constraints[:] == [u[3] - qd[1] + u[2]]
  79. )
  80. assert ('velocity_constraints' in exclude or
  81. self.system.velocity_constraints[:] == [
  82. qd[2] - qd[0] + qd[1], u[3] - qd[1] + u[2]])
  83. assert ('bodies' in exclude or
  84. self.system.bodies == tuple(self.bodies))
  85. assert ('joints' in exclude or
  86. self.system.joints == tuple(self.joints))
  87. @pytest.fixture()
  88. def _moving_point_mass(self, _empty_system_setup):
  89. self.system.q_ind = q[0]
  90. self.system.u_ind = u[0]
  91. self.system.kdes = u[0] - q[0].diff(t)
  92. p = Particle('p', mass=symbols('m'))
  93. self.system.add_bodies(p)
  94. p.masscenter.set_pos(self.system.fixed_point, q[0] * self.system.x)
  95. class TestSystem(TestSystemBase):
  96. def test_empty_system(self, _empty_system_setup):
  97. self._empty_system_check()
  98. self.system.validate_system()
  99. def test_filled_system(self, _filled_system_setup):
  100. self._filled_system_check()
  101. self.system.validate_system()
  102. @pytest.mark.parametrize('frame', [None, ReferenceFrame('frame')])
  103. @pytest.mark.parametrize('fixed_point', [None, Point('fixed_point')])
  104. def test_init(self, frame, fixed_point):
  105. if fixed_point is None and frame is None:
  106. self.system = System()
  107. else:
  108. self.system = System(frame, fixed_point)
  109. if fixed_point is None:
  110. assert self.system.fixed_point.name == 'inertial_point'
  111. else:
  112. assert self.system.fixed_point == fixed_point
  113. if frame is None:
  114. assert self.system.frame.name == 'inertial_frame'
  115. else:
  116. assert self.system.frame == frame
  117. self._empty_system_check()
  118. assert isinstance(self.system.q_ind, ImmutableMatrix)
  119. assert isinstance(self.system.q_dep, ImmutableMatrix)
  120. assert isinstance(self.system.q, ImmutableMatrix)
  121. assert isinstance(self.system.u_ind, ImmutableMatrix)
  122. assert isinstance(self.system.u_dep, ImmutableMatrix)
  123. assert isinstance(self.system.u, ImmutableMatrix)
  124. assert isinstance(self.system.kdes, ImmutableMatrix)
  125. assert isinstance(self.system.holonomic_constraints, ImmutableMatrix)
  126. assert isinstance(self.system.nonholonomic_constraints, ImmutableMatrix)
  127. def test_from_newtonian_rigid_body(self):
  128. rb = RigidBody('body')
  129. self.system = System.from_newtonian(rb)
  130. assert self.system.fixed_point == rb.masscenter
  131. assert self.system.frame == rb.frame
  132. self._empty_system_check(exclude=('bodies',))
  133. self.system.bodies = (rb,)
  134. def test_from_newtonian_particle(self):
  135. pt = Particle('particle')
  136. with pytest.raises(TypeError):
  137. System.from_newtonian(pt)
  138. @pytest.mark.parametrize('args, kwargs, exp_q_ind, exp_q_dep, exp_q', [
  139. (q[:3], {}, q[:3], [], q[:3]),
  140. (q[:3], {'independent': True}, q[:3], [], q[:3]),
  141. (q[:3], {'independent': False}, [], q[:3], q[:3]),
  142. (q[:3], {'independent': [True, False, True]}, [q[0], q[2]], [q[1]],
  143. [q[0], q[2], q[1]]),
  144. ])
  145. def test_coordinates(self, _empty_system_setup, args, kwargs,
  146. exp_q_ind, exp_q_dep, exp_q):
  147. # Test add_coordinates
  148. self.system.add_coordinates(*args, **kwargs)
  149. assert self.system.q_ind[:] == exp_q_ind
  150. assert self.system.q_dep[:] == exp_q_dep
  151. assert self.system.q[:] == exp_q
  152. self._empty_system_check(exclude=('q_ind', 'q_dep', 'q'))
  153. # Test setter for q_ind and q_dep
  154. self.system.q_ind = exp_q_ind
  155. self.system.q_dep = exp_q_dep
  156. assert self.system.q_ind[:] == exp_q_ind
  157. assert self.system.q_dep[:] == exp_q_dep
  158. assert self.system.q[:] == exp_q
  159. self._empty_system_check(exclude=('q_ind', 'q_dep', 'q'))
  160. @pytest.mark.parametrize('func', ['add_coordinates', 'add_speeds'])
  161. @pytest.mark.parametrize('args, kwargs', [
  162. ((q[0], q[5]), {}),
  163. ((u[0], u[5]), {}),
  164. ((q[0],), {'independent': False}),
  165. ((u[0],), {'independent': False}),
  166. ((u[0], q[5]), {}),
  167. ((symbols('a'), q[5]), {}),
  168. ])
  169. def test_coordinates_speeds_invalid(self, _filled_system_setup, func, args,
  170. kwargs):
  171. with pytest.raises(ValueError):
  172. getattr(self.system, func)(*args, **kwargs)
  173. self._filled_system_check()
  174. @pytest.mark.parametrize('args, kwargs, exp_u_ind, exp_u_dep, exp_u', [
  175. (u[:3], {}, u[:3], [], u[:3]),
  176. (u[:3], {'independent': True}, u[:3], [], u[:3]),
  177. (u[:3], {'independent': False}, [], u[:3], u[:3]),
  178. (u[:3], {'independent': [True, False, True]}, [u[0], u[2]], [u[1]],
  179. [u[0], u[2], u[1]]),
  180. ])
  181. def test_speeds(self, _empty_system_setup, args, kwargs, exp_u_ind,
  182. exp_u_dep, exp_u):
  183. # Test add_speeds
  184. self.system.add_speeds(*args, **kwargs)
  185. assert self.system.u_ind[:] == exp_u_ind
  186. assert self.system.u_dep[:] == exp_u_dep
  187. assert self.system.u[:] == exp_u
  188. self._empty_system_check(exclude=('u_ind', 'u_dep', 'u'))
  189. # Test setter for u_ind and u_dep
  190. self.system.u_ind = exp_u_ind
  191. self.system.u_dep = exp_u_dep
  192. assert self.system.u_ind[:] == exp_u_ind
  193. assert self.system.u_dep[:] == exp_u_dep
  194. assert self.system.u[:] == exp_u
  195. self._empty_system_check(exclude=('u_ind', 'u_dep', 'u'))
  196. @pytest.mark.parametrize('args, kwargs, exp_u_aux', [
  197. (ua[:3], {}, ua[:3]),
  198. ])
  199. def test_auxiliary_speeds(self, _empty_system_setup, args, kwargs,
  200. exp_u_aux):
  201. # Test add_speeds
  202. self.system.add_auxiliary_speeds(*args, **kwargs)
  203. assert self.system.u_aux[:] == exp_u_aux
  204. self._empty_system_check(exclude=('u_aux',))
  205. # Test setter for u_ind and u_dep
  206. self.system.u_aux = exp_u_aux
  207. assert self.system.u_aux[:] == exp_u_aux
  208. self._empty_system_check(exclude=('u_aux',))
  209. @pytest.mark.parametrize('args, kwargs', [
  210. ((ua[2], q[0]), {}),
  211. ((ua[2], u[1]), {}),
  212. ((ua[0], ua[2]), {}),
  213. ((symbols('a'), ua[2]), {}),
  214. ])
  215. def test_auxiliary_invalid(self, _filled_system_setup, args, kwargs):
  216. with pytest.raises(ValueError):
  217. self.system.add_auxiliary_speeds(*args, **kwargs)
  218. self._filled_system_check()
  219. @pytest.mark.parametrize('prop, add_func, args, kwargs', [
  220. ('q_ind', 'add_coordinates', (q[0],), {}),
  221. ('q_dep', 'add_coordinates', (q[3],), {'independent': False}),
  222. ('u_ind', 'add_speeds', (u[0],), {}),
  223. ('u_dep', 'add_speeds', (u[3],), {'independent': False}),
  224. ('u_aux', 'add_auxiliary_speeds', (ua[2],), {}),
  225. ('kdes', 'add_kdes', (qd[0] - u[0],), {}),
  226. ('holonomic_constraints', 'add_holonomic_constraints',
  227. (q[0] - q[1],), {}),
  228. ('nonholonomic_constraints', 'add_nonholonomic_constraints',
  229. (u[0] - u[1],), {}),
  230. ('bodies', 'add_bodies', (RigidBody('body'),), {}),
  231. ('loads', 'add_loads', (Force(Point('P'), ReferenceFrame('N').x),), {}),
  232. ('actuators', 'add_actuators', (TorqueActuator(
  233. symbols('T'), ReferenceFrame('N').x, ReferenceFrame('A')),), {}),
  234. ])
  235. def test_add_after_reset(self, _filled_system_setup, prop, add_func, args,
  236. kwargs):
  237. setattr(self.system, prop, ())
  238. exclude = (prop, 'q', 'u')
  239. if prop in ('holonomic_constraints', 'nonholonomic_constraints'):
  240. exclude += ('velocity_constraints',)
  241. self._filled_system_check(exclude=exclude)
  242. assert list(getattr(self.system, prop)[:]) == []
  243. getattr(self.system, add_func)(*args, **kwargs)
  244. assert list(getattr(self.system, prop)[:]) == list(args)
  245. @pytest.mark.parametrize('prop, add_func, value, error', [
  246. ('q_ind', 'add_coordinates', symbols('a'), ValueError),
  247. ('q_dep', 'add_coordinates', symbols('a'), ValueError),
  248. ('u_ind', 'add_speeds', symbols('a'), ValueError),
  249. ('u_dep', 'add_speeds', symbols('a'), ValueError),
  250. ('u_aux', 'add_auxiliary_speeds', symbols('a'), ValueError),
  251. ('kdes', 'add_kdes', 7, TypeError),
  252. ('holonomic_constraints', 'add_holonomic_constraints', 7, TypeError),
  253. ('nonholonomic_constraints', 'add_nonholonomic_constraints', 7,
  254. TypeError),
  255. ('bodies', 'add_bodies', symbols('a'), TypeError),
  256. ('loads', 'add_loads', symbols('a'), TypeError),
  257. ('actuators', 'add_actuators', symbols('a'), TypeError),
  258. ])
  259. def test_type_error(self, _filled_system_setup, prop, add_func, value,
  260. error):
  261. with pytest.raises(error):
  262. getattr(self.system, add_func)(value)
  263. with pytest.raises(error):
  264. setattr(self.system, prop, value)
  265. self._filled_system_check()
  266. @pytest.mark.parametrize('args, kwargs, exp_kdes', [
  267. ((), {}, [ui - qdi for ui, qdi in zip(u[:4], qd[:4])]),
  268. ((u[4] - qd[4], u[5] - qd[5]), {},
  269. [ui - qdi for ui, qdi in zip(u[:6], qd[:6])]),
  270. ])
  271. def test_kdes(self, _filled_system_setup, args, kwargs, exp_kdes):
  272. # Test add_speeds
  273. self.system.add_kdes(*args, **kwargs)
  274. self._filled_system_check(exclude=('kdes',))
  275. assert self.system.kdes[:] == exp_kdes
  276. # Test setter for kdes
  277. self.system.kdes = exp_kdes
  278. self._filled_system_check(exclude=('kdes',))
  279. assert self.system.kdes[:] == exp_kdes
  280. @pytest.mark.parametrize('args, kwargs', [
  281. ((u[0] - qd[0], u[4] - qd[4]), {}),
  282. ((-(u[0] - qd[0]), u[4] - qd[4]), {}),
  283. (([u[0] - u[0], u[4] - qd[4]]), {}),
  284. ])
  285. def test_kdes_invalid(self, _filled_system_setup, args, kwargs):
  286. with pytest.raises(ValueError):
  287. self.system.add_kdes(*args, **kwargs)
  288. self._filled_system_check()
  289. @pytest.mark.parametrize('args, kwargs, exp_con', [
  290. ((), {}, [q[2] - q[0] + q[1]]),
  291. ((q[4] - q[5], q[5] + q[3]), {},
  292. [q[2] - q[0] + q[1], q[4] - q[5], q[5] + q[3]]),
  293. ])
  294. def test_holonomic_constraints(self, _filled_system_setup, args, kwargs,
  295. exp_con):
  296. exclude = ('holonomic_constraints', 'velocity_constraints')
  297. exp_vel_con = [c.diff(t) for c in exp_con] + self.nhc
  298. # Test add_holonomic_constraints
  299. self.system.add_holonomic_constraints(*args, **kwargs)
  300. self._filled_system_check(exclude=exclude)
  301. assert self.system.holonomic_constraints[:] == exp_con
  302. assert self.system.velocity_constraints[:] == exp_vel_con
  303. # Test setter for holonomic_constraints
  304. self.system.holonomic_constraints = exp_con
  305. self._filled_system_check(exclude=exclude)
  306. assert self.system.holonomic_constraints[:] == exp_con
  307. assert self.system.velocity_constraints[:] == exp_vel_con
  308. @pytest.mark.parametrize('args, kwargs', [
  309. ((q[2] - q[0] + q[1], q[4] - q[3]), {}),
  310. ((-(q[2] - q[0] + q[1]), q[4] - q[3]), {}),
  311. ((q[0] - q[0], q[4] - q[3]), {}),
  312. ])
  313. def test_holonomic_constraints_invalid(self, _filled_system_setup, args,
  314. kwargs):
  315. with pytest.raises(ValueError):
  316. self.system.add_holonomic_constraints(*args, **kwargs)
  317. self._filled_system_check()
  318. @pytest.mark.parametrize('args, kwargs, exp_con', [
  319. ((), {}, [u[3] - qd[1] + u[2]]),
  320. ((u[4] - u[5], u[5] + u[3]), {},
  321. [u[3] - qd[1] + u[2], u[4] - u[5], u[5] + u[3]]),
  322. ])
  323. def test_nonholonomic_constraints(self, _filled_system_setup, args, kwargs,
  324. exp_con):
  325. exclude = ('nonholonomic_constraints', 'velocity_constraints')
  326. exp_vel_con = self.vc[:len(self.hc)] + exp_con
  327. # Test add_nonholonomic_constraints
  328. self.system.add_nonholonomic_constraints(*args, **kwargs)
  329. self._filled_system_check(exclude=exclude)
  330. assert self.system.nonholonomic_constraints[:] == exp_con
  331. assert self.system.velocity_constraints[:] == exp_vel_con
  332. # Test setter for nonholonomic_constraints
  333. self.system.nonholonomic_constraints = exp_con
  334. self._filled_system_check(exclude=exclude)
  335. assert self.system.nonholonomic_constraints[:] == exp_con
  336. assert self.system.velocity_constraints[:] == exp_vel_con
  337. @pytest.mark.parametrize('args, kwargs', [
  338. ((u[3] - qd[1] + u[2], u[4] - u[3]), {}),
  339. ((-(u[3] - qd[1] + u[2]), u[4] - u[3]), {}),
  340. ((u[0] - u[0], u[4] - u[3]), {}),
  341. (([u[0] - u[0], u[4] - u[3]]), {}),
  342. ])
  343. def test_nonholonomic_constraints_invalid(self, _filled_system_setup, args,
  344. kwargs):
  345. with pytest.raises(ValueError):
  346. self.system.add_nonholonomic_constraints(*args, **kwargs)
  347. self._filled_system_check()
  348. @pytest.mark.parametrize('constraints, expected', [
  349. ([], []),
  350. (qd[2] - qd[0] + qd[1], [qd[2] - qd[0] + qd[1]]),
  351. ([qd[2] + qd[1], u[2] - u[1]], [qd[2] + qd[1], u[2] - u[1]]),
  352. ])
  353. def test_velocity_constraints_overwrite(self, _filled_system_setup,
  354. constraints, expected):
  355. self.system.velocity_constraints = constraints
  356. self._filled_system_check(exclude=('velocity_constraints',))
  357. assert self.system.velocity_constraints[:] == expected
  358. def test_velocity_constraints_back_to_auto(self, _filled_system_setup):
  359. self.system.velocity_constraints = qd[3] - qd[2]
  360. self._filled_system_check(exclude=('velocity_constraints',))
  361. assert self.system.velocity_constraints[:] == [qd[3] - qd[2]]
  362. self.system.velocity_constraints = None
  363. self._filled_system_check()
  364. def test_bodies(self, _filled_system_setup):
  365. rb1, rb2 = RigidBody('rb1'), RigidBody('rb2')
  366. p1, p2 = Particle('p1'), Particle('p2')
  367. self.system.add_bodies(rb1, p1)
  368. assert self.system.bodies == (*self.bodies, rb1, p1)
  369. self.system.add_bodies(p2)
  370. assert self.system.bodies == (*self.bodies, rb1, p1, p2)
  371. self.system.bodies = []
  372. assert self.system.bodies == ()
  373. self.system.bodies = p2
  374. assert self.system.bodies == (p2,)
  375. symb = symbols('symb')
  376. pytest.raises(TypeError, lambda: self.system.add_bodies(symb))
  377. pytest.raises(ValueError, lambda: self.system.add_bodies(p2))
  378. with pytest.raises(TypeError):
  379. self.system.bodies = (rb1, rb2, p1, p2, symb)
  380. assert self.system.bodies == (p2,)
  381. def test_add_loads(self):
  382. system = System()
  383. N, A = ReferenceFrame('N'), ReferenceFrame('A')
  384. rb1 = RigidBody('rb1', frame=N)
  385. mc1 = Point('mc1')
  386. p1 = Particle('p1', mc1)
  387. system.add_loads(Torque(rb1, N.x), (mc1, A.x), Force(p1, A.x))
  388. assert system.loads == ((N, N.x), (mc1, A.x), (mc1, A.x))
  389. system.loads = [(A, A.x)]
  390. assert system.loads == ((A, A.x),)
  391. pytest.raises(ValueError, lambda: system.add_loads((N, N.x, N.y)))
  392. with pytest.raises(TypeError):
  393. system.loads = (N, N.x)
  394. assert system.loads == ((A, A.x),)
  395. def test_add_actuators(self):
  396. system = System()
  397. N, A = ReferenceFrame('N'), ReferenceFrame('A')
  398. act1 = TorqueActuator(symbols('T1'), N.x, N)
  399. act2 = TorqueActuator(symbols('T2'), N.y, N, A)
  400. system.add_actuators(act1)
  401. assert system.actuators == (act1,)
  402. assert system.loads == ()
  403. system.actuators = (act2,)
  404. assert system.actuators == (act2,)
  405. def test_add_joints(self):
  406. q1, q2, q3, q4, u1, u2, u3 = dynamicsymbols('q1:5 u1:4')
  407. rb1, rb2, rb3, rb4, rb5 = symbols('rb1:6', cls=RigidBody)
  408. J1 = PinJoint('J1', rb1, rb2, q1, u1)
  409. J2 = PrismaticJoint('J2', rb2, rb3, q2, u2)
  410. J3 = PinJoint('J3', rb3, rb4, q3, u3)
  411. J_lag = PinJoint('J_lag', rb4, rb5, q4, q4.diff(t))
  412. system = System()
  413. system.add_joints(J1)
  414. assert system.joints == (J1,)
  415. assert system.bodies == (rb1, rb2)
  416. assert system.q_ind == ImmutableMatrix([q1])
  417. assert system.u_ind == ImmutableMatrix([u1])
  418. assert system.kdes == ImmutableMatrix([u1 - q1.diff(t)])
  419. system.add_bodies(rb4)
  420. system.add_coordinates(q3)
  421. system.add_kdes(u3 - q3.diff(t))
  422. system.add_joints(J3)
  423. assert system.joints == (J1, J3)
  424. assert system.bodies == (rb1, rb2, rb4, rb3)
  425. assert system.q_ind == ImmutableMatrix([q1, q3])
  426. assert system.u_ind == ImmutableMatrix([u1, u3])
  427. assert system.kdes == ImmutableMatrix(
  428. [u1 - q1.diff(t), u3 - q3.diff(t)])
  429. system.add_kdes(-(u2 - q2.diff(t)))
  430. system.add_joints(J2)
  431. assert system.joints == (J1, J3, J2)
  432. assert system.bodies == (rb1, rb2, rb4, rb3)
  433. assert system.q_ind == ImmutableMatrix([q1, q3, q2])
  434. assert system.u_ind == ImmutableMatrix([u1, u3, u2])
  435. assert system.kdes == ImmutableMatrix([u1 - q1.diff(t), u3 - q3.diff(t),
  436. -(u2 - q2.diff(t))])
  437. system.add_joints(J_lag)
  438. assert system.joints == (J1, J3, J2, J_lag)
  439. assert system.bodies == (rb1, rb2, rb4, rb3, rb5)
  440. assert system.q_ind == ImmutableMatrix([q1, q3, q2, q4])
  441. assert system.u_ind == ImmutableMatrix([u1, u3, u2, q4.diff(t)])
  442. assert system.kdes == ImmutableMatrix([u1 - q1.diff(t), u3 - q3.diff(t),
  443. -(u2 - q2.diff(t))])
  444. assert system.q_dep[:] == []
  445. assert system.u_dep[:] == []
  446. pytest.raises(ValueError, lambda: system.add_joints(J2))
  447. pytest.raises(TypeError, lambda: system.add_joints(rb1))
  448. def test_joints_setter(self, _filled_system_setup):
  449. self.system.joints = self.joints[1:]
  450. assert self.system.joints == self.joints[1:]
  451. self._filled_system_check(exclude=('joints',))
  452. self.system.q_ind = ()
  453. self.system.u_ind = ()
  454. self.system.joints = self.joints
  455. self._filled_system_check()
  456. @pytest.mark.parametrize('name, joint_index', [
  457. ('J1', 0),
  458. ('J2', 1),
  459. ('not_existing', None),
  460. ])
  461. def test_get_joint(self, _filled_system_setup, name, joint_index):
  462. joint = self.system.get_joint(name)
  463. if joint_index is None:
  464. assert joint is None
  465. else:
  466. assert joint == self.joints[joint_index]
  467. @pytest.mark.parametrize('name, body_index', [
  468. ('rb1', 0),
  469. ('rb3', 2),
  470. ('not_existing', None),
  471. ])
  472. def test_get_body(self, _filled_system_setup, name, body_index):
  473. body = self.system.get_body(name)
  474. if body_index is None:
  475. assert body is None
  476. else:
  477. assert body == self.bodies[body_index]
  478. @pytest.mark.parametrize('eom_method', [KanesMethod, LagrangesMethod])
  479. def test_form_eoms_calls_subclass(self, _moving_point_mass, eom_method):
  480. class MyMethod(eom_method):
  481. pass
  482. self.system.form_eoms(eom_method=MyMethod)
  483. assert isinstance(self.system.eom_method, MyMethod)
  484. @pytest.mark.parametrize('kwargs, expected', [
  485. ({}, ImmutableMatrix([[-1, 0], [0, symbols('m')]])),
  486. ({'explicit_kinematics': True}, ImmutableMatrix([[1, 0],
  487. [0, symbols('m')]])),
  488. ])
  489. def test_system_kane_form_eoms_kwargs(self, _moving_point_mass, kwargs,
  490. expected):
  491. self.system.form_eoms(**kwargs)
  492. assert self.system.mass_matrix_full == expected
  493. @pytest.mark.parametrize('kwargs, mm, gm', [
  494. ({}, ImmutableMatrix([[1, 0], [0, symbols('m')]]),
  495. ImmutableMatrix([q[0].diff(t), 0])),
  496. ])
  497. def test_system_lagrange_form_eoms_kwargs(self, _moving_point_mass, kwargs,
  498. mm, gm):
  499. self.system.form_eoms(eom_method=LagrangesMethod, **kwargs)
  500. assert self.system.mass_matrix_full == mm
  501. assert self.system.forcing_full == gm
  502. @pytest.mark.parametrize('eom_method, kwargs, error', [
  503. (KanesMethod, {'non_existing_kwarg': 1}, TypeError),
  504. (LagrangesMethod, {'non_existing_kwarg': 1}, TypeError),
  505. (KanesMethod, {'bodies': []}, ValueError),
  506. (KanesMethod, {'kd_eqs': []}, ValueError),
  507. (LagrangesMethod, {'bodies': []}, ValueError),
  508. (LagrangesMethod, {'Lagrangian': 1}, ValueError),
  509. ])
  510. def test_form_eoms_kwargs_errors(self, _empty_system_setup, eom_method,
  511. kwargs, error):
  512. self.system.q_ind = q[0]
  513. p = Particle('p', mass=symbols('m'))
  514. self.system.add_bodies(p)
  515. p.masscenter.set_pos(self.system.fixed_point, q[0] * self.system.x)
  516. with pytest.raises(error):
  517. self.system.form_eoms(eom_method=eom_method, **kwargs)
  518. class TestValidateSystem(TestSystemBase):
  519. @pytest.mark.parametrize('valid_method, invalid_method, with_speeds', [
  520. (KanesMethod, LagrangesMethod, True),
  521. (LagrangesMethod, KanesMethod, False)
  522. ])
  523. def test_only_valid(self, valid_method, invalid_method, with_speeds):
  524. self._create_filled_system(with_speeds=with_speeds)
  525. self.system.validate_system(valid_method)
  526. # Test Lagrange should fail due to the usage of generalized speeds
  527. with pytest.raises(ValueError):
  528. self.system.validate_system(invalid_method)
  529. @pytest.mark.parametrize('method, with_speeds', [
  530. (KanesMethod, True), (LagrangesMethod, False)])
  531. def test_missing_joint_coordinate(self, method, with_speeds):
  532. self._create_filled_system(with_speeds=with_speeds)
  533. self.system.q_ind = self.q_ind[1:]
  534. self.system.u_ind = self.u_ind[:-1]
  535. self.system.kdes = self.kdes[:-1]
  536. pytest.raises(ValueError, lambda: self.system.validate_system(method))
  537. def test_missing_joint_speed(self, _filled_system_setup):
  538. self.system.q_ind = self.q_ind[:-1]
  539. self.system.u_ind = self.u_ind[1:]
  540. self.system.kdes = self.kdes[:-1]
  541. pytest.raises(ValueError, lambda: self.system.validate_system())
  542. def test_missing_joint_kdes(self, _filled_system_setup):
  543. self.system.kdes = self.kdes[1:]
  544. pytest.raises(ValueError, lambda: self.system.validate_system())
  545. def test_negative_joint_kdes(self, _filled_system_setup):
  546. self.system.kdes = [-self.kdes[0]] + self.kdes[1:]
  547. self.system.validate_system()
  548. @pytest.mark.parametrize('method, with_speeds', [
  549. (KanesMethod, True), (LagrangesMethod, False)])
  550. def test_missing_holonomic_constraint(self, method, with_speeds):
  551. self._create_filled_system(with_speeds=with_speeds)
  552. self.system.holonomic_constraints = []
  553. self.system.nonholonomic_constraints = self.nhc + [
  554. self.u_ind[1] - self.u_dep[0] + self.u_ind[0]]
  555. pytest.raises(ValueError, lambda: self.system.validate_system(method))
  556. self.system.q_dep = []
  557. self.system.q_ind = self.q_ind + self.q_dep
  558. self.system.validate_system(method)
  559. def test_missing_nonholonomic_constraint(self, _filled_system_setup):
  560. self.system.nonholonomic_constraints = []
  561. pytest.raises(ValueError, lambda: self.system.validate_system())
  562. self.system.u_dep = self.u_dep[1]
  563. self.system.u_ind = self.u_ind + [self.u_dep[0]]
  564. self.system.validate_system()
  565. def test_number_of_coordinates_speeds(self, _filled_system_setup):
  566. # Test more speeds than coordinates
  567. self.system.u_ind = self.u_ind + [u[5]]
  568. self.system.kdes = self.kdes + [u[5] - qd[5]]
  569. self.system.validate_system()
  570. # Test more coordinates than speeds
  571. self.system.q_ind = self.q_ind
  572. self.system.u_ind = self.u_ind[:-1]
  573. self.system.kdes = self.kdes[:-1]
  574. pytest.raises(ValueError, lambda: self.system.validate_system())
  575. def test_number_of_kdes(self, _filled_system_setup):
  576. # Test wrong number of kdes
  577. self.system.kdes = self.kdes[:-1]
  578. pytest.raises(ValueError, lambda: self.system.validate_system())
  579. self.system.kdes = self.kdes + [u[2] + u[1] - qd[2]]
  580. pytest.raises(ValueError, lambda: self.system.validate_system())
  581. def test_duplicates(self, _filled_system_setup):
  582. # This is basically a redundant feature, which should never fail
  583. self.system.validate_system(check_duplicates=True)
  584. def test_speeds_in_lagrange(self, _filled_system_setup_no_speeds):
  585. self.system.u_ind = u[:len(self.u_ind)]
  586. with pytest.raises(ValueError):
  587. self.system.validate_system(LagrangesMethod)
  588. self.system.u_ind = []
  589. self.system.validate_system(LagrangesMethod)
  590. self.system.u_aux = ua
  591. with pytest.raises(ValueError):
  592. self.system.validate_system(LagrangesMethod)
  593. self.system.u_aux = []
  594. self.system.validate_system(LagrangesMethod)
  595. self.system.add_joints(
  596. PinJoint('Ju', RigidBody('rbu1'), RigidBody('rbu2')))
  597. self.system.u_ind = []
  598. with pytest.raises(ValueError):
  599. self.system.validate_system(LagrangesMethod)
  600. class TestSystemExamples:
  601. def test_cart_pendulum_kanes(self):
  602. # This example is the same as in the top documentation of System
  603. # Added a spring to the cart
  604. g, l, mc, mp, k = symbols('g l mc mp k')
  605. F, qp, qc, up, uc = dynamicsymbols('F qp qc up uc')
  606. rail = RigidBody('rail')
  607. cart = RigidBody('cart', mass=mc)
  608. bob = Particle('bob', mass=mp)
  609. bob_frame = ReferenceFrame('bob_frame')
  610. system = System.from_newtonian(rail)
  611. assert system.bodies == (rail,)
  612. assert system.frame == rail.frame
  613. assert system.fixed_point == rail.masscenter
  614. slider = PrismaticJoint('slider', rail, cart, qc, uc, joint_axis=rail.x)
  615. pin = PinJoint('pin', cart, bob, qp, up, joint_axis=cart.z,
  616. child_interframe=bob_frame, child_point=l * bob_frame.y)
  617. system.add_joints(slider, pin)
  618. assert system.joints == (slider, pin)
  619. assert system.get_joint('slider') == slider
  620. assert system.get_body('bob') == bob
  621. system.apply_uniform_gravity(-g * system.y)
  622. system.add_loads((cart.masscenter, F * rail.x))
  623. system.add_actuators(TorqueActuator(k * qp, cart.z, bob_frame, cart))
  624. system.validate_system()
  625. system.form_eoms()
  626. assert isinstance(system.eom_method, KanesMethod)
  627. assert (simplify(system.mass_matrix - ImmutableMatrix(
  628. [[mp + mc, mp * l * cos(qp)], [mp * l * cos(qp), mp * l ** 2]]))
  629. == zeros(2, 2))
  630. assert (simplify(system.forcing - ImmutableMatrix([
  631. [mp * l * up ** 2 * sin(qp) + F],
  632. [-mp * g * l * sin(qp) + k * qp]])) == zeros(2, 1))
  633. system.add_holonomic_constraints(
  634. sympify(bob.masscenter.pos_from(rail.masscenter).dot(system.x)))
  635. assert system.eom_method is None
  636. system.q_ind, system.q_dep = qp, qc
  637. system.u_ind, system.u_dep = up, uc
  638. system.validate_system()
  639. # Computed solution based on manually solving the constraints
  640. subs = {qc: -l * sin(qp),
  641. uc: -l * cos(qp) * up,
  642. uc.diff(t): l * (up ** 2 * sin(qp) - up.diff(t) * cos(qp))}
  643. upd_expected = (
  644. (-g * mp * sin(qp) + k * qp / l + l * mc * sin(2 * qp) * up ** 2 / 2
  645. - l * mp * sin(2 * qp) * up ** 2 / 2 - F * cos(qp)) /
  646. (l * (mc * cos(qp) ** 2 + mp * sin(qp) ** 2)))
  647. upd_sol = tuple(solve(system.form_eoms().xreplace(subs),
  648. up.diff(t)).values())[0]
  649. assert simplify(upd_sol - upd_expected) == 0
  650. assert isinstance(system.eom_method, KanesMethod)
  651. # Test other output
  652. Mk = -ImmutableMatrix([[0, 1], [1, 0]])
  653. gk = -ImmutableMatrix([uc, up])
  654. Md = ImmutableMatrix([[-l ** 2 * mp * cos(qp) ** 2 + l ** 2 * mp,
  655. l * mp * cos(qp) - l * (mc + mp) * cos(qp)],
  656. [l * cos(qp), 1]])
  657. gd = ImmutableMatrix(
  658. [[-g * l * mp * sin(qp) + k * qp - l ** 2 * mp * up ** 2 * sin(qp) *
  659. cos(qp) - l * F * cos(qp)], [l * up ** 2 * sin(qp)]])
  660. Mm = (Mk.row_join(zeros(2, 2))).col_join(zeros(2, 2).row_join(Md))
  661. gm = gk.col_join(gd)
  662. assert simplify(system.mass_matrix - Md) == zeros(2, 2)
  663. assert simplify(system.forcing - gd) == zeros(2, 1)
  664. assert simplify(system.mass_matrix_full - Mm) == zeros(4, 4)
  665. assert simplify(system.forcing_full - gm) == zeros(4, 1)
  666. def test_cart_pendulum_lagrange(self):
  667. # Lagrange version of test_cart_pendulus_kanes
  668. # Added a spring to the cart
  669. g, l, mc, mp, k = symbols('g l mc mp k')
  670. F, qp, qc = dynamicsymbols('F qp qc')
  671. qpd, qcd = dynamicsymbols('qp qc', 1)
  672. rail = RigidBody('rail')
  673. cart = RigidBody('cart', mass=mc)
  674. bob = Particle('bob', mass=mp)
  675. bob_frame = ReferenceFrame('bob_frame')
  676. system = System.from_newtonian(rail)
  677. assert system.bodies == (rail,)
  678. assert system.frame == rail.frame
  679. assert system.fixed_point == rail.masscenter
  680. slider = PrismaticJoint('slider', rail, cart, qc, qcd,
  681. joint_axis=rail.x)
  682. pin = PinJoint('pin', cart, bob, qp, qpd, joint_axis=cart.z,
  683. child_interframe=bob_frame, child_point=l * bob_frame.y)
  684. system.add_joints(slider, pin)
  685. assert system.joints == (slider, pin)
  686. assert system.get_joint('slider') == slider
  687. assert system.get_body('bob') == bob
  688. for body in system.bodies:
  689. body.potential_energy = body.mass * g * body.masscenter.pos_from(
  690. system.fixed_point).dot(system.y)
  691. system.add_loads((cart.masscenter, F * rail.x))
  692. system.add_actuators(TorqueActuator(k * qp, cart.z, bob_frame, cart))
  693. system.validate_system(LagrangesMethod)
  694. system.form_eoms(LagrangesMethod)
  695. assert (simplify(system.mass_matrix - ImmutableMatrix(
  696. [[mp + mc, mp * l * cos(qp)], [mp * l * cos(qp), mp * l ** 2]]))
  697. == zeros(2, 2))
  698. assert (simplify(system.forcing - ImmutableMatrix([
  699. [mp * l * qpd ** 2 * sin(qp) + F], [-mp * g * l * sin(qp) + k * qp]]
  700. )) == zeros(2, 1))
  701. system.add_holonomic_constraints(
  702. sympify(bob.masscenter.pos_from(rail.masscenter).dot(system.x)))
  703. assert system.eom_method is None
  704. system.q_ind, system.q_dep = qp, qc
  705. # Computed solution based on manually solving the constraints
  706. subs = {qc: -l * sin(qp),
  707. qcd: -l * cos(qp) * qpd,
  708. qcd.diff(t): l * (qpd ** 2 * sin(qp) - qpd.diff(t) * cos(qp))}
  709. qpdd_expected = (
  710. (-g * mp * sin(qp) + k * qp / l + l * mc * sin(2 * qp) * qpd ** 2 /
  711. 2 - l * mp * sin(2 * qp) * qpd ** 2 / 2 - F * cos(qp)) /
  712. (l * (mc * cos(qp) ** 2 + mp * sin(qp) ** 2)))
  713. eoms = system.form_eoms(LagrangesMethod)
  714. lam1 = system.eom_method.lam_vec[0]
  715. lam1_sol = system.eom_method.solve_multipliers()[lam1]
  716. qpdd_sol = solve(eoms[0].xreplace({lam1: lam1_sol}).xreplace(subs),
  717. qpd.diff(t))[0]
  718. assert simplify(qpdd_sol - qpdd_expected) == 0
  719. assert isinstance(system.eom_method, LagrangesMethod)
  720. # Test other output
  721. Md = ImmutableMatrix([[l ** 2 * mp, l * mp * cos(qp), -l * cos(qp)],
  722. [l * mp * cos(qp), mc + mp, -1]])
  723. gd = ImmutableMatrix(
  724. [[-g * l * mp * sin(qp) + k * qp],
  725. [l * mp * sin(qp) * qpd ** 2 + F]])
  726. Mm = (eye(2).row_join(zeros(2, 3))).col_join(zeros(3, 2).row_join(
  727. Md.col_join(ImmutableMatrix([l * cos(qp), 1, 0]).T)))
  728. gm = ImmutableMatrix([qpd, qcd] + gd[:] + [l * sin(qp) * qpd ** 2])
  729. assert simplify(system.mass_matrix - Md) == zeros(2, 3)
  730. assert simplify(system.forcing - gd) == zeros(2, 1)
  731. assert simplify(system.mass_matrix_full - Mm) == zeros(5, 5)
  732. assert simplify(system.forcing_full - gm) == zeros(5, 1)
  733. def test_box_on_ground(self):
  734. # Particle sliding on ground with friction. The applied force is assumed
  735. # to be positive and to be higher than the friction force.
  736. g, m, mu = symbols('g m mu')
  737. q, u, ua = dynamicsymbols('q u ua')
  738. N, F = dynamicsymbols('N F', positive=True)
  739. P = Particle("P", mass=m)
  740. system = System()
  741. system.add_bodies(P)
  742. P.masscenter.set_pos(system.fixed_point, q * system.x)
  743. P.masscenter.set_vel(system.frame, u * system.x + ua * system.y)
  744. system.q_ind, system.u_ind, system.u_aux = [q], [u], [ua]
  745. system.kdes = [q.diff(t) - u]
  746. system.apply_uniform_gravity(-g * system.y)
  747. system.add_loads(
  748. Force(P, N * system.y),
  749. Force(P, F * system.x - mu * N * system.x))
  750. system.validate_system()
  751. system.form_eoms()
  752. # Test other output
  753. Mk = ImmutableMatrix([1])
  754. gk = ImmutableMatrix([u])
  755. Md = ImmutableMatrix([m])
  756. gd = ImmutableMatrix([F - mu * N])
  757. Mm = (Mk.row_join(zeros(1, 1))).col_join(zeros(1, 1).row_join(Md))
  758. gm = gk.col_join(gd)
  759. aux_eqs = ImmutableMatrix([N - m * g])
  760. assert simplify(system.mass_matrix - Md) == zeros(1, 1)
  761. assert simplify(system.forcing - gd) == zeros(1, 1)
  762. assert simplify(system.mass_matrix_full - Mm) == zeros(2, 2)
  763. assert simplify(system.forcing_full - gm) == zeros(2, 1)
  764. assert simplify(system.eom_method.auxiliary_eqs - aux_eqs
  765. ) == zeros(1, 1)