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 Vector Potential Calculation to Coil classes and Most MagneticField Classes #1026

Merged
merged 71 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
a0ded34
add biot savart vector potential for general and coils and add test f…
dpanici May 20, 2024
483ddec
add hanson hirshman vector pot calc for splinexyzcoil
dpanici May 20, 2024
c5e2c3f
add vector potential function for fourier current potential and add t…
dpanici May 20, 2024
a10028f
add vector potential computation to the current potential field class
dpanici May 20, 2024
63e849b
Merge branch 'master' into dp/vector-potential
dpanici May 21, 2024
7bf6915
add test for coils integrating over a flux loop, has revealed a bug i…
dpanici May 21, 2024
34ed85f
fix error in HH vector potential integral function
dpanici May 21, 2024
94fce67
fix comment in test
dpanici May 21, 2024
170b550
fix orientation confusion
dpanici May 21, 2024
acc6354
add vector potential calculation for analytic fields
dpanici May 22, 2024
dcf73f1
add vector potential method with not implemented error for the fields…
dpanici May 22, 2024
14f1265
Merge branch 'master' into dp/vector-potential
dpanici May 22, 2024
50028cd
Merge branch 'master' into dp/vector-potential
dpanici May 28, 2024
89f5200
add VectorPotentialField, ability to save/load vector potential for m…
dpanici Jun 9, 2024
08a6b3c
fix vfield vector potential
dpanici Jun 9, 2024
7ca27fc
Merge branch 'dp/vector-potential' of github.com:PlasmaControl/DESC i…
dpanici Jun 9, 2024
e5d2302
add catch for warnings in test
dpanici Jun 10, 2024
7d4de49
Merge branch 'master' into dp/vector-potential
dpanici Jun 10, 2024
877e5ad
add to changelog and add comment
dpanici Jun 11, 2024
d47699e
Merge branch 'master' into dp/vector-potential
dpanici Jun 19, 2024
d74e176
Merge branch 'master' into dp/vector-potential
dpanici Jun 20, 2024
830b6a4
Merge branch 'dp/vector-potential' of github.com:PlasmaControl/DESC i…
dpanici Jul 16, 2024
9e7c1f0
Merge branch 'master' into dp/vector-potential
dpanici Jul 16, 2024
5c8b56a
fix basic_fields test
dpanici Jul 16, 2024
ec576d6
fix bug in vector magnetic potential in spline magnetic field
dpanici Jul 16, 2024
08f7445
fix freeb test
dpanici Jul 16, 2024
15a1b1d
Merge branch 'master' into dp/vector-potential
dpanici Jul 17, 2024
3d4e820
fix tests
dpanici Jul 17, 2024
dc5aa01
fix tests
dpanici Jul 17, 2024
f961353
Merge branch 'master' into dp/vector-potential
dpanici Jul 17, 2024
6a5a4c9
Merge branch 'master' into dp/vector-potential
dpanici Jul 19, 2024
7a0042a
Merge branch 'master' into dp/vector-potential
dpanici Jul 19, 2024
c6b8090
make test more compact
dpanici Jul 19, 2024
c5464a9
make test more compact
dpanici Jul 19, 2024
ac979c5
address comments
dpanici Jul 20, 2024
778110b
Merge branch 'master' into dp/vector-potential
dpanici Jul 20, 2024
911650a
fix test
dpanici Jul 20, 2024
d22fa59
refactor magnetic field computes to re-use code
dpanici Jul 21, 2024
4d89bde
refactor compute methods of current potential fields
dpanici Jul 21, 2024
9d89709
refactor coils compute magnetic field to use less code
dpanici Jul 21, 2024
1d60c40
fix docstring error
dpanici Jul 21, 2024
7bdd425
Merge branch 'master' into dp/vector-potential
dpanici Jul 22, 2024
675383c
Merge branch 'master' into dp/vector-potential
dpanici Aug 1, 2024
35fd294
fix vector pot saving for spline field, add test
dpanici Aug 1, 2024
c54723d
change toroidal flux obj to use vector potential
dpanici Aug 1, 2024
4574dc4
add tests to cover both methods of toroidal flux obj
dpanici Aug 1, 2024
00e4b98
address comment
dpanici Aug 1, 2024
4e4410b
add test
dpanici Aug 1, 2024
d337ed9
address comment
dpanici Aug 1, 2024
bd61ef8
update changelog
dpanici Aug 1, 2024
ac7aaa2
update changelog (again)
dpanici Aug 1, 2024
008ecbc
put comments assuming coulomb gauge
dpanici Aug 2, 2024
e500676
Merge branch 'master' into dp/vector-potential
dpanici Aug 8, 2024
e3b0e44
address comments
dpanici Aug 8, 2024
c45a475
update changelog
dpanici Aug 8, 2024
68beebc
remove forgotten arg
dpanici Aug 8, 2024
9134103
fix list of expected errors
dpanici Aug 9, 2024
c44779e
Merge branch 'master' into dp/vector-potential
dpanici Aug 14, 2024
31dc536
Merge branch 'master' into dp/vector-potential
f0uriest Aug 18, 2024
d5dd005
Merge branch 'master' into dp/vector-potential
dpanici Aug 20, 2024
1c889e6
Merge branch 'master' into dp/vector-potential
dpanici Aug 20, 2024
4474a27
Merge branch 'master' into dp/vector-potential
dpanici Aug 21, 2024
8e3631f
Merge branch 'master' into dp/vector-potential
dpanici Aug 26, 2024
3dc95b3
Merge branch 'master' into dp/vector-potential
dpanici Aug 27, 2024
8932a77
Merge branch 'master' into dp/vector-potential
dpanici Aug 28, 2024
5f3d337
Merge branch 'master' into dp/vector-potential
dpanici Aug 28, 2024
0505027
Merge branch 'master' into dp/vector-potential
dpanici Aug 29, 2024
7bb7bd3
remove _AB fxns for simpler fields
dpanici Aug 29, 2024
cd2a041
address comment
dpanici Aug 29, 2024
2b99cc8
remove todo
dpanici Aug 29, 2024
98053b3
Merge branch 'master' into dp/vector-potential
dpanici Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 199 additions & 5 deletions desc/magnetic_fields/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,31 @@ def __call__(self, grid, params=None, basis="rpz"):
"""Compute magnetic field at a set of points."""
return self.compute_magnetic_field(grid, params, basis)

