cheatah
Module

space::irbem::api

cheatah-space v0.1.0-alpha — Biome Standard 0.6.5-alpha

Classes

Functions

fn Position< Frame::GSM > geo2gsm(const Position< Frame::GEO > &geo, const Rotations &r) #

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.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations.

Returns

the same point in GSM.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEO > gsm2geo(const Position< Frame::GSM > &gsm, const Rotations &r) #

Solar-magnetospheric to geographic.

Parameters
gsm

the position in GSM, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEO.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::SM > geo2sm(const Position< Frame::GEO > &geo, const Rotations &r) #

Geographic to solar-magnetic — the frame drift shells are traced in.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations.

Returns

the same point in SM.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEO > sm2geo(const Position< Frame::SM > &sm, const Rotations &r) #

Solar-magnetic to geographic.

Parameters
sm

the position in SM, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEO.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GSE > geo2gse(const Position< Frame::GEO > &geo, const Rotations &r) #

Geographic to solar-ecliptic.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations.

Returns

the same point in GSE.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEO > gse2geo(const Position< Frame::GSE > &gse, const Rotations &r) #

Solar-ecliptic to geographic.

Parameters
gse

the position in GSE, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEO.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::MAG > geo2mag(const Position< Frame::GEO > &geo, const Rotations &r) #

Geographic to centred-dipole geomagnetic.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations.

Returns

the same point in MAG.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEO > mag2geo(const Position< Frame::MAG > &mag, const Rotations &r) #

Centred-dipole geomagnetic to geographic.

Parameters
mag

the position in MAG, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEO.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEI > geo2gei(const Position< Frame::GEO > &geo, const Rotations &r) #

Geographic to inertial.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEI.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GEO > gei2geo(const Position< Frame::GEI > &gei, const Rotations &r) #

Inertial to geographic.

Parameters
gei

the position in GEI, Earth radii.

r

the epoch's rotations.

Returns

the same point in GEO.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::SM > gsm2sm(const Position< Frame::GSM > &gsm, const Rotations &r) #

Solar-magnetospheric to solar-magnetic.

Parameters
gsm

the position in GSM, Earth radii.

r

the epoch's rotations.

Returns

the same point in SM.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn Position< Frame::GSM > sm2gsm(const Position< Frame::SM > &sm, const Rotations &r) #

Solar-magnetic to solar-magnetospheric.

Parameters
sm

the position in SM, Earth radii.

r

the epoch's rotations.

Returns

the same point in GSM.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.TransformsRoundTrip
fn V< Frame::HEE > hae2hee(const V< Frame::HAE > &hae, const HelioGeometry &g) #

Heliocentric 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).

Template parameters
V

the vector kind, Position or FieldVector; deduced.

Parameters
hae

the HAE components.

g

the epoch geometry from helio_geometry.

Returns

the same physical vector in HAE.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericPairsRoundTrip
fn V< Frame::HAE > hee2hae(const V< Frame::HEE > &hee, const HelioGeometry &g) #

Heliocentric Earth Ecliptic to Heliocentric Aries Ecliptic.

Template parameters
V

the vector kind, Position or FieldVector; deduced.

Parameters
hee

the HEE components.

g

the epoch geometry from helio_geometry.

Returns

the same physical vector in HAE.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericPairsRoundTrip
fn V< Frame::HEEQ > hae2heeq(const V< Frame::HAE > &hae, const HelioGeometry &g) #

Heliocentric Aries Ecliptic to Heliocentric Earth Equatorial — into the frame of the solar equator, with +X on the solar central meridian.

Template parameters
V

the vector kind, Position or FieldVector; deduced.

Parameters
hae

the HAE components.

g

the epoch geometry from helio_geometry.

Returns

the same physical vector in HEEQ.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericPairsRoundTrip
fn V< Frame::HAE > heeq2hae(const V< Frame::HEEQ > &heeq, const HelioGeometry &g) #

