Sidereon.GNSS.ReducedOrbit.Piecewise (Sidereon v0.18.0)

Copy Markdown View Source

A long position track approximated by a sequence of contiguous, independently fitted Sidereon.GNSS.ReducedOrbit segments.

A single Sidereon.GNSS.ReducedOrbit distills a whole track into one set of mean elements. A Piecewise model instead splits the span [t0, t1] into contiguous segments of :segment_s seconds and fits each segment. Every query lands inside one fit window, so residuals are controlled by the samples in that segment. The orbit math, frames, and time scales are unchanged.

This trades stored segment count for residual control. It is not orbit determination and not a substitute for source ephemeris data. Measure the residual with drift/3 against caller-provided truth samples.

Models

Each segment is one of the two Sidereon.GNSS.ReducedOrbit models, selected with the same :model option:

  • :circular_secular (the default) - a circular orbit;
  • :eccentric_secular - recovers the radial a·e signal.

See Sidereon.GNSS.ReducedOrbit for the per-model details.

Size / residual tradeoff

Storage grows roughly linearly with segment count. Shorter segments cost more memory but keep queries closer to a fit window center.

Segment selection

Segments tile [t0, t1] with no gaps. A query epoch is resolved by finding the segment whose half-open interval [seg_t0, seg_t1) contains it; the final segment is treated as inclusive at the very end so the exact end-of-span epoch resolves to the last segment. An epoch exactly on an interior boundary resolves to the later segment (where it is the in-window start), which is deterministic. Selection is O(segments); the segment count is modest and the ordered list is binary-searchable if it ever grows large. An epoch before t0 or after t1 returns {:error, :out_of_range}.

Summary

Functions

Evaluate the piecewise model error against truth samples.

Fit a piecewise model over a span, one contiguous Sidereon.GNSS.ReducedOrbit segment per :segment_s seconds.

Position of the piecewise model at epoch, ECEF (ITRF) meters by default.

Position and velocity of the piecewise model at epoch.

Select the segment whose coverage interval contains epoch.

Types

epoch()

segment()

@type segment() :: %{
  t0: NaiveDateTime.t(),
  t1: NaiveDateTime.t(),
  model: Sidereon.GNSS.ReducedOrbit.t()
}

t()

@type t() :: %Sidereon.GNSS.ReducedOrbit.Piecewise{
  frame: String.t(),
  model: String.t(),
  segment_s: number(),
  segments: [segment()],
  time_scale: String.t(),
  version: pos_integer(),
  window: {NaiveDateTime.t(), NaiveDateTime.t()}
}

Functions

drift(pw, samples, opts)

@spec drift(t(), [{epoch(), {number(), number(), number()}}], keyword()) ::
  {:ok, map()} | {:error, term()}

Evaluate the piecewise model error against truth samples.

Returns

{:ok, %{per_epoch: [%{epoch:, error_m:}], max_m:, rms_m:, threshold_horizon:,
        requested:, used:}}

matching the single-segment Sidereon.GNSS.ReducedOrbit.drift/3 report. Epochs outside the model's span are skipped (counted in requested, not used). threshold_horizon is the first epoch the ECEF error exceeds :threshold_m (or nil).

fit(samples, opts \\ [])

@spec fit(
  [{epoch(), {number(), number(), number()}}],
  keyword()
) :: {:ok, t()} | {:error, term()}

Fit a piecewise model over a span, one contiguous Sidereon.GNSS.ReducedOrbit segment per :segment_s seconds.

Accepts a list of {epoch, {x_m, y_m, z_m}} ECEF samples.

Options

  • :window - {t0, t1} epochs bounding the full span (t1 strictly after t0, else :invalid_window)
  • :segment_s - positive segment length in seconds, e.g. 7200 (non-positive → :invalid_segment)
  • :model - :circular_secular (default) or :eccentric_secular
  • :time_scale - the scale the sample epochs are in

Segments are contiguous (seg_t1 of one is seg_t0 of the next); the final segment may be shorter. A :segment_s at least the full span yields a single segment equal to the whole window (piecewise with one segment ≡ single).

Returns {:ok, %Sidereon.GNSS.ReducedOrbit.Piecewise{}} or a tagged error. The error set is exactly the single model's fit errors ({:too_few_samples, got, req}, :invalid_window, {:unsupported_model, m}, {:unsupported_source_frame, f}, {:unsupported_time_scale, s}, :transform_unavailable, …) plus :invalid_segment. A too-few-samples failure on a non-terminal segment is surfaced (a genuinely under-covered interior span is an error, not a silent hole); only the terminal short segment may be dropped. If nothing fits at all, {:error, {:too_few_samples, 0, 4}}.

position(pw, epoch, opts \\ [])

@spec position(t(), epoch(), keyword()) ::
  {:ok, Sidereon.GNSS.ReducedOrbit.vec3()} | {:error, term()}

Position of the piecewise model at epoch, ECEF (ITRF) meters by default.

Selects the segment covering epoch and delegates to Sidereon.GNSS.ReducedOrbit.position/3. Pass frame: :gcrs for the inertial position. An epoch outside the full span returns {:error, :out_of_range}.

position_velocity(pw, epoch, opts \\ [])

@spec position_velocity(t(), epoch(), keyword()) :: {:ok, map()} | {:error, term()}

Position and velocity of the piecewise model at epoch.

Selects the covering segment and delegates to Sidereon.GNSS.ReducedOrbit.position_velocity/3. An epoch outside the full span returns {:error, :out_of_range}.

select_segment(pw, epoch)

@spec select_segment(t(), epoch()) :: {:ok, segment()} | {:error, term()}

Select the segment whose coverage interval contains epoch.

Returns {:ok, segment} or {:error, :out_of_range}. Interior boundaries resolve to the later segment; the exact end-of-span epoch resolves to the last segment.