XGC job composer

The XGC-GUI also exposes this composer through its experimental Job Composer page. See utils/input_gui/README.md for the form and package editor.

utils/python/job_composer/compose_job.py creates a job package for an existing XGC executable. It does not build XGC or submit a job. Run the composer with Python 3.9 or newer, including when composing on an HPC login node; use a suitable Python virtual environment if the system python3 is older. The previous quickstart/compose_job.py command remains a compatibility entry point. The package contains jobscript.sh, env_settings.sh, setup_rundir.sh, print_jobinfo.sh, job_artifacts.py, cleanup_failed_job.py, input_dir/, logs/, an empty rundir/, and a job_manifest.json recording the resolved configuration and input hashes. Submit from the package directory. At job start, the script copies the prepared input_dir/ into rundir/, stages its three required files there, creates rundir/restart_dir/, and launches XGC from rundir/. The Aurora DAOS option instead stages and runs XGC in its mounted DAOS work directory; its package has no local rundir/.

Package layout and saved settings

The version 2 manifest also records value_sources: chosen for supplied settings and default for settings resolved from the machine profile or composer defaults. The CLI can reload it with --config; provide a new --output-dir for the next package. The GUI can reload job specs and manifests for its available controls, using a path on the GUI server. It rejects custom companion recipes, which the GUI does not edit.

The manifest hashes files at composition time. Edit package input_dir/ before the first submission. On a restart, the existing run-local input files remain in place; edits to package input_dir/ are not staged again. Edits after composition are not reflected in the original manifest; fort.input.used captures XGC’s used namelist values, but it does not replace a full snapshot of every runtime input.

python3 utils/python/job_composer/compose_job.py \
  --platform perlmutter_gpu \
  --executable /path/visible/on/compute/nodes/xgca \
  --nodes 2 --account myproject --walltime 01:00:00 \
  --output-dir /path/to/new-run
cd /path/to/new-run
sbatch jobscript.sh

Use qsub jobscript.sh on Polaris and Aurora. --output-dir must name a new directory; the composer will not overwrite an existing run. The executable need not exist on the machine where the composer runs, but it must exist at the given absolute path on compute nodes. The default input directory is quickstart/inputs; --input-dir stages a prepared directory containing at least input, adios2cfg.xml, and petsc.rc. Review and edit these inputs before submission. The bundled default input is an example, not a scientific configuration for a particular campaign.

Platforms and resource settings

The nine canonical --platform values are perlmutter_cpu, perlmutter_gpu, flux, stellar, stellarai_cpu, stellarai_gpu, polaris, aurora, and frontier. Aliases perlmutter and stellarai select their GPU profiles. utils/python/job_composer/machines.json holds scheduler, environment, launcher, and resource defaults. GPU profiles use one MPI rank per GPU or GPU tile. CPU defaults are Perlmutter 16 ranks × 16 threads, Flux 8 × 16, and Stellar/Stellar-AI CPU 12 × 8. Aurora uses 12 ranks × 16 threads, keeping both hardware threads of each selected physical core in one rank. --tasks-per-node and --cores-per-task allow compatible overrides; Polaris currently requires its fixed four-rank NUMA mapping, and Aurora requires an even 2–16 threads per rank. The generated script overrides any OMP_NUM_THREADS value in the sourced environment file with the resolved value.

Profiles for Flux, Stellar, and both Perlmutter variants also record a suggested Python virtual environment. The GUI displays the path and offers a button to select it; other profiles omit the hint. A suggestion does not activate Python or change a direct job by itself. In the CLI, pass the desired path explicitly with --python-venv. Verify that the selected environment is visible on compute nodes and contains the packages needed by the chosen add-ons.