Heliocentric Earth Equatorial to Heliocentric Aries Ecliptic.

Template parameters
V

the vector kind, Position or FieldVector; deduced.

Parameters
heeq

the HEEQ components.

g

the epoch geometry from helio_geometry.

Returns

the same physical vector in HAE.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericPairsRoundTrip
fn gse2hee · 2 overloads
Position< Frame::HEE > gse2hee(const Position< Frame::GSE > &gse, const HelioGeometry &g)#
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.

Parameters
gse

the GSE position, Earth radii.

g

the epoch geometry.

Returns

the HEE position, Earth radii.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericOriginShiftAppliesToPositionsOnly
fn hee2gse · 2 overloads
Position< Frame::GSE > hee2gse(const Position< Frame::HEE > &hee, const HelioGeometry &g)#
FieldVector< Frame::GSE > hee2gse(const FieldVector< Frame::HEE > &hee, const HelioGeometry &g)#

Heliocentric Earth Ecliptic to solar-ecliptic, for a POSITION.

Parameters
hee

the HEE position, Earth radii.

g

the epoch geometry.

Returns

the GSE position, Earth radii.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.HeliosphericOriginShiftAppliesToPositionsOnly
fn int get_irbem_ntime_max() #

The largest batch the IRBEM-compatible entry points accept, matching the reference's ntime_max.

Returns

100000, the value IRBEM's generated ntime_max.inc carries; confirmed by calling get_irbem_ntime_max1_ on the shipped shared library.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.LibraryInfoMatchesTheReference
fn int get_igrf_version() #

Which IGRF generation the internal field implements.

Returns

14 — IGRF-14, the generation whose coefficients tables/igrf14.hpp carries. The shipped reference returns the same, measured.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.LibraryInfoMatchesTheReference
fn int irbem_fortran_version() #

Implementation version — the shim standing in for IRBEM's IRBEM_FORTRAN_VERSION.

Returns

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.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.LibraryInfoMatchesTheReference
fn std::string_view irbem_fortran_release() #

Implementation release tag — the shim standing in for IRBEM_FORTRAN_RELEASE.

Returns

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).

Complexity

O(1).

Allocation

none — the view refers to a string literal with static storage duration.

Unit testIrbemApi.LibraryInfoMatchesTheReference
fn Result< 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.

Parameters
year

the calendar year, e.g. 2015.

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.

Returns

the rotations, or Status::DomainError when the epoch is not representable.

Complexity

O(1) — a fixed number of trigonometric evaluations.

Allocation

none.

Unit testIrbemApi.RotationsAreBuiltOncePerEpoch
fn Position< Frame::GEO > gdz2geo(const Position< Frame::GDZ > &gdz) #

Geodetic (altitude km, latitude deg, east longitude deg) to geocentric Cartesian.

Parameters
gdz

the geodetic position.

Returns

the same point in GEO, Earth radii.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.GeodeticRoundTrips
fn Position< Frame::GDZ > geo2gdz(const Position< Frame::GEO > &geo) #

Geocentric Cartesian to geodetic.

Parameters
geo

the position in GEO, Earth radii.

Returns

the same point as GDZ.

Complexity

O(1) — a bounded closed-form inversion, not an iteration to tolerance.

Allocation

none.

Unit testIrbemApi.GeodeticRoundTrips
fn Position< Frame::GEO > sph2car(const Position< Frame::SPH > &sph) #

Spherical (radius Re, latitude deg, east longitude deg) to Cartesian.

Parameters
sph

the spherical position.

Returns

the same point in GEO, Earth radii.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.SphericalRoundTrips
fn Position< Frame::SPH > car2sph(const Position< Frame::GEO > &car) #

Cartesian 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.

Parameters
car

the position in GEO, Earth radii.

Returns

