Skip to content

En/review eg/coil perturbations fix#37

Open
eduardolneto wants to merge 3 commits into
eg/analysisfrom
en/review_eg/coil_perturbations_fix
Open

En/review eg/coil perturbations fix#37
eduardolneto wants to merge 3 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

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.

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
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)

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?

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

Comment thread essos/coils.py
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?

Comment thread essos/coils.py

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

Comment thread essos/coils.py
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?

@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

Comment thread essos/coils.py

@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

Comment thread essos/coils.py

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.

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