Lossy conversions¶
The contract at the heart of orbit-formats: a conversion either preserves information or says so. Nothing is dropped silently.
The guarantee¶
- Same-format round-trips are lossless. Reading a format and writing it back recovers
the source — byte-for-byte when the read opted into
retain_source=True, otherwise content-lossless (every field preserved, canonically reformatted). This works because the read keeps the full per-format fidelity model onsource_native. - Cross-format conversions are lossless only where the formats overlap. When a target cannot hold something the source carried, the conversion still proceeds — but emits a structured, catchable warning naming exactly what was lost.
- A conversion that cannot be done without modelling is refused, not faked. Turning a mean-element set into a state series is a propagation; orbit-formats raises rather than guessing (see below).
The warning types¶
Every lossy warning descends from LossyConversionWarning, so one handler catches the whole
family. Each carries its dropped information as structured DroppedField(name, reason)
records — inspect what was lost as data, not by scraping a message string.
| Warning | Fires when |
|---|---|
DroppedFieldWarning |
the target format structurally cannot represent a value the source had (e.g. covariance, or maneuvers, written to a format with no block for them) |
PrecisionLossWarning |
a value is narrowed to fit a target's field width or numeric precision |
ModelApproximationWarning |
a cross-category conversion introduces a model step, so the result is model-dependent rather than an exact restatement |
MissingFieldWarning |
the source omitted a value the canonical form has room for; the slot is filled with NaN, never fabricated |
Catching them¶
A lossy conversion warns through the standard warnings machinery, so catch it the usual
way:
import warnings
from orbit_formats import read, LossyConversionWarning
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
state = read("positions_only.report", format="gmat-report")
for record in caught:
message = record.message
if isinstance(message, LossyConversionWarning):
print(message.fields) # e.g. ('VX', 'VY', 'VZ')
for dropped in message.dropped:
print(dropped.name, "—", dropped.reason)
The orbit-formats CLI surfaces the same warnings on stderr while the conversion still
succeeds; see the command-line interface.
Frame and time-transform scope¶
Cross-format conversion sometimes needs a real transform. orbit-formats draws the line deliberately:
- Time scales — supported. Converting an instant between
UTC,TAI,TT,TDB,GPS, andUT1is a lossless reinterpretation of the same instant; it runs internally through astropy (with leap seconds and bundled Earth-orientation data, no network access). - Frame rotation — supported. Rotating a Cartesian state between
TEME,EME2000/J2000,GCRF,ICRF, andITRFis a rigid, lossless change of axes. Passframe=toconvert(or--frameto the CLI) to request it; omitted, the source frame is kept. The rotation runs through astropy (precession / nutation for the inertial frames, the IERS Earth-orientation tables and the Earth-rotation rate for the terrestrial ITRF), read hermetically with no network access —GCRFandICRFare identical, so that pair is a no-op. It drops the byte-losslesssource_nativehandle, since the rotated state no longer matches the original bytes; the canonical content stays exact. A frame outside the set, or a mean-element set with no Cartesian state to rotate, raisesFrameRotationUnsupportedErrorrather than performing a naïve, un-modelled transform.
Frame rotation does not turn a mean-element set into an ephemeris: that step needs a
propagator, which is out of scope. The conversion is refused with
UnsupportedConversionError rather than approximated — the
conversion-capability matrix lists which paths are available.