-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Hamilton's "operators" #20853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hamilton's "operators" #20853
Conversation
✅ 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:
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.
|
@oscarbenjamin please review |
@danilobellini can you review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be more consistent if the behavior of q0.is_pure()
would be the same of the zero is_imaginary()
, that is, zero itself would neither be seen a imaginary nor as a pure quaternion (it's the only one with real part zero that wouldn't be called "pure"). That is, is_pure should check something like self.a != 0 and self != 0
(but using self.b
, self.c
and self.d
instead in the second comparison).
Other checks with zero (like coplanarity) are indeed difficult details. The plane of a quaternion is orthogonal to its axis, and the axis of zero is undefined, therefore the binary coplanarity check should probably also be seen as undefined, I don't know if raising ValueError
or returning nan
would be better. At first, probably a ValueError
makes more sense for the binary quaternion coplanarity check, and I would say the same if the input quaternion has an undefined axis (that is, if b
, c
and d
are all zero for either self
or other
, self.coplanar(other)
should raise an error).
However, for the ternary coplanarity check, the origin is in every plane, it would make more sense if every three quaternions in such a check would be said coplanar. Actually, that's the modern coplanarity checking through the rank of the matrix of the coordinates. The current ternary_coplanar()
implementation isn't checking coplanarity and really need changes (binary and ternary coplanarity are really different operations). For the ternary, just check if the determinant of [[self.b, self.c, self.d], [q1.b, q1.c, q1.d], [q2.b, q2.c, q2.d]]
is zero: if it is, then the three are coplanar. Using just quaternions operations to check that is possible (self.orthogonal(q1/q2)
or any other combination if the division is undefined), but it's the same.
I would change some of the assert
s due to this "zero" behavior, and perhaps add some other checks because of the ternary coplanarity check.
The implementation of some of these could had been more straightforward, is there any reason to store everything into local variables before returning, e.g. return self.a
?
Some other details would help in the explanation of the docstrings, e.g. "pure quaternions seen as 3D vectors" instead of just "pure quaternions". There should be an explicit description that "coplanar()" checks the plane of the quaternion (which is orthogonal to its axis) whereas the "ternary_coplanar" checks the axis of the quaternions (i.e., the quaternions seen as 3D vectors). The axis is "the versor of the vector part of the quaternion", and the quaternion angle is the one "measured in the real-axis plane", these two are probably known by anyone who studies quaternions in the classical sense, but it's worth adding a little bit more text.
@danilobellini please review |
@danilobellini @oscarbenjamin I've made the suggested changes. Please review it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to replace everything just to use is_pure
instead of checking if self.a == 0
or something alike. The zero quaternion is orthogonal and parallel to every other quaternion at the same time, and the "ternary coplanar" (or axis coplanar) also makes sense for the zero quaternion (from the description of my previous review). There are only two cases where the quaternion zero should be really handled: (1) for the is_pure, to tell that "zero" isn't a right/pure quaternion (it might be, but it's really undefined, and returning False
makes sense, and the first updated assert
is already verifying this), (2) the binary coplanarity (quaternion planes), which is undefined if the axis is undefined (so it should handle not just the zero but all zero-axis inputs).
In Quaternion.coplanar
there are some double checks on a
, and the and/or/and
pattern looks confusing (which comes first?). Binary coplanarity is undefined if an input has an undefined axis, it makes sense to raise ValueError
unless self.vector().is_pure()
and the same for the otherinput.
The Quaternion.ternary_coplanar
implementation, as well as orthogonal
and parallel
, might just check if a
is zero, they still make sense otherwise (e.g. defining orthogonality as something like "dot product is zero" we would get the same result).
I'm not aware of how some internal automation of Sympy works, but I know sometimes I need to explicitly tell Sympy to expand/simplify/... an expression to get a straight result like "these two are indeed the same". I don't know if just checking (code) == 0
is better than code.simplify() == 0
or something alike using Eq
or something else "internal". Even the or
, when should it be used, and when the |
operator should be used? For stuff like that, as well as code style, I think someone else should review.
Actually in the definition |
Since By definition, Hamilton used the expression "right quaternion" to denote what we call "pure quaternion" nowadays. He uses "right" to mean that its angle is pi/2, something that happens for all quaternions whose real part is zero, but the zero quaternion, whose angle is undefined. However, "undefined" means neither "zero is a right quaternion" nor "zero isn't right quaternion", it just means that we don't know, it depends on something external (the context and/or how we use it). When we are using quaternions as 3D vectors (with their real part equals to zero), our context makes zero a right/pure quaternion (one can think on it as a limit, where everything else in the domain has angle equals to pi/2). When we are using unit quaternions (versors), they are like grand circle arcs in a specific plane but without a specific "position" in the circle, because we can interpret it as a rotation operator in that plane, multiplying it at left (that is, in |
I've made the suggested changes and for the confusion in |
@oscarbenjamin @danilobellini @oscargus please review this. |
@oscarbenjamin please review. |
All of the docstrings here need more explanation. Each concept should be defined. The definitions can refer to the other concepts provided they also reference them in a "see also" section. The "Returns" and "parameters" sections should be more consistent. They should specify the type of what is returned both as a Python type and also as a mathematical type like |
I don't know if you've already seen the style guide but it's here and gives examples of these things: |
I've made the suggested changes and made the doc strings bit more inofrmative. As mentioned here #20769 (comment) |
In general in sympy methods like |
@oscarbenjamin please review. |
@oscarbenjamin please review. |
@oscarbenjamin please review. |
@danilobellini does this look good to you? |
This needs better release notes. |
|
||
assert Quaternion.ternary_coplanar(Quaternion(0, 8, 12, 16), Quaternion(0, 4, 6, 8), Quaternion(0, 2, 3, 4)) == True | ||
assert Quaternion.ternary_coplanar(Quaternion(0, 8, 2, 6), Quaternion(0, 1, 6, 6), Quaternion(0, 0, 3, 4)) == False | ||
raises(ValueError, lambda: Quaternion.ternary_coplanar(q0, Quaternion(0, 4, 6, 8), q1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A test for None case can be added here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late review. Perhaps this should be rebased to at least to enforce a clean history with meaningful commit messages, unless another author is going to continue this. What do you think @oscarbenjamin?
This PR still doesn't finish every task proposed in the original issue, but it already solves most of them (11 out of 14), apart from some minor fixes I'm describing in this review. I'll comment the other 3 in another message.
Several times the leading single-sentence summary, the "Explanation" and the "Returns" sections have the same information, and sometimes with several repeated expressions within a single section. I believe there's no need for the three everywhere unless there's something new in each section. Probably all explanation sections (regarding this specific issue/PR) should have the mathematical notations (both the classical/Hamilton and the modern one, if any).
Methods scalar
and vector
were renamed to scalar_part
and vector_part
, that's okay. But, in Hamilton's notation, these would be S(q)
and V(q)
(using bold capital letters for S
and V
, something like \mathbf{S}(q)
in LaTeX), and this information should be both in the summary table and in the method docstring. Something like "Given axis
method would be explained using the the Ax(q)
(or, in LaTeX, $\mathbf{Ax}(q)$
) notation.
What happens on Quaternion(0, 0, 0, 0).axis()
? It's returning Quaternion(nan, nan, nan, nan)
, but the scalar part must be zero, not NaN (and this should be a test!).
And in the "axis" explanation can include a statement like "the axis is always an imaginary unit whose square is -1", and this could be a test for all quaternions (whose vector part isn't zero), this can be tested for a literal Quaternion.
Method coplanar
should be renamed to arc_coplanar
, it should not be a classmethod, and its explanation is misleading, since its "3D vector" (axis) is normal to the quaternion plane, and the origin is always the same (zero). Hamilton's interpretation (and a latter definition) of a quaternion is to see them as the result of a division of two 3D vectors, so that the multiplication of this quaternion by its denominator vector would "restore" the numerator, i.e., it would "rotate" and "scale" the vector in the denominator to make it match the numerator; the plane of this quaternion is the plane containing both the numerator and denominator, and the "rotation axis" (quaternion axis) is orthogonal to both. In a comment I've explained this.
The tests for arc_coplanar
are biased: they're just checking if one input is twice the other. There are several cases that it must return "True": the quaternion is always coplanar with itself (as long as it has a vector part), with its vector part scaled, with its vector part with its sign reversed, and the scalar part simply doesn't matter (it would be better if the tests didn't scale it together, actually a test should include a "w" as the scalar part, and it should still work without returning None).
Method ternary_coplanar
should be renamed to vector_coplanar
and its explanation must be fixed (it's explaining the other coplanar method). When renaming, the names appearing in some "See also" section should also be updated. As @praneethratna commented, this method can have more tests (it's missing a test for the None
output case).
|
||
""" | ||
if (q1.is_zero_quaternion()) or (q2.is_zero_quaternion()): | ||
raise ValueError('Any of the provided quaternions must not be 0') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Any" sounds like one of them could be zero. "Neither" instead of "Any" (removing the "not" afterwards) would be more clear.
checks if the 3D vectors written as quaternions are in the same plane which means | ||
the 3D vectors are parallel to the same plane. In order to return a True, | ||
the quaternions and the origin should be in the same plane, and the real | ||
part must be zero for it to make sense. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explanation is misleading. The "3D vector" of a quaternion (axis) is normal to its plane, and the origin is always the same (zero).
return atan2(self.vector_part().norm(), self.scalar_part()) | ||
|
||
@classmethod | ||
def coplanar(cls, q1, q2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be renamed to arc_coplanar
.
|
||
>>> from sympy.algebras.quaternion import Quaternion | ||
>>> q1 = Quaternion(1, 4, 4, 4) | ||
>>> q2 = Quaternion(2, 8, 8, 8) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to use something else instead of "2" (the only "True" in test_quaternion.py
is also checking one quaternion that is twice the other), and there's no need to store these in a variable.
|
||
return atan2(self.vector_part().norm(), self.scalar_part()) | ||
|
||
@classmethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does arc_coplanar
really need to be a classmethod?
return fuzzy_or([(aq - ar).is_zero_quaternion(), (aq + ar).is_zero_quaternion()]) | ||
|
||
@classmethod | ||
def ternary_coplanar(cls, q1, q2, q3): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be better named as vector_coplanar
, following a comment written later in the original issue.
Explanation | ||
=========== | ||
|
||
Two general quaternions are coplanar (in this arc sense) when their axes are parallel. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a short summary of the binary coplanar operator (arc_coplanar
, the other method), not an explanation of this method.
|
||
def mensor(self): | ||
""" | ||
Returns the log of the norm of the quaternion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be more explicit by using "natural logarithm" instead of "log" and by adding a parenthesis like "norm (magnitude)", because in this context "norm" can have another meaning.
About the item "Add Quaternion.versor() as a new name to Quaternion.normalize()", that can be done by a simple With "Add Hamilton operators to the Quaternion documentation" I was thinking on a new table (perhaps in the module/class docstring or elsewhere just for the rendering it all in the "Algebras" docs) summarizing Hamilton's notation and the modern notation where it makes sense. And, to avoid confusion with the names, the item "add the deprecated terminology 'norm' and 'tensor' to the documentation" was proposed, that could be part of that table or a single paragraph before/after it. These items weren't tackled yet, but perhaps these 3 items (as well as some documentation-related parts of my last review) can be written as another PR (after this one is fixed/finished and merged). |
Can i take up this PR and make the changes as mentioned above and open another PR for this ? since this PR seems to be stalled |
Yes, but please preserve the commits (including the original author details) if you do that. Since this has merge conflicts I would do that by rebasing or cherrypicking the commits onto master. |
This can be closed now as #22713 is merged. |
References to other Issues or PRs
Fixes #20769
Brief description of what is fixed or changed
Added the features mentioned in #20769