space-data-module-sdk 0.8.16 → 0.8.17
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.
- package/bin/space-data-module.js +24 -6
- package/docs/browser-wasmedge-isomorphic.html +1 -1
- package/docs/byo-wasm-quickstart.html +1 -1
- package/docs/conformance.html +10 -4
- package/docs/conformance.md +20 -4
- package/docs/events-abi.html +1 -1
- package/docs/families/analytics.html +1 -1
- package/docs/families/attitude.html +1 -1
- package/docs/families/behavior.html +1 -1
- package/docs/families/breakup.html +1 -1
- package/docs/families/conjunction.html +1 -1
- package/docs/families/data-source.html +7 -2
- package/docs/families/effects.html +1 -1
- package/docs/families/environment.html +1 -1
- package/docs/families/estimation.html +56 -54
- package/docs/families/estimation.md +155 -125
- package/docs/families/gnc.html +1 -1
- package/docs/families/maneuver.html +1 -1
- package/docs/families/obstruction.html +1 -1
- package/docs/families/propagator.html +16 -2
- package/docs/families/propulsion.html +1 -1
- package/docs/families/reentry.html +1 -1
- package/docs/families/rf.html +1 -1
- package/docs/families/scheduler.html +1 -1
- package/docs/families/sensor.html +1 -1
- package/docs/families/signature.html +1 -1
- package/docs/flatsql-host-contract.html +1 -1
- package/docs/flatsql-streaming-standard.html +1 -1
- package/docs/gpu-module-abi.html +1 -1
- package/docs/harness-family-matrix.md +7 -7
- package/docs/index.html +2 -2
- package/docs/isomorphic-pthreads.html +1 -1
- package/docs/isomorphic-sdn-runtime-plan.html +1 -1
- package/docs/language-runtime-matrix.html +1 -1
- package/docs/llms.txt +1 -1
- package/docs/module-bundle-runtime-plan.html +1 -1
- package/docs/module-publication-standard.html +1 -1
- package/docs/node-red-default-node-parity.html +1 -1
- package/docs/propagator-abi.md +52 -0
- package/docs/protect-and-sign.html +1 -1
- package/docs/protocol-installation.html +1 -1
- package/docs/provider-access-abi.md +25 -0
- package/docs/publication-submission.html +1 -1
- package/docs/secrets-capability.html +1 -1
- package/docs/testing-harness.html +1 -1
- package/docs/tri-runtime-parity-gate.html +1 -1
- package/docs/tri-runtime-parity.html +1 -1
- package/include/orbpro/orbpro_estimation_abi.h +883 -0
- package/include/orbpro/orbpro_propagator_abi.h +58 -0
- package/package.json +4 -2
- package/schemas/orbpro/Estimation.fbs +244 -0
- package/schemas/orbpro/Propagator.fbs +64 -0
- package/src/conformance/estimationReference.js +81 -0
- package/src/conformance/estimationSuite.js +134 -0
- package/src/conformance/index.js +39 -2
- package/src/generated/orbpro/propagator-abi.js +21 -0
- package/src/generated/orbpro/propagator-abi.ts +50 -0
- package/src/standards/sharedCatalog.js +7 -0
- package/templates/estimation-module/README.md +24 -0
- package/templates/estimation-module/build.mjs +32 -0
- package/templates/estimation-module/package.json +12 -0
- package/templates/estimation-module/plugin-manifest.json +34 -0
- package/templates/estimation-module/src/reference_estimation.cpp +53 -0
|
@@ -82,6 +82,64 @@ typedef enum {
|
|
|
82
82
|
ORBPRO_STATE_HAS_COVARIANCE = 32,
|
|
83
83
|
} OrbProStateFlags;
|
|
84
84
|
|
|
85
|
+
/* ========================================================================= */
|
|
86
|
+
/* ErrorCode */
|
|
87
|
+
/* ========================================================================= */
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The negative return codes every propagator export shares.
|
|
91
|
+
*
|
|
92
|
+
* These were documented in docs/propagator-abi.md and declared NOWHERE, so
|
|
93
|
+
* every module hand-typed them as literals and the contract lived only in
|
|
94
|
+
* prose. A propagator that returns -1 for everything is unconformable — the
|
|
95
|
+
* host cannot tell a bad entity index from an uninitialized module, so it
|
|
96
|
+
* cannot place the failure on the degradation ladder — and the cheapest way
|
|
97
|
+
* to end up there is having no names to return.
|
|
98
|
+
*
|
|
99
|
+
* VALUES ARE FROZEN. New classes are appended downward.
|
|
100
|
+
*/
|
|
101
|
+
typedef enum {
|
|
102
|
+
ORBPRO_PROP_OK = 0,
|
|
103
|
+
ORBPRO_PROP_NOT_INITIALIZED = -1, /**< No elements or ephemeris ingested yet. */
|
|
104
|
+
ORBPRO_PROP_BAD_ENTITY_INDEX = -2, /**< Entity index at or beyond the entity count. */
|
|
105
|
+
ORBPRO_PROP_NULL_OUTPUT = -3, /**< The caller passed a null output pointer. */
|
|
106
|
+
ORBPRO_PROP_BAD_INPUT = -4, /**< Malformed or short input buffer. */
|
|
107
|
+
ORBPRO_PROP_NOT_CONVERGED = -5, /**< The solve failed to converge. */
|
|
108
|
+
ORBPRO_PROP_UNPHYSICAL = -6, /**< The elements describe no closed orbit, or the result is not a number. */
|
|
109
|
+
ORBPRO_PROP_UNSUPPORTED_FORMAT = -7, /**< The ephemeris container is not one this module reads, or AUTO could not identify it, or its declared frame has no member on the ReferenceFrame roster. */
|
|
110
|
+
ORBPRO_PROP_EPOCH_OUT_OF_RANGE = -8, /**< The requested epoch lies outside every segment the loaded ephemeris covers. A refusal, never a silent extrapolation: a caller cannot detect an extrapolated state from the value alone. */
|
|
111
|
+
} OrbProPropagatorError;
|
|
112
|
+
|
|
113
|
+
/* ========================================================================= */
|
|
114
|
+
/* EphemerisFormat */
|
|
115
|
+
/* ========================================================================= */
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The container an ephemeris-driven propagator was handed on
|
|
119
|
+
* `plugin_init_ephemeris`. A format is DATA, not identity: one ephemeris
|
|
120
|
+
* provider reads every member below behind this discriminator rather than
|
|
121
|
+
* shipping one artifact per container, because the interpolation, the time
|
|
122
|
+
* scale handling and the frame rotation are shared and splitting them
|
|
123
|
+
* re-creates the five-mirror drift this file exists to end.
|
|
124
|
+
*
|
|
125
|
+
* AUTO is the honest default: every container here is self-identifying in its
|
|
126
|
+
* first bytes (`DAF/SPK ` / `NAIF/DAF`, the OEM version keyword, the STK
|
|
127
|
+
* `stk.v.` banner, the SP3 `#d` line, the Code-500 fixed header block), so a
|
|
128
|
+
* caller that does not know what it fetched says so rather than guessing. A
|
|
129
|
+
* module that cannot identify the bytes returns UNSUPPORTED_FORMAT; it never
|
|
130
|
+
* falls back to a default container.
|
|
131
|
+
*
|
|
132
|
+
* VALUES ARE FROZEN AND ARE NEVER RENUMBERED. New containers are APPENDED.
|
|
133
|
+
*/
|
|
134
|
+
typedef enum {
|
|
135
|
+
ORBPRO_EPHEM_FORMAT_AUTO = 0, /**< Identify the container from its own leading bytes. */
|
|
136
|
+
ORBPRO_EPHEM_FORMAT_CCSDS_OEM_KVN = 1, /**< CCSDS Orbit Ephemeris Message, keyword-value notation. */
|
|
137
|
+
ORBPRO_EPHEM_FORMAT_SPK_DAF = 2, /**< NAIF Double Precision Array File carrying SPK segments. */
|
|
138
|
+
ORBPRO_EPHEM_FORMAT_CODE500 = 3, /**< Fixed-record binary ephemeris, 500-word logical records. */
|
|
139
|
+
ORBPRO_EPHEM_FORMAT_STK_EPHEMERIS = 4, /**< Ephemeris interchange text with a scenario epoch and typed data blocks. */
|
|
140
|
+
ORBPRO_EPHEM_FORMAT_SP3_D = 5, /**< Precise orbit product, revision d. */
|
|
141
|
+
} OrbProEphemerisFormat;
|
|
142
|
+
|
|
85
143
|
/* ========================================================================= */
|
|
86
144
|
/* OrbProStateVector — 64 bytes, 8-byte aligned */
|
|
87
145
|
/* ========================================================================= */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.17",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
6
6
|
"type": "module",
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"gate:parity": "node ./bin/space-data-module.js parity-gate",
|
|
98
98
|
"gate:parity-negative-control": "node ./bin/space-data-module.js parity-gate --gate-manifest ./parity/negative-control.json --expect-fail",
|
|
99
99
|
"conformance:self-test": "node ./bin/space-data-module.js conformance propagator --self-test",
|
|
100
|
+
"conformance:estimation:self-test": "node ./bin/space-data-module.js conformance estimation --self-test",
|
|
100
101
|
"test:stream-ingest": "node --test test/runtime-host-stream-ingest.test.js",
|
|
101
102
|
"test:module-stream": "node --test test/module-flatbuffer-stream-pump.test.js",
|
|
102
103
|
"benchmark:stream-1gib": "SPACE_DATA_MODULE_SDK_ENABLE_1GB_STREAM_TEST=1 node --test test/runtime-host-stream-ingest.test.js",
|
|
@@ -109,6 +110,7 @@
|
|
|
109
110
|
"check:compliance": "node ./bin/space-data-module.js check --repo-root .",
|
|
110
111
|
"check:generated-bindings": "node scripts/check-generated-bindings.mjs",
|
|
111
112
|
"check:propagator-abi": "node scripts/check-propagator-abi.mjs",
|
|
113
|
+
"check:estimation-abi": "node scripts/check-estimation-abi.mjs",
|
|
112
114
|
"check:reference-frame": "node scripts/check-reference-frame-uniqueness.mjs",
|
|
113
115
|
"generate:propagator-abi": "node scripts/generate-propagator-abi.mjs",
|
|
114
116
|
"generate:vectors": "node ./examples/single-file-bundle/generate-vectors.mjs",
|
|
@@ -123,7 +125,7 @@
|
|
|
123
125
|
"flatsql": "^2.0.0",
|
|
124
126
|
"hd-wallet-wasm": "2.0.28",
|
|
125
127
|
"sdn-emception": "1.0.0",
|
|
126
|
-
"spacedatastandards.org": "1.
|
|
128
|
+
"spacedatastandards.org": "1.203.0"
|
|
127
129
|
},
|
|
128
130
|
"devDependencies": {
|
|
129
131
|
"esbuild": "^0.28.0",
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
include "Propagator.fbs";
|
|
2
|
+
|
|
3
|
+
namespace orbpro.estimation;
|
|
4
|
+
|
|
5
|
+
// Estimation family ABI. This schema is the single source of truth for the
|
|
6
|
+
// fixed-layout C boundary and the request/result wire. Propagation is an
|
|
7
|
+
// inverted port protocol: the guest emits state/STM requests and consumes the
|
|
8
|
+
// supplied Propagator.fbs answers. It imports no provider-specific function.
|
|
9
|
+
|
|
10
|
+
enum EstimatorKind : ubyte (abi, abi_c_name: "OrbProEstimatorKind", abi_c_prefix: "ORBPRO_ESTIMATOR_") {
|
|
11
|
+
BATCH_WEIGHTED_LEAST_SQUARES = 0,
|
|
12
|
+
EXTENDED_KALMAN_FILTER = 1,
|
|
13
|
+
UNSCENTED_KALMAN_FILTER = 2,
|
|
14
|
+
EXTENDED_KALMAN_FILTER_WITH_RTS = 3
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
enum MeasurementKind : ushort (abi, abi_c_name: "OrbProMeasurementKind", abi_c_prefix: "ORBPRO_MEASUREMENT_") {
|
|
18
|
+
RANGE = 0,
|
|
19
|
+
RANGE_RATE = 1,
|
|
20
|
+
DOPPLER = 2,
|
|
21
|
+
AZIMUTH_ELEVATION = 3,
|
|
22
|
+
X_EAST_Y_NORTH = 4,
|
|
23
|
+
X_SOUTH_Y_EAST = 5,
|
|
24
|
+
RIGHT_ASCENSION_DECLINATION = 6,
|
|
25
|
+
POSITION_VECTOR = 7,
|
|
26
|
+
SEQUENTIAL_RANGE = 8,
|
|
27
|
+
PSEUDONOISE_RANGE = 9,
|
|
28
|
+
TIME_CORRELATED_PHASE = 10,
|
|
29
|
+
RELAY_RANGE = 11,
|
|
30
|
+
RELAY_DOPPLER = 12,
|
|
31
|
+
RELAY_DIFFERENCED_DOPPLER = 13,
|
|
32
|
+
BISTATIC_RANGE = 14,
|
|
33
|
+
SKIN_RANGE = 15,
|
|
34
|
+
CROSSLINK_RANGE = 16,
|
|
35
|
+
CROSSLINK_RANGE_RATE = 17,
|
|
36
|
+
LASER_RANGE = 18,
|
|
37
|
+
TIME_DIFFERENCE_OF_ARRIVAL = 19,
|
|
38
|
+
FREQUENCY_DIFFERENCE_OF_ARRIVAL = 20
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
enum ProcessNoiseKind : ubyte (abi, abi_c_name: "OrbProProcessNoiseKind", abi_c_prefix: "ORBPRO_PROCESS_NOISE_") {
|
|
42
|
+
NONE = 0,
|
|
43
|
+
STATE_NOISE_COMPENSATION = 1,
|
|
44
|
+
DYNAMIC_MODEL_COMPENSATION = 2
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
enum TroposphereKind : ubyte (abi, abi_c_name: "OrbProTroposphereKind", abi_c_prefix: "ORBPRO_TROPOSPHERE_") {
|
|
48
|
+
NONE = 0,
|
|
49
|
+
HOPFIELD_SAASTAMOINEN = 1,
|
|
50
|
+
MARINI = 2
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
enum IonosphereKind : ubyte (abi, abi_c_name: "OrbProIonosphereKind", abi_c_prefix: "ORBPRO_IONOSPHERE_") {
|
|
54
|
+
NONE = 0,
|
|
55
|
+
TOTAL_ELECTRON_CONTENT = 1,
|
|
56
|
+
REFERENCE_PROFILE = 2
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
enum InitialOrbitKind : ubyte (abi, abi_c_name: "OrbProInitialOrbitKind", abi_c_prefix: "ORBPRO_IOD_") {
|
|
60
|
+
GAUSS = 0,
|
|
61
|
+
LAPLACE = 1,
|
|
62
|
+
GIBBS = 2,
|
|
63
|
+
HERRICK_GIBBS = 3
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
enum EstimationStatus : int (abi, abi_c_name: "OrbProEstimationStatus", abi_c_prefix: "ORBPRO_ESTIMATION_") {
|
|
67
|
+
INTERNAL = -20,
|
|
68
|
+
PROPAGATOR_PROTOCOL = -19,
|
|
69
|
+
BUFFER_TOO_SMALL = -18,
|
|
70
|
+
NOT_CONVERGED = -5,
|
|
71
|
+
BAD_INPUT = -4,
|
|
72
|
+
NULL_OUTPUT = -3,
|
|
73
|
+
NOT_CONFIGURED = -1,
|
|
74
|
+
OK = 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct EstimationEpoch (abi, abi_c_name: "OrbProEstimationEpoch", abi_c_helper_prefix: "orbpro_estimation_epoch_") {
|
|
78
|
+
jd_day:double;
|
|
79
|
+
seconds:double;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// One observation in SI units. The first value_count lanes of value and
|
|
83
|
+
/// sigma are meaningful. station_position_m is in the request frame.
|
|
84
|
+
struct EstimationObservation (abi, abi_c_name: "OrbProEstimationObservation", abi_c_helper_prefix: "orbpro_estimation_observation_") {
|
|
85
|
+
epoch:EstimationEpoch;
|
|
86
|
+
value:[double:4];
|
|
87
|
+
sigma:[double:4];
|
|
88
|
+
station_position_m:[double:3];
|
|
89
|
+
station_velocity_mps:[double:3];
|
|
90
|
+
station_east:[double:3];
|
|
91
|
+
station_north:[double:3];
|
|
92
|
+
station_up:[double:3];
|
|
93
|
+
remote_position_m:[double:3];
|
|
94
|
+
remote_velocity_mps:[double:3];
|
|
95
|
+
frequency_hz:double;
|
|
96
|
+
transmitter_delay_seconds:double;
|
|
97
|
+
receiver_delay_seconds:double;
|
|
98
|
+
transponder_delay_seconds:double;
|
|
99
|
+
elevation_rad:double;
|
|
100
|
+
station_latitude_rad:double;
|
|
101
|
+
station_height_m:double;
|
|
102
|
+
pressure_hpa:double;
|
|
103
|
+
temperature_k:double;
|
|
104
|
+
relative_humidity:double;
|
|
105
|
+
wavelength_m:double;
|
|
106
|
+
total_electron_content:double;
|
|
107
|
+
total_electron_content_rate_per_second:double;
|
|
108
|
+
turnaround_numerator:uint;
|
|
109
|
+
turnaround_denominator:uint;
|
|
110
|
+
kind:MeasurementKind;
|
|
111
|
+
value_count:ubyte;
|
|
112
|
+
flags:ubyte;
|
|
113
|
+
transmitter_index:uint;
|
|
114
|
+
receiver_index:uint;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
struct EstimationErrorModel (abi, abi_c_name: "OrbProEstimationErrorModel", abi_c_helper_prefix: "orbpro_estimation_error_model_") {
|
|
118
|
+
noise_sigma:double;
|
|
119
|
+
bias:double;
|
|
120
|
+
bias_sigma:double;
|
|
121
|
+
correlation_time_seconds:double;
|
|
122
|
+
minimum_value:double;
|
|
123
|
+
maximum_value:double;
|
|
124
|
+
sigma_edit_threshold:double;
|
|
125
|
+
random_seed:ulong;
|
|
126
|
+
measurement_kind:MeasurementKind;
|
|
127
|
+
troposphere:TroposphereKind;
|
|
128
|
+
ionosphere:IonosphereKind;
|
|
129
|
+
flags:ubyte;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Fixed configuration copied byte-for-byte in every runtime. initial_state
|
|
133
|
+
/// is Cartesian [x,y,z,vx,vy,vz] in metres and metres per second; covariance
|
|
134
|
+
/// is row-major 6 by 6 in matching SI units.
|
|
135
|
+
struct EstimationConfig (abi, abi_c_name: "OrbProEstimationConfig", abi_c_helper_prefix: "orbpro_estimation_config_") {
|
|
136
|
+
initial_epoch:EstimationEpoch;
|
|
137
|
+
initial_state:[double:6];
|
|
138
|
+
initial_covariance:[double:36];
|
|
139
|
+
process_noise_spectral_density:[double:6];
|
|
140
|
+
state_convergence_tolerance:double;
|
|
141
|
+
rms_convergence_tolerance:double;
|
|
142
|
+
sigma_edit_threshold:double;
|
|
143
|
+
dynamic_model_correlation_time_seconds:double;
|
|
144
|
+
maximum_iterations:uint;
|
|
145
|
+
reference_frame:orbpro.propagator.ReferenceFrame;
|
|
146
|
+
estimator:EstimatorKind;
|
|
147
|
+
process_noise:ProcessNoiseKind;
|
|
148
|
+
flags:ushort;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
struct EstimationState (abi, abi_c_name: "OrbProEstimationState", abi_c_helper_prefix: "orbpro_estimation_state_") {
|
|
152
|
+
epoch:EstimationEpoch;
|
|
153
|
+
state:[double:6];
|
|
154
|
+
covariance:[double:36];
|
|
155
|
+
residual_rms:double;
|
|
156
|
+
recovered_noise_sigma:double;
|
|
157
|
+
iteration_count:uint;
|
|
158
|
+
accepted_count:uint;
|
|
159
|
+
rejected_count:uint;
|
|
160
|
+
reference_frame:orbpro.propagator.ReferenceFrame;
|
|
161
|
+
converged:ubyte;
|
|
162
|
+
smoothed:ubyte;
|
|
163
|
+
estimator:EstimatorKind;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
struct FilterEpoch (abi, abi_c_name: "OrbProFilterEpoch", abi_c_helper_prefix: "orbpro_filter_epoch_") {
|
|
167
|
+
epoch:EstimationEpoch;
|
|
168
|
+
filtered_state:[double:6];
|
|
169
|
+
filtered_covariance:[double:36];
|
|
170
|
+
smoothed_state:[double:6];
|
|
171
|
+
smoothed_covariance:[double:36];
|
|
172
|
+
normalized_innovation_squared:double;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// State and state-transition matrix supplied by the propagator port. The STM
|
|
176
|
+
/// maps deviations at the estimator epoch to this sample epoch.
|
|
177
|
+
struct EstimationPropagatorSample (abi, abi_c_name: "OrbProEstimationPropagatorSample", abi_c_helper_prefix: "orbpro_estimation_propagator_sample_") {
|
|
178
|
+
epoch:EstimationEpoch;
|
|
179
|
+
state:[double:6];
|
|
180
|
+
stm:[double:36];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
struct InitialOrbitRequest (abi, abi_c_name: "OrbProInitialOrbitRequest", abi_c_helper_prefix: "orbpro_initial_orbit_request_") {
|
|
184
|
+
// Three inertial position vectors for Gibbs and Herrick-Gibbs.
|
|
185
|
+
positions_m:[double:9];
|
|
186
|
+
// Three observer positions followed by the three topocentric angle pairs
|
|
187
|
+
// used by Gauss and Laplace. The method selects which input set is read.
|
|
188
|
+
observer_positions_m:[double:9];
|
|
189
|
+
right_ascensions_rad:[double:3];
|
|
190
|
+
declinations_rad:[double:3];
|
|
191
|
+
epochs:EstimationEpoch;
|
|
192
|
+
interval_12_seconds:double;
|
|
193
|
+
interval_23_seconds:double;
|
|
194
|
+
gravitational_parameter_m3_s2:double;
|
|
195
|
+
method:InitialOrbitKind;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
struct InitialOrbitResult (abi, abi_c_name: "OrbProInitialOrbitResult", abi_c_helper_prefix: "orbpro_initial_orbit_result_") {
|
|
199
|
+
epoch:EstimationEpoch;
|
|
200
|
+
state:[double:6];
|
|
201
|
+
iterations:uint;
|
|
202
|
+
reference_frame:orbpro.propagator.ReferenceFrame;
|
|
203
|
+
status:EstimationStatus;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Request/result wire. observations are decoded from $TDM/$CRD before they
|
|
207
|
+
/// cross this family boundary; their source record identifiers remain on the
|
|
208
|
+
/// request for a complete provenance trail.
|
|
209
|
+
table EstimationRequest {
|
|
210
|
+
config:EstimationConfig;
|
|
211
|
+
observations:[EstimationObservation];
|
|
212
|
+
error_models:[EstimationErrorModel];
|
|
213
|
+
source_record_ids:[string];
|
|
214
|
+
propagator_port_id:string (required);
|
|
215
|
+
propagator_capability:string (required);
|
|
216
|
+
trace_id:string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
table EstimationResult {
|
|
220
|
+
status:EstimationStatus = OK;
|
|
221
|
+
estimate:EstimationState;
|
|
222
|
+
filter_history:[FilterEpoch];
|
|
223
|
+
residuals:[double];
|
|
224
|
+
/// Row-major 6x6 covariance blocks, one per batch iteration, in iteration
|
|
225
|
+
/// order. Empty for sequential estimators.
|
|
226
|
+
iteration_covariances:[double];
|
|
227
|
+
rejected_observation_indices:[uint];
|
|
228
|
+
odr_record:[ubyte];
|
|
229
|
+
ocm_record:[ubyte];
|
|
230
|
+
error_message:string;
|
|
231
|
+
trace_id:string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
table EstimationEnvelope {
|
|
235
|
+
request:EstimationRequest;
|
|
236
|
+
result:EstimationResult;
|
|
237
|
+
propagator_samples:[EstimationPropagatorSample];
|
|
238
|
+
simulated_observations:[EstimationObservation];
|
|
239
|
+
initial_orbit_request:InitialOrbitRequest;
|
|
240
|
+
initial_orbit_result:InitialOrbitResult;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
root_type EstimationEnvelope;
|
|
244
|
+
file_identifier "$EST";
|
|
@@ -165,6 +165,70 @@ enum PropagatorSourceKind : ubyte {
|
|
|
165
165
|
SDP8 = 12
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/// The negative return codes every propagator export shares.
|
|
169
|
+
///
|
|
170
|
+
/// These were documented in docs/propagator-abi.md and declared NOWHERE, so
|
|
171
|
+
/// every module hand-typed them as literals and the contract lived only in
|
|
172
|
+
/// prose. A propagator that returns -1 for everything is unconformable — the
|
|
173
|
+
/// host cannot tell a bad entity index from an uninitialized module, so it
|
|
174
|
+
/// cannot place the failure on the degradation ladder — and the cheapest way
|
|
175
|
+
/// to end up there is having no names to return.
|
|
176
|
+
///
|
|
177
|
+
/// VALUES ARE FROZEN. New classes are appended downward.
|
|
178
|
+
enum ErrorCode : byte (abi, abi_c_name: "OrbProPropagatorError", abi_c_prefix: "ORBPRO_PROP_") {
|
|
179
|
+
OK = 0,
|
|
180
|
+
/// No elements or ephemeris ingested yet.
|
|
181
|
+
NOT_INITIALIZED = -1,
|
|
182
|
+
/// Entity index at or beyond the entity count.
|
|
183
|
+
BAD_ENTITY_INDEX = -2,
|
|
184
|
+
/// The caller passed a null output pointer.
|
|
185
|
+
NULL_OUTPUT = -3,
|
|
186
|
+
/// Malformed or short input buffer.
|
|
187
|
+
BAD_INPUT = -4,
|
|
188
|
+
/// The solve failed to converge.
|
|
189
|
+
NOT_CONVERGED = -5,
|
|
190
|
+
/// The elements describe no closed orbit, or the result is not a number.
|
|
191
|
+
UNPHYSICAL = -6,
|
|
192
|
+
/// The ephemeris container is not one this module reads, or AUTO could not
|
|
193
|
+
/// identify it, or its declared frame has no member on the ReferenceFrame
|
|
194
|
+
/// roster.
|
|
195
|
+
UNSUPPORTED_FORMAT = -7,
|
|
196
|
+
/// The requested epoch lies outside every segment the loaded ephemeris
|
|
197
|
+
/// covers. A refusal, never a silent extrapolation: a caller cannot detect
|
|
198
|
+
/// an extrapolated state from the value alone.
|
|
199
|
+
EPOCH_OUT_OF_RANGE = -8
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// The container an ephemeris-driven propagator was handed on
|
|
203
|
+
/// `plugin_init_ephemeris`. A format is DATA, not identity: one ephemeris
|
|
204
|
+
/// provider reads every member below behind this discriminator rather than
|
|
205
|
+
/// shipping one artifact per container, because the interpolation, the time
|
|
206
|
+
/// scale handling and the frame rotation are shared and splitting them
|
|
207
|
+
/// re-creates the five-mirror drift this file exists to end.
|
|
208
|
+
///
|
|
209
|
+
/// AUTO is the honest default: every container here is self-identifying in its
|
|
210
|
+
/// first bytes (`DAF/SPK ` / `NAIF/DAF`, the OEM version keyword, the STK
|
|
211
|
+
/// `stk.v.` banner, the SP3 `#d` line, the Code-500 fixed header block), so a
|
|
212
|
+
/// caller that does not know what it fetched says so rather than guessing. A
|
|
213
|
+
/// module that cannot identify the bytes returns UNSUPPORTED_FORMAT; it never
|
|
214
|
+
/// falls back to a default container.
|
|
215
|
+
///
|
|
216
|
+
/// VALUES ARE FROZEN AND ARE NEVER RENUMBERED. New containers are APPENDED.
|
|
217
|
+
enum EphemerisFormat : ubyte (abi, abi_c_name: "OrbProEphemerisFormat", abi_c_prefix: "ORBPRO_EPHEM_FORMAT_") {
|
|
218
|
+
/// Identify the container from its own leading bytes.
|
|
219
|
+
AUTO = 0,
|
|
220
|
+
/// CCSDS Orbit Ephemeris Message, keyword-value notation.
|
|
221
|
+
CCSDS_OEM_KVN = 1,
|
|
222
|
+
/// NAIF Double Precision Array File carrying SPK segments.
|
|
223
|
+
SPK_DAF = 2,
|
|
224
|
+
/// Fixed-record binary ephemeris, 500-word logical records.
|
|
225
|
+
CODE500 = 3,
|
|
226
|
+
/// Ephemeris interchange text with a scenario epoch and typed data blocks.
|
|
227
|
+
STK_EPHEMERIS = 4,
|
|
228
|
+
/// Precise orbit product, revision d.
|
|
229
|
+
SP3_D = 5
|
|
230
|
+
}
|
|
231
|
+
|
|
168
232
|
/// Orbital state vector — 64 bytes, 8-byte aligned. Mirrors
|
|
169
233
|
/// `OrbProStateVector` in orbpro-integration/sdk/include/orbpro_propagator.h
|
|
170
234
|
/// byte for byte:
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deterministic, non-physics reference receipt for exercising the
|
|
3
|
+
* estimation-family conformance evaluator. Real modules replace every value
|
|
4
|
+
* with measurements produced by their compiled WASM artifact. Keeping the
|
|
5
|
+
* evaluator's own known-good input here lets the negative controls prove that
|
|
6
|
+
* each required lane can fail without shipping a second JS estimator.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const reference = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
family: "estimation",
|
|
12
|
+
artifact: { sha256: "reference-estimation-artifact" },
|
|
13
|
+
runtime: {
|
|
14
|
+
browser: { outputSha256: "reference-output" },
|
|
15
|
+
wasmedge: { outputSha256: "reference-output" },
|
|
16
|
+
threads: {
|
|
17
|
+
"1": "reference-output",
|
|
18
|
+
"2": "reference-output",
|
|
19
|
+
"4": "reference-output",
|
|
20
|
+
"8": "reference-output",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
batch: {
|
|
24
|
+
positionAbsoluteErrorM: 0.25,
|
|
25
|
+
positionRelativeError: 4e-8,
|
|
26
|
+
covarianceRssRelativeError: 0.02,
|
|
27
|
+
residualRmsRelativeError: 2e-8,
|
|
28
|
+
rejectedExpected: [7, 19],
|
|
29
|
+
rejectedActual: [7, 19],
|
|
30
|
+
iterationCount: 4,
|
|
31
|
+
iterationCovarianceCount: 4,
|
|
32
|
+
iterationCovariancesSpd: true,
|
|
33
|
+
},
|
|
34
|
+
filter: {
|
|
35
|
+
positionErrorM: 0.65,
|
|
36
|
+
smootherPositionErrorM: 0.21,
|
|
37
|
+
smootherCovarianceMonotone: true,
|
|
38
|
+
neesMean: 6.02,
|
|
39
|
+
nees95Lower: 5.34,
|
|
40
|
+
nees95Upper: 6.70,
|
|
41
|
+
sncCovarianceGrowthRatio: 1.21,
|
|
42
|
+
dmcCovarianceGrowthRatio: 1.58,
|
|
43
|
+
ekfUkfCovarianceDifference: 0.08,
|
|
44
|
+
},
|
|
45
|
+
measurements: {
|
|
46
|
+
rangeMaxRelativeError: 6e-16,
|
|
47
|
+
rangeRateMaxRelativeError: 5e-16,
|
|
48
|
+
azElMaxRelativeError: 5e-16,
|
|
49
|
+
raDecMaxRelativeError: 5e-16,
|
|
50
|
+
lightTimeMagnitudeErrorM: 2e-10,
|
|
51
|
+
sagnacMagnitudeErrorM: 2e-10,
|
|
52
|
+
},
|
|
53
|
+
media: {
|
|
54
|
+
hopfieldSaastamoinenMaxRelativeError: 6e-5,
|
|
55
|
+
mariniMaxRelativeError: 7e-5,
|
|
56
|
+
p531PublishedPrecisionError: 0,
|
|
57
|
+
},
|
|
58
|
+
iod: {
|
|
59
|
+
gaussPrintedPrecisionError: 0,
|
|
60
|
+
laplacePrintedPrecisionError: 0,
|
|
61
|
+
gibbsPrintedPrecisionError: 0,
|
|
62
|
+
herrickGibbsPrintedPrecisionError: 0,
|
|
63
|
+
},
|
|
64
|
+
simulator: {
|
|
65
|
+
runs: 200,
|
|
66
|
+
stateInsideThreeSigmaFraction: 0.965,
|
|
67
|
+
recoveredNoiseSigmaRelativeError: 0.031,
|
|
68
|
+
},
|
|
69
|
+
invariants: {
|
|
70
|
+
covarianceSymmetric: true,
|
|
71
|
+
covariancePositiveDefinite: true,
|
|
72
|
+
residualOrthogonalityMax: 3e-11,
|
|
73
|
+
actualOcmCovariance: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const ESTIMATION_REFERENCE_EVIDENCE = Object.freeze(reference);
|
|
78
|
+
|
|
79
|
+
export function makeEstimationReferenceEvidence() {
|
|
80
|
+
return structuredClone(reference);
|
|
81
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/** Estimation-family conformance receipt evaluator. No physics lives here. */
|
|
2
|
+
|
|
3
|
+
import { computeVerdict } from "./propagatorSuite.js";
|
|
4
|
+
|
|
5
|
+
const pass = (id, tier, detail) => ({ id, tier, required: true, status: "pass", detail });
|
|
6
|
+
const fail = (id, tier, detail) => ({ id, tier, required: true, status: "fail", detail });
|
|
7
|
+
|
|
8
|
+
function check(checks, id, tier, condition, detail, failure = detail) {
|
|
9
|
+
checks.push(condition ? pass(id, tier, detail) : fail(id, tier, failure));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function finiteAtMost(value, limit) {
|
|
13
|
+
return Number.isFinite(value) && value <= limit;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sameSet(left = [], right = []) {
|
|
17
|
+
const a = [...new Set(left)].sort((x, y) => x - y);
|
|
18
|
+
const b = [...new Set(right)].sort((x, y) => x - y);
|
|
19
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function runEstimationSuite(evidence) {
|
|
23
|
+
const checks = [];
|
|
24
|
+
const runtimeHashes = [
|
|
25
|
+
evidence?.runtime?.browser?.outputSha256,
|
|
26
|
+
evidence?.runtime?.wasmedge?.outputSha256,
|
|
27
|
+
evidence?.runtime?.threads?.["1"],
|
|
28
|
+
evidence?.runtime?.threads?.["2"],
|
|
29
|
+
evidence?.runtime?.threads?.["4"],
|
|
30
|
+
evidence?.runtime?.threads?.["8"],
|
|
31
|
+
];
|
|
32
|
+
check(checks, "tier0/tri-runtime-byte-identity", "0",
|
|
33
|
+
runtimeHashes.every(Boolean) && new Set(runtimeHashes).size === 1,
|
|
34
|
+
"browser, WasmEdge and worker widths 1/2/4/8 emitted byte-identical results",
|
|
35
|
+
"missing or divergent browser/WasmEdge/thread-width output digests");
|
|
36
|
+
|
|
37
|
+
const batch = evidence?.batch ?? {};
|
|
38
|
+
check(checks, "tierA/batch-state", "A",
|
|
39
|
+
finiteAtMost(batch.positionAbsoluteErrorM, 1) &&
|
|
40
|
+
finiteAtMost(batch.positionRelativeError, 1e-3),
|
|
41
|
+
`absolute=${batch.positionAbsoluteErrorM} m relative=${batch.positionRelativeError}`,
|
|
42
|
+
"batch state exceeds 1 m absolute or 1e-3 relative");
|
|
43
|
+
check(checks, "tierA/batch-covariance", "A",
|
|
44
|
+
finiteAtMost(batch.covarianceRssRelativeError, 0.05),
|
|
45
|
+
`RSS position sigma relative error=${batch.covarianceRssRelativeError}`,
|
|
46
|
+
"batch covariance RSS differs from authority by more than 5%");
|
|
47
|
+
check(checks, "tierA/batch-residual-rms", "A",
|
|
48
|
+
finiteAtMost(batch.residualRmsRelativeError, 1e-6),
|
|
49
|
+
`residual RMS relative error=${batch.residualRmsRelativeError}`,
|
|
50
|
+
"batch residual RMS differs from authority by more than 1e-6 relative");
|
|
51
|
+
check(checks, "tierA/sigma-edit-set", "A",
|
|
52
|
+
sameSet(batch.rejectedExpected, batch.rejectedActual),
|
|
53
|
+
`exact rejected set [${batch.rejectedActual ?? []}]`,
|
|
54
|
+
"sigma editing rejected a different observation set");
|
|
55
|
+
check(checks, "tierC/batch-iteration-covariance", "C",
|
|
56
|
+
Number.isInteger(batch.iterationCount) && batch.iterationCount > 0 &&
|
|
57
|
+
batch.iterationCovarianceCount === batch.iterationCount &&
|
|
58
|
+
batch.iterationCovariancesSpd === true,
|
|
59
|
+
`${batch.iterationCovarianceCount}/${batch.iterationCount} iteration covariances are SPD`,
|
|
60
|
+
"batch iteration covariance history is missing, incomplete, or not SPD");
|
|
61
|
+
|
|
62
|
+
const filter = evidence?.filter ?? {};
|
|
63
|
+
check(checks, "tierA/filter-and-smoother-state", "A",
|
|
64
|
+
finiteAtMost(filter.positionErrorM, 1) &&
|
|
65
|
+
finiteAtMost(filter.smootherPositionErrorM, 0.5),
|
|
66
|
+
`filter=${filter.positionErrorM} m smoother=${filter.smootherPositionErrorM} m`,
|
|
67
|
+
"filter exceeds 1 m or RTS smoother exceeds 0.5 m");
|
|
68
|
+
check(checks, "tierC/smoother-covariance", "C",
|
|
69
|
+
filter.smootherCovarianceMonotone === true,
|
|
70
|
+
"smoothed covariance is no larger than filtered covariance at every epoch");
|
|
71
|
+
check(checks, "tierC/nees-100-run", "C",
|
|
72
|
+
Number.isFinite(filter.neesMean) && filter.neesMean >= filter.nees95Lower &&
|
|
73
|
+
filter.neesMean <= filter.nees95Upper,
|
|
74
|
+
`NEES mean=${filter.neesMean} band=[${filter.nees95Lower},${filter.nees95Upper}]`,
|
|
75
|
+
"100-run NEES lies outside the declared chi-square 95% band");
|
|
76
|
+
check(checks, "tierC/process-noise", "C",
|
|
77
|
+
Number.isFinite(filter.sncCovarianceGrowthRatio) && filter.sncCovarianceGrowthRatio !== 1 &&
|
|
78
|
+
Number.isFinite(filter.dmcCovarianceGrowthRatio) && filter.dmcCovarianceGrowthRatio !== 1,
|
|
79
|
+
`SNC ratio=${filter.sncCovarianceGrowthRatio} DMC ratio=${filter.dmcCovarianceGrowthRatio}`,
|
|
80
|
+
"SNC or DMC failed to change covariance growth");
|
|
81
|
+
check(checks, "tierC/ekf-ukf-not-aliased", "C",
|
|
82
|
+
Number.isFinite(filter.ekfUkfCovarianceDifference) && filter.ekfUkfCovarianceDifference > 0,
|
|
83
|
+
`covariance history distance=${filter.ekfUkfCovarianceDifference}`,
|
|
84
|
+
"EKF and UKF covariance histories are identical");
|
|
85
|
+
|
|
86
|
+
const measurement = evidence?.measurements ?? {};
|
|
87
|
+
for (const [name, key] of [["range", "rangeMaxRelativeError"],
|
|
88
|
+
["range-rate", "rangeRateMaxRelativeError"], ["az-el", "azElMaxRelativeError"],
|
|
89
|
+
["ra-dec", "raDecMaxRelativeError"]]) {
|
|
90
|
+
check(checks, `tierA/measurement-${name}`, "A", finiteAtMost(measurement[key], 1e-6),
|
|
91
|
+
`max relative error=${measurement[key]}`, `${name} exceeds 1e-6 relative`);
|
|
92
|
+
}
|
|
93
|
+
check(checks, "tierA/light-time", "A", finiteAtMost(measurement.lightTimeMagnitudeErrorM, 1e-6),
|
|
94
|
+
`magnitude error=${measurement.lightTimeMagnitudeErrorM} m`);
|
|
95
|
+
check(checks, "tierA/sagnac", "A", finiteAtMost(measurement.sagnacMagnitudeErrorM, 1e-6),
|
|
96
|
+
`magnitude error=${measurement.sagnacMagnitudeErrorM} m`);
|
|
97
|
+
|
|
98
|
+
const media = evidence?.media ?? {};
|
|
99
|
+
check(checks, "tierA/troposphere-hopfield-saastamoinen", "A",
|
|
100
|
+
finiteAtMost(media.hopfieldSaastamoinenMaxRelativeError, 1e-4),
|
|
101
|
+
`relative error=${media.hopfieldSaastamoinenMaxRelativeError}`);
|
|
102
|
+
check(checks, "tierA/troposphere-marini", "A",
|
|
103
|
+
finiteAtMost(media.mariniMaxRelativeError, 1e-4),
|
|
104
|
+
`relative error=${media.mariniMaxRelativeError}`);
|
|
105
|
+
check(checks, "tierB/ionosphere-p531", "B", media.p531PublishedPrecisionError === 0,
|
|
106
|
+
"P.531 table value reproduced to published precision");
|
|
107
|
+
|
|
108
|
+
for (const [name, key] of [["gauss", "gaussPrintedPrecisionError"],
|
|
109
|
+
["laplace", "laplacePrintedPrecisionError"], ["gibbs", "gibbsPrintedPrecisionError"],
|
|
110
|
+
["herrick-gibbs", "herrickGibbsPrintedPrecisionError"]]) {
|
|
111
|
+
check(checks, `tierB/iod-${name}`, "B", evidence?.iod?.[key] === 0,
|
|
112
|
+
"Vallado worked example reproduced to printed precision");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const simulator = evidence?.simulator ?? {};
|
|
116
|
+
check(checks, "tierC/simulator-coverage", "C",
|
|
117
|
+
simulator.runs >= 200 && simulator.stateInsideThreeSigmaFraction >= 0.95,
|
|
118
|
+
`${simulator.stateInsideThreeSigmaFraction} inside 3-sigma over ${simulator.runs} runs`);
|
|
119
|
+
check(checks, "tierC/simulator-noise-recovery", "C",
|
|
120
|
+
finiteAtMost(simulator.recoveredNoiseSigmaRelativeError, 0.05),
|
|
121
|
+
`noise sigma relative error=${simulator.recoveredNoiseSigmaRelativeError}`);
|
|
122
|
+
|
|
123
|
+
const invariants = evidence?.invariants ?? {};
|
|
124
|
+
check(checks, "tierC/covariance-spd", "C",
|
|
125
|
+
invariants.covarianceSymmetric === true && invariants.covariancePositiveDefinite === true,
|
|
126
|
+
"covariance is symmetric positive definite");
|
|
127
|
+
check(checks, "tierC/residual-orthogonality", "C",
|
|
128
|
+
finiteAtMost(invariants.residualOrthogonalityMax, 1e-6),
|
|
129
|
+
`max normalized normal-equation projection=${invariants.residualOrthogonalityMax}`);
|
|
130
|
+
check(checks, "tierC/ocm-covariance", "C", invariants.actualOcmCovariance === true,
|
|
131
|
+
"the emitted OCM carries the estimator covariance");
|
|
132
|
+
|
|
133
|
+
return { checks, verdict: computeVerdict(checks) };
|
|
134
|
+
}
|