Known limitations¶
The list below captures behaviour that surprises users often enough to warrant
a heads-up. Most items fall out of the underlying gmatpy runtime or the GMAT
script semantics.
No built-in wall-clock timeout¶
Mission.run does not take a timeout= keyword. If
you need a wall-clock cap on a mission run — for example to bound a divergent
solver or a hung subscriber — see Wall-clock timeouts for a
subprocess recipe.
gmatpy single-init constraint¶
gmatpy cannot be cleanly reinitialised once it has been loaded into a Python
interpreter. gmat_run.bootstrap (and therefore
Mission.load) caches the loaded module and the
GmatInstall it was bound to; a second call requesting
a different install raises GmatLoadError. Calling
again with the same install is a no-op and returns the cached module.
Practical implication: a single Python process is bound to one GMAT install for its lifetime. If you need to compare missions across GMAT releases, run them in separate processes (subprocess, pytest workers, multiprocessing).
Supported Python versions¶
gmatpy ships per-Python-minor shared libraries inside the GMAT install's
bin/gmatpy/ directory. If your interpreter's minor version does not match
one of the prebuilt wheels in the install, bootstrap
raises GmatLoadError at import time.
R2026a ships builds for Python 3.10, 3.11, and 3.12. Older Python interpreters
or 3.13+ are not supported by the bundled gmatpy and cannot be made to work
without rebuilding GMAT from source.
Canonical-image drift is covered by a single CI canary¶
The main test matrix runs in action-mode (astro-tools/setup-gmat@v0) on
bare runners — it does not exercise the canonical ghcr.io/astro-tools/gmat
container image. A separate canonical-image-smoke CI cell runs pip install
gmat-run and a small Mission.load → run → assert non-empty ReportFile
round-trip inside ghcr.io/astro-tools/gmat:R2026a on every PR and every
push to main. It is the canary for image-vs-action drift: a stripped
plugin, a mangled gmat_startup_file.txt, a dropped Python ABI from the
bundled gmatpy set, or a tag that silently retargets a different GMAT
upload all surface here before downstream consumers running container-mode
CI hit them. The cell is pinned to :R2026a; the matrix coverage of the
image across multiple GMAT releases lives in setup-gmat itself.
R2022a is not exercised in CI¶
R2022a's bundled gmatpy tops out at Python 3.9 across all three OSes, while
gmat-run pins python>=3.10 in pyproject.toml. The two constraints are
incompatible — a CI cell on R2022a would have to drop the Python floor for
gmat-run as a whole or pin a one-off interpreter just for that cell. Neither
trade-off pays for itself given how few users run R2022a today.
R2022a is therefore listed as "expected to work" but is not exercised in CI. If you depend on R2022a and hit a regression, file an issue and we'll add a targeted cell.
Some integration tests are R2026a-only¶
The integration suite under tests/integration/ runs across the full CI
matrix, but a small handful of tests skip on GMAT releases other than R2026a:
test_attitude_inputs_*—Mission.attitude_inputswalksSpacecraftresources via gmatpy'sGetField("Attitude")accessor, whose return shape has drifted between releases. The discovery path is exercised on R2026a; tracking every release's accessor isn't worth the test churn.test_sample_round_trip[Ex_ContactLocatorAllFormats]— contact start/stop epochs drift by ~1 ms between R2025a and R2026a, andpandas.testing.assert_frame_equalignoresrtol/atolon datetime columns. Skipped rather than papered over with a coarser comparator.
Position-only ephemeris round-trips (Ex_LEOEphemeris, Ex_STKEphemeris)
run on every release with looser tolerances on non-R2026a runs to absorb
integrator/ephemeris drift; R2026a keeps strict tolerances so the regression
signal there isn't slackened.
If you're running the suite locally against R2025a (or any non-primary
release), expect a handful of SKIPPED lines for these tests. Real
gmat-run regressions still surface elsewhere.
Output paths must be set via Filename, not OUTPUT_PATH¶
FileManager.OUTPUT_PATH and GmatGlobal.SetOutputPath look like the right
knobs for redirecting ReportFile / EphemerisFile / ContactLocator
output, but they only take effect at parse time. Once
Mission.load has parsed a script, every subscriber
has its absolute output path resolved and cached internally; later changes to
the global OUTPUT_PATH are ignored at write time.
Mission.run handles this for you by rewriting each
subscriber's Filename field to an absolute path inside the run workspace.
If you bypass Mission.run and drive
gmat.RunScript() yourself, you need to do the same rewrite — set
<Subscriber>.Filename to an absolute path before calling RunScript.
GMAT R2026a does not write CCSDS-AEM¶
GMAT's EphemerisFile writer supports CCSDS-OEM, SPK, Code-500, and
STK-TimePosVel only — there is no FileFormat = CCSDS-AEM. Attitude history
in GMAT is a reader-side concept: a Spacecraft resource consumes an
external AEM file via its AttitudeFileName field. gmat-run surfaces those
input files via Mission.attitude_inputs
and parses them with gmat_run.parsers.aem_ephemeris.parse,
but you cannot ask GMAT to emit an AEM trace of a propagated spacecraft.
Parser format restrictions¶
The parsers in gmat_run.parsers cover the formats
GMAT actually emits; uncommon variants raise
GmatOutputParseError.
- CCSDS-OEM ephemeris: covariance blocks (
COVARIANCE_START…COVARIANCE_STOP) are skipped; acceleration columns past the mandatory six state components are rejected. A multi-segment file whose segments declare conflictingTIME_SYSTEMvalues is rejected, since the concatenated epoch column would mix time scales. - STK-TimePosVel ephemeris: the
EphemerisTimePosVelAcc(with acceleration) andEphemerisTimePos(position-only) data-section variants are rejected. - CCSDS-AEM attitude: only
QUATERNIONandEULER_ANGLEsegment types parse. Rate/derivative/spin variants (QUATERNION/DERIVATIVE,QUATERNION/RATE,EULER_ANGLE/RATE,SPIN,SPIN/NUTATION) are rejected. Multi-segment files are rejected when their segments mix differentATTITUDE_TYPEvalues (the column shapes are not concatenable) or conflictingTIME_SYSTEMvalues. - SPK ephemeris: the parser assumes one spacecraft per file (which matches GMAT's writer behaviour). Multi-target SPKs are rejected.
- Code-500 binary ephemeris: not implemented. No public tooling decodes the format, and GMAT does not exercise it in its stock R2026a samples.
Solver iteration logs¶
Results.solver_runs parses the per-Solver .data file
GMAT writes for a Target / Optimize run. A few properties of that file
shape what the mapping can offer.
- A shared
Solverkeeps only the last block's history. When severalTarget/Optimizeblocks name the sameSolverresource, GMAT writes all of them to one.datafile, each block overwriting the previous one. The surfaced DataFrame therefore covers the last block only;df.attrs["source_path"]points at the overwritten file. Declare a separateSolverresource per block to keep each block's iteration history. DifferentialCorrectorconvergence is derived, not stamped. Unlike the optimizers, a targeter's log ends with the same "Targeting Completed" line whether or not the goal was met. gmat-run computesconvergedfrom the last iteration's residual against its tolerance, sodf.attrs["converged"]for aDifferentialCorrectoris a library judgement, not a value read from the file.- Jacobians are not surfaced. A
DifferentialCorrectorlog also records the sensitivity matrix, its inverse, and scaled variable estimates. These are optimizer-specific (aYukonlog has no analogue) and are not parsed into the DataFrame. - Only the
Normalreport style is parsed. The parser targets the layout GMAT writes at the defaultSolver.ReportStyle = Normal.Concise,Verbose, andDebugstyles change the file structure and are not supported.
Epoch promotion is not a time-scale conversion¶
gmat_run.parsers.epoch.promote_epochs
turns the ten {scale}{format} GMAT epoch columns into
datetime64[ns] with the time scale recorded on df.attrs["epoch_scales"].
It does not apply leap-second-correct conversion between scales: a
TAIModJulian column becomes a datetime64[ns] representing the TAI instant,
labelled "TAI". For converting between scales after promotion, see
gmat_run.time, which routes A1/TAI/UTC/TT/TDB through
astropy.time.Time and is gated
behind the [astropy] extra.