Repository: moble/quaternionic

Issue:
Unexpected result when give int spherical coordinates
Hi, I'm not sure if this is a bug, but it's very confusing.

When I enter int theta and phi, it gives zeros.

```python
import quaternionic
quaternionic.array.from_spherical_coordinates(1, 2)
# --> quaternionic.array([0., 0., 0., 0.])
```

Only when I explicitly enter float theta and phi, it gives the correct result.

```python
import quaternionic
quaternionic.array.from_spherical_coordinates(1., 2.)
# --> quaternionic.array([ 0.47415988, -0.40342268,  0.25903472,  0.73846026])
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle) Report
> Merging [#30](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle) (707dcb1) into [main](https://codecov.io/gh/moble/quaternionic/commit/0c4ac8c38008cf9578e39d6f8b6e113df7cf1169?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle) (0c4ac8c) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/moble/quaternionic/pull/30/graphs/tree.svg?width=650&height=150&src=pr&token=1IYWIGWVT8&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle)](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle)

```diff
@@            Coverage Diff            @@
##              main       #30   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            8         8           
  Lines         1034      1041    +7     
  Branches       108       108           
=========================================
+ Hits          1034      1041    +7     
```


| [Impacted Files](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle) | Coverage Δ | |
|---|---|---|
| [quaternionic/converters.py](https://codecov.io/gh/moble/quaternionic/pull/30/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle#diff-cXVhdGVybmlvbmljL2NvbnZlcnRlcnMucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle). Last update [ae3dd08...707dcb1](https://codecov.io/gh/moble/quaternionic/pull/30?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Mike+Boyle).


Patch:
diff --git a/quaternionic/converters.py b/quaternionic/converters.py
index 8a3b4c6..a9db14f 100644
--- a/quaternionic/converters.py
+++ b/quaternionic/converters.py
@@ -394,8 +394,9 @@ def QuaternionConvertersMixin(jit=jit):
 
             """
             vec = np.asarray(vec)
-            quats = np.zeros(vec.shape[:-1] + (4,), dtype=vec.dtype)
-            quats[..., 1:] = vec[...] / 2
+            dtype = np.result_type(0.5, vec)
+            quats = np.zeros(vec.shape[:-1] + (4,), dtype=dtype)
+            quats[..., 1:] = 0.5 * vec[...]
             return np.exp(cls(quats))
 
         from_rotation_vector = from_axis_angle
@@ -495,14 +496,18 @@ def QuaternionConvertersMixin(jit=jit):
                 beta  = np.asarray(beta)
                 gamma = np.asarray(gamma)
 
+            # Pre-compute trig
+            cosβover2 = np.cos(beta/2)
+            sinβover2 = np.sin(beta/2)
+
             # Set up the output array
-            R = np.empty(np.broadcast(alpha, beta, gamma).shape + (4,), dtype=alpha.dtype)
+            R = np.empty(np.broadcast(alpha, beta, gamma).shape + (4,), dtype=cosβover2.dtype)
 
             # Compute the actual values of the quaternion components
-            R[..., 0] =  np.cos(beta/2)*np.cos((alpha+gamma)/2)  # scalar quaternion components
-            R[..., 1] = -np.sin(beta/2)*np.sin((alpha-gamma)/2)  # x quaternion components
-            R[..., 2] =  np.sin(beta/2)*np.cos((alpha-gamma)/2)  # y quaternion components
-            R[..., 3] =  np.cos(beta/2)*np.sin((alpha+gamma)/2)  # z quaternion components
+            R[..., 0] =  cosβover2*np.cos((alpha+gamma)/2)  # scalar quaternion components
+            R[..., 1] = -sinβover2*np.sin((alpha-gamma)/2)  # x quaternion components
+            R[..., 2] =  sinβover2*np.cos((alpha-gamma)/2)  # y quaternion components
+            R[..., 3] =  cosβover2*np.sin((alpha+gamma)/2)  # z quaternion components
 
             return cls(R)
 
@@ -653,14 +658,20 @@ def QuaternionConvertersMixin(jit=jit):
                 theta = np.asarray(theta_phi)
                 phi = np.asarray(phi)
 
+            # Pre-compute trig
+            cp = np.cos(phi/2)
+            ct = np.cos(theta/2)
+            sp = np.sin(phi/2)
+            st = np.sin(theta/2)
+
             # Set up the output array
-            R = np.empty(np.broadcast(theta, phi).shape + (4,), dtype=theta.dtype)
+            R = np.empty(np.broadcast(theta, phi).shape + (4,), dtype=cp.dtype)
 
             # Compute the actual values of the quaternion components
-            R[..., 0] =  np.cos(phi/2)*np.cos(theta/2)  # scalar quaternion components
-            R[..., 1] = -np.sin(phi/2)*np.sin(theta/2)  # x quaternion components
-            R[..., 2] =  np.cos(phi/2)*np.sin(theta/2)  # y quaternion components
-            R[..., 3] =  np.sin(phi/2)*np.cos(theta/2)  # z quaternion components
+            R[..., 0] =  cp*ct  # scalar quaternion components
+            R[..., 1] = -sp*st  # x quaternion components
+            R[..., 2] =  cp*st  # y quaternion components
+            R[..., 3] =  sp*ct  # z quaternion components
 
             return cls(R)
 
@@ -773,7 +784,7 @@ def QuaternionConvertersMixin(jit=jit):
                 ω = omega
             else:
                 omega = np.asarray(omega)
-                if omega.dtype != np.float:
+                if not np.issubdtype(omega.dtype, np.floating):
                     raise ValueError(f"Input omega must have float dtype; it has dtype {omega.dtype}.")
                 if omega.shape != (t.shape[0], 3):
                     raise ValueError(f"Input omega must have shape {(t.shape[0], 3)}; it has shape {omega.shape}.")


Test patch:
diff --git a/tests/test_converters.py b/tests/test_converters.py
index e068fde..6379eae 100644
--- a/tests/test_converters.py
+++ b/tests/test_converters.py
@@ -146,6 +146,12 @@ def test_from_rotation_vector():
     quats2 = quaternionic.array.from_rotation_vector(quat_vecs)
     assert np.allclose(quats, quats2)
 
+    assert (
+        quaternionic.array.from_axis_angle([1, 2, 3])
+        ==
+        quaternionic.array.from_axis_angle([1.0, 2.0, 3.0])
+    )
+
 
 def test_from_spherical_coordinates():
     one, x, y, z = tuple(quaternionic.array(np.eye(4)))
@@ -182,6 +188,13 @@ def test_from_spherical_coordinates():
         - quaternionic.array([quaternionic.array.from_spherical_coordinates(vartheta, varphi) for vartheta, varphi in random_angles])
     )) < 1.e-15
 
+    # https://github.com/moble/quaternionic/issues/29
+    assert (
+        quaternionic.array.from_spherical_coordinates(1, 2)
+        ==
+        quaternionic.array.from_spherical_coordinates(1.0, 2.0)
+    )
+
 
 def test_to_spherical_coordinates(Rs):
     one, x, y, z = tuple(quaternionic.array(np.eye(4)))
@@ -219,6 +232,12 @@ def test_from_euler_angles():
                          - quaternionic.array([quaternionic.array.from_euler_angles(alpha, beta, gamma)
                                                for alpha, beta, gamma in random_angles]))) < 1.e-15
 
+    assert (
+        quaternionic.array.from_euler_angles(1, 2, 3)
+        ==
+        quaternionic.array.from_euler_angles(1.0, 2.0, 3.0)
+    )
+
 
 def test_to_euler_angles(eps, array):
     np.random.seed(1843)