Repository: yt-project/yt

Issue:
Angular momentum orientation
I'm confused by the current definition of specific angular momentum as [`-ucross(r_vec, v_vec)`](https://github.com/yt-project/yt/blob/4ca693aed0ce6c3ef791be44ec8a6fe6743c82c4/yt/fields/particle_fields.py#L306). Why isn't it `ucross(r_vec, v_vec)` instead, which should be the standard definition?

I digged into the history and found the definition introduced in this [PR](https://bitbucket.org/yt_analysis/yt/pull-requests/1585). The situation was a little bit complicated, and there was actually a [conversation](https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/activity#LC7248981T310) specifically about the angular momentum orientation.

The situation was that the definition of the angular momentum vector and those of the components were opposite. The vector was defined as [`np.cross(r_vec, v_vec)`](https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/diff#Lyt/fields/particle_fields.pyF285), while the x component was defined as [`yv*z - zv*y`](https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/diff#Lyt/fields/particle_fields.pyF302) (similarly for other components), corresponding to `-np.cross(r_vec, v_vec)`. Eventually the decision was made to use the latter convention.

But I'm not convinced this is a proper convention. Could we reconsider this? @cosmosquark @MatthewTurk @ngoldbaum was in the previous conversation and might have more to say.



Discussion:
Yeah, I ran into this in the recent PRs I made addressing angular momentum
calculations and was confused why the L vector was flipped relative to what
I expected...

On Fri, Sep 21, 2018 at 10:58 AM, Bili Dong <notifications@github.com>
wrote:

> I'm confused by the current definition of specific angular momentum as -ucross(r_vec,
> v_vec)
> <https://github.com/yt-project/yt/blob/4ca693aed0ce6c3ef791be44ec8a6fe6743c82c4/yt/fields/particle_fields.py#L306>.
> Why isn't it ucross(r_vec, v_vec) instead, which should be the standard
> definition?
>
> I digged into the history and found the definition introduced in this PR
> <https://bitbucket.org/yt_analysis/yt/pull-requests/1585>. The situation
> was a little bit complicated, and there was actually a conversation
> <https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/activity#LC7248981T310>
> specifically about the angular momentum orientation.
>
> The situation was that the definition of the angular momentum vector and
> those of the components are opposite. The vector was defined as np.cross(r_vec,
> v_vec)
> <https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/diff#Lyt/fields/particle_fields.pyF285>,
> while the x component was defined as yv*z - zv*y
> <https://bitbucket.org/yt_analysis/yt/pull-requests/1585/bugfix-improvement-specific-angular/diff#Lyt/fields/particle_fields.pyF302>
> (similarly for other components), corresponding to -np.cross(r_vec, v_vec).
> Eventually the decision was made to use the latter convention.
>
> But I'm not convinced this is a proper convention. Could we reconsider
> this? @cosmosquark <https://github.com/cosmosquark> @MatthewTurk
> <https://github.com/MatthewTurk> @ngoldbaum <https://github.com/ngoldbaum>
> was in the previous conversation and might have more to day.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/yt-project/yt/issues/2042>, or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AH3md7gnB1n3CU-MAWcgVfrRhheahG5dks5udSjMgaJpZM4W0nj_>
> .
>



-- 
Cameron Hummels
NSF Postdoctoral Fellow
Department of Astronomy
California Institute of Technology
http://chummels.org

I don't remember why we made it like that. I think maybe for backward compatibility reasons?

If you put in a PR that changes it we can see what breaks. This might be a good change for yt 4.0.

Resolving patch:
diff --git a/yt/fields/angular_momentum.py b/yt/fields/angular_momentum.py
--- a/yt/fields/angular_momentum.py
+++ b/yt/fields/angular_momentum.py
@@ -32,6 +32,8 @@
 
 @register_field_plugin
 def setup_angular_momentum(registry, ftype = "gas", slice_info = None):
+    # Angular momentum defined here needs to be consistent with
+    # _particle_specific_angular_momentum in particle_fields.py
     unit_system = registry.ds.unit_system
     def _specific_angular_momentum_x(field, data):
         xv, yv, zv = obtain_relative_velocity_vector(data)
@@ -39,7 +41,7 @@ def _specific_angular_momentum_x(field, data):
         units = rv.units
         rv = np.rollaxis(rv, 0, len(rv.shape))
         rv = data.ds.arr(rv, input_units=units)
-        return yv * rv[...,2] - zv * rv[...,1]
+        return rv[...,1] * zv - rv[...,2] * yv
 
     def _specific_angular_momentum_y(field, data):
         xv, yv, zv = obtain_relative_velocity_vector(data)
@@ -47,7 +49,7 @@ def _specific_angular_momentum_y(field, data):
         units = rv.units
         rv = np.rollaxis(rv, 0, len(rv.shape))
         rv = data.ds.arr(rv, input_units=units)
-        return - (xv * rv[...,2] - zv * rv[...,0])
+        return rv[...,2] * xv - rv[...,0] * zv
 
     def _specific_angular_momentum_z(field, data):
         xv, yv, zv = obtain_relative_velocity_vector(data)
@@ -55,7 +57,7 @@ def _specific_angular_momentum_z(field, data):
         units = rv.units
         rv = np.rollaxis(rv, 0, len(rv.shape))
         rv = data.ds.arr(rv, input_units=units)
-        return xv * rv[...,1] - yv * rv[...,0]
+        return rv[...,0] * yv - rv[...,1] * xv
 
     registry.add_field((ftype, "specific_angular_momentum_x"),
                        sampling_type="cell",
diff --git a/yt/fields/particle_fields.py b/yt/fields/particle_fields.py
--- a/yt/fields/particle_fields.py
+++ b/yt/fields/particle_fields.py
@@ -303,7 +303,7 @@ def _particle_specific_angular_momentum(field, data):
         # adding in the unit registry allows us to have a reference to the
         # dataset and thus we will always get the correct units after applying
         # the cross product.
-        return -ucross(r_vec, v_vec, registry=data.ds.unit_registry)
+        return ucross(r_vec, v_vec, registry=data.ds.unit_registry)
 
 
     registry.add_field((ptype, "particle_specific_angular_momentum"),


Test patch:
diff --git a/yt/fields/tests/test_angular_momentum.py b/yt/fields/tests/test_angular_momentum.py
--- a/yt/fields/tests/test_angular_momentum.py
+++ b/yt/fields/tests/test_angular_momentum.py
@@ -25,7 +25,7 @@ def test_AM_value():
     X = (ds.arr([sp[k] for k in 'xyz']) - x0[:, None]).T
     V = (ds.arr([sp['velocity_'+k] for k in 'xyz']) - v0[:, None]).T
 
-    sAM_manual = ds.arr(np.cross(V, X), X.units*V.units)
+    sAM_manual = ds.arr(np.cross(X, V), X.units*V.units)
     sAM = ds.arr([sp['specific_angular_momentum_'+k] for k in 'xyz']).T
 
     assert_allclose_units(sAM_manual, sAM)