Repository: sympy/sympy

Issue:
ReferenceFrame should have attributes to access unit dyads and its unit dyadic
ReferenceFrame has `.x,.y,.z` to access the unit vectors. It would be nice if you could access unit dyads also with `.xx,.xy,.xz,...`. Then you can construct dyadics without having to do `outer(N.x, N.x)`. This is a simple fix and addition. It would need to be documented and shown in some examples.

Discussion:

Patch:
diff --git a/.mailmap b/.mailmap
index b7182684bc..856c606420 100644
--- a/.mailmap
+++ b/.mailmap
@@ -802,6 +802,7 @@ Kiyohito Yamazaki <kyamaz@openql.org>
 Klaus Rettinghaus <klaus.rettinghaus@enote.com>
 Konrad Meyer <konrad.meyer@gmail.com>
 Konstantin Togoi <konstantin.togoi@gmail.com> <togoi.konstantin@outlook.com>
+Konstantinos Riganas <kostasriganas24@gmail.com> kostas-rigan <t8200145@aueb.gr>
 Kristian Brünn <hello@kristianbrunn.com> Kristianmitk <hello@kristianbrunn.com>
 Krit Karan <kritkaran.b13@iiits.in> <kritkaran94@users.noreply.github.com>
 Kshitij <kshitijparwani.mat18@itbhu.ac.in> Kshitij Parwani <44468674+P-Kshitij@users.noreply.github.com>
@@ -845,7 +846,6 @@ Lucas Wiman <lucas.wiman@gmail.com>
 Lucy Mountain <lucymountain1@icloud.com> LucyMountain <lucymountain1@icloud.com>
 Luis Garcia <ppn.online@me.com>
 Luis Talavera <luisfertalavera15@gmail.com> Luis F. Talavera R <47088091+LuisFerTR@users.noreply.github.com>
-Lukas Molleman <Lukas.Molleman@gmail.com>
 Lukas Zorich <lukas.zorich@gmail.com>
 Luke Peterson <hazelnusse@gmail.com>
 Luv Agarwal <agarwal.iiit@gmail.com>
diff --git a/sympy/matrices/matrices.py b/sympy/matrices/matrices.py
index 9254c5773d..5c0f533812 100644
--- a/sympy/matrices/matrices.py
+++ b/sympy/matrices/matrices.py
@@ -1268,7 +1268,7 @@ def hat(self):
         Examples
         ========
 
-        Calling ``hat`` creates a skew-symmetric 3x3 Matrix from a 3x1 Matrix:
+        Calling `hat` creates a skew-symmetric Matrix:
 
         >>> from sympy import Matrix
         >>> a = Matrix([1, 2, 3])
@@ -1278,7 +1278,7 @@ def hat(self):
         [ 3,  0, -1],
         [-2,  1,  0]])
 
-        Multiplying it with another 3x1 Matrix calculates the cross product:
+        Multiplying it with another Matrix calculates the cross product:
 
         >>> b = Matrix([3, 2, 1])
         >>> a.hat() * b
@@ -1287,7 +1287,7 @@ def hat(self):
         [ 8],
         [-4]])
 
-        Which is equivalent to calling the ``cross`` method:
+        Which is equivalent to calling the `cross` method:
 
         >>> a.cross(b)
         Matrix([
@@ -1323,7 +1323,7 @@ def vee(self):
         Examples
         ========
 
-        Calling ``vee`` creates a vector from a skew-symmetric Matrix:
+        Calling `vee` creates a vector from a skew-symmetric Matrix:
 
         >>> from sympy import Matrix
         >>> A = Matrix([[0, -3, 2], [3, 0, -1], [-2, 1, 0]])
@@ -1350,7 +1350,7 @@ def vee(self):
         [ 8],
         [-4]])
 
-        ``vee`` can also be used to retrieve angular velocity expressions.
+        `vee` can also be used to retrieve angular velocity expressions.
         Defining a rotation matrix:
 
         >>> from sympy import rot_ccw_axis3, trigsimp
