Skip to content

Conversation

sidhu1012
Copy link
Contributor

References to other Issues or PRs

Fixes #20955
Partial #21036

Brief description of what is fixed or changed

Updated frame._dcm() to support orienting adjacent reference frame in arbitrary order.

Other comments

Release Notes

  • physics.vector
    • Reference frames can be oriented in arbitrary order.

@sidhu1012 sidhu1012 requested a review from moorepants as a code owner April 9, 2021 07:15
@sympy-bot
Copy link

sympy-bot commented Apr 9, 2021

Hi, I am the SymPy bot (v161). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • physics.vector

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

Click here to see the pull request description that was parsed.
<!-- 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 #20955
Partial #21036

#### Brief description of what is fixed or changed
Updated `frame._dcm()` to support orienting adjacent reference frame in arbitrary order. 

#### 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.

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
    * Reference frames can be oriented in arbitrary order.
<!-- END RELEASE NOTES -->

Update

The release notes on the wiki have been updated.

@sidhu1012
Copy link
Contributor Author

@moorepants Please review.

assert B._dcm_dict[C] == Matrix([[1, 0, 0], [0, cos(b), sin(b)], [0, -sin(b), cos(b)]])
assert A._dcm_dict[B] == Matrix([[1, 0, 0], [0, cos(c), sin(c)], [0, -sin(c), cos(c)]])
assert B._dcm_dict[A] == Matrix([[1, 0, 0], [0, cos(c), -sin(c)], [0, sin(c), cos(c)]])
assert C._dcm_dict[B] == Matrix([[1, 0, 0], [0, cos(b), -sin(b)], [0, sin(b), cos(b)]])
Copy link
Member

@moorepants moorepants Apr 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set B wrt A and then B wrt C, then overwrite A wrt B, B wrt C shouldn't change. It's not clear that is what is happening here.

I think these tests would be much clearer if you assert against the entire _dcm_dict. After each call to orient* we know what the whole dictionary should look like, so test the whole dict not just specific entires.

Please also write some comments that explain what you are testing here. It's not clear without very careful look at all these sin's and cosines.

*Please use the new functions e.g. orient_axis in all future code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set B wrt A and then B wrt C, then overwrite A wrt B, B wrt C shouldn't change. It's not clear that is what is happening here.

Yes this is happening here.

I'll update test cases to make it clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit tests should also have some tests that ensure the difference in the _dcm_dict and _dcm_cache is handled properly. Adding those tests with this change, would ensure we are breaking anything regarding that difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I will add seperate unit tests for _dcm_cache and _dcm_dict.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One or more tests like:

B.orient(A)
C.orient(B)
D.orient(C)
# check that all _dcm_dicts have all adjacent relations properly defined and check that _dcm_cache == _dcm_dict
D.dcm(A)
# check that _dcm_cache's have the D <-> A relationship defined (but not _dcm_dict)
A.dcm(D)  # check to make sure result is pulled from the cache, not recomputed
A.orient(B)  # change orientation of A <-> B
# check that _dcm_cache has been wiped and now equals _dcm_dict
# etc

for frame in dcm_dict_del:
del frame._dcm_dict[self]
for frame in dcm_cache_del:
del frame._dcm_cache[self]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally still don't know why there is a dcm_cache and a dcm_dict. If you understand that, could you make some comments here explaining it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I would try

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moorepants I think _dcm_cache and _dcm_dict both already have some comments explaining them.

#The _dcm_dict dictionary will only store the dcms of parent-child
#relationships. The _dcm_cache dictionary will work as the dcm
#cache.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does that mean and why are their two things?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, this sentence "The _dcm_cache dictionary will work as the dcm cache. " means nothing. It's like saying "The cow is the cow".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you change something in _dcm_dict then I guess that means you have to clear anything in the cache that could be affected, or maybe it just gets wiped completely if _dcm_dict changes. The explanation I'm wondering about would answer these questions.

In ._dcm() if we don't have relation already defined with parent

# Example
B.orient(A)
C.orient(A)
D.orient(C)
E.orient(C)

then in both _dcm_dict and _dcm_cache new relation is added.

But if a user do something like

B.orient(A,..)
A.orient(B,..)

then both _dcm_dict and _dcm_cache of A is cleared and new relation with B is added on the other hand _dcm_dict and _dcm_cache of frame B is updated with new relation with A while other relations of frame B stay intact.

Copy link
Member

@moorepants moorepants Apr 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't know why more than one data structure is needed.

Let's say we have a dictionary that stores the transformations of any adjacent (directly connected) frames. It works like this:

>>> B.orient(A)
>>> B.adjacent_relations
{A: A_dcm_B}
>>> A.adjacent_relations
{B: B_dcm_A}
>>> C.adjacent_relations
{}
>>> C.orient(A)
>>> A.adjacent_relations
{B: B_dcm_A, C: C_dcm_A}
>>> B.adjacent_relations
{A: A_dcm_B}
>>> C.adjacent_relations
{A: A_dcm_C}

Now try to change existing relationships:

>>> C.orient(B)
Error! you can't do this because it closes the loop and it's ambiguous what relation to delete to open the loop
>>> A.orient(B)  # replaces the A<->B relationship, no problem
>>> A.adjacent_relations
{B: B_dcm_A_new, C: C_dcm_A}
>>> B.adjacent_relations
{A: A_dcm_B_new}
>>> C.adjacent_relations
{A: A_dcm_C}

The A.orient(B) simply needs to update the adjacent_relations dict with the new relationship, nothing else should change.

So why is a _dcm_cache needed, what is that doing in this scenario?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You wrote:

print(A._dcm_cache) # Calculated dcm wrt C is added to _dcm_cache, no effect on _dcm_dict.

Earlier I wrote:

