diff --git a/design/system_interactions.txt b/design/system_interactions.txt new file mode 100644 index 0000000..b3b478c --- /dev/null +++ b/design/system_interactions.txt @@ -0,0 +1,52 @@ +@startuml + +participant "Simulation" as sim +participant "Filter" as filter +participant "Satellite" as sat +participant "Consensus (PoISE)" as poise +participant "Ledger (DAG)" as dag + +== Initialisation & Truth Generation == +sim -> sim: simulate_truth_and_meas(N, steps, dt) +note right + Generates truth_hist (ground truth orbits) + and z_hist (noisy ISL measurements) +end note + +sim -> filter: Create EKF instances (initialised with truth[0]) +sim -> sat: Create Satellite nodes +sim -> dag: Initialise DAG with PoISE + +== Simulation Loop == +/' +The simulation generates all possible observation records for a +maximum possible ISL range set at 5000km. This way, the ISL range +I want to test PoISE against can be set dynamically (up to the +5000km maximum), but also physically impossible observations +(such as 2 satellites at opposite sides of the earth seeing each +other) are not created as this wastes computational effort. +This is sufficient and efficient for research and testing. +'/ + +loop for each timestep k + sim -> filter: predict() & update(z_hist[k]) + note right: Filter estimates state based on noisy measurements + filter -->> sim: ObservationRecords (with NIS) + note right: Observation records generated for Max ISL for 5000km + + sim -> sat: update_position(truth_hist[k]) + + group In-Range Observation + sim -> sat: load_sensor_data(ObservationRecord) + note right: Only loads observations within specified ISL range + sat -> dag: submit_transaction(queue) + end +end + +== Consensus (Async) == +dag -> dag: listen() to queue +dag -> poise: validate(transaction, reputation) +poise -->> dag: validation_result +dag -> sat: update_reputation() + +@enduml diff --git a/images/system_interactions.png b/images/system_interactions.png new file mode 100644 index 0000000..24e9055 Binary files /dev/null and b/images/system_interactions.png differ diff --git a/src/filter.py b/src/filter.py index fee85b1..fee50ec 100644 --- a/src/filter.py +++ b/src/filter.py @@ -39,6 +39,7 @@ Re = 6378e3 STATE_DIM = 6 # State vector dimension (position and velocity) POS_VEL_DIM = 3 # Position or velocity dimension +MAX_ISL_RANGE = 5000e3 # Maximum range for ISL observation records (5000km) # ----------------------- Result Types --------------------- @dataclass @@ -476,6 +477,14 @@ def _log_nis(y: np.ndarray, ekf: ExtendedKalmanFilter, N: int, k: int, xj_idx_slice = slice(STATE_DIM*j, STATE_DIM*j+STATE_DIM) + # Skip records for satellites further than 5000km apart + dist = np.linalg.norm(ekf.x[STATE_DIM*i:STATE_DIM*i+3] - + ekf.x[STATE_DIM*j:STATE_DIM*j+3]) + if dist > MAX_ISL_RANGE: + # Advance index by 2 as every pair is range and range rate + idx += 2 + continue + yij = y[idx:idx+2] # Recalculate Ht and Ho for the current pair, this is necessary. @@ -507,6 +516,7 @@ def _log_nis(y: np.ndarray, ekf: ExtendedKalmanFilter, N: int, k: int, step=k, observer=i, target=j, nis=nis, dof=yij.shape[0], time = k*dt ) ) + # Advance index by 2 as every pair is range and range rate idx += 2 return obs_records diff --git a/tests/test_filter_ekf.py b/tests/test_filter_ekf.py index 2e4fd2f..ef3c91b 100644 --- a/tests/test_filter_ekf.py +++ b/tests/test_filter_ekf.py @@ -85,8 +85,8 @@ def test_joint_ekf_update(ekf, filter_config): assert not np.allclose(x_prior, x_posterior) # Check the observation records - expected_records = filter_config.N * (filter_config.N - 1) - assert len(obs_records) == expected_records + # Not all records are created due to the 5000km distance check + assert 0 < len(obs_records) <= filter_config.N * (filter_config.N - 1) for record in obs_records: assert record.step == 1 assert record.nis >= 0