m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1037 lines
36 KiB

6 months ago
  1. from collections.abc import Callable
  2. from sympy.core.basic import Basic
  3. from sympy.core.cache import cacheit
  4. from sympy.core import S, Dummy, Lambda
  5. from sympy.core.symbol import Str
  6. from sympy.core.symbol import symbols
  7. from sympy.matrices.immutable import ImmutableDenseMatrix as Matrix
  8. from sympy.matrices.matrices import MatrixBase
  9. from sympy.solvers import solve
  10. from sympy.vector.scalar import BaseScalar
  11. from sympy.core.containers import Tuple
  12. from sympy.core.function import diff
  13. from sympy.functions.elementary.miscellaneous import sqrt
  14. from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin)
  15. from sympy.matrices.dense import eye
  16. from sympy.matrices.immutable import ImmutableDenseMatrix
  17. from sympy.simplify.simplify import simplify
  18. from sympy.simplify.trigsimp import trigsimp
  19. import sympy.vector
  20. from sympy.vector.orienters import (Orienter, AxisOrienter, BodyOrienter,
  21. SpaceOrienter, QuaternionOrienter)
  22. class CoordSys3D(Basic):
  23. """
  24. Represents a coordinate system in 3-D space.
  25. """
  26. def __new__(cls, name, transformation=None, parent=None, location=None,
  27. rotation_matrix=None, vector_names=None, variable_names=None):
  28. """
  29. The orientation/location parameters are necessary if this system
  30. is being defined at a certain orientation or location wrt another.
  31. Parameters
  32. ==========
  33. name : str
  34. The name of the new CoordSys3D instance.
  35. transformation : Lambda, Tuple, str
  36. Transformation defined by transformation equations or chosen
  37. from predefined ones.
  38. location : Vector
  39. The position vector of the new system's origin wrt the parent
  40. instance.
  41. rotation_matrix : SymPy ImmutableMatrix
  42. The rotation matrix of the new coordinate system with respect
  43. to the parent. In other words, the output of
  44. new_system.rotation_matrix(parent).
  45. parent : CoordSys3D
  46. The coordinate system wrt which the orientation/location
  47. (or both) is being defined.
  48. vector_names, variable_names : iterable(optional)
  49. Iterables of 3 strings each, with custom names for base
  50. vectors and base scalars of the new system respectively.
  51. Used for simple str printing.
  52. """
  53. name = str(name)
  54. Vector = sympy.vector.Vector
  55. Point = sympy.vector.Point
  56. if not isinstance(name, str):
  57. raise TypeError("name should be a string")
  58. if transformation is not None:
  59. if (location is not None) or (rotation_matrix is not None):
  60. raise ValueError("specify either `transformation` or "
  61. "`location`/`rotation_matrix`")
  62. if isinstance(transformation, (Tuple, tuple, list)):
  63. if isinstance(transformation[0], MatrixBase):
  64. rotation_matrix = transformation[0]
  65. location = transformation[1]
  66. else:
  67. transformation = Lambda(transformation[0],
  68. transformation[1])
  69. elif isinstance(transformation, Callable):
  70. x1, x2, x3 = symbols('x1 x2 x3', cls=Dummy)
  71. transformation = Lambda((x1, x2, x3),
  72. transformation(x1, x2, x3))
  73. elif isinstance(transformation, str):
  74. transformation = Str(transformation)
  75. elif isinstance(transformation, (Str, Lambda)):
  76. pass
  77. else:
  78. raise TypeError("transformation: "
  79. "wrong type {}".format(type(transformation)))
  80. # If orientation information has been provided, store
  81. # the rotation matrix accordingly
  82. if rotation_matrix is None:
  83. rotation_matrix = ImmutableDenseMatrix(eye(3))
  84. else:
  85. if not isinstance(rotation_matrix, MatrixBase):
  86. raise TypeError("rotation_matrix should be an Immutable" +
  87. "Matrix instance")
  88. rotation_matrix = rotation_matrix.as_immutable()
  89. # If location information is not given, adjust the default
  90. # location as Vector.zero
  91. if parent is not None:
  92. if not isinstance(parent, CoordSys3D):
  93. raise TypeError("parent should be a " +
  94. "CoordSys3D/None")
  95. if location is None:
  96. location = Vector.zero
  97. else:
  98. if not isinstance(location, Vector):
  99. raise TypeError("location should be a Vector")
  100. # Check that location does not contain base
  101. # scalars
  102. for x in location.free_symbols:
  103. if isinstance(x, BaseScalar):
  104. raise ValueError("location should not contain" +
  105. " BaseScalars")
  106. origin = parent.origin.locate_new(name + '.origin',
  107. location)
  108. else:
  109. location = Vector.zero
  110. origin = Point(name + '.origin')
  111. if transformation is None:
  112. transformation = Tuple(rotation_matrix, location)
  113. if isinstance(transformation, Tuple):
  114. lambda_transformation = CoordSys3D._compose_rotation_and_translation(
  115. transformation[0],
  116. transformation[1],
  117. parent
  118. )
  119. r, l = transformation
  120. l = l._projections
  121. lambda_lame = CoordSys3D._get_lame_coeff('cartesian')
  122. lambda_inverse = lambda x, y, z: r.inv()*Matrix(
  123. [x-l[0], y-l[1], z-l[2]])
  124. elif isinstance(transformation, Str):
  125. trname = transformation.name
  126. lambda_transformation = CoordSys3D._get_transformation_lambdas(trname)
  127. if parent is not None:
  128. if parent.lame_coefficients() != (S.One, S.One, S.One):
  129. raise ValueError('Parent for pre-defined coordinate '
  130. 'system should be Cartesian.')
  131. lambda_lame = CoordSys3D._get_lame_coeff(trname)
  132. lambda_inverse = CoordSys3D._set_inv_trans_equations(trname)
  133. elif isinstance(transformation, Lambda):
  134. if not CoordSys3D._check_orthogonality(transformation):
  135. raise ValueError("The transformation equation does not "
  136. "create orthogonal coordinate system")
  137. lambda_transformation = transformation
  138. lambda_lame = CoordSys3D._calculate_lame_coeff(lambda_transformation)
  139. lambda_inverse = None
  140. else:
  141. lambda_transformation = lambda x, y, z: transformation(x, y, z)
  142. lambda_lame = CoordSys3D._get_lame_coeff(transformation)
  143. lambda_inverse = None
  144. if variable_names is None:
  145. if isinstance(transformation, Lambda):
  146. variable_names = ["x1", "x2", "x3"]
  147. elif isinstance(transformation, Str):
  148. if transformation.name == 'spherical':
  149. variable_names = ["r", "theta", "phi"]
  150. elif transformation.name == 'cylindrical':
  151. variable_names = ["r", "theta", "z"]
  152. else:
  153. variable_names = ["x", "y", "z"]
  154. else:
  155. variable_names = ["x", "y", "z"]
  156. if vector_names is None:
  157. vector_names = ["i", "j", "k"]
  158. # All systems that are defined as 'roots' are unequal, unless
  159. # they have the same name.
  160. # Systems defined at same orientation/position wrt the same
  161. # 'parent' are equal, irrespective of the name.
  162. # This is true even if the same orientation is provided via
  163. # different methods like Axis/Body/Space/Quaternion.
  164. # However, coincident systems may be seen as unequal if
  165. # positioned/oriented wrt different parents, even though
  166. # they may actually be 'coincident' wrt the root system.
  167. if parent is not None:
  168. obj = super().__new__(
  169. cls, Str(name), transformation, parent)
  170. else:
  171. obj = super().__new__(
  172. cls, Str(name), transformation)
  173. obj._name = name
  174. # Initialize the base vectors
  175. _check_strings('vector_names', vector_names)
  176. vector_names = list(vector_names)
  177. latex_vects = [(r'\mathbf{\hat{%s}_{%s}}' % (x, name)) for
  178. x in vector_names]
  179. pretty_vects = ['%s_%s' % (x, name) for x in vector_names]
  180. obj._vector_names = vector_names
  181. v1 = BaseVector(0, obj, pretty_vects[0], latex_vects[0])
  182. v2 = BaseVector(1, obj, pretty_vects[1], latex_vects[1])
  183. v3 = BaseVector(2, obj, pretty_vects[2], latex_vects[2])
  184. obj._base_vectors = (v1, v2, v3)
  185. # Initialize the base scalars
  186. _check_strings('variable_names', vector_names)
  187. variable_names = list(variable_names)
  188. latex_scalars = [(r"\mathbf{{%s}_{%s}}" % (x, name)) for
  189. x in variable_names]
  190. pretty_scalars = ['%s_%s' % (x, name) for x in variable_names]
  191. obj._variable_names = variable_names
  192. obj._vector_names = vector_names
  193. x1 = BaseScalar(0, obj, pretty_scalars[0], latex_scalars[0])
  194. x2 = BaseScalar(1, obj, pretty_scalars[1], latex_scalars[1])
  195. x3 = BaseScalar(2, obj, pretty_scalars[2], latex_scalars[2])
  196. obj._base_scalars = (x1, x2, x3)
  197. obj._transformation = transformation
  198. obj._transformation_lambda = lambda_transformation
  199. obj._lame_coefficients = lambda_lame(x1, x2, x3)
  200. obj._transformation_from_parent_lambda = lambda_inverse
  201. setattr(obj, variable_names[0], x1)
  202. setattr(obj, variable_names[1], x2)
  203. setattr(obj, variable_names[2], x3)
  204. setattr(obj, vector_names[0], v1)
  205. setattr(obj, vector_names[1], v2)
  206. setattr(obj, vector_names[2], v3)
  207. # Assign params
  208. obj._parent = parent
  209. if obj._parent is not None:
  210. obj._root = obj._parent._root
  211. else:
  212. obj._root = obj
  213. obj._parent_rotation_matrix = rotation_matrix
  214. obj._origin = origin
  215. # Return the instance
  216. return obj
  217. def _sympystr(self, printer):
  218. return self._name
  219. def __iter__(self):
  220. return iter(self.base_vectors())
  221. @staticmethod
  222. def _check_orthogonality(equations):
  223. """
  224. Helper method for _connect_to_cartesian. It checks if
  225. set of transformation equations create orthogonal curvilinear
  226. coordinate system
  227. Parameters
  228. ==========
  229. equations : Lambda
  230. Lambda of transformation equations
  231. """
  232. x1, x2, x3 = symbols("x1, x2, x3", cls=Dummy)
  233. equations = equations(x1, x2, x3)
  234. v1 = Matrix([diff(equations[0], x1),
  235. diff(equations[1], x1), diff(equations[2], x1)])
  236. v2 = Matrix([diff(equations[0], x2),
  237. diff(equations[1], x2), diff(equations[2], x2)])
  238. v3 = Matrix([diff(equations[0], x3),
  239. diff(equations[1], x3), diff(equations[2], x3)])
  240. if any(simplify(i[0] + i[1] + i[2]) == 0 for i in (v1, v2, v3)):
  241. return False
  242. else:
  243. if simplify(v1.dot(v2)) == 0 and simplify(v2.dot(v3)) == 0 \
  244. and simplify(v3.dot(v1)) == 0:
  245. return True
  246. else:
  247. return False
  248. @staticmethod
  249. def _set_inv_trans_equations(curv_coord_name):
  250. """
  251. Store information about inverse transformation equations for
  252. pre-defined coordinate systems.
  253. Parameters
  254. ==========
  255. curv_coord_name : str
  256. Name of coordinate system
  257. """
  258. if curv_coord_name == 'cartesian':
  259. return lambda x, y, z: (x, y, z)
  260. if curv_coord_name == 'spherical':
  261. return lambda x, y, z: (
  262. sqrt(x**2 + y**2 + z**2),
  263. acos(z/sqrt(x**2 + y**2 + z**2)),
  264. atan2(y, x)
  265. )
  266. if curv_coord_name == 'cylindrical':
  267. return lambda x, y, z: (
  268. sqrt(x**2 + y**2),
  269. atan2(y, x),
  270. z
  271. )
  272. raise ValueError('Wrong set of parameters.'
  273. 'Type of coordinate system is defined')
  274. def _calculate_inv_trans_equations(self):
  275. """
  276. Helper method for set_coordinate_type. It calculates inverse
  277. transformation equations for given transformations equations.
  278. """
  279. x1, x2, x3 = symbols("x1, x2, x3", cls=Dummy, reals=True)
  280. x, y, z = symbols("x, y, z", cls=Dummy)
  281. equations = self._transformation(x1, x2, x3)
  282. solved = solve([equations[0] - x,
  283. equations[1] - y,
  284. equations[2] - z], (x1, x2, x3), dict=True)[0]
  285. solved = solved[x1], solved[x2], solved[x3]
  286. self._transformation_from_parent_lambda = \
  287. lambda x1, x2, x3: tuple(i.subs(list(zip((x, y, z), (x1, x2, x3)))) for i in solved)
  288. @staticmethod
  289. def _get_lame_coeff(curv_coord_name):
  290. """
  291. Store information about Lame coefficients for pre-defined
  292. coordinate systems.
  293. Parameters
  294. ==========
  295. curv_coord_name : str
  296. Name of coordinate system
  297. """
  298. if isinstance(curv_coord_name, str):
  299. if curv_coord_name == 'cartesian':
  300. return lambda x, y, z: (S.One, S.One, S.One)
  301. if curv_coord_name == 'spherical':
  302. return lambda r, theta, phi: (S.One, r, r*sin(theta))
  303. if curv_coord_name == 'cylindrical':
  304. return lambda r, theta, h: (S.One, r, S.One)
  305. raise ValueError('Wrong set of parameters.'
  306. ' Type of coordinate system is not defined')
  307. return CoordSys3D._calculate_lame_coefficients(curv_coord_name)
  308. @staticmethod
  309. def _calculate_lame_coeff(equations):
  310. """
  311. It calculates Lame coefficients
  312. for given transformations equations.
  313. Parameters
  314. ==========
  315. equations : Lambda
  316. Lambda of transformation equations.
  317. """
  318. return lambda x1, x2, x3: (
  319. sqrt(diff(equations(x1, x2, x3)[0], x1)**2 +
  320. diff(equations(x1, x2, x3)[1], x1)**2 +
  321. diff(equations(x1, x2, x3)[2], x1)**2),
  322. sqrt(diff(equations(x1, x2, x3)[0], x2)**2 +
  323. diff(equations(x1, x2, x3)[1], x2)**2 +
  324. diff(equations(x1, x2, x3)[2], x2)**2),
  325. sqrt(diff(equations(x1, x2, x3)[0], x3)**2 +
  326. diff(equations(x1, x2, x3)[1], x3)**2 +
  327. diff(equations(x1, x2, x3)[2], x3)**2)
  328. )
  329. def _inverse_rotation_matrix(self):
  330. """
  331. Returns inverse rotation matrix.
  332. """
  333. return simplify(self._parent_rotation_matrix**-1)
  334. @staticmethod
  335. def _get_transformation_lambdas(curv_coord_name):
  336. """
  337. Store information about transformation equations for pre-defined
  338. coordinate systems.
  339. Parameters
  340. ==========
  341. curv_coord_name : str
  342. Name of coordinate system
  343. """
  344. if isinstance(curv_coord_name, str):
  345. if curv_coord_name == 'cartesian':
  346. return lambda x, y, z: (x, y, z)
  347. if curv_coord_name == 'spherical':
  348. return lambda r, theta, phi: (
  349. r*sin(theta)*cos(phi),
  350. r*sin(theta)*sin(phi),
  351. r*cos(theta)
  352. )
  353. if curv_coord_name == 'cylindrical':
  354. return lambda r, theta, h: (
  355. r*cos(theta),
  356. r*sin(theta),
  357. h
  358. )
  359. raise ValueError('Wrong set of parameters.'
  360. 'Type of coordinate system is defined')
  361. @classmethod
  362. def _rotation_trans_equations(cls, matrix, equations):
  363. """
  364. Returns the transformation equations obtained from rotation matrix.
  365. Parameters
  366. ==========
  367. matrix : Matrix
  368. Rotation matrix
  369. equations : tuple
  370. Transformation equations
  371. """
  372. return tuple(matrix * Matrix(equations))
  373. @property
  374. def origin(self):
  375. return self._origin
  376. def base_vectors(self):
  377. return self._base_vectors
  378. def base_scalars(self):
  379. return self._base_scalars
  380. def lame_coefficients(self):
  381. return self._lame_coefficients
  382. def transformation_to_parent(self):
  383. return self._transformation_lambda(*self.base_scalars())
  384. def transformation_from_parent(self):
  385. if self._parent is None:
  386. raise ValueError("no parent coordinate system, use "
  387. "`transformation_from_parent_function()`")
  388. return self._transformation_from_parent_lambda(
  389. *self._parent.base_scalars())
  390. def transformation_from_parent_function(self):
  391. return self._transformation_from_parent_lambda
  392. def rotation_matrix(self, other):
  393. """
  394. Returns the direction cosine matrix(DCM), also known as the
  395. 'rotation matrix' of this coordinate system with respect to
  396. another system.
  397. If v_a is a vector defined in system 'A' (in matrix format)
  398. and v_b is the same vector defined in system 'B', then
  399. v_a = A.rotation_matrix(B) * v_b.
  400. A SymPy Matrix is returned.
  401. Parameters
  402. ==========
  403. other : CoordSys3D
  404. The system which the DCM is generated to.
  405. Examples
  406. ========
  407. >>> from sympy.vector import CoordSys3D
  408. >>> from sympy import symbols
  409. >>> q1 = symbols('q1')
  410. >>> N = CoordSys3D('N')
  411. >>> A = N.orient_new_axis('A', q1, N.i)
  412. >>> N.rotation_matrix(A)
  413. Matrix([
  414. [1, 0, 0],
  415. [0, cos(q1), -sin(q1)],
  416. [0, sin(q1), cos(q1)]])
  417. """
  418. from sympy.vector.functions import _path
  419. if not isinstance(other, CoordSys3D):
  420. raise TypeError(str(other) +
  421. " is not a CoordSys3D")
  422. # Handle special cases
  423. if other == self:
  424. return eye(3)
  425. elif other == self._parent:
  426. return self._parent_rotation_matrix
  427. elif other._parent == self:
  428. return other._parent_rotation_matrix.T
  429. # Else, use tree to calculate position
  430. rootindex, path = _path(self, other)
  431. result = eye(3)
  432. i = -1
  433. for i in range(rootindex):
  434. result *= path[i]._parent_rotation_matrix
  435. i += 2
  436. while i < len(path):
  437. result *= path[i]._parent_rotation_matrix.T
  438. i += 1
  439. return result
  440. @cacheit
  441. def position_wrt(self, other):
  442. """
  443. Returns the position vector of the origin of this coordinate
  444. system with respect to another Point/CoordSys3D.
  445. Parameters
  446. ==========
  447. other : Point/CoordSys3D
  448. If other is a Point, the position of this system's origin
  449. wrt it is returned. If its an instance of CoordSyRect,
  450. the position wrt its origin is returned.
  451. Examples
  452. ========
  453. >>> from sympy.vector import CoordSys3D
  454. >>> N = CoordSys3D('N')
  455. >>> N1 = N.locate_new('N1', 10 * N.i)
  456. >>> N.position_wrt(N1)
  457. (-10)*N.i
  458. """
  459. return self.origin.position_wrt(other)
  460. def scalar_map(self, other):
  461. """
  462. Returns a dictionary which expresses the coordinate variables
  463. (base scalars) of this frame in terms of the variables of
  464. otherframe.
  465. Parameters
  466. ==========
  467. otherframe : CoordSys3D
  468. The other system to map the variables to.
  469. Examples
  470. ========
  471. >>> from sympy.vector import CoordSys3D
  472. >>> from sympy import Symbol
  473. >>> A = CoordSys3D('A')
  474. >>> q = Symbol('q')
  475. >>> B = A.orient_new_axis('B', q, A.k)
  476. >>> A.scalar_map(B)
  477. {A.x: B.x*cos(q) - B.y*sin(q), A.y: B.x*sin(q) + B.y*cos(q), A.z: B.z}
  478. """
  479. relocated_scalars = []
  480. origin_coords = tuple(self.position_wrt(other).to_matrix(other))
  481. for i, x in enumerate(other.base_scalars()):
  482. relocated_scalars.append(x - origin_coords[i])
  483. vars_matrix = (self.rotation_matrix(other) *
  484. Matrix(relocated_scalars))
  485. mapping = {}
  486. for i, x in enumerate(self.base_scalars()):
  487. mapping[x] = trigsimp(vars_matrix[i])
  488. return mapping
  489. def locate_new(self, name, position, vector_names=None,
  490. variable_names=None):
  491. """
  492. Returns a CoordSys3D with its origin located at the given
  493. position wrt this coordinate system's origin.
  494. Parameters
  495. ==========
  496. name : str
  497. The name of the new CoordSys3D instance.
  498. position : Vector
  499. The position vector of the new system's origin wrt this
  500. one.
  501. vector_names, variable_names : iterable(optional)
  502. Iterables of 3 strings each, with custom names for base
  503. vectors and base scalars of the new system respectively.
  504. Used for simple str printing.
  505. Examples
  506. ========
  507. >>> from sympy.vector import CoordSys3D
  508. >>> A = CoordSys3D('A')
  509. >>> B = A.locate_new('B', 10 * A.i)
  510. >>> B.origin.position_wrt(A.origin)
  511. 10*A.i
  512. """
  513. if variable_names is None:
  514. variable_names = self._variable_names
  515. if vector_names is None:
  516. vector_names = self._vector_names
  517. return CoordSys3D(name, location=position,
  518. vector_names=vector_names,
  519. variable_names=variable_names,
  520. parent=self)
  521. def orient_new(self, name, orienters, location=None,
  522. vector_names=None, variable_names=None):
  523. """
  524. Creates a new CoordSys3D oriented in the user-specified way
  525. with respect to this system.
  526. Please refer to the documentation of the orienter classes
  527. for more information about the orientation procedure.
  528. Parameters
  529. ==========
  530. name : str
  531. The name of the new CoordSys3D instance.
  532. orienters : iterable/Orienter
  533. An Orienter or an iterable of Orienters for orienting the
  534. new coordinate system.
  535. If an Orienter is provided, it is applied to get the new
  536. system.
  537. If an iterable is provided, the orienters will be applied
  538. in the order in which they appear in the iterable.
  539. location : Vector(optional)
  540. The location of the new coordinate system's origin wrt this
  541. system's origin. If not specified, the origins are taken to
  542. be coincident.
  543. vector_names, variable_names : iterable(optional)
  544. Iterables of 3 strings each, with custom names for base
  545. vectors and base scalars of the new system respectively.
  546. Used for simple str printing.
  547. Examples
  548. ========
  549. >>> from sympy.vector import CoordSys3D
  550. >>> from sympy import symbols
  551. >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
  552. >>> N = CoordSys3D('N')
  553. Using an AxisOrienter
  554. >>> from sympy.vector import AxisOrienter
  555. >>> axis_orienter = AxisOrienter(q1, N.i + 2 * N.j)
  556. >>> A = N.orient_new('A', (axis_orienter, ))
  557. Using a BodyOrienter
  558. >>> from sympy.vector import BodyOrienter
  559. >>> body_orienter = BodyOrienter(q1, q2, q3, '123')
  560. >>> B = N.orient_new('B', (body_orienter, ))
  561. Using a SpaceOrienter
  562. >>> from sympy.vector import SpaceOrienter
  563. >>> space_orienter = SpaceOrienter(q1, q2, q3, '312')
  564. >>> C = N.orient_new('C', (space_orienter, ))
  565. Using a QuaternionOrienter
  566. >>> from sympy.vector import QuaternionOrienter
  567. >>> q_orienter = QuaternionOrienter(q0, q1, q2, q3)
  568. >>> D = N.orient_new('D', (q_orienter, ))
  569. """
  570. if variable_names is None:
  571. variable_names = self._variable_names
  572. if vector_names is None:
  573. vector_names = self._vector_names
  574. if isinstance(orienters, Orienter):
  575. if isinstance(orienters, AxisOrienter):
  576. final_matrix = orienters.rotation_matrix(self)
  577. else:
  578. final_matrix = orienters.rotation_matrix()
  579. # TODO: trigsimp is needed here so that the matrix becomes
  580. # canonical (scalar_map also calls trigsimp; without this, you can
  581. # end up with the same CoordinateSystem that compares differently
  582. # due to a differently formatted matrix). However, this is
  583. # probably not so good for performance.
  584. final_matrix = trigsimp(final_matrix)
  585. else:
  586. final_matrix = Matrix(eye(3))
  587. for orienter in orienters:
  588. if isinstance(orienter, AxisOrienter):
  589. final_matrix *= orienter.rotation_matrix(self)
  590. else:
  591. final_matrix *= orienter.rotation_matrix()
  592. return CoordSys3D(name, rotation_matrix=final_matrix,
  593. vector_names=vector_names,
  594. variable_names=variable_names,
  595. location=location,
  596. parent=self)
  597. def orient_new_axis(self, name, angle, axis, location=None,
  598. vector_names=None, variable_names=None):
  599. """
  600. Axis rotation is a rotation about an arbitrary axis by
  601. some angle. The angle is supplied as a SymPy expr scalar, and
  602. the axis is supplied as a Vector.
  603. Parameters
  604. ==========
  605. name : string
  606. The name of the new coordinate system
  607. angle : Expr
  608. The angle by which the new system is to be rotated
  609. axis : Vector
  610. The axis around which the rotation has to be performed
  611. location : Vector(optional)
  612. The location of the new coordinate system's origin wrt this
  613. system's origin. If not specified, the origins are taken to
  614. be coincident.
  615. vector_names, variable_names : iterable(optional)
  616. Iterables of 3 strings each, with custom names for base
  617. vectors and base scalars of the new system respectively.
  618. Used for simple str printing.
  619. Examples
  620. ========
  621. >>> from sympy.vector import CoordSys3D
  622. >>> from sympy import symbols
  623. >>> q1 = symbols('q1')
  624. >>> N = CoordSys3D('N')
  625. >>> B = N.orient_new_axis('B', q1, N.i + 2 * N.j)
  626. """
  627. if variable_names is None:
  628. variable_names = self._variable_names
  629. if vector_names is None:
  630. vector_names = self._vector_names
  631. orienter = AxisOrienter(angle, axis)
  632. return self.orient_new(name, orienter,
  633. location=location,
  634. vector_names=vector_names,
  635. variable_names=variable_names)
  636. def orient_new_body(self, name, angle1, angle2, angle3,
  637. rotation_order, location=None,
  638. vector_names=None, variable_names=None):
  639. """
  640. Body orientation takes this coordinate system through three
  641. successive simple rotations.
  642. Body fixed rotations include both Euler Angles and
  643. Tait-Bryan Angles, see https://en.wikipedia.org/wiki/Euler_angles.
  644. Parameters
  645. ==========
  646. name : string
  647. The name of the new coordinate system
  648. angle1, angle2, angle3 : Expr
  649. Three successive angles to rotate the coordinate system by
  650. rotation_order : string
  651. String defining the order of axes for rotation
  652. location : Vector(optional)
  653. The location of the new coordinate system's origin wrt this
  654. system's origin. If not specified, the origins are taken to
  655. be coincident.
  656. vector_names, variable_names : iterable(optional)
  657. Iterables of 3 strings each, with custom names for base
  658. vectors and base scalars of the new system respectively.
  659. Used for simple str printing.
  660. Examples
  661. ========
  662. >>> from sympy.vector import CoordSys3D
  663. >>> from sympy import symbols
  664. >>> q1, q2, q3 = symbols('q1 q2 q3')
  665. >>> N = CoordSys3D('N')
  666. A 'Body' fixed rotation is described by three angles and
  667. three body-fixed rotation axes. To orient a coordinate system D
  668. with respect to N, each sequential rotation is always about
  669. the orthogonal unit vectors fixed to D. For example, a '123'
  670. rotation will specify rotations about N.i, then D.j, then
  671. D.k. (Initially, D.i is same as N.i)
  672. Therefore,
  673. >>> D = N.orient_new_body('D', q1, q2, q3, '123')
  674. is same as
  675. >>> D = N.orient_new_axis('D', q1, N.i)
  676. >>> D = D.orient_new_axis('D', q2, D.j)
  677. >>> D = D.orient_new_axis('D', q3, D.k)
  678. Acceptable rotation orders are of length 3, expressed in XYZ or
  679. 123, and cannot have a rotation about about an axis twice in a row.
  680. >>> B = N.orient_new_body('B', q1, q2, q3, '123')
  681. >>> B = N.orient_new_body('B', q1, q2, 0, 'ZXZ')
  682. >>> B = N.orient_new_body('B', 0, 0, 0, 'XYX')
  683. """
  684. orienter = BodyOrienter(angle1, angle2, angle3, rotation_order)
  685. return self.orient_new(name, orienter,
  686. location=location,
  687. vector_names=vector_names,
  688. variable_names=variable_names)
  689. def orient_new_space(self, name, angle1, angle2, angle3,
  690. rotation_order, location=None,
  691. vector_names=None, variable_names=None):
  692. """
  693. Space rotation is similar to Body rotation, but the rotations
  694. are applied in the opposite order.
  695. Parameters
  696. ==========
  697. name : string
  698. The name of the new coordinate system
  699. angle1, angle2, angle3 : Expr
  700. Three successive angles to rotate the coordinate system by
  701. rotation_order : string
  702. String defining the order of axes for rotation
  703. location : Vector(optional)
  704. The location of the new coordinate system's origin wrt this
  705. system's origin. If not specified, the origins are taken to
  706. be coincident.
  707. vector_names, variable_names : iterable(optional)
  708. Iterables of 3 strings each, with custom names for base
  709. vectors and base scalars of the new system respectively.
  710. Used for simple str printing.
  711. See Also
  712. ========
  713. CoordSys3D.orient_new_body : method to orient via Euler
  714. angles
  715. Examples
  716. ========
  717. >>> from sympy.vector import CoordSys3D
  718. >>> from sympy import symbols
  719. >>> q1, q2, q3 = symbols('q1 q2 q3')
  720. >>> N = CoordSys3D('N')
  721. To orient a coordinate system D with respect to N, each
  722. sequential rotation is always about N's orthogonal unit vectors.
  723. For example, a '123' rotation will specify rotations about
  724. N.i, then N.j, then N.k.
  725. Therefore,
  726. >>> D = N.orient_new_space('D', q1, q2, q3, '312')
  727. is same as
  728. >>> B = N.orient_new_axis('B', q1, N.i)
  729. >>> C = B.orient_new_axis('C', q2, N.j)
  730. >>> D = C.orient_new_axis('D', q3, N.k)
  731. """
  732. orienter = SpaceOrienter(angle1, angle2, angle3, rotation_order)
  733. return self.orient_new(name, orienter,
  734. location=location,
  735. vector_names=vector_names,
  736. variable_names=variable_names)
  737. def orient_new_quaternion(self, name, q0, q1, q2, q3, location=None,
  738. vector_names=None, variable_names=None):
  739. """
  740. Quaternion orientation orients the new CoordSys3D with
  741. Quaternions, defined as a finite rotation about lambda, a unit
  742. vector, by some amount theta.
  743. This orientation is described by four parameters:
  744. q0 = cos(theta/2)
  745. q1 = lambda_x sin(theta/2)
  746. q2 = lambda_y sin(theta/2)
  747. q3 = lambda_z sin(theta/2)
  748. Quaternion does not take in a rotation order.
  749. Parameters
  750. ==========
  751. name : string
  752. The name of the new coordinate system
  753. q0, q1, q2, q3 : Expr
  754. The quaternions to rotate the coordinate system by
  755. location : Vector(optional)
  756. The location of the new coordinate system's origin wrt this
  757. system's origin. If not specified, the origins are taken to
  758. be coincident.
  759. vector_names, variable_names : iterable(optional)
  760. Iterables of 3 strings each, with custom names for base
  761. vectors and base scalars of the new system respectively.
  762. Used for simple str printing.
  763. Examples
  764. ========
  765. >>> from sympy.vector import CoordSys3D
  766. >>> from sympy import symbols
  767. >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
  768. >>> N = CoordSys3D('N')
  769. >>> B = N.orient_new_quaternion('B', q0, q1, q2, q3)
  770. """
  771. orienter = QuaternionOrienter(q0, q1, q2, q3)
  772. return self.orient_new(name, orienter,
  773. location=location,
  774. vector_names=vector_names,
  775. variable_names=variable_names)
  776. def create_new(self, name, transformation, variable_names=None, vector_names=None):
  777. """
  778. Returns a CoordSys3D which is connected to self by transformation.
  779. Parameters
  780. ==========
  781. name : str
  782. The name of the new CoordSys3D instance.
  783. transformation : Lambda, Tuple, str
  784. Transformation defined by transformation equations or chosen
  785. from predefined ones.
  786. vector_names, variable_names : iterable(optional)
  787. Iterables of 3 strings each, with custom names for base
  788. vectors and base scalars of the new system respectively.
  789. Used for simple str printing.
  790. Examples
  791. ========
  792. >>> from sympy.vector import CoordSys3D
  793. >>> a = CoordSys3D('a')
  794. >>> b = a.create_new('b', transformation='spherical')
  795. >>> b.transformation_to_parent()
  796. (b.r*sin(b.theta)*cos(b.phi), b.r*sin(b.phi)*sin(b.theta), b.r*cos(b.theta))
  797. >>> b.transformation_from_parent()
  798. (sqrt(a.x**2 + a.y**2 + a.z**2), acos(a.z/sqrt(a.x**2 + a.y**2 + a.z**2)), atan2(a.y, a.x))
  799. """
  800. return CoordSys3D(name, parent=self, transformation=transformation,
  801. variable_names=variable_names, vector_names=vector_names)
  802. def __init__(self, name, location=None, rotation_matrix=None,
  803. parent=None, vector_names=None, variable_names=None,
  804. latex_vects=None, pretty_vects=None, latex_scalars=None,
  805. pretty_scalars=None, transformation=None):
  806. # Dummy initializer for setting docstring
  807. pass
  808. __init__.__doc__ = __new__.__doc__
  809. @staticmethod
  810. def _compose_rotation_and_translation(rot, translation, parent):
  811. r = lambda x, y, z: CoordSys3D._rotation_trans_equations(rot, (x, y, z))
  812. if parent is None:
  813. return r
  814. dx, dy, dz = [translation.dot(i) for i in parent.base_vectors()]
  815. t = lambda x, y, z: (
  816. x + dx,
  817. y + dy,
  818. z + dz,
  819. )
  820. return lambda x, y, z: t(*r(x, y, z))
  821. def _check_strings(arg_name, arg):
  822. errorstr = arg_name + " must be an iterable of 3 string-types"
  823. if len(arg) != 3:
  824. raise ValueError(errorstr)
  825. for s in arg:
  826. if not isinstance(s, str):
  827. raise TypeError(errorstr)
  828. # Delayed import to avoid cyclic import problems:
  829. from sympy.vector.vector import BaseVector