Skip to content

En/review eg/coil perturbations fix#37

Merged
eduardolneto merged 7 commits into
eg/analysisfrom
en/review_eg/coil_perturbations_fix
Jul 7, 2026
Merged

En/review eg/coil perturbations fix#37
eduardolneto merged 7 commits into
eg/analysisfrom
en/review_eg/coil_perturbations_fix

Conversation

@eduardolneto

Copy link
Copy Markdown
Contributor

Changing coil_perturbation and coils for coil perturbation and stochastic optimization functionalities to comply with custom_losses

…stic optimization functionalities to comply with custom_losses
@eduardolneto eduardolneto changed the base branch from main to eg/analysis June 27, 2026 14:29

@EstevaoMGomes EstevaoMGomes 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.

This PR does not seem master-ready. More exactly, the scaling implementation is an important feat, but it needs to be implemented properly.

Comment thread essos/coil_perturbation.py Outdated
return

raise TypeError(f"Unsupported type {type(curves)}. Expected Curves, Coils, or CoilsFromGamma.")
#return curves

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.

Remove

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread essos/coil_perturbation.py Outdated
perturbed_base_gamma = base_gamma + perturbation[:, 0, :, :]
dofs_new, _ = fit_dofs_from_coils(perturbed_base_gamma, curves.order, curves.n_segments,assume_uniform=True)
curves.dofs = dofs_new
return

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 thought the source code was implemented in a simpler way, like:

dofs, skeleton = ravel_pytree(curves)
new_dofs = dofs + perturbation
new_curves = skeleton(new_dofs)

where perturbation is a vector with the same shape as dofs.

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.

Btw, this creates a new object instead of altering the last one in-place. I think this is better.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@EstevaoMGomes I am not sure what you want with this comment. the purturbation is usually in cosntruction of the coils, so it is usually given in terms of the gamma quantity and not in fourier coeficients. if the instance is curves_from_Gammas it does what you are suggesting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

Comment thread essos/coil_perturbation.py Outdated


def perturb_curves_statistic(curves: Curves,sampler:GaussianSampler, key=None):
def perturb_curves_statistic(curves, sampler:GaussianSampler, key=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.

This works, but since both functions have similar implementations, wouldn't it make more sense to add another input to indicate whether the perturbation is statistical or systematic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread essos/coils.py Outdated
stellsym: bool = True):
stellsym: bool = True,
scaling_type: int = 2,
scaling_factor: float = 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.

Set 0.0 as it is a float

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread essos/coils.py Outdated
self._nfp = nfp
self._stellsym = stellsym

self._scaling_type = scaling_type # 1 for L-1 norm, 2 for L-2 norm, jnp.inf for L-infinity norm

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.

Maybe should be -1 for L-infinity

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can do that but I woudl think this is more confusing for the user? Surfaces.py was implemented like this as well, which I think is because the options used in jnp.linalg.norm functions are also these ones and not -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.

If it is well documented, it is not confusing. Using jnp.inf may lead to problems as not all infinities are the same (e.g., np.inf vs jnp.inf)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread essos/coils.py
@property
def dofs(self):
return jnp.array(self._dofs)
# Apply scaling to each coordinate (X, Y, Z) independently

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 makes no sense. When using the new loss class, the dofs that are used are those that form the PyTree, i.e., self._dofs. When you scale only the dofs property, this is only useful for the old optimizations. I think the scaling is a good addition, but as it is implemented, it should not be merged.
Furthermore, it seems overcomplicated for the user.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not sure I understand the problem here. Both the property and setter are scaled, as it is also done on surfaces.py, which was already implemented.

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.

The property is different from the attribute. _tree_flatten() gets the property, not the attribute. The scaling is just wrong as is.

@eduardolneto eduardolneto Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I understand that. But this is exactly what previous implemented scalling of surfaces.py do as well, which is what this is mimicking. @EstevaoMGomes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since that behaviour for surfaces comes from the original branch. Do you want to change both here or chnag eit after this has been pulled to the original branch?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread essos/coils.py
# Allow downstream code (e.g. coil_perturbation) to override the
# computed gamma; otherwise compute from Fourier coefficients.
if self._gamma is not None:
return self._gamma

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 is the reason for removing the delayed initialization? I am becoming more and more confused with these changes...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addresed, only discretized coils are eager because they are dofs

Comment thread essos/coils.py Outdated
return Curves(curves, n_segments=n_segments, nfp=nfp, stellsym=stellsym)
return Curves(curves, n_segments=n_segments, nfp=nfp, stellsym=stellsym, scaling_type=scaling_type, scaling_factor=scaling_factor, scale_fixed=scale_fixed)