the same point as SPH.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.SphericalRoundTrips IrbemApi.CoordTransUsesTheReferenceLongitudeConventions
fn Position< Frame::GDZ > rll2gdz(const Position< Frame::RLL > &rll) #

Radius/latitude/longitude to geodetic.

Parameters
rll

the RLL position.

Returns

the same point as GDZ.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.GeodeticRoundTrips
fn Result< double > get_mlt(const Position< Frame::GEO > &geo, const Rotations &r) #

Magnetic 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.

Parameters
geo

the position in GEO, Earth radii.

r

the epoch's rotations, built once by rotations_at.

Returns

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.

Complexity

O(1) — one 3×3 product and one atan2.

Allocation

none.

Unit testIrbemApi.MltIsTheSolarMagneticClockAngle IrbemApi.MltMatchesTheReference
fn Result< 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.

Parameters
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 [0, 24); same length as geo.

statuses

receives each point's status; same length as geo.

Returns

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.

Complexity

O(n).

Allocation

none — everything is written through the caller's spans.

Unit testIrbemApi.MltBatchAgreesWithTheScalarLane IrbemApi.BatchRejectsMismatchedSpans
fn Result< 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.)

Parameters
sysaxes_in

the input frame's IRBEM code, 0..8.

sysaxes_out

the output frame's IRBEM code, 0..8.

in

the input components, in whatever units sysaxes_in means; see kind_of.

r

the epoch's rotations, built once by rotations_at.

Returns

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.

Note

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.

Complexity

O(1) — at most two 3×3 products plus one closed-form geodetic conversion per side.

Allocation

none.

Unit testIrbemApi.CoordTransCoversEveryFramePair IrbemApi.CoordTransReportsBadSysaxes IrbemApi.CoordTransMatchesTheReference IrbemApi.CoordTransUsesTheReferenceLongitudeConventions IrbemApi.CoordTransIsRightWhereTheReferenceIsWrongOnThePolarAxis
fn Result< 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.

Parameters
sysaxes_in

the input frame's IRBEM code, 0..8.

sysaxes_out

the output frame's IRBEM code, 0..8.

in

the input components, one triple per point.

out

receives the converted components; same length as in.

rotations

either ONE rotation set shared by every point, or one per point — IRBEM takes a per-point iyr/idoy/secs, and both shapes are real: an ephemeris resampled to a single epoch wants the first, a multi-year survey the second.

statuses

receives each point's status; same length as in.

Returns

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.

Complexity

O(n).

Allocation

none.

Unit testIrbemApi.CoordTransVecAgreesWithTheScalarLane IrbemApi.CoordTransVecTakesPerPointEpochs IrbemApi.BatchRejectsMismatchedSpans
fn Result< 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.

Template parameters
To

the destination frame.

V

the tagged vector template — Position or FieldVector; deduced.

From

the source frame; deduced.

Parameters
in

the input values.

out

receives the transformed values; same length as in.

rotations

either ONE rotation set shared by every point, or one per point.

Returns

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.

Complexity

O(n) — one 3×3 product per point, and ONE matrix build per call when the epoch is shared rather than one per point.

Allocation

none.

Unit testIrbemApi.TypedBatchAgreesWithTheScalarTransform IrbemApi.BatchRejectsMismatchedSpans
fn Result< 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.

Template parameters
NMAX

the IGRF truncation degree.

Parameters
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 starts.

statuses

receives each point's status; same length as starts.

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 MAKE_LSTAR convention.

opt

the drift-shell resolution; DriftShellOptions::from_irbem translates IRBEM's options(3)/options(4) so a differential comparison is at matched resolution.

Returns

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.

Complexity

O(points × Nder × (trials + iterations)) traces; concurrent on the device.

Allocation

as make_lstar_batch — O(rounds) vectors for the root-find, nothing per trace.

