Adding QUADCOIL integration to DESC as an objective#2110
Adding QUADCOIL integration to DESC as an objective#2110
Conversation
…also coming soon.
… 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.
…for merging into main
…for merging into main
…e-commit Merging changes from main into the quadcoil branch to prepare for addition into the main branch
Memory benchmark result| Test Name | %Δ | Master (MB) | PR (MB) | Δ (MB) | Time PR (s) | Time Master (s) |
| -------------------------------------- | ------------ | ------------------ | ------------------ | ------------ | ------------------ | ------------------ |
test_objective_jac_w7x | -1.07 % | 3.954e+03 | 3.912e+03 | -42.27 | 39.45 | 37.44 |
test_proximal_jac_w7x_with_eq_update | 0.78 % | 6.635e+03 | 6.687e+03 | 51.93 | 161.98 | 163.60 |
test_proximal_freeb_jac | -0.39 % | 1.327e+04 | 1.321e+04 | -51.91 | 85.20 | 86.51 |
test_proximal_freeb_jac_blocked | -1.85 % | 7.607e+03 | 7.466e+03 | -141.04 | 75.89 | 74.68 |
test_proximal_freeb_jac_batched | 0.76 % | 7.467e+03 | 7.524e+03 | 56.92 | 74.79 | 74.27 |
test_proximal_jac_ripple | 1.60 % | 3.567e+03 | 3.624e+03 | 56.99 | 68.48 | 66.98 |
test_proximal_jac_ripple_bounce1d | -1.17 % | 3.566e+03 | 3.524e+03 | -41.86 | 79.14 | 80.93 |
test_eq_solve | 1.85 % | 1.993e+03 | 2.030e+03 | 36.78 | 96.32 | 95.31 |For the memory plots, go to the summary of |
f0uriest
left a comment
There was a problem hiding this comment.
Just some initial comments after a first pass, I'll have to understand quadcoil a bit better to give a more detailed review.
Also, you should add quadcoil to devtools/dev-requirements.txt so it will be installed for testing. (and maybe also requirements.txt? Or we can leave it as an optional dependency?)
| "desc.geometry.core.Surface", | ||
| "desc.magnetic_fields._core.MagneticField", | ||
| ], | ||
| "desc.magnetic_fields._quadcoil_field.QuadcoilField": [ |
There was a problem hiding this comment.
Thanks for catching this. It's a WIP class and I've decided to not include it here
| return net_poloidal_current_amperes | ||
|
|
||
|
|
||
| def _ptolemy_identity_rev_precompute(m_1, n_1): |
There was a problem hiding this comment.
I think you can use desc.vmec_utils.ptolemy_linear_transform instead of this. It returns a matrix A such that A@x_desc = x_vmec. You can create the matrix in the build method. See the QuasisymmetryBoozer objective for an example
There was a problem hiding this comment.
I'm already using ptolemy_linear_transform inside this function. It's just a wrapper to reduce some code reusing.
| if "winding_stellsym" in source_kwargs.keys(): | ||
| winding_stellsym = source_kwargs["winding_stellsym"] | ||
| else: | ||
| winding_stellsym = sym_default |
There was a problem hiding this comment.
could be winding_stellsym = source_kwargs.get("winding_stellsym", sym_default)
There was a problem hiding this comment.
Fixed. Also replaced a few other occurrences.
| def __init__( # noqa: C901 | ||
| self, | ||
| eq, | ||
| quadcoil_kwargs, |
There was a problem hiding this comment.
is there some sensible default we can give for quadcoil kwargs?
There was a problem hiding this comment.
I can make it solve the NESCOIL problem by default but it will require a default value for surface_plasma_distance, too. I feel maybe it's sensible to have the user think about it themselves? I've uploaded example ones in the tutorial notebook too.
| name="QUADCOIL Proxy", | ||
| source_grid=None, | ||
| # External coils - no external coils by default | ||
| field=[], |
There was a problem hiding this comment.
lists are mutable so they generally shouldn't be used as default args since it can lead to some weird behavior. Better to use None or an empty tuple
| target = 0 # default target value | ||
| # Uses LSE to smooth non-smooth problems rather than slack variables | ||
| # by default. | ||
| if "smoothing" not in quadcoil_kwargs.keys(): |
There was a problem hiding this comment.
can use quadcoil_kwargs.setdefault("smoothing", "approx")
| else: | ||
| self.net_toroidal_current_amperes = 0.0 | ||
| if "plasma_coil_distance" in quadcoil_kwargs.keys(): | ||
| # A sign flip is necessary here! |
There was a problem hiding this comment.
why sign flip? shouldn't distance always be positive?
There was a problem hiding this comment.
Because simsopt surface and DESC surface have different handedness. QUADCOIL uses the simsopt convention so a sign flip is necessary. I've added more comments here.
| if self._field: | ||
| from desc.magnetic_fields import SumMagneticField | ||
|
|
||
| self._constants["sum_field"] = SumMagneticField(self._field) |
There was a problem hiding this comment.
I think it's better to assume that the user will already give a sum field, rather than a possible list of fields to sum. The general format we've been working with is that field is always assumed to be a single MagneticField object
There was a problem hiding this comment.
We silently allow this in BoundaryError, VacuumBoundaryError and QuadraticFlux as a stopgap before we actually make things work correctly with SumMagneticField being passed in directly... this is just further impetus to actually get that working
It can be an optional dependence. I've just added it to dev-requirements. |
| "_use_jit", | ||
| ] | ||
| _static_attrs = [ | ||
| "_static_attrs", # A bug as of Oct 10 2025 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Should I keep this? I don't need to do anything about this right?
| notebooks/tutorials/coil_optimization_QUADCOIL.ipynb | ||
| notebooks/tutorials/omnigenity.ipynb | ||
| notebooks/tutorials/bootstrap_current.ipynb | ||
| notebooks/tutorials/coil_stage_two_optimization.ipynb |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
add the objectives, I mean
| 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 |
There was a problem hiding this comment.
Should be fixed now.
| return Bnormal | ||
|
|
||
|
|
||
| # Flipping the current potential of a quadcoil |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
this is converting just phi_mn quadcoil to desc?
| return Phi_mn, modes_M, modes_N | ||
|
|
||
|
|
||
| def _create_source(eq, source_grid, eval_grid): |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
would be nice to document what is being done here exactly?
| plasma_dofs = jnp.concatenate([rc, rs, zc, zs]) | ||
| return plasma_dofs | ||
|
|
||
| def compute_surface_current_field(self, *all_params): |
There was a problem hiding this comment.
instead of compute_surface_current_field maybe something more like run_regcoil or solve_quadcoil_surface_current_field or something is better?
There was a problem hiding this comment.
I renamed this to solve_quadcoil_surface_current_field, I also renamed compute_full to solve_quadcoil.
…ield to solve_quadcoil_surface_current_field
…maControl/DESC into ffu/quadcoil-qss-pre-commit
lankef
left a comment
There was a problem hiding this comment.
Addressed the comments
This pull request adds two files under /objective,
_quadcoil.pyand_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.