Other options are --queue, --job-name, --environment (an alternate source file staged as env_settings.sh), --python-venv /absolute/compute-visible/venv (sources its bin/activate after the site environment), and --debug (passes XGC’s --debug argument). PBS jobs also accept --filesystems with a colon-separated list, such as home:eagle; Polaris defaults to home:eagle. Aurora DAOS selects its required request automatically. The activated environment supplies python3 for artifact helpers and the supervisor; the built-in campaign packager runs with that venv’s bin/python. An explicit --optimizer-python can select a separate interpreter for the optimizer. --daos-pool and --daos-cont together enable the Aurora DAOS path: the script loads the DAOS module, calls launch-dfuse.sh, copies input_dir into the mounted work directory, and launches with DAOS MPI settings. Batch logs remain in the submission directory. DAOS pool and container must already exist and be accessible to the job. Consult site policy for the appropriate queue, account, filesystem, executable path, and walltime.

The Aurora profile currently supports the GPU-aware XGC build (USE_GPU_AWARE_MPI=On) and its matching runtime environment (MPIR_CVAR_ENABLE_GPU=1). DAOS is an independent filesystem choice: after loading its module, the script uses the module’s DAOS_PRELOAD value for both mpiexec and its ranks and adds --no-vni. A Flare run uses neither DAOS launch option. The composer does not yet offer a standard-MPI build mode; that mode would need its own verified runtime settings.

Python API

Import the Python API from utils.python.job_composer.compose_job. Its entry point is compose_job(output_dir, platform=..., executable=..., nodes=..., account=..., walltime=..., ...). resolve_job(...) computes machine and workflow defaults without writing files; plan_job(...) also validates the input source and records chosen/default provenance. save_job_spec(path, ...) writes a validated job_spec.json, and load_job_settings(path) restores actively chosen settings from either a saved spec or a package manifest. It deliberately omits the previous output directory.

Testing

With BUILD_TESTING=ON, CMake registers the standard-library composer suite as job_composer_python when it finds Python 3.9 or newer. If the same interpreter also has NumPy, CTest registers the input-package selection tests; if it also has Flask and Matplotlib, CTest registers the Job Composer GUI tests. Set Python3_EXECUTABLE at CMake configure time to select a suitable virtual environment. Run the registered tests from the build directory with ctest -L job_composer --output-on-failure. They do not need XGC-Analysis or an XGC executable.

To configure just these Python tests without XGC’s compiler and library dependencies, use the standalone CMake project:

cmake -S utils/python/job_composer/tests -B build-job-composer \
  -DPython3_EXECUTABLE=/path/to/python
ctest --test-dir build-job-composer -L job_composer --output-on-failure

The live ACA smoke test is kept separately in utils/python/job_composer/tests/integration_campaign.py and is not part of the default CTest suite. It requires a sibling XGC-Analysis checkout and an environment with ADIOS2, HPC-Campaign, and NumPy. Run it explicitly from the XGC-Devel source directory with python3 -m unittest discover -s utils/python/job_composer/tests -p integration_campaign.py -v.

Walltime and deadlock detection

Use --max-walltime to pass XGC -max_walltime as the scheduler limit minus a checkpoint margin, rounded down to whole minutes. --max-walltime-margin-minutes sets a positive margin (default 30) that must cover environment and input staging, the last time step, and writing the final restart; at least one whole minute must remain for XGC. The final restart also requires sml_restart_write_after_last_timestep, which defaults to true. Leave this feature off for short debug jobs. A selected deadlock detector, online campaign packager, or Diffusion Optimizer resolves the launch mode to supervised; otherwise it is direct. Deadlock detection has a 10-minute startup grace and a 20-minute default inactivity timeout, adjustable with --deadlock-timeout-minutes only when detection is enabled. The first inactivity interval begins after the startup grace, so no initial complete line causes a default stall after 30 minutes. Later complete lines reset the timer.

Generate a deadlock-monitored package without any companion commands:

python3 utils/python/job_composer/compose_job.py \
  --platform polaris --executable /path/to/xgc \
  --nodes 2 --account myproject --walltime 02:00:00 \
  --deadlock-detection --deadlock-timeout-minutes 25 \
  --output-dir /path/to/new-run

