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

LinkingCurrent objective #1222

Merged
merged 37 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ad68afa
LinkingCurrent objective
daniel-dudt Aug 22, 2024
e7351d3
get linking current working in single-stage
daniel-dudt Aug 23, 2024
59499bf
Merge branch 'master' into dd/linking_current
ddudt Aug 25, 2024
e489539
Merge branch 'master' into dd/linking_current
ddudt Aug 25, 2024
fc76f96
fix print_values
daniel-dudt Aug 26, 2024
9b88103
Merge branch 'master' into dd/linking_current
dpanici Aug 27, 2024
fd4542e
Merge branch 'master' into dd/linking_current
dpanici Aug 28, 2024
61112d3
Merge branch 'master' into dd/linking_current
f0uriest Oct 11, 2024
31e6228
Merge branch 'rc/linking_number' into dd/linking_current
f0uriest Oct 11, 2024
31eb816
Use linking number for linking current objective
f0uriest Oct 11, 2024
d941da7
Merge branch 'master' into dd/linking_current
f0uriest Oct 15, 2024
8db268d
fix test_objective_docstring
YigitElma Oct 15, 2024
fc23893
Merge branch 'master' into dd/linking_current
dpanici Oct 16, 2024
30dd572
Merge branch 'master' into dd/linking_current
f0uriest Oct 16, 2024
f5abd60
Add option to fix eq for LinkingCurrent
f0uriest Oct 16, 2024
4c5ec6c
Merge branch 'master' into dd/linking_current
f0uriest Nov 13, 2024
ed450b8
Add handling for linking currents from virtual coils
f0uriest Nov 13, 2024
db96420
Merge branch 'master' into dd/linking_current
f0uriest Nov 13, 2024
ea81721
Fix test
f0uriest Nov 13, 2024
6b102be
Update changelog
f0uriest Nov 13, 2024
48f4c40
Merge branch 'master' into dd/linking_current
f0uriest Nov 13, 2024
8a1958a
Update changelog action version
f0uriest Nov 13, 2024
87fb202
Merge branch 'dd/linking_current' of github.com:PlasmaControl/DESC in…
f0uriest Nov 13, 2024
0337023
Merge branch 'master' into dd/linking_current
f0uriest Nov 19, 2024
73f1286
Merge branch 'master' into dd/linking_current
f0uriest Dec 3, 2024
95cc0e8
Merge branch 'master' into dd/linking_current
dpanici Dec 3, 2024
4fd41f1
Merge branch 'master' into dd/linking_current
f0uriest Dec 4, 2024
815ad41
Merge branch 'master' into dd/linking_current
f0uriest Dec 5, 2024
f060c38
Merge branch 'master' into dd/linking_current
f0uriest Dec 10, 2024
d48f484
Merge branch 'master' into dd/linking_current
f0uriest Dec 11, 2024
39fec41
Merge branch 'master' into dd/linking_current
f0uriest Dec 11, 2024
a63f2ae
Rename linking current objective and expand docstring
f0uriest Dec 11, 2024
8f39791
suppress warning about incompatible constraints
f0uriest Dec 12, 2024
bffa25d
Merge branch 'master' into dd/linking_current
ddudt Dec 12, 2024
7dcd99f
Update desc/objectives/_coils.py
f0uriest Dec 13, 2024
88eaa93
Merge branch 'master' into dd/linking_current
f0uriest Dec 13, 2024
ab74f48
Merge branch 'master' into dd/linking_current
dpanici Dec 13, 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
17 changes: 9 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
New Feature

- Adds a new profile class ``PowerProfile`` for raising profiles to a power.
- Add ``desc.objectives.LinkingCurrent`` for ensuring that coils in a stage 2 or single stage optimization provide the required linking current for a given equilibrium.


Bug Fixes

Expand All @@ -13,7 +15,6 @@ v0.13.0
-------

New Features

