From e4f82329a97a2493d28940a60edf5ea5a10222e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Mar 2026 19:11:16 +0000 Subject: [PATCH] Add temporal graph evolution & RuVector integration research GOAP Agent 8 output: 1,528-line SOTA research document covering temporal graph models (TGN, JODIE, DyRep), RuVector graph memory design, mincut trajectory tracking with Kalman filtering, event detection pipelines, compressed temporal storage, cross-room transition graphs, and a 5-phase integration roadmap. Part of RF Topological Sensing research swarm (10 agents). https://claude.ai/code/session_01DGUAowNScGVp88bK2eiuRv --- .../08-temporal-graph-evolution-ruvector.md | 1528 +++++++++++++++++ 1 file changed, 1528 insertions(+) create mode 100644 docs/research/08-temporal-graph-evolution-ruvector.md diff --git a/docs/research/08-temporal-graph-evolution-ruvector.md b/docs/research/08-temporal-graph-evolution-ruvector.md new file mode 100644 index 00000000..55792fcd --- /dev/null +++ b/docs/research/08-temporal-graph-evolution-ruvector.md @@ -0,0 +1,1528 @@ +# Temporal Graph Evolution Tracking and RuVector Integration for RF Topological Sensing + +**Research Document 08** | March 2026 +**Status**: SOTA Survey + Design Proposal +**Scope**: Temporal dynamic graph models applied to WiFi CSI-based RF sensing, +with concrete integration points into the RuView/wifi-densepose Rust codebase. + +--- + +## Table of Contents + +1. [Introduction and Motivation](#1-introduction-and-motivation) +2. [Temporal Graph Models: SOTA Survey](#2-temporal-graph-models-sota-survey) +3. [RuVector as Graph Memory](#3-ruvector-as-graph-memory) +4. [Graph Evolution Patterns in RF Sensing](#4-graph-evolution-patterns-in-rf-sensing) +5. [Minimum Cut Trajectory Tracking](#5-minimum-cut-trajectory-tracking) +6. [Event Detection from Graph Dynamics](#6-event-detection-from-graph-dynamics) +7. [Compressed Temporal Storage](#7-compressed-temporal-storage) +8. [Cross-Room Transition Graphs](#8-cross-room-transition-graphs) +9. [Longitudinal Drift Detection on Graph Topology](#9-longitudinal-drift-detection-on-graph-topology) +10. [Proposed Data Structures](#10-proposed-data-structures) +11. [Integration Roadmap](#11-integration-roadmap) +12. [References](#12-references) + +--- + +## 1. Introduction and Motivation + +WiFi-based sensing produces a rich, continuously evolving graph structure. +Each ESP32 node is a vertex; each TX-RX link is an edge carrying time-varying +Channel State Information (CSI). People, furniture, doors, and environmental +conditions perturb this graph in characteristic patterns. Tracking *how* the +graph changes over time -- not just the current snapshot -- unlocks several +capabilities that static analysis cannot provide: + +- **Trajectory reconstruction** from the movement of minimum-cut boundaries. +- **Event classification** (entry, exit, gesture, fall) from graph dynamics. +- **Longitudinal health monitoring** by tracking topological drift over weeks. +- **Cross-room identity continuity** through temporal transition graphs. +- **Anomaly detection** when graph evolution violates learned patterns. + +This document surveys state-of-the-art temporal graph models, then designs +concrete data structures and algorithms for integrating temporal graph +evolution tracking into the RuView codebase via RuVector's graph engine. + +### 1.1 Scope Boundaries + +This research covers the RF sensing graph specifically -- the graph whose +vertices are ESP32 nodes and whose edges are CSI links. It does not address +the DensePose skeleton graph (which is a separate, downstream structure). +The two graphs interact at the fusion boundary where `MultistaticArray` +(in `ruvector/src/viewpoint/fusion.rs`) produces fused embeddings from +the RF graph and the pose tracker (in `signal/src/ruvsense/pose_tracker.rs`) +consumes them. + +### 1.2 Relationship to Existing Modules + +| Module | Current Role | Temporal Extension | +|--------|-------------|-------------------| +| `coherence.rs` | Per-link coherence scoring | Coherence time series per edge | +| `field_model.rs` | SVD eigenstructure (static) | Eigenmode drift trajectories | +| `multistatic.rs` | Single-cycle fusion | Cross-cycle graph state memory | +| `cross_room.rs` | Transition event log | Temporal transition graph | +| `longitudinal.rs` | Welford stats per person | Welford stats per graph metric | +| `coherence_gate.rs` | Accept/Reject decisions | Gate decision history analysis | +| `viewpoint/fusion.rs` | Aggregate root for fusion | Temporal GDI tracking | +| `viewpoint/geometry.rs` | GDI + Cramer-Rao bounds | Time-varying geometry quality | +| `intention.rs` | Embedding acceleration | Graph-level acceleration detection | + +--- + +## 2. Temporal Graph Models: SOTA Survey + +### 2.1 Taxonomy of Temporal Graph Representations + +Temporal graphs fall into two broad families: + +**Discrete-Time Dynamic Graphs (DTDGs)**: The graph is represented as a +sequence of snapshots G_1, G_2, ..., G_T at fixed time intervals. + +``` +DTDG State Diagram: + + [Snapshot t-2] --delta--> [Snapshot t-1] --delta--> [Snapshot t] + | | | + v v v + {V, E, W}_{t-2} {V, E, W}_{t-1} {V, E, W}_t + + Where each snapshot contains: + V = vertex set (ESP32 nodes, typically stable) + E = edge set (active links, may vary with node failures) + W = edge weights (CSI amplitude/phase/coherence) +``` + +**Continuous-Time Dynamic Graphs (CTDGs)**: Events (edge additions, +deletions, weight changes) are recorded as a timestamped event stream. + +``` +CTDG Event Stream: + + t=0.000 EdgeUpdate(A->B, coherence=0.95) + t=0.050 EdgeUpdate(A->C, coherence=0.91) + t=0.050 EdgeUpdate(B->C, coherence=0.88) + t=0.100 EdgeUpdate(A->B, coherence=0.72) <-- person crosses link + t=0.100 EdgeUpdate(B->D, coherence=0.93) + t=0.150 EdgeUpdate(A->B, coherence=0.45) <-- strong perturbation + ... +``` + +For RuView's 20 Hz TDMA cycle, the DTDG snapshot model aligns naturally +with the `MultistaticFuser` output cadence. However, within a single TDMA +cycle the individual node frames arrive asynchronously (per +`MultistaticConfig::guard_interval_us`), making a hybrid approach optimal: +DTDG at the cycle level, CTDG for intra-cycle event recording. + +### 2.2 Key Frameworks + +#### 2.2.1 Temporal Graph Networks (TGN) + +Rossi et al. (2020) introduced TGN as a unified framework combining: + +- **Memory module**: Per-node memory vectors updated after each interaction. +- **Message function**: Computes messages from temporal events. +- **Message aggregator**: Combines messages for nodes with multiple events. +- **Embedding module**: Generates node embeddings from memory + graph. + +TGN's per-node memory maps directly to the per-link `CoherenceState` in +`coherence.rs`. The EMA reference template is effectively a memory vector +that encodes the link's recent history. The `DriftProfile` enum +(Stable/Linear/StepChange) serves as a coarse embedding. + +**Relevance to RuView**: TGN's memory update mechanism can be adapted for +our per-edge CSI state. Rather than learning memory updates via +backpropagation, we use physics-informed updates (Welford statistics, +EMA reference tracking) that are deterministic and auditable. + +#### 2.2.2 JODIE (Joint Dynamic User-Item Embeddings) + +Kumar et al. (2019) model interactions between two types of nodes using +coupled RNN-based projections. Each interaction updates both nodes' +embeddings and projects them forward in time. + +**Relevance to RuView**: The TX-RX duality in our multistatic mesh is +analogous to JODIE's user-item pairs. When person P crosses link L(A,B), +we can update both the "transmitter A state" and "receiver B state" +simultaneously, projecting both forward to the next expected observation. + +#### 2.2.3 CT-DGNN (Continuous-Time Dynamic Graph Neural Network) + +Chen et al. (2021) use temporal point processes to model irregularly-sampled +graph events. Edge events are modeled as a Hawkes process with learned +triggering kernels. + +**Relevance to RuView**: The coherence gate decision stream +(Accept/PredictOnly/Reject/Recalibrate from `coherence_gate.rs`) is +naturally a point process. Gate transitions from Accept to Reject cluster +in time during person movement, exhibiting the self-exciting behavior that +Hawkes processes capture. + +#### 2.2.4 DyRep (Learning Representations over Dynamic Graphs) + +Trivedi et al. (2019) model two processes jointly: association (structural +changes) and communication (information flow). The temporal attention +mechanism weighs recent events more heavily. + +**Relevance to RuView**: The `CrossViewpointAttention` module in +`viewpoint/attention.rs` already implements geometric bias via +`GeometricBias::new(w_angle, w_dist, d_ref)`. DyRep suggests adding +temporal bias: more recent viewpoint observations should receive higher +attention weight. + +### 2.3 Comparison Matrix + +| Framework | Time Model | Memory | Scalability | RuView Fit | +|-----------|-----------|--------|-------------|-----------| +| TGN | Continuous | Per-node | O(N) update | High -- maps to CoherenceState | +| JODIE | Continuous | Per-pair | O(E) update | Medium -- TX-RX pairs | +| CT-DGNN | Continuous | Global | O(N^2) attention | Low -- too expensive at 20 Hz | +| DyRep | Continuous | Per-node | O(N*K) | Medium -- temporal attention useful | +| GraphSAGE-T | Discrete | Aggregated | O(N*K*L) | High -- snapshot aggregation | + +### 2.4 Recommended Hybrid Approach + +For RuView, we propose a **snapshot-anchored event-driven** model: + +1. **Anchor snapshots** at each TDMA cycle (20 Hz) capturing the full graph + state (all link coherences, amplitudes, phases). +2. **Between anchors**, record edge-level events (coherence drops, gate + decisions, perturbation detections) as a CTDG event stream. +3. **Memory** is maintained per-edge using the existing `CoherenceState` + and `WelfordStats`, extended with temporal query capabilities. +4. **Attention** uses the existing `CrossViewpointAttention` with an + additional temporal decay term. + +This avoids the computational overhead of full neural temporal graph models +while preserving the event-level granularity needed for gesture detection +and intention lead signals. + +--- + +## 3. RuVector as Graph Memory + +### 3.1 Current RuVector Graph Capabilities + +RuVector provides five crates relevant to graph memory: + +| Crate | Graph Primitive | Temporal Capability | +|-------|----------------|-------------------| +| `ruvector-mincut` | Dynamic min-cut partitioning | Per-frame partition snapshots | +| `ruvector-attn-mincut` | Attention-gated spectrogram | Spectral evolution tracking | +| `ruvector-temporal-tensor` | CompressedCsiBuffer | Ring-buffer CSI history | +| `ruvector-solver` | Sparse matrix (CsrMatrix) | System state solving | +| `ruvector-attention` | Spatial attention weights | Attention weight trajectories | + +### 3.2 Vertex and Edge Versioning + +To support temporal queries ("what was the coherence of link A-B at time +T?"), we need versioned graph state. The design follows an append-only +event sourcing pattern consistent with the project's DDD architecture. + +``` +Vertex/Edge Version Model: + + VertexState { + node_id: NodeId, + version: u64, // Monotonic version counter + timestamp_us: u64, // Wall clock at version creation + embedding: Vec, // AETHER embedding (128-d) + coherence_score: f32, // Aggregate coherence + calibration_status: CalibrationStatus, + } + + EdgeState { + link_id: (NodeId, NodeId), + version: u64, + timestamp_us: u64, + coherence: f32, // From CoherenceState::score() + drift_profile: DriftProfile, + gate_decision: GateDecision, + amplitude_hash: u64, // Compact representation of full CSI + perturbation_energy: f64, + } +``` + +### 3.3 Temporal Query Interface + +```rust +/// Temporal graph query interface for RF sensing graph. +pub trait TemporalGraphQuery { + /// Get the graph state at a specific timestamp. + fn snapshot_at(&self, timestamp_us: u64) -> Option; + + /// Get edge state history within a time range. + fn edge_history( + &self, + link: (NodeId, NodeId), + start_us: u64, + end_us: u64, + ) -> Vec; + + /// Get all edges that changed between two timestamps. + fn diff(&self, t1_us: u64, t2_us: u64) -> GraphDelta; + + /// Find the first timestamp where a predicate holds. + fn find_first bool>( + &self, + start_us: u64, + predicate: P, + ) -> Option; + + /// Aggregate a metric over a time window. + fn aggregate_window( + &self, + link: (NodeId, NodeId), + metric: EdgeMetric, + window_us: u64, + ) -> WelfordStats; +} +``` + +### 3.4 Integration with Existing Memory Stores + +The `EmbeddingHistory` in `longitudinal.rs` already implements a brute-force +nearest-neighbor store. We extend this pattern to graph state: + +``` +Memory Architecture: + + +------------------+ +-------------------+ +------------------+ + | CoherenceState | | TemporalGraphStore| | EmbeddingHistory | + | (per-edge, live) |---->| (versioned, disk) |<----| (per-person) | + +------------------+ +-------------------+ +------------------+ + | | | + v v v + [20 Hz live feed] [Queryable history] [HNSW-indexed] + | + +-------+-------+ + | | + [Snapshot Index] [Event Stream] + (binary search) (append-only) +``` + +### 3.5 Storage Budget + +For a 6-node mesh with 15 bidirectional links at 20 Hz: + +| Component | Per-Frame | Per-Second | Per-Hour | Per-Day | +|-----------|-----------|-----------|----------|---------| +| Edge coherence (15 x f32) | 60 B | 1.2 KB | 4.3 MB | 103 MB | +| Edge amplitude hash (15 x u64) | 120 B | 2.4 KB | 8.6 MB | 207 MB | +| Gate decisions (15 x u8) | 15 B | 300 B | 1.1 MB | 26 MB | +| Full snapshot (anchor) | ~2 KB | 40 KB | 144 MB | 3.4 GB | +| Delta (inter-anchor) | ~200 B | 4 KB | 14 MB | 340 MB | + +With delta compression (Section 7), the per-day cost drops to approximately +100 MB for full temporal history, well within ESP32 aggregator SD card limits. + +--- + +## 4. Graph Evolution Patterns in RF Sensing + +### 4.1 Pattern Taxonomy + +RF field graphs exhibit characteristic evolution patterns during different +physical events. We classify these as **temporal motifs** -- recurring +subgraph evolution signatures. + +``` +Temporal Motif State Machine: + + +----------+ + | Static | + | (Stable) | + +----+-----+ + | + +---------------+---------------+ + | | | + v v v + +-----------+ +-----------+ +-----------+ + | Single | | Multi | | Global | + | Link Drop | | Link Drop | | Shift | + +-----------+ +-----------+ +-----------+ + Person crosses Person in Environmental + one link open area change (door, + | | HVAC, etc.) + v v v + +-----------+ +-----------+ +-----------+ + | Sweep | | Cluster | | Offset | + | Pattern | | Migration | | Plateau | + +-----------+ +-----------+ +-----------+ + Sequential Correlated All links shift + link drops group moves to new baseline + | | | + +-------+-------+ | + | | + v v + +-----------+ +-----------+ + | Recovery | | New | + | to Static | | Baseline | + +-----------+ +-----------+ +``` + +### 4.2 Person Walking Across a Room + +When a person walks from position P1 to P2, the RF graph evolves in a +characteristic sweep pattern: + +1. **Pre-movement phase** (200-500 ms): Subtle coherence shifts detected by + the `IntentionDetector` in `intention.rs`. The embedding acceleration + exceeds the threshold while velocity remains low. + +2. **Leading edge**: Links nearest to the person's current position show + coherence drops first. The `CoherenceState` transitions from `Stable` + to `StepChange` drift profile. + +3. **Body zone**: Links directly traversing the person show minimum coherence + and maximum perturbation energy (from `FieldModel::extract_perturbation`). + +4. **Trailing recovery**: Links the person has passed recover coherence, + transitioning back to `Stable` drift profile. + +The temporal signature is a **traveling wave of coherence depression** that +sweeps across the graph in the direction of movement. + +``` +Coherence Evolution During Walk (6-link example): + +Time --> 0s 0.5s 1.0s 1.5s 2.0s 2.5s +Link A-B: 0.95 0.95 0.92 0.45 0.88 0.94 +Link A-C: 0.93 0.91 0.50 0.82 0.93 0.95 +Link B-C: 0.94 0.55 0.78 0.92 0.94 0.93 +Link B-D: 0.92 0.48 0.72 0.91 0.93 0.94 +Link C-D: 0.95 0.93 0.88 0.52 0.85 0.93 +Link A-D: 0.94 0.92 0.78 0.60 0.55 0.90 + + ^ sweep starts ^ sweep peak ^ recovery +``` + +### 4.3 Door Opening/Closing + +A door event produces a **global step change** in the graph: + +1. Links traversing the door aperture show sudden, large coherence drops. +2. Links not traversing the door show smaller, delayed coherence shifts + (due to changed multipath structure). +3. The new coherence pattern stabilizes at a **different baseline** from + the pre-door state. + +The `FieldModel` eigenstructure changes because the room's electromagnetic +boundary conditions have changed. The environmental modes shift, requiring +recalibration (detected by `CalibrationStatus::Stale` or `Expired`). + +### 4.4 Environmental Shift (HVAC, Temperature) + +Slow environmental changes produce a **linear drift** pattern: + +1. All links show gradual, correlated coherence changes over minutes/hours. +2. The `DriftProfile::Linear` classification activates. +3. The `FieldNormalMode` environmental projection magnitude increases. +4. Per-link Welford statistics track the drift rate. + +This pattern is distinct from person-caused changes because: +- It affects all links simultaneously (not a traveling wave). +- The drift rate is slow (sub-Hz) compared to body motion (0.5-5 Hz). +- The eigenmode projection captures most of the change (high `variance_explained`). + +### 4.5 Temporal Motif Detection Algorithm + +```rust +/// Temporal motif classifier for RF graph evolution. +pub struct TemporalMotifClassifier { + /// Per-link coherence history (ring buffer, 10 seconds at 20 Hz). + link_histories: Vec>, // [n_links][200] + /// Cross-correlation matrix of link coherence changes. + cross_correlation: Vec>, // [n_links][n_links] + /// Detected motif patterns. + active_motifs: Vec, +} + +/// A detected temporal motif in the graph evolution. +pub struct ActiveMotif { + /// Motif type. + pub motif_type: MotifType, + /// Links involved in this motif. + pub affected_links: Vec<(NodeId, NodeId)>, + /// Start timestamp. + pub start_us: u64, + /// Current phase of the motif. + pub phase: MotifPhase, + /// Confidence (0.0-1.0). + pub confidence: f32, + /// Estimated velocity (for sweep motifs, m/s). + pub estimated_velocity: Option, +} + +pub enum MotifType { + /// Sequential coherence drops along a path. + Sweep { direction: [f32; 2] }, + /// Correlated drops in a spatial cluster. + ClusterDrop, + /// All links shift simultaneously. + GlobalShift, + /// Single isolated link perturbation. + Isolated, +} + +pub enum MotifPhase { + Leading, + Peak, + Trailing, + Recovery, +} +``` + +--- + +## 5. Minimum Cut Trajectory Tracking + +### 5.1 Background: Min-Cut in RF Graphs + +The `ruvector-mincut` crate provides `DynamicMinCut` for partitioning the +CSI correlation graph into person clusters (`PersonCluster` in +`multistatic.rs`). At each TDMA cycle, the min-cut boundary separates +regions of the graph associated with different people. + +### 5.2 Cut Boundary as a Spatial Contour + +The min-cut boundary in the RF graph corresponds to a physical contour in +the room. Each cut edge (link) has a known geometry (from node positions), +so the cut boundary can be projected into 2D room coordinates. + +``` +Min-Cut Boundary Projection: + + Graph Space: Room Space: + + A ----[cut]---- B A(0,0) ........... B(5,0) + | | . +---------+ . + | Person 1 | . | Person | . + | | . | Region | . + C ----[cut]---- D . +---------+ . + C(0,5) ........... D(5,5) + + Cut edges: A-B, C-D Cut contour: horizontal line at y~2.5 +``` + +### 5.3 Kalman Filtering of Graph Partitions + +To track smooth person trajectories from noisy min-cut outputs, we apply +Kalman filtering to the cut boundary parameters: + +```rust +/// Kalman-filtered min-cut boundary tracker. +pub struct CutBoundaryTracker { + /// State: [centroid_x, centroid_y, velocity_x, velocity_y, area]. + state: [f64; 5], + /// 5x5 covariance matrix (upper triangle, 15 elements). + covariance: [f64; 15], + /// Process noise (acceleration variance). + process_noise: f64, + /// Measurement noise (cut boundary estimation variance). + measurement_noise: f64, + /// Track ID linking to pose_tracker TrackId. + track_id: u64, + /// History of filtered centroids for trajectory extraction. + trajectory: VecDeque<(u64, [f64; 2])>, // (timestamp_us, [x, y]) +} + +impl CutBoundaryTracker { + /// Predict step: advance state by dt seconds. + pub fn predict(&mut self, dt: f64) { + // Constant velocity model + self.state[0] += self.state[2] * dt; // x += vx * dt + self.state[1] += self.state[3] * dt; // y += vy * dt + // Covariance prediction: P = F*P*F' + Q + // (simplified: add process noise to velocity components) + } + + /// Update step: incorporate new min-cut boundary measurement. + pub fn update(&mut self, measurement: &CutBoundaryMeasurement) { + // Kalman gain, state update, covariance update + // Measurement model: observe centroid_x, centroid_y, area + } + + /// Extract the smoothed trajectory over the last N seconds. + pub fn trajectory(&self, duration_us: u64) -> &[(u64, [f64; 2])] { + // Return from self.trajectory deque + &[] // placeholder + } +} + +/// Measurement from a single min-cut partition. +pub struct CutBoundaryMeasurement { + /// Centroid of the partition in room coordinates. + pub centroid: [f64; 2], + /// Estimated area of the partition (square metres). + pub area: f64, + /// Number of cut edges (higher = more confident boundary). + pub n_cut_edges: usize, + /// Mean coherence of cut edges (lower = stronger signal). + pub mean_cut_coherence: f32, +} +``` + +### 5.4 Smooth Interpolation of Cut Boundaries + +Between TDMA cycles (50 ms intervals), the cut boundary position can be +interpolated using the Kalman velocity estimate: + +``` +Interpolation Timeline: + + Cycle N Cycle N+1 Cycle N+2 + | | | + v v v + [Measurement] [Measurement] [Measurement] + | ^ ^ ^ | ^ ^ ^ | + | | | | | | | | | + | Interpolated positions at 5ms intervals | + | using Kalman velocity prediction | +``` + +This gives the sensing-server UI (in `wifi-densepose-sensing-server`) a +smooth 200 Hz rendering of person positions even though the underlying +measurements arrive at 20 Hz. + +### 5.5 Multi-Person Cut Tracking + +For K persons, the min-cut produces K partitions. Each partition is tracked +by a separate `CutBoundaryTracker`. The assignment of partitions to trackers +across frames uses the Hungarian algorithm (already available via +`ruvector-mincut::DynamicPersonMatcher`). + +``` +Multi-Person State Diagram: + + [Partition Detection] + | + v + [Assignment] <-- Hungarian algorithm (DynamicPersonMatcher) + | + +---+---+---+ + | | | + v v v + [Track 1] [Track 2] [Track 3] + Kalman Kalman Kalman + filter filter filter + | | | + v v v + [Smoothed Trajectories] +``` + +--- + +## 6. Event Detection from Graph Dynamics + +### 6.1 Change-Point Detection on Graph Time Series + +Discrete events (person entry, exit, gesture, fall) manifest as change +points in the graph evolution. We detect these using three complementary +methods: + +#### 6.1.1 CUSUM (Cumulative Sum) on Coherence + +```rust +/// CUSUM change-point detector for per-link coherence. +pub struct CusumDetector { + /// Target mean (expected coherence under null hypothesis). + target: f64, + /// Allowable slack before triggering. + slack: f64, + /// Detection threshold. + threshold: f64, + /// Cumulative sum (positive direction). + s_pos: f64, + /// Cumulative sum (negative direction). + s_neg: f64, + /// Frame count since last reset. + frame_count: u64, +} + +impl CusumDetector { + pub fn update(&mut self, value: f64) -> Option { + self.frame_count += 1; + let deviation = value - self.target; + + self.s_pos = (self.s_pos + deviation - self.slack).max(0.0); + self.s_neg = (self.s_neg - deviation - self.slack).max(0.0); + + if self.s_pos > self.threshold { + let cp = ChangePoint { + frame: self.frame_count, + direction: ChangeDirection::Increasing, + magnitude: self.s_pos, + }; + self.s_pos = 0.0; + return Some(cp); + } + if self.s_neg > self.threshold { + let cp = ChangePoint { + frame: self.frame_count, + direction: ChangeDirection::Decreasing, + magnitude: self.s_neg, + }; + self.s_neg = 0.0; + return Some(cp); + } + None + } +} +``` + +#### 6.1.2 Graph Spectral Analysis + +Changes in the graph's Laplacian eigenvalues indicate topological shifts: + +- **Fiedler value** (second-smallest eigenvalue of the Laplacian) drops when + the graph becomes easier to partition (person creating a bottleneck). +- **Spectral gap** changes indicate connectivity shifts. +- **Eigenvalue tracking** over time reveals smooth vs. sudden transitions. + +The existing `FieldModel` SVD in `field_model.rs` computes eigenvalues of +the CSI covariance. Extending this to the graph Laplacian requires building +the Laplacian from the `CoherenceState` of all links: + +```rust +/// Build the coherence-weighted Laplacian of the RF sensing graph. +pub fn build_coherence_laplacian( + links: &[(NodeId, NodeId)], + coherences: &[f32], + n_nodes: usize, +) -> Vec> { + let mut laplacian = vec![vec![0.0f64; n_nodes]; n_nodes]; + + for (link, &coh) in links.iter().zip(coherences.iter()) { + let i = link.0 as usize; + let j = link.1 as usize; + let w = coh as f64; + + laplacian[i][j] -= w; + laplacian[j][i] -= w; + laplacian[i][i] += w; + laplacian[j][j] += w; + } + + laplacian +} +``` + +#### 6.1.3 Temporal Motif Matching + +Using the motif patterns from Section 4.5, event detection becomes a +pattern-matching problem. Each event type has a characteristic temporal +motif signature: + +| Event | Motif Type | Duration | Distinguishing Feature | +|-------|-----------|----------|----------------------| +| Person entry | Sweep (inward) | 1-3 s | Links near door drop first | +| Person exit | Sweep (outward) | 1-3 s | Links near door drop last | +| Gesture | Isolated oscillation | 0.5-2 s | Single-link high-frequency perturbation | +| Fall | Sudden cluster drop | 0.2-0.5 s | Multiple links drop simultaneously, fast | +| Door open | Global step change | 0.1-0.5 s | All links shift, new baseline forms | +| HVAC cycle | Global linear drift | 10-60 s | Slow, correlated, recoverable | + +### 6.2 Event Detection Pipeline + +``` +Event Detection State Machine: + + [Raw CSI Frames at 20 Hz] + | + v + [Per-Link Coherence Update] --> coherence.rs + | + v + [Gate Decision] --> coherence_gate.rs + | + +-----+-----+ + | | + v v + [CUSUM [Spectral + Detector] Analysis] + | | + +-----+-----+ + | + v + [Temporal Motif Matching] + | + v + [Event Classification] + | + +---> EntryEvent --> cross_room.rs + +---> ExitEvent --> cross_room.rs + +---> GestureEvent --> gesture.rs + +---> FallEvent --> pose_tracker.rs (emergency) + +---> DoorEvent --> field_model.rs (recalibrate) + +---> DriftEvent --> longitudinal.rs +``` + +### 6.3 Integration with Existing Event Types + +The `CrossRoomTracker` in `cross_room.rs` already defines `ExitEvent`, +`EntryEvent`, and `TransitionEvent`. The temporal graph event detector +feeds these types: + +```rust +/// Bridge between temporal graph events and cross-room tracker. +pub fn graph_event_to_cross_room( + event: &DetectedEvent, + tracker: &mut CrossRoomTracker, + embedding: &[f32], +) -> Result<(), CrossRoomError> { + match event.event_type { + EventType::PersonExit { room_id, track_id } => { + tracker.record_exit(ExitEvent { + embedding: embedding.to_vec(), + room_id, + track_id, + timestamp_us: event.timestamp_us, + matched: false, + }) + } + EventType::PersonEntry { room_id, track_id } => { + let entry = EntryEvent { + embedding: embedding.to_vec(), + room_id, + track_id, + timestamp_us: event.timestamp_us, + }; + let _match_result = tracker.match_entry(&entry)?; + Ok(()) + } + _ => Ok(()), // Other events don't affect cross-room tracking + } +} +``` + +--- + +## 7. Compressed Temporal Storage + +### 7.1 The CompressedCsiBuffer Concept + +The `ruvector-temporal-tensor` crate provides `CompressedCsiBuffer` for +efficient ring-buffer storage of CSI data. We extend this concept to +store graph evolution history with minimal memory overhead. + +### 7.2 Delta Compression of Graph Snapshots + +Since the RF graph changes incrementally (most edges remain similar between +consecutive frames), delta encoding provides significant compression: + +```rust +/// Delta-compressed temporal graph store. +pub struct DeltaGraphStore { + /// Anchor snapshots at regular intervals (every 1 second = 20 frames). + anchors: Vec, + /// Delta frames between anchors. + deltas: Vec>, + /// Anchor interval in frames. + anchor_interval: usize, + /// Maximum history depth (anchors). + max_anchors: usize, + /// Current frame within the anchor interval. + frame_in_interval: usize, +} + +/// Full graph state at an anchor point. +pub struct AnchorSnapshot { + pub timestamp_us: u64, + pub frame_id: u64, + /// Per-edge coherence values (quantized to u8: 0-255 maps to 0.0-1.0). + pub coherences: Vec, + /// Per-edge gate decisions (packed: 2 bits each). + pub gate_decisions: Vec, + /// Per-edge perturbation energy (quantized to u16). + pub perturbation_energies: Vec, + /// Graph-level Fiedler value. + pub fiedler_value: f32, + /// Graph-level total perturbation. + pub total_perturbation: f32, +} + +/// Change to a single edge between consecutive frames. +pub struct EdgeDelta { + /// Edge index (into the link array). + pub edge_idx: u8, + /// Coherence change (quantized: i8, where 1 unit = 1/255). + pub coherence_delta: i8, + /// Whether the gate decision changed. + pub gate_changed: bool, + /// New gate decision (only present if gate_changed). + pub new_gate: Option, +} +``` + +### 7.3 Compression Ratios + +For a 15-link mesh: + +| Representation | Per-Frame Size | 1-Hour Size | Compression Ratio | +|---------------|---------------|-------------|------------------| +| Full snapshot | 135 B | 9.7 MB | 1.0x (baseline) | +| Delta (typical 3 edges change) | 12 B | 864 KB | 11.2x | +| Delta (quiet, 0 edges change) | 2 B | 144 KB | 67.3x | +| Delta (active, 8 edges change) | 34 B | 2.4 MB | 4.0x | + +With 1-second anchor intervals (every 20 frames), the anchor overhead adds +135 B * 3600 = 486 KB/hour, bringing the total to approximately 1.3 MB/hour +for typical occupancy, or 31 MB/day. + +### 7.4 Temporal Index Structure + +To support efficient temporal queries, we maintain a two-level index: + +``` +Index Structure: + + Level 0 (Anchor Index): Binary search over anchor timestamps. + Level 1 (Delta Index): Sequential scan within anchor interval. + + Query: "coherence of link A-B at time T" + 1. Binary search anchors for latest anchor before T --> O(log A) + 2. Reconstruct state at anchor --> O(1) + 3. Apply deltas from anchor to T --> O(F) where F <= 20 + Total: O(log A + F), F bounded by anchor_interval +``` + +For 24 hours of data with 1-second anchors, A = 86,400 anchors. +Binary search costs log2(86400) ~ 17 comparisons. Delta replay costs +at most 20 frame applications. Total: ~37 operations per point query. + +### 7.5 Ring-Buffer Lifecycle + +``` +Ring-Buffer Rotation: + + +---+---+---+---+---+---+---+---+ + | A | d | d | d | A | d | d | d | ... + +---+---+---+---+---+---+---+---+ + ^ ^ + oldest newest + + When buffer is full: + 1. Evict oldest anchor + its deltas + 2. (Optionally) downsample to hourly archive before eviction + 3. Write new anchor at tail + + Archive downsampling: + - Keep 1 anchor per minute (instead of per second) + - Discard inter-anchor deltas + - Retain only aggregate statistics (mean, min, max coherence) +``` + +--- + +## 8. Cross-Room Transition Graphs + +### 8.1 Current Implementation + +The `CrossRoomTracker` in `cross_room.rs` maintains: +- **Room fingerprints**: 128-dim AETHER embeddings of each room's static profile. +- **Pending exits**: Unmatched exit events with person embeddings. +- **Transition log**: Append-only record of cross-room transitions. + +The transition log is already a temporal graph: rooms are vertices, +transitions are directed temporal edges with timestamps and similarity scores. + +### 8.2 Extending to Full Temporal Transition Graphs + +```rust +/// Temporal transition graph extending CrossRoomTracker. +pub struct TemporalTransitionGraph { + /// Room-to-room adjacency with temporal statistics. + adjacency: Vec>, + /// Per-room temporal occupancy profile. + room_profiles: Vec, + /// Global transition patterns (time-of-day effects). + circadian_patterns: Vec, +} + +/// Aggregated statistics for transitions between two rooms. +pub struct TransitionEdgeStats { + pub from_room: u64, + pub to_room: u64, + /// Total transition count. + pub count: u64, + /// Welford statistics on transition gap times. + pub gap_stats: WelfordStats, + /// Welford statistics on similarity scores. + pub similarity_stats: WelfordStats, + /// Time-of-day histogram (24 bins, 1 hour each). + pub hourly_histogram: [u32; 24], + /// Most recent transition timestamp. + pub last_transition_us: u64, +} + +/// Per-room temporal occupancy model. +pub struct RoomTemporalProfile { + pub room_id: u64, + /// Welford statistics on occupancy duration. + pub duration_stats: WelfordStats, + /// Average occupancy by hour of day. + pub hourly_occupancy: [f32; 24], + /// Total person-seconds observed. + pub total_person_seconds: f64, + /// Fingerprint drift (cosine similarity of current vs. initial). + pub fingerprint_drift: f32, +} +``` + +### 8.3 Transition Prediction + +With sufficient history, the temporal transition graph enables prediction +of likely next transitions: + +```rust +/// Predict the most likely next room for a person. +pub fn predict_next_room( + graph: &TemporalTransitionGraph, + current_room: u64, + current_hour: u8, + person_history: &[TransitionEvent], +) -> Vec<(u64, f64)> { + // Combine three signals: + // 1. Global transition frequency (base rate) + // 2. Time-of-day pattern (circadian bias) + // 3. Person-specific history (Markov chain) + + let mut predictions = Vec::new(); + + for edge_stats in &graph.adjacency[current_room as usize] { + let base_rate = edge_stats.count as f64; + let circadian_weight = edge_stats.hourly_histogram[current_hour as usize] as f64 + / (edge_stats.count as f64).max(1.0); + let personal_weight = person_specific_weight( + person_history, + current_room, + edge_stats.to_room, + ); + + let score = base_rate * circadian_weight * personal_weight; + predictions.push((edge_stats.to_room, score)); + } + + predictions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + predictions +} +``` + +### 8.4 Environment Fingerprint Evolution + +Room fingerprints drift over time as furniture moves, seasonal temperature +changes affect multipath, and building modifications alter RF propagation. +The temporal transition graph tracks this drift: + +``` +Fingerprint Drift Timeline: + + Day 1 Day 30 Day 60 Day 90 + | | | | + v v v v + [F_0] cos=0.99 [F_30] cos=0.95 [F_60] cos=0.88 [F_90] + | + v + Drift threshold exceeded + --> Re-fingerprint room +``` + +When `fingerprint_drift` drops below `CrossRoomConfig::min_similarity`, +the room's fingerprint should be recomputed to maintain cross-room +matching accuracy. + +--- + +## 9. Longitudinal Drift Detection on Graph Topology + +### 9.1 Current Implementation + +The `PersonalBaseline` in `longitudinal.rs` tracks five biophysical metrics +per person using `WelfordStats`: +- Gait symmetry +- Stability index +- Breathing regularity +- Micro-tremor amplitude +- Activity level + +Drift is detected when a metric exceeds 2-sigma for 3+ consecutive days, +escalating to `MonitoringLevel::RiskCorrelation` after 7+ days. + +### 9.2 Extending to Graph Topology Metrics + +The same Welford-based drift detection can monitor graph-level properties: + +```rust +/// Graph-level longitudinal health metrics. +pub enum GraphHealthMetric { + /// Mean coherence across all links. + MeanCoherence, + /// Minimum coherence (weakest link). + MinCoherence, + /// Standard deviation of coherence across links. + CoherenceSpread, + /// Fiedler value (graph connectivity). + FiedlerValue, + /// Total perturbation energy. + TotalPerturbation, + /// Fraction of links in Accept gate state. + AcceptFraction, + /// Geometric Diversity Index. + Gdi, + /// Mean Cramer-Rao bound (localisation accuracy). + MeanCrb, +} + +/// Per-graph longitudinal baseline extending PersonalBaseline pattern. +pub struct GraphBaseline { + /// Per-metric Welford accumulators. + pub metrics: Vec<(GraphHealthMetric, WelfordStats)>, + /// Observation count (TDMA cycles). + pub observation_count: u64, + /// Consecutive drift counters (one per metric). + pub drift_counters: Vec, + /// Minimum observations before drift detection activates. + pub min_observations: u64, + /// Z-score threshold for drift. + pub z_threshold: f64, + /// Consecutive-frame threshold for drift alert. + pub sustained_threshold: u32, +} + +impl GraphBaseline { + /// Update with a new graph-level observation. + pub fn update(&mut self, observation: &GraphObservation) -> Vec { + self.observation_count += 1; + let mut reports = Vec::new(); + + for (i, (metric, stats)) in self.metrics.iter_mut().enumerate() { + let value = observation.value_for(metric); + stats.update(value); + + if self.observation_count < self.min_observations { + continue; + } + + let z = stats.z_score(value); + if z.abs() > self.z_threshold { + self.drift_counters[i] += 1; + } else { + self.drift_counters[i] = 0; + } + + if self.drift_counters[i] >= self.sustained_threshold { + reports.push(GraphDriftReport { + metric: *metric, + z_score: z, + current_value: value, + baseline_mean: stats.mean, + baseline_std: stats.std_dev(), + sustained_frames: self.drift_counters[i], + }); + } + } + + reports + } +} +``` + +### 9.3 Graph Health Monitoring + +``` +Graph Health State Machine: + + [Healthy] + | coherence stable, Fiedler stable, GDI stable + | + +-- Mean coherence drops > 2-sigma for 5 min + | | + | v + | [Degraded] + | | Investigate: node failure? environmental shift? + | | + | +-- Node offline detected + | | | + | | v + | | [Node Failure] + | | | GDI drops, CRB increases + | | | Alert: reduced sensing accuracy + | | | + | | +-- Node recovers --> [Healthy] + | | +-- Sustained --> [Reconfigure] + | | + | +-- Environmental shift detected + | | | + | | v + | | [Recalibrating] + | | | FieldModel::reset_calibration() + | | | Collect new baseline frames + | | | FieldModel::finalize_calibration() + | | +-- Success --> [Healthy] + | | +-- Fails --> [Degraded] + | | + | +-- Recovers spontaneously --> [Healthy] + | + +-- Fiedler value drops sharply (< 3-sigma) + | + v + [Partitioned] + | Graph connectivity compromised + | Fall-back to per-partition sensing + +-- Connectivity restored --> [Healthy] +``` + +### 9.4 Biomechanics-Inspired Graph Health + +Drawing from the `DriftMetric` enum in `longitudinal.rs`, we define +analogous graph health metrics with biomechanical parallels: + +| Biomechanics Metric | Graph Analogue | Interpretation | +|---------------------|---------------|---------------| +| Gait Symmetry | Link coherence symmetry | Even sensing quality across all links | +| Stability Index | Fiedler value stability | Consistent graph connectivity | +| Breathing Regularity | Coherence periodicity | Regular environmental cycles (HVAC) | +| Micro-Tremor | High-freq coherence jitter | Electronic noise floor health | +| Activity Level | Total perturbation rate | Sensing volume utilisation | + +--- + +## 10. Proposed Data Structures + +### 10.1 Core Temporal Graph Type + +```rust +/// The RF sensing temporal graph. +/// +/// Central data structure for temporal graph evolution tracking. +/// Integrates with existing modules via the integration points +/// listed in Section 1.2. +pub struct RfTemporalGraph { + // -- Topology (stable) -- + /// Node identifiers. + nodes: Vec, + /// Link definitions (directed: tx -> rx). + links: Vec<(NodeId, NodeId)>, + /// Node positions in room coordinates. + positions: Vec<[f32; 3]>, + + // -- Live state (updated at 20 Hz) -- + /// Per-link coherence state (from coherence.rs). + coherence_states: Vec, + /// Per-link gate policy (from coherence_gate.rs). + gate_policies: Vec, + /// Field model for eigenstructure tracking. + field_model: FieldModel, + + // -- Temporal storage -- + /// Delta-compressed graph history. + history: DeltaGraphStore, + /// Graph-level Welford baseline. + graph_baseline: GraphBaseline, + + // -- Analysis -- + /// Per-link CUSUM detectors for change-point detection. + cusum_detectors: Vec, + /// Temporal motif classifier. + motif_classifier: TemporalMotifClassifier, + /// Cut boundary trackers (one per tracked person). + cut_trackers: Vec, + + // -- Configuration -- + config: TemporalGraphConfig, +} + +pub struct TemporalGraphConfig { + /// TDMA cycle rate (Hz). + pub cycle_rate_hz: f64, + /// Anchor interval for delta compression (frames). + pub anchor_interval: usize, + /// Maximum history depth (seconds). + pub max_history_s: f64, + /// CUSUM slack parameter. + pub cusum_slack: f64, + /// CUSUM detection threshold. + pub cusum_threshold: f64, + /// Graph health z-score threshold. + pub health_z_threshold: f64, +} + +impl Default for TemporalGraphConfig { + fn default() -> Self { + Self { + cycle_rate_hz: 20.0, + anchor_interval: 20, // 1 second + max_history_s: 3600.0, // 1 hour live + cusum_slack: 0.05, + cusum_threshold: 2.0, + health_z_threshold: 2.0, + } + } +} +``` + +### 10.2 Frame Processing Pipeline + +```rust +impl RfTemporalGraph { + /// Process one TDMA cycle's worth of data. + /// + /// This is the main entry point called at 20 Hz. + pub fn process_cycle( + &mut self, + fused_frame: &FusedSensingFrame, + timestamp_us: u64, + ) -> CycleResult { + let mut result = CycleResult::default(); + + // 1. Update per-link coherence states + for (i, link) in self.links.iter().enumerate() { + if let Some(amplitude) = extract_link_amplitude(fused_frame, link) { + if let Ok(score) = self.coherence_states[i].update(&litude) { + // 2. Evaluate gate decision + let stale = self.coherence_states[i].stale_count(); + let decision = self.gate_policies[i].evaluate(score, stale); + + // 3. Run CUSUM change-point detection + if let Some(cp) = self.cusum_detectors[i].update(score as f64) { + result.change_points.push((*link, cp)); + } + } + } + } + + // 4. Extract perturbation from field model + if let Ok(perturbation) = self.field_model.extract_perturbation( + &build_observations(fused_frame), + ) { + result.total_perturbation = perturbation.total_energy; + } + + // 5. Store snapshot/delta in temporal history + self.history.record_frame( + timestamp_us, + &self.coherence_states, + &self.gate_policies, + ); + + // 6. Run temporal motif classification + result.motifs = self.motif_classifier.classify( + &self.coherence_states, + timestamp_us, + ); + + // 7. Update graph baseline for longitudinal monitoring + let observation = GraphObservation::from_states( + &self.coherence_states, + &self.gate_policies, + result.total_perturbation, + ); + result.drift_reports = self.graph_baseline.update(&observation); + + // 8. Update cut boundary trackers + // (Requires min-cut output from ruvector-mincut, omitted for clarity) + + result + } +} + +#[derive(Default)] +pub struct CycleResult { + pub change_points: Vec<((NodeId, NodeId), ChangePoint)>, + pub motifs: Vec, + pub drift_reports: Vec, + pub total_perturbation: f64, +} +``` + +### 10.3 Type Summary + +| Type | Module Location | Responsibility | +|------|----------------|---------------| +| `RfTemporalGraph` | `signal/src/ruvsense/temporal_graph.rs` (new) | Aggregate root | +| `DeltaGraphStore` | `signal/src/ruvsense/temporal_graph.rs` (new) | Compressed history | +| `CusumDetector` | `signal/src/ruvsense/temporal_graph.rs` (new) | Change-point detection | +| `TemporalMotifClassifier` | `signal/src/ruvsense/temporal_graph.rs` (new) | Pattern recognition | +| `CutBoundaryTracker` | `signal/src/ruvsense/temporal_graph.rs` (new) | Kalman-filtered cuts | +| `GraphBaseline` | `signal/src/ruvsense/temporal_graph.rs` (new) | Longitudinal health | +| `TemporalTransitionGraph` | `signal/src/ruvsense/cross_room.rs` (extend) | Room transitions | +| `CoherenceState` | `signal/src/ruvsense/coherence.rs` (existing) | Per-link live state | +| `GatePolicy` | `signal/src/ruvsense/coherence_gate.rs` (existing) | Per-link gate | +| `FieldModel` | `signal/src/ruvsense/field_model.rs` (existing) | Eigenstructure | +| `WelfordStats` | `signal/src/ruvsense/field_model.rs` (existing) | Online statistics | +| `PersonalBaseline` | `signal/src/ruvsense/longitudinal.rs` (existing) | Per-person drift | +| `CrossRoomTracker` | `signal/src/ruvsense/cross_room.rs` (existing) | Identity continuity | +| `MultistaticArray` | `ruvector/src/viewpoint/fusion.rs` (existing) | Viewpoint fusion | +| `GeometricDiversityIndex` | `ruvector/src/viewpoint/geometry.rs` (existing) | Array quality | +| `CramerRaoBound` | `ruvector/src/viewpoint/geometry.rs` (existing) | Localisation bound | + +--- + +## 11. Integration Roadmap + +### 11.1 Phase 1: Temporal Storage Foundation (2-3 weeks) + +**Goal**: Implement `DeltaGraphStore` and basic temporal queries. + +**Files to create**: +- `signal/src/ruvsense/temporal_graph.rs` -- Core temporal graph types +- `signal/src/ruvsense/temporal_store.rs` -- Delta compression engine + +**Files to modify**: +- `signal/src/ruvsense/mod.rs` -- Register new modules +- `signal/src/ruvsense/coherence.rs` -- Add `snapshot()` method to `CoherenceState` + +**Dependencies**: None (builds on existing `WelfordStats`, `CoherenceState`). + +**Validation**: +- Unit tests for delta encode/decode roundtrip. +- Property tests: reconstruct any timestamp from anchors + deltas. +- Memory budget tests: verify < 100 MB/day for 6-node mesh. + +### 11.2 Phase 2: Change-Point Detection (1-2 weeks) + +**Goal**: Implement CUSUM detectors and event classification. + +**Files to create**: +- `signal/src/ruvsense/change_point.rs` -- CUSUM and spectral detectors + +**Files to modify**: +- `signal/src/ruvsense/cross_room.rs` -- Accept events from detector + +**Dependencies**: Phase 1 (temporal store for history access). + +**Validation**: +- Replay recorded CSI sessions, compare detected events to ground truth. +- False positive rate < 1 per hour for empty room. +- Detection latency < 500 ms for person entry/exit. + +### 11.3 Phase 3: Min-Cut Trajectory Tracking (2-3 weeks) + +**Goal**: Implement `CutBoundaryTracker` with Kalman filtering. + +**Files to create**: +- `signal/src/ruvsense/cut_trajectory.rs` -- Kalman-filtered cut tracking + +**Files to modify**: +- `signal/src/ruvsense/multistatic.rs` -- Feed `PersonCluster` to tracker + +**Dependencies**: Phase 1, `ruvector-mincut` integration. + +**Validation**: +- Trajectory smoothness: velocity discontinuity < 0.5 m/s between frames. +- Interpolation accuracy: compare 200 Hz interpolated vs. 20 Hz measured. + +### 11.4 Phase 4: Longitudinal Graph Health (1-2 weeks) + +**Goal**: Implement `GraphBaseline` with drift detection. + +**Files to modify**: +- `signal/src/ruvsense/longitudinal.rs` -- Extract `WelfordStats` pattern + into shared trait, implement for graph metrics. + +**Dependencies**: Phase 1, Phase 2. + +**Validation**: +- Inject simulated node failures, verify detection within 5 minutes. +- Inject simulated environmental drift, verify detection within 10 minutes. +- No false drift alerts during 24-hour stable operation. + +### 11.5 Phase 5: Temporal Transition Graph (1 week) + +**Goal**: Extend `CrossRoomTracker` with `TemporalTransitionGraph`. + +**Files to modify**: +- `signal/src/ruvsense/cross_room.rs` -- Add temporal statistics to + transition log, implement transition prediction. + +**Dependencies**: Phase 2 (event detection feeds transitions). + +**Validation**: +- Transition prediction accuracy > 70% for top-1 room after 7 days. +- Circadian patterns detected within 3 days of continuous operation. + +### 11.6 Proposed ADR + +This work warrants a new Architecture Decision Record: + +**ADR-044: Temporal Graph Evolution Tracking** +- Status: Proposed +- Context: Static graph analysis misses temporal patterns critical for + event detection, trajectory tracking, and longitudinal monitoring. +- Decision: Implement `RfTemporalGraph` as described in Section 10. +- Consequences: Adds ~100 MB/day storage, ~2 ms per-frame processing + overhead, enables 5 new sensing capabilities. + +--- + +## 12. References + +### 12.1 Temporal Graph Networks + +1. Rossi, E., Chamberlain, B., Frasca, F., Eynard, D., Monti, F., & + Bronstein, M. (2020). "Temporal Graph Networks for Deep Learning on + Dynamic Graphs." ICML Workshop on GRL+. + +2. Kumar, S., Zhang, X., & Leskovec, J. (2019). "Predicting Dynamic + Embedding Trajectory in Temporal Interaction Networks." KDD. + +3. Chen, J., Zheng, S., Song, H., & Zhu, J. (2021). "Continuous-Time + Dynamic Graph Learning via Neural Interaction Processes." CIKM. + +4. Trivedi, R., Farajtabar, M., Bisber, P., & Zha, H. (2019). "DyRep: + Learning Representations over Dynamic Graphs." ICLR. + +5. Xu, D., Ruan, C., Korpeoglu, E., Kumar, S., & Achan, K. (2020). + "Inductive Representation Learning on Temporal Graphs." ICLR. + +### 12.2 Graph Signal Processing + +6. Shuman, D., Narang, S., Frossard, P., Ortega, A., & Vandergheynst, P. + (2013). "The Emerging Field of Signal Processing on Graphs." IEEE + Signal Processing Magazine. + +7. Sandryhaila, A. & Moura, J. M. F. (2014). "Big Data Analysis with + Signal Processing on Graphs." IEEE Signal Processing Magazine. + +### 12.3 Change-Point Detection + +8. Page, E. S. (1954). "Continuous Inspection Schemes." Biometrika. + +9. Aminikhanghahi, S. & Cook, D. J. (2017). "A Survey of Methods for + Time Series Change Point Detection." Knowledge and Information Systems. + +### 12.4 RF Tomography and WiFi Sensing + +10. Wilson, J. & Patwari, N. (2010). "Radio Tomographic Imaging with + Wireless Networks." IEEE Transactions on Mobile Computing. + +11. Wang, H., Zhang, D., Wang, Y., Ma, J., Wang, Y., & Li, S. (2017). + "RT-Fall: A Real-Time and Contactless Fall Detection System with + Commodity WiFi Devices." IEEE Transactions on Mobile Computing. + +12. Ma, Y., Zhou, G., & Wang, S. (2019). "WiFi Sensing with Channel State + Information: A Survey." ACM Computing Surveys. + +### 12.5 Internal Architecture References + +13. ADR-029: RuvSense Multistatic Sensing Mode +14. ADR-030: RuvSense Persistent Field Model +15. ADR-031: RuView Sensing-First RF Mode +16. ADR-024: Contrastive CSI Embedding / AETHER +17. ADR-027: Cross-Environment Domain Generalization / MERIDIAN + +### 12.6 Kalman Filtering + +18. Welch, G. & Bishop, G. (2006). "An Introduction to the Kalman Filter." + UNC-Chapel Hill, TR 95-041. + +19. Rauch, H. E., Tung, F., & Striebel, C. T. (1965). "Maximum Likelihood + Estimates of Linear Dynamic Systems." AIAA Journal. + +### 12.7 Graph Spectral Analysis + +20. Chung, F. R. K. (1997). "Spectral Graph Theory." CBMS Regional + Conference Series in Mathematics, AMS. + +21. Fiedler, M. (1973). "Algebraic Connectivity of Graphs." Czechoslovak + Mathematical Journal.