Repository: sympy/sympy

Issue:
Add new method that is more intuitive than orient_explicit()
The mechanics method `orient_explicit()` takes a direction cosine matrix that is the inverse of what a `ReferenceFrame.dcm()` outputs and it is confusing. We have to make clear warnings about this everywhere. It was a mistake that we designed it this way (or rather didn't design it).

This shows the issue:

```python
import sympy as sm
import sympy.physics.mechanics as me

cxx, cyy, czz = me.dynamicsymbols('c_{xx}, c_{yy}, c_{zz}')
cxy, cxz, cyx = me.dynamicsymbols('c_{xy}, c_{xz}, c_{yx}')
cyz, czx, czy = me.dynamicsymbols('c_{yz}, c_{zx}, c_{zy}')

B_C_A = sm.Matrix([[cxx, cxy, cxz],
                   [cyx, cyy, cyz],
                   [czx, czy, czz]])

A = me.ReferenceFrame('A')
B = me.ReferenceFrame('B')
B.orient_explicit(A, B_C_A)
B.dcm(A)
```

returns:

```
Matrix([
[c_{xx}(t), c_{yx}(t), c_{zx}(t)],
[c_{xy}(t), c_{yy}(t), c_{zy}(t)],
[c_{xz}(t), c_{yz}(t), c_{zz}(t)]])
```

which is the inverse of the input.

So to get the expected behavior you must do:

```python
B.orient_explicit(A, B_C_A.transpose())
B.dcm(A)
```
which returns the expected result:

```
Matrix([
[c_{xx}(t), c_{xy}(t), c_{xz}(t)],
[c_{yx}(t), c_{yy}(t), c_{yz}(t)],
[c_{zx}(t), c_{zy}(t), c_{zz}(t)]])
```

We should introduce a `orient_from_dcm()` or similarly named method that works just like `orient_explicit()` but does the intuitive behavior. We can just leave `orient_explicit()` in place for perpetuity to avoid breaking people's code.

Discussion:

Patch:
diff --git a/.mailmap b/.mailmap
index 34ec491296..5e153ae5e2 100644
--- a/.mailmap
+++ b/.mailmap
@@ -657,6 +657,7 @@ Harry Zheng <harry@harryzheng.com>
 Harsh Agarwal <hagarwal9200@gmail.com>
 Harsh Gupta <mail@hargup.in> <gupta.harsh96@gmail.com>
 Harsh Jain <harshjniitr@gmail.com>
+Harsh Kasat <hkasat@waymore.io>
 Harshil Goel <harshil158@gmail.com>
 Harshil Goel <harshil158@gmail.com> <darkcoderrises@users.noreply.github.com>
 Harshil Meena <harshil.7535@gmail.com>
diff --git a/doc/src/modules/physics/vector/api/classes.rst b/doc/src/modules/physics/vector/api/classes.rst
index 7e2e10c079..79f1aa9091 100644
--- a/doc/src/modules/physics/vector/api/classes.rst
+++ b/doc/src/modules/physics/vector/api/classes.rst
@@ -7,6 +7,7 @@ Essential Classes
 
 .. autoclass:: sympy.physics.vector.frame.ReferenceFrame
    :members:
+   :exclude-members: orient_explicit
 
 .. autoclass:: sympy.physics.vector.vector.Vector
    :members:
diff --git a/sympy/physics/vector/frame.py b/sympy/physics/vector/frame.py
index e93435188e..23f37b6873 100644
--- a/sympy/physics/vector/frame.py
+++ b/sympy/physics/vector/frame.py
@@ -708,8 +708,8 @@ def orient_axis(self, parent, axis, angle):
         self._var_dict = {}
 
     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.
+        """Sets the orientation of this reference frame relative to another (parent) reference frame
+        using a direction cosine matrix that describes the rotation from the parent to the child.
 
         Parameters
         ==========
@@ -773,16 +773,77 @@ def orient_explicit(self, parent, dcm):
         [0, -sin(q1), cos(q1)]])
 
         """
-
         _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.")
 
-        parent_orient_dcm = dcm
+        self.orient_dcm(parent, dcm.T)
+
+    def orient_dcm(self, parent, dcm):
+        """Sets the orientation of this reference frame relative to another (parent) reference frame
+        using a direction cosine matrix that describes the rotation from the child to the parent.
+
+        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.
+
+        Warns
+        ======
+
+        UserWarning
+            If the orientation creates a kinematic loop.
+
+        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')
+        >>> 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_dcm(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)]])
+
+        """
+
+        _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.")
 
-        self._dcm(parent, parent_orient_dcm)
+        self._dcm(parent, dcm.T)
 
         wvec = self._w_diff_dcm(parent)
         self._ang_vel_dict.update({parent: wvec})


Test patch:
diff --git a/sympy/physics/vector/tests/test_frame.py b/sympy/physics/vector/tests/test_frame.py
index 8e6e2cb1ca..8e2d0234c7 100644
--- a/sympy/physics/vector/tests/test_frame.py
+++ b/sympy/physics/vector/tests/test_frame.py
@@ -452,10 +452,38 @@ def test_dcm_diff_16824():
     assert simplify(AwB.dot(B.y) - beta2) == 0
 
 def test_orient_explicit():
+    cxx, cyy, czz = dynamicsymbols('c_{xx}, c_{yy}, c_{zz}')
+    cxy, cxz, cyx = dynamicsymbols('c_{xy}, c_{xz}, c_{yx}')
+    cyz, czx, czy = dynamicsymbols('c_{yz}, c_{zx}, c_{zy}')
+    dcxx, dcyy, dczz = dynamicsymbols('c_{xx}, c_{yy}, c_{zz}', 1)
+    dcxy, dcxz, dcyx = dynamicsymbols('c_{xy}, c_{xz}, c_{yx}', 1)
+    dcyz, dczx, dczy = dynamicsymbols('c_{yz}, c_{zx}, c_{zy}', 1)
     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]])
+    B_C_A = Matrix([[cxx, cxy, cxz],
+                    [cyx, cyy, cyz],
+                    [czx, czy, czz]])
+    B_w_A = ((cyx*dczx + cyy*dczy + cyz*dczz)*B.x +
+            (czx*dcxx + czy*dcxy + czz*dcxz)*B.y +
+            (cxx*dcyx + cxy*dcyy + cxz*dcyz)*B.z)
+    A.orient_explicit(B, B_C_A)
+    assert B.dcm(A) == B_C_A
+    assert A.ang_vel_in(B) == B_w_A
+    assert B.ang_vel_in(A) == -B_w_A
+
+def test_orient_dcm():
+    cxx, cyy, czz = dynamicsymbols('c_{xx}, c_{yy}, c_{zz}')
+    cxy, cxz, cyx = dynamicsymbols('c_{xy}, c_{xz}, c_{yx}')
+    cyz, czx, czy = dynamicsymbols('c_{yz}, c_{zx}, c_{zy}')
+    B_C_A = Matrix([[cxx, cxy, cxz],
+                    [cyx, cyy, cyz],
+                    [czx, czy, czz]])
+    A = ReferenceFrame('A')
+    B = ReferenceFrame('B')
+    B.orient_dcm(A, B_C_A)
+    assert B.dcm(A) == Matrix([[cxx, cxy, cxz],
+                               [cyx, cyy, cyz],
+                               [czx, czy, czz]])
 
 def test_orient_axis():
     A = ReferenceFrame('A')