sidereon 3.0.4__tar.gz → 3.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. {sidereon-3.0.4 → sidereon-3.0.5}/.github/workflows/ci.yml +2 -0
  2. {sidereon-3.0.4 → sidereon-3.0.5}/.github/workflows/release.yml +4 -4
  3. {sidereon-3.0.4 → sidereon-3.0.5}/CHANGELOG.md +14 -0
  4. {sidereon-3.0.4 → sidereon-3.0.5}/Cargo.lock +5 -5
  5. {sidereon-3.0.4 → sidereon-3.0.5}/Cargo.toml +3 -3
  6. {sidereon-3.0.4 → sidereon-3.0.5}/PKG-INFO +5 -2
  7. {sidereon-3.0.4 → sidereon-3.0.5}/README.md +4 -1
  8. {sidereon-3.0.4 → sidereon-3.0.5}/THIRD-PARTY-NOTICES.md +3 -3
  9. {sidereon-3.0.4 → sidereon-3.0.5}/docs/public-gnss-distribution-sources.md +5 -5
  10. {sidereon-3.0.4 → sidereon-3.0.5}/docs/sp3.md +15 -5
  11. {sidereon-3.0.4 → sidereon-3.0.5}/pyproject.toml +4 -4
  12. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/__init__.pyi +43 -7
  13. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/data.py +33 -7
  14. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/data.pyi +3 -0
  15. {sidereon-3.0.4 → sidereon-3.0.5}/scripts/check-release.py +3 -3
  16. {sidereon-3.0.4 → sidereon-3.0.5}/scripts/core_source_identity.rs +1 -1
  17. sidereon-3.0.5/scripts/smoke-release.py +154 -0
  18. {sidereon-3.0.4 → sidereon-3.0.5}/src/ephemeris.rs +96 -3
  19. {sidereon-3.0.4 → sidereon-3.0.5}/src/lnav.rs +7 -7
  20. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/core_goldens.json +2 -2
  21. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/ppp_esbc.json +2 -2
  22. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_core_revision.py +34 -25
  23. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_data.py +14 -0
  24. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_lnav.py +110 -2
  25. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_merge.py +28 -2
  26. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_write_and_records.py +34 -23
  27. sidereon-3.0.5/third_party_source/sidereon-core-3.0.5/tides/mod.rs +1501 -0
  28. sidereon-3.0.5/third_party_source/sidereon-core-3.0.5/tides/ocean.rs +987 -0
  29. sidereon-3.0.5/third_party_source/sidereon-core-3.0.5/tides/pole.rs +203 -0
  30. {sidereon-3.0.4 → sidereon-3.0.5}/uv.lock +1 -1
  31. sidereon-3.0.4/scripts/smoke-release.py +0 -52
  32. {sidereon-3.0.4 → sidereon-3.0.5}/.cargo/config.toml.disabled +0 -0
  33. {sidereon-3.0.4 → sidereon-3.0.5}/.gitattributes +0 -0
  34. {sidereon-3.0.4 → sidereon-3.0.5}/.gitignore +0 -0
  35. {sidereon-3.0.4 → sidereon-3.0.5}/COVERAGE.md +0 -0
  36. {sidereon-3.0.4 → sidereon-3.0.5}/LICENSE +0 -0
  37. {sidereon-3.0.4 → sidereon-3.0.5}/bench/README.md +0 -0
  38. {sidereon-3.0.4 → sidereon-3.0.5}/bench/bench_fleet.py +0 -0
  39. {sidereon-3.0.4 → sidereon-3.0.5}/docs/antex.md +0 -0
  40. {sidereon-3.0.4 → sidereon-3.0.5}/docs/observations.md +0 -0
  41. {sidereon-3.0.4 → sidereon-3.0.5}/docs/releasing.md +0 -0
  42. {sidereon-3.0.4 → sidereon-3.0.5}/docs/rinex-clock.md +0 -0
  43. {sidereon-3.0.4 → sidereon-3.0.5}/docs/tdm.md +0 -0
  44. {sidereon-3.0.4 → sidereon-3.0.5}/examples/clean_a_phone_track.ipynb +0 -0
  45. {sidereon-3.0.4 → sidereon-3.0.5}/examples/static_occupation_vs_cors.ipynb +0 -0
  46. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/__init__.py +0 -0
  47. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/_compression.py +0 -0
  48. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/_exact_cache.py +0 -0
  49. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/_ingress.py +0 -0
  50. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/distribution.py +0 -0
  51. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/distribution.pyi +0 -0
  52. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/exact_cache.py +0 -0
  53. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/exact_cache.pyi +0 -0
  54. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/exact_sp3.py +0 -0
  55. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/exact_sp3.pyi +0 -0
  56. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/ntrip.py +0 -0
  57. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/ntrip.pyi +0 -0
  58. {sidereon-3.0.4 → sidereon-3.0.5}/python/sidereon/py.typed +0 -0
  59. {sidereon-3.0.4 → sidereon-3.0.5}/rust-toolchain.toml +0 -0
  60. {sidereon-3.0.4 → sidereon-3.0.5}/scripts/coverage/bindmap.py +0 -0
  61. {sidereon-3.0.4 → sidereon-3.0.5}/scripts/coverage/pubapi.py +0 -0
  62. {sidereon-3.0.4 → sidereon-3.0.5}/scripts/coverage/render_coverage.py +0 -0
  63. {sidereon-3.0.4 → sidereon-3.0.5}/src/almanac.rs +0 -0
  64. {sidereon-3.0.4 → sidereon-3.0.5}/src/angles.rs +0 -0
  65. {sidereon-3.0.4 → sidereon-3.0.5}/src/anomaly.rs +0 -0
  66. {sidereon-3.0.4 → sidereon-3.0.5}/src/antex.rs +0 -0
  67. {sidereon-3.0.4 → sidereon-3.0.5}/src/araim.rs +0 -0
  68. {sidereon-3.0.4 → sidereon-3.0.5}/src/atmosphere.rs +0 -0
  69. {sidereon-3.0.4 → sidereon-3.0.5}/src/bias.rs +0 -0
  70. {sidereon-3.0.4 → sidereon-3.0.5}/src/bodies.rs +0 -0
  71. {sidereon-3.0.4 → sidereon-3.0.5}/src/body_observe.rs +0 -0
  72. {sidereon-3.0.4 → sidereon-3.0.5}/src/broadcast_comparison.rs +0 -0
  73. {sidereon-3.0.4 → sidereon-3.0.5}/src/cdm.rs +0 -0
  74. {sidereon-3.0.4 → sidereon-3.0.5}/src/clock_stability.rs +0 -0
  75. {sidereon-3.0.4 → sidereon-3.0.5}/src/conjunction.rs +0 -0
  76. {sidereon-3.0.4 → sidereon-3.0.5}/src/constellation.rs +0 -0
  77. {sidereon-3.0.4 → sidereon-3.0.5}/src/core_error_detail.rs +0 -0
  78. {sidereon-3.0.4 → sidereon-3.0.5}/src/covariance.rs +0 -0
  79. {sidereon-3.0.4 → sidereon-3.0.5}/src/coverage.rs +0 -0
  80. {sidereon-3.0.4 → sidereon-3.0.5}/src/data_catalog.rs +0 -0
  81. {sidereon-3.0.4 → sidereon-3.0.5}/src/defaults.rs +0 -0
  82. {sidereon-3.0.4 → sidereon-3.0.5}/src/dgnss.rs +0 -0
  83. {sidereon-3.0.4 → sidereon-3.0.5}/src/doppler.rs +0 -0
  84. {sidereon-3.0.4 → sidereon-3.0.5}/src/elements.rs +0 -0
  85. {sidereon-3.0.4 → sidereon-3.0.5}/src/emission.rs +0 -0
  86. {sidereon-3.0.4 → sidereon-3.0.5}/src/equinoctial.rs +0 -0
  87. {sidereon-3.0.4 → sidereon-3.0.5}/src/error_metrics.rs +0 -0
  88. {sidereon-3.0.4 → sidereon-3.0.5}/src/estimation.rs +0 -0
  89. {sidereon-3.0.4 → sidereon-3.0.5}/src/events.rs +0 -0
  90. {sidereon-3.0.4 → sidereon-3.0.5}/src/exact_cache.rs +0 -0
  91. {sidereon-3.0.4 → sidereon-3.0.5}/src/exact_time.rs +0 -0
  92. {sidereon-3.0.4 → sidereon-3.0.5}/src/fallback.rs +0 -0
  93. {sidereon-3.0.4 → sidereon-3.0.5}/src/forces.rs +0 -0
  94. {sidereon-3.0.4 → sidereon-3.0.5}/src/format_diagnostics.rs +0 -0
  95. {sidereon-3.0.4 → sidereon-3.0.5}/src/frame_catalog.rs +0 -0
  96. {sidereon-3.0.4 → sidereon-3.0.5}/src/frames.rs +0 -0
  97. {sidereon-3.0.4 → sidereon-3.0.5}/src/fusion.rs +0 -0
  98. {sidereon-3.0.4 → sidereon-3.0.5}/src/geodesic.rs +0 -0
  99. {sidereon-3.0.4 → sidereon-3.0.5}/src/geodetic_time_series.rs +0 -0
  100. {sidereon-3.0.4 → sidereon-3.0.5}/src/geofence.rs +0 -0
  101. {sidereon-3.0.4 → sidereon-3.0.5}/src/geoid.rs +0 -0
  102. {sidereon-3.0.4 → sidereon-3.0.5}/src/geometry.rs +0 -0
  103. {sidereon-3.0.4 → sidereon-3.0.5}/src/geometry_quality.rs +0 -0
  104. {sidereon-3.0.4 → sidereon-3.0.5}/src/ils.rs +0 -0
  105. {sidereon-3.0.4 → sidereon-3.0.5}/src/iod.rs +0 -0
  106. {sidereon-3.0.4 → sidereon-3.0.5}/src/ionex.rs +0 -0
  107. {sidereon-3.0.4 → sidereon-3.0.5}/src/lambert.rs +0 -0
  108. {sidereon-3.0.4 → sidereon-3.0.5}/src/leap.rs +0 -0
  109. {sidereon-3.0.4 → sidereon-3.0.5}/src/least_squares.rs +0 -0
  110. {sidereon-3.0.4 → sidereon-3.0.5}/src/lib.rs +0 -0
  111. {sidereon-3.0.4 → sidereon-3.0.5}/src/marshal.rs +0 -0
  112. {sidereon-3.0.4 → sidereon-3.0.5}/src/nmea.rs +0 -0
  113. {sidereon-3.0.4 → sidereon-3.0.5}/src/normality.rs +0 -0
  114. {sidereon-3.0.4 → sidereon-3.0.5}/src/ntrip.rs +0 -0
  115. {sidereon-3.0.4 → sidereon-3.0.5}/src/observables.rs +0 -0
  116. {sidereon-3.0.4 → sidereon-3.0.5}/src/observation.rs +0 -0
  117. {sidereon-3.0.4 → sidereon-3.0.5}/src/oem.rs +0 -0
  118. {sidereon-3.0.4 → sidereon-3.0.5}/src/omm.rs +0 -0
  119. {sidereon-3.0.4 → sidereon-3.0.5}/src/opm.rs +0 -0
  120. {sidereon-3.0.4 → sidereon-3.0.5}/src/orbit_determination.rs +0 -0
  121. {sidereon-3.0.4 → sidereon-3.0.5}/src/ppp.rs +0 -0
  122. {sidereon-3.0.4 → sidereon-3.0.5}/src/ppp_corrections.rs +0 -0
  123. {sidereon-3.0.4 → sidereon-3.0.5}/src/products.rs +0 -0
  124. {sidereon-3.0.4 → sidereon-3.0.5}/src/propagation.rs +0 -0
  125. {sidereon-3.0.4 → sidereon-3.0.5}/src/qc.rs +0 -0
  126. {sidereon-3.0.4 → sidereon-3.0.5}/src/reduced_orbit.rs +0 -0
  127. {sidereon-3.0.4 → sidereon-3.0.5}/src/relative.rs +0 -0
  128. {sidereon-3.0.4 → sidereon-3.0.5}/src/reliability.rs +0 -0
  129. {sidereon-3.0.4 → sidereon-3.0.5}/src/rf.rs +0 -0
  130. {sidereon-3.0.4 → sidereon-3.0.5}/src/rinex.rs +0 -0
  131. {sidereon-3.0.4 → sidereon-3.0.5}/src/rinex_clock.rs +0 -0
  132. {sidereon-3.0.4 → sidereon-3.0.5}/src/rtcm.rs +0 -0
  133. {sidereon-3.0.4 → sidereon-3.0.5}/src/rtk.rs +0 -0
  134. {sidereon-3.0.4 → sidereon-3.0.5}/src/sbas_pl.rs +0 -0
  135. {sidereon-3.0.4 → sidereon-3.0.5}/src/sbas_ssr.rs +0 -0
  136. {sidereon-3.0.4 → sidereon-3.0.5}/src/scenario.rs +0 -0
  137. {sidereon-3.0.4 → sidereon-3.0.5}/src/sidereal.rs +0 -0
  138. {sidereon-3.0.4 → sidereon-3.0.5}/src/signal_analysis.rs +0 -0
  139. {sidereon-3.0.4 → sidereon-3.0.5}/src/sky.rs +0 -0
  140. {sidereon-3.0.4 → sidereon-3.0.5}/src/solve_error_detail.rs +0 -0
  141. {sidereon-3.0.4 → sidereon-3.0.5}/src/source_localization.rs +0 -0
  142. {sidereon-3.0.4 → sidereon-3.0.5}/src/space_weather.rs +0 -0
  143. {sidereon-3.0.4 → sidereon-3.0.5}/src/spk.rs +0 -0
  144. {sidereon-3.0.4 → sidereon-3.0.5}/src/spp.rs +0 -0
  145. {sidereon-3.0.4 → sidereon-3.0.5}/src/spp_error_detail.rs +0 -0
  146. {sidereon-3.0.4 → sidereon-3.0.5}/src/staleness.rs +0 -0
  147. {sidereon-3.0.4 → sidereon-3.0.5}/src/static_positioning.rs +0 -0
  148. {sidereon-3.0.4 → sidereon-3.0.5}/src/tca.rs +0 -0
  149. {sidereon-3.0.4 → sidereon-3.0.5}/src/tdm.rs +0 -0
  150. {sidereon-3.0.4 → sidereon-3.0.5}/src/terrain.rs +0 -0
  151. {sidereon-3.0.4 → sidereon-3.0.5}/src/terrain_store.rs +0 -0
  152. {sidereon-3.0.4 → sidereon-3.0.5}/src/tides.rs +0 -0
  153. {sidereon-3.0.4 → sidereon-3.0.5}/src/time_model_error.rs +0 -0
  154. {sidereon-3.0.4 → sidereon-3.0.5}/src/tle_fit_error_detail.rs +0 -0
  155. {sidereon-3.0.4 → sidereon-3.0.5}/src/tropo.rs +0 -0
  156. {sidereon-3.0.4 → sidereon-3.0.5}/src/unix_compress.rs +0 -0
  157. {sidereon-3.0.4 → sidereon-3.0.5}/tests/_helpers.py +0 -0
  158. {sidereon-3.0.4 → sidereon-3.0.5}/tests/_sp3_merge.py +0 -0
  159. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/README.md +0 -0
  160. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/antex/igs20_pasa_scoa_gps.atx +0 -0
  161. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/antex/igs20_wettzell_trim.atx +0 -0
  162. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/batch_fleet.json +0 -0
  163. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/bodies/observe_de.bsp +0 -0
  164. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/cdm.json +0 -0
  165. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/clk/lossless/rinex_clock304_table_a17.clk +0 -0
  166. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/clk/lossless/rinex_clock304_table_a18.clk +0 -0
  167. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/clk/synthetic_rinex_clock.clk +0 -0
  168. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/conjunction.json +0 -0
  169. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/constellation/gps_ops_sample.json +0 -0
  170. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/constellation/navcen_forecast_cases.html +0 -0
  171. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/constellation/navcen_gps_sample.html +0 -0
  172. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/events_bodies_dop.json +0 -0
  173. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/format_diagnostics_inputs.json +0 -0
  174. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/frames_time.json +0 -0
  175. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/geodesic/geodtest_row.dat +0 -0
  176. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/geoid/egm2008_25_norcal_crop.bin +0 -0
  177. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/gnss_data/igs22376.sp3.Z.b64 +0 -0
  178. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/gnss_data/ncompress-current-b9-counter.Z.b64 +0 -0
  179. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/listings/aiub-iono-prd-20260923.csv +0 -0
  180. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/listings/gfz-ultra-w2430-20260804.html +0 -0
  181. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/nav/BRDC00GOP_R_20210010000_01D_MN.rnx +0 -0
  182. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/nav/ESBC00DNK_R_20201770000_01D_MN.rnx +0 -0
  183. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/nav/KMS300DNK_R_20221591000_01H_MN.rnx +0 -0
  184. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/numerical_propagation.json +0 -0
  185. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/obs/ESBC00DNK_R_20201770000_01D_30S_MO_trim.crx +0 -0
  186. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/obs/ESBC00DNK_R_20201770000_01D_30S_MO_trim.rnx +0 -0
  187. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/obs/algo0010_2015001_v1_trim.crx +0 -0
  188. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/obs/algo0010_2015001_v1_trim.rnx +0 -0
  189. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/omm.json +0 -0
  190. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/pass_finder.json +0 -0
  191. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/rf_link_budget.json +0 -0
  192. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/rtk_wtzr.json +0 -0
  193. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/scenario_base.json +0 -0
  194. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/sgp4_topocentric.json +0 -0
  195. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/sp3/IGS0OPSFIN_20261200945_02H30M_15M_ORB.SP3 +0 -0
  196. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/sp3-merge-input-v1.json +0 -0
  197. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/sp3-merge-report-v3.json +0 -0
  198. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/sp3_bodies.json +0 -0
  199. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/spk/horizons_eros_type21.bsp +0 -0
  200. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/tdm/annex_e_01.kvn +0 -0
  201. {sidereon-3.0.4 → sidereon-3.0.5}/tests/fixtures/tle_roundtrip.json +0 -0
  202. {sidereon-3.0.4 → sidereon-3.0.5}/tests/golden/sp3-terminal-record-v1.json +0 -0
  203. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_012_bindings.py +0 -0
  204. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_013_bindings.py +0 -0
  205. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_014_bindings.py +0 -0
  206. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_015_bindings.py +0 -0
  207. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_016_wave2_bindings.py +0 -0
  208. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_017_domain_exposure.py +0 -0
  209. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_018_domain_exposure.py +0 -0
  210. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_019_track.py +0 -0
  211. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_020_error_metrics.py +0 -0
  212. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_antex.py +0 -0
  213. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_antex_public_equality.py +0 -0
  214. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_antex_public_pcv_sample_fields.py +0 -0
  215. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_atmosphere.py +0 -0
  216. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_attested_open.py +0 -0
  217. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_batch.py +0 -0
  218. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_body_observe_reference.py +0 -0
  219. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_broadcast_comparison.py +0 -0
  220. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_broadcast_fallback.py +0 -0
  221. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_cdm.py +0 -0
  222. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_cdm_top_level.py +0 -0
  223. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_cdm_value_equality.py +0 -0
  224. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_codec_error_details.py +0 -0
  225. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_conjunction.py +0 -0
  226. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_constellation.py +0 -0
  227. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_constellation_fleet.py +0 -0
  228. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_core_defaults.py +0 -0
  229. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_core_public_api_parity.py +0 -0
  230. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_core_round3_surfaces.py +0 -0
  231. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_covariance.py +0 -0
  232. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_covariance_propagation.py +0 -0
  233. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_coverage.py +0 -0
  234. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_crinex.py +0 -0
  235. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_data_publication_resilience.py +0 -0
  236. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_dgnss.py +0 -0
  237. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_distribution.py +0 -0
  238. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_distribution_fallback.py +0 -0
  239. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_dop_at_epoch.py +0 -0
  240. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_dop_convention.py +0 -0
  241. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_doppler.py +0 -0
  242. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_dted_tile_error_details.py +0 -0
  243. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_egm96.py +0 -0
  244. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_elements.py +0 -0
  245. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_events_bodies_dop.py +0 -0
  246. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_exact_cache_atomicity.py +0 -0
  247. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_field_mode_bindings.py +0 -0
  248. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_forces.py +0 -0
  249. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_format_diagnostics.py +0 -0
  250. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_frames_time.py +0 -0
  251. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ftp_transport.py +0 -0
  252. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_fusion_error_details.py +0 -0
  253. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_geoid.py +0 -0
  254. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_geometry.py +0 -0
  255. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ils.py +0 -0
  256. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_import.py +0 -0
  257. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_inertial_mechanization.py +0 -0
  258. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ionex.py +0 -0
  259. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ionex_instant_queries.py +0 -0
  260. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ionex_policies.py +0 -0
  261. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ionex_public_equality.py +0 -0
  262. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ionosphere_nequick.py +0 -0
  263. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_klobuchar_native.py +0 -0
  264. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_lambert_iod.py +0 -0
  265. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_leap_seconds.py +0 -0
  266. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_least_squares.py +0 -0
  267. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_misc_public_equality.py +0 -0
  268. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_moving_baseline.py +0 -0
  269. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_new_core_api.py +0 -0
  270. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_next_issue_due.py +0 -0
  271. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_nmea_finish_output.py +0 -0
  272. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ntrip.py +0 -0
  273. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_numerical.py +0 -0
  274. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_obs_policies.py +0 -0
  275. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observables.py +0 -0
  276. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observables_broadcast.py +0 -0
  277. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observables_error_details.py +0 -0
  278. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observation.py +0 -0
  279. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observation_qc.py +0 -0
  280. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_observe_batch.py +0 -0
  281. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_oem.py +0 -0
  282. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_oem_full_dto.py +0 -0
  283. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_omm.py +0 -0
  284. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_omm_full_dto.py +0 -0
  285. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_opm.py +0 -0
  286. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_opm_full_dto.py +0 -0
  287. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_parity_gaps.py +0 -0
  288. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_parse_tle_file.py +0 -0
  289. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_passes.py +0 -0
  290. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ppp.py +0 -0
  291. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ppp_corrections.py +0 -0
  292. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_ppp_range_corrections.py +0 -0
  293. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_precise_interpolant_public_values.py +0 -0
  294. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_precise_samples.py +0 -0
  295. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_precise_samples_errors.py +0 -0
  296. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_propagation.py +0 -0
  297. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_qc.py +0 -0
  298. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_reduced_orbit.py +0 -0
  299. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_reduced_orbit_piecewise.py +0 -0
  300. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_release_guard.py +0 -0
  301. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_residual_stats.py +0 -0
  302. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rf.py +0 -0
  303. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_clock.py +0 -0
  304. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_clock_equality.py +0 -0
  305. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_clock_header_field_details.py +0 -0
  306. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_clock_lossless.py +0 -0
  307. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_nav.py +0 -0
  308. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rinex_obs.py +0 -0
  309. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rtcm.py +0 -0
  310. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rtcm_departure_message_number_contract.py +0 -0
  311. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rtk.py +0 -0
  312. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_rtk_unresolved_carriers.py +0 -0
  313. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sbas_decoded_lists.py +0 -0
  314. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sbas_unassigned_mask.py +0 -0
  315. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_scenario_external_sources.py +0 -0
  316. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sky.py +0 -0
  317. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_bodies.py +0 -0
  318. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_catalog_exact.py +0 -0
  319. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_clock_reference.py +0 -0
  320. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_interpolation_policy.py +0 -0
  321. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_merge_coverage.py +0 -0
  322. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_merge_per_epoch_provenance.py +0 -0
  323. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_public_metadata.py +0 -0
  324. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_terminal_record.py +0 -0
  325. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_sp3_window_continuity.py +0 -0
  326. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_spk.py +0 -0
  327. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_spp.py +0 -0
  328. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_spp_error_details.py +0 -0
  329. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_spp_glonass.py +0 -0
  330. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_spp_robustness.py +0 -0
  331. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_staleness.py +0 -0
  332. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_stub_accuracy.py +0 -0
  333. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tca.py +0 -0
  334. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tdm_policies.py +0 -0
  335. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_terrain_unknown_elevation.py +0 -0
  336. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tides.py +0 -0
  337. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_time_model_error_details.py +0 -0
  338. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_time_offset_error_details.py +0 -0
  339. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tle_fit.py +0 -0
  340. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tle_roundtrip.py +0 -0
  341. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_tropo.py +0 -0
  342. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_unix_compress.py +0 -0
  343. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_visible_ground_track.py +0 -0
  344. {sidereon-3.0.4 → sidereon-3.0.5}/tests/test_weather_terrain_error_details.py +0 -0
  345. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_licenses/ERFA-BSD-3-Clause.txt +0 -0
  346. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_licenses/IERS-CONVENTIONS-SOFTWARE-LICENSE.txt +0 -0
  347. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_licenses/SciPy-BSD-3-Clause.txt +0 -0
  348. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_source/sidereon-core-3.0.4/tides/mod.rs +0 -0
  349. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_source/sidereon-core-3.0.4/tides/ocean.rs +0 -0
  350. {sidereon-3.0.4 → sidereon-3.0.5}/third_party_source/sidereon-core-3.0.4/tides/pole.rs +0 -0
