Carrier-phase precise-positioning primitives.
This is the first precise-positioning layer above the code and carrier-phase
combinations in Sidereon.GNSS.IonosphereFree / Sidereon.GNSS.CarrierPhase. It
solves one SP3-backed epoch from dual-frequency ionosphere-free code and phase
observations:
P_IF_i = rho_i(x) + b - c * dt_sat_i + T_i
L_IF_i = rho_i(x) + b - c * dt_sat_i + T_i + N_iwhere x is the receiver ECEF position, b is the receiver clock in metres,
T_i is the optional a-priori slant tropospheric delay plus any estimated
residual zenith delay mapped to the line of sight, and N_i is one float
carrier-phase ambiguity per satellite, also in metres. The single-epoch state
is linearized and iterated over [x, y, z, b, N_1, N_2, ...].
solve_float/4 solves one epoch. solve_float_epochs/3 solves a static
multi-epoch arc with one receiver position, one receiver clock per epoch, and
one ambiguity per satellite held constant across the arc. That multi-epoch
model is the first step where carrier phase can tighten position instead of
being absorbed entirely by one ambiguity per epoch. Multi-epoch and fixed
solves can also estimate one residual zenith troposphere delay over the arc
(estimate_ztd: true) after the a-priori Saastamoinen/Niell correction.
solve_fixed_epochs/3 starts from the same multi-epoch float model, runs
LAMBDA/MLAMBDA integer least-squares on an explicit caller-supplied wavelength
grid, then re-solves position and per-epoch clocks with those ambiguities held
fixed.
Observation format
Observations may be maps or tuples:
%{satellite_id: "G05", code_m: 24_000_000.0, phase_m: 24_012_345.0}
{"G05", 24_000_000.0, 24_012_345.0}code_m and phase_m should normally be ionosphere-free combinations. Use
Sidereon.GNSS.IonosphereFree.iono_free/4 and
Sidereon.GNSS.IonosphereFree.iono_free_phase_cycles/4 to form them from raw
dual-frequency RINEX observations.
Summary
Types
A set of code/phase observations for one epoch.
A dual-frequency ionosphere-free code/phase observation.
A receiver ECEF position in metres.
Functions
Solve a static multi-epoch position with integer-fixed ambiguities.
Solve a float-ambiguity carrier-phase position for one SP3-backed epoch.
Solve a static multi-epoch float-ambiguity carrier-phase position.
Solve a static multi-epoch integer-fixed PPP arc with SPP auto-initialization,
delegating the whole driver (auto-init seed, the float solve, the LAMBDA
integer fix, and the ambiguity-conditioned re-solve) to the sidereon-core
solve_ppp_auto_init_fixed kernel.
Solve a static multi-epoch float PPP arc with SPP auto-initialization,
delegating the whole driver (the SPP code seed, the mean-position and per-epoch
clock seeds, the phase-minus-code float ambiguity seeds, and the static float
solve) to the sidereon-core solve_ppp_auto_init_float kernel.
Solve a static multi-epoch integer-fixed PPP arc from an existing float solution.
Solve a static multi-epoch float PPP arc from an explicit initial state.
Types
@type epoch_observations() :: %{epoch: NaiveDateTime.t(), observations: [observation()]} | {NaiveDateTime.t(), [observation()]}
A set of code/phase observations for one epoch.
@type observation() :: %{satellite_id: String.t(), code_m: number(), phase_m: number()} | {String.t(), number(), number()}
A dual-frequency ionosphere-free code/phase observation.
A receiver ECEF position in metres.
Functions
@spec solve_fixed_epochs(Sidereon.GNSS.SP3.t(), [epoch_observations()], keyword()) :: {:ok, Sidereon.GNSS.PrecisePositioning.FixedSolution.t()} | {:error, term()}
Solve a static multi-epoch position with integer-fixed ambiguities.
The function first solves the float multi-epoch model (solve_float_epochs/3),
converts each float ambiguity from metres to cycles using the explicit
:ambiguity_wavelength_m option, runs the LAMBDA/MLAMBDA integer
least-squares search, and re-solves the receiver position and per-epoch clocks
with the best integer ambiguities held fixed.
Required option
:ambiguity_wavelength_m- either a positive scalar wavelength in metres for every satellite, or a map%{"G05" => wavelength_m, ...}.
Additional options
:integer_ratio_threshold- minimum second-best / best weighted-score ratio formetadata.integer_status == :fixed(default3.0).:integer_search_radius_cycles/:integer_candidate_limit- retained and still validated for backward compatibility, but no longer bound the search: integer resolution uses the LAMBDA method (decorrelation + reduction + MLAMBDA search), which finds the true integer-least-squares optimum for any geometry with no search box, so it cannot return{:error, {:too_many_integer_candidates, ...}}.:ambiguity_offset_m- optional scalar or%{"G05" => offset_m, ...}map subtracted from each float ambiguity before converting to cycles and added back after fixing (default0.0). This is mainly for affine carrier-phase combinations such as wide-lane/narrow-lane fixing.
The fixed solution is returned even when the ratio test is not met; in that
case metadata.integer_status is :not_fixed so callers can reject it.
@spec solve_float( Sidereon.GNSS.SP3.t(), [observation()], NaiveDateTime.t(), keyword() ) :: {:ok, Sidereon.GNSS.PrecisePositioning.Solution.t()} | {:error, term()}
Solve a float-ambiguity carrier-phase position for one SP3-backed epoch.
source is a loaded Sidereon.GNSS.SP3 product. observations is a list of
ionosphere-free code/phase pairs for one epoch. epoch is interpreted in the
SP3 product's time scale.
Options
:initial_guess-{x_m, y_m, z_m, clock_m}. If omitted, the code observations are first passed throughSidereon.GNSS.Positioning.solve/4with ionosphere/troposphere disabled, and that code-only solution seeds the float solve.:spp_initial_guess- code-only SPP seed used only when:initial_guessis omitted (default{0, 0, 0, 0}).:code_sigma_m- code row standard deviation (default1.0m).:phase_sigma_m- phase row standard deviation (default0.01m).:elevation_weighting- whentrue, scale both code and phase row standard deviations by1 / sin(elevation)so low-elevation observations contribute less to the float, fixed, and ambiguity-covariance solves (defaultfalse).:max_iterations- maximum nonlinear iterations (default8).:position_tolerance_m- position-update convergence threshold (default1.0e-4m).:clock_tolerance_m- receiver-clock update threshold (default1.0e-4m).:troposphere- apply an a-priori Saastamoinen/Niell slant tropospheric delay to both code and phase (defaultfalse).:pressure_hpa- surface pressure in hPa when:troposphereis true (default1013.25).:temperature_k- surface temperature in kelvin when:troposphereis true (default288.15).:relative_humidity- relative humidity fraction when:troposphereis true (default0.5).:tropo_mapping- the tropospheric mapping function when:troposphereis true::niell(the climatological default) or{:vmf1, samples}wheresamplesis a non-empty, ascending list of%{mjd:, ah:, aw:}site-wise VMF1a-coefficient samples (the Saastamoinen zenith delays are unchanged; only the mapping differs).:estimate_ztd- on multi-epoch/fixed solves only, estimate one residual zenith troposphere delay in metres over the whole static arc, mapped with the Niell wet mapping factor. Requirestroposphere: true(defaultfalse).:ztd_tolerance_m- residual-ZTD update convergence threshold when:estimate_ztdis true (default1.0e-4m).
Returns {:ok, %Solution{}} or {:error, reason}. Reasons include
:no_observations, {:too_few_satellites, used, 4},
{:duplicate_observation, sat}, {:invalid_observation, entry},
:invalid_initial_guess, {:invalid_sigma, key}, {:invalid_option, key},
{:code_seed_failed, reason}, {:no_ephemeris, sat, reason},
{:troposphere_failed, sat, reason}, and :singular_geometry. If the
iteration limit is reached after a valid solve step, the function returns a
solution with metadata.converged == false and
metadata.status == :max_iterations so callers can inspect the residuals and
decide whether to reject it.
@spec solve_float_epochs(Sidereon.GNSS.SP3.t(), [epoch_observations()], keyword()) :: {:ok, Sidereon.GNSS.PrecisePositioning.MultiEpochSolution.t()} | {:error, term()}
Solve a static multi-epoch float-ambiguity carrier-phase position.
epoch_observations is a list of %{epoch: epoch, observations: obs} maps or
{epoch, obs} tuples. The receiver position is static across the whole arc,
each epoch gets its own receiver clock, and each satellite gets one ambiguity
held constant across every epoch where that satellite appears.
This model is still float ambiguity only. It does not fix integer ambiguities or estimate a stochastic PPP process, but it lets changing geometry across the arc separate position from carrier ambiguities.
Options are the same as solve_float/4, plus:
:ambiguity_tolerance_m- maximum ambiguity-update convergence threshold (default1.0e-4m).
Returns {:ok, %MultiEpochSolution{}} or {:error, reason}. Reasons include
:no_epochs, {:too_few_epochs, used, 2}, {:duplicate_epoch, epoch},
{:too_few_epoch_observations, epoch, used, 4},
{:too_few_equations, equations, unknowns}, and the same observation,
option, ephemeris, seeding, and geometry errors as solve_float/4.
@spec solve_ppp_auto_init_fixed(Sidereon.GNSS.SP3.t(), list(), keyword()) :: {:ok, map()} | {:error, term()}
Solve a static multi-epoch integer-fixed PPP arc with SPP auto-initialization,
delegating the whole driver (auto-init seed, the float solve, the LAMBDA
integer fix, and the ambiguity-conditioned re-solve) to the sidereon-core
solve_ppp_auto_init_fixed kernel.
Options match solve_fixed_epochs/3 plus the auto-init options documented on
solve_ppp_auto_init_float/3. Returns {:ok, solution} or {:error, reason}.
@spec solve_ppp_auto_init_float(Sidereon.GNSS.SP3.t(), list(), keyword()) :: {:ok, map()} | {:error, term()}
Solve a static multi-epoch float PPP arc with SPP auto-initialization,
delegating the whole driver (the SPP code seed, the mean-position and per-epoch
clock seeds, the phase-minus-code float ambiguity seeds, and the static float
solve) to the sidereon-core solve_ppp_auto_init_float kernel.
This is a thin delegation to the core auto-init driver: the seed the existing
solve_float_epochs/3 builds in Elixir is now formed inside the kernel. The
epoch_observations and the measurement/tropo/correction options are the same
as solve_float_epochs/3. Auto-init specific options:
:initial_guess-%{position: {x, y, z}, clock_m: c}to bypass the SPP seed entirely, or absent to run the per-epoch SPP auto-init:spp_initial_guess-{x, y, z, b}SPP cold-start (default all-zero):spp_troposphere- apply the troposphere in the SPP seed (defaultfalse):spp_met-%{pressure_hpa:, temperature_k:, relative_humidity:}for the SPP seed troposphere (default standard atmosphere)
Returns {:ok, solution} or {:error, reason}.
@spec solve_ppp_fixed( Sidereon.GNSS.SP3.t(), list(), Sidereon.GNSS.PrecisePositioning.MultiEpochSolution.t(), keyword() ) :: {:ok, Sidereon.GNSS.PrecisePositioning.FixedSolution.t()} | {:error, term()}
Solve a static multi-epoch integer-fixed PPP arc from an existing float solution.
@spec solve_ppp_float(Sidereon.GNSS.SP3.t(), list(), map(), keyword()) :: {:ok, Sidereon.GNSS.PrecisePositioning.MultiEpochSolution.t()} | {:error, term()}
Solve a static multi-epoch float PPP arc from an explicit initial state.