Diffusion Optimizer workflow
Diffusion Optimizer fits the anomalous transport coefficients used by axisymmetric XGC (XGCa) to maintain prescribed flux-surface averaged plasma profiles. XGCa advances the plasma with its current coefficients; the optimizer reads a short history of density, parallel flow, and temperature, estimates how the coefficients should change, and writes the next coefficient profiles. Repeating this cycle is intended to reduce slow profile evolution.
The implementation assumes a pure, quasineutral plasma. Electron density is
the particle-transport density for both species. It fits particle diffusivity
D and pinch velocity v_p from the electron density equation, ion
momentum diffusivity eta from the ion momentum equation, and separate
electron and ion heat conductivities chi_e and chi_i from their energy
equations. Electron momentum is not fitted here; XGCa controls the current
through its loop-voltage model.
Data flow
XGCa: evolve with current coefficients
-> xgc.diffusion_profiles.bp (profile histories)
-> Diffusion Optimizer: preprocess, fit, post-process
-> xgc.diffusion_coeff.bp (updated full coefficients)
-> XGCa: next update interval
At startup, the optimizer builds axisymmetric flux-surface geometry from the
XGC mesh and equilibrium. It opens and drains the profile stream to its latest
step, then opens the coefficient output stream. The profile stream stays open
while it waits for subsequent XGC updates. The timestep.dat state
distinguishes a restart from a new run: on restart, the latest available
profile step has already been processed, so the first loop iteration waits
for a new one. Stream ordering matters when both programs restart together.
Each profile output contains several time samples. The optimizer smooths the
histories in time and radius, differentiates them on the nonuniform flux grid,
and computes Vprime = dV/dpsi from enclosed flux-surface volume. By
default, the fitting system averages its rows over the samples in the current
update interval. --no-time-avg-system instead retains the individual
time-sample rows.
Fitting order and coefficient updates
The current solver uses full-domain radial matrices. It fits corrections to the coefficients active during the preceding XGC interval, rather than reconstructing each full coefficient profile independently:
Fit electron
Delta DandDelta v_pto counter the measured density evolution. The density solve constrains diffusion and pinch jointly to limit ambiguity between their particle flux contributions.Pass those undamped particle corrections to the ion solve. Fit ion
Delta etausing the conservative momentum equation.Fit
Delta chi_eandDelta chi_iusing the conservative energy equations. The ion energy fit uses the undamped particle and momentum corrections obtained earlier in the same update.
The matrix builders form coefficient-to-rate operators from the measured
profiles and geometry. Their radial flux-divergence terms use one-sided
upwind stencils on the flux grid. Known convective and coupling terms enter
the momentum and energy targets. The sign convention asks the fitted
correction to counter the observed profile time derivative. The QP backend
is the default; --solver-method lsq selects bounded least squares.
The code estimates particle, momentum, and heat flux balances at the separatrix. In the current full-domain solves, those separatrix estimates are diagnostics; they are not fixed coefficient values. A fixed correction may be imposed at the inner edge of the retained fit domain. At the outer edge, the coefficient is free within its applicable bounds. Rows where XGC’s diffusion boundary factor is small are excluded from fitting.
After each solve, the optional red-black filter processes the correction
while restoring any fixed inner value. This filter is off by default. The
full trial coefficient is then kept nonnegative for D, eta, and
chi. In the default constrained cap mode, bounds and regularization
are part of the solve, followed by a safety clip. In post-solve mode, the
fit drops its regularization penalties and applies a smooth rational cap to
the full trial profile afterward. The core cap defaults to 40 in the
coefficient’s units and can be set with --coefficient-cap-core.
Only after the density, momentum, and energy stages have finished are their
corrections multiplied by --update-damping-factor. Thus later stages see
the undamped corrections from earlier stages; damping stabilizes the
coefficients written to XGC without changing the equations used for that
update’s fit. A configured momentum RHS taper also drives the existing ion
eta profile toward zero outside its active radial band after fitting.
Learned response correction
The one-dimensional upwind operator does not exactly reproduce XGCa’s
two-dimensional finite-element diffusion response, especially near the
separatrix. --online-response optionally learns a short radial response
kernel from successive intervals. It synchronizes
xgc.fsourcediag.*.bp samples with the diffusion-profile history and
compares the one-dimensional predicted diffusion rates with the
flux-surface averaged XGC diffusion changes. Separate five-point kernels
are trained for density, ion momentum, electron heat, and ion heat. The
kernel uses changes between consecutive intervals and starts from an
identity response until enough intervals have been observed.
--online-response-window PSI_MIN PSI_MAX selects a normalized-flux
training and application interval. The correction blends back to the
uncorrected operator over two surfaces beyond that interval. Without this
option, the default window is centered near the separatrix. The learned
state is saved in online_response_state.npz for restarts, and prediction
quality is plotted in plots/online_response_quality.svg. Online response
requires the time-averaged fitting system.
Diagnostics and control switches
Fit-quality plots compare the target rate with the rate predicted by the
current fit, including processed undamped corrections. They are intended to
diagnose the fitting equation; under-relaxation is applied later. Coefficient
plots show the full profiles before and after the update. If
xgc.heatdiag2.bp is available, wall-loss plots are also produced. Wall
loss is diagnostic and does not impose an outer coefficient boundary value.
--first-update-gstep permits fitting and plotting before coefficient
changes are applied. --fitting-stop-gstep skips subsequent fits and
writes unchanged coefficients while continuing the update loop and available
diagnostics. The active command-line and hard-wired configuration values are
printed at startup for reproducibility. For the complete list of switches,
run python Diffusion_Optimizer/diffusion_optimizer.py --help.
Model boundary
The optimizer fits flux-surface averaged, one-dimensional equations to a two-dimensional XGCa calculation. Near the closed/open flux-surface transition, the mapped area, enclosed volume, parallel wall loss, and XGC’s finite-element operator can all affect how closely the fitted one-dimensional rate predicts the actual response. Fit-quality plots measure consistency with the optimizer’s equations; the online-response diagnostic tests how well changes predicted by those equations agree with changes measured by XGC. Profile control in a live run remains the final check.
The driver is Diffusion_Optimizer/diffusion_optimizer.py. Matrix
assembly, solver stages, preprocessing, cap processing, and online response
are kept in the neighboring diffopt_*.py modules.
The live profile reader, coefficient writer, and synthetic profile generator
also live in Diffusion_Optimizer/. They are workflow components rather
than members of the installed xgc_analysis library. Run the driver from
the repository checkout with XGC-Analysis installed in the Python environment
or supply --xgc-analysis-path.
Workflow stream classes
- class Diffusion_Optimizer.DiffusionProfiles.DiffusionProfiles(adios: adios2.Adios, *, bp_dir: os.PathLike | str = './xgc.diffusion_profiles.bp', wait_interval: float = 1.0, timeout: float | None = None)[source]
Live reader for
xgc.diffusion_profiles.bp.Reads diffusion profile data from an Adios BP file (directory)
xgc.diffusion_profiles.bp. The constructor opens the BP directory named bybp_dir. Once it is found, it opens it as a persistentadios2.Stream, drains all existing steps while retaining only the final one. The Stream is left open until it is closed with the member method close(). A blocking request for a new step can be posted with the member method read_next_step.The file
xgc.diffusion_profiles.bphas the following structure:double density n_steps*{n_species, n_samples, n_surf} double flow n_steps*{n_species, n_samples, n_surf} double temp n_steps*{n_species, n_samples, n_surf} int32_t n_samples n_steps*scalar int32_t n_species n_steps*scalar int32_t n_surf n_steps*scalar double psi n_steps*{n_surf} int32_t steps n_steps*{n_samples} double time n_steps*{n_samples}
That is, there are (for example) n_steps steps recorded in the BP file. The constructor of this class reads only the last step (i.e. the most recent one).
- The property latest_step_data returns a dictionary with the following keys:
density : A 3D array (shape (n_species, n_samples, n_surf)) of the diffusion density. flow : A 3D array (shape (n_species, n_samples, n_surf) of the parallel mean flow. temp : A 3D array (shape (n_species, n_samples, n_surf)) of temperature (in eV). n_samples : An integer (n_samples) representing the number of time samples. n_species : An integer (default 2) representing the number of particle species. n_surf : An integer (n_surf) representing the number of ψ-surfaces. psi : A 1D array (shape (n_surf,)) containing the poloidal magnetic flux values. steps : A 1D array (shape (n_samples,)) of time step indices. time : A 1D array (shape (n_samples,)) of simulation times (in seconds).
- Parameters:
bp_dir (str | os.PathLike) – Path to the BP directory written by XGC. The constructor blocks until the directory exists (subject to timeout).
wait_interval (float, optional) – Polling interval in seconds while waiting for the directory. Defaults to 1.0 s.
timeout (float | None, optional) – Abort after this many seconds if the directory still does not exist.
None(default) means wait indefinitely.
Notes
The object keeps exactly one snapshot in memory. Callers needing to store a history must copy out the data returned by
latest_step_data.- property latest_step_data: Dict[str, ndarray]
Return a shallow copy of the dictionary holding the current step.
- Raises:
RuntimeError – If no step has been read yet (e.g., the writer hasn’t flushed a step to disk).
- classmethod load_last_sample_history(adios: Adios, *, bp_dir: PathLike | str = './xgc.diffusion_profiles.bp', max_steps: int | None = None, stride: int = 1, open_timeout: float | None = 0.0) Dict[str, list][source]
Reconstruct per-step history using the last time sample of each profile.
- Parameters:
adios (Adios) – ADIOS handle used to create a temporary read stream.
bp_dir (str | os.PathLike) – Path to
xgc.diffusion_profiles.bp.max_steps (int | None) – If provided and > 0, keep only the last
max_stepsentries.stride (int) – Keep every
stride-th step while scanning BP history.open_timeout (float | None) –
OpenTimeoutSecspassed to the temporary read stream.
- Returns:
History dictionary with keys:
density,flow,temp(each list of(n_species, n_surf)arrays),gstep(list[int]), andtime(list[float]).- Return type:
dict[str, list]
Notes
This helper is independent from live-stream operation and does not change the behavior of the default constructor.
- property n_samples: int | None
Number of (time) samples per species in snapshot, else
None.
- property n_species: int | None
Number of species in the current snapshot (or
None).
- property n_surf: int | None
Radial surface count (psi grid size) in snapshot, else
None.
- read_next_step(*, my_timeout: float = 10.0, poll_delay: float = 0.5) bool[source]
Advance the stream by one time step.
Examples
>>> prof = DiffusionProfiles("xgc.diffusion_profiles.bp") >>> prof.read_next_step(block=False) False # no new data yet >>> # ... later >>> prof.read_next_step() True # newest snapshot now loaded
- Parameters:
my_timeout – Maximum wait time for a new step; if no new step is found within my_timeout seconds, return False.
poll_delay – Sleep duration (seconds) between availability checks when block is True.
- Returns:
True if a new step was successfully read, False otherwise.
- Return type:
bool
- class Diffusion_Optimizer.DiffusionCoefficients.DiffusionCoefficients(adios: Adios, *, timeout=10.0, nspecies=2, npsi=100)[source]
Stores the profiles of diffusion model coefficients defined as functions of the poloidal magnetic flux ψ.
- The four diffusion coefficients are:
ptl_diffusivity (m^2/s)
momentum_diffusivity (m^2/s)
heat_conductivity (m^2/s)
ptl_pinch_velocity (m/s)
The data layout of each coefficient is (nspecies, npsi) where the second dimension (the ψ-grid) is contiguous in memory. Also the ψ–grid is stored as a 1D NumPy array of length npsi.
- Parameters:
nspecies: Number of particle species (default: 2). npsi: Number of points in the ψ–grid.
- write_to_file(filename='xgc.diffusion_coeff.bp', gstep=-1)[source]
Writes the diffusion coefficient profiles and the ψ–grid to an Adios BP file as a new step.
For each diffusion coefficient, the data for each species is split into an individual variable using a species suffix. The species suffixes (for species indices 0 through 6) are:
0: _elec 1: _ion 2: _imp1 3: _imp2 4: _imp3 5: _imp4 6: _imp5
- In addition, the file will store:
n_species: a scalar integer (int64).
psi: a 1D array (of length npsi).
The method opens the specified file (filename) using the Adios2 FileWriter in append mode, writes all the individual diffusion profiles, and then ends the step.
- class Diffusion_Optimizer.AnalyticDiffusionProfiles.AnalyticDiffusionProfiles(psi, time, S, Vol, grad_avg, psi_norm)[source]
Creates diffusion profile data using analytic functions.
- Attributes:
density : A 3D array (shape (n_species, n_samples, n_surf)) of the diffusion density. flow : A 3D array (shape (n_species, n_samples, n_surf) of the parallel mean flow. temp : A 3D array (shape (n_species, n_samples, n_surf)) of temperature (in eV). n_samples : An integer (n_samples) representing the number of time samples. n_species : An integer (default 2) representing the number of particle species. n_surf : An integer (n_surf) representing the number of ψ-surfaces. psi : A 1D array (shape (n_surf,)) containing the poloidal magnetic flux values. steps : A 1D array (shape (n_samples,)) of time step indices. time : A 1D array (shape (n_samples,)) of simulation times (in seconds).