Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
871d169
Add new integrator scripts for field tracing and guiding center dynamics
EstevaoMGomes Apr 20, 2025
f86f5ee
Improve loss functions speed
EstevaoMGomes Apr 20, 2025
408c2c5
Refine integrator analysis
EstevaoMGomes Apr 21, 2025
6ff79db
Optimize rotation matrix computation in RotatedCurve function
EstevaoMGomes Apr 27, 2025
fdb1508
Edit fo and gc integration analysis plots
EstevaoMGomes Apr 28, 2025
415bbae
Improve loss functions computational efficiency
EstevaoMGomes Apr 28, 2025
f38fdea
Feature: gradient analysis
EstevaoMGomes Apr 28, 2025
079cdb7
Add block_until_ready to integrators analysis
EstevaoMGomes May 4, 2025
fe5d81d
Minor tweaks to gradient analysis plot
EstevaoMGomes May 4, 2025
9595ab4
Create Poincare Plots analysis
EstevaoMGomes May 4, 2025
108123e
Refactor integrators analysis: add cyclotron frequency calculation an…
EstevaoMGomes May 11, 2025
c80ff2c
Change loss functions to be quadratic & implement separation loss
EstevaoMGomes May 11, 2025
ea0af03
Minor improvements in coils class
EstevaoMGomes May 11, 2025
5617e7f
Add join method to Particles class and optimize tracing parameters in…
EstevaoMGomes May 11, 2025
0bb4462
Simplify curve appending logic in apply_symmetries_to_curves
EstevaoMGomes May 11, 2025
91f8cd0
Update cyclotron frequency calculation and adjust tracing parameters …
EstevaoMGomes May 12, 2025
549a43f
Enhance Particles class with phase angle parameter and update Tracing…
EstevaoMGomes May 12, 2025
23bf271
Refactor coil separation loss function and optimize tracing parameter…
EstevaoMGomes May 12, 2025
dcef625
Tentative to incorporate pytree methods for BiotSavart class
EstevaoMGomes May 12, 2025
3c96714
Refactor Tracing class initialization and improve parameter handling …
EstevaoMGomes May 14, 2025
b1a227e
Add gc_vs_fo.py for particle tracing and visualization; adjust parame…
EstevaoMGomes May 14, 2025
8332e1e
Add output directory creation and update file saving paths in analysi…
EstevaoMGomes May 19, 2025
c64a9c1
Add comparison_coils.py for BiotSavart field analysis and performance…
EstevaoMGomes May 19, 2025
f867857
Fix errors derived from dynamics refactoring
EstevaoMGomes May 21, 2025
7d7be44
Enhance plotting in gc_integrators.py: adjust figure sizes, add toler…
EstevaoMGomes May 21, 2025
53682b6
Change dynamics to accept Integrator name
EstevaoMGomes May 24, 2025
093f11d
Add comparison_gc.py for comparing gc trajectories between SIMSOPT an…
EstevaoMGomes May 24, 2025
1c6584c
Fixed energy calculation for fo trajectories
EstevaoMGomes May 26, 2025
32b84ad
Finalize gc analysis ESSOS vs SIMSOPT
EstevaoMGomes May 26, 2025
f902e22
Add comparison script for fo tracing & improve gc script plots
EstevaoMGomes May 28, 2025
823c6db
Fixed error in dynamics when tracing fieldlines
EstevaoMGomes May 28, 2025
32c36b3
Based on work from PR #19
EstevaoMGomes Jun 6, 2025
798731d
Fix energy calls in integrators analysis
EstevaoMGomes Jun 6, 2025
c3134b9
Fixed loss functions and enhanced coil separation logic
EstevaoMGomes Jun 25, 2025
5a728c0
Fixed coils&surface opt example
EstevaoMGomes Jun 25, 2025
43dd5a0
Added extra simsopt compilation runs
EstevaoMGomes Jun 25, 2025
e6cb9fa
Added field line comparisons & minor improvements to fo and gc compar…
EstevaoMGomes Jun 25, 2025
97f2985
Improve length calculation & refactor gammas to lazy initialization &…
EstevaoMGomes Jun 26, 2025
312dab3
Fix coils.from_simsopt imports
EstevaoMGomes Jun 26, 2025
32fa067
Add loss function comparison with simsopt
EstevaoMGomes Jun 26, 2025
d4bb387
Add surface comparison with simsopt
EstevaoMGomes Jun 26, 2025
00f2160
Refactor surfaces gamma to lazy initialization & add SquaredFlux func…
EstevaoMGomes Jun 26, 2025
9c2407b
Fix Coils.from_ imports
EstevaoMGomes Jun 27, 2025
8975934
Finished surface comparison
EstevaoMGomes Jun 27, 2025
70dcaa6
Finish simsopt comparison & improve analysis plots
EstevaoMGomes Jul 9, 2025
bbf6cc4
Update Ubuntu for workflows
EstevaoMGomes Jul 9, 2025
e844b67
Merge branch 'main' into eg/analysis
EstevaoMGomes Sep 8, 2025
7d807ff
Fixed near-axis & surfaces examples
EstevaoMGomes Sep 22, 2025
9853449
Fixed merge changes
EstevaoMGomes Sep 22, 2025
1395650
Deleting comparison_simsopt folder in examples
EstevaoMGomes Sep 22, 2025
ddb2282
Changing surfaces.py to correct the number of modes used, added optio…
eduardolneto Oct 14, 2025
a0374cc
Fix(coils, fields): turned field BiotSavart into a PyTree and modifie…
EstevaoMGomes Oct 21, 2025
c7378bb
Fix: minor fixes
EstevaoMGomes Oct 27, 2025
d3b073c
Merge branch 'en/surface_modes_hotfix' into eg/analysis
EstevaoMGomes Oct 27, 2025
d40fa9c
Fix(coils): assertion removal in Coils class; Perf(coils): vmap & sep…
EstevaoMGomes Oct 28, 2025
37252ef
Fix(analysis): precompile coil gammas for comparison with simsopt
EstevaoMGomes Oct 28, 2025
df5a525
Fix(surfaces): PyTree able Surfaces
EstevaoMGomes Nov 5, 2025
a4fad74
Fix(fields,surfaces): fixed mpol in vmec import
EstevaoMGomes Nov 5, 2025
ce7caf7
Fix(surfaces): jit and pjit fix
EstevaoMGomes Nov 12, 2025
43fd404
Feat (losses): Create losses files
EstevaoMGomes Nov 17, 2025
ac5c7bc
Fix (losses): Allow for grad usage during optimization
EstevaoMGomes Nov 17, 2025
c9efcf7
Clean (losses): Move example to correct file
EstevaoMGomes Nov 17, 2025
41a0c1a
Refactor (example): Simplify coils_from_vmec_surface example with new…
EstevaoMGomes Nov 17, 2025
2324cfe
Refactor (coils): Decache gamma calculation
EstevaoMGomes Nov 17, 2025
a2af497
refactored examples folder
rogeriojorge Dec 9, 2025
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
549 changes: 353 additions & 196 deletions essos/coils.py

