cheatah
Module

space::cdf

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

Classes

Functions

fn long long leap_second_count() noexcept #

The number of entries in the leap-second table.

Returns

42 as shipped; grows only when a leap second is added upstream.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

io.print(cdf.leap_second_count())   # 42
fn long long leap_second_drift_rows() noexcept #

How many leading entries lie in the pre-1972 "rubber second" era, where TAI-UTC drifts continuously with the day rather than stepping by whole seconds.

Returns

14, matching NERA1 in NASA's own implementation.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

io.print(cdf.leap_second_drift_rows())   # 14 — entries 0..13 drift
fn LeapSecondEntry leap_second_entry(long long index) noexcept #

One entry of the leap-second table, by index.

Parameters
index

0-based; anything outside [0, leap_second_count()) yields a zeroed entry.

Returns

the entry, by value.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

# The most recent entry: TAI-UTC became 37 s on 2017-01-01.
let e = cdf.leap_second_entry(41)
io.print(e.year, e.month, e.day)   # 2017 1 1
io.print(e.delta_at_e7)            # 370000000 — 37.0 s, in units of 1e-7 s
fn long long tai_minus_utc_ns(int year, int month, int day) noexcept #

TAI-UTC in EXACT nanoseconds for a UTC calendar date.

Pure integer arithmetic. Before 1972 the offset drifts with the day, which is what the drift columns encode; the MJD at noon is a half-integer, so the drift term is evaluated at twice scale and halved by folding the 2 into the 1e-7 -> 1e-9 conversion. Nothing rounds.

Warning

This is EXACT, and NASA's library is not. NASA computes the same quantity in double and truncates toward zero, so on 430 of the 4384 days between 1960-01-01 and 1972-01-01 its answer is 1 ns lower than this one. From 1972-01-01 onward the two agree on every day, because the offset is a whole number of seconds and the drift term vanishes. Use tai_minus_utc_ns_nasa_compat() when bit-identical agreement with NASA matters more than being right; the divergence is enumerated day by day in tests/space_cdf_leapseconds_test.cpp so it stays a reviewed fact, not a surprise.

Parameters
year

UTC year.

month

UTC month, 1-12.

day

UTC day of month.

Returns

TAI-UTC in nanoseconds; 0 before 1960-01-01, where the table does not reach.

Complexity

O(leap_second_count()) — a linear scan of 42 entries.

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

# Since 2017-01-01, TAI runs 37 seconds ahead of UTC.
io.print(cdf.tai_minus_utc_ns(2026, 8, 28))   # 37000000000
fn long long tai_minus_utc_ns_nasa_compat(int year, int month, int day) noexcept #

TAI-UTC in nanoseconds, computed the way NASA's CDF library computes it.

A faithful reproduction of the reference implementation: accumulate in double, then truncate toward zero. It exists so bit-identical agreement with NASA is available when that is what is wanted — and so the difference between the two is a tested, enumerated fact rather than a suspicion.

Parameters
year

UTC year.

month

UTC month, 1-12.

day

UTC day of month.

Returns

TAI-UTC in nanoseconds as NASA would compute it; 0 before 1960-01-01.

Complexity

O(leap_second_count()) — a linear scan of 42 entries.

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

# Identical to the exact form from 1972 onward; both are 37 s in 2026.
io.print(cdf.tai_minus_utc_ns_nasa_compat(2026, 8, 28))   # 37000000000
fn std::string_view leap_seconds_url() noexcept #

Where the table comes from.

Returns

NASA's published leap-second table URL.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

io.print(cdf.leap_seconds_url())
fn std::string_view leap_seconds_upstream_updated() noexcept #

The Updated: stamp carried inside the upstream file itself.

Returns

the upstream stamp, YYYYMMDD.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

io.print(cdf.leap_seconds_upstream_updated())   # 20161025
fn std::string_view leap_seconds_sha256() noexcept #

SHA-256 of the NORMALIZED upstream table — comments and blank lines dropped, each remaining line's whitespace collapsed and trimmed.

Normalizing means a reformat or an edited comment raises no false alarm, while any change to the data does.

Returns

the 64-character lowercase hex digest.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

# scripts/check_leapseconds.sh recomputes this from the live file and compares.
io.print(cdf.leap_seconds_sha256())
fn ::cheatah::space::time::CivilDate leap_seconds_verified_on() noexcept #

The date this copy of the table was last checked against upstream.

Returns

the verification date.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

let d = cdf.leap_seconds_verified_on()
io.print(d.year, d.month, d.day)   # 2026 8 28
fn ::cheatah::space::time::CivilDate leap_seconds_known_good_through() noexcept #

The date through which this table is guaranteed COMPLETE.

Derived, not guessed: IERS announces a leap second at least six months ahead in Bulletin C. Upstream carried no entry after 2017-01-01 when we checked on leap_seconds_verified_on(), so none can occur before six months after that date. Past this date the table may be merely current rather than provably complete — ask leap_seconds_covers() before trusting a conversion of a future timestamp.

