Spaces:
Sleeping
Sleeping
from collections.abc import Callable | |
from sympy.core.basic import Basic | |
from sympy.core.cache import cacheit | |
from sympy.core import S, Dummy, Lambda | |
from sympy.core.symbol import Str | |
from sympy.core.symbol import symbols | |
from sympy.matrices.immutable import ImmutableDenseMatrix as Matrix | |
from sympy.matrices.matrixbase import MatrixBase | |
from sympy.solvers import solve | |
from sympy.vector.scalar import BaseScalar | |
from sympy.core.containers import Tuple | |
from sympy.core.function import diff | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.functions.elementary.trigonometric import (acos, atan2, cos, sin) | |
from sympy.matrices.dense import eye | |
from sympy.matrices.immutable import ImmutableDenseMatrix | |
from sympy.simplify.simplify import simplify | |
from sympy.simplify.trigsimp import trigsimp | |
import sympy.vector | |
from sympy.vector.orienters import (Orienter, AxisOrienter, BodyOrienter, | |
SpaceOrienter, QuaternionOrienter) | |
class CoordSys3D(Basic): | |
""" | |
Represents a coordinate system in 3-D space. | |
""" | |
def __new__(cls, name, transformation=None, parent=None, location=None, | |
rotation_matrix=None, vector_names=None, variable_names=None): | |
""" | |
The orientation/location parameters are necessary if this system | |
is being defined at a certain orientation or location wrt another. | |
Parameters | |
========== | |
name : str | |
The name of the new CoordSys3D instance. | |
transformation : Lambda, Tuple, str | |
Transformation defined by transformation equations or chosen | |
from predefined ones. | |
location : Vector | |
The position vector of the new system's origin wrt the parent | |
instance. | |
rotation_matrix : SymPy ImmutableMatrix | |
The rotation matrix of the new coordinate system with respect | |
to the parent. In other words, the output of | |
new_system.rotation_matrix(parent). | |
parent : CoordSys3D | |
The coordinate system wrt which the orientation/location | |
(or both) is being defined. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
""" | |
name = str(name) | |
Vector = sympy.vector.Vector | |
Point = sympy.vector.Point | |
if not isinstance(name, str): | |
raise TypeError("name should be a string") | |
if transformation is not None: | |
if (location is not None) or (rotation_matrix is not None): | |
raise ValueError("specify either `transformation` or " | |
"`location`/`rotation_matrix`") | |
if isinstance(transformation, (Tuple, tuple, list)): | |
if isinstance(transformation[0], MatrixBase): | |
rotation_matrix = transformation[0] | |
location = transformation[1] | |
else: | |
transformation = Lambda(transformation[0], | |
transformation[1]) | |
elif isinstance(transformation, Callable): | |
x1, x2, x3 = symbols('x1 x2 x3', cls=Dummy) | |
transformation = Lambda((x1, x2, x3), | |
transformation(x1, x2, x3)) | |
elif isinstance(transformation, str): | |
transformation = Str(transformation) | |
elif isinstance(transformation, (Str, Lambda)): | |
pass | |
else: | |
raise TypeError("transformation: " | |
"wrong type {}".format(type(transformation))) | |
# If orientation information has been provided, store | |
# the rotation matrix accordingly | |
if rotation_matrix is None: | |
rotation_matrix = ImmutableDenseMatrix(eye(3)) | |
else: | |
if not isinstance(rotation_matrix, MatrixBase): | |
raise TypeError("rotation_matrix should be an Immutable" + | |
"Matrix instance") | |
rotation_matrix = rotation_matrix.as_immutable() | |
# If location information is not given, adjust the default | |
# location as Vector.zero | |
if parent is not None: | |
if not isinstance(parent, CoordSys3D): | |
raise TypeError("parent should be a " + | |
"CoordSys3D/None") | |
if location is None: | |
location = Vector.zero | |
else: | |
if not isinstance(location, Vector): | |
raise TypeError("location should be a Vector") | |
# Check that location does not contain base | |
# scalars | |
for x in location.free_symbols: | |
if isinstance(x, BaseScalar): | |
raise ValueError("location should not contain" + | |
" BaseScalars") | |
origin = parent.origin.locate_new(name + '.origin', | |
location) | |
else: | |
location = Vector.zero | |
origin = Point(name + '.origin') | |
if transformation is None: | |
transformation = Tuple(rotation_matrix, location) | |
if isinstance(transformation, Tuple): | |
lambda_transformation = CoordSys3D._compose_rotation_and_translation( | |
transformation[0], | |
transformation[1], | |
parent | |
) | |
r, l = transformation | |
l = l._projections | |
lambda_lame = CoordSys3D._get_lame_coeff('cartesian') | |
lambda_inverse = lambda x, y, z: r.inv()*Matrix( | |
[x-l[0], y-l[1], z-l[2]]) | |
elif isinstance(transformation, Str): | |
trname = transformation.name | |
lambda_transformation = CoordSys3D._get_transformation_lambdas(trname) | |
if parent is not None: | |
if parent.lame_coefficients() != (S.One, S.One, S.One): | |
raise ValueError('Parent for pre-defined coordinate ' | |
'system should be Cartesian.') | |
lambda_lame = CoordSys3D._get_lame_coeff(trname) | |
lambda_inverse = CoordSys3D._set_inv_trans_equations(trname) | |
elif isinstance(transformation, Lambda): | |
if not CoordSys3D._check_orthogonality(transformation): | |
raise ValueError("The transformation equation does not " | |
"create orthogonal coordinate system") | |
lambda_transformation = transformation | |
lambda_lame = CoordSys3D._calculate_lame_coeff(lambda_transformation) | |
lambda_inverse = None | |
else: | |
lambda_transformation = lambda x, y, z: transformation(x, y, z) | |
lambda_lame = CoordSys3D._get_lame_coeff(transformation) | |
lambda_inverse = None | |
if variable_names is None: | |
if isinstance(transformation, Lambda): | |
variable_names = ["x1", "x2", "x3"] | |
elif isinstance(transformation, Str): | |
if transformation.name == 'spherical': | |
variable_names = ["r", "theta", "phi"] | |
elif transformation.name == 'cylindrical': | |
variable_names = ["r", "theta", "z"] | |
else: | |
variable_names = ["x", "y", "z"] | |
else: | |
variable_names = ["x", "y", "z"] | |
if vector_names is None: | |
vector_names = ["i", "j", "k"] | |
# All systems that are defined as 'roots' are unequal, unless | |
# they have the same name. | |
# Systems defined at same orientation/position wrt the same | |
# 'parent' are equal, irrespective of the name. | |
# This is true even if the same orientation is provided via | |
# different methods like Axis/Body/Space/Quaternion. | |
# However, coincident systems may be seen as unequal if | |
# positioned/oriented wrt different parents, even though | |
# they may actually be 'coincident' wrt the root system. | |
if parent is not None: | |
obj = super().__new__( | |
cls, Str(name), transformation, parent) | |
else: | |
obj = super().__new__( | |
cls, Str(name), transformation) | |
obj._name = name | |
# Initialize the base vectors | |
_check_strings('vector_names', vector_names) | |
vector_names = list(vector_names) | |
latex_vects = [(r'\mathbf{\hat{%s}_{%s}}' % (x, name)) for | |
x in vector_names] | |
pretty_vects = ['%s_%s' % (x, name) for x in vector_names] | |
obj._vector_names = vector_names | |
v1 = BaseVector(0, obj, pretty_vects[0], latex_vects[0]) | |
v2 = BaseVector(1, obj, pretty_vects[1], latex_vects[1]) | |
v3 = BaseVector(2, obj, pretty_vects[2], latex_vects[2]) | |
obj._base_vectors = (v1, v2, v3) | |
# Initialize the base scalars | |
_check_strings('variable_names', vector_names) | |
variable_names = list(variable_names) | |
latex_scalars = [(r"\mathbf{{%s}_{%s}}" % (x, name)) for | |
x in variable_names] | |
pretty_scalars = ['%s_%s' % (x, name) for x in variable_names] | |
obj._variable_names = variable_names | |
obj._vector_names = vector_names | |
x1 = BaseScalar(0, obj, pretty_scalars[0], latex_scalars[0]) | |
x2 = BaseScalar(1, obj, pretty_scalars[1], latex_scalars[1]) | |
x3 = BaseScalar(2, obj, pretty_scalars[2], latex_scalars[2]) | |
obj._base_scalars = (x1, x2, x3) | |
obj._transformation = transformation | |
obj._transformation_lambda = lambda_transformation | |
obj._lame_coefficients = lambda_lame(x1, x2, x3) | |
obj._transformation_from_parent_lambda = lambda_inverse | |
setattr(obj, variable_names[0], x1) | |
setattr(obj, variable_names[1], x2) | |
setattr(obj, variable_names[2], x3) | |
setattr(obj, vector_names[0], v1) | |
setattr(obj, vector_names[1], v2) | |
setattr(obj, vector_names[2], v3) | |
# Assign params | |
obj._parent = parent | |
if obj._parent is not None: | |
obj._root = obj._parent._root | |
else: | |
obj._root = obj | |
obj._parent_rotation_matrix = rotation_matrix | |
obj._origin = origin | |
# Return the instance | |
return obj | |
def _sympystr(self, printer): | |
return self._name | |
def __iter__(self): | |
return iter(self.base_vectors()) | |
def _check_orthogonality(equations): | |
""" | |
Helper method for _connect_to_cartesian. It checks if | |
set of transformation equations create orthogonal curvilinear | |
coordinate system | |
Parameters | |
========== | |
equations : Lambda | |
Lambda of transformation equations | |
""" | |
x1, x2, x3 = symbols("x1, x2, x3", cls=Dummy) | |
equations = equations(x1, x2, x3) | |
v1 = Matrix([diff(equations[0], x1), | |
diff(equations[1], x1), diff(equations[2], x1)]) | |
v2 = Matrix([diff(equations[0], x2), | |
diff(equations[1], x2), diff(equations[2], x2)]) | |
v3 = Matrix([diff(equations[0], x3), | |
diff(equations[1], x3), diff(equations[2], x3)]) | |
if any(simplify(i[0] + i[1] + i[2]) == 0 for i in (v1, v2, v3)): | |
return False | |
else: | |
if simplify(v1.dot(v2)) == 0 and simplify(v2.dot(v3)) == 0 \ | |
and simplify(v3.dot(v1)) == 0: | |
return True | |
else: | |
return False | |
def _set_inv_trans_equations(curv_coord_name): | |
""" | |
Store information about inverse transformation equations for | |
pre-defined coordinate systems. | |
Parameters | |
========== | |
curv_coord_name : str | |
Name of coordinate system | |
""" | |
if curv_coord_name == 'cartesian': | |
return lambda x, y, z: (x, y, z) | |
if curv_coord_name == 'spherical': | |
return lambda x, y, z: ( | |
sqrt(x**2 + y**2 + z**2), | |
acos(z/sqrt(x**2 + y**2 + z**2)), | |
atan2(y, x) | |
) | |
if curv_coord_name == 'cylindrical': | |
return lambda x, y, z: ( | |
sqrt(x**2 + y**2), | |
atan2(y, x), | |
z | |
) | |
raise ValueError('Wrong set of parameters.' | |
'Type of coordinate system is defined') | |
def _calculate_inv_trans_equations(self): | |
""" | |
Helper method for set_coordinate_type. It calculates inverse | |
transformation equations for given transformations equations. | |
""" | |
x1, x2, x3 = symbols("x1, x2, x3", cls=Dummy, reals=True) | |
x, y, z = symbols("x, y, z", cls=Dummy) | |
equations = self._transformation(x1, x2, x3) | |
solved = solve([equations[0] - x, | |
equations[1] - y, | |
equations[2] - z], (x1, x2, x3), dict=True)[0] | |
solved = solved[x1], solved[x2], solved[x3] | |
self._transformation_from_parent_lambda = \ | |
lambda x1, x2, x3: tuple(i.subs(list(zip((x, y, z), (x1, x2, x3)))) for i in solved) | |
def _get_lame_coeff(curv_coord_name): | |
""" | |
Store information about Lame coefficients for pre-defined | |
coordinate systems. | |
Parameters | |
========== | |
curv_coord_name : str | |
Name of coordinate system | |
""" | |
if isinstance(curv_coord_name, str): | |
if curv_coord_name == 'cartesian': | |
return lambda x, y, z: (S.One, S.One, S.One) | |
if curv_coord_name == 'spherical': | |
return lambda r, theta, phi: (S.One, r, r*sin(theta)) | |
if curv_coord_name == 'cylindrical': | |
return lambda r, theta, h: (S.One, r, S.One) | |
raise ValueError('Wrong set of parameters.' | |
' Type of coordinate system is not defined') | |
return CoordSys3D._calculate_lame_coefficients(curv_coord_name) | |
def _calculate_lame_coeff(equations): | |
""" | |
It calculates Lame coefficients | |
for given transformations equations. | |
Parameters | |
========== | |
equations : Lambda | |
Lambda of transformation equations. | |
""" | |
return lambda x1, x2, x3: ( | |
sqrt(diff(equations(x1, x2, x3)[0], x1)**2 + | |
diff(equations(x1, x2, x3)[1], x1)**2 + | |
diff(equations(x1, x2, x3)[2], x1)**2), | |
sqrt(diff(equations(x1, x2, x3)[0], x2)**2 + | |
diff(equations(x1, x2, x3)[1], x2)**2 + | |
diff(equations(x1, x2, x3)[2], x2)**2), | |
sqrt(diff(equations(x1, x2, x3)[0], x3)**2 + | |
diff(equations(x1, x2, x3)[1], x3)**2 + | |
diff(equations(x1, x2, x3)[2], x3)**2) | |
) | |
def _inverse_rotation_matrix(self): | |
""" | |
Returns inverse rotation matrix. | |
""" | |
return simplify(self._parent_rotation_matrix**-1) | |
def _get_transformation_lambdas(curv_coord_name): | |
""" | |
Store information about transformation equations for pre-defined | |
coordinate systems. | |
Parameters | |
========== | |
curv_coord_name : str | |
Name of coordinate system | |
""" | |
if isinstance(curv_coord_name, str): | |
if curv_coord_name == 'cartesian': | |
return lambda x, y, z: (x, y, z) | |
if curv_coord_name == 'spherical': | |
return lambda r, theta, phi: ( | |
r*sin(theta)*cos(phi), | |
r*sin(theta)*sin(phi), | |
r*cos(theta) | |
) | |
if curv_coord_name == 'cylindrical': | |
return lambda r, theta, h: ( | |
r*cos(theta), | |
r*sin(theta), | |
h | |
) | |
raise ValueError('Wrong set of parameters.' | |
'Type of coordinate system is defined') | |
def _rotation_trans_equations(cls, matrix, equations): | |
""" | |
Returns the transformation equations obtained from rotation matrix. | |
Parameters | |
========== | |
matrix : Matrix | |
Rotation matrix | |
equations : tuple | |
Transformation equations | |
""" | |
return tuple(matrix * Matrix(equations)) | |
def origin(self): | |
return self._origin | |
def base_vectors(self): | |
return self._base_vectors | |
def base_scalars(self): | |
return self._base_scalars | |
def lame_coefficients(self): | |
return self._lame_coefficients | |
def transformation_to_parent(self): | |
return self._transformation_lambda(*self.base_scalars()) | |
def transformation_from_parent(self): | |
if self._parent is None: | |
raise ValueError("no parent coordinate system, use " | |
"`transformation_from_parent_function()`") | |
return self._transformation_from_parent_lambda( | |
*self._parent.base_scalars()) | |
def transformation_from_parent_function(self): | |
return self._transformation_from_parent_lambda | |
def rotation_matrix(self, other): | |
""" | |
Returns the direction cosine matrix(DCM), also known as the | |
'rotation matrix' of this coordinate system with respect to | |
another system. | |
If v_a is a vector defined in system 'A' (in matrix format) | |
and v_b is the same vector defined in system 'B', then | |
v_a = A.rotation_matrix(B) * v_b. | |
A SymPy Matrix is returned. | |
Parameters | |
========== | |
other : CoordSys3D | |
The system which the DCM is generated to. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q1 = symbols('q1') | |
>>> N = CoordSys3D('N') | |
>>> A = N.orient_new_axis('A', q1, N.i) | |
>>> N.rotation_matrix(A) | |
Matrix([ | |
[1, 0, 0], | |
[0, cos(q1), -sin(q1)], | |
[0, sin(q1), cos(q1)]]) | |
""" | |
from sympy.vector.functions import _path | |
if not isinstance(other, CoordSys3D): | |
raise TypeError(str(other) + | |
" is not a CoordSys3D") | |
# Handle special cases | |
if other == self: | |
return eye(3) | |
elif other == self._parent: | |
return self._parent_rotation_matrix | |
elif other._parent == self: | |
return other._parent_rotation_matrix.T | |
# Else, use tree to calculate position | |
rootindex, path = _path(self, other) | |
result = eye(3) | |
i = -1 | |
for i in range(rootindex): | |
result *= path[i]._parent_rotation_matrix | |
i += 2 | |
while i < len(path): | |
result *= path[i]._parent_rotation_matrix.T | |
i += 1 | |
return result | |
def position_wrt(self, other): | |
""" | |
Returns the position vector of the origin of this coordinate | |
system with respect to another Point/CoordSys3D. | |
Parameters | |
========== | |
other : Point/CoordSys3D | |
If other is a Point, the position of this system's origin | |
wrt it is returned. If its an instance of CoordSyRect, | |
the position wrt its origin is returned. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> N = CoordSys3D('N') | |
>>> N1 = N.locate_new('N1', 10 * N.i) | |
>>> N.position_wrt(N1) | |
(-10)*N.i | |
""" | |
return self.origin.position_wrt(other) | |
def scalar_map(self, other): | |
""" | |
Returns a dictionary which expresses the coordinate variables | |
(base scalars) of this frame in terms of the variables of | |
otherframe. | |
Parameters | |
========== | |
otherframe : CoordSys3D | |
The other system to map the variables to. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import Symbol | |
>>> A = CoordSys3D('A') | |
>>> q = Symbol('q') | |
>>> B = A.orient_new_axis('B', q, A.k) | |
>>> A.scalar_map(B) | |
{A.x: B.x*cos(q) - B.y*sin(q), A.y: B.x*sin(q) + B.y*cos(q), A.z: B.z} | |
""" | |
origin_coords = tuple(self.position_wrt(other).to_matrix(other)) | |
relocated_scalars = [x - origin_coords[i] | |
for i, x in enumerate(other.base_scalars())] | |
vars_matrix = (self.rotation_matrix(other) * | |
Matrix(relocated_scalars)) | |
return {x: trigsimp(vars_matrix[i]) | |
for i, x in enumerate(self.base_scalars())} | |
def locate_new(self, name, position, vector_names=None, | |
variable_names=None): | |
""" | |
Returns a CoordSys3D with its origin located at the given | |
position wrt this coordinate system's origin. | |
Parameters | |
========== | |
name : str | |
The name of the new CoordSys3D instance. | |
position : Vector | |
The position vector of the new system's origin wrt this | |
one. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> A = CoordSys3D('A') | |
>>> B = A.locate_new('B', 10 * A.i) | |
>>> B.origin.position_wrt(A.origin) | |
10*A.i | |
""" | |
if variable_names is None: | |
variable_names = self._variable_names | |
if vector_names is None: | |
vector_names = self._vector_names | |
return CoordSys3D(name, location=position, | |
vector_names=vector_names, | |
variable_names=variable_names, | |
parent=self) | |
def orient_new(self, name, orienters, location=None, | |
vector_names=None, variable_names=None): | |
""" | |
Creates a new CoordSys3D oriented in the user-specified way | |
with respect to this system. | |
Please refer to the documentation of the orienter classes | |
for more information about the orientation procedure. | |
Parameters | |
========== | |
name : str | |
The name of the new CoordSys3D instance. | |
orienters : iterable/Orienter | |
An Orienter or an iterable of Orienters for orienting the | |
new coordinate system. | |
If an Orienter is provided, it is applied to get the new | |
system. | |
If an iterable is provided, the orienters will be applied | |
in the order in which they appear in the iterable. | |
location : Vector(optional) | |
The location of the new coordinate system's origin wrt this | |
system's origin. If not specified, the origins are taken to | |
be coincident. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3') | |
>>> N = CoordSys3D('N') | |
Using an AxisOrienter | |
>>> from sympy.vector import AxisOrienter | |
>>> axis_orienter = AxisOrienter(q1, N.i + 2 * N.j) | |
>>> A = N.orient_new('A', (axis_orienter, )) | |
Using a BodyOrienter | |
>>> from sympy.vector import BodyOrienter | |
>>> body_orienter = BodyOrienter(q1, q2, q3, '123') | |
>>> B = N.orient_new('B', (body_orienter, )) | |
Using a SpaceOrienter | |
>>> from sympy.vector import SpaceOrienter | |
>>> space_orienter = SpaceOrienter(q1, q2, q3, '312') | |
>>> C = N.orient_new('C', (space_orienter, )) | |
Using a QuaternionOrienter | |
>>> from sympy.vector import QuaternionOrienter | |
>>> q_orienter = QuaternionOrienter(q0, q1, q2, q3) | |
>>> D = N.orient_new('D', (q_orienter, )) | |
""" | |
if variable_names is None: | |
variable_names = self._variable_names | |
if vector_names is None: | |
vector_names = self._vector_names | |
if isinstance(orienters, Orienter): | |
if isinstance(orienters, AxisOrienter): | |
final_matrix = orienters.rotation_matrix(self) | |
else: | |
final_matrix = orienters.rotation_matrix() | |
# TODO: trigsimp is needed here so that the matrix becomes | |
# canonical (scalar_map also calls trigsimp; without this, you can | |
# end up with the same CoordinateSystem that compares differently | |
# due to a differently formatted matrix). However, this is | |
# probably not so good for performance. | |
final_matrix = trigsimp(final_matrix) | |
else: | |
final_matrix = Matrix(eye(3)) | |
for orienter in orienters: | |
if isinstance(orienter, AxisOrienter): | |
final_matrix *= orienter.rotation_matrix(self) | |
else: | |
final_matrix *= orienter.rotation_matrix() | |
return CoordSys3D(name, rotation_matrix=final_matrix, | |
vector_names=vector_names, | |
variable_names=variable_names, | |
location=location, | |
parent=self) | |
def orient_new_axis(self, name, angle, axis, location=None, | |
vector_names=None, variable_names=None): | |
""" | |
Axis rotation is a rotation about an arbitrary axis by | |
some angle. The angle is supplied as a SymPy expr scalar, and | |
the axis is supplied as a Vector. | |
Parameters | |
========== | |
name : string | |
The name of the new coordinate system | |
angle : Expr | |
The angle by which the new system is to be rotated | |
axis : Vector | |
The axis around which the rotation has to be performed | |
location : Vector(optional) | |
The location of the new coordinate system's origin wrt this | |
system's origin. If not specified, the origins are taken to | |
be coincident. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q1 = symbols('q1') | |
>>> N = CoordSys3D('N') | |
>>> B = N.orient_new_axis('B', q1, N.i + 2 * N.j) | |
""" | |
if variable_names is None: | |
variable_names = self._variable_names | |
if vector_names is None: | |
vector_names = self._vector_names | |
orienter = AxisOrienter(angle, axis) | |
return self.orient_new(name, orienter, | |
location=location, | |
vector_names=vector_names, | |
variable_names=variable_names) | |
def orient_new_body(self, name, angle1, angle2, angle3, | |
rotation_order, location=None, | |
vector_names=None, variable_names=None): | |
""" | |
Body orientation takes this coordinate system through three | |
successive simple rotations. | |
Body fixed rotations include both Euler Angles and | |
Tait-Bryan Angles, see https://en.wikipedia.org/wiki/Euler_angles. | |
Parameters | |
========== | |
name : string | |
The name of the new coordinate system | |
angle1, angle2, angle3 : Expr | |
Three successive angles to rotate the coordinate system by | |
rotation_order : string | |
String defining the order of axes for rotation | |
location : Vector(optional) | |
The location of the new coordinate system's origin wrt this | |
system's origin. If not specified, the origins are taken to | |
be coincident. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q1, q2, q3 = symbols('q1 q2 q3') | |
>>> N = CoordSys3D('N') | |
A 'Body' fixed rotation is described by three angles and | |
three body-fixed rotation axes. To orient a coordinate system D | |
with respect to N, each sequential rotation is always about | |
the orthogonal unit vectors fixed to D. For example, a '123' | |
rotation will specify rotations about N.i, then D.j, then | |
D.k. (Initially, D.i is same as N.i) | |
Therefore, | |
>>> D = N.orient_new_body('D', q1, q2, q3, '123') | |
is same as | |
>>> D = N.orient_new_axis('D', q1, N.i) | |
>>> D = D.orient_new_axis('D', q2, D.j) | |
>>> D = D.orient_new_axis('D', q3, D.k) | |
Acceptable rotation orders are of length 3, expressed in XYZ or | |
123, and cannot have a rotation about about an axis twice in a row. | |
>>> B = N.orient_new_body('B', q1, q2, q3, '123') | |
>>> B = N.orient_new_body('B', q1, q2, 0, 'ZXZ') | |
>>> B = N.orient_new_body('B', 0, 0, 0, 'XYX') | |
""" | |
orienter = BodyOrienter(angle1, angle2, angle3, rotation_order) | |
return self.orient_new(name, orienter, | |
location=location, | |
vector_names=vector_names, | |
variable_names=variable_names) | |
def orient_new_space(self, name, angle1, angle2, angle3, | |
rotation_order, location=None, | |
vector_names=None, variable_names=None): | |
""" | |
Space rotation is similar to Body rotation, but the rotations | |
are applied in the opposite order. | |
Parameters | |
========== | |
name : string | |
The name of the new coordinate system | |
angle1, angle2, angle3 : Expr | |
Three successive angles to rotate the coordinate system by | |
rotation_order : string | |
String defining the order of axes for rotation | |
location : Vector(optional) | |
The location of the new coordinate system's origin wrt this | |
system's origin. If not specified, the origins are taken to | |
be coincident. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
See Also | |
======== | |
CoordSys3D.orient_new_body : method to orient via Euler | |
angles | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q1, q2, q3 = symbols('q1 q2 q3') | |
>>> N = CoordSys3D('N') | |
To orient a coordinate system D with respect to N, each | |
sequential rotation is always about N's orthogonal unit vectors. | |
For example, a '123' rotation will specify rotations about | |
N.i, then N.j, then N.k. | |
Therefore, | |
>>> D = N.orient_new_space('D', q1, q2, q3, '312') | |
is same as | |
>>> B = N.orient_new_axis('B', q1, N.i) | |
>>> C = B.orient_new_axis('C', q2, N.j) | |
>>> D = C.orient_new_axis('D', q3, N.k) | |
""" | |
orienter = SpaceOrienter(angle1, angle2, angle3, rotation_order) | |
return self.orient_new(name, orienter, | |
location=location, | |
vector_names=vector_names, | |
variable_names=variable_names) | |
def orient_new_quaternion(self, name, q0, q1, q2, q3, location=None, | |
vector_names=None, variable_names=None): | |
""" | |
Quaternion orientation orients the new CoordSys3D with | |
Quaternions, defined as a finite rotation about lambda, a unit | |
vector, by some amount theta. | |
This orientation is described by four parameters: | |
q0 = cos(theta/2) | |
q1 = lambda_x sin(theta/2) | |
q2 = lambda_y sin(theta/2) | |
q3 = lambda_z sin(theta/2) | |
Quaternion does not take in a rotation order. | |
Parameters | |
========== | |
name : string | |
The name of the new coordinate system | |
q0, q1, q2, q3 : Expr | |
The quaternions to rotate the coordinate system by | |
location : Vector(optional) | |
The location of the new coordinate system's origin wrt this | |
system's origin. If not specified, the origins are taken to | |
be coincident. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> from sympy import symbols | |
>>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3') | |
>>> N = CoordSys3D('N') | |
>>> B = N.orient_new_quaternion('B', q0, q1, q2, q3) | |
""" | |
orienter = QuaternionOrienter(q0, q1, q2, q3) | |
return self.orient_new(name, orienter, | |
location=location, | |
vector_names=vector_names, | |
variable_names=variable_names) | |
def create_new(self, name, transformation, variable_names=None, vector_names=None): | |
""" | |
Returns a CoordSys3D which is connected to self by transformation. | |
Parameters | |
========== | |
name : str | |
The name of the new CoordSys3D instance. | |
transformation : Lambda, Tuple, str | |
Transformation defined by transformation equations or chosen | |
from predefined ones. | |
vector_names, variable_names : iterable(optional) | |
Iterables of 3 strings each, with custom names for base | |
vectors and base scalars of the new system respectively. | |
Used for simple str printing. | |
Examples | |
======== | |
>>> from sympy.vector import CoordSys3D | |
>>> a = CoordSys3D('a') | |
>>> b = a.create_new('b', transformation='spherical') | |
>>> b.transformation_to_parent() | |
(b.r*sin(b.theta)*cos(b.phi), b.r*sin(b.phi)*sin(b.theta), b.r*cos(b.theta)) | |
>>> b.transformation_from_parent() | |
(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)) | |
""" | |
return CoordSys3D(name, parent=self, transformation=transformation, | |
variable_names=variable_names, vector_names=vector_names) | |
def __init__(self, name, location=None, rotation_matrix=None, | |
parent=None, vector_names=None, variable_names=None, | |
latex_vects=None, pretty_vects=None, latex_scalars=None, | |
pretty_scalars=None, transformation=None): | |
# Dummy initializer for setting docstring | |
pass | |
__init__.__doc__ = __new__.__doc__ | |
def _compose_rotation_and_translation(rot, translation, parent): | |
r = lambda x, y, z: CoordSys3D._rotation_trans_equations(rot, (x, y, z)) | |
if parent is None: | |
return r | |
dx, dy, dz = [translation.dot(i) for i in parent.base_vectors()] | |
t = lambda x, y, z: ( | |
x + dx, | |
y + dy, | |
z + dz, | |
) | |
return lambda x, y, z: t(*r(x, y, z)) | |
def _check_strings(arg_name, arg): | |
errorstr = arg_name + " must be an iterable of 3 string-types" | |
if len(arg) != 3: | |
raise ValueError(errorstr) | |
for s in arg: | |
if not isinstance(s, str): | |
raise TypeError(errorstr) | |
# Delayed import to avoid cyclic import problems: | |
from sympy.vector.vector import BaseVector | |