- Adds ``from_input_file`` method to ``Equilibrium`` class to generate an ``Equilibrium`` object with boundary, profiles, resolution and flux specified in a given DESC or VMEC input file
- Adds function ``solve_regularized_surface_current`` to ``desc.magnetic_fields`` module that implements the REGCOIL algorithm (Landreman, (2017)) for surface current normal field optimization
* Can specify the tuple ``current_helicity=(M_coil, N_coil)`` to determine if resulting contours correspond to helical topology (both ``(M_coil, N_coil)`` not equal to 0), modular (``N_coil`` equal to 0 and ``M_coil`` nonzero) or windowpane/saddle (``M_coil`` and ``N_coil`` both zero)
Expand Down Expand Up @@ -296,7 +297,7 @@ optimization. Set to False by default.
non-singular, non-degenerate) coordinate mappings for initial guesses. This is applied
automatically when creating a new `Equilibrium` if the default initial guess of scaling
the boundary surface produces self-intersecting surfaces. This can be disabled by
passing `ensure_nested=False` when constructing the `Equilibrum`.
passing `ensure_nested=False` when constructing the `Equilibrium`.
- Adds `loss_function` argument to all `Objective`s for applying one of min/max/mean
to objective function values (for targeting the average value of a profile, etc).
- `Equilibrium.get_profile` now allows user to choose a profile type (power series, spline, etc)
Expand Down Expand Up @@ -448,7 +449,7 @@ Breaking changes
- Renames ``theta_sfl`` to ``theta_PEST`` in compute functions to avoid confusion with
other straight field line coordinate systems.
- Makes plotting kwargs a bit more uniform. ``zeta``, ``nzeta``, ``nphi`` have all been
superceded by ``phi`` which can be an integer for equally spaced angles or a float or
superseded by ``phi`` which can be an integer for equally spaced angles or a float or
array of float to specify angles manually.

