The frozen pulsar-data record, its on-disk schema, its own linear engine, and the pure par-text rules — the layer every pulsar-timing package in this stack agrees on, and the only one none of them owns.
from psrdata import PulsarData
psr = PulsarData.from_feather("J1909-3744.feather")
psr.toas, psr.residuals, psr.Mmat, psr.fitpars # named, frozen, in the writer's row order
engine = psr.linear_engine() # Δr = −Mmat δ, in nltiming's engine shapedocs/surfaces.md is the readable API: the Enterprise /
Discovery array attributes and the linear engine nltiming consumes.
SPEC.md is the normative description;
SPEC-motivation.md says why it is designed the way it is.
Tag v0.1.0 is the first freeze of the v1 record, feather schema, and
linear engine. Stock Enterprise and Discovery readers and nltiming's
record-engine conformance suite are included. Pre-v1 development Feather
files are intentionally not accepted by the v1 reader.
Four things, each of which was being duplicated or defined one layer too high:
PulsarData— a frozen record of named arrays (TOAs, residuals, design matrix, flags, ephemeris vectors) plus the metadata that makes it self-describing: which software wrote it (producer), which timing package calculated each data set's residuals and matrix (timing_package), which of PINT or tempo2 read the files (partim_compatibility), the exact reference parameter values and PINT units (parameters), and the residual centering per data set.feather.write/readare its on-disk form, schemapulsardata-feather-v1, whose columns are exactly what Enterprise'sFeatherPulsarand Discovery'sPulsaralready read. Array attributes:docs/surfaces.md.- The record's linear engine —
PulsarData.linear_engine()returnsΔr = −Mmat δover the record's own matrix, single-leg or composite, in the shape nltiming'sTimingEngineprotocol describes, without importing nltiming. Method list:docs/surfaces.md. A combined record declares no partition: a data set's rows are the support of its phase-offset column (Offset_<key>orPHOFF_<key>), and its active linear columns are the ones nonzero on those rows. ParameterFactandResidualCentering— the validated value types of theparametersandresidual_centeringfields, here because they are serialized in the record.partext— the par-file rules that are pure text: which lines are noise hyperparameters, whatUNITSmeans, the two keyword respellings PINT and tempo2 disagree about, and collapsing a doubled non-repeatable line.
It does not know about PINT, tempo2, JAX, or sorting, and it defines no
protocol for a live function of the timing parameters. Its runtime
dependencies are numpy and pyarrow, and a test asserts that importing it
pulls in nothing else — that is the property that lets it sit below everything,
and what lets a frozen linear timing analysis run from the file alone.
It never reorders rows. Row i of every array is row i as the writer
emitted it. A consumer that wants time order sorts when it reads; a timing
package that permutes the rows its residual and design matrix are built from
publishes two orders for one freeze, and the reconciling permutation is the
identity on most real files and therefore never exercised.
MetaPulsar (one record per multi-PTA composite), vela-jax (one per pulsar)
and any other timing package that emits the record. Enterprise and Discovery
read the feather with their own readers and never import this package.
nltiming consumes the record and its linear engine.