Repository: sympy/sympy
Pull request: physics: Unit dyads access from ReferenceFrames
<!-- 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 #24965



#### Brief description of what is fixed or changed
Unit dyads and unit dyadic can be accessed through attributes of a ReferenceFrame object. It is an addition related to issue #24965, allowing easy access to unit dyads without the need to use the outer() function. This commit adds 10 functions with the property decorator, along with a few examples in the constructor's documentation.


#### Other comments


#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

* physics.units
  * Corrected a semantical error in the conversion between volt and statvolt which
    reported the volt as being larger than the statvolt.

or if no release note(s) should be included use:

NO ENTRY

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
    * Added access to unit dyads and unit dyadic directly from ReferenceFrame objects.

<!-- END RELEASE NOTES -->


Problem statement:
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.

Hints from issue discussion:
We could add an attribute for the unit dyadic. Maybe `N.dyadic` or `N.unit`. Not sure what the best name would be.
Hello, I am willing to work on this. I wanted to ask if the intended behavior should be something like:
``` python
>>> N = ReferenceFrame('N')
>>> N.xx
(N.x|N.x)
>>> N.yz
(N.y|N.z)
```
I have some basic linear algebra knowledge, however I haven't worked with dyadics before, and probably I have misunderstood this. 
Yes that's the idea. No linear algebra knowledge needed. `.xx` should simply execute `outer(self.x, self.x)`.
Thank you, I will work on that!
I made a function inside ReferenceFrame class that returns unit_dyadics of a given reference frame.
The code is like this

def unit_dyadics(N):

    """
    Returns a dictionary of unit dyadics in the given reference frame N.
    parameters :
        N : ReferenceFrame
        The reference frame in which to create the unit dyadics.

    returns :
        dict
        A dictionary of unit dyadics with keys of the form "xx", "xy", "xz", etc.
    """
    unit_dyadics = {}
    for i, b1 in enumerate(N):
        for j, b2 in enumerate(N):
            unit_dyadics[b1.name + b2.name] = outer(b1, b2) if i == j else 0
    return unit_dyadics



This creates a matrix of dyadics 
I will also try to write code for dyads in sometime
Should I start a pull request now ?
> I made a function inside ReferenceFrame class that returns unit_dyadics of a given reference frame.

No, that is not what we are looking for. A unit dyadic is simply an identity matrix.
More info on unit dyads and the unit dyadic can be found here: https://moorepants.github.io/learn-multibody-dynamics/mass.html

Commit message: Fixed unit dyad example documentation mistake

Patch to review:
diff --git a/.mailmap b/.mailmap
index b3945e4c9ad8..856c6064202e 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>
diff --git a/sympy/physics/vector/frame.py b/sympy/physics/vector/frame.py
index 23f9c0a841de..bd13f7f37b18 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.unit
+        (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.
diff --git a/sympy/physics/vector/tests/test_frame.py b/sympy/physics/vector/tests/test_frame.py
index 69cbc97d7451..7d19b99ea01f 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


Code review comments:
diff_hunk: @@ -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.unit
path: sympy/physics/vector/frame.py
text: @user1:
```suggestion
        >>> N.u
```