@abstractmethod
def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
dpanici marked this conversation as resolved.
Show resolved Hide resolved
params : dict or array-like of dict, optional
Dictionary of optimizable parameters, eg field.params_dict.
basis : {"rpz", "xyz"}
dpanici marked this conversation as resolved.
Show resolved Hide resolved
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Grid used to discretize MagneticField object if calculating A from
Biot-Savart. Should NOT include endpoint at 2pi.

Returns
-------
A : ndarray, shape(N,3)
magnetic vector potential at specified points

"""

def compute_Bnormal(
self,
surface,
Expand Down Expand Up @@ -734,6 +759,33 @@ def compute_magnetic_field(
coords, params, basis, source_grid
)

def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
params : dict or array-like of dict, optional
Dictionary of optimizable parameters, eg field.params_dict.
basis : {"rpz", "xyz"}
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Grid used to discretize MagneticField object if calculating A from
Biot-Savart. Should NOT include endpoint at 2pi.

Returns
-------
A : ndarray, shape(N,3)
scaled magnetic vector potential at specified points

"""
return self._scale * self._field.compute_magnetic_vector_potential(
coords, params, basis, source_grid
)


class SumMagneticField(_MagneticField, MutableSequence, OptimizableCollection):
"""Sum of two or more magnetic field sources.
Expand Down Expand Up @@ -799,6 +851,50 @@ def compute_magnetic_field(

return B

def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
params : dict or array-like of dict, optional
Dictionary of optimizable parameters, eg field.params_dict.
basis : {"rpz", "xyz"}
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Grid used to discretize MagneticField object if calculating A from
Biot-Savart. Should NOT include endpoint at 2pi.

Returns
-------
A : ndarray, shape(N,3)
scaled magnetic vector potential at specified points

"""
if params is None:
params = [None] * len(self._fields)
if isinstance(params, dict):
params = [params]
if source_grid is None:
source_grid = [None] * len(self._fields)
if not isinstance(source_grid, (list, tuple)):
source_grid = [source_grid]
if len(source_grid) != len(self._fields):
# ensure that if source_grid is shorter, that it is simply repeated so that
# zip does not terminate early
source_grid = source_grid * len(self._fields)

A = 0
for i, (field, g) in enumerate(zip(self._fields, source_grid)):
A += field.compute_magnetic_vector_potential(
coords, params[i % len(params)], basis, source_grid=g
)

return A

