Skip to content
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

Add Bounding Box to Field Line Integration #908

Merged
merged 14 commits into from
Mar 1, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Changelog
=========

- Adds a bounding box to the `field_line_integrate` defined by `bounds_R` and `bounds_Z` keyword arguments, which form a hollow cylindrical bounding box. If the field line trajectory exits these bounds, the RHS will be multiplied by `exp(-(R**2+Z**2))` to stop the trajectory and prevent tracing the field line out to infinity, which is both costly and unnecessary when making a Poincare plot, which is the principle purpose of the function.

v0.10.4
-------

Expand Down
56 changes: 53 additions & 3 deletions desc/magnetic_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,8 @@
rtol=1e-8,
atol=1e-8,
maxstep=1000,
bounds_R=(0, np.inf),
bounds_Z=(-np.inf, np.inf),
):
"""Trace field lines by integration.

Expand All @@ -1401,6 +1403,21 @@
relative and absolute tolerances for ode integration
maxstep : int
maximum number of steps between different phis
bounds_R : tuple of (float,float), optional
R bounds for field line integration bounding box.
If supplied, the RHS of the field line equations will be
multipled by exp(-r) where r is the distance from the origin,
dpanici marked this conversation as resolved.
Show resolved Hide resolved
this is meant to prevent the field lines which escape to infinity from
slowing the integration down by being traced to infinity.
defaults to (0,np.inf)
bounds_Z : tuple of (float,float), optional
Z bounds for field line integration bounding box.
If supplied, the RHS of the field line equations will be
multipled by exp(-r) where r is the distance from the origin,
dpanici marked this conversation as resolved.
Show resolved Hide resolved
this is meant to prevent the field lines which escape to infinity from
slowing the integration down by being traced to infinity.
Defaults to (-np.inf,np.inf)


Returns
-------
Expand All @@ -1419,12 +1436,45 @@
def odefun(rpz, s):
rpz = rpz.reshape((3, -1)).T
r = rpz[:, 0]
z = rpz[:, 2]

Check warning on line 1439 in desc/magnetic_fields.py

View check run for this annotation

Codecov / codecov/patch

desc/magnetic_fields.py#L1439

Added line #L1439 was not covered by tests
# if bounds are given, will decay the magnetic field line eqn
# RHS if the trajectory is outside of bounds to avoid
# integrating the field line to infinity, which is costly
# and not useful in most cases
decay_factor = jnp.where(

Check warning on line 1444 in desc/magnetic_fields.py

View check run for this annotation

Codecov / codecov/patch

desc/magnetic_fields.py#L1444

Added line #L1444 was not covered by tests
jnp.array(
[
jnp.less(r, bounds_R[0]),
jnp.greater(r, bounds_R[1]),
jnp.less(z, bounds_Z[0]),
jnp.greater(z, bounds_Z[1]),
]
),
jnp.array(
[
# we mult by 1e6 to accelerate the decay so that the integration
# is stopped soon after the bounds are exited.
jnp.exp(-(1e6 * (r - bounds_R[0]) ** 2)),
jnp.exp(-(1e6 * (r - bounds_R[1]) ** 2)),
jnp.exp(-(1e6 * (z - bounds_Z[0]) ** 2)),
jnp.exp(-(1e6 * (z - bounds_Z[1]) ** 2)),
dpanici marked this conversation as resolved.
Show resolved Hide resolved
]
),
1.0,
)
# multiply all together, the conditions that are not violated
# are just one while the violated ones are continous decaying exponentials
decay_factor = jnp.prod(decay_factor, axis=0)

Check warning on line 1467 in desc/magnetic_fields.py

View check run for this annotation

Codecov / codecov/patch

desc/magnetic_fields.py#L1467

Added line #L1467 was not covered by tests

br, bp, bz = field.compute_magnetic_field(
rpz, params, basis="rpz", source_grid=source_grid
).T
return jnp.array(
[r * br / bp * jnp.sign(bp), jnp.sign(bp), r * bz / bp * jnp.sign(bp)]
).squeeze()
return (

Check warning on line 1472 in desc/magnetic_fields.py

View check run for this annotation

Codecov / codecov/patch

desc/magnetic_fields.py#L1472

Added line #L1472 was not covered by tests
decay_factor
* jnp.array(
[r * br / bp * jnp.sign(bp), jnp.sign(bp), r * bz / bp * jnp.sign(bp)]
).squeeze()
)

intfun = lambda x: odeint(odefun, x, phis, rtol=rtol, atol=atol, mxstep=maxstep)
x = jnp.vectorize(intfun, signature="(k)->(n,k)")(x0)
Expand Down
12 changes: 12 additions & 0 deletions tests/test_magnetic_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,18 @@ def test_field_line_integrate(self):
r, z = field_line_integrate(r0, z0, phis, field)
np.testing.assert_allclose(r[-1], 10, rtol=1e-6, atol=1e-6)
np.testing.assert_allclose(z[-1], 0.001, rtol=1e-6, atol=1e-6)
# test that bounds work correctly, and stop integration when trajectory
# hits the bounds
dpanici marked this conversation as resolved.
Show resolved Hide resolved
r0 = [10.1]
z0 = [0.0]
# this will hit the R bound
# (there is no Z bound, and R would go to 10.0 if not bounded)
r, z = field_line_integrate(r0, z0, phis, field, bounds_R=(10.05, np.inf))
np.testing.assert_allclose(r[-1], 10.05, rtol=3e-4)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we don't expect the integration to stop immediately after the bound is exited, but it should stop soon after depending on how fast the RHS decays. I made the decay very fast so we can test it, but if for some reason we should lessen the decay rates, then we will need to make these test tols here less tight

# this will hit the Z bound
# (there is no R bound, and Z would go to 0.1 if not bounded)
r, z = field_line_integrate(r0, z0, phis, field, bounds_Z=(-np.inf, 0.05))
np.testing.assert_allclose(z[-1], 0.05, atol=3e-3)

@pytest.mark.unit
def test_Bnormal_calculation(self):
Expand Down
Loading