Large diffs are not rendered by default.

128 changes: 67 additions & 61 deletions essos/dynamics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pyexpat import model
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Import of 'model' is not used.

Suggested change
from pyexpat import model

Copilot uses AI. Check for mistakes.
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
Expand Down Expand Up @@ -53,7 +54,7 @@ def compute_orbit_params(xyz, vpar):
class Particles():
def __init__(self, initial_xyz=None, initial_vparallel_over_v=None, charge=ALPHA_PARTICLE_CHARGE,
mass=ALPHA_PARTICLE_MASS, energy=FUSION_ALPHA_PARTICLE_ENERGY, min_vparallel_over_v=-1,
max_vparallel_over_v=1, field=None, initial_vxvyvz=None, initial_xyz_fullorbit=None):
max_vparallel_over_v=1, field=None, initial_vxvyvz=None, initial_xyz_fullorbit=None, phase_angle_full_orbit = 0):
self.charge = charge
self.mass = mass
self.energy = energy
Expand Down Expand Up @@ -85,6 +86,21 @@ def to_full_orbit(self, field):
self.initial_xyz_fullorbit, self.initial_vxvyvz = gc_to_fullorbit(field=field, initial_xyz=self.initial_xyz, initial_vparallel=self.initial_vparallel,
total_speed=self.total_speed, mass=self.mass, charge=self.charge,
phase_angle_full_orbit=self.phase_angle_full_orbit)

def join(self, other, field=None):
assert isinstance(other, Particles), "Cannot join with non-Particles object"
assert self.charge == other.charge, "Cannot join particles with different charges"
assert self.mass == other.mass, "Cannot join particles with different masses"
assert self.energy == other.energy, "Cannot join particles with different energies"