# dunder methods required by MutableSequence
def __getitem__(self, i):
return self._fields[i]
Expand Down Expand Up @@ -904,6 +1000,43 @@ def compute_magnetic_field(

return B

def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
dpanici marked this conversation as resolved.
Show resolved Hide resolved
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
params : dict or array-like of dict, optional
Dict of values for R0 and B0.
basis : {"rpz", "xyz"}
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Unused by this MagneticField class.

Returns
-------
A : ndarray, shape(N,3)
magnetic vector potential at specified points

"""
params = setdefault(params, {})
B0 = params.get("B0", self.B0)
R0 = params.get("R0", self.R0)

assert basis.lower() in ["rpz", "xyz"]
coords = jnp.atleast_2d(jnp.asarray(coords))
if basis == "xyz":
coords = xyz2rpz(coords)
az = -B0 * R0 * jnp.log(coords[:, 0])
arp = jnp.zeros_like(az)
A = jnp.array([arp, arp, az]).T
# b/c it only has a nonzero z component, no need
# to switch bases back if xyz is given
return A


class VerticalMagneticField(_MagneticField, Optimizable):
"""Uniform magnetic field purely in the vertical (Z) direction.
Expand Down Expand Up @@ -955,18 +1088,52 @@ def compute_magnetic_field(
params = setdefault(params, {})
B0 = params.get("B0", self.B0)

assert basis.lower() in ["rpz", "xyz"]
coords = jnp.atleast_2d(jnp.asarray(coords))
if basis == "xyz":
coords = xyz2rpz(coords)
bz = B0 * jnp.ones_like(coords[:, 2])
brp = jnp.zeros_like(bz)
B = jnp.array([brp, brp, bz]).T
if basis == "xyz":
B = rpz2xyz_vec(B, phi=coords[:, 1])
# b/c it only has a nonzero z component, no need
# to switch bases back if xyz is given

return B

def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
params : dict or array-like of dict, optional
Dict of values for B0.
basis : {"rpz", "xyz"}
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Unused by this MagneticField class.

Returns
-------
A : ndarray, shape(N,3)
magnetic vector potential at specified points

"""
params = setdefault(params, {})
B0 = params.get("B0", self.B0)

assert basis.lower() in ["rpz", "xyz"]
coords = jnp.atleast_2d(jnp.asarray(coords))
if basis == "xyz":
coords = xyz2rpz(coords)
axy = (B0 / 2) * jnp.ones_like(coords[:, 2])
az = jnp.zeros_like(axy)
A = jnp.array([axy, -axy, az]).T
if basis == "xyz":
A = rpz2xyz_vec(A, phi=coords[:, 1])

return A


class PoloidalMagneticField(_MagneticField, Optimizable):
"""Pure poloidal magnetic field (ie in theta direction).
Expand Down Expand Up @@ -1074,6 +1241,33 @@ def compute_magnetic_field(

return B

def compute_magnetic_vector_potential(
self, coords, params=None, basis="rpz", source_grid=None
):
"""Compute magnetic vector potential at a set of points.

Parameters
----------
coords : array-like shape(n,3)
Nodes to evaluate vector potential at in [R,phi,Z] or [X,Y,Z] coordinates.
params : dict or array-like of dict, optional
Dict of values for B0.
basis : {"rpz", "xyz"}
Basis for input coordinates and returned magnetic vector potential.
source_grid : Grid, int or None or array-like, optional
Unused by this MagneticField class.

Returns
-------
A : ndarray, shape(N,3)
magnetic vector potential at specified points

"""
raise NotImplementedError(
"PoloidalMagneticField has nonzero divergence, therefore it can't be "
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we want to just remove this class entirely, or maybe we can figure out some form that actually obeys div B=0?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am fine with keeping it for now, though yea I have never used this one

"represented with a vector potential."
)


class SplineMagneticField(_MagneticField, Optimizable):
"""Magnetic field from precomputed values on a grid.
Expand Down
21 changes: 20 additions & 1 deletion tests/test_magnetic_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,37 @@ def test_combined_fields(self):
assert scaled_field.B0 == 2
assert scaled_field.scale == 3.1
np.testing.assert_allclose(scaled_field([1.0, 0, 0]), np.array([[0, 6.2, 0]]))
np.testing.assert_allclose(
scaled_field.compute_magnetic_vector_potential([2.0, 0, 0]),
np.array([[0, 0, -3.1 * 2 * 1 * np.log(2)]]),
)

scaled_field.R0 = 1.3
scaled_field.scale = 1.0
np.testing.assert_allclose(scaled_field([1.3, 0, 0]), np.array([[0, 2, 0]]))
np.testing.assert_allclose(
scaled_field.compute_magnetic_vector_potential([2.0, 0, 0]),
np.array([[0, 0, -2 * 1.3 * np.log(2)]]),
)
assert scaled_field.optimizable_params == ["B0", "R0", "scale"]
assert hasattr(scaled_field, "B0")

sum_field = vfield + pfield + tfield
sum_field_tv = vfield + tfield # to test A since pfield does not have A
assert len(sum_field) == 3
assert len(sum_field_tv) == 2

np.testing.assert_allclose(
sum_field([1.3, 0, 0.0]), [[0.0, 2, 3.2 + 2 * 1.2 * 0.3]]
)
R = 1.3
tfield_A = np.array([[0, 0, -2 * 1.3 * np.log(R)]])
vfield_A = np.array([[vfield.B0, -vfield.B0, 0]]) / 2
np.testing.assert_allclose(
sum_field_tv.compute_magnetic_vector_potential([R, 0, 0.0], basis="xyz"),
tfield_A + vfield_A,
)

assert sum_field.optimizable_params == [
["B0"],
["B0", "R0", "iota"],
Expand Down Expand Up @@ -337,7 +357,6 @@ def test_current_potential_vector_potential(self):
)
# test the loop integral of A around a curve encompassing the torus
# against the analytic result for flux in an ideal toroidal solenoid
## expression for flux inside of toroidal solenoid of radius a
prefactors = mu_0 * G / 2 / jnp.pi
correct_flux = -2 * np.pi * prefactors * (np.sqrt(R0**2 - a**2) - R0)

Expand Down
Loading