Returns

the last date the table is provably complete through.

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

let d = cdf.leap_seconds_known_good_through()
io.print(d.year, d.month, d.day)   # 2027 2 28
fn bool leap_seconds_covers(int year, int month, int day) noexcept #

Whether a date falls in the range this table is provably complete for.

A conversion outside it is not necessarily wrong — it is merely unverifiable, because a leap second announced after leap_seconds_verified_on() would not be in this copy. Callers converting future timestamps should branch on this rather than take a silently stale answer.

Parameters
year

UTC year.

month

UTC month, 1-12.

day

UTC day of month.

Returns

true when the date is on or before leap_seconds_known_good_through().

Complexity

O(1).

Allocation

none.

System testsystests/test_leapseconds.purr
Example
import io
import space.cdf as cdf

io.print(cdf.leap_seconds_covers(2026, 8, 28))   # True
io.print(cdf.leap_seconds_covers(2099, 1, 1))    # False — beyond the guarantee
fn File open(const std::string &path) #

Open a CDF by path.

Maps the file and parses every descriptor. Only CDF 3.x single-file, uncompressed-container, IEEE-encoded files are accepted; anything else is refused here with the ErrorCode that says why, so a later read cannot fail for a reason that was knowable at open.

Parameters
path

the .cdf file.

Returns

the opened file.

Complexity

O(v) in the number of variables; no record data is read.

Allocation

one descriptor list and name index; the file itself is memory-mapped, not read.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(len(cdf.var_names(f)))   # 47
fn File open_from_memory(const std::byte *data, std::uint64_t size) #

Open a CDF from bytes already in memory.

The bytes are copied and owned by the File. This is the path every hostile-input test uses: ASan sees each byte of a heap buffer, but cannot see an over-read that stays inside a mapped page, so a bounds bug is only reliably caught here.

Parameters
data

the file's bytes.

size

how many.

Returns

the opened file.

Complexity

O(size) to copy, then O(v) in the number of variables.

Allocation

one copy of the bytes, plus the descriptor list and name index.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

# From purr, prefer cdf.open(path); this entry point exists for tests and embedders.
let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(cdf.record_count(f, "Epoch"))   # 44640
fn std::vector< std::string > var_names(const File &f) #

The names of every variable in the file, rVariables first, in file order.

Parameters
f

an opened file.

Returns

the names.

Complexity

O(v).

Allocation

one list of v strings.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(cdf.var_names(f)[0])   # Epoch
fn long long record_count(const File &f, const std::string &name) #

How many records a variable has.

Parameters
f

an opened file.

name

the variable.

Returns

the record count; 0 for a variable with nothing written.

Complexity

O(1) after the name lookup.

Allocation

none.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(cdf.record_count(f, "F"))   # 44640 — one day at one-minute cadence
fn std::string data_type_name(const File &f, const std::string &name) #

The CDF data type of a variable, by its format name.

Parameters
f

an opened file.

name

the variable.

Returns

e.g. "CDF_REAL4", "CDF_EPOCH", "CDF_TIME_TT2000".

Complexity

O(1) after the name lookup.

Allocation

one short string.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(cdf.data_type_name(f, "Epoch"))   # CDF_EPOCH
fn std::vector< long long > shape(const File &f, const std::string &name) #

The shape values() will return for a variable.

Records first, then each dimension that varies per record, then one trailing axis when a value is itself several elements — the string length of a CHAR variable, or 2 for EPOCH16.

Parameters
f

an opened file.

name

the variable.

Returns

the shape.

Complexity

O(d) in the number of dimensions.

Allocation

one short list.

System testsystests/test_cdf_read.purr
Example
import io
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
io.print(cdf.shape(f, "F"))   # [44640] — a scalar per record
fn inline ::cheatah::ndarray::basic_ndarray< double > values(const File &f, const std::string &name) #

Every record of a variable, as float64.

Lossless for every CDF type except CDF_INT8 and CDF_TIME_TT2000, which a double cannot hold exactly; those are refused with ErrorCode::LossyConversion — use values_i64(). CHAR data comes back as byte values with the string length as the last axis; EPOCH16 as a trailing axis of 2. This is the array cheatah-plot draws directly.

Parameters
f

an opened file.

name

the variable.

Returns

an ndarray shaped as shape() describes. A copy of the file's data.

Complexity

O(n) in the number of values — one decode pass.

Allocation

the result array, exactly once; nothing intermediate.

System testsystests/test_cdf_read.purr
Example
import io
import ndarray
import space.cdf as cdf

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
let b = cdf.values(f, "F")            # IMF magnitude, nT, one value per minute
io.print(b[0], b[1], b[2])            # 6.92 5.84 5.71
Example

The whole point of returning an ndarray: it goes straight into cheatah-plot.

import ndarray
import space.cdf as cdf
import plot
import plot.figure as figure

