frame.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435
  1. from sympy.core.backend import (diff, expand, sin, cos, sympify, eye, zeros,
  2. ImmutableMatrix as Matrix, MatrixBase)
  3. from sympy.core.symbol import Symbol
  4. from sympy.simplify.trigsimp import trigsimp
  5. from sympy.physics.vector.vector import Vector, _check_vector
  6. from sympy.utilities.misc import translate
  7. from warnings import warn
  8. __all__ = ['CoordinateSym', 'ReferenceFrame']
  9. class CoordinateSym(Symbol):
  10. """
  11. A coordinate symbol/base scalar associated wrt a Reference Frame.
  12. Ideally, users should not instantiate this class. Instances of
  13. this class must only be accessed through the corresponding frame
  14. as 'frame[index]'.
  15. CoordinateSyms having the same frame and index parameters are equal
  16. (even though they may be instantiated separately).
  17. Parameters
  18. ==========
  19. name : string
  20. The display name of the CoordinateSym
  21. frame : ReferenceFrame
  22. The reference frame this base scalar belongs to
  23. index : 0, 1 or 2
  24. The index of the dimension denoted by this coordinate variable
  25. Examples
  26. ========
  27. >>> from sympy.physics.vector import ReferenceFrame, CoordinateSym
  28. >>> A = ReferenceFrame('A')
  29. >>> A[1]
  30. A_y
  31. >>> type(A[0])
  32. <class 'sympy.physics.vector.frame.CoordinateSym'>
  33. >>> a_y = CoordinateSym('a_y', A, 1)
  34. >>> a_y == A[1]
  35. True
  36. """
  37. def __new__(cls, name, frame, index):
  38. # We can't use the cached Symbol.__new__ because this class depends on
  39. # frame and index, which are not passed to Symbol.__xnew__.
  40. assumptions = {}
  41. super()._sanitize(assumptions, cls)
  42. obj = super().__xnew__(cls, name, **assumptions)
  43. _check_frame(frame)
  44. if index not in range(0, 3):
  45. raise ValueError("Invalid index specified")
  46. obj._id = (frame, index)
  47. return obj
  48. @property
  49. def frame(self):
  50. return self._id[0]
  51. def __eq__(self, other):
  52. # Check if the other object is a CoordinateSym of the same frame and
  53. # same index
  54. if isinstance(other, CoordinateSym):
  55. if other._id == self._id:
  56. return True
  57. return False
  58. def __ne__(self, other):
  59. return not self == other
  60. def __hash__(self):
  61. return (self._id[0].__hash__(), self._id[1]).__hash__()
  62. class ReferenceFrame:
  63. """A reference frame in classical mechanics.
  64. ReferenceFrame is a class used to represent a reference frame in classical
  65. mechanics. It has a standard basis of three unit vectors in the frame's
  66. x, y, and z directions.
  67. It also can have a rotation relative to a parent frame; this rotation is
  68. defined by a direction cosine matrix relating this frame's basis vectors to
  69. the parent frame's basis vectors. It can also have an angular velocity
  70. vector, defined in another frame.
  71. """
  72. _count = 0
  73. def __init__(self, name, indices=None, latexs=None, variables=None):
  74. """ReferenceFrame initialization method.
  75. A ReferenceFrame has a set of orthonormal basis vectors, along with
  76. orientations relative to other ReferenceFrames and angular velocities
  77. relative to other ReferenceFrames.
  78. Parameters
  79. ==========
  80. indices : tuple of str
  81. Enables the reference frame's basis unit vectors to be accessed by
  82. Python's square bracket indexing notation using the provided three
  83. indice strings and alters the printing of the unit vectors to
  84. reflect this choice.
  85. latexs : tuple of str
  86. Alters the LaTeX printing of the reference frame's basis unit
  87. vectors to the provided three valid LaTeX strings.
  88. Examples
  89. ========
  90. >>> from sympy.physics.vector import ReferenceFrame, vlatex
  91. >>> N = ReferenceFrame('N')
  92. >>> N.x
  93. N.x
  94. >>> O = ReferenceFrame('O', indices=('1', '2', '3'))
  95. >>> O.x
  96. O['1']
  97. >>> O['1']
  98. O['1']
  99. >>> P = ReferenceFrame('P', latexs=('A1', 'A2', 'A3'))
  100. >>> vlatex(P.x)
  101. 'A1'
  102. ``symbols()`` can be used to create multiple Reference Frames in one
  103. step, for example:
  104. >>> from sympy.physics.vector import ReferenceFrame
  105. >>> from sympy import symbols
  106. >>> A, B, C = symbols('A B C', cls=ReferenceFrame)
  107. >>> D, E = symbols('D E', cls=ReferenceFrame, indices=('1', '2', '3'))
  108. >>> A[0]
  109. A_x
  110. >>> D.x
  111. D['1']
  112. >>> E.y
  113. E['2']
  114. >>> type(A) == type(D)
  115. True
  116. """
  117. if not isinstance(name, str):
  118. raise TypeError('Need to supply a valid name')
  119. # The if statements below are for custom printing of basis-vectors for
  120. # each frame.
  121. # First case, when custom indices are supplied
  122. if indices is not None:
  123. if not isinstance(indices, (tuple, list)):
  124. raise TypeError('Supply the indices as a list')
  125. if len(indices) != 3:
  126. raise ValueError('Supply 3 indices')
  127. for i in indices:
  128. if not isinstance(i, str):
  129. raise TypeError('Indices must be strings')
  130. self.str_vecs = [(name + '[\'' + indices[0] + '\']'),
  131. (name + '[\'' + indices[1] + '\']'),
  132. (name + '[\'' + indices[2] + '\']')]
  133. self.pretty_vecs = [(name.lower() + "_" + indices[0]),
  134. (name.lower() + "_" + indices[1]),
  135. (name.lower() + "_" + indices[2])]
  136. self.latex_vecs = [(r"\mathbf{\hat{%s}_{%s}}" % (name.lower(),
  137. indices[0])),
  138. (r"\mathbf{\hat{%s}_{%s}}" % (name.lower(),
  139. indices[1])),
  140. (r"\mathbf{\hat{%s}_{%s}}" % (name.lower(),
  141. indices[2]))]
  142. self.indices = indices
  143. # Second case, when no custom indices are supplied
  144. else:
  145. self.str_vecs = [(name + '.x'), (name + '.y'), (name + '.z')]
  146. self.pretty_vecs = [name.lower() + "_x",
  147. name.lower() + "_y",
  148. name.lower() + "_z"]
  149. self.latex_vecs = [(r"\mathbf{\hat{%s}_x}" % name.lower()),
  150. (r"\mathbf{\hat{%s}_y}" % name.lower()),
  151. (r"\mathbf{\hat{%s}_z}" % name.lower())]
  152. self.indices = ['x', 'y', 'z']
  153. # Different step, for custom latex basis vectors
  154. if latexs is not None:
  155. if not isinstance(latexs, (tuple, list)):
  156. raise TypeError('Supply the indices as a list')
  157. if len(latexs) != 3:
  158. raise ValueError('Supply 3 indices')
  159. for i in latexs:
  160. if not isinstance(i, str):
  161. raise TypeError('Latex entries must be strings')
  162. self.latex_vecs = latexs
  163. self.name = name
  164. self._var_dict = {}
  165. # The _dcm_dict dictionary will only store the dcms of adjacent
  166. # parent-child relationships. The _dcm_cache dictionary will store
  167. # calculated dcm along with all content of _dcm_dict for faster
  168. # retrieval of dcms.
  169. self._dcm_dict = {}
  170. self._dcm_cache = {}
  171. self._ang_vel_dict = {}
  172. self._ang_acc_dict = {}
  173. self._dlist = [self._dcm_dict, self._ang_vel_dict, self._ang_acc_dict]
  174. self._cur = 0
  175. self._x = Vector([(Matrix([1, 0, 0]), self)])
  176. self._y = Vector([(Matrix([0, 1, 0]), self)])
  177. self._z = Vector([(Matrix([0, 0, 1]), self)])
  178. # Associate coordinate symbols wrt this frame
  179. if variables is not None:
  180. if not isinstance(variables, (tuple, list)):
  181. raise TypeError('Supply the variable names as a list/tuple')
  182. if len(variables) != 3:
  183. raise ValueError('Supply 3 variable names')
  184. for i in variables:
  185. if not isinstance(i, str):
  186. raise TypeError('Variable names must be strings')
  187. else:
  188. variables = [name + '_x', name + '_y', name + '_z']
  189. self.varlist = (CoordinateSym(variables[0], self, 0),
  190. CoordinateSym(variables[1], self, 1),
  191. CoordinateSym(variables[2], self, 2))
  192. ReferenceFrame._count += 1
  193. self.index = ReferenceFrame._count
  194. def __getitem__(self, ind):
  195. """
  196. Returns basis vector for the provided index, if the index is a string.
  197. If the index is a number, returns the coordinate variable correspon-
  198. -ding to that index.
  199. """
  200. if not isinstance(ind, str):
  201. if ind < 3:
  202. return self.varlist[ind]
  203. else:
  204. raise ValueError("Invalid index provided")
  205. if self.indices[0] == ind:
  206. return self.x
  207. if self.indices[1] == ind:
  208. return self.y
  209. if self.indices[2] == ind:
  210. return self.z
  211. else:
  212. raise ValueError('Not a defined index')
  213. def __iter__(self):
  214. return iter([self.x, self.y, self.z])
  215. def __str__(self):
  216. """Returns the name of the frame. """
  217. return self.name
  218. __repr__ = __str__
  219. def _dict_list(self, other, num):
  220. """Returns an inclusive list of reference frames that connect this
  221. reference frame to the provided reference frame.
  222. Parameters
  223. ==========
  224. other : ReferenceFrame
  225. The other reference frame to look for a connecting relationship to.
  226. num : integer
  227. ``0``, ``1``, and ``2`` will look for orientation, angular
  228. velocity, and angular acceleration relationships between the two
  229. frames, respectively.
  230. Returns
  231. =======
  232. list
  233. Inclusive list of reference frames that connect this reference
  234. frame to the other reference frame.
  235. Examples
  236. ========
  237. >>> from sympy.physics.vector import ReferenceFrame
  238. >>> A = ReferenceFrame('A')
  239. >>> B = ReferenceFrame('B')
  240. >>> C = ReferenceFrame('C')
  241. >>> D = ReferenceFrame('D')
  242. >>> B.orient_axis(A, A.x, 1.0)
  243. >>> C.orient_axis(B, B.x, 1.0)
  244. >>> D.orient_axis(C, C.x, 1.0)
  245. >>> D._dict_list(A, 0)
  246. [D, C, B, A]
  247. Raises
  248. ======
  249. ValueError
  250. When no path is found between the two reference frames or ``num``
  251. is an incorrect value.
  252. """
  253. connect_type = {0: 'orientation',
  254. 1: 'angular velocity',
  255. 2: 'angular acceleration'}
  256. if num not in connect_type.keys():
  257. raise ValueError('Valid values for num are 0, 1, or 2.')
  258. possible_connecting_paths = [[self]]
  259. oldlist = [[]]
  260. while possible_connecting_paths != oldlist:
  261. oldlist = possible_connecting_paths[:] # make a copy
  262. for frame_list in possible_connecting_paths:
  263. frames_adjacent_to_last = frame_list[-1]._dlist[num].keys()
  264. for adjacent_frame in frames_adjacent_to_last:
  265. if adjacent_frame not in frame_list:
  266. connecting_path = frame_list + [adjacent_frame]
  267. if connecting_path not in possible_connecting_paths:
  268. possible_connecting_paths.append(connecting_path)
  269. for connecting_path in oldlist:
  270. if connecting_path[-1] != other:
  271. possible_connecting_paths.remove(connecting_path)
  272. possible_connecting_paths.sort(key=len)
  273. if len(possible_connecting_paths) != 0:
  274. return possible_connecting_paths[0] # selects the shortest path
  275. msg = 'No connecting {} path found between {} and {}.'
  276. raise ValueError(msg.format(connect_type[num], self.name, other.name))
  277. def _w_diff_dcm(self, otherframe):
  278. """Angular velocity from time differentiating the DCM. """
  279. from sympy.physics.vector.functions import dynamicsymbols
  280. dcm2diff = otherframe.dcm(self)
  281. diffed = dcm2diff.diff(dynamicsymbols._t)
  282. angvelmat = diffed * dcm2diff.T
  283. w1 = trigsimp(expand(angvelmat[7]), recursive=True)
  284. w2 = trigsimp(expand(angvelmat[2]), recursive=True)
  285. w3 = trigsimp(expand(angvelmat[3]), recursive=True)
  286. return Vector([(Matrix([w1, w2, w3]), otherframe)])
  287. def variable_map(self, otherframe):
  288. """
  289. Returns a dictionary which expresses the coordinate variables
  290. of this frame in terms of the variables of otherframe.
  291. If Vector.simp is True, returns a simplified version of the mapped
  292. values. Else, returns them without simplification.
  293. Simplification of the expressions may take time.
  294. Parameters
  295. ==========
  296. otherframe : ReferenceFrame
  297. The other frame to map the variables to
  298. Examples
  299. ========
  300. >>> from sympy.physics.vector import ReferenceFrame, dynamicsymbols
  301. >>> A = ReferenceFrame('A')
  302. >>> q = dynamicsymbols('q')
  303. >>> B = A.orientnew('B', 'Axis', [q, A.z])
  304. >>> A.variable_map(B)
  305. {A_x: B_x*cos(q(t)) - B_y*sin(q(t)), A_y: B_x*sin(q(t)) + B_y*cos(q(t)), A_z: B_z}
  306. """
  307. _check_frame(otherframe)
  308. if (otherframe, Vector.simp) in self._var_dict:
  309. return self._var_dict[(otherframe, Vector.simp)]
  310. else:
  311. vars_matrix = self.dcm(otherframe) * Matrix(otherframe.varlist)
  312. mapping = {}
  313. for i, x in enumerate(self):
  314. if Vector.simp:
  315. mapping[self.varlist[i]] = trigsimp(vars_matrix[i],
  316. method='fu')
  317. else:
  318. mapping[self.varlist[i]] = vars_matrix[i]
  319. self._var_dict[(otherframe, Vector.simp)] = mapping
  320. return mapping
  321. def ang_acc_in(self, otherframe):
  322. """Returns the angular acceleration Vector of the ReferenceFrame.
  323. Effectively returns the Vector:
  324. ``N_alpha_B``
  325. which represent the angular acceleration of B in N, where B is self,
  326. and N is otherframe.
  327. Parameters
  328. ==========
  329. otherframe : ReferenceFrame
  330. The ReferenceFrame which the angular acceleration is returned in.
  331. Examples
  332. ========
  333. >>> from sympy.physics.vector import ReferenceFrame
  334. >>> N = ReferenceFrame('N')
  335. >>> A = ReferenceFrame('A')
  336. >>> V = 10 * N.x
  337. >>> A.set_ang_acc(N, V)
  338. >>> A.ang_acc_in(N)
  339. 10*N.x
  340. """
  341. _check_frame(otherframe)
  342. if otherframe in self._ang_acc_dict:
  343. return self._ang_acc_dict[otherframe]
  344. else:
  345. return self.ang_vel_in(otherframe).dt(otherframe)
  346. def ang_vel_in(self, otherframe):
  347. """Returns the angular velocity Vector of the ReferenceFrame.
  348. Effectively returns the Vector:
  349. ^N omega ^B
  350. which represent the angular velocity of B in N, where B is self, and
  351. N is otherframe.
  352. Parameters
  353. ==========
  354. otherframe : ReferenceFrame
  355. The ReferenceFrame which the angular velocity is returned in.
  356. Examples
  357. ========
  358. >>> from sympy.physics.vector import ReferenceFrame
  359. >>> N = ReferenceFrame('N')
  360. >>> A = ReferenceFrame('A')
  361. >>> V = 10 * N.x
  362. >>> A.set_ang_vel(N, V)
  363. >>> A.ang_vel_in(N)
  364. 10*N.x
  365. """
  366. _check_frame(otherframe)
  367. flist = self._dict_list(otherframe, 1)
  368. outvec = Vector(0)
  369. for i in range(len(flist) - 1):
  370. outvec += flist[i]._ang_vel_dict[flist[i + 1]]
  371. return outvec
  372. def dcm(self, otherframe):
  373. r"""Returns the direction cosine matrix of this reference frame
  374. relative to the provided reference frame.
  375. The returned matrix can be used to express the orthogonal unit vectors
  376. of this frame in terms of the orthogonal unit vectors of
  377. ``otherframe``.
  378. Parameters
  379. ==========
  380. otherframe : ReferenceFrame
  381. The reference frame which the direction cosine matrix of this frame
  382. is formed relative to.
  383. Examples
  384. ========
  385. The following example rotates the reference frame A relative to N by a
  386. simple rotation and then calculates the direction cosine matrix of N
  387. relative to A.
  388. >>> from sympy import symbols, sin, cos
  389. >>> from sympy.physics.vector import ReferenceFrame
  390. >>> q1 = symbols('q1')
  391. >>> N = ReferenceFrame('N')
  392. >>> A = ReferenceFrame('A')
  393. >>> A.orient_axis(N, q1, N.x)
  394. >>> N.dcm(A)
  395. Matrix([
  396. [1, 0, 0],
  397. [0, cos(q1), -sin(q1)],
  398. [0, sin(q1), cos(q1)]])
  399. The second row of the above direction cosine matrix represents the
  400. ``N.y`` unit vector in N expressed in A. Like so:
  401. >>> Ny = 0*A.x + cos(q1)*A.y - sin(q1)*A.z
  402. Thus, expressing ``N.y`` in A should return the same result:
  403. >>> N.y.express(A)
  404. cos(q1)*A.y - sin(q1)*A.z
  405. Notes
  406. =====
  407. It is important to know what form of the direction cosine matrix is
  408. returned. If ``B.dcm(A)`` is called, it means the "direction cosine
  409. matrix of B rotated relative to A". This is the matrix
  410. :math:`{}^B\mathbf{C}^A` shown in the following relationship:
  411. .. math::
  412. \begin{bmatrix}
  413. \hat{\mathbf{b}}_1 \\
  414. \hat{\mathbf{b}}_2 \\
  415. \hat{\mathbf{b}}_3
  416. \end{bmatrix}
  417. =
  418. {}^B\mathbf{C}^A
  419. \begin{bmatrix}
  420. \hat{\mathbf{a}}_1 \\
  421. \hat{\mathbf{a}}_2 \\
  422. \hat{\mathbf{a}}_3
  423. \end{bmatrix}.
  424. :math:`{}^B\mathbf{C}^A` is the matrix that expresses the B unit
  425. vectors in terms of the A unit vectors.
  426. """
  427. _check_frame(otherframe)
  428. # Check if the dcm wrt that frame has already been calculated
  429. if otherframe in self._dcm_cache:
  430. return self._dcm_cache[otherframe]
  431. flist = self._dict_list(otherframe, 0)
  432. outdcm = eye(3)
  433. for i in range(len(flist) - 1):
  434. outdcm = outdcm * flist[i]._dcm_dict[flist[i + 1]]
  435. # After calculation, store the dcm in dcm cache for faster future
  436. # retrieval
  437. self._dcm_cache[otherframe] = outdcm
  438. otherframe._dcm_cache[self] = outdcm.T
  439. return outdcm
  440. def _dcm(self, parent, parent_orient):
  441. # If parent.oreint(self) is already defined,then
  442. # update the _dcm_dict of parent while over write
  443. # all content of self._dcm_dict and self._dcm_cache
  444. # with new dcm relation.
  445. # Else update _dcm_cache and _dcm_dict of both
  446. # self and parent.
  447. frames = self._dcm_cache.keys()
  448. dcm_dict_del = []
  449. dcm_cache_del = []
  450. if parent in frames:
  451. for frame in frames:
  452. if frame in self._dcm_dict:
  453. dcm_dict_del += [frame]
  454. dcm_cache_del += [frame]
  455. # Reset the _dcm_cache of this frame, and remove it from the
  456. # _dcm_caches of the frames it is linked to. Also remove it from
  457. # the _dcm_dict of its parent
  458. for frame in dcm_dict_del:
  459. del frame._dcm_dict[self]
  460. for frame in dcm_cache_del:
  461. del frame._dcm_cache[self]
  462. # Reset the _dcm_dict
  463. self._dcm_dict = self._dlist[0] = {}
  464. # Reset the _dcm_cache
  465. self._dcm_cache = {}
  466. else:
  467. # Check for loops and raise warning accordingly.
  468. visited = []
  469. queue = list(frames)
  470. cont = True # Flag to control queue loop.
  471. while queue and cont:
  472. node = queue.pop(0)
  473. if node not in visited:
  474. visited.append(node)
  475. neighbors = node._dcm_dict.keys()
  476. for neighbor in neighbors:
  477. if neighbor == parent:
  478. warn('Loops are defined among the orientation of '
  479. 'frames. This is likely not desired and may '
  480. 'cause errors in your calculations.')
  481. cont = False
  482. break
  483. queue.append(neighbor)
  484. # Add the dcm relationship to _dcm_dict
  485. self._dcm_dict.update({parent: parent_orient.T})
  486. parent._dcm_dict.update({self: parent_orient})
  487. # Update the dcm cache
  488. self._dcm_cache.update({parent: parent_orient.T})
  489. parent._dcm_cache.update({self: parent_orient})
  490. def orient_axis(self, parent, axis, angle):
  491. """Sets the orientation of this reference frame with respect to a
  492. parent reference frame by rotating through an angle about an axis fixed
  493. in the parent reference frame.
  494. Parameters
  495. ==========
  496. parent : ReferenceFrame
  497. Reference frame that this reference frame will be rotated relative
  498. to.
  499. axis : Vector
  500. Vector fixed in the parent frame about about which this frame is
  501. rotated. It need not be a unit vector and the rotation follows the
  502. right hand rule.
  503. angle : sympifiable
  504. Angle in radians by which it the frame is to be rotated.
  505. Warns
  506. ======
  507. UserWarning
  508. If the orientation creates a kinematic loop.
  509. Examples
  510. ========
  511. Setup variables for the examples:
  512. >>> from sympy import symbols
  513. >>> from sympy.physics.vector import ReferenceFrame
  514. >>> q1 = symbols('q1')
  515. >>> N = ReferenceFrame('N')
  516. >>> B = ReferenceFrame('B')
  517. >>> B.orient_axis(N, N.x, q1)
  518. The ``orient_axis()`` method generates a direction cosine matrix and
  519. its transpose which defines the orientation of B relative to N and vice
  520. versa. Once orient is called, ``dcm()`` outputs the appropriate
  521. direction cosine matrix:
  522. >>> B.dcm(N)
  523. Matrix([
  524. [1, 0, 0],
  525. [0, cos(q1), sin(q1)],
  526. [0, -sin(q1), cos(q1)]])
  527. >>> N.dcm(B)
  528. Matrix([
  529. [1, 0, 0],
  530. [0, cos(q1), -sin(q1)],
  531. [0, sin(q1), cos(q1)]])
  532. The following two lines show that the sense of the rotation can be
  533. defined by negating the vector direction or the angle. Both lines
  534. produce the same result.
  535. >>> B.orient_axis(N, -N.x, q1)
  536. >>> B.orient_axis(N, N.x, -q1)
  537. """
  538. from sympy.physics.vector.functions import dynamicsymbols
  539. _check_frame(parent)
  540. if not isinstance(axis, Vector) and isinstance(angle, Vector):
  541. axis, angle = angle, axis
  542. axis = _check_vector(axis)
  543. theta = sympify(angle)
  544. if not axis.dt(parent) == 0:
  545. raise ValueError('Axis cannot be time-varying.')
  546. unit_axis = axis.express(parent).normalize()
  547. unit_col = unit_axis.args[0][0]
  548. parent_orient_axis = (
  549. (eye(3) - unit_col * unit_col.T) * cos(theta) +
  550. Matrix([[0, -unit_col[2], unit_col[1]],
  551. [unit_col[2], 0, -unit_col[0]],
  552. [-unit_col[1], unit_col[0], 0]]) *
  553. sin(theta) + unit_col * unit_col.T)
  554. self._dcm(parent, parent_orient_axis)
  555. thetad = (theta).diff(dynamicsymbols._t)
  556. wvec = thetad*axis.express(parent).normalize()
  557. self._ang_vel_dict.update({parent: wvec})
  558. parent._ang_vel_dict.update({self: -wvec})
  559. self._var_dict = {}
  560. def orient_explicit(self, parent, dcm):
  561. """Sets the orientation of this reference frame relative to a parent
  562. reference frame by explicitly setting the direction cosine matrix.
  563. Parameters
  564. ==========
  565. parent : ReferenceFrame
  566. Reference frame that this reference frame will be rotated relative
  567. to.
  568. dcm : Matrix, shape(3, 3)
  569. Direction cosine matrix that specifies the relative rotation
  570. between the two reference frames.
  571. Warns
  572. ======
  573. UserWarning
  574. If the orientation creates a kinematic loop.
  575. Examples
  576. ========
  577. Setup variables for the examples:
  578. >>> from sympy import symbols, Matrix, sin, cos
  579. >>> from sympy.physics.vector import ReferenceFrame
  580. >>> q1 = symbols('q1')
  581. >>> A = ReferenceFrame('A')
  582. >>> B = ReferenceFrame('B')
  583. >>> N = ReferenceFrame('N')
  584. A simple rotation of ``A`` relative to ``N`` about ``N.x`` is defined
  585. by the following direction cosine matrix:
  586. >>> dcm = Matrix([[1, 0, 0],
  587. ... [0, cos(q1), -sin(q1)],
  588. ... [0, sin(q1), cos(q1)]])
  589. >>> A.orient_explicit(N, dcm)
  590. >>> A.dcm(N)
  591. Matrix([
  592. [1, 0, 0],
  593. [0, cos(q1), sin(q1)],
  594. [0, -sin(q1), cos(q1)]])
  595. This is equivalent to using ``orient_axis()``:
  596. >>> B.orient_axis(N, N.x, q1)
  597. >>> B.dcm(N)
  598. Matrix([
  599. [1, 0, 0],
  600. [0, cos(q1), sin(q1)],
  601. [0, -sin(q1), cos(q1)]])
  602. **Note carefully that** ``N.dcm(B)`` **(the transpose) would be passed
  603. into** ``orient_explicit()`` **for** ``A.dcm(N)`` **to match**
  604. ``B.dcm(N)``:
  605. >>> A.orient_explicit(N, N.dcm(B))
  606. >>> A.dcm(N)
  607. Matrix([
  608. [1, 0, 0],
  609. [0, cos(q1), sin(q1)],
  610. [0, -sin(q1), cos(q1)]])
  611. """
  612. _check_frame(parent)
  613. # amounts must be a Matrix type object
  614. # (e.g. sympy.matrices.dense.MutableDenseMatrix).
  615. if not isinstance(dcm, MatrixBase):
  616. raise TypeError("Amounts must be a SymPy Matrix type object.")
  617. parent_orient_dcm = dcm
  618. self._dcm(parent, parent_orient_dcm)
  619. wvec = self._w_diff_dcm(parent)
  620. self._ang_vel_dict.update({parent: wvec})
  621. parent._ang_vel_dict.update({self: -wvec})
  622. self._var_dict = {}
  623. def _rot(self, axis, angle):
  624. """DCM for simple axis 1,2,or 3 rotations."""
  625. if axis == 1:
  626. return Matrix([[1, 0, 0],
  627. [0, cos(angle), -sin(angle)],
  628. [0, sin(angle), cos(angle)]])
  629. elif axis == 2:
  630. return Matrix([[cos(angle), 0, sin(angle)],
  631. [0, 1, 0],
  632. [-sin(angle), 0, cos(angle)]])
  633. elif axis == 3:
  634. return Matrix([[cos(angle), -sin(angle), 0],
  635. [sin(angle), cos(angle), 0],
  636. [0, 0, 1]])
  637. def _parse_consecutive_rotations(self, angles, rotation_order):
  638. """Helper for orient_body_fixed and orient_space_fixed.
  639. Parameters
  640. ==========
  641. angles : 3-tuple of sympifiable
  642. Three angles in radians used for the successive rotations.
  643. rotation_order : 3 character string or 3 digit integer
  644. Order of the rotations. The order can be specified by the strings
  645. ``'XZX'``, ``'131'``, or the integer ``131``. There are 12 unique
  646. valid rotation orders.
  647. Returns
  648. =======
  649. amounts : list
  650. List of sympifiables corresponding to the rotation angles.
  651. rot_order : list
  652. List of integers corresponding to the axis of rotation.
  653. rot_matrices : list
  654. List of DCM around the given axis with corresponding magnitude.
  655. """
  656. amounts = list(angles)
  657. for i, v in enumerate(amounts):
  658. if not isinstance(v, Vector):
  659. amounts[i] = sympify(v)
  660. approved_orders = ('123', '231', '312', '132', '213', '321', '121',
  661. '131', '212', '232', '313', '323', '')
  662. # make sure XYZ => 123
  663. rot_order = translate(str(rotation_order), 'XYZxyz', '123123')
  664. if rot_order not in approved_orders:
  665. raise TypeError('The rotation order is not a valid order.')
  666. rot_order = [int(r) for r in rot_order]
  667. if not (len(amounts) == 3 & len(rot_order) == 3):
  668. raise TypeError('Body orientation takes 3 values & 3 orders')
  669. rot_matrices = [self._rot(order, amount)
  670. for (order, amount) in zip(rot_order, amounts)]
  671. return amounts, rot_order, rot_matrices
  672. def orient_body_fixed(self, parent, angles, rotation_order):
  673. """Rotates this reference frame relative to the parent reference frame
  674. by right hand rotating through three successive body fixed simple axis
  675. rotations. Each subsequent axis of rotation is about the "body fixed"
  676. unit vectors of a new intermediate reference frame. This type of
  677. rotation is also referred to rotating through the `Euler and Tait-Bryan
  678. Angles`_.
  679. .. _Euler and Tait-Bryan Angles: https://en.wikipedia.org/wiki/Euler_angles
  680. The computed angular velocity in this method is by default expressed in
  681. the child's frame, so it is most preferable to use ``u1 * child.x + u2 *
  682. child.y + u3 * child.z`` as generalized speeds.
  683. Parameters
  684. ==========
  685. parent : ReferenceFrame
  686. Reference frame that this reference frame will be rotated relative
  687. to.
  688. angles : 3-tuple of sympifiable
  689. Three angles in radians used for the successive rotations.
  690. rotation_order : 3 character string or 3 digit integer
  691. Order of the rotations about each intermediate reference frames'
  692. unit vectors. The Euler rotation about the X, Z', X'' axes can be
  693. specified by the strings ``'XZX'``, ``'131'``, or the integer
  694. ``131``. There are 12 unique valid rotation orders (6 Euler and 6
  695. Tait-Bryan): zxz, xyx, yzy, zyz, xzx, yxy, xyz, yzx, zxy, xzy, zyx,
  696. and yxz.
  697. Warns
  698. ======
  699. UserWarning
  700. If the orientation creates a kinematic loop.
  701. Examples
  702. ========
  703. Setup variables for the examples:
  704. >>> from sympy import symbols
  705. >>> from sympy.physics.vector import ReferenceFrame
  706. >>> q1, q2, q3 = symbols('q1, q2, q3')
  707. >>> N = ReferenceFrame('N')
  708. >>> B = ReferenceFrame('B')
  709. >>> B1 = ReferenceFrame('B1')
  710. >>> B2 = ReferenceFrame('B2')
  711. >>> B3 = ReferenceFrame('B3')
  712. For example, a classic Euler Angle rotation can be done by:
  713. >>> B.orient_body_fixed(N, (q1, q2, q3), 'XYX')
  714. >>> B.dcm(N)
  715. Matrix([
  716. [ cos(q2), sin(q1)*sin(q2), -sin(q2)*cos(q1)],
  717. [sin(q2)*sin(q3), -sin(q1)*sin(q3)*cos(q2) + cos(q1)*cos(q3), sin(q1)*cos(q3) + sin(q3)*cos(q1)*cos(q2)],
  718. [sin(q2)*cos(q3), -sin(q1)*cos(q2)*cos(q3) - sin(q3)*cos(q1), -sin(q1)*sin(q3) + cos(q1)*cos(q2)*cos(q3)]])
  719. This rotates reference frame B relative to reference frame N through
  720. ``q1`` about ``N.x``, then rotates B again through ``q2`` about
  721. ``B.y``, and finally through ``q3`` about ``B.x``. It is equivalent to
  722. three successive ``orient_axis()`` calls:
  723. >>> B1.orient_axis(N, N.x, q1)
  724. >>> B2.orient_axis(B1, B1.y, q2)
  725. >>> B3.orient_axis(B2, B2.x, q3)
  726. >>> B3.dcm(N)
  727. Matrix([
  728. [ cos(q2), sin(q1)*sin(q2), -sin(q2)*cos(q1)],
  729. [sin(q2)*sin(q3), -sin(q1)*sin(q3)*cos(q2) + cos(q1)*cos(q3), sin(q1)*cos(q3) + sin(q3)*cos(q1)*cos(q2)],
  730. [sin(q2)*cos(q3), -sin(q1)*cos(q2)*cos(q3) - sin(q3)*cos(q1), -sin(q1)*sin(q3) + cos(q1)*cos(q2)*cos(q3)]])
  731. Acceptable rotation orders are of length 3, expressed in as a string
  732. ``'XYZ'`` or ``'123'`` or integer ``123``. Rotations about an axis
  733. twice in a row are prohibited.
  734. >>> B.orient_body_fixed(N, (q1, q2, 0), 'ZXZ')
  735. >>> B.orient_body_fixed(N, (q1, q2, 0), '121')
  736. >>> B.orient_body_fixed(N, (q1, q2, q3), 123)
  737. """
  738. from sympy.physics.vector.functions import dynamicsymbols
  739. _check_frame(parent)
  740. amounts, rot_order, rot_matrices = self._parse_consecutive_rotations(
  741. angles, rotation_order)
  742. self._dcm(parent, rot_matrices[0] * rot_matrices[1] * rot_matrices[2])
  743. rot_vecs = [zeros(3, 1) for _ in range(3)]
  744. for i, order in enumerate(rot_order):
  745. rot_vecs[i][order - 1] = amounts[i].diff(dynamicsymbols._t)
  746. u1, u2, u3 = rot_vecs[2] + rot_matrices[2].T * (
  747. rot_vecs[1] + rot_matrices[1].T * rot_vecs[0])
  748. wvec = u1 * self.x + u2 * self.y + u3 * self.z # There is a double -
  749. self._ang_vel_dict.update({parent: wvec})
  750. parent._ang_vel_dict.update({self: -wvec})
  751. self._var_dict = {}
  752. def orient_space_fixed(self, parent, angles, rotation_order):
  753. """Rotates this reference frame relative to the parent reference frame
  754. by right hand rotating through three successive space fixed simple axis
  755. rotations. Each subsequent axis of rotation is about the "space fixed"
  756. unit vectors of the parent reference frame.
  757. The computed angular velocity in this method is by default expressed in
  758. the child's frame, so it is most preferable to use ``u1 * child.x + u2 *
  759. child.y + u3 * child.z`` as generalized speeds.
  760. Parameters
  761. ==========
  762. parent : ReferenceFrame
  763. Reference frame that this reference frame will be rotated relative
  764. to.
  765. angles : 3-tuple of sympifiable
  766. Three angles in radians used for the successive rotations.
  767. rotation_order : 3 character string or 3 digit integer
  768. Order of the rotations about the parent reference frame's unit
  769. vectors. The order can be specified by the strings ``'XZX'``,
  770. ``'131'``, or the integer ``131``. There are 12 unique valid
  771. rotation orders.
  772. Warns
  773. ======
  774. UserWarning
  775. If the orientation creates a kinematic loop.
  776. Examples
  777. ========
  778. Setup variables for the examples:
  779. >>> from sympy import symbols
  780. >>> from sympy.physics.vector import ReferenceFrame
  781. >>> q1, q2, q3 = symbols('q1, q2, q3')
  782. >>> N = ReferenceFrame('N')
  783. >>> B = ReferenceFrame('B')
  784. >>> B1 = ReferenceFrame('B1')
  785. >>> B2 = ReferenceFrame('B2')
  786. >>> B3 = ReferenceFrame('B3')
  787. >>> B.orient_space_fixed(N, (q1, q2, q3), '312')
  788. >>> B.dcm(N)
  789. Matrix([
  790. [ sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1)],
  791. [-sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3)],
  792. [ sin(q3)*cos(q2), -sin(q2), cos(q2)*cos(q3)]])
  793. is equivalent to:
  794. >>> B1.orient_axis(N, N.z, q1)
  795. >>> B2.orient_axis(B1, N.x, q2)
  796. >>> B3.orient_axis(B2, N.y, q3)
  797. >>> B3.dcm(N).simplify()
  798. Matrix([
  799. [ sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1)],
  800. [-sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3)],
  801. [ sin(q3)*cos(q2), -sin(q2), cos(q2)*cos(q3)]])
  802. It is worth noting that space-fixed and body-fixed rotations are
  803. related by the order of the rotations, i.e. the reverse order of body
  804. fixed will give space fixed and vice versa.
  805. >>> B.orient_space_fixed(N, (q1, q2, q3), '231')
  806. >>> B.dcm(N)
  807. Matrix([
  808. [cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3), -sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)],
  809. [ -sin(q2), cos(q2)*cos(q3), sin(q3)*cos(q2)],
  810. [sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1), sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3)]])
  811. >>> B.orient_body_fixed(N, (q3, q2, q1), '132')
  812. >>> B.dcm(N)
  813. Matrix([
  814. [cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3), -sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)],
  815. [ -sin(q2), cos(q2)*cos(q3), sin(q3)*cos(q2)],
  816. [sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1), sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3)]])
  817. """
  818. from sympy.physics.vector.functions import dynamicsymbols
  819. _check_frame(parent)
  820. amounts, rot_order, rot_matrices = self._parse_consecutive_rotations(
  821. angles, rotation_order)
  822. self._dcm(parent, rot_matrices[2] * rot_matrices[1] * rot_matrices[0])
  823. rot_vecs = [zeros(3, 1) for _ in range(3)]
  824. for i, order in enumerate(rot_order):
  825. rot_vecs[i][order - 1] = amounts[i].diff(dynamicsymbols._t)
  826. u1, u2, u3 = rot_vecs[0] + rot_matrices[0].T * (
  827. rot_vecs[1] + rot_matrices[1].T * rot_vecs[2])
  828. wvec = u1 * self.x + u2 * self.y + u3 * self.z # There is a double -
  829. self._ang_vel_dict.update({parent: wvec})
  830. parent._ang_vel_dict.update({self: -wvec})
  831. self._var_dict = {}
  832. def orient_quaternion(self, parent, numbers):
  833. """Sets the orientation of this reference frame relative to a parent
  834. reference frame via an orientation quaternion. An orientation
  835. quaternion is defined as a finite rotation a unit vector, ``(lambda_x,
  836. lambda_y, lambda_z)``, by an angle ``theta``. The orientation
  837. quaternion is described by four parameters:
  838. - ``q0 = cos(theta/2)``
  839. - ``q1 = lambda_x*sin(theta/2)``
  840. - ``q2 = lambda_y*sin(theta/2)``
  841. - ``q3 = lambda_z*sin(theta/2)``
  842. See `Quaternions and Spatial Rotation
  843. <https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation>`_ on
  844. Wikipedia for more information.
  845. Parameters
  846. ==========
  847. parent : ReferenceFrame
  848. Reference frame that this reference frame will be rotated relative
  849. to.
  850. numbers : 4-tuple of sympifiable
  851. The four quaternion scalar numbers as defined above: ``q0``,
  852. ``q1``, ``q2``, ``q3``.
  853. Warns
  854. ======
  855. UserWarning
  856. If the orientation creates a kinematic loop.
  857. Examples
  858. ========
  859. Setup variables for the examples:
  860. >>> from sympy import symbols
  861. >>> from sympy.physics.vector import ReferenceFrame
  862. >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
  863. >>> N = ReferenceFrame('N')
  864. >>> B = ReferenceFrame('B')
  865. Set the orientation:
  866. >>> B.orient_quaternion(N, (q0, q1, q2, q3))
  867. >>> B.dcm(N)
  868. Matrix([
  869. [q0**2 + q1**2 - q2**2 - q3**2, 2*q0*q3 + 2*q1*q2, -2*q0*q2 + 2*q1*q3],
  870. [ -2*q0*q3 + 2*q1*q2, q0**2 - q1**2 + q2**2 - q3**2, 2*q0*q1 + 2*q2*q3],
  871. [ 2*q0*q2 + 2*q1*q3, -2*q0*q1 + 2*q2*q3, q0**2 - q1**2 - q2**2 + q3**2]])
  872. """
  873. from sympy.physics.vector.functions import dynamicsymbols
  874. _check_frame(parent)
  875. numbers = list(numbers)
  876. for i, v in enumerate(numbers):
  877. if not isinstance(v, Vector):
  878. numbers[i] = sympify(v)
  879. if not (isinstance(numbers, (list, tuple)) & (len(numbers) == 4)):
  880. raise TypeError('Amounts are a list or tuple of length 4')
  881. q0, q1, q2, q3 = numbers
  882. parent_orient_quaternion = (
  883. Matrix([[q0**2 + q1**2 - q2**2 - q3**2,
  884. 2 * (q1 * q2 - q0 * q3),
  885. 2 * (q0 * q2 + q1 * q3)],
  886. [2 * (q1 * q2 + q0 * q3),
  887. q0**2 - q1**2 + q2**2 - q3**2,
  888. 2 * (q2 * q3 - q0 * q1)],
  889. [2 * (q1 * q3 - q0 * q2),
  890. 2 * (q0 * q1 + q2 * q3),
  891. q0**2 - q1**2 - q2**2 + q3**2]]))
  892. self._dcm(parent, parent_orient_quaternion)
  893. t = dynamicsymbols._t
  894. q0, q1, q2, q3 = numbers
  895. q0d = diff(q0, t)
  896. q1d = diff(q1, t)
  897. q2d = diff(q2, t)
  898. q3d = diff(q3, t)
  899. w1 = 2 * (q1d * q0 + q2d * q3 - q3d * q2 - q0d * q1)
  900. w2 = 2 * (q2d * q0 + q3d * q1 - q1d * q3 - q0d * q2)
  901. w3 = 2 * (q3d * q0 + q1d * q2 - q2d * q1 - q0d * q3)
  902. wvec = Vector([(Matrix([w1, w2, w3]), self)])
  903. self._ang_vel_dict.update({parent: wvec})
  904. parent._ang_vel_dict.update({self: -wvec})
  905. self._var_dict = {}
  906. def orient(self, parent, rot_type, amounts, rot_order=''):
  907. """Sets the orientation of this reference frame relative to another
  908. (parent) reference frame.
  909. .. note:: It is now recommended to use the ``.orient_axis,
  910. .orient_body_fixed, .orient_space_fixed, .orient_quaternion``
  911. methods for the different rotation types.
  912. Parameters
  913. ==========
  914. parent : ReferenceFrame
  915. Reference frame that this reference frame will be rotated relative
  916. to.
  917. rot_type : str
  918. The method used to generate the direction cosine matrix. Supported
  919. methods are:
  920. - ``'Axis'``: simple rotations about a single common axis
  921. - ``'DCM'``: for setting the direction cosine matrix directly
  922. - ``'Body'``: three successive rotations about new intermediate
  923. axes, also called "Euler and Tait-Bryan angles"
  924. - ``'Space'``: three successive rotations about the parent
  925. frames' unit vectors
  926. - ``'Quaternion'``: rotations defined by four parameters which
  927. result in a singularity free direction cosine matrix
  928. amounts :
  929. Expressions defining the rotation angles or direction cosine
  930. matrix. These must match the ``rot_type``. See examples below for
  931. details. The input types are:
  932. - ``'Axis'``: 2-tuple (expr/sym/func, Vector)
  933. - ``'DCM'``: Matrix, shape(3,3)
  934. - ``'Body'``: 3-tuple of expressions, symbols, or functions
  935. - ``'Space'``: 3-tuple of expressions, symbols, or functions
  936. - ``'Quaternion'``: 4-tuple of expressions, symbols, or
  937. functions
  938. rot_order : str or int, optional
  939. If applicable, the order of the successive of rotations. The string
  940. ``'123'`` and integer ``123`` are equivalent, for example. Required
  941. for ``'Body'`` and ``'Space'``.
  942. Warns
  943. ======
  944. UserWarning
  945. If the orientation creates a kinematic loop.
  946. """
  947. _check_frame(parent)
  948. approved_orders = ('123', '231', '312', '132', '213', '321', '121',
  949. '131', '212', '232', '313', '323', '')
  950. rot_order = translate(str(rot_order), 'XYZxyz', '123123')
  951. rot_type = rot_type.upper()
  952. if rot_order not in approved_orders:
  953. raise TypeError('The supplied order is not an approved type')
  954. if rot_type == 'AXIS':
  955. self.orient_axis(parent, amounts[1], amounts[0])
  956. elif rot_type == 'DCM':
  957. self.orient_explicit(parent, amounts)
  958. elif rot_type == 'BODY':
  959. self.orient_body_fixed(parent, amounts, rot_order)
  960. elif rot_type == 'SPACE':
  961. self.orient_space_fixed(parent, amounts, rot_order)
  962. elif rot_type == 'QUATERNION':
  963. self.orient_quaternion(parent, amounts)
  964. else:
  965. raise NotImplementedError('That is not an implemented rotation')
  966. def orientnew(self, newname, rot_type, amounts, rot_order='',
  967. variables=None, indices=None, latexs=None):
  968. r"""Returns a new reference frame oriented with respect to this
  969. reference frame.
  970. See ``ReferenceFrame.orient()`` for detailed examples of how to orient
  971. reference frames.
  972. Parameters
  973. ==========
  974. newname : str
  975. Name for the new reference frame.
  976. rot_type : str
  977. The method used to generate the direction cosine matrix. Supported
  978. methods are:
  979. - ``'Axis'``: simple rotations about a single common axis
  980. - ``'DCM'``: for setting the direction cosine matrix directly
  981. - ``'Body'``: three successive rotations about new intermediate
  982. axes, also called "Euler and Tait-Bryan angles"
  983. - ``'Space'``: three successive rotations about the parent
  984. frames' unit vectors
  985. - ``'Quaternion'``: rotations defined by four parameters which
  986. result in a singularity free direction cosine matrix
  987. amounts :
  988. Expressions defining the rotation angles or direction cosine
  989. matrix. These must match the ``rot_type``. See examples below for
  990. details. The input types are:
  991. - ``'Axis'``: 2-tuple (expr/sym/func, Vector)
  992. - ``'DCM'``: Matrix, shape(3,3)
  993. - ``'Body'``: 3-tuple of expressions, symbols, or functions
  994. - ``'Space'``: 3-tuple of expressions, symbols, or functions
  995. - ``'Quaternion'``: 4-tuple of expressions, symbols, or
  996. functions
  997. rot_order : str or int, optional
  998. If applicable, the order of the successive of rotations. The string
  999. ``'123'`` and integer ``123`` are equivalent, for example. Required
  1000. for ``'Body'`` and ``'Space'``.
  1001. indices : tuple of str
  1002. Enables the reference frame's basis unit vectors to be accessed by
  1003. Python's square bracket indexing notation using the provided three
  1004. indice strings and alters the printing of the unit vectors to
  1005. reflect this choice.
  1006. latexs : tuple of str
  1007. Alters the LaTeX printing of the reference frame's basis unit
  1008. vectors to the provided three valid LaTeX strings.
  1009. Examples
  1010. ========
  1011. >>> from sympy import symbols
  1012. >>> from sympy.physics.vector import ReferenceFrame, vlatex
  1013. >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
  1014. >>> N = ReferenceFrame('N')
  1015. Create a new reference frame A rotated relative to N through a simple
  1016. rotation.
  1017. >>> A = N.orientnew('A', 'Axis', (q0, N.x))
  1018. Create a new reference frame B rotated relative to N through body-fixed
  1019. rotations.
  1020. >>> B = N.orientnew('B', 'Body', (q1, q2, q3), '123')
  1021. Create a new reference frame C rotated relative to N through a simple
  1022. rotation with unique indices and LaTeX printing.
  1023. >>> C = N.orientnew('C', 'Axis', (q0, N.x), indices=('1', '2', '3'),
  1024. ... latexs=(r'\hat{\mathbf{c}}_1',r'\hat{\mathbf{c}}_2',
  1025. ... r'\hat{\mathbf{c}}_3'))
  1026. >>> C['1']
  1027. C['1']
  1028. >>> print(vlatex(C['1']))
  1029. \hat{\mathbf{c}}_1
  1030. """
  1031. newframe = self.__class__(newname, variables=variables,
  1032. indices=indices, latexs=latexs)
  1033. approved_orders = ('123', '231', '312', '132', '213', '321', '121',
  1034. '131', '212', '232', '313', '323', '')
  1035. rot_order = translate(str(rot_order), 'XYZxyz', '123123')
  1036. rot_type = rot_type.upper()
  1037. if rot_order not in approved_orders:
  1038. raise TypeError('The supplied order is not an approved type')
  1039. if rot_type == 'AXIS':
  1040. newframe.orient_axis(self, amounts[1], amounts[0])
  1041. elif rot_type == 'DCM':
  1042. newframe.orient_explicit(self, amounts)
  1043. elif rot_type == 'BODY':
  1044. newframe.orient_body_fixed(self, amounts, rot_order)
  1045. elif rot_type == 'SPACE':
  1046. newframe.orient_space_fixed(self, amounts, rot_order)
  1047. elif rot_type == 'QUATERNION':
  1048. newframe.orient_quaternion(self, amounts)
  1049. else:
  1050. raise NotImplementedError('That is not an implemented rotation')
  1051. return newframe
  1052. def set_ang_acc(self, otherframe, value):
  1053. """Define the angular acceleration Vector in a ReferenceFrame.
  1054. Defines the angular acceleration of this ReferenceFrame, in another.
  1055. Angular acceleration can be defined with respect to multiple different
  1056. ReferenceFrames. Care must be taken to not create loops which are
  1057. inconsistent.
  1058. Parameters
  1059. ==========
  1060. otherframe : ReferenceFrame
  1061. A ReferenceFrame to define the angular acceleration in
  1062. value : Vector
  1063. The Vector representing angular acceleration
  1064. Examples
  1065. ========
  1066. >>> from sympy.physics.vector import ReferenceFrame
  1067. >>> N = ReferenceFrame('N')
  1068. >>> A = ReferenceFrame('A')
  1069. >>> V = 10 * N.x
  1070. >>> A.set_ang_acc(N, V)
  1071. >>> A.ang_acc_in(N)
  1072. 10*N.x
  1073. """
  1074. if value == 0:
  1075. value = Vector(0)
  1076. value = _check_vector(value)
  1077. _check_frame(otherframe)
  1078. self._ang_acc_dict.update({otherframe: value})
  1079. otherframe._ang_acc_dict.update({self: -value})
  1080. def set_ang_vel(self, otherframe, value):
  1081. """Define the angular velocity vector in a ReferenceFrame.
  1082. Defines the angular velocity of this ReferenceFrame, in another.
  1083. Angular velocity can be defined with respect to multiple different
  1084. ReferenceFrames. Care must be taken to not create loops which are
  1085. inconsistent.
  1086. Parameters
  1087. ==========
  1088. otherframe : ReferenceFrame
  1089. A ReferenceFrame to define the angular velocity in
  1090. value : Vector
  1091. The Vector representing angular velocity
  1092. Examples
  1093. ========
  1094. >>> from sympy.physics.vector import ReferenceFrame
  1095. >>> N = ReferenceFrame('N')
  1096. >>> A = ReferenceFrame('A')
  1097. >>> V = 10 * N.x
  1098. >>> A.set_ang_vel(N, V)
  1099. >>> A.ang_vel_in(N)
  1100. 10*N.x
  1101. """
  1102. if value == 0:
  1103. value = Vector(0)
  1104. value = _check_vector(value)
  1105. _check_frame(otherframe)
  1106. self._ang_vel_dict.update({otherframe: value})
  1107. otherframe._ang_vel_dict.update({self: -value})
  1108. @property
  1109. def x(self):
  1110. """The basis Vector for the ReferenceFrame, in the x direction. """
  1111. return self._x
  1112. @property
  1113. def y(self):
  1114. """The basis Vector for the ReferenceFrame, in the y direction. """
  1115. return self._y
  1116. @property
  1117. def z(self):
  1118. """The basis Vector for the ReferenceFrame, in the z direction. """
  1119. return self._z
  1120. def partial_velocity(self, frame, *gen_speeds):
  1121. """Returns the partial angular velocities of this frame in the given
  1122. frame with respect to one or more provided generalized speeds.
  1123. Parameters
  1124. ==========
  1125. frame : ReferenceFrame
  1126. The frame with which the angular velocity is defined in.
  1127. gen_speeds : functions of time
  1128. The generalized speeds.
  1129. Returns
  1130. =======
  1131. partial_velocities : tuple of Vector
  1132. The partial angular velocity vectors corresponding to the provided
  1133. generalized speeds.
  1134. Examples
  1135. ========
  1136. >>> from sympy.physics.vector import ReferenceFrame, dynamicsymbols
  1137. >>> N = ReferenceFrame('N')
  1138. >>> A = ReferenceFrame('A')
  1139. >>> u1, u2 = dynamicsymbols('u1, u2')
  1140. >>> A.set_ang_vel(N, u1 * A.x + u2 * N.y)
  1141. >>> A.partial_velocity(N, u1)
  1142. A.x
  1143. >>> A.partial_velocity(N, u1, u2)
  1144. (A.x, N.y)
  1145. """
  1146. partials = [self.ang_vel_in(frame).diff(speed, frame, var_in_dcm=False)
  1147. for speed in gen_speeds]
  1148. if len(partials) == 1:
  1149. return partials[0]
  1150. else:
  1151. return tuple(partials)
  1152. def _check_frame(other):
  1153. from .vector import VectorTypeError
  1154. if not isinstance(other, ReferenceFrame):
  1155. raise VectorTypeError(other, ReferenceFrame('A'))