Skip to content

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_inputs walks Spacecraft resources via gmatpy's GetField("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, and pandas.testing.assert_frame_equal ignores rtol/atol on 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_STARTCOVARIANCE_STOP) are skipped; acceleration columns past the mandatory six state components are rejected. A multi-segment file whose segments declare conflicting TIME_SYSTEM values is rejected, since the concatenated epoch column would mix time scales.
  • STK-TimePosVel ephemeris: the EphemerisTimePosVelAcc (with acceleration) and EphemerisTimePos (position-only) data-section variants are rejected.
  • CCSDS-AEM attitude: only QUATERNION and EULER_ANGLE segment 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 different ATTITUDE_TYPE values (the column shapes are not concatenable) or conflicting TIME_SYSTEM values.
  • 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 Solver keeps only the last block's history. When several Target / Optimize blocks name the same Solver resource, GMAT writes all of them to one .data file, 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 separate Solver resource per block to keep each block's iteration history.
  • DifferentialCorrector convergence 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 computes converged from the last iteration's residual against its tolerance, so df.attrs["converged"] for a DifferentialCorrector is a library judgement, not a value read from the file.
  • Jacobians are not surfaced. A DifferentialCorrector log also records the sensitivity matrix, its inverse, and scaled variable estimates. These are optimizer-specific (a Yukon log has no analogue) and are not parsed into the DataFrame.
  • Only the Normal report style is parsed. The parser targets the layout GMAT writes at the default Solver.ReportStyle = Normal. Concise, Verbose, and Debug styles 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.