diff --git a/sympy/physics/mechanics/joint.py b/sympy/physics/mechanics/joint.py
index da9bcd772f..02f28e026d 100644
--- a/sympy/physics/mechanics/joint.py
+++ b/sympy/physics/mechanics/joint.py
@@ -132,8 +132,8 @@ class Joint(ABC):
     """
 
     def __init__(self, name, parent, child, coordinates=None, speeds=None,
-                 parent_point=None, child_point=None, parent_interframe=None,
-                 child_interframe=None, parent_axis=None, child_axis=None,
+                 parent_point=None, child_point=None, parent_axis=None,
+                 child_axis=None, parent_interframe=None, child_interframe=None,
                  parent_joint_pos=None, child_joint_pos=None):
 
         if not isinstance(name, str):
@@ -775,14 +775,14 @@ class PinJoint(Joint):
     """
 
     def __init__(self, name, parent, child, coordinates=None, speeds=None,
-                 parent_point=None, child_point=None, parent_interframe=None,
-                 child_interframe=None, parent_axis=None, child_axis=None,
+                 parent_point=None, child_point=None, parent_axis=None,
+                 child_axis=None, parent_interframe=None, child_interframe=None,
                  joint_axis=None, parent_joint_pos=None, child_joint_pos=None):
 
         self._joint_axis = joint_axis
         super().__init__(name, parent, child, coordinates, speeds, parent_point,
-                         child_point, parent_interframe, child_interframe,
-                         parent_axis, child_axis, parent_joint_pos,
+                         child_point, parent_axis, child_axis,
+                         parent_interframe, child_interframe, parent_joint_pos,
                          child_joint_pos)
 
     def __str__(self):
@@ -1038,14 +1038,14 @@ class PrismaticJoint(Joint):
     """
 
     def __init__(self, name, parent, child, coordinates=None, speeds=None,
-                 parent_point=None, child_point=None, parent_interframe=None,
-                 child_interframe=None, parent_axis=None, child_axis=None,
+                 parent_point=None, child_point=None, parent_axis=None,
+                 child_axis=None, parent_interframe=None, child_interframe=None,
                  joint_axis=None, parent_joint_pos=None, child_joint_pos=None):
 
         self._joint_axis = joint_axis
         super().__init__(name, parent, child, coordinates, speeds, parent_point,
-                         child_point, parent_interframe, child_interframe,
-                         parent_axis, child_axis, parent_joint_pos,
+                         child_point, parent_axis, child_axis,
+                         parent_interframe, child_interframe, parent_joint_pos,
                          child_joint_pos)
 
     def __str__(self):
diff --git a/sympy/physics/vector/frame.py b/sympy/physics/vector/frame.py
index 23f9c0a841..f337a23e5a 100644
--- a/sympy/physics/vector/frame.py
+++ b/sympy/physics/vector/frame.py
@@ -145,6 +145,30 @@ def __init__(self, name, indices=None, latexs=None, variables=None):
         >>> type(A) == type(D)
         True
 
+        Unit dyads for the ReferenceFrame can be accessed through the attributes ``xx``, ``xy``, etc. For example:
+
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> N = ReferenceFrame('N')
+        >>> N.yz
+        (N.y|N.z)
+        >>> N.zx
+        (N.z|N.x)
+        >>> P = ReferenceFrame('P', indices=['1', '2', '3'])
+        >>> P.xx
+        (P['1']|P['1'])
+        >>> P.zy
+        (P['3']|P['2'])
+
+        Unit dyadic is also accessible via the ``u`` attribute:
+
+        >>> from sympy.physics.vector import ReferenceFrame
+        >>> N = ReferenceFrame('N')
+        >>> N.u
+        (N.x|N.x) + (N.y|N.y) + (N.z|N.z)
+        >>> P = ReferenceFrame('P', indices=['1', '2', '3'])
+        >>> P.u
+        (P['1']|P['1']) + (P['2']|P['2']) + (P['3']|P['3'])
+
         """
 
         if not isinstance(name, str):
@@ -1388,6 +1412,56 @@ def z(self):
         """The basis Vector for the ReferenceFrame, in the z direction. """
         return self._z
 
