-
Notifications
You must be signed in to change notification settings - Fork 56
Fix for NaN in reverse mode gradient of FourierPlanarCoil and Hessian Scaling Issue for small problems #2277
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
base: master
Are you sure you want to change the base?
Changes from all commits
1a272b3
0b7f12d
8052752
ea858c8
b712fe8
27feda3
f769aef
daf4e45
e820240
f41f74d
0ee1273
f8eceac
389f4c6
214a775
cae9cb4
de03923
b541eb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -518,7 +518,7 @@ def compute_hess_scale(H, prev_scale_inv=None): | |
| """Compute scaling factors based on diagonal of Hessian matrix.""" | ||
| scale_inv = jnp.abs(jnp.diag(H)) | ||
| scale_inv = jnp.where( | ||
| scale_inv < jnp.finfo(H.dtype).eps * max(H.shape), 1, scale_inv | ||
| scale_inv < jnp.finfo(H.dtype).eps * max(H.shape) * 1e1, 1, scale_inv | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we have something like, eps = jnp.maximum(jnp.finfo(H.dtype).eps * max(H.shape), 1e-12)
scale_inv = jnp.where(scale_inv < eps, 1, scale_inv)1e-12 or something else can be chosen as the minimum threshold. |
||
| ) | ||
|
|
||
| if prev_scale_inv is not None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -983,6 +983,15 @@ def reflection_matrix(normal): | |||||
| def rotation_matrix(axis, angle=None): | ||||||
| """Matrix to rotate points about axis by given angle. | ||||||
|
|
||||||
| NOTE: This function works but will have zero gradient w.r.t. | ||||||
| the angle when the angle is nearly zero (specifically, when | ||||||
| the angle<sqrt(epsilon)) as there is a manual replacement of the | ||||||
| result for this case to avoid a NaN leak due to 1/0 division. | ||||||
|
|
||||||
| If seeking to rotate one vector onto another, it is recommended | ||||||
| to use rotate_vector_to_vector instead which uses quaternions | ||||||
| to avoid this issue | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| axis : array-like, shape(3,) | ||||||
|
|
@@ -1001,13 +1010,86 @@ def rotation_matrix(axis, angle=None): | |||||
| axis = safenormalize(axis) | ||||||
| if angle is None: | ||||||
| angle = norm | ||||||
| eps = 1e2 * jnp.finfo(axis.dtype).eps | ||||||
| eps = jnp.sqrt(jnp.finfo(axis.dtype).eps) | ||||||
| R1 = jnp.cos(angle) * jnp.eye(3) | ||||||
| R2 = jnp.sin(angle) * jnp.cross(axis, jnp.identity(axis.shape[0]) * -1) | ||||||
| R3 = (1 - jnp.cos(angle)) * jnp.outer(axis, axis) | ||||||
| return jnp.where(norm < eps, jnp.eye(3), R1 + R2 + R3) # if axis=0, no rotation | ||||||
|
|
||||||
|
|
||||||
| def rotate_vector_to_vector(u, v): | ||||||
| """ | ||||||
| Computes a rotation matrix that rotates vector u onto vector v. | ||||||
|
|
||||||
| Numerically stable when vectors are parallel or anti-parallel, and avoids | ||||||
| both NaN gradient when parallel/antiparallel, and also avoids zero | ||||||
| gradient (wrt v) | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| u : array-like, shape(3,) | ||||||
| first vector, to be rotated onto second vector v | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These need to be in cartesian coordinates, right? |
||||||
| v : array-like, shape(3,) | ||||||
| vector to rotate u onto | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| rotmat : ndarray, shape(3,3) | ||||||
| Matrix to rotate points in cartesian (X,Y,Z) coordinates. | ||||||
|
|
||||||
| """ | ||||||
| u = safenormalize(u) | ||||||
| v = safenormalize(v) | ||||||
|
|
||||||
| # 2. Compute the dot product | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| dot = jnp.dot(u, v) | ||||||
|
|
||||||
| # things needed for near antiparallel case | ||||||
| axis = jnp.where( | ||||||
| jnp.all(jnp.less(jnp.abs(v[0]), jnp.abs(v[1]))), | ||||||
| jnp.array([0, -v[2], v[1]]), | ||||||
| jnp.array([-v[2], 0, v[0]]), | ||||||
| ) | ||||||
| axis = safenormalize(axis) | ||||||
| K = jnp.array( | ||||||
| [[0, -axis[2], axis[1]], [axis[2], 0, -axis[0]], [-axis[1], axis[0], 0]] | ||||||
| ) | ||||||
| R_antiparallel = jnp.eye(3) + 2 * jnp.dot(K, K) | ||||||
|
|
||||||
| # Handle general cases using the half-vector (bisector) method | ||||||
| # This prevents the division-by-zero errors common in standard cross-product methods | ||||||
| h = u + v | ||||||
| h = safenormalize(h) | ||||||
|
|
||||||
| # Calculate quaternion components q = [w, x, y, z] | ||||||
| # Equivalent to rotating around (u x h) by the half-angle | ||||||
| qw = jnp.dot(u, h) | ||||||
| qx, qy, qz = jnp.cross(u, h) | ||||||
|
|
||||||
| # Convert quaternion to a standard 3x3 rotation matrix | ||||||
| R = jnp.array( | ||||||
| [ | ||||||
| [ | ||||||
| 1 - 2 * (qy**2 + qz**2), | ||||||
| 2 * (qx * qy - qw * qz), | ||||||
| 2 * (qx * qz + qw * qy), | ||||||
| ], | ||||||
| [ | ||||||
| 2 * (qx * qy + qw * qz), | ||||||
| 1 - 2 * (qx**2 + qz**2), | ||||||
| 2 * (qy * qz - qw * qx), | ||||||
| ], | ||||||
| [ | ||||||
| 2 * (qx * qz - qw * qy), | ||||||
| 2 * (qy * qz + qw * qx), | ||||||
| 1 - 2 * (qx**2 + qy**2), | ||||||
| ], | ||||||
| ] | ||||||
| ) | ||||||
| # where to return antiparallel R if needed, else normal R | ||||||
| return jnp.where(jnp.allclose(dot, -1.0, atol=1e-8, rtol=1e-8), R_antiparallel, R) | ||||||
|
|
||||||
|
|
||||||
| def xyz2rpz(pts): | ||||||
| """Transform points from cartesian (X,Y,Z) to polar (R,phi,Z) form. | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -552,9 +552,9 @@ def test_rotation(self): | |
| datax = cx.compute("x", grid=20, basis="xyz") | ||
| datay = cy.compute("x", grid=20, basis="xyz") | ||
| dataz = cz.compute("x", grid=20, basis="xyz") | ||
| np.testing.assert_allclose(datax["x"][:, 0], 0, atol=2e-16) # only in Y-Z plane | ||
| np.testing.assert_allclose(datay["x"][:, 1], 0, atol=2e-16) # only in X-Z plane | ||
| np.testing.assert_allclose(dataz["x"][:, 2], 0, atol=2e-16) # only in X-Y plane | ||
| np.testing.assert_allclose(datax["x"][:, 0], 0, atol=5e-16) # only in Y-Z plane | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. relaxed this bc 2e-16 was a too tight tolerance. The new method for rotations has a few more arithmetic operations than the old which just pushed it past the 2e-16 threshold (it was failing at like 2.4e-16 which is well within machine precision). |
||
| np.testing.assert_allclose(datay["x"][:, 1], 0, atol=5e-16) # only in X-Z plane | ||
| np.testing.assert_allclose(dataz["x"][:, 2], 0, atol=5e-16) # only in X-Y plane | ||
|
|
||
| @pytest.mark.unit | ||
| def test_length(self): | ||
|
|
@@ -763,9 +763,9 @@ def test_rotation(self): | |
| datax = cx.compute("x", grid=20, basis="xyz") | ||
| datay = cy.compute("x", grid=20, basis="xyz") | ||
| dataz = cz.compute("x", grid=20, basis="xyz") | ||
| np.testing.assert_allclose(datax["x"][:, 0], 0, atol=2e-16) # only in Y-Z plane | ||
| np.testing.assert_allclose(datay["x"][:, 1], 0, atol=2e-16) # only in X-Z plane | ||
| np.testing.assert_allclose(dataz["x"][:, 2], 0, atol=2e-16) # only in X-Y plane | ||
| np.testing.assert_allclose(datax["x"][:, 0], 0, atol=5e-16) # only in Y-Z plane | ||
| np.testing.assert_allclose(datay["x"][:, 1], 0, atol=5e-16) # only in X-Z plane | ||
| np.testing.assert_allclose(dataz["x"][:, 2], 0, atol=5e-16) # only in X-Y plane | ||
|
|
||
| @pytest.mark.unit | ||
| def test_length(self): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.