@@ -169,6 +169,8 @@ jobs:
169
169
  run: uv run --locked --extra test --group dev mypy
170
170
  - name: Build the extension (maturin develop)
171
171
  run: uv run --locked --extra test --group dev maturin develop --locked --features exact-cache-test-failpoints
172
+ - name: Release consumer contract
173
+ run: uv run --locked --extra test --group dev python scripts/smoke-release.py
172
174
  - name: stub accuracy
173
175
  env:
174
176
  SIDEREON_CORE_FIXTURES: ${{ github.workspace }}/.core-checkout/crates/sidereon-core/tests/fixtures
@@ -153,10 +153,10 @@ jobs:
153
153
  done
154
154
  for source in mod.rs ocean.rs pole.rs; do
155
155
  unzip -p "$wheel" \
156
- "third_party_source/sidereon-core-3.0.4/tides/$source" \
156
+ "third_party_source/sidereon-core-3.0.5/tides/$source" \
157
157
  >"$RUNNER_TEMP/$source"
158
158
  cmp "$RUNNER_TEMP/$source" \
159
- "third_party_source/sidereon-core-3.0.4/tides/$source"
159
+ "third_party_source/sidereon-core-3.0.5/tides/$source"
160
160
  done
161
161
  done
162
162
  notice="$RUNNER_TEMP/sdist.notices"