+    @property
+    def xx(self):
+        """Unit dyad of basis Vectors x and x for the ReferenceFrame."""
+        return Vector.outer(self.x, self.x)
+
+    @property
+    def xy(self):
+        """Unit dyad of basis Vectors x and y for the ReferenceFrame."""
+        return Vector.outer(self.x, self.y)
+
+    @property
+    def xz(self):
+        """Unit dyad of basis Vectors x and z for the ReferenceFrame."""
+        return Vector.outer(self.x, self.z)
+
+    @property
+    def yx(self):
+        """Unit dyad of basis Vectors y and x for the ReferenceFrame."""
+        return Vector.outer(self.y, self.x)
+
+    @property
+    def yy(self):
+        """Unit dyad of basis Vectors y and y for the ReferenceFrame."""
+        return Vector.outer(self.y, self.y)
+
+    @property
+    def yz(self):
+        """Unit dyad of basis Vectors y and z for the ReferenceFrame."""
+        return Vector.outer(self.y, self.z)
+
+    @property
+    def zx(self):
+        """Unit dyad of basis Vectors z and x for the ReferenceFrame."""
+        return Vector.outer(self.z, self.x)
+
+    @property
+    def zy(self):
+        """Unit dyad of basis Vectors z and y for the ReferenceFrame."""
+        return Vector.outer(self.z, self.y)
+
+    @property
+    def zz(self):
+        """Unit dyad of basis Vectors z and z for the ReferenceFrame."""
+        return Vector.outer(self.z, self.z)
+
+    @property
+    def u(self):
+        """Unit dyadic for the ReferenceFrame."""
+        return self.xx + self.yy + self.zz
+
     def partial_velocity(self, frame, *gen_speeds):
         """Returns the partial angular velocities of this frame in the given
         frame with respect to one or more provided generalized speeds.


Test patch:
diff --git a/sympy/physics/vector/tests/test_frame.py b/sympy/physics/vector/tests/test_frame.py
index 69cbc97d74..7d19b99ea0 100644
--- a/sympy/physics/vector/tests/test_frame.py
+++ b/sympy/physics/vector/tests/test_frame.py
@@ -658,3 +658,63 @@ def test_dcm_cache_dict():
     assert A._dcm_dict == A._dcm_cache
     assert B._dcm_dict == {C: Matrix([[1, 0, 0],[0, cos(b), -sin(b)],[0, sin(b),  cos(b)]]), \
         A: Matrix([[1, 0, 0],[0, cos(b), -sin(b)],[0, sin(b),  cos(b)]])}
+
+def test_xx_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.xx == Vector.outer(N.x, N.x)
+    assert F.xx == Vector.outer(F.x, F.x)
+
+def test_xy_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.xy == Vector.outer(N.x, N.y)
+    assert F.xy == Vector.outer(F.x, F.y)
+
+def test_xz_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.xz == Vector.outer(N.x, N.z)
+    assert F.xz == Vector.outer(F.x, F.z)
+
+def test_yx_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.yx == Vector.outer(N.y, N.x)
+    assert F.yx == Vector.outer(F.y, F.x)
+
+def test_yy_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.yy == Vector.outer(N.y, N.y)
+    assert F.yy == Vector.outer(F.y, F.y)
+
+def test_yz_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.yz == Vector.outer(N.y, N.z)
+    assert F.yz == Vector.outer(F.y, F.z)
+
+def test_zx_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.zx == Vector.outer(N.z, N.x)
+    assert F.zx == Vector.outer(F.z, F.x)
+
+def test_zy_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.zy == Vector.outer(N.z, N.y)
+    assert F.zy == Vector.outer(F.z, F.y)
+
+def test_zz_dyad():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.zz == Vector.outer(N.z, N.z)
+    assert F.zz == Vector.outer(F.z, F.z)
+
+def test_unit_dyadic():
+    N = ReferenceFrame('N')
+    F = ReferenceFrame('F', indices=['1', '2', '3'])
+    assert N.u == N.xx + N.yy + N.zz
+    assert F.u == F.xx + F.yy + F.zz