dyadic.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. from sympy.core.backend import sympify, Add, ImmutableMatrix as Matrix
  2. from sympy.core.evalf import EvalfMixin
  3. from sympy.printing.defaults import Printable
  4. from mpmath.libmp.libmpf import prec_to_dps
  5. __all__ = ['Dyadic']
  6. class Dyadic(Printable, EvalfMixin):
  7. """A Dyadic object.
  8. See:
  9. https://en.wikipedia.org/wiki/Dyadic_tensor
  10. Kane, T., Levinson, D. Dynamics Theory and Applications. 1985 McGraw-Hill
  11. A more powerful way to represent a rigid body's inertia. While it is more
  12. complex, by choosing Dyadic components to be in body fixed basis vectors,
  13. the resulting matrix is equivalent to the inertia tensor.
  14. """
  15. is_number = False
  16. def __init__(self, inlist):
  17. """
  18. Just like Vector's init, you should not call this unless creating a
  19. zero dyadic.
  20. zd = Dyadic(0)
  21. Stores a Dyadic as a list of lists; the inner list has the measure
  22. number and the two unit vectors; the outerlist holds each unique
  23. unit vector pair.
  24. """
  25. self.args = []
  26. if inlist == 0:
  27. inlist = []
  28. while len(inlist) != 0:
  29. added = 0
  30. for i, v in enumerate(self.args):
  31. if ((str(inlist[0][1]) == str(self.args[i][1])) and
  32. (str(inlist[0][2]) == str(self.args[i][2]))):
  33. self.args[i] = (self.args[i][0] + inlist[0][0],
  34. inlist[0][1], inlist[0][2])
  35. inlist.remove(inlist[0])
  36. added = 1
  37. break
  38. if added != 1:
  39. self.args.append(inlist[0])
  40. inlist.remove(inlist[0])
  41. i = 0
  42. # This code is to remove empty parts from the list
  43. while i < len(self.args):
  44. if ((self.args[i][0] == 0) | (self.args[i][1] == 0) |
  45. (self.args[i][2] == 0)):
  46. self.args.remove(self.args[i])
  47. i -= 1
  48. i += 1
  49. @property
  50. def func(self):
  51. """Returns the class Dyadic. """
  52. return Dyadic
  53. def __add__(self, other):
  54. """The add operator for Dyadic. """
  55. other = _check_dyadic(other)
  56. return Dyadic(self.args + other.args)
  57. def __and__(self, other):
  58. """The inner product operator for a Dyadic and a Dyadic or Vector.
  59. Parameters
  60. ==========
  61. other : Dyadic or Vector
  62. The other Dyadic or Vector to take the inner product with
  63. Examples
  64. ========
  65. >>> from sympy.physics.vector import ReferenceFrame, outer
  66. >>> N = ReferenceFrame('N')
  67. >>> D1 = outer(N.x, N.y)
  68. >>> D2 = outer(N.y, N.y)
  69. >>> D1.dot(D2)
  70. (N.x|N.y)
  71. >>> D1.dot(N.y)
  72. N.x
  73. """
  74. from sympy.physics.vector.vector import Vector, _check_vector
  75. if isinstance(other, Dyadic):
  76. other = _check_dyadic(other)
  77. ol = Dyadic(0)
  78. for i, v in enumerate(self.args):
  79. for i2, v2 in enumerate(other.args):
  80. ol += v[0] * v2[0] * (v[2] & v2[1]) * (v[1] | v2[2])
  81. else:
  82. other = _check_vector(other)
  83. ol = Vector(0)
  84. for i, v in enumerate(self.args):
  85. ol += v[0] * v[1] * (v[2] & other)
  86. return ol
  87. def __truediv__(self, other):
  88. """Divides the Dyadic by a sympifyable expression. """
  89. return self.__mul__(1 / other)
  90. def __eq__(self, other):
  91. """Tests for equality.
  92. Is currently weak; needs stronger comparison testing
  93. """
  94. if other == 0:
  95. other = Dyadic(0)
  96. other = _check_dyadic(other)
  97. if (self.args == []) and (other.args == []):
  98. return True
  99. elif (self.args == []) or (other.args == []):
  100. return False
  101. return set(self.args) == set(other.args)
  102. def __mul__(self, other):
  103. """Multiplies the Dyadic by a sympifyable expression.
  104. Parameters
  105. ==========
  106. other : Sympafiable
  107. The scalar to multiply this Dyadic with
  108. Examples
  109. ========
  110. >>> from sympy.physics.vector import ReferenceFrame, outer
  111. >>> N = ReferenceFrame('N')
  112. >>> d = outer(N.x, N.x)
  113. >>> 5 * d
  114. 5*(N.x|N.x)
  115. """
  116. newlist = list(self.args)
  117. other = sympify(other)
  118. for i, v in enumerate(newlist):
  119. newlist[i] = (other * newlist[i][0], newlist[i][1],
  120. newlist[i][2])
  121. return Dyadic(newlist)
  122. def __ne__(self, other):
  123. return not self == other
  124. def __neg__(self):
  125. return self * -1
  126. def _latex(self, printer):
  127. ar = self.args # just to shorten things
  128. if len(ar) == 0:
  129. return str(0)
  130. ol = [] # output list, to be concatenated to a string
  131. for i, v in enumerate(ar):
  132. # if the coef of the dyadic is 1, we skip the 1
  133. if ar[i][0] == 1:
  134. ol.append(' + ' + printer._print(ar[i][1]) + r"\otimes " +
  135. printer._print(ar[i][2]))
  136. # if the coef of the dyadic is -1, we skip the 1
  137. elif ar[i][0] == -1:
  138. ol.append(' - ' +
  139. printer._print(ar[i][1]) +
  140. r"\otimes " +
  141. printer._print(ar[i][2]))
  142. # If the coefficient of the dyadic is not 1 or -1,
  143. # we might wrap it in parentheses, for readability.
  144. elif ar[i][0] != 0:
  145. arg_str = printer._print(ar[i][0])
  146. if isinstance(ar[i][0], Add):
  147. arg_str = '(%s)' % arg_str
  148. if arg_str.startswith('-'):
  149. arg_str = arg_str[1:]
  150. str_start = ' - '
  151. else:
  152. str_start = ' + '
  153. ol.append(str_start + arg_str + printer._print(ar[i][1]) +
  154. r"\otimes " + printer._print(ar[i][2]))
  155. outstr = ''.join(ol)
  156. if outstr.startswith(' + '):
  157. outstr = outstr[3:]
  158. elif outstr.startswith(' '):
  159. outstr = outstr[1:]
  160. return outstr
  161. def _pretty(self, printer):
  162. e = self
  163. class Fake:
  164. baseline = 0
  165. def render(self, *args, **kwargs):
  166. ar = e.args # just to shorten things
  167. mpp = printer
  168. if len(ar) == 0:
  169. return str(0)
  170. bar = "\N{CIRCLED TIMES}" if printer._use_unicode else "|"
  171. ol = [] # output list, to be concatenated to a string
  172. for i, v in enumerate(ar):
  173. # if the coef of the dyadic is 1, we skip the 1
  174. if ar[i][0] == 1:
  175. ol.extend([" + ",
  176. mpp.doprint(ar[i][1]),
  177. bar,
  178. mpp.doprint(ar[i][2])])
  179. # if the coef of the dyadic is -1, we skip the 1
  180. elif ar[i][0] == -1:
  181. ol.extend([" - ",
  182. mpp.doprint(ar[i][1]),
  183. bar,
  184. mpp.doprint(ar[i][2])])
  185. # If the coefficient of the dyadic is not 1 or -1,
  186. # we might wrap it in parentheses, for readability.
  187. elif ar[i][0] != 0:
  188. if isinstance(ar[i][0], Add):
  189. arg_str = mpp._print(
  190. ar[i][0]).parens()[0]
  191. else:
  192. arg_str = mpp.doprint(ar[i][0])
  193. if arg_str.startswith("-"):
  194. arg_str = arg_str[1:]
  195. str_start = " - "
  196. else:
  197. str_start = " + "
  198. ol.extend([str_start, arg_str, " ",
  199. mpp.doprint(ar[i][1]),
  200. bar,
  201. mpp.doprint(ar[i][2])])
  202. outstr = "".join(ol)
  203. if outstr.startswith(" + "):
  204. outstr = outstr[3:]
  205. elif outstr.startswith(" "):
  206. outstr = outstr[1:]
  207. return outstr
  208. return Fake()
  209. def __rand__(self, other):
  210. """The inner product operator for a Vector or Dyadic, and a Dyadic
  211. This is for: Vector dot Dyadic
  212. Parameters
  213. ==========
  214. other : Vector
  215. The vector we are dotting with
  216. Examples
  217. ========
  218. >>> from sympy.physics.vector import ReferenceFrame, dot, outer
  219. >>> N = ReferenceFrame('N')
  220. >>> d = outer(N.x, N.x)
  221. >>> dot(N.x, d)
  222. N.x
  223. """
  224. from sympy.physics.vector.vector import Vector, _check_vector
  225. other = _check_vector(other)
  226. ol = Vector(0)
  227. for i, v in enumerate(self.args):
  228. ol += v[0] * v[2] * (v[1] & other)
  229. return ol
  230. def __rsub__(self, other):
  231. return (-1 * self) + other
  232. def __rxor__(self, other):
  233. """For a cross product in the form: Vector x Dyadic
  234. Parameters
  235. ==========
  236. other : Vector
  237. The Vector that we are crossing this Dyadic with
  238. Examples
  239. ========
  240. >>> from sympy.physics.vector import ReferenceFrame, outer, cross
  241. >>> N = ReferenceFrame('N')
  242. >>> d = outer(N.x, N.x)
  243. >>> cross(N.y, d)
  244. - (N.z|N.x)
  245. """
  246. from sympy.physics.vector.vector import _check_vector
  247. other = _check_vector(other)
  248. ol = Dyadic(0)
  249. for i, v in enumerate(self.args):
  250. ol += v[0] * ((other ^ v[1]) | v[2])
  251. return ol
  252. def _sympystr(self, printer):
  253. """Printing method. """
  254. ar = self.args # just to shorten things
  255. if len(ar) == 0:
  256. return printer._print(0)
  257. ol = [] # output list, to be concatenated to a string
  258. for i, v in enumerate(ar):
  259. # if the coef of the dyadic is 1, we skip the 1
  260. if ar[i][0] == 1:
  261. ol.append(' + (' + printer._print(ar[i][1]) + '|' +
  262. printer._print(ar[i][2]) + ')')
  263. # if the coef of the dyadic is -1, we skip the 1
  264. elif ar[i][0] == -1:
  265. ol.append(' - (' + printer._print(ar[i][1]) + '|' +
  266. printer._print(ar[i][2]) + ')')
  267. # If the coefficient of the dyadic is not 1 or -1,
  268. # we might wrap it in parentheses, for readability.
  269. elif ar[i][0] != 0:
  270. arg_str = printer._print(ar[i][0])
  271. if isinstance(ar[i][0], Add):
  272. arg_str = "(%s)" % arg_str
  273. if arg_str[0] == '-':
  274. arg_str = arg_str[1:]
  275. str_start = ' - '
  276. else:
  277. str_start = ' + '
  278. ol.append(str_start + arg_str + '*(' +
  279. printer._print(ar[i][1]) +
  280. '|' + printer._print(ar[i][2]) + ')')
  281. outstr = ''.join(ol)
  282. if outstr.startswith(' + '):
  283. outstr = outstr[3:]
  284. elif outstr.startswith(' '):
  285. outstr = outstr[1:]
  286. return outstr
  287. def __sub__(self, other):
  288. """The subtraction operator. """
  289. return self.__add__(other * -1)
  290. def __xor__(self, other):
  291. """For a cross product in the form: Dyadic x Vector.
  292. Parameters
  293. ==========
  294. other : Vector
  295. The Vector that we are crossing this Dyadic with
  296. Examples
  297. ========
  298. >>> from sympy.physics.vector import ReferenceFrame, outer, cross
  299. >>> N = ReferenceFrame('N')
  300. >>> d = outer(N.x, N.x)
  301. >>> cross(d, N.y)
  302. (N.x|N.z)
  303. """
  304. from sympy.physics.vector.vector import _check_vector
  305. other = _check_vector(other)
  306. ol = Dyadic(0)
  307. for i, v in enumerate(self.args):
  308. ol += v[0] * (v[1] | (v[2] ^ other))
  309. return ol
  310. __radd__ = __add__
  311. __rmul__ = __mul__
  312. def express(self, frame1, frame2=None):
  313. """Expresses this Dyadic in alternate frame(s)
  314. The first frame is the list side expression, the second frame is the
  315. right side; if Dyadic is in form A.x|B.y, you can express it in two
  316. different frames. If no second frame is given, the Dyadic is
  317. expressed in only one frame.
  318. Calls the global express function
  319. Parameters
  320. ==========
  321. frame1 : ReferenceFrame
  322. The frame to express the left side of the Dyadic in
  323. frame2 : ReferenceFrame
  324. If provided, the frame to express the right side of the Dyadic in
  325. Examples
  326. ========
  327. >>> from sympy.physics.vector import ReferenceFrame, outer, dynamicsymbols
  328. >>> from sympy.physics.vector import init_vprinting
  329. >>> init_vprinting(pretty_print=False)
  330. >>> N = ReferenceFrame('N')
  331. >>> q = dynamicsymbols('q')
  332. >>> B = N.orientnew('B', 'Axis', [q, N.z])
  333. >>> d = outer(N.x, N.x)
  334. >>> d.express(B, N)
  335. cos(q)*(B.x|N.x) - sin(q)*(B.y|N.x)
  336. """
  337. from sympy.physics.vector.functions import express
  338. return express(self, frame1, frame2)
  339. def to_matrix(self, reference_frame, second_reference_frame=None):
  340. """Returns the matrix form of the dyadic with respect to one or two
  341. reference frames.
  342. Parameters
  343. ----------
  344. reference_frame : ReferenceFrame
  345. The reference frame that the rows and columns of the matrix
  346. correspond to. If a second reference frame is provided, this
  347. only corresponds to the rows of the matrix.
  348. second_reference_frame : ReferenceFrame, optional, default=None
  349. The reference frame that the columns of the matrix correspond
  350. to.
  351. Returns
  352. -------
  353. matrix : ImmutableMatrix, shape(3,3)
  354. The matrix that gives the 2D tensor form.
  355. Examples
  356. ========
  357. >>> from sympy import symbols
  358. >>> from sympy.physics.vector import ReferenceFrame, Vector
  359. >>> Vector.simp = True
  360. >>> from sympy.physics.mechanics import inertia
  361. >>> Ixx, Iyy, Izz, Ixy, Iyz, Ixz = symbols('Ixx, Iyy, Izz, Ixy, Iyz, Ixz')
  362. >>> N = ReferenceFrame('N')
  363. >>> inertia_dyadic = inertia(N, Ixx, Iyy, Izz, Ixy, Iyz, Ixz)
  364. >>> inertia_dyadic.to_matrix(N)
  365. Matrix([
  366. [Ixx, Ixy, Ixz],
  367. [Ixy, Iyy, Iyz],
  368. [Ixz, Iyz, Izz]])
  369. >>> beta = symbols('beta')
  370. >>> A = N.orientnew('A', 'Axis', (beta, N.x))
  371. >>> inertia_dyadic.to_matrix(A)
  372. Matrix([
  373. [ Ixx, Ixy*cos(beta) + Ixz*sin(beta), -Ixy*sin(beta) + Ixz*cos(beta)],
  374. [ Ixy*cos(beta) + Ixz*sin(beta), Iyy*cos(2*beta)/2 + Iyy/2 + Iyz*sin(2*beta) - Izz*cos(2*beta)/2 + Izz/2, -Iyy*sin(2*beta)/2 + Iyz*cos(2*beta) + Izz*sin(2*beta)/2],
  375. [-Ixy*sin(beta) + Ixz*cos(beta), -Iyy*sin(2*beta)/2 + Iyz*cos(2*beta) + Izz*sin(2*beta)/2, -Iyy*cos(2*beta)/2 + Iyy/2 - Iyz*sin(2*beta) + Izz*cos(2*beta)/2 + Izz/2]])
  376. """
  377. if second_reference_frame is None:
  378. second_reference_frame = reference_frame
  379. return Matrix([i.dot(self).dot(j) for i in reference_frame for j in
  380. second_reference_frame]).reshape(3, 3)
  381. def doit(self, **hints):
  382. """Calls .doit() on each term in the Dyadic"""
  383. return sum([Dyadic([(v[0].doit(**hints), v[1], v[2])])
  384. for v in self.args], Dyadic(0))
  385. def dt(self, frame):
  386. """Take the time derivative of this Dyadic in a frame.
  387. This function calls the global time_derivative method
  388. Parameters
  389. ==========
  390. frame : ReferenceFrame
  391. The frame to take the time derivative in
  392. Examples
  393. ========
  394. >>> from sympy.physics.vector import ReferenceFrame, outer, dynamicsymbols
  395. >>> from sympy.physics.vector import init_vprinting
  396. >>> init_vprinting(pretty_print=False)
  397. >>> N = ReferenceFrame('N')
  398. >>> q = dynamicsymbols('q')
  399. >>> B = N.orientnew('B', 'Axis', [q, N.z])
  400. >>> d = outer(N.x, N.x)
  401. >>> d.dt(B)
  402. - q'*(N.y|N.x) - q'*(N.x|N.y)
  403. """
  404. from sympy.physics.vector.functions import time_derivative
  405. return time_derivative(self, frame)
  406. def simplify(self):
  407. """Returns a simplified Dyadic."""
  408. out = Dyadic(0)
  409. for v in self.args:
  410. out += Dyadic([(v[0].simplify(), v[1], v[2])])
  411. return out
  412. def subs(self, *args, **kwargs):
  413. """Substitution on the Dyadic.
  414. Examples
  415. ========
  416. >>> from sympy.physics.vector import ReferenceFrame
  417. >>> from sympy import Symbol
  418. >>> N = ReferenceFrame('N')
  419. >>> s = Symbol('s')
  420. >>> a = s*(N.x|N.x)
  421. >>> a.subs({s: 2})
  422. 2*(N.x|N.x)
  423. """
  424. return sum([Dyadic([(v[0].subs(*args, **kwargs), v[1], v[2])])
  425. for v in self.args], Dyadic(0))
  426. def applyfunc(self, f):
  427. """Apply a function to each component of a Dyadic."""
  428. if not callable(f):
  429. raise TypeError("`f` must be callable.")
  430. out = Dyadic(0)
  431. for a, b, c in self.args:
  432. out += f(a) * (b | c)
  433. return out
  434. dot = __and__
  435. cross = __xor__
  436. def _eval_evalf(self, prec):
  437. if not self.args:
  438. return self
  439. new_args = []
  440. dps = prec_to_dps(prec)
  441. for inlist in self.args:
  442. new_inlist = list(inlist)
  443. new_inlist[0] = inlist[0].evalf(n=dps)
  444. new_args.append(tuple(new_inlist))
  445. return Dyadic(new_args)
  446. def xreplace(self, rule):
  447. """
  448. Replace occurrences of objects within the measure numbers of the
  449. Dyadic.
  450. Parameters
  451. ==========
  452. rule : dict-like
  453. Expresses a replacement rule.
  454. Returns
  455. =======
  456. Dyadic
  457. Result of the replacement.
  458. Examples
  459. ========
  460. >>> from sympy import symbols, pi
  461. >>> from sympy.physics.vector import ReferenceFrame, outer
  462. >>> N = ReferenceFrame('N')
  463. >>> D = outer(N.x, N.x)
  464. >>> x, y, z = symbols('x y z')
  465. >>> ((1 + x*y) * D).xreplace({x: pi})
  466. (pi*y + 1)*(N.x|N.x)
  467. >>> ((1 + x*y) * D).xreplace({x: pi, y: 2})
  468. (1 + 2*pi)*(N.x|N.x)
  469. Replacements occur only if an entire node in the expression tree is
  470. matched:
  471. >>> ((x*y + z) * D).xreplace({x*y: pi})
  472. (z + pi)*(N.x|N.x)
  473. >>> ((x*y*z) * D).xreplace({x*y: pi})
  474. x*y*z*(N.x|N.x)
  475. """
  476. new_args = []
  477. for inlist in self.args:
  478. new_inlist = list(inlist)
  479. new_inlist[0] = new_inlist[0].xreplace(rule)
  480. new_args.append(tuple(new_inlist))
  481. return Dyadic(new_args)
  482. def _check_dyadic(other):
  483. if not isinstance(other, Dyadic):
  484. raise TypeError('A Dyadic must be supplied')
  485. return other