pathway.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. """Implementations of pathways for use by actuators."""
  2. from abc import ABC, abstractmethod
  3. from sympy.core.singleton import S
  4. from sympy.physics.mechanics.loads import Force
  5. from sympy.physics.mechanics.wrapping_geometry import WrappingGeometryBase
  6. from sympy.physics.vector import Point, dynamicsymbols
  7. __all__ = ['PathwayBase', 'LinearPathway', 'ObstacleSetPathway',
  8. 'WrappingPathway']
  9. class PathwayBase(ABC):
  10. """Abstract base class for all pathway classes to inherit from.
  11. Notes
  12. =====
  13. Instances of this class cannot be directly instantiated by users. However,
  14. it can be used to created custom pathway types through subclassing.
  15. """
  16. def __init__(self, *attachments):
  17. """Initializer for ``PathwayBase``."""
  18. self.attachments = attachments
  19. @property
  20. def attachments(self):
  21. """The pair of points defining a pathway's ends."""
  22. return self._attachments
  23. @attachments.setter
  24. def attachments(self, attachments):
  25. if hasattr(self, '_attachments'):
  26. msg = (
  27. f'Can\'t set attribute `attachments` to {repr(attachments)} '
  28. f'as it is immutable.'
  29. )
  30. raise AttributeError(msg)
  31. if len(attachments) != 2:
  32. msg = (
  33. f'Value {repr(attachments)} passed to `attachments` was an '
  34. f'iterable of length {len(attachments)}, must be an iterable '
  35. f'of length 2.'
  36. )
  37. raise ValueError(msg)
  38. for i, point in enumerate(attachments):
  39. if not isinstance(point, Point):
  40. msg = (
  41. f'Value {repr(point)} passed to `attachments` at index '
  42. f'{i} was of type {type(point)}, must be {Point}.'
  43. )
  44. raise TypeError(msg)
  45. self._attachments = tuple(attachments)
  46. @property
  47. @abstractmethod
  48. def length(self):
  49. """An expression representing the pathway's length."""
  50. pass
  51. @property
  52. @abstractmethod
  53. def extension_velocity(self):
  54. """An expression representing the pathway's extension velocity."""
  55. pass
  56. @abstractmethod
  57. def to_loads(self, force):
  58. """Loads required by the equations of motion method classes.
  59. Explanation
  60. ===========
  61. ``KanesMethod`` requires a list of ``Point``-``Vector`` tuples to be
  62. passed to the ``loads`` parameters of its ``kanes_equations`` method
  63. when constructing the equations of motion. This method acts as a
  64. utility to produce the correctly-structred pairs of points and vectors
  65. required so that these can be easily concatenated with other items in
  66. the list of loads and passed to ``KanesMethod.kanes_equations``. These
  67. loads are also in the correct form to also be passed to the other
  68. equations of motion method classes, e.g. ``LagrangesMethod``.
  69. """
  70. pass
  71. def __repr__(self):
  72. """Default representation of a pathway."""
  73. attachments = ', '.join(str(a) for a in self.attachments)
  74. return f'{self.__class__.__name__}({attachments})'
  75. class LinearPathway(PathwayBase):
  76. """Linear pathway between a pair of attachment points.
  77. Explanation
  78. ===========
  79. A linear pathway forms a straight-line segment between two points and is
  80. the simplest pathway that can be formed. It will not interact with any
  81. other objects in the system, i.e. a ``LinearPathway`` will intersect other
  82. objects to ensure that the path between its two ends (its attachments) is
  83. the shortest possible.
  84. A linear pathway is made up of two points that can move relative to each
  85. other, and a pair of equal and opposite forces acting on the points. If the
  86. positive time-varying Euclidean distance between the two points is defined,
  87. then the "extension velocity" is the time derivative of this distance. The
  88. extension velocity is positive when the two points are moving away from
  89. each other and negative when moving closer to each other. The direction for
  90. the force acting on either point is determined by constructing a unit
  91. vector directed from the other point to this point. This establishes a sign
  92. convention such that a positive force magnitude tends to push the points
  93. apart. The following diagram shows the positive force sense and the
  94. distance between the points::
  95. P Q
  96. o<--- F --->o
  97. | |
  98. |<--l(t)--->|
  99. Examples
  100. ========
  101. >>> from sympy.physics.mechanics import LinearPathway
  102. To construct a pathway, two points are required to be passed to the
  103. ``attachments`` parameter as a ``tuple``.
  104. >>> from sympy.physics.mechanics import Point
  105. >>> pA, pB = Point('pA'), Point('pB')
  106. >>> linear_pathway = LinearPathway(pA, pB)
  107. >>> linear_pathway
  108. LinearPathway(pA, pB)
  109. The pathway created above isn't very interesting without the positions and
  110. velocities of its attachment points being described. Without this its not
  111. possible to describe how the pathway moves, i.e. its length or its
  112. extension velocity.
  113. >>> from sympy.physics.mechanics import ReferenceFrame
  114. >>> from sympy.physics.vector import dynamicsymbols
  115. >>> N = ReferenceFrame('N')
  116. >>> q = dynamicsymbols('q')
  117. >>> pB.set_pos(pA, q*N.x)
  118. >>> pB.pos_from(pA)
  119. q(t)*N.x
  120. A pathway's length can be accessed via its ``length`` attribute.
  121. >>> linear_pathway.length
  122. sqrt(q(t)**2)
  123. Note how what appears to be an overly-complex expression is returned. This
  124. is actually required as it ensures that a pathway's length is always
  125. positive.
  126. A pathway's extension velocity can be accessed similarly via its
  127. ``extension_velocity`` attribute.
  128. >>> linear_pathway.extension_velocity
  129. sqrt(q(t)**2)*Derivative(q(t), t)/q(t)
  130. Parameters
  131. ==========
  132. attachments : tuple[Point, Point]
  133. Pair of ``Point`` objects between which the linear pathway spans.
  134. Constructor expects two points to be passed, e.g.
  135. ``LinearPathway(Point('pA'), Point('pB'))``. More or fewer points will
  136. cause an error to be thrown.
  137. """
  138. def __init__(self, *attachments):
  139. """Initializer for ``LinearPathway``.
  140. Parameters
  141. ==========
  142. attachments : Point
  143. Pair of ``Point`` objects between which the linear pathway spans.
  144. Constructor expects two points to be passed, e.g.
  145. ``LinearPathway(Point('pA'), Point('pB'))``. More or fewer points
  146. will cause an error to be thrown.
  147. """
  148. super().__init__(*attachments)
  149. @property
  150. def length(self):
  151. """Exact analytical expression for the pathway's length."""
  152. return _point_pair_length(*self.attachments)
  153. @property
  154. def extension_velocity(self):
  155. """Exact analytical expression for the pathway's extension velocity."""
  156. return _point_pair_extension_velocity(*self.attachments)
  157. def to_loads(self, force):
  158. """Loads required by the equations of motion method classes.
  159. Explanation
  160. ===========
  161. ``KanesMethod`` requires a list of ``Point``-``Vector`` tuples to be
  162. passed to the ``loads`` parameters of its ``kanes_equations`` method
  163. when constructing the equations of motion. This method acts as a
  164. utility to produce the correctly-structred pairs of points and vectors
  165. required so that these can be easily concatenated with other items in
  166. the list of loads and passed to ``KanesMethod.kanes_equations``. These
  167. loads are also in the correct form to also be passed to the other
  168. equations of motion method classes, e.g. ``LagrangesMethod``.
  169. Examples
  170. ========
  171. The below example shows how to generate the loads produced in a linear
  172. actuator that produces an expansile force ``F``. First, create a linear
  173. actuator between two points separated by the coordinate ``q`` in the
  174. ``x`` direction of the global frame ``N``.
  175. >>> from sympy.physics.mechanics import (LinearPathway, Point,
  176. ... ReferenceFrame)
  177. >>> from sympy.physics.vector import dynamicsymbols
  178. >>> q = dynamicsymbols('q')
  179. >>> N = ReferenceFrame('N')
  180. >>> pA, pB = Point('pA'), Point('pB')
  181. >>> pB.set_pos(pA, q*N.x)
  182. >>> linear_pathway = LinearPathway(pA, pB)
  183. Now create a symbol ``F`` to describe the magnitude of the (expansile)
  184. force that will be produced along the pathway. The list of loads that
  185. ``KanesMethod`` requires can be produced by calling the pathway's
  186. ``to_loads`` method with ``F`` passed as the only argument.
  187. >>> from sympy import symbols
  188. >>> F = symbols('F')
  189. >>> linear_pathway.to_loads(F)
  190. [(pA, - F*q(t)/sqrt(q(t)**2)*N.x), (pB, F*q(t)/sqrt(q(t)**2)*N.x)]
  191. Parameters
  192. ==========
  193. force : Expr
  194. Magnitude of the force acting along the length of the pathway. As
  195. per the sign conventions for the pathway length, pathway extension
  196. velocity, and pair of point forces, if this ``Expr`` is positive
  197. then the force will act to push the pair of points away from one
  198. another (it is expansile).
  199. """
  200. relative_position = _point_pair_relative_position(*self.attachments)
  201. loads = [
  202. Force(self.attachments[0], -force*relative_position/self.length),
  203. Force(self.attachments[-1], force*relative_position/self.length),
  204. ]
  205. return loads
  206. class ObstacleSetPathway(PathwayBase):
  207. """Obstacle-set pathway between a set of attachment points.
  208. Explanation
  209. ===========
  210. An obstacle-set pathway forms a series of straight-line segment between
  211. pairs of consecutive points in a set of points. It is similar to multiple
  212. linear pathways joined end-to-end. It will not interact with any other
  213. objects in the system, i.e. an ``ObstacleSetPathway`` will intersect other
  214. objects to ensure that the path between its pairs of points (its
  215. attachments) is the shortest possible.
  216. Examples
  217. ========
  218. To construct an obstacle-set pathway, three or more points are required to
  219. be passed to the ``attachments`` parameter as a ``tuple``.
  220. >>> from sympy.physics.mechanics import ObstacleSetPathway, Point
  221. >>> pA, pB, pC, pD = Point('pA'), Point('pB'), Point('pC'), Point('pD')
  222. >>> obstacle_set_pathway = ObstacleSetPathway(pA, pB, pC, pD)
  223. >>> obstacle_set_pathway
  224. ObstacleSetPathway(pA, pB, pC, pD)
  225. The pathway created above isn't very interesting without the positions and
  226. velocities of its attachment points being described. Without this its not
  227. possible to describe how the pathway moves, i.e. its length or its
  228. extension velocity.
  229. >>> from sympy import cos, sin
  230. >>> from sympy.physics.mechanics import ReferenceFrame
  231. >>> from sympy.physics.vector import dynamicsymbols
  232. >>> N = ReferenceFrame('N')
  233. >>> q = dynamicsymbols('q')
  234. >>> pO = Point('pO')
  235. >>> pA.set_pos(pO, N.y)
  236. >>> pB.set_pos(pO, -N.x)
  237. >>> pC.set_pos(pA, cos(q) * N.x - (sin(q) + 1) * N.y)
  238. >>> pD.set_pos(pA, sin(q) * N.x + (cos(q) - 1) * N.y)
  239. >>> pB.pos_from(pA)
  240. - N.x - N.y
  241. >>> pC.pos_from(pA)
  242. cos(q(t))*N.x + (-sin(q(t)) - 1)*N.y
  243. >>> pD.pos_from(pA)
  244. sin(q(t))*N.x + (cos(q(t)) - 1)*N.y
  245. A pathway's length can be accessed via its ``length`` attribute.
  246. >>> obstacle_set_pathway.length.simplify()
  247. sqrt(2)*(sqrt(cos(q(t)) + 1) + 2)
  248. A pathway's extension velocity can be accessed similarly via its
  249. ``extension_velocity`` attribute.
  250. >>> obstacle_set_pathway.extension_velocity.simplify()
  251. -sqrt(2)*sin(q(t))*Derivative(q(t), t)/(2*sqrt(cos(q(t)) + 1))
  252. Parameters
  253. ==========
  254. attachments : tuple[Point, ...]
  255. The set of ``Point`` objects that define the segmented obstacle-set
  256. pathway.
  257. """
  258. def __init__(self, *attachments):
  259. """Initializer for ``ObstacleSetPathway``.
  260. Parameters
  261. ==========
  262. attachments : tuple[Point, ...]
  263. The set of ``Point`` objects that define the segmented obstacle-set
  264. pathway.
  265. """
  266. super().__init__(*attachments)
  267. @property
  268. def attachments(self):
  269. """The set of points defining a pathway's segmented path."""
  270. return self._attachments
  271. @attachments.setter
  272. def attachments(self, attachments):
  273. if hasattr(self, '_attachments'):
  274. msg = (
  275. f'Can\'t set attribute `attachments` to {repr(attachments)} '
  276. f'as it is immutable.'
  277. )
  278. raise AttributeError(msg)
  279. if len(attachments) <= 2:
  280. msg = (
  281. f'Value {repr(attachments)} passed to `attachments` was an '
  282. f'iterable of length {len(attachments)}, must be an iterable '
  283. f'of length 3 or greater.'
  284. )
  285. raise ValueError(msg)
  286. for i, point in enumerate(attachments):
  287. if not isinstance(point, Point):
  288. msg = (
  289. f'Value {repr(point)} passed to `attachments` at index '
  290. f'{i} was of type {type(point)}, must be {Point}.'
  291. )
  292. raise TypeError(msg)
  293. self._attachments = tuple(attachments)
  294. @property
  295. def length(self):
  296. """Exact analytical expression for the pathway's length."""
  297. length = S.Zero
  298. attachment_pairs = zip(self.attachments[:-1], self.attachments[1:])
  299. for attachment_pair in attachment_pairs:
  300. length += _point_pair_length(*attachment_pair)
  301. return length
  302. @property
  303. def extension_velocity(self):
  304. """Exact analytical expression for the pathway's extension velocity."""
  305. extension_velocity = S.Zero
  306. attachment_pairs = zip(self.attachments[:-1], self.attachments[1:])
  307. for attachment_pair in attachment_pairs:
  308. extension_velocity += _point_pair_extension_velocity(*attachment_pair)
  309. return extension_velocity
  310. def to_loads(self, force):
  311. """Loads required by the equations of motion method classes.
  312. Explanation
  313. ===========
  314. ``KanesMethod`` requires a list of ``Point``-``Vector`` tuples to be
  315. passed to the ``loads`` parameters of its ``kanes_equations`` method
  316. when constructing the equations of motion. This method acts as a
  317. utility to produce the correctly-structred pairs of points and vectors
  318. required so that these can be easily concatenated with other items in
  319. the list of loads and passed to ``KanesMethod.kanes_equations``. These
  320. loads are also in the correct form to also be passed to the other
  321. equations of motion method classes, e.g. ``LagrangesMethod``.
  322. Examples
  323. ========
  324. The below example shows how to generate the loads produced in an
  325. actuator that follows an obstacle-set pathway between four points and
  326. produces an expansile force ``F``. First, create a pair of reference
  327. frames, ``A`` and ``B``, in which the four points ``pA``, ``pB``,
  328. ``pC``, and ``pD`` will be located. The first two points in frame ``A``
  329. and the second two in frame ``B``. Frame ``B`` will also be oriented
  330. such that it relates to ``A`` via a rotation of ``q`` about an axis
  331. ``N.z`` in a global frame (``N.z``, ``A.z``, and ``B.z`` are parallel).
  332. >>> from sympy.physics.mechanics import (ObstacleSetPathway, Point,
  333. ... ReferenceFrame)
  334. >>> from sympy.physics.vector import dynamicsymbols
  335. >>> q = dynamicsymbols('q')
  336. >>> N = ReferenceFrame('N')
  337. >>> N = ReferenceFrame('N')
  338. >>> A = N.orientnew('A', 'axis', (0, N.x))
  339. >>> B = A.orientnew('B', 'axis', (q, N.z))
  340. >>> pO = Point('pO')
  341. >>> pA, pB, pC, pD = Point('pA'), Point('pB'), Point('pC'), Point('pD')
  342. >>> pA.set_pos(pO, A.x)
  343. >>> pB.set_pos(pO, -A.y)
  344. >>> pC.set_pos(pO, B.y)
  345. >>> pD.set_pos(pO, B.x)
  346. >>> obstacle_set_pathway = ObstacleSetPathway(pA, pB, pC, pD)
  347. Now create a symbol ``F`` to describe the magnitude of the (expansile)
  348. force that will be produced along the pathway. The list of loads that
  349. ``KanesMethod`` requires can be produced by calling the pathway's
  350. ``to_loads`` method with ``F`` passed as the only argument.
  351. >>> from sympy import Symbol
  352. >>> F = Symbol('F')
  353. >>> obstacle_set_pathway.to_loads(F)
  354. [(pA, sqrt(2)*F/2*A.x + sqrt(2)*F/2*A.y),
  355. (pB, - sqrt(2)*F/2*A.x - sqrt(2)*F/2*A.y),
  356. (pB, - F/sqrt(2*cos(q(t)) + 2)*A.y - F/sqrt(2*cos(q(t)) + 2)*B.y),
  357. (pC, F/sqrt(2*cos(q(t)) + 2)*A.y + F/sqrt(2*cos(q(t)) + 2)*B.y),
  358. (pC, - sqrt(2)*F/2*B.x + sqrt(2)*F/2*B.y),
  359. (pD, sqrt(2)*F/2*B.x - sqrt(2)*F/2*B.y)]
  360. Parameters
  361. ==========
  362. force : Expr
  363. The force acting along the length of the pathway. It is assumed
  364. that this ``Expr`` represents an expansile force.
  365. """
  366. loads = []
  367. attachment_pairs = zip(self.attachments[:-1], self.attachments[1:])
  368. for attachment_pair in attachment_pairs:
  369. relative_position = _point_pair_relative_position(*attachment_pair)
  370. length = _point_pair_length(*attachment_pair)
  371. loads.extend([
  372. Force(attachment_pair[0], -force*relative_position/length),
  373. Force(attachment_pair[1], force*relative_position/length),
  374. ])
  375. return loads
  376. class WrappingPathway(PathwayBase):
  377. """Pathway that wraps a geometry object.
  378. Explanation
  379. ===========
  380. A wrapping pathway interacts with a geometry object and forms a path that
  381. wraps smoothly along its surface. The wrapping pathway along the geometry
  382. object will be the geodesic that the geometry object defines based on the
  383. two points. It will not interact with any other objects in the system, i.e.
  384. a ``WrappingPathway`` will intersect other objects to ensure that the path
  385. between its two ends (its attachments) is the shortest possible.
  386. To explain the sign conventions used for pathway length, extension
  387. velocity, and direction of applied forces, we can ignore the geometry with
  388. which the wrapping pathway interacts. A wrapping pathway is made up of two
  389. points that can move relative to each other, and a pair of equal and
  390. opposite forces acting on the points. If the positive time-varying
  391. Euclidean distance between the two points is defined, then the "extension
  392. velocity" is the time derivative of this distance. The extension velocity
  393. is positive when the two points are moving away from each other and
  394. negative when moving closer to each other. The direction for the force
  395. acting on either point is determined by constructing a unit vector directed
  396. from the other point to this point. This establishes a sign convention such
  397. that a positive force magnitude tends to push the points apart. The
  398. following diagram shows the positive force sense and the distance between
  399. the points::
  400. P Q
  401. o<--- F --->o
  402. | |
  403. |<--l(t)--->|
  404. Examples
  405. ========
  406. >>> from sympy.physics.mechanics import WrappingPathway
  407. To construct a wrapping pathway, like other pathways, a pair of points must
  408. be passed, followed by an instance of a wrapping geometry class as a
  409. keyword argument. We'll use a cylinder with radius ``r`` and its axis
  410. parallel to ``N.x`` passing through a point ``pO``.
  411. >>> from sympy import symbols
  412. >>> from sympy.physics.mechanics import Point, ReferenceFrame, WrappingCylinder
  413. >>> r = symbols('r')
  414. >>> N = ReferenceFrame('N')
  415. >>> pA, pB, pO = Point('pA'), Point('pB'), Point('pO')
  416. >>> cylinder = WrappingCylinder(r, pO, N.x)
  417. >>> wrapping_pathway = WrappingPathway(pA, pB, cylinder)
  418. >>> wrapping_pathway
  419. WrappingPathway(pA, pB, geometry=WrappingCylinder(radius=r, point=pO,
  420. axis=N.x))
  421. Parameters
  422. ==========
  423. attachment_1 : Point
  424. First of the pair of ``Point`` objects between which the wrapping
  425. pathway spans.
  426. attachment_2 : Point
  427. Second of the pair of ``Point`` objects between which the wrapping
  428. pathway spans.
  429. geometry : WrappingGeometryBase
  430. Geometry about which the pathway wraps.
  431. """
  432. def __init__(self, attachment_1, attachment_2, geometry):
  433. """Initializer for ``WrappingPathway``.
  434. Parameters
  435. ==========
  436. attachment_1 : Point
  437. First of the pair of ``Point`` objects between which the wrapping
  438. pathway spans.
  439. attachment_2 : Point
  440. Second of the pair of ``Point`` objects between which the wrapping
  441. pathway spans.
  442. geometry : WrappingGeometryBase
  443. Geometry about which the pathway wraps.
  444. The geometry about which the pathway wraps.
  445. """
  446. super().__init__(attachment_1, attachment_2)
  447. self.geometry = geometry
  448. @property
  449. def geometry(self):
  450. """Geometry around which the pathway wraps."""
  451. return self._geometry
  452. @geometry.setter
  453. def geometry(self, geometry):
  454. if hasattr(self, '_geometry'):
  455. msg = (
  456. f'Can\'t set attribute `geometry` to {repr(geometry)} as it '
  457. f'is immutable.'
  458. )
  459. raise AttributeError(msg)
  460. if not isinstance(geometry, WrappingGeometryBase):
  461. msg = (
  462. f'Value {repr(geometry)} passed to `geometry` was of type '
  463. f'{type(geometry)}, must be {WrappingGeometryBase}.'
  464. )
  465. raise TypeError(msg)
  466. self._geometry = geometry
  467. @property
  468. def length(self):
  469. """Exact analytical expression for the pathway's length."""
  470. return self.geometry.geodesic_length(*self.attachments)
  471. @property
  472. def extension_velocity(self):
  473. """Exact analytical expression for the pathway's extension velocity."""
  474. return self.length.diff(dynamicsymbols._t)
  475. def to_loads(self, force):
  476. """Loads required by the equations of motion method classes.
  477. Explanation
  478. ===========
  479. ``KanesMethod`` requires a list of ``Point``-``Vector`` tuples to be
  480. passed to the ``loads`` parameters of its ``kanes_equations`` method
  481. when constructing the equations of motion. This method acts as a
  482. utility to produce the correctly-structred pairs of points and vectors
  483. required so that these can be easily concatenated with other items in
  484. the list of loads and passed to ``KanesMethod.kanes_equations``. These
  485. loads are also in the correct form to also be passed to the other
  486. equations of motion method classes, e.g. ``LagrangesMethod``.
  487. Examples
  488. ========
  489. The below example shows how to generate the loads produced in an
  490. actuator that produces an expansile force ``F`` while wrapping around a
  491. cylinder. First, create a cylinder with radius ``r`` and an axis
  492. parallel to the ``N.z`` direction of the global frame ``N`` that also
  493. passes through a point ``pO``.
  494. >>> from sympy import symbols
  495. >>> from sympy.physics.mechanics import (Point, ReferenceFrame,
  496. ... WrappingCylinder)
  497. >>> N = ReferenceFrame('N')
  498. >>> r = symbols('r', positive=True)
  499. >>> pO = Point('pO')
  500. >>> cylinder = WrappingCylinder(r, pO, N.z)
  501. Create the pathway of the actuator using the ``WrappingPathway`` class,
  502. defined to span between two points ``pA`` and ``pB``. Both points lie
  503. on the surface of the cylinder and the location of ``pB`` is defined
  504. relative to ``pA`` by the dynamics symbol ``q``.
  505. >>> from sympy import cos, sin
  506. >>> from sympy.physics.mechanics import WrappingPathway, dynamicsymbols
  507. >>> q = dynamicsymbols('q')
  508. >>> pA = Point('pA')
  509. >>> pB = Point('pB')
  510. >>> pA.set_pos(pO, r*N.x)
  511. >>> pB.set_pos(pO, r*(cos(q)*N.x + sin(q)*N.y))
  512. >>> pB.pos_from(pA)
  513. (r*cos(q(t)) - r)*N.x + r*sin(q(t))*N.y
  514. >>> pathway = WrappingPathway(pA, pB, cylinder)
  515. Now create a symbol ``F`` to describe the magnitude of the (expansile)
  516. force that will be produced along the pathway. The list of loads that
  517. ``KanesMethod`` requires can be produced by calling the pathway's
  518. ``to_loads`` method with ``F`` passed as the only argument.
  519. >>> F = symbols('F')
  520. >>> loads = pathway.to_loads(F)
  521. >>> [load.__class__(load.location, load.vector.simplify()) for load in loads]
  522. [(pA, F*N.y), (pB, F*sin(q(t))*N.x - F*cos(q(t))*N.y),
  523. (pO, - F*sin(q(t))*N.x + F*(cos(q(t)) - 1)*N.y)]
  524. Parameters
  525. ==========
  526. force : Expr
  527. Magnitude of the force acting along the length of the pathway. It
  528. is assumed that this ``Expr`` represents an expansile force.
  529. """
  530. pA, pB = self.attachments
  531. pO = self.geometry.point
  532. pA_force, pB_force = self.geometry.geodesic_end_vectors(pA, pB)
  533. pO_force = -(pA_force + pB_force)
  534. loads = [
  535. Force(pA, force * pA_force),
  536. Force(pB, force * pB_force),
  537. Force(pO, force * pO_force),
  538. ]
  539. return loads
  540. def __repr__(self):
  541. """Representation of a ``WrappingPathway``."""
  542. attachments = ', '.join(str(a) for a in self.attachments)
  543. return (
  544. f'{self.__class__.__name__}({attachments}, '
  545. f'geometry={self.geometry})'
  546. )
  547. def _point_pair_relative_position(point_1, point_2):
  548. """The relative position between a pair of points."""
  549. return point_2.pos_from(point_1)
  550. def _point_pair_length(point_1, point_2):
  551. """The length of the direct linear path between two points."""
  552. return _point_pair_relative_position(point_1, point_2).magnitude()
  553. def _point_pair_extension_velocity(point_1, point_2):
  554. """The extension velocity of the direct linear path between two points."""
  555. return _point_pair_length(point_1, point_2).diff(dynamicsymbols._t)