let f = cdf.open("omni_hro2_1min_20150101_v01.cdf")
let t = cdf.values(f, "Epoch")
let b = cdf.values(f, "F")

# Rebase onto hours: cheatah-plot has no time axis, and a raw CDF_EPOCH of 6.3e13 ms would
# render as one repeated tick label.
let n = ndarray.size_of(b)
let hours = ndarray.zeros([n])
for i in range(n) { hours[i] = (t[i] - t[0]) / 3600000.0 }

let fig = figure.line(figure.new_figure(), hours, b)
fig = figure.ylabel(fig, "|B| (nT)")
plot.save(fig, "omni_imf.png")
fn inline ::cheatah::ndarray::basic_ndarray< long long > values_i64(const File &f, const std::string &name) #

Every record of an integer variable, as int64 — the exact path for CDF_INT8 and TT2000.

Accepts every integer type; refuses REAL4/REAL8/EPOCH/EPOCH16 with ErrorCode::LossyConversion because truncating a float is not a conversion anyone asked for.

Parameters
f

an opened file.

name

the variable.

Returns

an ndarray of int shaped as shape() describes. A copy of the file's data.

Complexity

O(n) in the number of values — one decode pass.

Allocation

the result array, exactly once; nothing intermediate.

System testsystests/test_cdf_read.purr
Example
import io
import ndarray
import space.cdf as cdf

# TT2000 is nanoseconds since J2000; an int64 holds it exactly, a double does not.
let f = cdf.open("rbsp-a_density_emfisis-l4_20130101_v1.5.17.cdf")
let t = cdf.values_i64(f, "Epoch")
io.print(t[0])

Constants & variables

var std::uint32_t kMagicV3 #

First magic number of a CDF 3.x file, at byte offset 0.

var std::uint32_t kMagicV26 #

First magic number of a CDF 2.6/2.7 file — the 32-bit-offset generation.

var std::uint32_t kMagicPreV26 #

First magic number of a pre-2.6 file. Not supported; see ErrorCode::UnsupportedPreV26.

var std::uint32_t kMagicUncompressed #

Second magic number: the file's records are stored uncompressed.

var std::uint32_t kMagicCompressed #

Second magic number: the whole file is compressed and a CCR follows the magic.

var std::uint64_t kCdrOffset #

Byte offset of the CDR, which always follows the two 4-byte magic numbers.

var std::uint64_t kChecksumBytes #

Bytes a checksummed file carries past GDR.eof — an MD5 digest, not a CDF record.

var bool kHostIsLittleEndian #

True when this build targets a little-endian host.

CDF stores no encoding meaning "native", so the Host selector has to be resolved against the machine actually doing the reading.

var std::uint32_t kCdrFlagRowMajority #

CDR flags bit 0: set when the file's majority is row-major.

var std::uint32_t kCdrFlagSingleFile #

CDR flags bit 1: set when all data lives in this one file rather than .v*/.z* companions.

var std::uint32_t kCdrFlagChecksum #

CDR flags bit 2: set when the file carries a trailing checksum.

var std::uint32_t kCdrFlagChecksumMd5 #

CDR flags bit 3: set when that checksum is MD5. Meaningful only with kCdrFlagChecksum.

var std::size_t kErrorCodeCount #

How many ErrorCode enumerators exist, including None.

The unit tests assert one crafted failure per code against this, so a new code cannot be added without a test that provokes it.

Types

enum std::int32_t RecordType #

The internal record types, as written in the 4-byte big-endian field at record offset 8.

Note

UnusedInternal is -1 and marks a hole left by a rewrite; such records are skipped, never parsed. Every other value is a real record with a defined layout.

enum std::int32_t DataType #

The CDF data types.

The numbering is the format's, not ours — see the file docs for why REAL4/FLOAT and REAL8/DOUBLE are distinct values with identical layouts.

enum std::int32_t Encoding #

The byte orders and floating-point formats a CDF's values may be stored in.

Note

"NETWORK" is not XDR despite the name — it is plain big-endian IEEE-754. That is worth stating because assuming otherwise invents a whole conversion layer that is not needed.

enum std::uint8_t EncodingClass #

The four ways a CDF's bytes can actually need decoding.

Twenty-one encodings collapse to four decode behaviours, which is the single simplification that keeps the decoder small: what matters is integer byte order and whether floats are IEEE or one of the two VAX formats.

enum std::uint8_t Majority #

How a multi-dimensional variable's values are laid out within one record.

enum std::int32_t SparseRecords #

What a variable's unwritten records contain.

enum std::int32_t CompressionType #

The compression algorithms CDF defines. Only Gzip occurs in the public archive.

enum std::uint8_t ErrorCode #

Everything that can be wrong with a CDF we are asked to read.

One flat enumeration rather than exception subtypes: a caller almost always wants to report what happened and where, not to branch on the kind, and a closed set can be table-tested for reachability — every code below is provoked by a crafted file in the unit tests, so none of them is aspirational.