Skip to content

Adding QUADCOIL integration to DESC as an objective#2110

Open
lankef wants to merge 53 commits into
masterfrom
ffu/quadcoil-qss-pre-commit
Open

Adding QUADCOIL integration to DESC as an objective#2110
lankef wants to merge 53 commits into
masterfrom
ffu/quadcoil-qss-pre-commit

Conversation

@lankef

@lankef lankef commented Mar 3, 2026

Copy link
Copy Markdown
Collaborator

This pull request adds two files under /objective, _quadcoil.py and _quadcoil_utils.py. These allows one to call QUADCOIL using a DESC equilibrium and filament coil set as input, and perform quasi-single-stage optimization on the equilibrium and/or the coilset. There are some additions in coils and curves for conversion from simsopt objects. (since quadcoil is written in the simsopt convention). Winding surface can be either fixed or auto-generated, but optimizing the geometry of the winding surface is not yet implemented. The unit test compares quadcoil to desc's built-in REGCOIL.

lankef and others added 30 commits April 4, 2025 12:23
… optimizable_io.py and objective_funs.py to avoid error for static, empty tuple attributes and _objectives with self.coordinates == \'\'
…nversion, but it's not working because quadcoilproxy does not directly store the input dictionary, and desc static dict cannot contain strings (?)
…: Colorbar layout of new layout engine not compatible with old engine, and a colorbar has been created. Engine not changed.
"_use_jit",
]
_static_attrs = [
"_static_attrs", # A bug as of Oct 10 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

specifically, for some reason one needs static_attrs included in the static_attrs or one will get JAX errors. This seems unintuitive but is empirically what we've found

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Should I keep this? I don't need to do anything about this right?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_static_attrs is already included couple lines below. You probably had to add it before we added in master.

Comment thread docs/index.rst
notebooks/tutorials/coil_optimization_QUADCOIL.ipynb
notebooks/tutorials/omnigenity.ipynb
notebooks/tutorials/bootstrap_current.ipynb
notebooks/tutorials/coil_stage_two_optimization.ipynb

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also need to add QUADCOIL stuff to the API, I think the file is https://github.com/PlasmaControl/DESC/blob/master/docs/api_objectives.rst?plain=1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

add the objectives, I mean

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added!
9faf614

Comment thread desc/objectives/_quadcoil.py Outdated
targets for each objective terms individually besides using ``target``
and ``bounds`` that comes with other DESC objectives.
Targets of each property. 0 by default.
metric_weight : scalar or ndarray, default=None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

1.0 not None

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Should be fixed now.

return Bnormal


# Flipping the current potential of a quadcoil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what are the different coordinate directions/representations/normalizations used? it woudl be good to list these all somewhere, even if just as a comment or in this PR, of the differences in convention btwn DESC and QUADCOIL. Just so we have it clearly down somewhere. Seems phi is flipped?

return phi_swapped


def _quadcoil_phi_to_desc_phi(phi_mn_quadcoil, stellsym, mpol, ntor):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is converting just phi_mn quadcoil to desc?

return Phi_mn, modes_M, modes_N


def _create_source(eq, source_grid, eval_grid):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this could be placed perhaps into the integrals/singularities.py or in a util file there. Also could be used again inside of the other places like BoundaryError. I think it is a reasonable util function, with perhaps some additional documentation attached to it

return (source_profiles, source_transforms, interpolator)


def _quadcoil_kwargs_to_field_kwargs( # noqa: C901

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would be nice to document what is being done here exactly?

Comment thread desc/objectives/_quadcoil.py Outdated
plasma_dofs = jnp.concatenate([rc, rs, zc, zs])
return plasma_dofs

def compute_surface_current_field(self, *all_params):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

instead of compute_surface_current_field maybe something more like run_regcoil or solve_quadcoil_surface_current_field or something is better?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I renamed this to solve_quadcoil_surface_current_field, I also renamed compute_full to solve_quadcoil.

@lankef lankef left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed the comments

@dpanici dpanici requested review from dpanici and f0uriest March 26, 2026 17:45
@rahulgaur104

Copy link
Copy Markdown
Collaborator

qp_nescoil.winding_surface.plot() requires simsopt. Please replace with DESC surface plotter.

@YigitElma YigitElma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks fore adding this!
I will review rest of the code later, but it would be nice if you can resolve the problem with CI tests first. It looks like CI and pip in general cannot install quadcoil, I couldn't install it locally either.

"_use_jit",
]
_static_attrs = [
"_static_attrs", # A bug as of Oct 10 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_static_attrs is already included couple lines below. You probably had to add it before we added in master.

if hasattr(self, "_constants") and ("quad_weights" not in self._constants):
grid = self._constants["transforms"]["grid"]
if self._coordinates == "rtz":
grid = self._constants["transforms"]["grid"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can you revert these changes?

Comment thread desc/plotting.py
else:
im = ax.contourf(xx, yy, data, **contourf_kwargs)
cax = divider.append_axes("right", **cax_kwargs)
_set_tight_layout(fig)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This change is also unnecessary

Comment thread desc/io/optimizable_io.py
def _unmake_hashable(x):
# turn tuple of ints and shape to ndarray
if isinstance(x, tuple) and x[0] == "ndarray":
if isinstance(x, tuple) and len(x) and x[0] == "ndarray":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think it hurts to have it, but why did you need this change?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The default values for some QUADCOIL arguments are empty list/tuples (I had to do it because the adjoint derivative is wrapped in a vjp rule before handed to DESC and this way some flows are simpler). Without this check it breaks DESC Optimizable.

qicna @ git+https://github.com/rogeriojorge/pyQIC/
qsc <= 0.1.3
shapely >= 1.8.2, <= 2.1.2
quadcoil >= 0.1.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry if I am missing but is quadcoil a pip package? I coulnd't find it on PyPI and our CI cannot install it and the tests don't run because of that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It wasn't but now it is!

Comment thread desc/geometry/curve.py Outdated
@@ -0,0 +1,916 @@
import warnings

from jax import jit

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
from jax import jit
from desc.backend import jit

Comment thread desc/objectives/_quadcoil.py Outdated
metric_name,
value_only,
verbose,
plasma_M_theta : int, optional, default=eq.M_grid

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We usually give defaults at the end of the description

Comment thread desc/objectives/_quadcoil.py Outdated
bs_chunk_size : int, optional, default=None
Size to split Biot-Savart computation into chunks of evaluation points.

Attributes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These don't seem necessary

Comment thread desc/objectives/_quadcoil.py Outdated
# we set them here. This is necessary because we are calling quadcoil
# through quadcoil.io.quadcoil_for_diff, which cannot see their default
# values in quadcoil.quadcoil.
if "net_toroidal_current_amperes" in quadcoil_kwargs.keys():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can replace all if/else statements by something = dict.pop("something", default)

if not plasma_N_phi:
plasma_N_phi = eq.N_grid
elif plasma_N_phi <= eq.N_grid:
warnings.warn(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as above

Comment thread desc/objectives/_quadcoil.py Outdated
self._quadcoil_for_diff = jit(_quadcoil_for_diff)
self._quadcoil_values = jit(_quadcoil_values)
# ----- Setting and registering keyword arguments -----
self._static_attrs = _Objective._static_attrs + [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can move this to line 165, right after docs. We usually define it there

Comment thread desc/objectives/_quadcoil.py Outdated
"_field_fixed",
"_bs_chunk_size",
# Basics
"_static_attrs",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_Objective._static_attrs have the _static_attrs and most of these other keys in it. You can remove them safely

from functools import partial

import numpy as np
from jax import jit

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
from jax import jit
from desc.backend import jit

Comment thread desc/objectives/_quadcoil.py Outdated
"""Computes the scalar value of the QUADCOIL proxy.

Computes the scalar value of the QUADCOIL proxy. A wrapper for
``compute_full``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do you have a wrapper here? And what is compute_full? You actually pass full_mode=False.

lankef and others added 5 commits May 2, 2026 13:22
Co-authored-by: Yigit Gunsur Elmacioglu <102380275+YigitElma@users.noreply.github.com>
…dict popping with defaults using pop(default=**) to improve readability
@YigitElma

Copy link
Copy Markdown
Collaborator

@lankef https://github.com/PlasmaControl/DESC/actions/runs/25258105132/job/74060745121?pr=2110 this error happens if you commit a notebook from cluster or after running notebook in Jupyter Lab session (tbh it is very annoying and we have a long standing issue about this #1215, but it is actually not our problem but nbmake or jupyterlab's). You can check this related comment #1498 (comment) for fix. Basically change kernel name of the notebook metadata or just open/save (without running it) on VS code extension.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants