test_pathway.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. """Tests for the ``sympy.physics.mechanics.pathway.py`` module."""
  2. import pytest
  3. from sympy import (
  4. Rational,
  5. Symbol,
  6. cos,
  7. pi,
  8. sin,
  9. sqrt,
  10. )
  11. from sympy.physics.mechanics import (
  12. Force,
  13. LinearPathway,
  14. ObstacleSetPathway,
  15. PathwayBase,
  16. Point,
  17. ReferenceFrame,
  18. WrappingCylinder,
  19. WrappingGeometryBase,
  20. WrappingPathway,
  21. WrappingSphere,
  22. dynamicsymbols,
  23. )
  24. from sympy.simplify.simplify import simplify
  25. def _simplify_loads(loads):
  26. return [
  27. load.__class__(load.location, load.vector.simplify())
  28. for load in loads
  29. ]
  30. class TestLinearPathway:
  31. def test_is_pathway_base_subclass(self):
  32. assert issubclass(LinearPathway, PathwayBase)
  33. @staticmethod
  34. @pytest.mark.parametrize(
  35. 'args, kwargs',
  36. [
  37. ((Point('pA'), Point('pB')), {}),
  38. ]
  39. )
  40. def test_valid_constructor(args, kwargs):
  41. pointA, pointB = args
  42. instance = LinearPathway(*args, **kwargs)
  43. assert isinstance(instance, LinearPathway)
  44. assert hasattr(instance, 'attachments')
  45. assert len(instance.attachments) == 2
  46. assert instance.attachments[0] is pointA
  47. assert instance.attachments[1] is pointB
  48. assert isinstance(instance.attachments[0], Point)
  49. assert instance.attachments[0].name == 'pA'
  50. assert isinstance(instance.attachments[1], Point)
  51. assert instance.attachments[1].name == 'pB'
  52. @staticmethod
  53. @pytest.mark.parametrize(
  54. 'attachments',
  55. [
  56. (Point('pA'), ),
  57. (Point('pA'), Point('pB'), Point('pZ')),
  58. ]
  59. )
  60. def test_invalid_attachments_incorrect_number(attachments):
  61. with pytest.raises(ValueError):
  62. _ = LinearPathway(*attachments)
  63. @staticmethod
  64. @pytest.mark.parametrize(
  65. 'attachments',
  66. [
  67. (None, Point('pB')),
  68. (Point('pA'), None),
  69. ]
  70. )
  71. def test_invalid_attachments_not_point(attachments):
  72. with pytest.raises(TypeError):
  73. _ = LinearPathway(*attachments)
  74. @pytest.fixture(autouse=True)
  75. def _linear_pathway_fixture(self):
  76. self.N = ReferenceFrame('N')
  77. self.pA = Point('pA')
  78. self.pB = Point('pB')
  79. self.pathway = LinearPathway(self.pA, self.pB)
  80. self.q1 = dynamicsymbols('q1')
  81. self.q2 = dynamicsymbols('q2')
  82. self.q3 = dynamicsymbols('q3')
  83. self.q1d = dynamicsymbols('q1', 1)
  84. self.q2d = dynamicsymbols('q2', 1)
  85. self.q3d = dynamicsymbols('q3', 1)
  86. self.F = Symbol('F')
  87. def test_properties_are_immutable(self):
  88. instance = LinearPathway(self.pA, self.pB)
  89. with pytest.raises(AttributeError):
  90. instance.attachments = None
  91. with pytest.raises(TypeError):
  92. instance.attachments[0] = None
  93. with pytest.raises(TypeError):
  94. instance.attachments[1] = None
  95. def test_repr(self):
  96. pathway = LinearPathway(self.pA, self.pB)
  97. expected = 'LinearPathway(pA, pB)'
  98. assert repr(pathway) == expected
  99. def test_static_pathway_length(self):
  100. self.pB.set_pos(self.pA, 2*self.N.x)
  101. assert self.pathway.length == 2
  102. def test_static_pathway_extension_velocity(self):
  103. self.pB.set_pos(self.pA, 2*self.N.x)
  104. assert self.pathway.extension_velocity == 0
  105. def test_static_pathway_to_loads(self):
  106. self.pB.set_pos(self.pA, 2*self.N.x)
  107. expected = [
  108. (self.pA, - self.F*self.N.x),
  109. (self.pB, self.F*self.N.x),
  110. ]
  111. assert self.pathway.to_loads(self.F) == expected
  112. def test_2D_pathway_length(self):
  113. self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
  114. expected = 2*sqrt(self.q1**2)
  115. assert self.pathway.length == expected
  116. def test_2D_pathway_extension_velocity(self):
  117. self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
  118. expected = 2*sqrt(self.q1**2)*self.q1d/self.q1
  119. assert self.pathway.extension_velocity == expected
  120. def test_2D_pathway_to_loads(self):
  121. self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
  122. expected = [
  123. (self.pA, - self.F*(self.q1 / sqrt(self.q1**2))*self.N.x),
  124. (self.pB, self.F*(self.q1 / sqrt(self.q1**2))*self.N.x),
  125. ]
  126. assert self.pathway.to_loads(self.F) == expected
  127. def test_3D_pathway_length(self):
  128. self.pB.set_pos(
  129. self.pA,
  130. self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
  131. )
  132. expected = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
  133. assert simplify(self.pathway.length - expected) == 0
  134. def test_3D_pathway_extension_velocity(self):
  135. self.pB.set_pos(
  136. self.pA,
  137. self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
  138. )
  139. length = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
  140. expected = (
  141. self.q1*self.q1d/length
  142. + self.q2*self.q2d/length
  143. + 4*self.q3*self.q3d/length
  144. )
  145. assert simplify(self.pathway.extension_velocity - expected) == 0
  146. def test_3D_pathway_to_loads(self):
  147. self.pB.set_pos(
  148. self.pA,
  149. self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
  150. )
  151. length = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
  152. pO_force = (
  153. - self.F*self.q1*self.N.x/length
  154. + self.F*self.q2*self.N.y/length
  155. - 2*self.F*self.q3*self.N.z/length
  156. )
  157. pI_force = (
  158. self.F*self.q1*self.N.x/length
  159. - self.F*self.q2*self.N.y/length
  160. + 2*self.F*self.q3*self.N.z/length
  161. )
  162. expected = [
  163. (self.pA, pO_force),
  164. (self.pB, pI_force),
  165. ]
  166. assert self.pathway.to_loads(self.F) == expected
  167. class TestObstacleSetPathway:
  168. def test_is_pathway_base_subclass(self):
  169. assert issubclass(ObstacleSetPathway, PathwayBase)
  170. @staticmethod
  171. @pytest.mark.parametrize(
  172. 'num_attachments, attachments',
  173. [
  174. (3, [Point(name) for name in ('pO', 'pA', 'pI')]),
  175. (4, [Point(name) for name in ('pO', 'pA', 'pB', 'pI')]),
  176. (5, [Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pI')]),
  177. (6, [Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pD', 'pI')]),
  178. ]
  179. )
  180. def test_valid_constructor(num_attachments, attachments):
  181. instance = ObstacleSetPathway(*attachments)
  182. assert isinstance(instance, ObstacleSetPathway)
  183. assert hasattr(instance, 'attachments')
  184. assert len(instance.attachments) == num_attachments
  185. for attachment in instance.attachments:
  186. assert isinstance(attachment, Point)
  187. @staticmethod
  188. @pytest.mark.parametrize(
  189. 'attachments',
  190. [[Point('pO')], [Point('pO'), Point('pI')]],
  191. )
  192. def test_invalid_constructor_attachments_incorrect_number(attachments):
  193. with pytest.raises(ValueError):
  194. _ = ObstacleSetPathway(*attachments)
  195. @staticmethod
  196. @pytest.mark.parametrize(
  197. 'attachments',
  198. [
  199. (None, Point('pA'), Point('pI')),
  200. (Point('pO'), None, Point('pI')),
  201. (Point('pO'), Point('pA'), None),
  202. ]
  203. )
  204. def test_invalid_constructor_attachments_not_point(attachments):
  205. with pytest.raises(TypeError):
  206. _ = WrappingPathway(*attachments) # type: ignore
  207. def test_properties_are_immutable(self):
  208. pathway = ObstacleSetPathway(Point('pO'), Point('pA'), Point('pI'))
  209. with pytest.raises(AttributeError):
  210. pathway.attachments = None # type: ignore
  211. with pytest.raises(TypeError):
  212. pathway.attachments[0] = None # type: ignore
  213. with pytest.raises(TypeError):
  214. pathway.attachments[1] = None # type: ignore
  215. with pytest.raises(TypeError):
  216. pathway.attachments[-1] = None # type: ignore
  217. @staticmethod
  218. @pytest.mark.parametrize(
  219. 'attachments, expected',
  220. [
  221. (
  222. [Point(name) for name in ('pO', 'pA', 'pI')],
  223. 'ObstacleSetPathway(pO, pA, pI)'
  224. ),
  225. (
  226. [Point(name) for name in ('pO', 'pA', 'pB', 'pI')],
  227. 'ObstacleSetPathway(pO, pA, pB, pI)'
  228. ),
  229. (
  230. [Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pI')],
  231. 'ObstacleSetPathway(pO, pA, pB, pC, pI)'
  232. ),
  233. ]
  234. )
  235. def test_repr(attachments, expected):
  236. pathway = ObstacleSetPathway(*attachments)
  237. assert repr(pathway) == expected
  238. @pytest.fixture(autouse=True)
  239. def _obstacle_set_pathway_fixture(self):
  240. self.N = ReferenceFrame('N')
  241. self.pO = Point('pO')
  242. self.pI = Point('pI')
  243. self.pA = Point('pA')
  244. self.pB = Point('pB')
  245. self.q = dynamicsymbols('q')
  246. self.qd = dynamicsymbols('q', 1)
  247. self.F = Symbol('F')
  248. def test_static_pathway_length(self):
  249. self.pA.set_pos(self.pO, self.N.x)
  250. self.pB.set_pos(self.pO, self.N.y)
  251. self.pI.set_pos(self.pO, self.N.z)
  252. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  253. assert pathway.length == 1 + 2 * sqrt(2)
  254. def test_static_pathway_extension_velocity(self):
  255. self.pA.set_pos(self.pO, self.N.x)
  256. self.pB.set_pos(self.pO, self.N.y)
  257. self.pI.set_pos(self.pO, self.N.z)
  258. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  259. assert pathway.extension_velocity == 0
  260. def test_static_pathway_to_loads(self):
  261. self.pA.set_pos(self.pO, self.N.x)
  262. self.pB.set_pos(self.pO, self.N.y)
  263. self.pI.set_pos(self.pO, self.N.z)
  264. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  265. expected = [
  266. Force(self.pO, -self.F * self.N.x),
  267. Force(self.pA, self.F * self.N.x),
  268. Force(self.pA, self.F * sqrt(2) / 2 * (self.N.x - self.N.y)),
  269. Force(self.pB, self.F * sqrt(2) / 2 * (self.N.y - self.N.x)),
  270. Force(self.pB, self.F * sqrt(2) / 2 * (self.N.y - self.N.z)),
  271. Force(self.pI, self.F * sqrt(2) / 2 * (self.N.z - self.N.y)),
  272. ]
  273. assert pathway.to_loads(self.F) == expected
  274. def test_2D_pathway_length(self):
  275. self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
  276. self.pB.set_pos(
  277. self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
  278. )
  279. self.pI.set_pos(
  280. self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
  281. )
  282. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  283. expected = 2 * sqrt(2) + sqrt(2 + 2*cos(self.q))
  284. assert (pathway.length - expected).simplify() == 0
  285. def test_2D_pathway_extension_velocity(self):
  286. self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
  287. self.pB.set_pos(
  288. self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
  289. )
  290. self.pI.set_pos(
  291. self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
  292. )
  293. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  294. expected = - (sqrt(2) * sin(self.q) * self.qd) / (2 * sqrt(cos(self.q) + 1))
  295. assert (pathway.extension_velocity - expected).simplify() == 0
  296. def test_2D_pathway_to_loads(self):
  297. self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
  298. self.pB.set_pos(
  299. self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
  300. )
  301. self.pI.set_pos(
  302. self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
  303. )
  304. pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
  305. pO_pA_force_vec = sqrt(2) / 2 * (self.N.x + self.N.y)
  306. pA_pB_force_vec = (
  307. - sqrt(2 * cos(self.q) + 2) / 2 * self.N.x
  308. + sqrt(2) * sin(self.q) / (2 * sqrt(cos(self.q) + 1)) * self.N.y
  309. )
  310. pB_pI_force_vec = cos(self.q + pi/4) * self.N.x - sin(self.q + pi/4) * self.N.y
  311. expected = [
  312. Force(self.pO, self.F * pO_pA_force_vec),
  313. Force(self.pA, -self.F * pO_pA_force_vec),
  314. Force(self.pA, self.F * pA_pB_force_vec),
  315. Force(self.pB, -self.F * pA_pB_force_vec),
  316. Force(self.pB, self.F * pB_pI_force_vec),
  317. Force(self.pI, -self.F * pB_pI_force_vec),
  318. ]
  319. assert _simplify_loads(pathway.to_loads(self.F)) == expected
  320. class TestWrappingPathway:
  321. def test_is_pathway_base_subclass(self):
  322. assert issubclass(WrappingPathway, PathwayBase)
  323. @pytest.fixture(autouse=True)
  324. def _wrapping_pathway_fixture(self):
  325. self.pA = Point('pA')
  326. self.pB = Point('pB')
  327. self.r = Symbol('r', positive=True)
  328. self.pO = Point('pO')
  329. self.N = ReferenceFrame('N')
  330. self.ax = self.N.z
  331. self.sphere = WrappingSphere(self.r, self.pO)
  332. self.cylinder = WrappingCylinder(self.r, self.pO, self.ax)
  333. self.pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
  334. self.F = Symbol('F')
  335. def test_valid_constructor(self):
  336. instance = WrappingPathway(self.pA, self.pB, self.cylinder)
  337. assert isinstance(instance, WrappingPathway)
  338. assert hasattr(instance, 'attachments')
  339. assert len(instance.attachments) == 2
  340. assert isinstance(instance.attachments[0], Point)
  341. assert instance.attachments[0] == self.pA
  342. assert isinstance(instance.attachments[1], Point)
  343. assert instance.attachments[1] == self.pB
  344. assert hasattr(instance, 'geometry')
  345. assert isinstance(instance.geometry, WrappingGeometryBase)
  346. assert instance.geometry == self.cylinder
  347. @pytest.mark.parametrize(
  348. 'attachments',
  349. [
  350. (Point('pA'), ),
  351. (Point('pA'), Point('pB'), Point('pZ')),
  352. ]
  353. )
  354. def test_invalid_constructor_attachments_incorrect_number(self, attachments):
  355. with pytest.raises(TypeError):
  356. _ = WrappingPathway(*attachments, self.cylinder)
  357. @staticmethod
  358. @pytest.mark.parametrize(
  359. 'attachments',
  360. [
  361. (None, Point('pB')),
  362. (Point('pA'), None),
  363. ]
  364. )
  365. def test_invalid_constructor_attachments_not_point(attachments):
  366. with pytest.raises(TypeError):
  367. _ = WrappingPathway(*attachments)
  368. def test_invalid_constructor_geometry_is_not_supplied(self):
  369. with pytest.raises(TypeError):
  370. _ = WrappingPathway(self.pA, self.pB)
  371. @pytest.mark.parametrize(
  372. 'geometry',
  373. [
  374. Symbol('r'),
  375. dynamicsymbols('q'),
  376. ReferenceFrame('N'),
  377. ReferenceFrame('N').x,
  378. ]
  379. )
  380. def test_invalid_geometry_not_geometry(self, geometry):
  381. with pytest.raises(TypeError):
  382. _ = WrappingPathway(self.pA, self.pB, geometry)
  383. def test_attachments_property_is_immutable(self):
  384. with pytest.raises(TypeError):
  385. self.pathway.attachments[0] = self.pB
  386. with pytest.raises(TypeError):
  387. self.pathway.attachments[1] = self.pA
  388. def test_geometry_property_is_immutable(self):
  389. with pytest.raises(AttributeError):
  390. self.pathway.geometry = None
  391. def test_repr(self):
  392. expected = (
  393. f'WrappingPathway(pA, pB, '
  394. f'geometry={self.cylinder!r})'
  395. )
  396. assert repr(self.pathway) == expected
  397. @staticmethod
  398. def _expand_pos_to_vec(pos, frame):
  399. return sum(mag*unit for (mag, unit) in zip(pos, frame))
  400. @pytest.mark.parametrize(
  401. 'pA_vec, pB_vec, factor',
  402. [
  403. ((1, 0, 0), (0, 1, 0), pi/2),
  404. ((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0), 3*pi/4),
  405. ((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0), pi/3),
  406. ]
  407. )
  408. def test_static_pathway_on_sphere_length(self, pA_vec, pB_vec, factor):
  409. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  410. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  411. self.pA.set_pos(self.pO, self.r*pA_vec)
  412. self.pB.set_pos(self.pO, self.r*pB_vec)
  413. pathway = WrappingPathway(self.pA, self.pB, self.sphere)
  414. expected = factor*self.r
  415. assert simplify(pathway.length - expected) == 0
  416. @pytest.mark.parametrize(
  417. 'pA_vec, pB_vec, factor',
  418. [
  419. ((1, 0, 0), (0, 1, 0), Rational(1, 2)*pi),
  420. ((1, 0, 0), (-1, 0, 0), pi),
  421. ((-1, 0, 0), (1, 0, 0), pi),
  422. ((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0), 5*pi/4),
  423. ((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0), pi/3),
  424. (
  425. (0, 1, 0),
  426. (sqrt(2)*Rational(1, 2), -sqrt(2)*Rational(1, 2), 1),
  427. sqrt(1 + (Rational(5, 4)*pi)**2),
  428. ),
  429. (
  430. (1, 0, 0),
  431. (Rational(1, 2), sqrt(3)*Rational(1, 2), 1),
  432. sqrt(1 + (Rational(1, 3)*pi)**2),
  433. ),
  434. ]
  435. )
  436. def test_static_pathway_on_cylinder_length(self, pA_vec, pB_vec, factor):
  437. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  438. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  439. self.pA.set_pos(self.pO, self.r*pA_vec)
  440. self.pB.set_pos(self.pO, self.r*pB_vec)
  441. pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
  442. expected = factor*sqrt(self.r**2)
  443. assert simplify(pathway.length - expected) == 0
  444. @pytest.mark.parametrize(
  445. 'pA_vec, pB_vec',
  446. [
  447. ((1, 0, 0), (0, 1, 0)),
  448. ((0, 1, 0), (sqrt(2)*Rational(1, 2), -sqrt(2)*Rational(1, 2), 0)),
  449. ((1, 0, 0), (Rational(1, 2), sqrt(3)*Rational(1, 2), 0)),
  450. ]
  451. )
  452. def test_static_pathway_on_sphere_extension_velocity(self, pA_vec, pB_vec):
  453. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  454. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  455. self.pA.set_pos(self.pO, self.r*pA_vec)
  456. self.pB.set_pos(self.pO, self.r*pB_vec)
  457. pathway = WrappingPathway(self.pA, self.pB, self.sphere)
  458. assert pathway.extension_velocity == 0
  459. @pytest.mark.parametrize(
  460. 'pA_vec, pB_vec',
  461. [
  462. ((1, 0, 0), (0, 1, 0)),
  463. ((1, 0, 0), (-1, 0, 0)),
  464. ((-1, 0, 0), (1, 0, 0)),
  465. ((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0)),
  466. ((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0)),
  467. ((0, 1, 0), (sqrt(2)*Rational(1, 2), -sqrt(2)/2, 1)),
  468. ((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 1)),
  469. ]
  470. )
  471. def test_static_pathway_on_cylinder_extension_velocity(self, pA_vec, pB_vec):
  472. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  473. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  474. self.pA.set_pos(self.pO, self.r*pA_vec)
  475. self.pB.set_pos(self.pO, self.r*pB_vec)
  476. pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
  477. assert pathway.extension_velocity == 0
  478. @pytest.mark.parametrize(
  479. 'pA_vec, pB_vec, pA_vec_expected, pB_vec_expected, pO_vec_expected',
  480. (
  481. ((1, 0, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (-1, -1, 0)),
  482. (
  483. (0, 1, 0),
  484. (sqrt(2)/2, -sqrt(2)/2, 0),
  485. (1, 0, 0),
  486. (sqrt(2)/2, sqrt(2)/2, 0),
  487. (-1 - sqrt(2)/2, -sqrt(2)/2, 0)
  488. ),
  489. (
  490. (1, 0, 0),
  491. (Rational(1, 2), sqrt(3)/2, 0),
  492. (0, 1, 0),
  493. (sqrt(3)/2, -Rational(1, 2), 0),
  494. (-sqrt(3)/2, Rational(1, 2) - 1, 0),
  495. ),
  496. )
  497. )
  498. def test_static_pathway_on_sphere_to_loads(
  499. self,
  500. pA_vec,
  501. pB_vec,
  502. pA_vec_expected,
  503. pB_vec_expected,
  504. pO_vec_expected,
  505. ):
  506. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  507. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  508. self.pA.set_pos(self.pO, self.r*pA_vec)
  509. self.pB.set_pos(self.pO, self.r*pB_vec)
  510. pathway = WrappingPathway(self.pA, self.pB, self.sphere)
  511. pA_vec_expected = sum(
  512. mag*unit for (mag, unit) in zip(pA_vec_expected, self.N)
  513. )
  514. pB_vec_expected = sum(
  515. mag*unit for (mag, unit) in zip(pB_vec_expected, self.N)
  516. )
  517. pO_vec_expected = sum(
  518. mag*unit for (mag, unit) in zip(pO_vec_expected, self.N)
  519. )
  520. expected = [
  521. Force(self.pA, self.F*(self.r**3/sqrt(self.r**6))*pA_vec_expected),
  522. Force(self.pB, self.F*(self.r**3/sqrt(self.r**6))*pB_vec_expected),
  523. Force(self.pO, self.F*(self.r**3/sqrt(self.r**6))*pO_vec_expected),
  524. ]
  525. assert pathway.to_loads(self.F) == expected
  526. @pytest.mark.parametrize(
  527. 'pA_vec, pB_vec, pA_vec_expected, pB_vec_expected, pO_vec_expected',
  528. (
  529. ((1, 0, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (-1, -1, 0)),
  530. ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, 1, 0), (0, -2, 0)),
  531. ((-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, -1, 0), (0, 2, 0)),
  532. (
  533. (0, 1, 0),
  534. (sqrt(2)/2, -sqrt(2)/2, 0),
  535. (-1, 0, 0),
  536. (-sqrt(2)/2, -sqrt(2)/2, 0),
  537. (1 + sqrt(2)/2, sqrt(2)/2, 0)
  538. ),
  539. (
  540. (1, 0, 0),
  541. (Rational(1, 2), sqrt(3)/2, 0),
  542. (0, 1, 0),
  543. (sqrt(3)/2, -Rational(1, 2), 0),
  544. (-sqrt(3)/2, Rational(1, 2) - 1, 0),
  545. ),
  546. (
  547. (1, 0, 0),
  548. (sqrt(2)/2, sqrt(2)/2, 0),
  549. (0, 1, 0),
  550. (sqrt(2)/2, -sqrt(2)/2, 0),
  551. (-sqrt(2)/2, sqrt(2)/2 - 1, 0),
  552. ),
  553. ((0, 1, 0), (0, 1, 1), (0, 0, 1), (0, 0, -1), (0, 0, 0)),
  554. (
  555. (0, 1, 0),
  556. (sqrt(2)/2, -sqrt(2)/2, 1),
  557. (-5*pi/sqrt(16 + 25*pi**2), 0, 4/sqrt(16 + 25*pi**2)),
  558. (
  559. -5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
  560. -5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
  561. -4/sqrt(16 + 25*pi**2),
  562. ),
  563. (
  564. 5*(sqrt(2) + 2)*pi/(2*sqrt(16 + 25*pi**2)),
  565. 5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
  566. 0,
  567. ),
  568. ),
  569. )
  570. )
  571. def test_static_pathway_on_cylinder_to_loads(
  572. self,
  573. pA_vec,
  574. pB_vec,
  575. pA_vec_expected,
  576. pB_vec_expected,
  577. pO_vec_expected,
  578. ):
  579. pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
  580. pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
  581. self.pA.set_pos(self.pO, self.r*pA_vec)
  582. self.pB.set_pos(self.pO, self.r*pB_vec)
  583. pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
  584. pA_force_expected = self.F*self._expand_pos_to_vec(pA_vec_expected,
  585. self.N)
  586. pB_force_expected = self.F*self._expand_pos_to_vec(pB_vec_expected,
  587. self.N)
  588. pO_force_expected = self.F*self._expand_pos_to_vec(pO_vec_expected,
  589. self.N)
  590. expected = [
  591. Force(self.pA, pA_force_expected),
  592. Force(self.pB, pB_force_expected),
  593. Force(self.pO, pO_force_expected),
  594. ]
  595. assert _simplify_loads(pathway.to_loads(self.F)) == expected
  596. def test_2D_pathway_on_cylinder_length(self):
  597. q = dynamicsymbols('q')
  598. pA_pos = self.r*self.N.x
  599. pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
  600. self.pA.set_pos(self.pO, pA_pos)
  601. self.pB.set_pos(self.pO, pB_pos)
  602. expected = self.r*sqrt(q**2)
  603. assert simplify(self.pathway.length - expected) == 0
  604. def test_2D_pathway_on_cylinder_extension_velocity(self):
  605. q = dynamicsymbols('q')
  606. qd = dynamicsymbols('q', 1)
  607. pA_pos = self.r*self.N.x
  608. pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
  609. self.pA.set_pos(self.pO, pA_pos)
  610. self.pB.set_pos(self.pO, pB_pos)
  611. expected = self.r*(sqrt(q**2)/q)*qd
  612. assert simplify(self.pathway.extension_velocity - expected) == 0
  613. def test_2D_pathway_on_cylinder_to_loads(self):
  614. q = dynamicsymbols('q')
  615. pA_pos = self.r*self.N.x
  616. pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
  617. self.pA.set_pos(self.pO, pA_pos)
  618. self.pB.set_pos(self.pO, pB_pos)
  619. pA_force = self.F*self.N.y
  620. pB_force = self.F*(sin(q)*self.N.x - cos(q)*self.N.y)
  621. pO_force = self.F*(-sin(q)*self.N.x + (cos(q) - 1)*self.N.y)
  622. expected = [
  623. Force(self.pA, pA_force),
  624. Force(self.pB, pB_force),
  625. Force(self.pO, pO_force),
  626. ]
  627. loads = _simplify_loads(self.pathway.to_loads(self.F))
  628. assert loads == expected