The supervisor observes its explicitly configured XGC stdout file every 10 seconds for normal minute-scale timeouts, counts complete newline-terminated lines, and returns status 124 when the log stalls. It records the path, cumulative line count, last observed progress time, and idle duration as a JSON event in batch stderr before terminating XGC and any companions. A file that has not appeared yet is treated as quiet; truncation or replacement restarts reading from the beginning. Unflushed stdout and partial lines cannot be observed as progress, so check buffering on a short site run before using the detector for production. utils.python.job_composer.deadlock_detector can also be imported by other workflow adapters or run as an observer-only CLI: python3 utils/python/job_composer/deadlock_detector.py /path/to/xgc.out --timeout-minutes 20 --startup-grace-minutes 10. Its CLI reports a stall with exit status 2 but does not cancel processes.

Boolean composer options also accept --no-... forms, so a CLI override can explicitly turn an option off. The GUI exposes both built-in companion recipes.

Online campaign packaging

The thin supervisor uses only Python’s standard library and can run under the site’s Python 3.6. The built-in --online-campaign-packaging recipe requires --python-venv /absolute/path/to/venv; its bin/python runs the packager. XGC-Analysis requires Python 3.10 or newer, and the venv must also provide ADIOS2 and HPC-Campaign. The batch script checks the interpreter version and imports before launching XGC. The earlier stage’s --campaign-python setting is not used in this stage; when loading an older manifest, provide --python-venv for a new package. An explicit custom companion command controls its own Python environment. Optional --campaign-interval-seconds (default 300), --campaign-settle-seconds (default 15), and --campaign-finalize-seconds (default 600) tune polling, BP settling, and the post-XGC scan limit. The packager writes xgc_run.aca and xgc_run_manifest.json in the XGC working directory. It references attempt files under the package root, separate from the run directory on all current layouts.

Generated live and final packager commands include --diagnostics. Their stderr logs show flushed timestamps and elapsed times for each catalog phase and each HPC-Campaign manager operation. XGC may create BP5 directories before writing their first step. The live packager therefore uses a filename-only catalog scan and defers BP products whose index is still empty, then checks the remaining sources for the configured quiet period before ACA registration. The final forced scan does not wait for a quiet period, but still omits BP products with empty indexes. The packager applies one ACA update through a single worker process using HPC-Campaign’s Manager API, so it opens the staged ACA once instead of starting one manager process per entry. The campaign Python environment must provide Manager.open, close, data, text, and add_time_series. HPC-Campaign opens registered BP products with ADIOS2; its reads remain in the worker, where a native crash fails that update without killing the long-lived packager. A nonempty index and quiet period do not guarantee that a writer will not resume.

After XGC exits, the supervisor gives the packager time to finish its scan before releasing other tasks. The batch script then snapshots per-job artifacts and runs one final ACA scan, so the archive includes the completed attempt’s logs, timing, and artifact references. A failed final scan returns a nonzero batch status when XGC itself succeeded. A short debug job without --max-walltime may still run out of scheduler time before this final scan; choose enough walltime for packaging. Concurrent BP reader/writer behavior and remote replicas require site validation.

Custom companions and supervision

Custom packager and optimizer commands remain available through a JSON array passed to --companion-tasks-file. A custom packager command replaces the built-in command. For example:

[{"name": "online_campaign_packaging", "argv": ["python3", "/path/to/packager.py"]}]

