FPT Topology Control (pyna.control)¶
Note
pyna.control contains the generic FPT topology control framework,
applicable to any area-preserving dynamical system. For toroidal-specific
control helpers (gap response, island control, q-profile), use
pyna.toroidal.control.
Magnetic topology state vector â?observable quantities for control.
This module defines the complete set of observables that characterize
magnetic topology for both tokamaks (axisymmetric) and stellarators (3D).
Observable categories
Boundary : g_i (plasma-wall gap), X/O-point positions, DPm eigenvalues,
DPm eigenvectors, B_pol on LCFS
Core : q-profile, J-iota profile, O-point rotation transform
Derived : connection length L_c, flux expansion f_x, surface fate
- class pyna.control.topology_state.OPointState(R, Z, A_matrix, DPm, DPm_eigenvalues, iota=0.0, q=inf)[source]¶
Bases:
objectState of a magnetic O-point (axis or island centre).
- class pyna.control.topology_state.SurfaceFate(value)[source]¶
Bases:
EnumClassification of a flux surface under perturbation.
- CHAOTIC = 4¶
- DEFORMED = 2¶
- INTACT = 1¶
- ISLAND = 3¶
- UNKNOWN = 5¶
- class pyna.control.topology_state.TopologyState(xpoints=<factory>, opoints=<factory>, gap_gi=<factory>, Bpol_lcfs=None, theta_lcfs=None, q_samples=None, s_samples=None, J_iota_samples=None, surface_fate=<factory>, is_axisymmetric=True, phi_ref=0.0)[source]¶
Bases:
objectComplete magnetic topology state vector.
Contains all observables needed for multi-objective magnetic topology
control. Works for both axisymmetric (tokamak) and 3D (stellarator)
configurations.
Attributes
xpoints : list of XPointState
All X-points (divertor, saddle, â?.
opoints : list of OPointState
All O-points (magnetic axis + island O-points).
gap_gi : dict
Plasma-wall gaps g_i at named monitoring points {name: gap_m}.
Bpol_lcfs : ndarray or None
|B_pol| on LCFS at theta_lcfs grid points.
q_samples : ndarray or None
q(s) at s = s_samples.
J_iota_samples : ndarray or None
Current-density rotation transform at the same s grid.
surface_fate : dict
{psi_norm: SurfaceFate} for monitored flux surfaces.
is_axisymmetric : bool
If True use closed-form FPT; otherwise integrate along Ï.
phi_ref : float
Reference toroidal angle for the Poincaré section (default 0).
- opoints: List[OPointState]¶
- surface_fate: Dict[float, SurfaceFate]¶
- to_vector(keys=None)[source]¶
Flatten state into a 1-D numpy array for optimisation.
Returns
values : ndarray, shape (n_obs,)
labels : list of str, length n_obs
- xpoints: List[XPointState]¶
- class pyna.control.topology_state.XPointState(R, Z, A_matrix, DPm, DPm_eigenvalues, DPm_eigenvectors, D2Pm=None, D3Pm=None, Greene_residue=0.0)[source]¶
Bases:
objectState of a divertor X-point.
- property connection_length_scale: float¶
Inverse of max log-eigenvalue â?proportional to L_c near X-point.
When eigenvalues are close to 1 the separatrix is nearly degenerate
and nearby field lines have a long connection length (good for
detachment). When they diverge from 1 the connection length drops.
- pyna.control.topology_state.compute_topology_state(field_func, xpoint_guesses, opoint_guesses, is_axisymmetric=True, phi_ref=0.0, eps=0.0001)[source]¶
Compute full topology state from a field function.
Parameters
field_func : callable
Magnetic field function: [R,Z,phi] â?[dR/dl, dZ/dl, dphi/dl].
xpoint_guesses : list of (R, Z)
Initial guesses for X-point positions.
opoint_guesses : list of (R, Z)
Initial guesses for O-point positions.
is_axisymmetric : bool
Use closed-form exp(2ÏA) for DPm.
phi_ref : float
Reference toroidal angle.
eps : float
Finite-difference step for A-matrix.
Returns
TopologyState
- Return type:
Response matrix ∂(topology_state)/∂(control_inputs).
R_ij = ∂(observable_i) / ∂(control_j)
- For axisymmetric tokamaks:
∂(x_cyc)/∂(I_coil_k) = -A⁻¹ · ∂g(x_cyc)/∂(I_coil_k) ∂(DPm_eigval)/∂(I_coil_k) = from δDPm formula
For 3D/stellarator: same structure but needs φ-integration.
- The response matrix enables the linear control problem:
δ_state ≈ R · δ_controls
- hence:
min Σ_i w_i |state_i + R_ij δI_j − target_i|² s.t. constraints
- pyna.control.response_matrix.build_full_response_matrix(base_field_func, coil_field_funcs, state, wall=None, field_func_key='default', observables=None, eps_current=1.0, equilibrium=None, plasma_response=False, R_grid=None, Z_grid=None)[source]¶
Full response matrix combining all observable categories.
Combines: - X/O-point shifts and DPm eigenvalue changes (FPT closed-form) - Plasma-wall gap responses (FPT manifold shift, if wall is supplied) - q-profile (placeholder zeros, to be filled by pyna-qprofile-response)
- Parameters:
base_field_func (callable) – Base equilibrium field function.
coil_field_funcs (list of callable) – Per-unit-current coil field functions.
state (TopologyState) – Current topology state with at least one X-point.
wall (WallGeometry or None) – First wall geometry. If None, gap rows are zero placeholders.
field_func_key (str) – Hashable key identifying the equilibrium (for caching manifold growth).
observables (list of str or None) – Reserved for future filtering.
eps_current (float) – Current perturbation for per-unit normalisation.
equilibrium (EquilibriumSolovev or compatible object, optional) – If provided and
plasma_response=True, the plasma response δB_plasma is added to each coil’s δB_ext before computing topology/gap observables.plasma_response (bool) –
Whether to include plasma response. - True (recommended for core quantities like q-profile):
δB_total = δB_ext + solve_perturbed_gs(B0, J0, p0, δB_ext)
- False (OK for edge quantities like gap_gi when coils are far):
δB_total = δB_ext (vacuum approximation)
R_grid (array-like or None) – Grid for plasma response computation. If None, a default grid around the equilibrium is constructed automatically.
Z_grid (array-like or None) – Grid for plasma response computation. If None, a default grid around the equilibrium is constructed automatically.
- Returns:
R_full (ndarray, shape (n_obs_full, n_coils))
labels_full (list of str)
- pyna.control.response_matrix.build_response_matrix(base_field_func, coil_field_funcs, state, observables=None, eps_current=1.0, raw_coil_B_funcs=None, base_raw_B_func=None)[source]¶
Build response matrix R[n_obs, n_coils] using FPT formulae.
For each coil k, computes δ_state when I_k increases by eps_current, using closed-form FPT for axisymmetric observables (X/O-point shifts and DPm changes) and zero placeholders for observables that require additional geometric data (gaps, q-profile).
- Parameters:
base_field_func (callable) – Base equilibrium field function: [R,Z,phi] → [BR/|B|, BZ/|B|, Bphi/(R|B|)].
coil_field_funcs (list of callable) –
Coil perturbation field functions (per unit current). Legacy form (only used when raw_coil_B_funcs is None):
This form is first-order correct for |δB| << |B0| away from X-points. Near the poloidal-field null (X-point), |Bpol/B| ~ 1e-5, so normalized coil components are unreliable. Use raw_coil_B_funcs instead.
state (TopologyState) – Current topology state (must be pre-computed).
observables (list of str or None) – Reserved for future observable filtering; currently unused.
eps_current (float) – Current perturbation amplitude used to define per-unit response. (The result is divided by eps_current so R is per-ampere.)
raw_coil_B_funcs (list of callable or None) –
If provided, preferred over coil_field_funcs for DPm eigenvalue computation. Each callable returns raw coil B components:
raw_coil_B_funcs[k]([R,Z,phi]) → [δBR, δBZ, δBphi] (in Tesla)
The base field (B0) must then also be recoverable. Supply base_raw_B_func alongside this argument.
base_raw_B_func (callable or None) –
- Returns raw base B components:
base_raw_B_func([R,Z,phi]) → [BR0, BZ0, Bphi0] (in Tesla)
Required when raw_coil_B_funcs is provided.
- Returns:
R_mat (ndarray, shape (n_obs, n_coils))
obs_labels (list of str, length n_obs)
Multi-objective magnetic topology controller.
Solves the weighted least-squares problem:
min Σ_i w_i · |state_i + R_ij · δI_j − target_i|² + λ · ||δI||² s.t. I_min_j ≤ δI_j ≤ I_max_j (coil current limits)
Uses scipy.optimize.minimize with the SLSQP method.
- Design principle — “avoid whack-a-mole”
All objectives are optimised simultaneously, not sequentially.
Weights encode the physicist’s priorities.
Hard bound constraints enforce safety limits.
L2 regularisation on ΔI penalises large coil changes.
- class pyna.control.optimizer.ControlConstraints(I_max=None, I_min=None, gap_min=<factory>, xpoint_R_bounds=None, xpoint_Z_bounds=None)[source]¶
Bases:
objectHard constraints for the optimisation.
- class pyna.control.optimizer.ControlWeights(gap_gi=100.0, xpoint_position=10.0, DPm_eigenvalue=5.0, DPm_eigenvector=2.0, Bpol_lcfs=2.0, q_profile=1.0, opoint_position=3.0, iota_core=0.5, delta_I_regularization=0.1)[source]¶
Bases:
objectWeights for multi-objective topology optimisation.
Higher weight → higher priority. Safety-critical observables (gap_gi) should carry the highest weight.
- class pyna.control.optimizer.TopologyController(n_coils)[source]¶
Bases:
objectMulti-objective magnetic topology controller.
Uses the linear response matrix R (∂state/∂controls) to find the optimal coil-current changes δI that move the topology toward a target state while respecting coil limits.
- For nonlinear problems iterate:
compute state → build R → solve → apply δI → repeat.
Example
>>> ctrl = TopologyController(n_coils=6) >>> weights = ControlWeights(gap_gi=100.0, DPm_eigenvalue=10.0) >>> delta_I, result = ctrl.solve( ... current_state, target_state, ... response_matrix, obs_labels, ... weights=weights, ... )
- predict_response(current_state, delta_I, response_matrix, obs_labels)[source]¶
Predict how each observable changes under δI.
- solve(current_state, target_state, response_matrix, obs_labels, weights=None, constraints=None)[source]¶
Solve multi-objective optimisation for optimal δI.
- Parameters:
current_state (TopologyState)
target_state (TopologyState)
response_matrix (ndarray, shape (n_obs, n_coils))
weights (ControlWeights or None)
constraints (ControlConstraints or None)
- Return type:
- Returns:
delta_I (ndarray, shape (n_coils,))
result (scipy OptimizeResult)
Flux surface fate classification under perturbation.
Classifies whether a flux surface (characterised by Ï or ι) will:
Remain intact (KAM torus survives)
Deform but stay closed
Break into an island chain (resonance â?residue crosses 0 or 1)
Become chaotic (Chirikov overlap)
Criteria used:
Greene’s residue R = (2 â?Tr(DPm)) / 4
Relative DPm perturbation magnitude vs ε_KAM / ε_chaos thresholds
Higher-order tensors D²Pm, D³Pm (future extension)
- pyna.control.surface_fate.Greene_residue(DPm)[source]¶
Greene’s residue R = (2 â?Tr(DPm)) / 4.
Interpretation
R < 0 : hyperbolic (X-point-like), field lines diverge
0 < R < 1 : elliptic (O-point-like), field lines rotate
R > 1 : inverse hyperbolic
R = 0 or 1 : parabolic (marginal / transition point)
Parameters
DPm : ndarray, shape (2,2)
Poincaré map Jacobian (one full period).
Returns
float
- Return type:
- pyna.control.surface_fate.classify_surface_fate(iota, delta_iota, DPm, delta_DPm, epsilon_KAM=0.05, epsilon_chaos=0.3)[source]¶
Classify how a flux surface responds to a field perturbation.
Parameters
iota : float
Unperturbed rotation transform ι = 1/q.
delta_iota : float
Change in rotation transform under perturbation.
DPm : ndarray, shape (2,2)
Unperturbed Poincaré map Jacobian.
delta_DPm : ndarray, shape (2,2)
Change in DPm under perturbation.
epsilon_KAM : float
Relative DPm-change threshold below which the KAM torus survives.
epsilon_chaos : float
Relative DPm-change threshold above which chaos is expected.
Returns
SurfaceFate
- Return type:
- pyna.control.surface_fate.scan_surface_fates(field_func, delta_field_func, psi_values, q_values, field_period=1, epsilon_KAM=0.05, epsilon_chaos=0.3)[source]¶
Scan flux surfaces from core to edge and classify each.
For each (psi, q) pair this function would compute the A-matrix and DPm
at the corresponding O-point (requiring the actual flux surface geometry).
The present implementation returns UNKNOWN for all surfaces as a stub â? a full implementation needs the O-point (R,Z) positions for each Ï.
Parameters
field_func : callable
Base magnetic field function.
delta_field_func : callable
Perturbation field function.
psi_values : ndarray
Normalised poloidal flux values Ï_N â?[0, 1].
q_values : ndarray
Safety factor q at each Ï.
field_period : int
Toroidal field period (1 for tokamak, N_fp for stellarator).
epsilon_KAM : float
KAM survival threshold.
epsilon_chaos : float
Chaos threshold.
Returns
dict {psi_norm: (SurfaceFate, Greene_residue_before, delta_residue)}