QUADCOIL API
Subpackages
Submodules
quadcoil.math_utils module
quadcoil.quadcoil module
quadcoil.quadcoil_params module
- class quadcoil.quadcoil_params.QuadcoilParams(plasma_surface, winding_surface, net_poloidal_current_amperes: float, net_toroidal_current_amperes: float, Bnormal_plasma=None, mpol=4, ntor=4, quadpoints_phi=None, quadpoints_theta=None, stellsym=None)
Bases:
_ParamsA class storing all informations required to solve a quadcoil problem. These includes plasma info, winding surface info, but does not include problem-specific info such as objectives, constraints or solutions. This class is primarily intended as a concise way to pass information into objective functions. It allows functions in
quadcoil.objectiveto have the same signature, despite requiring different info to calculate.- Parameters:
plasma_surface (SurfaceRZFourierJAX) – The plasma surface.
winding_surface (SurfaceRZFourierJAX) – The winding surface. Must have all field periods.
net_poloidal_current_amperes (float) – The net poloidal current.
net_toroidal_current_amperes (float) – The net toroidal current.
Bnormal_plasma (ndarray, shape (nphi, ntheta), optional, default=None) – The magnetic field distribution on the plasma surface.
mpol (int, optional, default=4) – The number of poloidal Fourier harmonics in the current potential \(\Phi_{sv}\).
ntor (int, optional, default=4) – The number of toroidal Fourier harmonics in \(\Phi_{sv}\).
quadpoints_phi (ndarray, shape (nphi,), optional, default=None) – The toroidal quadrature points to evaluate quantities at. Takes one field period from the winding surface by default.
quadpoints_theta (ndarray, shape (ntheta,), optional, default=None) – The poloidal quadrature points to evaluate quantities at. Takes the winding surface’s quadrature points by default.
- plasma_surface
(Traced) The plasma surface.
- Type:
- winding_surface
(Traced) The winding surface. Must have all field periods.
- Type:
- eval_surface
(Traced) The evaluation surface. Has the same dofs as the winding surface, but uses the quadrature points given by
self.quadpoints_phiandself.quadpoints_phi.- Type:
- net_poloidal_current_amperes
(Traced) The net poloidal current.
- Type:
float
- net_toroidal_current_amperes
(Traced) The net toroidal current.
- Type:
float
- Bnormal_plasma
(Traced) The magnetic field distribution on the plasma surface. will be filled with zeros by default.
- Type:
ndarray, shape (nphi, ntheta)
- quadpoints_phi
(Traced) The toroidal quadrature points to evaluate quantities at.
- Type:
ndarray, shape (nphi,)
- quadpoints_theta
(Traced) The poloidal quadrature points to evaluate quantities at.
- Type:
ndarray, shape (ntheta,)
- nfp
(Static) The number of field periods.
- Type:
int
- stellsym
(Static) Stellarator symmetry.
- Type:
bool
- mpol
(Static) The number of poloidal Fourier harmonics in \(\Phi_{sv}\).
- Type:
int
- ntor
(Static) The number of toroidal Fourier harmonics in \(\Phi_{sv}\).
- Type:
int
- ndofs
(Static) The number of degrees of freedom in \(\Phi_{sv}\).
- Type:
int
- ndofs_half
(Static)
ndofifstellsym==True,ndof//2otherwise.- Type:
int
- Kdash_helper()
- change_phi_resolution()
- diff_helper()
- make_mn()
- make_mn_helper()
- tree_flatten()
- tree_unflatten()
quadcoil.solver module
quadcoil.surface module
- class quadcoil.surface.SurfaceJAX(nfp: int, stellsym: bool, mpol: int, ntor: int, quadpoints_phi: jax.numpy.ndarray, quadpoints_theta: jax.numpy.ndarray, dofs: jax.numpy.ndarray)[source]
Bases:
objectAbstract base class for JAX-native toroidal surfaces.
Subclasses must implement
gammadash()and register themselves as JAX pytrees. All geometric quantities (normals, curvatures, etc.) are derived fromgammadashand defined here so that every concrete surface type shares the same interface without code duplication.- quadpoints_phi, quadpoints_theta
Quadrature grid in [0, 1).
- Type:
jnp.ndarray, shape (nphi,) / (ntheta,)
- phi_mesh, theta_mesh
Meshgrid counterparts (phi varies along axis-0).
- Type:
jnp.ndarray, shape (nphi, ntheta)
- dphi, dtheta
Grid spacings.
- Type:
float
- area()
- dga_inv_n_dashb()
Derivatives of (1/|N|) * (dγ/dphi) and (1/|N|) * (dγ/dtheta).
- Returns:
- dg2_inv_n_dash1, dg2_inv_n_dash2)
Each of shape (nphi, ntheta, 3).
- Return type:
(dg1_inv_n_dash1, dg1_inv_n_dash2,
- classmethod dof_to_gamma(dofs, phi_grid, theta_grid, nfp, stellsym, dash1_order=0, dash2_order=0, mpol: int = 10, ntor: int = 10)[source]
Map DOF vector to gamma (or derivatives) on the quadrature grid.
- first_fund_form()
First fundamental form [E, F, G], shape (nphi, ntheta, 3).
- classmethod fit(phi_target, theta_target, gamma_target, nfp: int, stellsym: bool, quadpoints_phi, quadpoints_theta, mpol: int = 7, ntor: int = 7, lam_tikhonov=0.0, custom_weight=None)
- gamma()
- gamma_at_point(phi, theta)
- gammadash(a: int, b: int) jax.numpy.ndarray
Surface position or mixed partial derivative.
- Parameters:
a (int) – Order of the phi derivative (0, 1, or 2).
b (int) – Order of the theta derivative (0, 1, or 2).
- Returns:
The quantity
d^(a+b) gamma / d phi^a d theta^bevaluated on the quadrature grid. Derivatives are with respect to the normalised angles in [0, 1).- Return type:
jnp.ndarray, shape (nphi, ntheta, 3)
Notes
Forwards to
gammadash_at_point()with the separable meshgrid formphi=quadpoints_phi[:, None],theta=quadpoints_theta[None, :]. This is strictly a faster backend than the historical_dof_to_gamma_op @ dofspath (5x-17x for RZ/XYZ Fourier surfaces, 30x-40x for the separable XYZ tensor Fourier surface) and produces output that matches the operator approach to round-off. The operator (cls._dof_to_gamma_op) remains available and is still used internally by_fit_dofs_from_gamma.
- gammadash1()
- gammadash1_at_point(phi, theta)
- gammadash1dash1()
- gammadash1dash1_at_point(phi, theta)
- gammadash1dash2()
- gammadash1dash2_at_point(phi, theta)
- gammadash2()
- gammadash2_at_point(phi, theta)
- gammadash2dash2()
- gammadash2dash2_at_point(phi, theta)
- gammadash_at_point(phi, theta, a: int, b: int) jax.numpy.ndarray
Broadcastable evaluation of
d^(a+b) gamma / dphi^a dtheta^b.Unlike
gammadash(), this never materialises the(nphi, ntheta, 3, ndof)operator: it computes the basis functions at the requested(phi, theta)points and contracts directly againstself.dofs.- Parameters:
phi (jnp.ndarray) – Broadcast-compatible arrays of normalised angles in
[0, 1).theta (jnp.ndarray) – Broadcast-compatible arrays of normalised angles in
[0, 1).a (int) – Order of the phi derivative (0, 1, 2, or 3).
b (int) – Order of the theta derivative (0, 1, 2, or 3).
- Returns:
The mixed partial of gamma at each requested point.
- Return type:
jnp.ndarray, shape
broadcast(phi, theta).shape + (3,)
Notes
Reproduces
self.gammadash(a, b)bit-for-bit when called with the fully expanded meshgridphi = quadpoints_phi[:, None] + 0 * quadpoints_theta[None, :],theta = quadpoints_theta[None, :] + 0 * quadpoints_phi[:, None].Subclasses must override this method.
- gen_winding_surface(d_expand, mpol=7, ntor=7, phi_interp: int = 2, theta_interp: int = 5, theta_rule_subsample: int = None, quadpoints_phi=None, quadpoints_theta=None, lam_tikhonov=1e-05, winding_surface_mode='self-intersection', theta_mode='arclen', weight_mode=None)
Generate a smoothed winding surface from a uniform normal offset.
- Parameters:
d_expand (float) – Normal offset distance.
mpol (int) – Fourier resolution of the fitted surface.
ntor (int) – Fourier resolution of the fitted surface.
phi_interp (int)
theta_interp (int) – Toroidal/poloidal oversampling factor over
self.quadpoints_thetaduring the Fourier fit. higher values leads to more accurate offset surfaces.theta_rule_subsample (int) – Poloidal subsampling stride for removing self-intersection relative to
len(self.quadpoints_theta) * theta_interp. This is necessary because the complexity of the self-intersection check is O(len(self.quadpoints_theta)**2). The default value istheta_interp.quadpoints_phi (array or None) – Quadrature points for the output surface. Defaults to
jnp.linspace(0, 1, self.nfp*len(self.quadpoints_phi), endpoint=False)andjnp.linspace(0, 1, len(self.quadpoints_theta), endpoint=False).quadpoints_theta (array or None) – Quadrature points for the output surface. Defaults to
jnp.linspace(0, 1, self.nfp*len(self.quadpoints_phi), endpoint=False)andjnp.linspace(0, 1, len(self.quadpoints_theta), endpoint=False).lam_tikhonov (float) – Tikhonov regularization weight for least-square surface fit.
winding_surface_mode ({'uniform', 'self-intersection'}) – Type of winding surface to generate.
theta_mode ({'arclen', 'arctan'}) – Function to use for assigning poloidal parameterization.
'arctan'uses arctan on poloidal section, using the center-of-weight of the plasma poloidal cross sections as origin.'arclen'uses the arc lengths of the winding surface’s poloidal cross sections.'arctan'generates smoother surfaces, but can misbehave when the winding surface is concave.'arctan'is robust for concave winding surfaces but less smooth.weight_mode ({'poloidal_arclen', 'area'} or None) – How to weight fit points. Can be based on arclength of poloidal cross sections or the surface jacobian.
'poloidal_arclen'behaves better for compact configurations.'area'behaves better for long-aspect ratio, helical configurations.
- Returns:
fit_surface – Fitted winding surface with the given quadrature points.
- Return type:
same type as
self
- gen_winding_surface_dofs(d_expand, mpol=7, ntor=7, phi_interp: int = 2, theta_interp: int = 5, theta_rule_subsample: int = 5, lam_tikhonov=1e-05, winding_surface_mode='self-intersection', theta_mode='arclen', weight_mode=None)
Fit winding surface DOFs from a uniform normal offset of this surface.
Generates an exact offset surface, bisects poloidal slices onto best-fit planes, computes arc-length or arc-tan parameterisation, removes self-intersecting regions, and solves a weighted least-squares fit. Please see
SurfaceJAX.gen_winding_surface()for parameters.- Returns:
dofs – DOF vector for the fitted surface (same layout as
self.dofs).- Return type:
jnp.ndarray
- grad_helper()
Contravariant vectors grad-phi and grad-theta.
- Return type:
(grad1, grad2) each of shape (nphi, ntheta, 3)
- integrate(scalar_field)
Integrate a scalar field over the surface.
- normal()
- second_fund_form()
Second fundamental form [e, f, g], shape (nphi, ntheta, 3).
- surface_curvatures()
Mean (H), Gaussian (K), and principal (κ₁, κ₂) curvatures.
- Returns:
Stacked [H, K, kappa1, kappa2].
- Return type:
jnp.ndarray, shape (nphi, ntheta, 4)
- unitnormal()
- unitnormal_at_point(phi, theta) jax.numpy.ndarray[source]
Convenience:
unitnormaldash_at_point(phi, theta, 0, 0).
- unitnormaldash(a: int, b: int) jax.numpy.ndarray
Compute d^(a+b)(unitnormal) / dphi^a dtheta^b using autodiff.
Uses nested automatic differentiation for arbitrary-order derivatives.
- Parameters:
a (int) – Order of derivative with respect to phi
b (int) – Order of derivative with respect to theta
- Returns:
The derivative d^(a+b)(unitnormal) / dphi^a dtheta^b
- Return type:
jnp.ndarray, shape (nphi, ntheta, 3)
Examples
>>> surf.unitnormaldash(0, 0) # Returns unitnormal >>> surf.unitnormaldash(1, 0) # Returns d(unitnormal)/dphi >>> surf.unitnormaldash(0, 1) # Returns d(unitnormal)/dtheta >>> surf.unitnormaldash(2, 0) # Returns d²(unitnormal)/dphi²
- unitnormaldash_at_point(phi, theta, a: int, b: int) jax.numpy.ndarray
Broadcastable mixed derivative of the unit normal at arbitrary points.
Built on top of
gammadash_at_point()and JAX forward-mode autodiff (vmapof repeatedjacfwdover scalar phi/theta). Output shape:broadcast(phi, theta).shape + (3,).Reproduces
self.unitnormaldash(a, b)when called with the fully-expanded meshgridphi=quadpoints_phi[:, None] + 0*theta_1d,theta=quadpoints_theta[None, :] + 0*phi_1d.- Parameters:
phi (jnp.ndarray) – Broadcast-compatible normalised angles in
[0, 1).theta (jnp.ndarray) – Broadcast-compatible normalised angles in
[0, 1).a (int) – Order of the phi / theta derivative.
b (int) – Order of the phi / theta derivative.
Notes
For
(a, b) == (0, 0)we take an analytic fast path that evaluatescross(gammadash1_at_point, gammadash2_at_point) / normdirectly on the broadcasted arrays (no autodiff, no vmap). For all other orders we vmap a per-point scalar function and applyjacfwda + btimes.
- unitnormaldash_legacy()
d(unitnormal)/dphi and d(unitnormal)/dtheta (legacy hard-coded implementation).
This is the original hard-coded gradient implementation, preserved for performance comparison. For production code, use unitnormaldash(a, b) instead.
- Return type:
(unitnormaldash1, unitnormaldash2), each (nphi, ntheta, 3)
- class quadcoil.surface.SurfaceOffsetJAX(base_surface, d_expand, quadpoints_phi=None, quadpoints_theta=None)
Bases:
SurfaceJAXSubclass that applies a fixed normal offset to any SurfaceJAX subclass.
This class inherits from SurfaceJAX and offsets all geometric quantities by a fixed distance along the surface normal. It maintains the full SurfaceJAX interface and can be used anywhere a SurfaceJAX is expected.
- Parameters:
base_surface (SurfaceJAX) – The underlying surface to offset.
d_expand (float) – Distance to offset along the unit normal (positive = outward).
Examples
>>> plasma_surf = SurfaceRZFourierJAX(...) >>> winding_surf = SurfaceOffsetJAX(plasma_surf, d_expand=0.2) >>> gamma_offset = winding_surf.gamma() >>> isinstance(winding_surf, SurfaceJAX) # Returns True True
Notes
DOF-related methods (e.g., get_dofs(), from_simsopt(), _fit_dofs_from_gamma()) raise NotImplementedError since offset surfaces don’t have independent DOFs.
- copy_and_set_quadpoints()
- dof_to_gamma()
- from_simsopt()
- gammadash()
- gammadash_at_point()
- gen_winding_surface()
- gen_winding_surface_dofs()
- get_dofs()
- plot()
- to_simsopt()
- tree_flatten()
- tree_unflatten()
- uniform_offset()
- class quadcoil.surface.SurfaceRZFourierJAX(nfp: int, stellsym: bool, mpol: int, ntor: int, quadpoints_phi: jax.numpy.ndarray, quadpoints_theta: jax.numpy.ndarray, dofs: jax.numpy.ndarray)
Bases:
SurfaceJAXJAX-native surface in cylindrical Fourier (RZ) coordinates.
Representation:
r(phi, theta) = sum_{m,n} [rc_{mn} cos(m*theta - nfp*n*phi) + rs_{mn} sin(m*theta - nfp*n*phi)] z(phi, theta) = sum_{m,n} [zc_{mn} cos(m*theta - nfp*n*phi) + zs_{mn} sin(m*theta - nfp*n*phi)]
The DOF vector is
[rc, zs]for stellarator-symmetric surfaces and[rc, rs, zc, zs]otherwise, matching simsopt’s convention exactly.- dof_to_rz_op()
- from_desc()
- from_simsopt()
- gammadash_at_point()
- to_desc()
- to_simsopt()
- tree_flatten()
- tree_unflatten()
- class quadcoil.surface.SurfaceXYZFourierJAX(nfp: int, stellsym: bool, mpol: int, ntor: int, quadpoints_phi: jax.numpy.ndarray, quadpoints_theta: jax.numpy.ndarray, dofs: jax.numpy.ndarray)
Bases:
SurfaceJAXJAX-native surface in Cartesian Fourier (XYZ) coordinates.
Matches
simsopt.geo.SurfaceXYZFourierexactly.Representation:
x_hat(phi, theta) = sum_{m,n} [xc_{mn} cos(m*theta - nfp*n*phi) + xs_{mn} sin(m*theta - nfp*n*phi)] y_hat(phi, theta) = sum_{m,n} [yc_{mn} cos(m*theta - nfp*n*phi) + ys_{mn} sin(m*theta - nfp*n*phi)] z(phi, theta) = sum_{m,n} [zc_{mn} cos(m*theta - nfp*n*phi) + zs_{mn} sin(m*theta - nfp*n*phi)] x = x_hat * cos(2*pi*phi) - y_hat * sin(2*pi*phi) y = x_hat * sin(2*pi*phi) + y_hat * cos(2*pi*phi)
Under stellarator symmetry the
xs,yc, andzcterms are zero.The DOF vector is
[xc, ys, zs]for stellarator-symmetric surfaces and[xc, xs, yc, ys, zc, zs]otherwise, matching simsopt’s convention exactly. The (m, n) mode indexing followsmake_rzfourier_mc_ms_nc_ns().- dof_to_xhatz_op()
- from_simsopt()
- gammadash_at_point()
- to_simsopt()
- tree_flatten()
- tree_unflatten()
- class quadcoil.surface.SurfaceXYZTensorFourierJAX(nfp: int, stellsym: bool, mpol: int, ntor: int, quadpoints_phi: jax.numpy.ndarray, quadpoints_theta: jax.numpy.ndarray, dofs: jax.numpy.ndarray)
Bases:
SurfaceJAXJAX-native surface in Cartesian tensor-product Fourier coordinates.
Matches
simsopt.geo.SurfaceXYZTensorFourierexactly.Representation:
x_hat(theta, phi) = sum_{i,j} x_{ij} w_i(theta) v_j(phi) y_hat(theta, phi) = sum_{i,j} y_{ij} w_i(theta) v_j(phi) x(phi, theta) = x_hat * cos(phi_rad) - y_hat * sin(phi_rad) y(phi, theta) = x_hat * sin(phi_rad) + y_hat * cos(phi_rad) z(theta, phi) = sum_{i,j} z_{ij} w_i(theta) v_j(phi)
where
phi_rad = 2*pi*phi_normalised, and the toroidal basis is:v_j : j=0..ntor -> cos(nfp*j*phi_rad) j=ntor+1..2*ntor -> sin(nfp*(j-ntor)*phi_rad)
and the poloidal basis is:
w_i : i=0..mpol -> cos(i*theta_rad) i=mpol+1..2*mpol -> sin((i-mpol)*theta_rad)
The DOF vector is
[x_active, y_active, z_active]where the active coefficients follow simsopt’sget_dofs()ordering (row-major over (m, n), skipping stellarator-symmetric zeros).Stellarator symmetry rules
x: keep
(n <= ntor and m <= mpol)OR(n > ntor and m > mpol)y, z: keep
(n <= ntor and m > mpol)OR(n > ntor and m <= mpol)
- param nfp:
- type nfp:
int
- param stellsym:
- type stellsym:
bool
- param mpol:
- type mpol:
int
- param ntor:
- type ntor:
int
- param quadpoints_phi:
- type quadpoints_phi:
array-like 1-D, values in [0, 1)
- param quadpoints_theta:
- type quadpoints_theta:
array-like 1-D, values in [0, 1)
- param dofs:
Active Fourier coefficients in simsopt ordering.
- type dofs:
1-D array
- from_simsopt()
- gammadash_at_point()
- num_dofs()
- to_RZFourier()
- to_simsopt()
- tree_flatten()
- tree_unflatten()
- quadcoil.surface.make_rzfourier_mc_ms_nc_ns(mpol: int, ntor: int)
- quadcoil.surface.xyztensor_gammadash(dofs, quadpoints_phi, quadpoints_theta, nfp: int, stellsym: bool, a: int, b: int, mpol: int, ntor: int)
Compute
d^(a+b) gamma / dphi^a dtheta^bfor XYZ tensor Fourier surface.Uses the Leibniz product rule to differentiate
x = x_hat * cos(phi_rad) - y_hat * sin(phi_rad)and similarly for y, then combines with the theta derivative contained in the W basis.- Parameters:
dofs (1-D jax array)
quadpoints_phi (1-D jax arrays in [0, 1))
quadpoints_theta (1-D jax arrays in [0, 1))
nfp (static)
stellsym (static)
a (static)
b (static)
mpol (static)
ntor (static)
- Return type:
jnp.ndarray, shape (nphi, ntheta, 3)