charge = self.charge
mass = self.mass
energy = self.energy
initial_xyz = jnp.concatenate((self.initial_xyz, other.initial_xyz), axis=0)
initial_vparallel_over_v = jnp.concatenate((self.initial_vparallel_over_v, other.initial_vparallel_over_v), axis=0)

return Particles(initial_xyz=initial_xyz, initial_vparallel_over_v=initial_vparallel_over_v, charge=charge, mass=mass, energy=energy, field=field)

@partial(jit, static_argnums=(2))
def GuidingCenterCollisionsDiffusionMu(t,
initial_condition,
Expand Down Expand Up @@ -584,50 +600,6 @@ def condition_BioSavart(t, y, args, **kwargs):

self._trajectories = self.trace()

if self.particles is not None:
self.energy = jnp.zeros((self.particles.nparticles, self.times_to_trace))

if model == 'GuidingCenter' or model == 'GuidingCenterAdaptative' :
@jit
def compute_energy_gc(trajectory):
xyz = trajectory[:, :3]
vpar = trajectory[:, 3]
AbsB = vmap(self.field.AbsB)(xyz)
mu = (self.particles.energy - self.particles.mass * vpar[0]**2 / 2) / AbsB[0]
return self.particles.mass * vpar**2 / 2 + mu * AbsB
self.energy = vmap(compute_energy_gc)(self._trajectories)
elif model == 'GuidingCenterCollisions':
@jit
def compute_energy_gc(trajectory):
return 0.5*self.particles.mass* trajectory[:, 3]**2
self.energy = vmap(compute_energy_gc)(self._trajectories)
elif model == 'GuidingCenterCollisionsMuIto' or model == 'GuidingCenterCollisionsMuFixed' or model == 'GuidingCenterCollisionsMuAdaptative' :
@jit
def compute_energy_gc(trajectory):
xyz = trajectory[:, :3]
vpar = trajectory[:, 3]*SPEED_OF_LIGHT
mu = trajectory[:, 4]*self.particles.mass*SPEED_OF_LIGHT**2
AbsB = vmap(self.field.AbsB)(xyz)
return self.particles.mass * vpar**2 / 2 + mu*AbsB
self.energy = vmap(compute_energy_gc)(self._trajectories)
@jit
def compute_vperp_gc(trajectory):
xyz = trajectory[:, :3]
mu = trajectory[:, 4]*self.particles.mass*SPEED_OF_LIGHT**2
AbsB = vmap(self.field.AbsB)(xyz)
return jnp.sqrt(2.*mu*AbsB/self.particles.mass)
self.vperp_final = vmap(compute_vperp_gc)(self._trajectories)
elif model == 'FullOrbit' or model == 'FullOrbit_Boris' or model == 'FullOrbitCollisions':
@jit
def compute_energy_fo(trajectory):
vxvyvz = trajectory[:, 3:]
return self.particles.mass / 2 * (vxvyvz[:, 0]**2 + vxvyvz[:, 1]**2 + vxvyvz[:, 2]**2)
self.energy = vmap(compute_energy_fo)(self._trajectories)
elif model == 'FieldLine' or model== 'FieldLineAdaptative':
self.energy = jnp.ones((len(initial_conditions), self.times_to_trace))



self.trajectories_xyz = vmap(lambda xyz: vmap(lambda point: self.field.to_xyz(point[:3]))(xyz))(self.trajectories)

if isinstance(field, Vmec):
Expand All @@ -641,11 +613,8 @@ def compute_energy_fo(trajectory):
else:
self.loss_fractions, self.total_particles_lost, self.lost_times = self.loss_fraction_BioSavart(boundary)
else:
self.loss_fractions = None
self.total_particles_lost = None
self.loss_times = None
self.trajectories_xyz = self.trajectories

@partial(jit, static_argnums=(0))
def trace(self):
@jit
def compute_trajectory(initial_condition, particle_key) -> jnp.ndarray:
Expand Down Expand Up @@ -848,7 +817,7 @@ def update_state(state, _):
solver=diffrax.Dopri8(),
args=self.args,
saveat=SaveAt(ts=self.times),
throw=False,
throw=True,
# adjoint=DirectAdjoint(),
progress_meter=self.progress_meter,
max_steps=10000000000,
Expand All @@ -871,15 +840,41 @@ def trajectories(self):
def trajectories(self, value):
self._trajectories = value

def _tree_flatten(self):
children = (self.trajectories,) # arrays / dynamic values
aux_data = {'field': self.field, 'model': self.model} # static values
return (children, aux_data)

@classmethod
def _tree_unflatten(cls, aux_data, children):
return cls(*children, **aux_data)
def energy(self):
assert 'GuidingCenter' in self.model or 'FullOrbit' in self.model, "Energy calculation is only available for GuidingCenter and FullOrbit models"
mass = self.particles.mass

if self.model == 'GuidingCenter' or self.model == 'GuidingCenterAdaptative' or \
self.model == 'GuidingCenterCollisionsMuIto' or self.model == 'GuidingCenterCollisionsMuFixed' or self.model == 'GuidingCenterCollisionsMuAdaptative':
initial_xyz = self.initial_conditions[:, :3]
initial_vparallel = self.initial_conditions[:, 3]
initial_B = vmap(self.field.AbsB)(initial_xyz)
mu_array = (self.particles.energy - 0.5 * mass * jnp.square(initial_vparallel)) / initial_B
def compute_energy(trajectory, mu):
xyz = trajectory[:, :3]
vpar = trajectory[:, 3]
AbsB = vmap(self.field.AbsB)(xyz)
return 0.5 * mass * jnp.square(vpar) + mu * AbsB

energy = vmap(compute_energy)(self.trajectories, mu_array)

elif self.model == 'GuidingCenterCollisions':
def compute_energy(trajectory):
return 0.5 * mass * trajectory[:, 3]**2
energy = vmap(compute_energy)(self.trajectories)

elif self.model == 'FullOrbit':
def compute_energy(trajectory):
vxvyvz = trajectory[:, 3:]
v_squared = jnp.sum(jnp.square(vxvyvz), axis=1)
return 0.5 * mass * v_squared
energy = vmap(compute_energy)(self.trajectories)

elif self.model == 'FieldLine' or self.model == 'FieldLineAdaptative':
energy = jnp.ones((len(self.initial_conditions), self.times_to_trace))

return energy

def to_vtk(self, filename):
try: import numpy as np
except ImportError: raise ImportError("The 'numpy' library is required. Please install it using 'pip install numpy'.")
Expand All @@ -899,7 +894,7 @@ def plot(self, ax=None, show=True, axis_equal=True, n_trajectories_plot=5, **kwa
trajectories_xyz = jnp.array(self.trajectories_xyz)
n_trajectories_plot = jnp.min(jnp.array([n_trajectories_plot, trajectories_xyz.shape[0]]))
for i in random.choice(random.PRNGKey(0), trajectories_xyz.shape[0], (n_trajectories_plot,), replace=False):
ax.plot(trajectories_xyz[i, :, 0], trajectories_xyz[i, :, 1], trajectories_xyz[i, :, 2], linewidth=0.5, **kwargs)
ax.plot(trajectories_xyz[i, :, 0], trajectories_xyz[i, :, 1], trajectories_xyz[i, :, 2], **kwargs)
ax.grid(False)
if axis_equal:
fix_matplotlib_3d(ax)
Expand Down Expand Up @@ -966,7 +961,7 @@ def poincare_plot(self, shifts = [jnp.pi/2], orientation = 'toroidal', length =
"""
Plot Poincare plots using scipy to find the roots of an interpolation. Can take particle trace or field lines.
Args:
shifts (list, optional): Apply a linear shift to dependent data. Default is [0].
shifts (list, optional): Apply a linear shift to dependent data. Default is [pi/2].
orientation (str, optional):
'toroidal' - find time values when toroidal angle = shift [0, 2pi].
'z' - find time values where z coordinate = shift. Default is 'toroidal'.
Expand Down Expand Up @@ -1053,7 +1048,18 @@ def process_trajectory(X_i, Y_i, T_i):
plt.show()

return plotting_data


def _tree_flatten(self):
children = (self.trajectories, self.initial_conditions, self.times) # arrays / dynamic values
aux_data = {'field': self.field, 'electric_field': self.electric_field, 'model': self.model, 'maxtime': self.maxtime, 'timestep': self.timestep,
'rtol': self.rtol, 'atol': self.atol, 'particles': self.particles, 'condition': self.condition, 'tag_gc': self.tag_gc} # static values
return (children, aux_data)

@classmethod
def _tree_unflatten(cls, aux_data, children):
return cls(*children, **aux_data)


tree_util.register_pytree_node(Tracing,
Tracing._tree_flatten,
Tracing._tree_unflatten)
Loading
Loading