Unit testIrbemApi.MakeLstarVecAgreesWithTheScalarLane IrbemApi.MakeLstarVecDeviceLaneAgreesWithTheHostLane IrbemApi.MakeLstarReportsAnUnsupportedExternalModel IrbemApi.MakeLstarMatchesTheOracle
fn Result< 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.

Template parameters
NMAX

the IGRF truncation degree.

Parameters
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 MAKE_LSTAR convention.

opt

the drift-shell resolution.

Returns

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.

Complexity

O(Nder × (trials + iterations)) traces — ~10⁵ field evaluations, ~15 ms on the host.

Allocation

as make_lstar_vec at n = 1.

Unit testIrbemApi.MakeLstarMatchesTheOracle IrbemApi.MakeLstarVecAgreesWithTheScalarLane
fn Result< 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.

Template parameters
NMAX

the IGRF truncation degree.

Parameters
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 alpha.

out

receives starts.size() × pitch_angles_deg.size() records, POINT-MAJOR: the record for point i and pitch angle k is at i * pitch_angles_deg.size() + k. IRBEM's Fortran array is (Nipa, ntime), which is the same layout read the other way round.

statuses

receives one status per record; same length as out.

kext

IRBEM's external-field key; only ExternalModel::None is supported.

opt

the drift-shell resolution.

Returns

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.

Complexity

O(points × angles × Nder × (trials + iterations)) traces; concurrent on the device.

Allocation

one point vector and one pitch vector of points × angles, plus what make_lstar_batch allocates; nothing per trace.

Unit testIrbemApi.ShellSplittingVariesLstarWithPitchAngle IrbemApi.MakeLstarReportsAnUnsupportedExternalModel
fn Result< double > lstar_phi(int whichinv, double value, double dipole_moment_nt) #

LSTAR_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.

Parameters
whichinv

IRBEM's direction code: 1 converts L* to Φ, 2 converts Φ to L*.

value

the L* in Earth radii (whichinv = 1) or the Φ in nT·Re² (whichinv = 2).

dipole_moment_nt

the epoch's dipole moment k₀ in nT; use dipole_moment.

Returns

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.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.LstarPhiInvertsItself IrbemApi.LstarPhiMatchesTheReference
fn int get_doy(int year, int month, int day) #

Day of year from a calendar date, leap-year correct.

Parameters
year

the year.

month

1-12.

day

1-based day of month.

Returns

the day of year, 1-based.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips
fn std::int64_t julday(int year, int month, int day) #

The Julian Day Number of a calendar date — IRBEM's JULDAY.

Parameters
year

the year.

month

1-12.

day

1-based day of month.

Returns

the Julian Day Number, which begins at NOON of the given date.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips
fn CalendarDate caldat(std::int64_t jdn) #

The calendar date of a Julian Day Number — IRBEM's CALDAT, the inverse of julday.

Parameters
jdn

the Julian Day Number.

Returns

the year, month and day.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips
fn double date_and_time2decy(int year, int month, int day, int hour, int minute, int second) #

A calendar date and time as a decimal year — the epoch argument the field models take.

Parameters
year

the year.

month

1-12.

day

1-based.

hour

0-23.

minute

0-59.

second

0-59.

Returns

the decimal year.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips
fn DateTime decy2date_and_time(double decy) #

A decimal year back to a full broken-down date and time — IRBEM's DECY2DATE_AND_TIME.

Parameters
decy

the decimal year, where yyyy.0 is January 1st at 00:00.

Returns

the broken-down date and time.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips
fn DateTime doy_and_ut2date_and_time(int year, int doy, double ut_seconds) #

A year, day-of-year and UT as a full broken-down date and time.

Parameters
year

the year.

doy

1-based day of year.

ut_seconds

seconds since midnight.

Returns

the broken-down date and time.

Complexity

O(1).

Allocation

none.

Unit testIrbemApi.DateRoundTrips

Constants & variables

var double hours_per_radian #

Hours of magnetic local time per radian of SM longitude: a full turn is 24 h.