@@ -173,10 +173,10 @@ jobs:
173
173
  done
174
174
  for source in mod.rs ocean.rs pole.rs; do
175
175
  tar -xOf "${sdists[0]}" --wildcards \
176
- "*/third_party_source/sidereon-core-3.0.4/tides/$source" \
176
+ "*/third_party_source/sidereon-core-3.0.5/tides/$source" \
177
177
  >"$RUNNER_TEMP/sdist-$source"
178
178
  cmp "$RUNNER_TEMP/sdist-$source" \
179
- "third_party_source/sidereon-core-3.0.4/tides/$source"
179
+ "third_party_source/sidereon-core-3.0.5/tides/$source"
180
180
  done
181
181
  - name: Smoke-test installed x86_64 Linux wheel
182
182
  shell: bash
@@ -4,6 +4,20 @@ All notable changes to the Sidereon Python interface are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.0.5] - 2026-10-30
8
+
9
+ ### Added
10
+
11
+ - Add opt-in SP3 column rounding writers that report maximum absolute SI changes; strict writers remain unchanged.
12
+ - Preserve strictly writable SP3 columns for grid-aligned merge and alignment products.
13
+
14
+ ### Fixed
15
+
16
+ - Normalize extreme-scale quaternions robustly while preserving ordinary quaternion rounding.
17
+ - Reject zero probability values where probability inputs must be strictly between zero and one.
18
+ - Describe the seven raw LNAV broadcast angular fields as semicircles or
19
+ semicircles/second, matching their unchanged transmitted-integer scaling.
20
+
7
21
  ## [3.0.4] - 2026-10-18