Bug fixes
Expand Down Expand Up @@ -518,7 +519,7 @@ the future all quantities should evaluate correctly at the magnetic axis. Note t
evaluating quantities at the axis generally requires higher order derivatives and so
can be much more expensive than evaluating at nonsingular points, so during optimization
it is not recommended to include a grid point at the axis. Generally a small finite value
such as ``rho = 1e-6`` will avoid the singuarlity with a negligible loss in accuracy for
such as ``rho = 1e-6`` will avoid the singularity with a negligible loss in accuracy for
analytic quantities.
- Adds new optimizers ``fmin-auglag`` and ``lsq-auglag`` for performing constrained
optimization using the augmented Lagrangian method. These generally perform much better
Expand All @@ -534,7 +535,7 @@ the existing methods ``compute_theta_coordinates`` and ``compute_flux_coordinate
but allows mapping between arbitrary coordinates.
- Adds calculation of $\nabla \mathbf{B}$ tensor and corresponding $L_{\nabla B}$ metric
- Adds objective ``BScaleLength`` for penalizing strong magnetic field curvature.
- Adds objective ``ObjectiveFromUser`` for wrapping an arbitary user defined function.
- Adds objective ``ObjectiveFromUser`` for wrapping an arbitrary user defined function.
- Adds utilities ``desc.grid.find_least_rational_surfaces`` and
``desc.grid.find_most_rational_surfaces`` for finding the least/most rational surfaces
for a given rotational transform profile.
Expand Down Expand Up @@ -1070,7 +1071,7 @@ New Features:
- Updates `Equilibrium` to make creating them more straightforward.
- Instead of a dictionary of arrays and values, init method now
takes individual arguments. These can either be objects of the
correct type (ie `Surface` objects for boundary condiitons,
correct type (ie `Surface` objects for boundary conditions,
`Profile` for pressure and iota etc,) or ndarrays which will get
parsed into objects of the correct type (for backwards
compatibility)
Expand Down Expand Up @@ -1354,7 +1355,7 @@ Major changes:
indexing, where only M+1 points were used instead of the correct
2\*M+1
- Rotated concentric grids by 2pi/3M to avoid symmetry plane at
theta=0,pi. Previously, for stellarator symmetic cases, the nodes at
theta=0,pi. Previously, for stellarator symmetric cases, the nodes at
theta=0 did not contribute to helical force balance.
- Added [L\_grid]{.title-ref} parameter to specify radial resolution
of grid nodes directly and making the API more consistent.
Expand Down Expand Up @@ -1520,7 +1521,7 @@ saved, and objectives getting compiled more often than necessary

Major Changes:

- Changes to Equilibium/EquilibriaFamily:
- Changes to Equilibrium/EquilibriaFamily:
- general switching to using properties rather than direct
attributes when referencing things (ie, `eq.foo`, not
`eq._foo`). This allows getter methods to have safeguards if
Expand Down
25 changes: 25 additions & 0 deletions desc/coils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,15 @@
for coil, cur in zip(self.coils, new):
coil.current = cur

def _all_currents(self, currents=None):
"""Return an array of all the currents (including those in virtual coils)."""
if currents is None:
currents = self.current
currents = jnp.asarray(currents)
if self.sym:
currents = jnp.concatenate([currents, -1 * currents[::-1]])
return jnp.tile(currents, self.NFP)

Check warning on line 1310 in desc/coils.py

View check run for this annotation

Codecov / codecov/patch

desc/coils.py#L1305-L1310

Added lines #L1305 - L1310 were not covered by tests

def _make_arraylike(self, x):
if isinstance(x, dict):
x = [x] * len(self)
Expand Down Expand Up @@ -2337,6 +2346,22 @@
"""int: Number of coils."""
return sum([c.num_coils for c in self])

def _all_currents(self, currents=None):
"""Return an array of all the currents (including those in virtual coils)."""
if currents is None:
currents = jnp.array(flatten_list(self.current))
all_currents = []
i = 0
for coil in self.coils:
if isinstance(coil, CoilSet):
curr = currents[i : i + len(coil)]
all_currents += [coil._all_currents(curr)]
i += len(coil)

Check warning on line 2359 in desc/coils.py

View check run for this annotation

Codecov / codecov/patch

desc/coils.py#L2351-L2359

Added lines #L2351 - L2359 were not covered by tests
else:
all_currents += [jnp.atleast_1d(currents[i])]
i += 1
return jnp.concatenate(all_currents)

Check warning on line 2363 in desc/coils.py

View check run for this annotation

Codecov / codecov/patch

desc/coils.py#L2361-L2363

Added lines #L2361 - L2363 were not covered by tests

def compute(
self,
names,
Expand Down
1 change: 1 addition & 0 deletions desc/objectives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
CoilSetLinkingNumber,
CoilSetMinDistance,
CoilTorsion,
LinkingCurrent,
PlasmaCoilSetMinDistance,
QuadraticFlux,
SurfaceCurrentRegularization,
Expand Down
191 changes: 190 additions & 1 deletion desc/objectives/_coils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import warnings

import numpy as np
from scipy.constants import mu_0

from desc.backend import (
fori_loop,
Expand All @@ -15,7 +16,7 @@
from desc.compute.utils import _compute as compute_fun
from desc.grid import LinearGrid, _Grid
from desc.integrals import compute_B_plasma
from desc.utils import Timer, errorif, safenorm, warnif
from desc.utils import Timer, broadcast_tree, errorif, safenorm, warnif

from .normalization import compute_scaling_factors
from .objective_funs import _Objective, collect_docs
Expand Down Expand Up @@ -1762,6 +1763,194 @@
return Psi


class LinkingCurrent(_Objective):
"""Target the self-consistent poloidal linking current between the plasma and coils.
f0uriest marked this conversation as resolved.
Show resolved Hide resolved

Assumes the coil topology does not change (ie the linking number with the plasma
is fixed).

Parameters
----------
eq : Equilibrium
Equilibrium that will be optimized to satisfy the Objective.
coil : CoilSet
Coil(s) that are to be optimized.
grid : Grid, optional
Collocation grid containing the nodes to evaluate plasma current at.
Defaults to ``LinearGrid(M=eq.M_grid, N=eq.N_grid, NFP=eq.NFP, sym=eq.sym)``.
dpanici marked this conversation as resolved.
Show resolved Hide resolved
eq_fixed : bool
Whether the equilibrium is assumed fixed (should be true for stage 2, false
for single stage).
"""

__doc__ = __doc__.rstrip() + collect_docs(
target_default="``target=0``.",
bounds_default="``target=0``.",
)

_scalar = True
_units = "(A)"
_print_value_fmt = "Linking current error: "

def __init__(
self,
eq,
coil,
target=None,
bounds=None,
weight=1,
normalize=True,
normalize_target=True,
loss_function=None,
deriv_mode="auto",
grid=None,
eq_fixed=False,
jac_chunk_size=None,
name="linking current",
):
if target is None and bounds is None:
target = 0
self._grid = grid
self._eq_fixed = eq_fixed
self._linear = eq_fixed
self._eq = eq
self._coil = coil

Check warning on line 1817 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1811-L1817

Added lines #L1811 - L1817 were not covered by tests

super().__init__(

Check warning on line 1819 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1819

Added line #L1819 was not covered by tests
things=[coil] if eq_fixed else [coil, eq],
target=target,
bounds=bounds,
weight=weight,
normalize=normalize,
normalize_target=normalize_target,
loss_function=loss_function,
deriv_mode=deriv_mode,
jac_chunk_size=jac_chunk_size,
name=name,
)

def build(self, use_jit=True, verbose=1):
"""Build constant arrays.

Parameters
----------
use_jit : bool, optional
Whether to just-in-time compile the objective and derivatives.
verbose : int, optional
Level of output.

"""
eq = self._eq
coil = self._coil
grid = self._grid or LinearGrid(

Check warning on line 1845 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1843-L1845

Added lines #L1843 - L1845 were not covered by tests
M=eq.M_grid, N=eq.N_grid, NFP=eq.NFP, sym=eq.sym
)
warnif(

Check warning on line 1848 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1848

Added line #L1848 was not covered by tests
not np.allclose(grid.nodes[:, 0], 1),
UserWarning,
"grid includes interior points, should be rho=1.",
)

self._dim_f = 1
self._data_keys = ["G"]

Check warning on line 1855 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1854-L1855

Added lines #L1854 - L1855 were not covered by tests

all_params = tree_map(lambda dim: np.arange(dim), coil.dimensions)
current_params = tree_map(lambda idx: {"current": idx}, True)

Check warning on line 1858 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1857-L1858

Added lines #L1857 - L1858 were not covered by tests
# indices of coil currents
self._indices = tree_leaves(broadcast_tree(current_params, all_params))
self._num_coils = coil.num_coils

Check warning on line 1861 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1860-L1861

Added lines #L1860 - L1861 were not covered by tests

profiles = get_profiles(self._data_keys, obj=eq, grid=grid)
transforms = get_transforms(self._data_keys, obj=eq, grid=grid)

Check warning on line 1864 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1863-L1864

Added lines #L1863 - L1864 were not covered by tests

# compute linking number of coils with plasma. To do this we add a fake "coil"
# along the magnetic axis and compute the linking number of that coilset
from desc.coils import FourierRZCoil, MixedCoilSet

Check warning on line 1868 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1868

Added line #L1868 was not covered by tests

axis_coil = FourierRZCoil(

Check warning on line 1870 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1870

Added line #L1870 was not covered by tests
1.0,
eq.axis.R_n,
eq.axis.Z_n,
eq.axis.R_basis.modes[:, 2],
eq.axis.Z_basis.modes[:, 2],
eq.axis.NFP,
)
dummy_coilset = MixedCoilSet(axis_coil, coil, check_intersection=False)

Check warning on line 1878 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1878

Added line #L1878 was not covered by tests
# linking number for coils with axis
link = np.round(dummy_coilset._compute_linking_number())[0, 1:]

Check warning on line 1880 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1880

Added line #L1880 was not covered by tests

self._constants = {

Check warning on line 1882 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1882

Added line #L1882 was not covered by tests
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this could be 1 line instead of 4

"quad_weights": 1.0,
"link": link,
dpanici marked this conversation as resolved.
Show resolved Hide resolved
}

if self._eq_fixed:
data = compute_fun(

Check warning on line 1888 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1887-L1888

Added lines #L1887 - L1888 were not covered by tests
"desc.equilibrium.equilibrium.Equilibrium",
self._data_keys,
params=eq.params_dict,
transforms=transforms,
profiles=profiles,
)
eq_linking_current = 2 * jnp.pi * data["G"][0] / mu_0
self._constants["eq_linking_current"] = eq_linking_current

Check warning on line 1896 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1895-L1896

Added lines #L1895 - L1896 were not covered by tests
else:
self._constants["profiles"] = profiles
self._constants["transforms"] = transforms

Check warning on line 1899 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1898-L1899

Added lines #L1898 - L1899 were not covered by tests

if self._normalize:
params = tree_leaves(

Check warning on line 1902 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1901-L1902

Added lines #L1901 - L1902 were not covered by tests
coil.params_dict, is_leaf=lambda x: isinstance(x, dict)
)
self._normalization = np.sum([np.abs(param["current"]) for param in params])

Check warning on line 1905 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1905

Added line #L1905 was not covered by tests

super().build(use_jit=use_jit, verbose=verbose)

Check warning on line 1907 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1907

Added line #L1907 was not covered by tests

def compute(self, coil_params, eq_params=None, constants=None):
"""Compute linking current error.

Parameters
----------
coil_params : dict
Dictionary of coilset degrees of freedom, eg ``CoilSet.params_dict``
eq_params : dict
Dictionary of equilibrium degrees of freedom, eg ``Equilibrium.params_dict``
Only required if eq_fixed=False.
constants : dict
Dictionary of constant data, eg transforms, profiles etc.
Defaults to self._constants.

Returns
-------
f : array of floats
Linking current error.
Copy link
Collaborator

Choose a reason for hiding this comment

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

techincally just a single float but not a big deal


"""
if constants is None:
constants = self.constants
if self._eq_fixed:
eq_linking_current = constants["eq_linking_current"]

Check warning on line 1932 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1929-L1932

Added lines #L1929 - L1932 were not covered by tests
else:
data = compute_fun(

Check warning on line 1934 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1934

Added line #L1934 was not covered by tests
"desc.equilibrium.equilibrium.Equilibrium",
self._data_keys,
params=eq_params,
transforms=constants["transforms"],
profiles=constants["profiles"],
)
eq_linking_current = 2 * jnp.pi * data["G"][0] / mu_0

Check warning on line 1941 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1941

Added line #L1941 was not covered by tests

coil_currents = jnp.concatenate(

Check warning on line 1943 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1943

Added line #L1943 was not covered by tests
[
jnp.atleast_1d(param[idx])
for param, idx in zip(tree_leaves(coil_params), self._indices)
]
)
coil_currents = self.things[0]._all_currents(coil_currents)
coil_linking_current = jnp.sum(constants["link"] * coil_currents)
f0uriest marked this conversation as resolved.
Show resolved Hide resolved
return eq_linking_current - coil_linking_current

Check warning on line 1951 in desc/objectives/_coils.py

View check run for this annotation

Codecov / codecov/patch

desc/objectives/_coils.py#L1949-L1951

Added lines #L1949 - L1951 were not covered by tests


class CoilSetLinkingNumber(_Objective):
"""Prevents coils from becoming interlinked.

Expand Down
Loading
Loading