_dcm_cache can have everything that is in _dcm_dict but can also store relationships between self and any other reference frame that has a relative relationship (even if through a chain of reference frames).

So, is that not the same? That the cache stores relationships that are not adjacent? If you then change an adjacent relationship, you better wipe the cache because it could affect that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't know why more than one data structure is needed.

@moorepants I too believe we can merge _dcm_cache and _dcm_dict. _dcm_cache is only being used in frame.dcm(). _dcm_cache functionality could be added to _dcm_dict.

The A.orient(B) simply needs to update the adjacent_relations dict with the new relationship, nothing else should change.
So why is a _dcm_cache needed, what is that doing in this scenario?

_dcm_cache's sole purpose is in frame.dcm() for which _dcm_cache needs to be treated as _dcm_dict but with extra feature of storing calculated dcm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, is that not the same? That the cache stores relationships that are not adjacent?

_dcm_cache stores adjacent relationship along with those which are not adjacent but only when user explicitly calls dcm(). The dcm calculated between 2 frames adjacent or not is stored in _dcm_cache along with all adjacent ones.

If you then change an adjacent relationship, you better wipe the cache because it could affect that.

Yes that's True and this the default behavior in this PR.

#The _dcm_dict dictionary will only store the dcms of parent-child
#relationships. The _dcm_cache dictionary will work as the dcm
#cache.
#The _dcm_dict dictionary will only store the dcms of immediate parent-child
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would "adjacent" be better than "immediate"? I don't really know what immediate means in this context.

Copy link
Contributor Author

@sidhu1012 sidhu1012 Apr 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By immediate I mean, chain parents-child relations won't be stored.

A.orient(B)
C.orient(A)

Then C._dcm_dict won't have dcm wrt B

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moorepants I think "adjacent" would be better.

#cache.
#The _dcm_dict dictionary will only store the dcms of immediate parent-child
#relationships. The _dcm_cache dictionary will store calculated dcm along with
#all content of _dcm_dict for faster retrieval of dcms.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand this last sentence. What is different in _dcm_cache than _dcm_dict?

Copy link
Contributor Author

@sidhu1012 sidhu1012 Apr 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moorepants _dcm_cache stores everything inside _dcm_dict but also stores calculated dcm value of that frame.
If we do something like

A.oreint(B)
B.orient(C)

then for frame C _dcm_dict and _dcm_cache would have nothing related to frame A, but we do have a connecting path between A and C.

So now if we do

C.dcm(A) or A.dcm(A)

then this calculated dcm would be stored in _dcm_cache and won't effect _dcm_dict.

@sidhu1012
Copy link
Contributor Author

@moorepants I think it would be better if we merge _dcm_cache into _dcm_dict. If you agree then I could proceed with that. It would end all confusion and make code simpler.

@sidhu1012
Copy link
Contributor Author

@moorepants I think it would be better if we merge _dcm_cache into _dcm_dict. If you agree then I could proceed with that. It would end all confusion and make code simpler.

@moorepants On second thought, we shouldn't merge _dcm_cache and _dcm_dict because if we do so, then we won't be able to differentiate between adjacent frames and non adjacent frames.

@sidhu1012
Copy link
Contributor Author

@moorepants I'll summarize the difference between _dcm_cache and _dcm_dict .

_dcm_dict automatically stores adjacent parent child dcm when we call any orient function

dcm_cache stores all calculated dcm values, adjacent parent-child dcm are added when we call orient and the dcm value calculated between two non adjacent frames by dcm() is also added.

So _dcm_cache stores only those non adjacent frames which user explicitly wants to calculate by dcm().

@moorepants
Copy link
Member

I think it is clear now for me from the discussion. It would be helpful to make it clear to whoever reads the code in the future.

@sidhu1012
Copy link
Contributor Author

@moorepants Does comments seem right?

@sidhu1012
Copy link
Contributor Author

sidhu1012 commented Apr 19, 2021

Only test for _dcm_dict and _dcm_cache are left #21271 (comment).

Tagging link here so that it's easy to find, took few minutes to find that comment.

@sidhu1012 sidhu1012 requested a review from moorepants April 21, 2021 08:04
@sidhu1012
Copy link
Contributor Author

@moorepants Please review, I have added tests and comments.

assert D._dcm_dict == D._dcm_cache

D.dcm(A) # Check calculated dcm relation is stored in _dcm_cache and not in _dcm_dict
assert list(A._dcm_cache.keys()) == [A, B, D]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would A have a relationship to A?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why is it behaving like this, I tried to dig but till now I'm not sure why is it happening but I do know what is happening .

Copy link
Contributor Author

@sidhu1012 sidhu1012 Apr 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do know what is happening .

@moorepants If we pass a frame as parent frame in any orient() then that frame's _dcm_cache stores {self: self.dcm(self)} i.e dcm value wrt itself.

Copy link
Member

@moorepants moorepants Apr 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it store the identity matrix?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does:

In [14]: A._dcm_cache
Out[14]: 
{A: Matrix([
 [1, 0, 0],
 [0, 1, 0],
 [0, 0, 1]]),
 B: Matrix([
 [1,      0,       0],
 [0, cos(4), -sin(4)],
 [0, sin(4),  cos(4)]]),
 D: Matrix([
 [                       cos(4)**2,                   -sin(4)*cos(4),         sin(4)],
 [sin(4)**2*cos(4) + sin(4)*cos(4),            cos(4)**2 - sin(4)**3, -sin(4)*cos(4)],
 [   -sin(4)*cos(4)**2 + sin(4)**2, sin(4)**2*cos(4) + sin(4)*cos(4),      cos(4)**2]])}

@moorepants
Copy link
Member

LGTM! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Doctest failure in sympy/physics/vector/frame.py
4 participants