Each command runs as a local process on the batch host, oversubscribing CPUs on the first allocated node. The generated supervisor.json records the XGC launch command and scheduler, XGC stdout and stderr paths, companion commands, output files, placement, and shutdown grace. launch_xgc.sh holds the platform’s MPI launcher and XGC flags and uses exec so its shell is replaced by srun or mpiexec; the supervisor therefore tracks the local launcher PID directly. The batch script sources the staged site environment, prepares the working directory, and executes python3 -u supervise_job.py. Ensure that environment makes a standard-library Python 3 interpreter available on compute nodes. The supervisor owns task process groups. A companion that completes before XGC is treated as an error unless its entry sets "required_until_xgc_exit": false. Set "on_failure": "continue" to report such a failure while XGC continues, or "stop_xgc" to stop the workflow. Campaign packaging defaults to continue; Diffusion Optimizer defaults to stop_xgc. XGC completion stops remaining companions and returns XGC’s exit status. Cancellation or a fatal companion failure stops all tasks. On abnormal shutdown under Slurm, the supervisor also sends TERM and then KILL to active job steps through scancel --signal using SLURM_JOB_ID, while leaving the batch script to collect artifacts. The provided commands must themselves be valid on the target machine; site smoke jobs remain necessary to verify placement, concurrent launcher behavior, and remote-rank termination.

Diffusion Optimizer workflow

Select --diffusion-optimizer --optimizer-script /absolute/path/to/XGC-Analysis/Diffusion_Optimizer/diffusion_optimizer.py for the built-in optimizer recipe. The script path must be visible on compute nodes; provide either --python-venv or an absolute --optimizer-python path to a Python 3.10+ environment with the optimizer’s dependencies. The supervisor starts the optimizer in the XGC working directory 120 seconds after launching XGC by default, giving xgc.heatdiag2.bp time to become readable. Set --optimizer-start-delay-seconds to a nonnegative integer to change this delay; zero starts it immediately. If XGC exits before the delay, the optimizer is not started. The delay does not itself verify BP readiness. The optimizer waits for its required static files and communicates through XGC’s configured ADIOS streams. It uses the first node’s CPU resources without a separate scheduler step. The batch script checks that the entry point and Python version are available after environment and DAOS setup. supervisor.json records its exact argv, delay, per-attempt stdout/stderr paths, and failure policy. An optimizer exit with nonzero status stops XGC immediately. A normal optimizer exit gives XGC up to 600 seconds to finish; if XGC is still running afterward, the supervisor stops it. This accommodates stream closure during XGC teardown while keeping the optimizer’s 90-minute no-data timeout fatal to the workflow after that grace. For an unusually long teardown, edit the optimizer task’s normal_exit_grace_seconds in the generated supervisor.json before submission. A custom optimizer companion can still be supplied in --companion-tasks-file, but the GUI cannot reload or edit custom companion recipes.

Every batch-safe diffusion_optimizer.py argument is available as a composer flag with an --optimizer- prefix: for example, --optimizer-update-damping-factor 0.3, --optimizer-solver-method lsq, --no-optimizer-use-time-avg-system, --optimizer-momentum-rhs-taper 0.1 0.2 0.8 0.1, and --optimizer-online-response-window 0.2 0.8. Run python3 utils/python/job_composer/compose_job.py --help for the full list. The shared optimizer_options.json defines the default values, input types, and help used by the CLI and GUI. The resolved values and per-option chosen/default provenance are in job_manifest.json. Only explicitly chosen values are passed to the optimizer; its own checkout supplies the other defaults. Check explicitly chosen flags against the installed XGC-Analysis optimizer CLI whenever using an older checkout; the composer does not import its scientific code. Interactive --debug-solver is excluded because it opens X11 plots and breakpoints, and --use-wall-loss from older examples is absent from the current optimizer CLI.

The optimizer’s scientific inputs and XGC’s ADIOS stream configuration are not changed by the composer. The current recipe requires site smoke tests for the optimizer Python environment, live stream connection, concurrent launcher behavior, and clean shutdown on each target platform. The supervisor exposes XGC_STDOUT_PATH to companion processes.

Logs, artifacts, and restarts

