space::irbem::api
cheatah-space v0.1.0-alpha — Biome Standard 0.6.5-alpha
Classes
MagneticCoordinates— The six per-point outputs of IRBEM'sMAKE_LSTAR, in one record.
Functions
The frame-pair transforms, in IRBEM's spelling.
Each is one line over transform, so the physics lives in exactly one place; what these add is the name, the argument order and the @test tag a caller can grep for. Geographic to solar-magnetospheric — the frame every external field model is defined in.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations. |
the same point in GSM.
O(1).
none.
IrbemApi.TransformsRoundTripSolar-magnetospheric to geographic.
gsm | the position in GSM, Earth radii. |
r | the epoch's rotations. |
the same point in GEO.
O(1).
none.
IrbemApi.TransformsRoundTripGeographic to solar-magnetic — the frame drift shells are traced in.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations. |
the same point in SM.
O(1).
none.
IrbemApi.TransformsRoundTripSolar-magnetic to geographic.
sm | the position in SM, Earth radii. |
r | the epoch's rotations. |
the same point in GEO.
O(1).
none.
IrbemApi.TransformsRoundTripGeographic to solar-ecliptic.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations. |
the same point in GSE.
O(1).
none.
IrbemApi.TransformsRoundTripSolar-ecliptic to geographic.
gse | the position in GSE, Earth radii. |
r | the epoch's rotations. |
the same point in GEO.
O(1).
none.
IrbemApi.TransformsRoundTripGeographic to centred-dipole geomagnetic.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations. |
the same point in MAG.
O(1).
none.
IrbemApi.TransformsRoundTripCentred-dipole geomagnetic to geographic.
mag | the position in MAG, Earth radii. |
r | the epoch's rotations. |
the same point in GEO.
O(1).
none.
IrbemApi.TransformsRoundTripGeographic to inertial.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations. |
the same point in GEI.
O(1).
none.
IrbemApi.TransformsRoundTripInertial to geographic.
gei | the position in GEI, Earth radii. |
r | the epoch's rotations. |
the same point in GEO.
O(1).
none.
IrbemApi.TransformsRoundTripSolar-magnetospheric to solar-magnetic.
gsm | the position in GSM, Earth radii. |
r | the epoch's rotations. |
the same point in SM.
O(1).
none.
IrbemApi.TransformsRoundTripSolar-magnetic to solar-magnetospheric.
sm | the position in SM, Earth radii. |
r | the epoch's rotations. |
the same point in GSM.
O(1).
none.
IrbemApi.TransformsRoundTripHeliocentric Aries Ecliptic to Heliocentric Earth Ecliptic — a rotation by the Earth's heliocentric longitude, with no origin shift (both frames are centred on the Sun).
V | the vector kind, Position or FieldVector; deduced. |
hae | the HAE components. |
g | the epoch geometry from helio_geometry. |
the same physical vector in HAE.
O(1).
none.
IrbemApi.HeliosphericPairsRoundTripHeliocentric Earth Ecliptic to Heliocentric Aries Ecliptic.
V | the vector kind, Position or FieldVector; deduced. |
hee | the HEE components. |
g | the epoch geometry from helio_geometry. |
the same physical vector in HAE.
O(1).
none.
IrbemApi.HeliosphericPairsRoundTripHeliocentric Aries Ecliptic to Heliocentric Earth Equatorial — into the frame of the solar equator, with +X on the solar central meridian.
V | the vector kind, Position or FieldVector; deduced. |
hae | the HAE components. |
g | the epoch geometry from helio_geometry. |
the same physical vector in HEEQ.
O(1).
none.
IrbemApi.HeliosphericPairsRoundTripHeliocentric Earth Equatorial to Heliocentric Aries Ecliptic.
V | the vector kind, Position or FieldVector; deduced. |
heeq | the HEEQ components. |
g | the epoch geometry from helio_geometry. |
the same physical vector in HAE.
O(1).
none.
IrbemApi.HeliosphericPairsRoundTripgse2hee · 2 overloads
FieldVector< Frame::HEE > gse2hee(const FieldVector< Frame::GSE > &gse, const HelioGeometry &g)#Solar-ecliptic to Heliocentric Earth Ecliptic, for a POSITION: the half turn about the ecliptic pole AND the shift from the geocentric to the heliocentric origin.
gse | the GSE position, Earth radii. |
g | the epoch geometry. |
the HEE position, Earth radii.
O(1).
none.
IrbemApi.HeliosphericOriginShiftAppliesToPositionsOnlyhee2gse · 2 overloads
FieldVector< Frame::GSE > hee2gse(const FieldVector< Frame::HEE > &hee, const HelioGeometry &g)#Heliocentric Earth Ecliptic to solar-ecliptic, for a POSITION.
hee | the HEE position, Earth radii. |
g | the epoch geometry. |
the GSE position, Earth radii.
O(1).
none.
IrbemApi.HeliosphericOriginShiftAppliesToPositionsOnlyThe largest batch the IRBEM-compatible entry points accept, matching the reference's ntime_max.
100000, the value IRBEM's generated ntime_max.inc carries; confirmed by calling get_irbem_ntime_max1_ on the shipped shared library.
O(1).
none.
IrbemApi.LibraryInfoMatchesTheReferenceWhich IGRF generation the internal field implements.
14 — IGRF-14, the generation whose coefficients tables/igrf14.hpp carries. The shipped reference returns the same, measured.
O(1).
none.
IrbemApi.LibraryInfoMatchesTheReferenceImplementation version — the shim standing in for IRBEM's IRBEM_FORTRAN_VERSION.
a monotonically increasing integer identifying THIS C++ implementation. It is NOT an IRBEM revision: see implementation_version for why returning a plausible-looking one would be worse than useless to a caller feature-detecting against it.
O(1).
none.
IrbemApi.LibraryInfoMatchesTheReferenceImplementation release tag — the shim standing in for IRBEM_FORTRAN_RELEASE.
a static, never-empty view naming this implementation in words, so a log line carrying it cannot be mistaken for the reference's own tag (measured: a git short hash, e7cecb0, space-padded into an 80-character buffer).
O(1).
none — the view refers to a string literal with static storage duration.
IrbemApi.LibraryInfoMatchesTheReferenceResult< Rotations > rotations_at(int year, int doy, double ut_seconds, const Igrf< NMAX > &igrf)
#
Build the rotations an epoch needs — the argument every transform below shares.
Hoisted deliberately: IRBEM rebuilds this per call, but the matrices depend only on the epoch, so a batch over one timestamp should pay for the trigonometry once. This is the single largest structural difference between calling the two libraries in a loop.
year | the calendar year, e.g. |
doy | the day of year, 1-based. |
ut_seconds | seconds of UT within the day. |
igrf | the internal-field model the dipole axis is taken from. |
the rotations, or Status::DomainError when the epoch is not representable.
O(1) — a fixed number of trigonometric evaluations.
none.
IrbemApi.RotationsAreBuiltOncePerEpochGeodetic (altitude km, latitude deg, east longitude deg) to geocentric Cartesian.
gdz | the geodetic position. |
the same point in GEO, Earth radii.
O(1).
none.
IrbemApi.GeodeticRoundTripsGeocentric Cartesian to geodetic.
geo | the position in GEO, Earth radii. |
the same point as GDZ.
O(1) — a bounded closed-form inversion, not an iteration to tolerance.
none.
IrbemApi.GeodeticRoundTripsSpherical (radius Re, latitude deg, east longitude deg) to Cartesian.
sph | the spherical position. |
the same point in GEO, Earth radii.
O(1).
none.
IrbemApi.SphericalRoundTripsCartesian to spherical.
East longitude comes back in (-180, 180], this module's convention everywhere; coord_trans with sysaxes_out = 7 folds the SAME answer into the reference's [0, 360) instead. Both name one meridian; see detail::sph_in_irbem_longitude.
car | the position in GEO, Earth radii. |
the same point as SPH.
O(1).
none.
IrbemApi.SphericalRoundTrips IrbemApi.CoordTransUsesTheReferenceLongitudeConventionsRadius/latitude/longitude to geodetic.
rll | the RLL position. |
the same point as GDZ.
O(1).
none.
IrbemApi.GeodeticRoundTripsMagnetic local time at a geographic position — IRBEM's GET_MLT.
MLT is the SM azimuth read as a clock: MLT = 12 + λ_SM / 15°, so local magnetic noon (the SM +X half-plane, which by construction contains the Sun-facing side of the dipole meridian) is 12 h and midnight is 0 h. Nothing but the SM rotation enters it, which is why this routine takes the epoch's Rotations and no field model.
Verified as a black box against the shipped reference: over forty points spanning 1965–2029, get_mlt1_ agrees with this expression to 9.6e-5 h — 0.34 s of local time — once the dipole epochs are matched (see the file brief). That is the measurement that fixed the definition; no Fortran was read.
geo | the position in GEO, Earth radii. |
r | the epoch's rotations, built once by rotations_at. |
MLT in hours, folded into [0, 24). Status::DomainError for a non-finite input component, and for a point ON the dipole axis, where the SM meridian — and therefore MLT — is not defined; the value returned there is the 12.0 that atan2(0, 0) produces, so a caller that ignores the status still gets a number in range rather than a NaN.
O(1) — one 3×3 product and one atan2.
none.
IrbemApi.MltIsTheSolarMagneticClockAngle IrbemApi.MltMatchesTheReferenceResult< bool > get_mlt_vec(std::span< const Position< Frame::GEO > > geo, std::span< const Rotations > rotations, std::span< double > mlt, std::span< Status > statuses)
#
Magnetic local time for a whole ephemeris — the ntime form of get_mlt.
geo | the positions in GEO, Earth radii. |
rotations | either ONE rotation set shared by every point, or one per point. The first is the common case and is why this form exists: a satellite pass is one epoch's worth of geometry over thousands of samples, and rebuilding it per sample is ~14 transcendental calls against the nine multiplies of the transform itself. |
mlt | receives one MLT per input, hours in |
statuses | receives each point's status; same length as |
Status::Ok when every point converted, the first failing point's status otherwise, and Status::DomainError for a length mismatch or a rotations span that is neither 1 nor geo.size() long. The value is whether a device serviced the call: always false, and see the file brief's batch-lane table for why that is a measurement rather than a missing feature.
O(n).
none — everything is written through the caller's spans.
IrbemApi.MltBatchAgreesWithTheScalarLane IrbemApi.BatchRejectsMismatchedSpansResult< fixarray::vec3d > coord_trans(int sysaxes_in, int sysaxes_out, const fixarray::vec3d &in, const Rotations &r)
#
One point through the runtime frame-pair dispatcher — the scalar half of COORD_TRANS_VEC.
This is the ONE routine in the module where the frames are runtime values rather than template arguments, which is exactly why it is written as a hub through GEO over frame_from_sysaxes rather than as a switch that picks a default. An unrecognised code is reported; it never silently becomes GEO. (The reference prints sysaxesOUT out of range ! to stdout and returns baddata — measured — which a caller redirecting stdout will never see.)
sysaxes_in | the input frame's IRBEM code, |
sysaxes_out | the output frame's IRBEM code, |
in | the input components, in whatever units |
r | the epoch's rotations, built once by rotations_at. |
the components in the output frame. Status::DomainError when either code is outside 0..8 (see the file brief on codes 9–14) or when any input component is not finite.
SPH output carries east longitude in [0, 360), which is the reference's convention for sysaxes = 7 and NOT the (-180, 180] the typed car2sph returns — see detail::sph_in_irbem_longitude for the measurement. GDZ and RLL output keep (-180, 180], because that is what the reference returns for codes 0 and 8. On the polar axis this module's GDZ altitude is right and the reference's is not; the file brief has the numbers.
O(1) — at most two 3×3 products plus one closed-form geodetic conversion per side.
none.
IrbemApi.CoordTransCoversEveryFramePair IrbemApi.CoordTransReportsBadSysaxes IrbemApi.CoordTransMatchesTheReference IrbemApi.CoordTransUsesTheReferenceLongitudeConventions IrbemApi.CoordTransIsRightWhereTheReferenceIsWrongOnThePolarAxisResult< bool > coord_trans_vec(int sysaxes_in, int sysaxes_out, std::span< const fixarray::vec3d > in, std::span< fixarray::vec3d > out, std::span< const Rotations > rotations, std::span< Status > statuses)
#
COORD_TRANS_VEC — a whole ephemeris through one frame pair.
sysaxes_in | the input frame's IRBEM code, |
sysaxes_out | the output frame's IRBEM code, |
in | the input components, one triple per point. |
out | receives the converted components; same length as |
rotations | either ONE rotation set shared by every point, or one per point — IRBEM takes a per-point |
statuses | receives each point's status; same length as |
Status::Ok when every point converted, the first failing point's status otherwise, and Status::DomainError for a bad frame code or a length mismatch — in which case nothing is written at all, since the fault is in the call and not in the data. The value is whether a device serviced the call: always false; see the file brief's batch table.
O(n).
none.
IrbemApi.CoordTransVecAgreesWithTheScalarLane IrbemApi.CoordTransVecTakesPerPointEpochs IrbemApi.BatchRejectsMismatchedSpansResult< bool > transform_vec(std::span< const V< From > > in, std::span< V< To > > out, std::span< const Rotations > rotations)
#
The typed ntime transform: a whole ephemeris through ONE compile-time frame pair.
coord_trans_vec is the runtime-code lane and pays a switch per point per side; this is the lane for code that knows its frames, and it hoists the 3×3 out of the loop when the epoch is shared, leaving nine multiplies and six adds per point with no branch and no dispatch. Both exist because both callers exist — a generic converter driven by a configuration file, and an inner loop that has known it was going GEO→GSM since it was written.
To | the destination frame. |
V | the tagged vector template — Position or FieldVector; deduced. |
From | the source frame; deduced. |
in | the input values. |
out | receives the transformed values; same length as |
rotations | either ONE rotation set shared by every point, or one per point. |
Status::Ok, or Status::DomainError for a length mismatch or a rotations span that is neither 1 nor in.size() long. The value is whether a device serviced the call: always false; see the file brief's batch table for the measurement behind that.
O(n) — one 3×3 product per point, and ONE matrix build per call when the epoch is shared rather than one per point.
none.
IrbemApi.TypedBatchAgreesWithTheScalarTransform IrbemApi.BatchRejectsMismatchedSpansResult< bool > make_lstar_vec(const Igrf< NMAX > &model, const Rotations &rotations, std::span< const Position< Frame::GEO > > starts, std::span< MagneticCoordinates > out, std::span< Status > statuses, ExternalModel kext=ExternalModel::None, double pitch_angle_deg=90.0, const DriftShellOptions &opt={})
#
MAKE_LSTAR for a whole ephemeris — this is the routine to call, and the reason the batch forms exist at all.
One L* point is Nder = 25 independent root-finds of a few field lines each. That is an order of magnitude below the ~512-line crossover gpu/dispatch.hpp measures, so a single point runs on the host no matter what hardware is present. Hand over ntime points and every stage of the root-find becomes ntime × Nder wide, which is where the trace kernel's measured 48.9× lands. A loop calling make_lstar per point cannot be accelerated — that is a property of the problem, not of this implementation.
Measured through THIS entry point on an RTX 3070 Ti against its own fp64 host lane (-O3 -march=native, IGRF-14, points spread over L = 3…5, Nder = 25):
points | host | device | |
|---|---|---|---|
64 | 13.1 ms/point | 20.2 ms/point | device loses 0.65× |
512 | 13.8 ms/point | 0.74 ms/point | device wins 18.6× |
The crossover is the one gpu/dispatch.hpp measures, reached here in POINTS rather than lines because each point contributes Nder of them. Re-measured on an RTX 3070 Ti with the device flag confirmed true: 20.16 ms/point at n = 64 against the host's 13.13 (device loses 0.65×) and 0.739 ms/point at n = 512 against 13.77 (device wins 18.6×). Both lanes are bit-identical run-to-run, and the device lane also serves the call on llvmpipe (8.27 ms/point) and on an Intel iGPU (14.42), so the shape is not NVIDIA-only.
The device lane traces in fp32, and over 512 points spread across L = 3…5 the two lanes' L* differ by at most 4.2e-3 absolute and L_m by 4.0e-3 — inside docs/ERROR_BUDGET.md's 0.01 absolute on L*, which is what makes the fp32 trace admissible, but ABOVE its 1e-3 on L_m, so a caller who needs L_m to that budget must pin the host lane with CHEATAH_SPACE_IRBEM_NO_GPU=1. (An earlier revision of this comment quoted 2.8e-3 and 2.4e-3; those are one point set's numbers, not the routine's.)
ONE CONSEQUENCE THAT BITES: which lane runs depends on the BATCH SIZE, so make_lstar_vec over n points and n separate make_lstar calls are NOT bit-identical on a machine with a device. n = 1 is 25 field lines and stays on the host; n = 6 is 150, which crosses irbem_igrf_f32's measured crossover of 128, and the flux quadrature then runs in fp32 on the device. The two answers agree to the budget above, not to the last bit. == between the two lanes is only meaningful with the device pinned off, which is what IrbemApi.MakeLstarVecAgreesWithTheScalarLane does.
The epoch is ONE Rotations for the whole batch, not one per point as IRBEM takes. That is deliberate: the drift shell is organised about the dipole axis, so points at different epochs belong to different shells and cannot share a dispatch. Splitting the batch internally would hand back the speed the batch form exists to provide, silently. Group by epoch in the caller, which is what a caller must do anyway to get the throughput.
NMAX | the IGRF truncation degree. |
model | the internal field model, already built for the epoch. |
rotations | the epoch's rotations, shared by every point. |
starts | the points, GEO, Earth radii. |
out | receives one record per point; same length as |
statuses | receives each point's status; same length as |
kext | IRBEM's external-field key. Only ExternalModel::None is supported; see detail::external_model_supported and the section comment above for why an unsupported key is refused rather than silently answered with the internal field. |
pitch_angle_deg | the local pitch angle; 90° is IRBEM's |
opt | the drift-shell resolution; DriftShellOptions::from_irbem translates IRBEM's |
the statuses make_lstar_batch reports, or Status::ParametersMissing / Status::DomainError for an unsupported or unrecognised kext, in which case nothing is computed and every output stays at baddata. The value is true when the device serviced the traces.
O(points × Nder × (trials + iterations)) traces; concurrent on the device.
as make_lstar_batch — O(rounds) vectors for the root-find, nothing per trace.
IrbemApi.MakeLstarVecAgreesWithTheScalarLane IrbemApi.MakeLstarVecDeviceLaneAgreesWithTheHostLane IrbemApi.MakeLstarReportsAnUnsupportedExternalModel IrbemApi.MakeLstarMatchesTheOracleResult< MagneticCoordinates > make_lstar(const Igrf< NMAX > &model, const Rotations &rotations, const Position< Frame::GEO > &start, ExternalModel kext=ExternalModel::None, double pitch_angle_deg=90.0, const DriftShellOptions &opt={})
#
MAKE_LSTAR for one point — the reference lane, not the fast one.
NMAX | the IGRF truncation degree. |
model | the internal field model, already built for the epoch. |
rotations | the epoch's rotations. |
start | the point, GEO, Earth radii. |
kext | IRBEM's external-field key; only ExternalModel::None is supported. |
pitch_angle_deg | the local pitch angle; 90° is IRBEM's |
opt | the drift-shell resolution. |
the six outputs and the point's status. Every output is baddata when kext is not supported; otherwise the record is filled even on a failed shell, because L_m, I and the fields are what make the failure diagnosable.
O(Nder × (trials + iterations)) traces — ~10⁵ field evaluations, ~15 ms on the host.
as make_lstar_vec at n = 1.
IrbemApi.MakeLstarMatchesTheOracle IrbemApi.MakeLstarVecAgreesWithTheScalarLaneResult< bool > make_lstar_shell_splitting(const Igrf< NMAX > &model, const Rotations &rotations, std::span< const Position< Frame::GEO > > starts, std::span< const double > pitch_angles_deg, std::span< MagneticCoordinates > out, std::span< Status > statuses, ExternalModel kext=ExternalModel::None, const DriftShellOptions &opt={})
#
MAKE_LSTAR_SHELL_SPLITTING — the same six outputs for every (point, pitch angle) pair.
Shell splitting is the phenomenon that particles of different pitch angle at the SAME point drift on DIFFERENT shells once the field is not a dipole, so L* is a function of α and not just of position. Computationally it is the batch form's best case: ntime × Nipa points that all share one epoch, so the whole grid is one dispatch of ntime × Nipa × Nder field lines.
NMAX | the IGRF truncation degree. |
model | the internal field model, already built for the epoch. |
rotations | the epoch's rotations, shared by every point. |
starts | the points, GEO, Earth radii. |
pitch_angles_deg | the local pitch angles, applied to every point — IRBEM's |
out | receives |
statuses | receives one status per record; same length as |
kext | IRBEM's external-field key; only ExternalModel::None is supported. |
opt | the drift-shell resolution. |
the statuses make_lstar_batch reports, or Status::ParametersMissing / Status::DomainError for an unsupported kext. Status::DomainError also for a length mismatch. The value is true when the device serviced the traces.
O(points × angles × Nder × (trials + iterations)) traces; concurrent on the device.
one point vector and one pitch vector of points × angles, plus what make_lstar_batch allocates; nothing per trace.
IrbemApi.ShellSplittingVariesLstarWithPitchAngle IrbemApi.MakeLstarReportsAnUnsupportedExternalModelLSTAR_PHI — convert between Roederer's L* and the third invariant Φ.
Φ = 2π k₀ / L*, the relation driftshell.hpp computes L* with, and the one the reference documents for LSTAR_PHI as Φ = 2π B₀/L*. The reference takes iyear and idoy, and MEASURED it uses that epoch's own dipole moment rather than a frozen one: lstar_phi1_(whichinv = 1) at L* = 4 returns 48599.478358059241 nT·Re² in 1965 and 46590.805305975191 in 2029, whose implied B₀ — 30939.3888 and 29660.6279 nT — is dipole_moment of Igrf<>::at(year + 0.5) to the last bit. So this routine reproduces the reference EXACTLY, 0 to 1.5e-16 relative over 1965, 1990, 2015 and 2029 in both directions, and there is no convention to reconcile. Taking k₀ from the epoch is therefore agreement, not the divergence an earlier revision of this comment claimed. mcilwain_l documents why a frozen moment would be wrong: it has fallen several percent over the last century, and freezing it shows up as a constant offset at every shell rather than as an obvious error.
whichinv | IRBEM's direction code: |
value | the |
dipole_moment_nt | the epoch's dipole moment |
the converted quantity, or Status::DomainError for a whichinv outside 1..2, a non-positive or non-finite value, or a non-positive moment. The relation is a reciprocal, so zero has no image in either direction and is refused rather than turned into an infinity.
O(1).
none.
IrbemApi.LstarPhiInvertsItself IrbemApi.LstarPhiMatchesTheReferenceDay of year from a calendar date, leap-year correct.
year | the year. |
month | 1-12. |
day | 1-based day of month. |
the day of year, 1-based.
O(1).
none.
IrbemApi.DateRoundTripsThe Julian Day Number of a calendar date — IRBEM's JULDAY.
year | the year. |
month | 1-12. |
day | 1-based day of month. |
the Julian Day Number, which begins at NOON of the given date.
O(1).
none.
IrbemApi.DateRoundTripsThe calendar date of a Julian Day Number — IRBEM's CALDAT, the inverse of julday.
jdn | the Julian Day Number. |
the year, month and day.
O(1).
none.
IrbemApi.DateRoundTripsA calendar date and time as a decimal year — the epoch argument the field models take.
year | the year. |
month | 1-12. |
day | 1-based. |
hour | 0-23. |
minute | 0-59. |
second | 0-59. |
the decimal year.
O(1).
none.
IrbemApi.DateRoundTripsA decimal year back to a full broken-down date and time — IRBEM's DECY2DATE_AND_TIME.
decy | the decimal year, where |
the broken-down date and time.
O(1).
none.
IrbemApi.DateRoundTripsA year, day-of-year and UT as a full broken-down date and time.
year | the year. |
doy | 1-based day of year. |
ut_seconds | seconds since midnight. |
the broken-down date and time.
O(1).
none.
IrbemApi.DateRoundTripsConstants & variables
Hours of magnetic local time per radian of SM longitude: a full turn is 24 h.