def extract_axis_from_surface(surface, n_samples: int = 200):

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.

Shouldn't this be in surface.py as a method of the class?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread essos/coils.py Outdated

return axis_gamma

def CreateCoilsAroundAxis(n_coils: int,

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 am not saying this is wrong but the code looks way too complex for what it does...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread essos/coils.py Outdated
return dofs, gamma_uni No newline at end of file
return dofs, gamma_uni

class CoilsFromGamma:

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.

Just so I know, is this supposed to be an optimizable or not?

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 see it is registered as a pytree, so I suppose so. Then what are the dofs? Is it all the gammas?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@rogeriojorge rogeriojorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A couple things I suggest:

  • Remove unused variables such as old_scaling in the order setter and R_axis in the surface Frenet-frame helper.

  • Curves use self._dofs, but Coils.to_simsopt() iterates over self.dofs_curves, which goes through the scaled Curves.dofs property. That can make exported SIMSOPT coils differ from the actual geometry when scaling is active.

  • Curves.dofs now returns scaled DOFs, but _tree_flatten() still exposes self._dofs, so custom_losses/JAX tree operations will see the unscaled variables, not the scaled public dofs. Also, scale_fixed is not included in the PyTree aux data, and scaling_type is stored but not actually used when building self.scaling.

Comment on lines -211 to -212
This means taht an independent perturbation is applied to the each unique coil
Then, the required symmetries are applied to the perturbed unique set of coils

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I actually liked these descriptions better, "systematic" is not well defined and this defined it pretty well

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread essos/coils.py
Comment on lines 164 to -187
@@ -177,14 +227,8 @@ def create_data(order: int) -> jnp.ndarray:
# gamma_dashdash property
@property
def gamma_dashdash(self):
if self._gamma_dashdash is not None:
return self._gamma_dashdash
return self._compute_gamma_dashdash()

@gamma_dashdash.setter
def gamma_dashdash(self, value):
self._gamma_dashdash = value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks important

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addresed

Comment thread essos/coils.py Outdated

@classmethod
def from_simsopt(cls, simsopt_curves, nfp=1, stellsym=True):
def from_simsopt(cls, simsopt_curves, nfp=1, stellsym=True, scaling_type=2, scaling_factor=0.0):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure what scaling type is? Perhaps should be given in the docstring

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread essos/coils.py Outdated

return axis_gamma

def CreateCoilsAroundAxis(n_coils: int,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems like something that would help with pyQSC, when optimizing for coils with the near-axis approach. But I would probably move this for a different PR, unless it is really needed for the coils perturbation PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

…cretizedCoils. Removed fucntion to intialize coils frtom axis which will added innstead in future pull request together with other implementations.
…fty or -1. Adding docstrings to clealry state the scalling. Fixing the unflatten and flatten definitions for the surface pytree. So that custom_losses now see the scaled dofs, and unflatten unscales the dofs. This should make custom_losses take gradients against scaled variables while loss values will be still obtained by applying the loss function to the unscaled dofs-
…asses. Note that s fixed scaled option is added and explained on docstrings. In order to also have the intended behaviour in custom_losses/tree flatten/ unflatten interaction (that is loss(unflatten dofs) but gradient of scaled dofs) the current_scale behaviour had to be slighlty changed. Now it can be given as an input, and if it is None, the current_scale will be calculated at the beggining and kept frozen for the rest of the optimization. This is needed because otherwise the tracing gets confused and gives the wrong gradient.
…and currents dofs consistent with Coils/Curves
@eduardolneto

Copy link
Copy Markdown
Contributor Author

@rogeriojorge @EstevaoMGomes I fixed the scaling dofs behaviour. It is explained in the commits summary. But basically, now the dofs are unscaled consistently during unflatten, and the scaled ones are flatten. This was changed for both surfaces and coils. This means that the loss is calculated with unscaled and the gradients against the scaled ones. For the coils this meant that the current_scale had to be given as an input or, if not, it is calculated at the beginning and frozen. Before it was updated every time, but this would mean that the gradient tracer would see d loss(scaling(sofs) d dofs) which woudl give wrong gradient. DiscretizedCoils now also do the same buit the geometry dofs are the unique gammas which are scaled always to the maximum value of X,Y,Z like currents.

@eduardolneto

Copy link
Copy Markdown
Contributor Author

I think it should be good to merge. And then test more with the examples once merged into #29 .

@rogeriojorge rogeriojorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me

@eduardolneto eduardolneto merged commit ac3ffa4 into eg/analysis Jul 7, 2026
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.

3 participants