Measurement-quality control for single-point positioning.
The numerical modeling and FDE orchestration live in the
sidereon-core Rust core. This module keeps the Elixir API,
normalizes options and epochs for the NIF, maps errors, and decodes the
unchanged public result maps.
Summary
Types
FDE state returned when the core stops with a detected fault.
The public exception raised or returned for a core quality-control refusal.
The result of raim/2.
A {satellite_id, elevation_deg} or {satellite_id, elevation_deg, cn0_dbhz} entry.
Functions
Chi-square inverse CDF (quantile).
Fault detection and exclusion: solve, run RAIM, compare leave-one-out candidate solves when a fault is detected, and repeat until the accepted solution passes RAIM or the exclusion budget is exhausted.
Lint RINEX NAV text.
Lint a parsed RINEX OBS file.
Lint RINEX OBS or CRINEX text.
Observation completeness and signal-quality rollup for a parsed RINEX OBS file.
Pseudorange measurement variance (m^2) from satellite elevation.
Residual-based RAIM: a chi-square goodness-of-fit test on a positioning solution.
Standalone range RAIM/FDE over a caller-supplied linearized measurement set, independent of any full positioning solve.
Residual-based RAIM for an existing SPP solution.
Render an observation QC report as HTML.
Render an observation QC report as fixed-width text.
Mechanically repair RINEX NAV text.
Mechanically repair RINEX OBS or CRINEX text.
Core robust-reweighted SPP under the RAIM/FDE exclusion loop.
Build a satellite => sigma_m map for a list of weight entries.
Serialize an observation QC report as JSON.
Build a satellite => inverse_variance_weight map for a list of weight entries.
Types
@type fde_unresolved() :: %{ reason: String.t(), solution: Sidereon.GNSS.Positioning.Solution.t(), excluded: [{String.t(), :raim_excluded}], iterations: non_neg_integer(), raim: raim_result() }
FDE state returned when the core stops with a detected fault.
@type quality_error() :: Sidereon.GNSS.QC.QualityError.t()
The public exception raised or returned for a core quality-control refusal.
@type raim_result() :: %{ fault_detected?: boolean(), fault_detected: boolean(), test_statistic: float(), threshold: float() | nil, reduced_chi_square: float() | nil, dof: integer(), testable?: boolean(), testable: boolean(), normalized_residuals: %{required(String.t()) => float()}, rms_m: float(), worst_sat: String.t() | nil }
The result of raim/2.
A {satellite_id, elevation_deg} or {satellite_id, elevation_deg, cn0_dbhz} entry.
Functions
Chi-square inverse CDF (quantile).
@spec fde( term(), [Sidereon.GNSS.Positioning.observation()], Sidereon.GNSS.Positioning.epoch(), keyword() ) :: {:ok, %{ solution: Sidereon.GNSS.Positioning.Solution.t(), excluded: [{String.t(), :raim_excluded}], iterations: non_neg_integer(), raim: raim_result() }} | {:error, {:fault_unresolved, fde_unresolved()}} | {:error, term()}
Fault detection and exclusion: solve, run RAIM, compare leave-one-out candidate solves when a fault is detected, and repeat until the accepted solution passes RAIM or the exclusion budget is exhausted.
:pseudorange_code selects the code of the pseudoranges as in
Sidereon.GNSS.Positioning.solve/4 (default :single_frequency).
Malformed FDE options are returned as tagged errors, including
{:invalid_option, :p_fa}, {:invalid_option, :weights},
{:invalid_option, :max_exclusions}, {:invalid_option, :max_exclusion_rms_m}
and {:invalid_option, :pseudorange_code}. Weighting defaults to the solution's
actual per-satellite variances; weights: :unit and a positive satellite map
remain explicit alternatives. max_exclusions defaults to one and the RMS cap
defaults to 100 metres; use :infinity to remove the cap.
The removed :max_iterations option is explicitly rejected, including when
supplied together with :max_exclusions.
Core quality refusals return {:error, %Sidereon.GNSS.QC.QualityError{}};
malformed binding options remain tagged with :invalid_option.
Success returns the accepted :solution, ordered :excluded list, number of
exclusions, and the accepted solution's :raim result. An unresolved fault
returns {:error, {:fault_unresolved, unresolved}}; the map includes the
core :reason ("exclusion_budget_exhausted" or
"no_admissible_exclusion"), last :solution, :excluded list,
:iterations, and its faulted :raim result. An ephemeris source that
refuses a satellite state reading UT1 outside the UT1 table fails with
{:ut1_outside_coverage, :before_coverage | :after_coverage}.
@spec lint_obs(Sidereon.GNSS.RINEX.Observations.t()) :: {:ok, map()} | {:error, term()}
Lint a parsed RINEX OBS file.
Each finding preserves :code, :severity, :spec_ref, :repairable, and :at, and also reports :kind plus :details. A populated detail is a tagged tuple such as {:obs_event_epoch, %{flag: 4}}; a fieldless detail is the variant atom, such as :obs_interval_unavailable.
Lint RINEX OBS or CRINEX text.
Findings include their tagged variant and typed payload in :details, while retaining the existing code, severity, spec reference, repairability, and source-location fields.
@spec observation_report( Sidereon.GNSS.RINEX.Observations.t(), keyword() ) :: {:ok, Sidereon.GNSS.QC.ObservationReport.t()} | {:error, term()}
Observation completeness and signal-quality rollup for a parsed RINEX OBS file.
@spec pseudorange_variance( number(), keyword() ) :: float() | {:error, Sidereon.GNSS.QC.QualityError.kind()}
Pseudorange measurement variance (m^2) from satellite elevation.
Returns a float or a tagged core refusal. The core accepts finite elevations
in [-90, 90]; a zero-elevation observation is valid when b: 0.0, while a
nonzero elevation-scaled term has undefined variance at the horizon.
@spec raim( Sidereon.GNSS.Positioning.Solution.t() | Sidereon.GNSS.QC.RaimInput.t(), keyword() ) :: raim_result() | Sidereon.GNSS.QC.RaimResult.t()
Residual-based RAIM: a chi-square goodness-of-fit test on a positioning solution.
The default weights: :solution uses the actual per-satellite variances from
the solution or RaimInput; pass weights: :unit or a satellite-weight map
for the other modes. Solution input also carries its actual receiver-clock
count so shared GPS/QZSS/SBAS clocks are not counted as separate parameters.
Pass inverse-variance weights derived from per-satellite residual variances,
either as a %{sat => weight} map or as weight entries consumed by
weight_vector/2. Unit weights with metre-scale residuals make
fault_detected saturate near 100%.
A core refusal raises Sidereon.GNSS.QC.QualityError, whose :kind is the
core variant (for example :missing_variances or :invalid_variance).
Binding-level malformed options continue to raise ArgumentError.
entries = [
%{satellite_id: "G01", elevation_deg: 72.0},
%{satellite_id: "G02", elevation_deg: 42.0}
]
weights = Sidereon.GNSS.QC.weight_vector(entries, a_m: 0.8, b_m: 0.8)
Sidereon.GNSS.QC.raim(input, weights: weights)
Standalone range RAIM/FDE over a caller-supplied linearized measurement set, independent of any full positioning solve.
Each row of rows is a map describing one linearized range measurement:
:id- stable measurement identifier (e.g. a satellite token"G01"):residual_m- observed-minus-computed range residual, metres:design_row- the measurement's row of the design matrix (a list of the partials of the predicted range with respect to each estimated state parameter); every row must carry the same length:weight- inverse-variance weight1 / sigma^2, strictly positive
Options:
:p_fa- false-alarm probability for the global chi-square test (default0.001):max_exclusions- maximum measurements the exclusion loop may remove (default: one, matching RTKLIB demo5):min_redundancy- minimum redundancy an exclusion must leave behind (default1):max_exclusion_rms_m- largest admissible unweighted residual RMS for a leave-one-out candidate (default100.0;:infinitydisables this cap)
Returns {:ok, result} where result carries the protected
:state_correction, :state_covariance, the :global_test chi-square map,
the :excluded ids, per-measurement :diagnostics, and the exclusion
:iterations; or {:error, reason} for a malformed or rank-deficient input.
The removed :max_iterations option is explicitly rejected as
{:invalid_option, :max_iterations}.
Core quality refusals return {:error, %Sidereon.GNSS.QC.QualityError{}}.
@spec raim_for_solution( Sidereon.GNSS.Positioning.Solution.t(), keyword() ) :: raim_result()
Residual-based RAIM for an existing SPP solution.
This is the direct post-solve variant matching the Rust and C
raim_for_solution surface. It uses the solution's used satellites and
post-fit residuals, actual variances and solved receiver-clock count, with the
same options accepted by raim/2. An explicit :n_systems overrides that
count.
@spec render_html(Sidereon.GNSS.QC.ObservationReport.t()) :: {:ok, String.t()} | {:error, term()}
Render an observation QC report as HTML.
@spec render_text(Sidereon.GNSS.QC.ObservationReport.t()) :: {:ok, String.t()} | {:error, term()}
Render an observation QC report as fixed-width text.
Mechanically repair RINEX OBS or CRINEX text.
Content-changing repairs other than record sorting are opt-in. Set
:set_interval to true to replace an unavailable or mismatched INTERVAL
from the observed epoch cadence, for example.
The repaired product is written with the observation writer, which returns text only when it reads back as the product, and that text is then compressed to CRINEX. On success the map holds both texts, the actions taken and the lint report after repair. Each failure keeps its own shape:
{:error, {:parse, message}}or{:error, {:invalid_input, message}}- the input does not read, or repair refuses it, such as text whose unretained header records a repair would drop withoutdrop_unsupported: true.{:error, {:repaired_product_unwritable, {tag, fields}}}- the repaired product cannot be written exactly;{tag, fields}is one of the writer refusalsSidereon.GNSS.RINEX.Observationslists.{:error, {:crinex_encode_failed, {kind, message}}}- the written RINEX does not compress,kindbeing:parse,:invalid_inputor:unhandled.
@spec robust_fde( term(), [Sidereon.GNSS.Positioning.observation()], Sidereon.GNSS.Positioning.epoch(), keyword() ) :: {:ok, %{ solution: Sidereon.GNSS.Positioning.Solution.t(), excluded: [{String.t(), :raim_excluded}], iterations: non_neg_integer(), raim: raim_result() }} | {:error, {:fault_unresolved, fde_unresolved()}} | {:error, term()}
Core robust-reweighted SPP under the RAIM/FDE exclusion loop.
The successful result carries the accepted solution, ordered exclusions,
exclusion count and that solution's :raim test. An unresolved fault carries
the core stop reason, last solution, exclusions and faulted RAIM result.
@spec sigmas( [weight_entry()], keyword() ) :: %{required(String.t()) => float()}
Build a satellite => sigma_m map for a list of weight entries.
@spec to_json(Sidereon.GNSS.QC.ObservationReport.t()) :: {:ok, String.t()} | {:error, term()}
Serialize an observation QC report as JSON.
@spec weight_vector( [weight_entry()], keyword() ) :: %{required(String.t()) => float()}
Build a satellite => inverse_variance_weight map for a list of weight entries.