Repository: sympy/sympy

Issue:
Break sympy.physics.vector.frame.RefererenceFrame.orient() into 5 seperate methods.
`sympy.physics.vector.frame.RefererenceFrame.orient()` is a very long, complicated, and unwieldy method and there are just two many different types of arguments that it accepts, which requires very detailed explanations. It should be broken up into sub-methods. These could be:

`ReferenceFrame.orient_body()`
`orient_axis()`
`orient_dcm()`
etc.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v161). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* physics.vector
  - Broke sympy.physics.vector.frame.RefererenceFrame.orient() into 5 parts ([#20318](https://github.com/sympy/sympy/pull/20318) by [@sidhu1012](https://github.com/sidhu1012) and [@sksingh1202](https://github.com/sksingh1202))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.8.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    <!-- Your title above should be a short description of what
    was changed. Do not include the issue number in the title. -->

    #### References to other Issues or PRs
    <!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
    format, e.g. "Fixes #1234" (see
    https://tinyurl.com/auto-closing for more information). Also, please
    write a comment on that issue linking back to this pull request once it is
    open. -->
    Fixes #17758 

    #### Brief description of what is fixed or changed
    Break sympy.physics.vector.frame.RefererenceFrame.orient() into 5 parts 

    #### Other comments


    #### Release Notes

    <!-- Write the release notes for this release below. See
    https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
    on how to write release notes. The bot will check your release notes
    automatically to see if they are formatted correctly. -->

    <!-- BEGIN RELEASE NOTES -->
    - physics.vector
          - Broke sympy.physics.vector.frame.RefererenceFrame.orient() into 5 parts 
    <!-- END RELEASE NOTES -->

</details><p>

sympy-bot: ### 🟠

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v161). I've noticed that some of your commits add or delete files. Since this is sometimes done unintentionally, I wanted to alert you about it.

This is an experimental feature of SymPy Bot. If you have any feedback on it, please comment at https://github.com/sympy/sympy-bot/issues/75.

The following commits **add new files**:
* 9c19cc90b4e273a912cfca0c355c3d50e278c0a7:
  - `sympy/matrices/tests/test_solvers.py`

The following commits **delete files**:
* 6d2e79eda385ff82e4911cd3b401daf976a662d0:
  - `sympy/matrices/tests/test_solvers.py`

If these files were added/deleted on purpose, you can ignore this message.

oscarbenjamin: This PR removes all of the blame history for the `test_solvers.py` file because the file is deleted and then added again.

It would be better to squash the commits into a single commit with @sksingh1202 listed as co-author.
sidhu1012: @moorepants Please review and tell what's need to be done further.
sidhu1012: @moorepants I have added test cases , added back orient function, fixed docstring abd everything is working
Test failed because of HTML error.
 
What need's to be done further
sidhu1012: @moorepants Please review
sidhu1012: Is this an error with code or build?

Makefile:49: recipe for target 'html' failed
make: *** [html] Error 1
codecov[bot]: # [Codecov](https://codecov.io/gh/sympy/sympy/pull/20318?src=pr&el=h1) Report
> Merging [#20318](https://codecov.io/gh/sympy/sympy/pull/20318?src=pr&el=desc) (d73a66d) into [master](https://codecov.io/gh/sympy/sympy/commit/a1fbd0066219a7a1d14d4d9024d8aeeb5cb8d51a?el=desc) (a1fbd00) will **increase** coverage by `0.086%`.
> The diff coverage is `85.106%`.

```diff
@@              Coverage Diff              @@
##            master    #20318       +/-   ##
=============================================
+ Coverage   75.659%   75.746%   +0.086%     
=============================================
  Files          647       673       +26     
  Lines       168437    174260     +5823     
  Branches     39689     41134     +1445     
=============================================
+ Hits        127439    131996     +4557     
- Misses       35443     36529     +1086     
- Partials      5555      5735      +180     
```

sidhu1012: @moorepants Finally passed all tests. Please review now.
moorepants: I'll try to get to it soon. Thanks.
sidhu1012: Closing and opening to restart tests.
sidhu1012: @moorepants Please Review.
czgdp1807: @moorepants This seems ready. Are there any more changes required to conclude this PR. Thank you.
sidhu1012: @moorepants Done! Ready for review 😂
sidhu1012: @moorepants Please review.
moorepants: @sidhu1012 I added a commit with some improvements to the new docstrings. Have a look to see if you agree with the changes.
sidhu1012: > @sidhu1012 I added a commit with some improvements to the new docstrings. Have a look to see if you agree with the changes.

New docstring looks much better. I have fixed the failing tests. Does it look good now?
moorepants: Ok, I'm good with this now. If tests pass it can be merged.
moorepants: We need to do this:

> This PR removes all of the blame history for the test_solvers.py file because the file is deleted and then added again.

> It would be better to squash the commits into a single commit with @sksingh1202 listed as co-author.

I'm not sure how to squash and retain contributions from the three authors though.
oscarbenjamin: > I'm not sure how to squash and retain contributions from the three authors though.

I would just squash to a single commit and use Co-authored-by
moorepants: I assume you mean to simply mention the authors in the commit message?
oscarbenjamin: There is an established convention to use `Co-authored-by`:
https://stackoverflow.com/questions/7442112/how-to-attribute-a-single-commit-to-multiple-developers

This is what I do if I squash the commits for someone else e.g.: https://github.com/sympy/sympy/pull/20771

Patch:
diff --git a/sympy/physics/vector/frame.py b/sympy/physics/vector/frame.py
index dadc0813ac..496f37bc77 100644
--- a/sympy/physics/vector/frame.py
+++ b/sympy/physics/vector/frame.py
@@ -1,5 +1,5 @@
-from sympy.core.backend import (diff, expand, sin, cos, sympify,
-                   eye, symbols, ImmutableMatrix as Matrix, MatrixBase)
+from sympy.core.backend import (diff, expand, sin, cos, sympify, eye, symbols,
+                                ImmutableMatrix as Matrix, MatrixBase)
 from sympy import (trigsimp, solve, Symbol, Dummy)
 from sympy.physics.vector.vector import Vector, _check_vector
 from sympy.utilities.misc import translate
@@ -457,7 +457,7 @@ def dcm(self, otherframe):
              \hat{\mathbf{a}}_3
            \end{bmatrix}.
 
-        :math:`^{}A\mathbf{R}^B` is the matrix that expresses the B unit
+        :math:`{}^A\mathbf{R}^B` is the matrix that expresses the B unit
         vectors in terms of the A unit vectors.
 
         """
@@ -476,9 +476,34 @@ def dcm(self, otherframe):
         otherframe._dcm_cache[self] = outdcm.T
         return outdcm
 
-    def orient(self, parent, rot_type, amounts, rot_order=''):
-        """Sets the orientation of this reference frame relative to another
-        (parent) reference frame.
+    def _dcm(self, parent, parent_orient):
+        # Reset the _dcm_cache of this frame, and remove it from the
+        # _dcm_caches of the frames it is linked to. Also remove it from the
+        # _dcm_dict of its parent
+        frames = self._dcm_cache.keys()
+        dcm_dict_del = []
+        dcm_cache_del = []
+        for frame in frames:
+            if frame in self._dcm_dict:
+                dcm_dict_del += [frame]
+            dcm_cache_del += [frame]
+        for frame in dcm_dict_del:
+            del frame._dcm_dict[self]
+        for frame in dcm_cache_del:
+            del frame._dcm_cache[self]
+        # Add the dcm relationship to _dcm_dict
+        self._dcm_dict = self._dlist[0] = {}
+        self._dcm_dict.update({parent: parent_orient.T})
+        parent._dcm_dict.update({self: parent_orient})
+        # Also update the dcm cache after resetting it
+        self._dcm_cache = {}
+        self._dcm_cache.update({parent: parent_orient.T})
+        parent._dcm_cache.update({self: parent_orient})
+
+    def orient_axis(self, parent, axis, angle):
+        """Sets the orientation of this reference frame with respect to a
+        parent reference frame by rotating through an angle about an axis fixed
+        in the parent reference frame.
 
         Parameters
         ==========
@@ -486,35 +511,12 @@ def orient(self, parent, rot_type, amounts, rot_order=''):
         parent : ReferenceFrame
             Reference frame that this reference frame will be rotated relative
             to.
-        rot_type : str
-            The method used to generate the direction cosine matrix. Supported
-            methods are:
-
-            - ``'Axis'``: simple rotations about a single common axis
-            - ``'DCM'``: for setting the direction cosine matrix directly
-            - ``'Body'``: three successive rotations about new intermediate
-              axes, also called "Euler and Tait-Bryan angles"
-            - ``'Space'``: three successive rotations about the parent
-              frames' unit vectors
-            - ``'Quaternion'``: rotations defined by four parameters which
-              result in a singularity free direction cosine matrix
-
-        amounts :
-            Expressions defining the rotation angles or direction cosine
-            matrix. These must match the ``rot_type``. See examples below for
-            details. The input types are:
-
-            - ``'Axis'``: 2-tuple (expr/sym/func, Vector)
-            - ``'DCM'``: Matrix, shape(3,3)
-            - ``'Body'``: 3-tuple of expressions, symbols, or functions
-            - ``'Space'``: 3-tuple of expressions, symbols, or functions
-            - ``'Quaternion'``: 4-tuple of expressions, symbols, or
-              functions
-
-        rot_order : str or int, optional
-            If applicable, the order of the successive of rotations. The string
-            ``'123'`` and integer ``123`` are equivalent, for example. Required
-            for ``'Body'`` and ``'Space'``.
+        axis : Vector
+            Vector fixed in the parent frame about about which this frame is
+            rotated. It need not be a unit vector and the rotation follows the
+            right hand rule.
+        angle : sympifiable
+            Angle in radians by which it the frame is to be rotated.
 
         Examples
         ========
@@ -523,89 +525,211 @@ def orient(self, parent, rot_type, amounts, rot_order=''):
 
         >>> from sympy import symbols
         >>> from sympy.physics.vector import ReferenceFrame
-        >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
+        >>> q1 = symbols('q1')
         >>> N = ReferenceFrame('N')
         >>> B = ReferenceFrame('B')
-        >>> B1 = ReferenceFrame('B')
-        >>> B2 = ReferenceFrame('B2')
-
-        Axis
-        ----
-
-        ``rot_type='Axis'`` creates a direction cosine matrix defined by a
-        simple rotation about a single axis fixed in both reference frames.
-        This is a rotation about an arbitrary, non-time-varying
-        axis by some angle. The axis is supplied as a Vector. This is how
-        simple rotations are defined.
+        >>> B.orient_axis(N, N.x, q1)
 
-        >>> B.orient(N, 'Axis', (q1, N.x))
-
-        The ``orient()`` method generates a direction cosine matrix and its
-        transpose which defines the orientation of B relative to N and vice
+        The ``orient_axis()`` method generates a direction cosine matrix and
+        its transpose which defines the orientation of B relative to N and vice
         versa. Once orient is called, ``dcm()`` outputs the appropriate
-        direction cosine matrix.
+        direction cosine matrix:
 
         >>> B.dcm(N)
         Matrix([
         [1,       0,      0],
         [0,  cos(q1), sin(q1)],
         [0, -sin(q1), cos(q1)]])
+        >>> N.dcm(B)
+        Matrix([
+        [1,       0,        0],
+        [0, cos(q1), -sin(q1)],
+        [0, sin(q1),  cos(q1)]])
 
-        The following two lines show how the sense of the rotation can be
-        defined. Both lines produce the same result.
+        The following two lines show that the sense of the rotation can be
+        defined by negating the vector direction or the angle. Both lines
+        produce the same result.
 
-        >>> B.orient(N, 'Axis', (q1, -N.x))
-        >>> B.orient(N, 'Axis', (-q1, N.x))
+        >>> B.orient_axis(N, -N.x, q1)
+        >>> B.orient_axis(N, N.x, -q1)
 
-        The axis does not have to be defined by a unit vector, it can be any
-        vector in the parent frame.
+        """
 
-        >>> B.orient(N, 'Axis', (q1, N.x + 2 * N.y))
+        from sympy.physics.vector.functions import dynamicsymbols
+        _check_frame(parent)
 
-        DCM
-        ---
+        amount = sympify(angle)
+        theta = amount
+        axis = _check_vector(axis)
+        parent_orient_axis = []
+
+        if not axis.dt(parent) == 0:
+            raise ValueError('Axis cannot be time-varying.')
+        unit_axis = axis.express(parent).normalize()
+        unit_col = unit_axis.args[0][0]
+        parent_orient_axis = (
+            (eye(3) - unit_col * unit_col.T) * cos(theta) +
+            Matrix([[0, -unit_col[2], unit_col[1]],
+                    [unit_col[2], 0, -unit_col[0]],
+                    [-unit_col[1], unit_col[0], 0]]) *
+            sin(theta) + unit_col * unit_col.T)
+
+        self._dcm(parent, parent_orient_axis)
+
+        thetad = (amount).diff(dynamicsymbols._t)
+        wvec = thetad*axis.express(parent).normalize()
+        self._ang_vel_dict.update({parent: wvec})
+        parent._ang_vel_dict.update({self: -wvec})
+        self._var_dict = {}
 
-        The direction cosine matrix can be set directly. The orientation of a
-        frame A can be set to be the same as the frame B above like so:
+    def orient_explicit(self, parent, dcm):
+        """Sets the orientation of this reference frame relative to a parent
+        reference frame by explicitly setting the direction cosine matrix.
 
-        >>> B.orient(N, 'Axis', (q1, N.x))
+        Parameters
+        ==========
+
+        parent : ReferenceFrame
+            Reference frame that this reference frame will be rotated relative
+            to.
+        dcm : Matrix, shape(3, 3)
+            Direction cosine matrix that specifies the relative rotation
+            between the two reference frames.
+
+        Examples
+        ========
+
+        Setup variables for the examples:
+
+        >>> from sympy import symbols, Matrix, sin, cos
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> q1 = symbols('q1')
         >>> A = ReferenceFrame('A')
-        >>> A.orient(N, 'DCM', N.dcm(B))
+        >>> B = ReferenceFrame('B')
+        >>> N = ReferenceFrame('N')
+
+        A simple rotation of ``A`` relative to ``N`` about ``N.x`` is defined
+        by the following direction cosine matrix:
+
+        >>> dcm = Matrix([[1, 0, 0],
+        ...               [0, cos(q1), -sin(q1)],
+        ...               [0, sin(q1), cos(q1)]])
+        >>> A.orient_explicit(N, dcm)
+        >>> A.dcm(N)
+        Matrix([
+        [1,       0,      0],
+        [0,  cos(q1), sin(q1)],
+        [0, -sin(q1), cos(q1)]])
+
+        This is equivalent to using ``orient_axis()``:
+
+        >>> B.orient_axis(N, N.x, q1)
+        >>> B.dcm(N)
+        Matrix([
+        [1,       0,      0],
+        [0,  cos(q1), sin(q1)],
+        [0, -sin(q1), cos(q1)]])
+
+        **Note carefully that** ``N.dcm(B)`` **(the transpose) would be passed
+        into** ``orient_explicit()`` **for** ``A.dcm(N)`` **to match**
+        ``B.dcm(N)``:
+
+        >>> A.orient_explicit(N, N.dcm(B))
         >>> A.dcm(N)
         Matrix([
         [1,       0,      0],
         [0,  cos(q1), sin(q1)],
         [0, -sin(q1), cos(q1)]])
 
-        **Note carefully that** ``N.dcm(B)`` **was passed into** ``orient()``
-        **for** ``A.dcm(N)`` **to match** ``B.dcm(N)``.
+        """
 
-        Body
-        ----
+        _check_frame(parent)
+        # amounts must be a Matrix type object
+        # (e.g. sympy.matrices.dense.MutableDenseMatrix).
+        if not isinstance(dcm, MatrixBase):
+            raise TypeError("Amounts must be a sympy Matrix type object.")
 
-        ``rot_type='Body'`` rotates this reference frame relative to the
-        provided reference frame by rotating through three successive simple
-        rotations.  Each subsequent axis of rotation is about the "body fixed"
-        unit vectors of the new intermediate reference frame. This type of
+        parent_orient_dcm = []
+        parent_orient_dcm = dcm
+
+        self._dcm(parent, parent_orient_dcm)
+
+        wvec = self._w_diff_dcm(parent)
+        self._ang_vel_dict.update({parent: wvec})
+        parent._ang_vel_dict.update({self: -wvec})
+        self._var_dict = {}
+
+    def _rot(self, axis, angle):
+        """DCM for simple axis 1,2,or 3 rotations."""
+        if axis == 1:
+            return Matrix([[1, 0, 0],
+                           [0, cos(angle), -sin(angle)],
+                           [0, sin(angle), cos(angle)]])
+        elif axis == 2:
+            return Matrix([[cos(angle), 0, sin(angle)],
+                           [0, 1, 0],
+                           [-sin(angle), 0, cos(angle)]])
+        elif axis == 3:
+            return Matrix([[cos(angle), -sin(angle), 0],
+                           [sin(angle), cos(angle), 0],
+                           [0, 0, 1]])
+
+    def orient_body_fixed(self, parent, angles, rotation_order):
+        """Rotates this reference frame relative to the parent reference frame
+        by right hand rotating through three successive body fixed simple axis
+        rotations. Each subsequent axis of rotation is about the "body fixed"
+        unit vectors of a new intermediate reference frame. This type of
         rotation is also referred to rotating through the `Euler and Tait-Bryan
-        Angles <https://en.wikipedia.org/wiki/Euler_angles>`_.
+        Angles`_.
+
+        .. _Euler and Tait-Bryan Angles: https://en.wikipedia.org/wiki/Euler_angles
+
+        Parameters
+        ==========
+
+        parent : ReferenceFrame
+            Reference frame that this reference frame will be rotated relative
+            to.
+        angles : 3-tuple of sympifiable
+            Three angles in radians used for the successive rotations.
+        rotation_order : 3 character string or 3 digit integer
+            Order of the rotations about each intermediate reference frames'
+            unit vectors. The Euler rotation about the X, Z', X'' axes can be
+            specified by the strings ``'XZX'``, ``'131'``, or the integer
+            ``131``. There are 12 unique valid rotation orders (6 Euler and 6
+            Tait-Bryan): zxz, xyx, yzy, zyz, xzx, yxy, xyz, yzx, zxy, xzy, zyx,
+            and yxz.
 
-        For example, the classic Euler Angle rotation can be done by:
+        Examples
+        ========
 
-        >>> B.orient(N, 'Body', (q1, q2, q3), 'XYX')
+        Setup variables for the examples:
+
+        >>> from sympy import symbols
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> q1, q2, q3 = symbols('q1, q2, q3')
+        >>> N = ReferenceFrame('N')
+        >>> B = ReferenceFrame('B')
+        >>> B1 = ReferenceFrame('B')
+        >>> B2 = ReferenceFrame('B2')
+
+        For example, a classic Euler Angle rotation can be done by:
+
+        >>> B.orient_body_fixed(N, (q1, q2, q3), 'XYX')
         >>> B.dcm(N)
         Matrix([
         [        cos(q2),                            sin(q1)*sin(q2),                           -sin(q2)*cos(q1)],
         [sin(q2)*sin(q3), -sin(q1)*sin(q3)*cos(q2) + cos(q1)*cos(q3),  sin(q1)*cos(q3) + sin(q3)*cos(q1)*cos(q2)],
         [sin(q2)*cos(q3), -sin(q1)*cos(q2)*cos(q3) - sin(q3)*cos(q1), -sin(q1)*sin(q3) + cos(q1)*cos(q2)*cos(q3)]])
 
-        This rotates B relative to N through ``q1`` about ``N.x``, then rotates
-        B again through q2 about B.y, and finally through q3 about B.x. It is
-        equivalent to:
+        This rotates reference frame B relative to reference frame N through
+        ``q1`` about ``N.x``, then rotates B again through ``q2`` about
+        ``B.y``, and finally through ``q3`` about ``B.x``. It is equivalent to
+        three successive ``orient_axis()`` calls:
 
-        >>> B1.orient(N, 'Axis', (q1, N.x))
-        >>> B2.orient(B1, 'Axis', (q2, B1.y))
-        >>> B.orient(B2, 'Axis', (q3, B2.x))
+        >>> B1.orient_axis(N, N.x, q1)
+        >>> B2.orient_axis(B1, B1.y, q2)
+        >>> B.orient_axis(B2, B2.x, q3)
         >>> B.dcm(N)
         Matrix([
         [        cos(q2),                            sin(q1)*sin(q2),                           -sin(q2)*cos(q1)],
@@ -616,18 +740,90 @@ def orient(self, parent, rot_type, amounts, rot_order=''):
         ``'XYZ'`` or ``'123'`` or integer ``123``. Rotations about an axis
         twice in a row are prohibited.
 
-        >>> B.orient(N, 'Body', (q1, q2, 0), 'ZXZ')
-        >>> B.orient(N, 'Body', (q1, q2, 0), '121')
-        >>> B.orient(N, 'Body', (q1, q2, q3), 123)
+        >>> B.orient_body_fixed(N, (q1, q2, 0), 'ZXZ')
+        >>> B.orient_body_fixed(N, (q1, q2, 0), '121')
+        >>> B.orient_body_fixed(N, (q1, q2, q3), 123)
+
+        """
+
+        _check_frame(parent)
+
+        amounts = list(angles)
+        for i, v in enumerate(amounts):
+            if not isinstance(v, Vector):
+                amounts[i] = sympify(v)
+
+        approved_orders = ('123', '231', '312', '132', '213', '321', '121',
+                           '131', '212', '232', '313', '323', '')
+        # make sure XYZ => 123
+        rot_order = translate(str(rotation_order), 'XYZxyz', '123123')
+        if rot_order not in approved_orders:
+            raise TypeError('The rotation order is not a valid order.')
+
+        parent_orient_body = []
+        if not (len(amounts) == 3 & len(rot_order) == 3):
+            raise TypeError('Body orientation takes 3 values & 3 orders')
+        a1 = int(rot_order[0])
+        a2 = int(rot_order[1])
+        a3 = int(rot_order[2])
+        parent_orient_body = (self._rot(a1, amounts[0]) *
+                              self._rot(a2, amounts[1]) *
+                              self._rot(a3, amounts[2]))
+
+        self._dcm(parent, parent_orient_body)
+
+        try:
+            from sympy.polys.polyerrors import CoercionFailed
+            from sympy.physics.vector.functions import kinematic_equations
+            q1, q2, q3 = amounts
+            u1, u2, u3 = symbols('u1, u2, u3', cls=Dummy)
+            templist = kinematic_equations([u1, u2, u3], [q1, q2, q3],
+                                           'body', rot_order)
+            templist = [expand(i) for i in templist]
+            td = solve(templist, [u1, u2, u3])
+            u1 = expand(td[u1])
+            u2 = expand(td[u2])
+            u3 = expand(td[u3])
+            wvec = u1 * self.x + u2 * self.y + u3 * self.z
+        except (CoercionFailed, AssertionError):
+            wvec = self._w_diff_dcm(parent)
+        self._ang_vel_dict.update({parent: wvec})
+        parent._ang_vel_dict.update({self: -wvec})
+        self._var_dict = {}
+
+    def orient_space_fixed(self, parent, angles, rotation_order):
+        """Rotates this reference frame relative to the parent reference frame
+        by right hand rotating through three successive space fixed simple axis
+        rotations. Each subsequent axis of rotation is about the "space fixed"
+        unit vectors of the parent reference frame.
+
+        Parameters
+        ==========
+        parent : ReferenceFrame
+            Reference frame that this reference frame will be rotated relative
+            to.
+        angles : 3-tuple of sympifiable
+            Three angles in radians used for the successive rotations.
+        rotation_order : 3 character string or 3 digit integer
+            Order of the rotations about the parent reference frame's unit
+            vectors. The order can be specified by the strings ``'XZX'``,
+            ``'131'``, or the integer ``131``. There are 12 unique valid
+            rotation orders.
+
+        Examples
+        ========
 
-        Space
-        -----
+        Setup variables for the examples:
 
-        ``rot_type='Space'`` also rotates the reference frame in three
-        successive simple rotations but the axes of rotation are the
-        "Space-fixed" axes. For example:
+        >>> from sympy import symbols
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> q1, q2, q3 = symbols('q1, q2, q3')
+        >>> N = ReferenceFrame('N')
+        >>> B = ReferenceFrame('B')
+        >>> B1 = ReferenceFrame('B')
+        >>> B2 = ReferenceFrame('B2')
 
-        >>> B.orient(N, 'Space', (q1, q2, q3), '312')
+        >>> B.orient_space_fixed(N, (q1, q2, q3), '312')
         >>> B.dcm(N)
         Matrix([
         [ sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1)],
@@ -636,49 +832,119 @@ def orient(self, parent, rot_type, amounts, rot_order=''):
 
         is equivalent to:
 
-        >>> B1.orient(N, 'Axis', (q1, N.z))
-        >>> B2.orient(B1, 'Axis', (q2, N.x))
-        >>> B.orient(B2, 'Axis', (q3, N.y))
-        >>> B.dcm(N).simplify()  # doctest: +SKIP
+        >>> B1.orient_axis(N, N.z, q1)
+        >>> B2.orient_axis(B1, N.x, q2)
+        >>> B.orient_axis(B2, N.y, q3)
+        >>> B.dcm(N).simplify()
         Matrix([
-        [ sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1)],
-        [-sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3)],
-        [                           sin(q3)*cos(q2),        -sin(q2),                           cos(q2)*cos(q3)]])
+        [ sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3), sin(q1)*cos(q2),                                                                                sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1)],
+        [-sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1), cos(q1)*cos(q2), sin(-q1 + q2 + q3)/4 - sin(q1 - q2 + q3)/4 + sin(q1 + q2 - q3)/4 + sin(q1 + q2 + q3)/4 + cos(q1 - q3)/2 - cos(q1 + q3)/2],
+        [                           sin(q3)*cos(q2),        -sin(q2),                                                                                                          cos(q2)*cos(q3)]])
 
         It is worth noting that space-fixed and body-fixed rotations are
         related by the order of the rotations, i.e. the reverse order of body
         fixed will give space fixed and vice versa.
 
-        >>> B.orient(N, 'Space', (q1, q2, q3), '231')
+        >>> B.orient_space_fixed(N, (q1, q2, q3), '231')
         >>> B.dcm(N)
         Matrix([
         [cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3), -sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)],
         [       -sin(q2),                           cos(q2)*cos(q3),                            sin(q3)*cos(q2)],
         [sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1),  sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3)]])
 
-        >>> B.orient(N, 'Body', (q3, q2, q1), '132')
+        >>> B.orient_body_fixed(N, (q3, q2, q1), '132')
         >>> B.dcm(N)
         Matrix([
         [cos(q1)*cos(q2), sin(q1)*sin(q3) + sin(q2)*cos(q1)*cos(q3), -sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)],
         [       -sin(q2),                           cos(q2)*cos(q3),                            sin(q3)*cos(q2)],
         [sin(q1)*cos(q2), sin(q1)*sin(q2)*cos(q3) - sin(q3)*cos(q1),  sin(q1)*sin(q2)*sin(q3) + cos(q1)*cos(q3)]])
 
-        Quaternion
-        ----------
+        """
+
+        _check_frame(parent)
 
-        ``rot_type='Quaternion'`` orients the reference frame using
-        quaternions. Quaternion rotation is defined as a finite rotation about
-        lambda, a unit vector, by an amount theta. This orientation is
-        described by four parameters:
+        amounts = list(angles)
+        for i, v in enumerate(amounts):
+            if not isinstance(v, Vector):
+                amounts[i] = sympify(v)
+
+        approved_orders = ('123', '231', '312', '132', '213', '321', '121',
+                           '131', '212', '232', '313', '323', '')
+        # make sure XYZ => 123
+        rot_order = translate(str(rotation_order), 'XYZxyz', '123123')
+        if rot_order not in approved_orders:
+            raise TypeError('The supplied order is not an approved type')
+        parent_orient_space = []
+
+        if not (len(amounts) == 3 & len(rot_order) == 3):
+            raise TypeError('Space orientation takes 3 values & 3 orders')
+        a1 = int(rot_order[0])
+        a2 = int(rot_order[1])
+        a3 = int(rot_order[2])
+        parent_orient_space = (self._rot(a3, amounts[2]) *
+                               self._rot(a2, amounts[1]) *
+                               self._rot(a1, amounts[0]))
+
+        self._dcm(parent, parent_orient_space)
+
+        try:
+            from sympy.polys.polyerrors import CoercionFailed
+            from sympy.physics.vector.functions import kinematic_equations
+            q1, q2, q3 = amounts
+            u1, u2, u3 = symbols('u1, u2, u3', cls=Dummy)
+            templist = kinematic_equations([u1, u2, u3], [q1, q2, q3],
+                                           'space', rot_order)
+            templist = [expand(i) for i in templist]
+            td = solve(templist, [u1, u2, u3])
+            u1 = expand(td[u1])
+            u2 = expand(td[u2])
+            u3 = expand(td[u3])
+            wvec = u1 * self.x + u2 * self.y + u3 * self.z
+        except (CoercionFailed, AssertionError):
+            wvec = self._w_diff_dcm(parent)
+        self._ang_vel_dict.update({parent: wvec})
+        parent._ang_vel_dict.update({self: -wvec})
+        self._var_dict = {}
+
+    def orient_quaternion(self, parent, numbers):
+        """Sets the orientation of this reference frame relative to a parent
+        reference frame via an orientation quaternion. An orientation
+        quaternion is defined as a finite rotation a unit vector, ``(lambda_x,
+        lambda_y, lambda_z)``, by an angle ``theta``. The orientation
+        quaternion 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)``
+        - ``q1 = lambda_x*sin(theta/2)``
+        - ``q2 = lambda_y*sin(theta/2)``
+        - ``q3 = lambda_z*sin(theta/2)``
 
-        This type does not need a ``rot_order``.
+        See `Quaternions and Spatial Rotation
+        <https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation>`_ on
+        Wikipedia for more information.
 
-        >>> B.orient(N, 'Quaternion', (q0, q1, q2, q3))
+        Parameters
+        ==========
+        parent : ReferenceFrame
+            Reference frame that this reference frame will be rotated relative
+            to.
+        numbers : 4-tuple of sympifiable
+            The four quaternion scalar numbers as defined above: ``q0``,
+            ``q1``, ``q2``, ``q3``.
+
+        Examples
+        ========
+
+        Setup variables for the examples:
+
+        >>> from sympy import symbols
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
+        >>> N = ReferenceFrame('N')
+        >>> B = ReferenceFrame('B')
+
+        Set the orientation:
+
+        >>> B.orient_quaternion(N, (q0, q1, q2, q3))
         >>> B.dcm(N)
         Matrix([
         [q0**2 + q1**2 - q2**2 - q3**2,             2*q0*q3 + 2*q1*q2,            -2*q0*q2 + 2*q1*q3],
@@ -690,151 +956,116 @@ def orient(self, parent, rot_type, amounts, rot_order=''):
         from sympy.physics.vector.functions import dynamicsymbols
         _check_frame(parent)
 
-        # Allow passing a rotation matrix manually.
-        if rot_type == 'DCM':
-            # When rot_type == 'DCM', then amounts must be a Matrix type object
-            # (e.g. sympy.matrices.dense.MutableDenseMatrix).
-            if not isinstance(amounts, MatrixBase):
-                raise TypeError("Amounts must be a sympy Matrix type object.")
-        else:
-            amounts = list(amounts)
-            for i, v in enumerate(amounts):
-                if not isinstance(v, Vector):
-                    amounts[i] = sympify(v)
-
-        def _rot(axis, angle):
-            """DCM for simple axis 1,2,or 3 rotations. """
-            if axis == 1:
-                return Matrix([[1, 0, 0],
-                               [0, cos(angle), -sin(angle)],
-                               [0, sin(angle), cos(angle)]])
-            elif axis == 2:
-                return Matrix([[cos(angle), 0, sin(angle)],
-                               [0, 1, 0],
-                               [-sin(angle), 0, cos(angle)]])
-            elif axis == 3:
-                return Matrix([[cos(angle), -sin(angle), 0],
-                               [sin(angle), cos(angle), 0],
-                               [0, 0, 1]])
+        numbers = list(numbers)
+        for i, v in enumerate(numbers):
+            if not isinstance(v, Vector):
+                numbers[i] = sympify(v)
+
+        parent_orient_quaternion = []
+        if not (isinstance(numbers, (list, tuple)) & (len(numbers) == 4)):
+            raise TypeError('Amounts are a list or tuple of length 4')
+        q0, q1, q2, q3 = numbers
+        parent_orient_quaternion = (
+            Matrix([[q0**2 + q1**2 - q2**2 - q3**2,
+                     2 * (q1 * q2 - q0 * q3),
+                     2 * (q0 * q2 + q1 * q3)],
+                    [2 * (q1 * q2 + q0 * q3),
+                     q0**2 - q1**2 + q2**2 - q3**2,
+                     2 * (q2 * q3 - q0 * q1)],
+                    [2 * (q1 * q3 - q0 * q2),
+                     2 * (q0 * q1 + q2 * q3),
+                     q0**2 - q1**2 - q2**2 + q3**2]]))
+
+        self._dcm(parent, parent_orient_quaternion)
+
+        t = dynamicsymbols._t
+        q0, q1, q2, q3 = numbers
+        q0d = diff(q0, t)
+        q1d = diff(q1, t)
+        q2d = diff(q2, t)
+        q3d = diff(q3, t)
+        w1 = 2 * (q1d * q0 + q2d * q3 - q3d * q2 - q0d * q1)
+        w2 = 2 * (q2d * q0 + q3d * q1 - q1d * q3 - q0d * q2)
+        w3 = 2 * (q3d * q0 + q1d * q2 - q2d * q1 - q0d * q3)
+        wvec = Vector([(Matrix([w1, w2, w3]), self)])
+
+        self._ang_vel_dict.update({parent: wvec})
+        parent._ang_vel_dict.update({self: -wvec})
+        self._var_dict = {}
+
+    def orient(self, parent, rot_type, amounts, rot_order=''):
+        """Sets the orientation of this reference frame relative to another
+        (parent) reference frame.
+
+        .. note:: It is now recommended to use the ``.orient_axis,
+           .orient_body_fixed, .orient_space_fixed, .orient_quaternion``
+           methods for the different rotation types.
+
+        Parameters
+        ==========
+
+        parent : ReferenceFrame
+            Reference frame that this reference frame will be rotated relative
+            to.
+        rot_type : str
+            The method used to generate the direction cosine matrix. Supported
+            methods are:
+
+            - ``'Axis'``: simple rotations about a single common axis
+            - ``'DCM'``: for setting the direction cosine matrix directly
+            - ``'Body'``: three successive rotations about new intermediate
+              axes, also called "Euler and Tait-Bryan angles"
+            - ``'Space'``: three successive rotations about the parent
+              frames' unit vectors
+            - ``'Quaternion'``: rotations defined by four parameters which
+              result in a singularity free direction cosine matrix
+
+        amounts :
+            Expressions defining the rotation angles or direction cosine
+            matrix. These must match the ``rot_type``. See examples below for
+            details. The input types are:
+
+            - ``'Axis'``: 2-tuple (expr/sym/func, Vector)
+            - ``'DCM'``: Matrix, shape(3,3)
+            - ``'Body'``: 3-tuple of expressions, symbols, or functions
+            - ``'Space'``: 3-tuple of expressions, symbols, or functions
+            - ``'Quaternion'``: 4-tuple of expressions, symbols, or
+              functions
+
+        rot_order : str or int, optional
+            If applicable, the order of the successive of rotations. The string
+            ``'123'`` and integer ``123`` are equivalent, for example. Required
+            for ``'Body'`` and ``'Space'``.
+
+        """
+
+        _check_frame(parent)
 
         approved_orders = ('123', '231', '312', '132', '213', '321', '121',
                            '131', '212', '232', '313', '323', '')
-        # make sure XYZ => 123 and rot_type is in upper case
         rot_order = translate(str(rot_order), 'XYZxyz', '123123')
         rot_type = rot_type.upper()
+
         if rot_order not in approved_orders:
             raise TypeError('The supplied order is not an approved type')
-        parent_orient = []
+
         if rot_type == 'AXIS':
-            if not rot_order == '':
-                raise TypeError('Axis orientation takes no rotation order')
-            if not (isinstance(amounts, (list, tuple)) & (len(amounts) == 2)):
-                raise TypeError('Amounts are a list or tuple of length 2')
-            theta = amounts[0]
-            axis = amounts[1]
-            axis = _check_vector(axis)
-            if not axis.dt(parent) == 0:
-                raise ValueError('Axis cannot be time-varying')
-            axis = axis.express(parent).normalize()
-            axis = axis.args[0][0]
-            parent_orient = ((eye(3) - axis * axis.T) * cos(theta) +
-                             Matrix([[0, -axis[2], axis[1]],
-                                     [axis[2], 0, -axis[0]],
-                                     [-axis[1], axis[0], 0]]) *
-                             sin(theta) + axis * axis.T)
-        elif rot_type == 'QUATERNION':
-            if not rot_order == '':
-                raise TypeError(
-                    'Quaternion orientation takes no rotation order')
-            if not (isinstance(amounts, (list, tuple)) & (len(amounts) == 4)):
-                raise TypeError('Amounts are a list or tuple of length 4')
-            q0, q1, q2, q3 = amounts
-            parent_orient = (Matrix([[q0**2 + q1**2 - q2**2 - q3**2,
-                                      2 * (q1 * q2 - q0 * q3),
-                                      2 * (q0 * q2 + q1 * q3)],
-                                     [2 * (q1 * q2 + q0 * q3),
-                                      q0**2 - q1**2 + q2**2 - q3**2,
-                                      2 * (q2 * q3 - q0 * q1)],
-                                     [2 * (q1 * q3 - q0 * q2),
-                                      2 * (q0 * q1 + q2 * q3),
-                                      q0**2 - q1**2 - q2**2 + q3**2]]))
+            self.orient_axis(parent, amounts[1], amounts[0])
+
+        elif rot_type == 'DCM':
+            self.orient_explicit(parent, amounts)
+
         elif rot_type == 'BODY':
-            if not (len(amounts) == 3 & len(rot_order) == 3):
-                raise TypeError('Body orientation takes 3 values & 3 orders')
-            a1 = int(rot_order[0])
-            a2 = int(rot_order[1])
-            a3 = int(rot_order[2])
-            parent_orient = (_rot(a1, amounts[0]) * _rot(a2, amounts[1]) *
-                             _rot(a3, amounts[2]))
+            self.orient_body_fixed(parent, amounts, rot_order)
+
         elif rot_type == 'SPACE':
-            if not (len(amounts) == 3 & len(rot_order) == 3):
-                raise TypeError('Space orientation takes 3 values & 3 orders')
-            a1 = int(rot_order[0])
-            a2 = int(rot_order[1])
-            a3 = int(rot_order[2])
-            parent_orient = (_rot(a3, amounts[2]) * _rot(a2, amounts[1]) *
-                             _rot(a1, amounts[0]))
-        elif rot_type == 'DCM':
-            parent_orient = amounts
+            self.orient_space_fixed(parent, amounts, rot_order)
+
+        elif rot_type == 'QUATERNION':
+            self.orient_quaternion(parent, amounts)
+
         else:
             raise NotImplementedError('That is not an implemented rotation')
-        # Reset the _dcm_cache of this frame, and remove it from the
-        # _dcm_caches of the frames it is linked to. Also remove it from the
-        # _dcm_dict of its parent
-        frames = self._dcm_cache.keys()
-        dcm_dict_del = []
-        dcm_cache_del = []
-        for frame in frames:
-            if frame in self._dcm_dict:
-                dcm_dict_del += [frame]
-            dcm_cache_del += [frame]
-        for frame in dcm_dict_del:
-            del frame._dcm_dict[self]
-        for frame in dcm_cache_del:
-            del frame._dcm_cache[self]
-        # Add the dcm relationship to _dcm_dict
-        self._dcm_dict = self._dlist[0] = {}
-        self._dcm_dict.update({parent: parent_orient.T})
-        parent._dcm_dict.update({self: parent_orient})
-        # Also update the dcm cache after resetting it
-        self._dcm_cache = {}
-        self._dcm_cache.update({parent: parent_orient.T})
-        parent._dcm_cache.update({self: parent_orient})
-        if rot_type == 'QUATERNION':
-            t = dynamicsymbols._t
-            q0, q1, q2, q3 = amounts
-            q0d = diff(q0, t)
-            q1d = diff(q1, t)
-            q2d = diff(q2, t)
-            q3d = diff(q3, t)
-            w1 = 2 * (q1d * q0 + q2d * q3 - q3d * q2 - q0d * q1)
-            w2 = 2 * (q2d * q0 + q3d * q1 - q1d * q3 - q0d * q2)
-            w3 = 2 * (q3d * q0 + q1d * q2 - q2d * q1 - q0d * q3)
-            wvec = Vector([(Matrix([w1, w2, w3]), self)])
-        elif rot_type == 'AXIS':
-            thetad = (amounts[0]).diff(dynamicsymbols._t)
-            wvec = thetad * amounts[1].express(parent).normalize()
-        elif rot_type == 'DCM':
-            wvec = self._w_diff_dcm(parent)
-        else:
-            try:
-                from sympy.polys.polyerrors import CoercionFailed
-                from sympy.physics.vector.functions import kinematic_equations
-                q1, q2, q3 = amounts
-                u1, u2, u3 = symbols('u1, u2, u3', cls=Dummy)
-                templist = kinematic_equations([u1, u2, u3], [q1, q2, q3],
-                                               rot_type, rot_order)
-                templist = [expand(i) for i in templist]
-                td = solve(templist, [u1, u2, u3])
-                u1 = expand(td[u1])
-                u2 = expand(td[u2])
-                u3 = expand(td[u3])
-                wvec = u1 * self.x + u2 * self.y + u3 * self.z
-            except (CoercionFailed, AssertionError):
-                wvec = self._w_diff_dcm(parent)
-        self._ang_vel_dict.update({parent: wvec})
-        parent._ang_vel_dict.update({self: -wvec})
-        self._var_dict = {}
 
     def orientnew(self, newname, rot_type, amounts, rot_order='',
                   variables=None, indices=None, latexs=None):
@@ -920,7 +1151,32 @@ def orientnew(self, newname, rot_type, amounts, rot_order='',
 
         newframe = self.__class__(newname, variables=variables,
                                   indices=indices, latexs=latexs)
-        newframe.orient(self, rot_type, amounts, rot_order)
+
+        approved_orders = ('123', '231', '312', '132', '213', '321', '121',
+                           '131', '212', '232', '313', '323', '')
+        rot_order = translate(str(rot_order), 'XYZxyz', '123123')
+        rot_type = rot_type.upper()
+
+        if rot_order not in approved_orders:
+            raise TypeError('The supplied order is not an approved type')
+
+        if rot_type == 'AXIS':
+            newframe.orient_axis(self, amounts[1], amounts[0])
+
+        elif rot_type == 'DCM':
+            newframe.orient_explicit(self, amounts)
+
+        elif rot_type == 'BODY':
+            newframe.orient_body_fixed(self, amounts, rot_order)
+
+        elif rot_type == 'SPACE':
+            newframe.orient_space_fixed(self, amounts, rot_order)
+
+        elif rot_type == 'QUATERNION':
+            newframe.orient_quaternion(self, amounts)
+
+        else:
+            raise NotImplementedError('That is not an implemented rotation')
         return newframe
 
     def set_ang_acc(self, otherframe, value):


Test patch:
diff --git a/sympy/physics/vector/tests/test_frame.py b/sympy/physics/vector/tests/test_frame.py
index ce5eb809b0..5c96b82b6f 100644
--- a/sympy/physics/vector/tests/test_frame.py
+++ b/sympy/physics/vector/tests/test_frame.py
@@ -367,7 +367,7 @@ def test_reference_frame():
     raises(TypeError, lambda: B.orient(N, 'Space', [q1, q2, q3], '222'))
     raises(TypeError, lambda: B.orient(N, 'Axis', [q1, N.x + 2 * N.y], '222'))
     raises(TypeError, lambda: B.orient(N, 'Axis', q1))
-    raises(TypeError, lambda: B.orient(N, 'Axis', [q1]))
+    raises(IndexError, lambda: B.orient(N, 'Axis', [q1]))
     raises(TypeError, lambda: B.orient(N, 'Quaternion', [q0, q1, q2, q3], '222'))
     raises(TypeError, lambda: B.orient(N, 'Quaternion', q0))
     raises(TypeError, lambda: B.orient(N, 'Quaternion', [q0, q1, q2]))
@@ -416,3 +416,32 @@ def test_dcm_diff_16824():
 
     assert simplify(AwB.dot(A.y) - alpha2) == 0
     assert simplify(AwB.dot(B.y) - beta2) == 0
+
+def test_orient_explicit():
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    A.orient_explicit(B, eye(3))
+    assert A.dcm(B) == Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
+
+def test_orient_axis():
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    assert A.orient_axis(B,-B.x, 1) == A.orient_axis(B, B.x, -1)
+
+def test_orient_body():
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    B.orient_body_fixed(A, (1,1,0), 'XYX')
+    assert B.dcm(A) == Matrix([[cos(1), sin(1)**2, -sin(1)*cos(1)], [0, cos(1), sin(1)], [sin(1), -sin(1)*cos(1), cos(1)**2]])
+
+def test_orient_space():
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    B.orient_space_fixed(A, (0,0,0), '123')
+    assert B.dcm(A) == Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
+
+def test_orient_quaternion():
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    B.orient_quaternion(A, (0,0,0,0))
+    assert B.dcm(A) == Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])