8
22
 
9
23
  ### Fixed
@@ -611,9 +611,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
611
611
 
612
612
  [[package]]
613
613
  name = "sidereon"
614
- version = "3.0.4"
614
+ version = "3.0.5"
615
615
  source = "registry+https://github.com/rust-lang/crates.io-index"
616
- checksum = "c042d74f42d0e2a9f8b2645109fd06f407e42ecab964fb91c492b18a2c034096"
616
+ checksum = "fa3b2d18f88640b1bc0a86ed17bbacd9298eeea5b202174ec8da5ce7ae9a46e2"
617
617
  dependencies = [
618
618
  "crc32fast",
619
619
  "flate2",
@@ -622,9 +622,9 @@ dependencies = [
622
622
 
623
623
  [[package]]
624
624
  name = "sidereon-core"
625
- version = "3.0.4"
625
+ version = "3.0.5"
626
626
  source = "registry+https://github.com/rust-lang/crates.io-index"
627
- checksum = "3634af272103625414a0e63bacbf2b76e181c33f8652690823acdfa0e25c938e"
627
+ checksum = "3ba2c37c8b12bc20827e8e90971d635895e8a45fc715ad81c38d45cc3a4d3591"
628
628
  dependencies = [
629
629
  "approx",
630
630
  "cc",
@@ -646,7 +646,7 @@ dependencies = [
646
646
 
647
647
  [[package]]
648
648
  name = "sidereon-py"
649
- version = "3.0.4"
649
+ version = "3.0.5"
650
650
  dependencies = [
651
651
  "nalgebra",
652
652
  "numpy",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sidereon-py"
3
- version = "3.0.4"
3
+ version = "3.0.5"
4
4
  authors = []
5
5
  edition = "2021"
6
6
  description = "Python (PyO3) bindings over the sidereon ergonomic API: SP3 loading and SPP/RTK/PPP positioning solves"
@@ -25,8 +25,8 @@ exact-cache-test-failpoints = ["sidereon-core/exact-cache-test-failpoints"]
25
25
  # The thin ergonomic engine surface this binding marshals to. No modeling logic
26
26
  # lives here: every solve delegates to a `sidereon` entry point, which in turn
27
27
  # delegates to `sidereon-core`.
28
- sidereon = "=3.0.4"
29
- sidereon-core = { version = "=3.0.4", features = ["mmap"] }
28
+ sidereon = { version = "=3.0.5" }
29
+ sidereon-core = { version = "=3.0.5", features = ["mmap"] }
30
30
  # The standalone general-purpose trust-region least-squares solver: a data-driven
31
31
  # `least_squares` whose residual and Jacobian are evaluated entirely in Rust, so
32
32
  # no per-iteration Python callback is paid.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sidereon
3
- Version: 3.0.4
3
+ Version: 3.0.5
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Classifier: Topic :: Scientific/Engineering
@@ -280,7 +280,10 @@ The Python package mirrors the full breadth of the engine.
280
280
  defines, including exact validity seconds (see `docs/antex.md`).
281
281
  - **Data acquisition:** the `sidereon.data` module downloads and caches GNSS
282
282
  products (SP3 and IONEX from IGS/MGEX analysis centers, including merged
283
- multi-center SP3) and DTED terrain tiles. Exact-product requests can select
283
+ multi-center SP3) and DTED terrain tiles. Merges from column-grid inputs
284
+ retain SP3-writable records; higher-precision inputs can still need the
285
+ opt-in rounding writer. Agreement metrics use combined values before final
286
+ column quantization. Exact-product requests can select
284
287
  direct archives, NASA CDDIS/Earthdata, local files, or caller-provided bytes
285
288
  without changing product identity. `ExactSp3Request` validates the declared
286
289
  cadence, regular epoch grid, exact half-open or inclusive coverage, producer,
@@ -261,7 +261,10 @@ The Python package mirrors the full breadth of the engine.
261
261
  defines, including exact validity seconds (see `docs/antex.md`).
262
262
  - **Data acquisition:** the `sidereon.data` module downloads and caches GNSS
263
263
  products (SP3 and IONEX from IGS/MGEX analysis centers, including merged
264
- multi-center SP3) and DTED terrain tiles. Exact-product requests can select
264
+ multi-center SP3) and DTED terrain tiles. Merges from column-grid inputs
265
+ retain SP3-writable records; higher-precision inputs can still need the
266
+ opt-in rounding writer. Agreement metrics use combined values before final
267
+ column quantization. Exact-product requests can select
265
268
  direct archives, NASA CDDIS/Earthdata, local files, or caller-provided bytes
266
269
  without changing product identity. `ExactSp3Request` validates the declared
267
270
  cadence, regular epoch grid, exact half-open or inclusive coverage, producer,
@@ -330,9 +330,9 @@ endorsed by the IERS Conventions Center. The Rust source describes the origin,
330
330
  the renamed routines, and the differences from the Fortran implementation.
331
331
  The complete non-test tide source distributed in the compiled extension is
332
332
  included under
333
- `third_party_source/sidereon-core-3.0.4/tides/` in wheels and source
334
- distributions. These files match sidereon-core 3.0.4 at commit 5cfcdf929b57a5998e304df786735b171ba8afaf.
335
- Source: [sidereon-core tides at the pinned commit](https://github.com/neilberkman/sidereon/tree/5cfcdf929b57a5998e304df786735b171ba8afaf/crates/sidereon-core/src/tides).
333
+ `third_party_source/sidereon-core-3.0.5/tides/` in wheels and source
334
+ distributions. These files match sidereon-core 3.0.5 at commit da59d0eba8e273cc37821eac5535662e5d991126.
335
+ Source: [sidereon-core tides at the pinned commit](https://github.com/neilberkman/sidereon/tree/da59d0eba8e273cc37821eac5535662e5d991126/crates/sidereon-core/src/tides).
336
336
  The authoritative original is available from:
337
337
 
338
338
  <https://iers-conventions.obspm.fr/content/chapter7/software/dehanttideinel/DEHANTTIDEINEL.F>
@@ -278,11 +278,11 @@ except sidereon.Sp3WriteError as error:
278
278
  assert data.verify_merge_report(json.loads(persisted_report_json))
279
279
  ```
280
280
 
281
- The file helpers write SP3 text only when every value restates exactly. The
282
- default mean merge of two centers holds positions finer than the millimetre
283
- record column wherever the centers differ, and a clock moved onto the reference
284
- clock datum can be finer than its column; `Sp3WriteError` names the value
285
- instead of the helper rounding it.
281
+ The merge path preserves grid-in/grid-out for record values derived from
282
+ on-grid inputs, including means between column values. The file helpers write
283
+ those merged products directly. Other products can still hold higher-precision
284
+ values than the record columns; the strict writer names those with
285
+ `Sp3WriteError`, while the explicit rounding writer reports its SI changes.
286
286
 
287
287
  `report.to_dict()` writes merged-SP3 report schema 3. A merge keeps a
288
288
  satellite whose orbit is missing but whose clock reached a consensus as a
@@ -25,6 +25,7 @@ parsing, rounding or repair of its own.
25
25
  | `clock_record(satellite, epoch_index)` | `Sp3ClockRecord`: a clock beside a missing orbit |
26
26
  | `clock_records_at(epoch_index)` | dict from satellite token to `Sp3ClockRecord` at one epoch, ascending |
27
27
  | `to_sp3_string()` | the SP3 text, or `Sp3WriteError` |
28
+ | `to_sp3_string_with_rounding()` | `(text, Sp3RoundingReport)`, or `Sp3WriteError` |
28
29
 
29
30
  `state` and `clock_record` raise `IndexError` for an epoch index past the end
30
31
  and `KeyError` when the satellite has no such record at that epoch.
@@ -86,11 +87,20 @@ back unchanged; it can still be refused on a header base finer than its field,
86
87
  on header text wider than its columns, or on velocity records under a `P`
87
88
  header.
88
89
 
89
- A mean or median merge usually holds positions finer than the millimetre
90
- `F14.6` column, and a clock shifted onto the reference clock datum can hold a
91
- clock finer than its column, so writing such a merge raises
92
- `RecordValueNotRepresentable` naming the value. `data.write_sp3` and
93
- `data.fetch_merged_sp3_file` write nothing in that case.
90
+ `to_sp3_string_with_rounding()` is an explicit alternative for products whose
91
+ record values need the precision of their SP3 columns. It returns
92
+ `(text, Sp3RoundingReport)`. The immutable report gives maximum absolute changes
93
+ per field in SI units (`position_*_m`, `velocity_*_m_s`, `clock_s`, and
94
+ `clock_rate_s_s`). It applies no rounding to header fields or epochs; refusals
95
+ there still raise `Sp3WriteError`. `data.write_sp3_with_rounding()` writes the
96
+ text atomically and returns `(path, report)`.
97
+
98
+ The merge path preserves grid-in/grid-out for record values derived from
99
+ on-grid inputs, including means between column values. Agreement and dispersion
100
+ metrics are computed from combined values before the final column quantization;
101
+ their arithmetic is unchanged. Other products can still hold record values
102
+ finer than their columns; the strict writer refuses those, and the explicit
103
+ rounding writer can state them while reporting the maximum SI change.
94
104
 
95
105
  ### Sp3WriteError
96
106
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "sidereon"
7
- version = "3.0.4"
7
+ version = "3.0.5"
8
8
  description = "Python (PyO3) bindings over the sidereon engine: SP3 loading and SPP/RTK/PPP positioning solves"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -82,7 +82,7 @@ include = [
82
82
  "third_party_licenses/ERFA-BSD-3-Clause.txt",
83
83
  "third_party_licenses/IERS-CONVENTIONS-SOFTWARE-LICENSE.txt",
84
84
  "third_party_licenses/SciPy-BSD-3-Clause.txt",
85
- "third_party_source/sidereon-core-3.0.4/tides/mod.rs",
86
- "third_party_source/sidereon-core-3.0.4/tides/ocean.rs",
87
- "third_party_source/sidereon-core-3.0.4/tides/pole.rs",
85
+ "third_party_source/sidereon-core-3.0.5/tides/mod.rs",
86
+ "third_party_source/sidereon-core-3.0.5/tides/ocean.rs",
87
+ "third_party_source/sidereon-core-3.0.5/tides/pole.rs",
88
88
  ]
@@ -509,6 +509,8 @@ class Sp3:
509
509
  def to_sp3_string(self) -> str:
510
510
  """Serialize to SP3 text; raises `Sp3WriteError` rather than change a
511
511
  value to fit its columns."""
512
+ def to_sp3_string_with_rounding(self) -> tuple[str, Sp3RoundingReport]:
513
+ """Write rounded record values and return their maximum SI deltas."""
512
514
  def precise_ephemeris_samples(self) -> list[PreciseEphemerisSample]: ...
513
515
  def precise_ephemeris_accuracy_samples(
514
516
  self,
@@ -518,6 +520,26 @@ class Sp3:
518
520
  ) -> bytes: ...
519
521
  def __repr__(self) -> str: ...
520
522
 
523
+ class Sp3RoundingReport:
524
+ """Immutable maximum absolute SI changes made by the SP3 rounding writer."""
525
+ @property
526
+ def position_x_m(self) -> float: ...
527
+ @property
528
+ def position_y_m(self) -> float: ...
529
+ @property
530
+ def position_z_m(self) -> float: ...
531
+ @property
532
+ def velocity_x_m_s(self) -> float: ...
533
+ @property
534
+ def velocity_y_m_s(self) -> float: ...
535
+ @property
536
+ def velocity_z_m_s(self) -> float: ...
537
+ @property
538
+ def clock_s(self) -> float: ...
539
+ @property
540
+ def clock_rate_s_s(self) -> float: ...
541
+ def __repr__(self) -> str: ...
542
+
521
543
  class ExactSp3ValidationError(ValueError):
522
544
  kind: str | None
523
545
  detail: dict[str, object] | None
@@ -13177,9 +13199,13 @@ class LnavDecoded:
13177
13199
  @property
13178
13200
  def crs(self) -> float: ...
13179
13201
  @property
13180
- def delta_n(self) -> float: ...
13202
+ def delta_n(self) -> float:
13203
+ """Mean motion difference, semicircles/second."""
13204
+ ...
13181
13205
  @property
13182
- def m0(self) -> float: ...
13206
+ def m0(self) -> float:
13207
+ """Mean anomaly at reference time, semicircles."""
13208
+ ...
13183
13209
  @property
13184
13210
  def cuc(self) -> float: ...
13185
13211
  @property
@@ -13197,19 +13223,29 @@ class LnavDecoded:
13197
13223
  @property
13198
13224
  def cic(self) -> float: ...
13199
13225
  @property
13200
- def omega0(self) -> float: ...
13226
+ def omega0(self) -> float:
13227
+ """Longitude of ascending node at reference time, semicircles."""
13228
+ ...
13201
13229
  @property
13202
13230
  def cis(self) -> float: ...
13203
13231
  @property
13204
- def i0(self) -> float: ...
13232
+ def i0(self) -> float:
13233
+ """Inclination at reference time, semicircles."""
13234
+ ...
13205
13235
  @property
13206
13236
  def crc(self) -> float: ...
13207
13237
  @property
13208
- def omega(self) -> float: ...
13238
+ def omega(self) -> float:
13239
+ """Argument of perigee, semicircles."""
13240
+ ...
13209
13241
  @property
13210
- def omega_dot(self) -> float: ...
13242
+ def omega_dot(self) -> float:
13243
+ """Rate of right ascension, semicircles/second."""
13244
+ ...
13211
13245
  @property
13212
- def idot(self) -> float: ...
13246
+ def idot(self) -> float:
13247
+ """Rate of inclination, semicircles/second."""
13248
+ ...
13213
13249
  def __repr__(self) -> str: ...
13214
13250
 
13215
13251
  def lnav_tow(bits: Sequence[int] | bytes) -> int | None: ...
@@ -284,6 +284,7 @@ __all__ = [
284
284
  "sp3_merge_input_identity",
285
285
  "verify_merge_report",
286
286
  "write_sp3",
287
+ "write_sp3_with_rounding",
287
288
  "DistributionSource",
288
289
  "Distribution",
289
290
  "ProductIdentity",
@@ -5872,9 +5873,10 @@ def write_sp3(sp3: "sidereon.Sp3", path: str, *, gzip: bool = False) -> str:
5872
5873
 
5873
5874
  Pass ``gzip=True`` to gzip-compress the output (pair it with a ``.gz``
5874
5875
  extension on ``path``). Returns the written path. Raises
5875
- :class:`sidereon.Sp3WriteError` before anything is written when the
5876
- product holds a value SP3 text cannot state, such as a mean-merged position
5877
- finer than a millimetre; ``detail`` names the value.
5876
+ :class:`sidereon.Sp3WriteError` before anything is written when a product
5877
+ holds record values finer than SP3 columns; ``detail`` names the value.
5878
+ Merges from column-grid inputs keep records on that grid, including means.
5879
+ Higher-precision inputs can still require :func:`write_sp3_with_rounding`.
5878
5880
  """
5879
5881
  data = sp3.to_sp3_string().encode("ascii")
5880
5882
  if gzip:
@@ -5890,6 +5892,29 @@ def write_sp3(sp3: "sidereon.Sp3", path: str, *, gzip: bool = False) -> str:
5890
5892
  return path
5891
5893
 
5892
5894
 
5895
+ def write_sp3_with_rounding(
5896
+ sp3: "sidereon.Sp3", path: str, *, gzip: bool = False
5897
+ ) -> tuple[str, "sidereon.Sp3RoundingReport"]:
5898
+ """Write rounded SP3 record values atomically and return ``(path, report)``.
5899
+
5900
+ Header fields and epochs retain the exact checks of the strict writer. The
5901
+ immutable report gives maximum absolute changes in SI units for each field.
5902
+ """
5903
+ text, report = sp3.to_sp3_string_with_rounding()
5904
+ data = text.encode("ascii")
5905
+ if gzip:
5906
+ data = _gzip.compress(data)
5907
+ directory = _os.path.dirname(_os.path.abspath(path))
5908
+ _ensure_dir(directory)
5909
+ tmp = _write_temp(directory, data)
5910
+ try:
5911
+ _os.replace(tmp, path)
5912
+ except OSError as exc:
5913
+ _silent_remove(tmp)
5914
+ raise CacheNotWritable(f"cannot write {path}: {exc}") from exc
5915
+ return path, report
5916
+
5917
+
5893
5918
  def fetch_merged_sp3_file(
5894
5919
  target: Union[_dt.date, _dt.datetime],
5895
5920
  centers: Sequence[str],
@@ -5911,10 +5936,11 @@ def fetch_merged_sp3_file(
5911
5936
  path by default. Pass ``return_report=True`` to receive ``(path, report)``
5912
5937
  and retain the exact contributor provenance and stable input identity.
5913
5938
  Nothing is written if the fetch/merge step raises, or if the merged product
5914
- cannot be written as SP3 text (:class:`sidereon.Sp3WriteError`). A mean or
5915
- median of positions that differ below the column resolution, or a clock
5916
- shifted onto the reference clock datum, can be finer than its SP3 column;
5917
- ``detail`` on the exception names the value.
5939
+ cannot be written as SP3 text (:class:`sidereon.Sp3WriteError`). When
5940
+ contributors are on the SP3 column grid, merge results stay on that grid,
5941
+ including means, medians and clock-datum adjustments. Higher-precision
5942
+ contributor values can still require :func:`write_sp3_with_rounding`;
5943
+ ``detail`` on a strict-writer exception names the value.
5918
5944
  """
5919
5945
  merged, report = fetch_merged_sp3(
5920
5946
  target,
@@ -494,3 +494,6 @@ def fetch_merged_sp3_file(
494
494
  **fetch_opts: object,
495
495
  ) -> Union[str, tuple[str, MergeReport]]: ...
496
496
  def write_sp3(sp3: sidereon.Sp3, path: str, *, gzip: bool = ...) -> str: ...
497
+ def write_sp3_with_rounding(
498
+ sp3: sidereon.Sp3, path: str, *, gzip: bool = ...
499
+ ) -> tuple[str, sidereon.Sp3RoundingReport]: ...
@@ -13,7 +13,7 @@ import tomllib
13
13
  ROOT = Path(__file__).resolve().parents[1]
14
14
  CORE_GIT_URL = "https://github.com/neilberkman/sidereon"
15
15
  FULL_GIT_REVISION = re.compile(r"^[0-9a-f]{40}$")
16
- CORE_SOURCE_REVISION = "5cfcdf929b57a5998e304df786735b171ba8afaf"
16
+ CORE_SOURCE_REVISION = "da59d0eba8e273cc37821eac5535662e5d991126"
17
17
 
18
18
 
19
19
  def registry_version(name, value, package_version):
@@ -307,7 +307,7 @@ def main() -> None:
307
307
  "ocean.rs": "25946677944425671a92717860ac2d70f255de5403eeb1fbf98b361716821d5c",
308
308
  "pole.rs": "b4cc4c16bdd8ce1d8f04073602ab47dfb85a002b946ab192e8d4d2d600f0a1f8",
309
309
  }
310
- tide_root = ROOT / "third_party_source" / "sidereon-core-3.0.4" / "tides"
310
+ tide_root = ROOT / "third_party_source" / "sidereon-core-3.0.5" / "tides"
311
311
  for filename, expected_digest in tide_sources.items():
312
312
  source = tide_root / filename
313
313
  if not source.is_file():
@@ -316,7 +316,7 @@ def main() -> None:
316
316
  if digest != expected_digest:
317
317
  raise SystemExit(
318
318
  f"IERS-derived source disclosure {source} has digest {digest}, "
319
- f"expected {expected_digest} from sidereon-core 3.0.4 at "
319
+ f"expected {expected_digest} from sidereon-core 3.0.5 at "
320
320
  f"{CORE_SOURCE_REVISION}"
321
321
  )
322
322
 
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
8
8
 
9
9
  const REGISTRY_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index";
10
10
  const GIT_SOURCE_PREFIX: &str = "git+https://github.com/neilberkman/sidereon?rev=";
11
- const RELEASE_VERSION: &str = "3.0.4";
11
+ const RELEASE_VERSION: &str = "3.0.5";
12
12
 
13
13
  #[derive(Debug)]
14
14
  struct LockedPackage {
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ """Smoke-test public APIs from the installed release artifact."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import math
7
+ import os
8
+ import struct
9
+
10
+ import sidereon
11
+
12
+
13
+ def _sp3(text: str):
14
+ return sidereon.load_sp3(text.encode("ascii"))
15
+
16
+
17
+ def _check_sp3_grid_merge() -> None:
18
+ first_text = """\
19
+ #cP2020 6 25 0 0 0.00000000 1 ORBIT IGS14 FIT TST
20
+ ## 2111 432000.00000000 900.00000000 59025 0.0000000000000
21
+ + 1 G01 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
22
+ ++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
23
+ %c G cc GPS ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
24
+ %c cc cc ccc ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
25
+ %f 1.2500000 1.025000000 0.00000000000 0.000000000000000
26
+ %f 0.0000000 0.000000000 0.00000000000 0.000000000000000
27
+ %i 0 0 0 0 0 0 0 0 0
28
+ %i 0 0 0 0 0 0 0 0 0
29
+ /* RELEASE CONSUMER SP3-C FIXTURE
30
+ * 2020 6 25 0 0 0.00000000
31
+ PG01 15000.000000 -20000.000000 5000.000000 100.000000
32
+ EOF
33
+ """
34
+ second_text = first_text.replace("15000.000000", "15000.000001")
35
+ merged, _report = sidereon.merge_sp3(
36
+ [_sp3(first_text), _sp3(second_text)],
37
+ sidereon.Sp3MergeOptions(min_agree=1, clock_min_common=1),
38
+ )
39
+
40
+ strict_text = merged.to_sp3_string()
41
+ reread = sidereon.load_sp3(strict_text.encode("ascii"))
42
+ first_x = _sp3(first_text).state("G01", 0).position_m[0]
43
+ second_x = _sp3(second_text).state("G01", 0).position_m[0]
44
+ x_m = reread.state("G01", 0).position_m[0]
45
+ if x_m != merged.state("G01", 0).position_m[0] or x_m not in (first_x, second_x):
46
+ raise SystemExit(
47
+ "strict SP3 merge round-trip did not preserve a parser-scaled grid value"
48
+ )
49
+
50
+ rounded_text, rounding = merged.to_sp3_string_with_rounding()
51
+ if rounded_text != strict_text or rounding.position_x_m != 0.0:
52
+ raise SystemExit(
53
+ "SP3 rounding report disagrees with the writable merged product"
54
+ )
55
+
56
+
57
+ def _clock_sp3(clocks_us: list[float]):
58
+ satellites = ["G01", "G02", "G03"]
59
+ satellite_header = "".join(satellites) + " 0" * 14
60
+ records = "\n".join(
61
+ f"P{sat}{15000.0:14.6f}{-20000.0:14.6f}{5000.0:14.6f}{clock:14.6f}"
62
+ for sat, clock in zip(satellites, clocks_us)
63
+ )
64
+ text = f"""\
65
+ #cP2020 6 25 0 0 0.00000000 1 ORBIT IGS14 FIT TST
66
+ ## 2111 432000.00000000 900.00000000 59025 0.0000000000000
67
+ + 3 {satellite_header}
68
+ ++ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
69
+ %c G cc GPS ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
70
+ %c cc cc ccc ccc cccc cccc cccc cccc ccccc ccccc ccccc ccccc
71
+ %f 1.2500000 1.025000000 0.00000000000 0.000000000000000
72
+ %f 0.0000000 0.000000000 0.00000000000 0.000000000000000
73
+ %i 0 0 0 0 0 0 0 0 0
74
+ %i 0 0 0 0 0 0 0 0 0
75
+ /* RELEASE CONSUMER SP3 CLOCK FIXTURE
76
+ * 2020 6 25 0 0 0.00000000
77
+ {records}
78
+ EOF
79
+ """
80
+ return _sp3(text)
81
+
82
+
83
+ def _check_sp3_half_grid_clock_merge() -> None:
84
+ first = _clock_sp3([-598.410235, 138.983111, 0.0])
85
+ second = _clock_sp3([-598.410234, 138.983110, 0.0])
86
+ first.to_sp3_string()
87
+ second.to_sp3_string()
88
+ merged, _report = sidereon.merge_sp3(
89
+ [first, second], sidereon.Sp3MergeOptions(min_agree=1, clock_min_common=3)
90
+ )
91
+
92
+ strict_text = merged.to_sp3_string()
93
+ reread = sidereon.load_sp3(strict_text.encode("ascii"))
94
+ for satellite in ("G01", "G02", "G03"):
95
+ left = first.state(satellite, 0).clock_s
96
+ right = second.state(satellite, 0).clock_s
97
+ clock = reread.state(satellite, 0).clock_s
98
+ if clock != merged.state(satellite, 0).clock_s or clock not in (left, right):
99
+ raise SystemExit(f"strict SP3 merge round-trip changed {satellite} clock")
100
+
101
+ rounded_text, report = merged.to_sp3_string_with_rounding()
102
+ if (
103
+ rounded_text != strict_text
104
+ or report.clock_s != 0.0
105
+ or report.clock_rate_s_s != 0.0
106
+ ):
107
+ raise SystemExit(
108
+ "SP3 half-grid clock merge did not return a zero rounding report"
109
+ )
110
+
111
+
112
+ def main() -> None:
113
+ expected_version = os.environ.get("SIDEREON_EXPECTED_VERSION")
114
+ if expected_version is not None and sidereon.__version__ != expected_version:
115
+ raise SystemExit(
116
+ f"installed sidereon {sidereon.__version__}, expected {expected_version}"
117
+ )
118
+
119
+ header = struct.pack(">ddddii", -90.0, -180.0, 0.25, 0.25, 721, 1440)
120
+ data = header + struct.pack(">f", 2.5) * (721 * 1440)
121
+ grid = sidereon.GeoidGrid.from_proj_egm96_gtx(data)
122
+
123
+ for arithmetic in (
124
+ sidereon.ProjVgridshiftArithmetic.SEPARATE_MULTIPLY_ADD,
125
+ sidereon.ProjVgridshiftArithmetic.FUSED_MULTIPLY_ADD,
126
+ ):
127
+ got = grid.undulation_proj_rad(
128
+ math.radians(40.0), math.radians(-105.0), arithmetic
129
+ )
130
+ if got != 2.5:
131
+ raise SystemExit(f"PROJ EGM96 GTX smoke returned {got}, expected 2.5")
132
+
133
+ try:
134
+ grid.undulation_proj_rad(
135
+ math.nan,
136
+ 0.0,
137
+ sidereon.ProjVgridshiftArithmetic.FUSED_MULTIPLY_ADD,
138
+ )
139
+ except sidereon.ProjVgridshiftNonFiniteCoordinateError as error:
140
+ if not isinstance(error, (sidereon.ProjVgridshiftError, ValueError)):
141
+ raise SystemExit(
142
+ "typed PROJ coordinate exception hierarchy is broken"
143
+ ) from None
144
+ else:
145
+ raise SystemExit("non-finite PROJ coordinate did not raise")
146
+
147
+ _check_sp3_grid_merge()
148
+ _check_sp3_half_grid_clock_merge()
149
+
150
+ print(f"sidereon {sidereon.__version__} release smoke passed")
151
+
152
+
153
+ if __name__ == "__main__":
154
+ main()