The generated script records UTC XGC start and end times and propagates XGC’s exit status. Each scheduler submission writes its batch, XGC, and companion stdout/stderr under logs/<restart-count>/<job-id>/; XGC writes its timing files directly under timing/<restart-count>/<job-id>/ through its --timing-dir option. Timing output compatibility change: when launching the C++ XGC independently without --timing-dir, XGC now creates ./timing/ and writes its timing files there. Earlier builds wrote them in the run directory when ./timing/ did not already exist. Update scripts that read timing_all.txt or adios_timer.txt from the run directory. XGC-S continues to use the run directory for its timing files. The post-run hook copies fort.input.used, runinfo.dat, TASKMAP_*.txt, units.m, units.txt, Efield_excluded_*, xgc.used_inputs.bp, git_diff_XGC_core.txt, wallsource.dat, and m3dc1_eqd_out.eqd into artifacts/<restart-count>/<job-id>/ when present. Each artifact directory also contains job_attempt.json with timestamps, exit status, and the copied product names. These files remain at the working-directory root where XGC may need them. Legacy timing_*.dat files found there are copied to the per-job timing directory.

The count is zero padded to at least four digits. A fresh run uses 0001; for a restart the script uses the second integer from timestep.dat in the XGC working directory plus one, matching XGC’s checkpoint counter. The job ID keeps a retry distinct when it starts again from the same checkpoint. Composer jobs call setup_rundir.sh --in-place: on a new run it stages only missing input files, preserving edits in the working directory; on a checkpointed run it requires the existing input files and restart_dir/ without replacing them. The one-argument form remains for older quickstart scripts that create a separate rundir/; it also preserves existing runtime inputs and checkpoints and copies only missing inputs. On a checkpointed composer run, the artifact hook requires sml_param:sml_restart=.true. in the working-directory input before it archives the previous attempt or launches XGC. A missing or false setting stops the job to protect the checkpoint and BP outputs. Before a subsequent attempt, the hook snapshots the previous attempt again, covering interruptions before the post-run hook completed. It rejects an existing checkpoint without a composer attempt marker so that an unmanaged run is not silently relabeled. A later submission without a checkpoint is rejected because it cannot safely resume and may overwrite BP outputs.

For an uncheckpointed failed attempt, run ./cleanup_failed_job.py --dry-run from the idle package to see its archive destination, then ./cleanup_failed_job.py to move rundir/ to failed_runs/<restart-count>/<job-id>/ and recreate an empty rundir/. Existing per-job logs/, timing/, and artifacts/ stay in place. Correct the source files in package input_dir/ and submit again. The helper refuses a run with timestep.dat because it may be restartable, and refuses to act while the package lock is held. The hidden .xgc_run.lock file need not be removed: an unlocked file does not block another job. The helper also accepts older flat packages, archiving run-root entries outside their generated scaffold. DAOS cleanup needs its mounted work directory and is not supported by this local helper.

Package locking and scheduler output

The generated batch script acquires a nonblocking exclusive lock on .xgc_run.lock in the package before staging or launching XGC. It holds file descriptor 9 for the lifetime of the job, including child tasks. A second job using the same package exits rather than sharing its working directory. After an interrupted job has released the lock, the artifact hook can recover a marker still labeled running; direct callers of the hook must hold the same exclusive lock before requesting recovery. This uses Python’s standard-library fcntl module and requires working advisory file locks on the package filesystem. The batch host needs standard-library Python 3 even for direct jobs; job_artifacts.py uses Python 3.6 compatible APIs. A lock failure reports the OS error and errno so a filesystem that does not support flock can be distinguished from an active job. Include package locking on the intended filesystem in each site’s smoke test; the job stops if the lock is unavailable.

The scheduler captures startup failures before the script redirects its own streams to the per-job log directory. Slurm places those bootstrap files at logs/batch-<job-id>.out/.err; PBS uses logs/batch.out/.err, which can be overwritten by a later PBS submission. The per-job batch.out/.err capture the script after setup. PBS directives contain the package’s absolute path, so recompose after moving a PBS bundle. The post-run hook cannot execute after an abrupt scheduler kill; the next submission performs its recovery snapshot if the same working directory is available.