Files
wifi-densepose/vendor/ruvector/crates/ruvector-crv/src/session.rs

619 lines
19 KiB
Rust

//! CRV Session Manager
//!
//! Manages CRV sessions as directed acyclic graphs (DAGs), where each session
//! progresses through stages I-VI. Provides cross-session convergence analysis
//! to find agreement between multiple viewers targeting the same coordinate.
//!
//! # Architecture
//!
//! Each session is a DAG of stage entries. Cross-session convergence is computed
//! by finding entries with high embedding similarity across different sessions
//! targeting the same coordinate.
use crate::error::{CrvError, CrvResult};
use crate::stage_i::StageIEncoder;
use crate::stage_ii::StageIIEncoder;
use crate::stage_iii::StageIIIEncoder;
use crate::stage_iv::StageIVEncoder;
use crate::stage_v::StageVEngine;
use crate::stage_vi::StageVIModeler;
use crate::types::*;
use ruvector_gnn::search::cosine_similarity;
use std::collections::HashMap;
/// A session entry stored in the session graph.
#[derive(Debug, Clone)]
struct SessionEntry {
/// The stage data embedding.
embedding: Vec<f32>,
/// Stage number (1-6).
stage: u8,
/// Entry index within the stage.
entry_index: usize,
/// Metadata.
metadata: HashMap<String, serde_json::Value>,
/// Timestamp.
timestamp_ms: u64,
}
/// A complete CRV session with all stage data.
#[derive(Debug)]
struct Session {
/// Session identifier.
id: SessionId,
/// Target coordinate.
coordinate: TargetCoordinate,
/// Entries organized by stage.
entries: Vec<SessionEntry>,
}
/// CRV Session Manager: coordinates all stage encoders and manages sessions.
#[derive(Debug)]
pub struct CrvSessionManager {
/// Configuration.
config: CrvConfig,
/// Stage I encoder.
stage_i: StageIEncoder,
/// Stage II encoder.
stage_ii: StageIIEncoder,
/// Stage III encoder.
stage_iii: StageIIIEncoder,
/// Stage IV encoder.
stage_iv: StageIVEncoder,
/// Stage V engine.
stage_v: StageVEngine,
/// Stage VI modeler.
stage_vi: StageVIModeler,
/// Active sessions indexed by session ID.
sessions: HashMap<SessionId, Session>,
}
impl CrvSessionManager {
/// Create a new session manager with the given configuration.
pub fn new(config: CrvConfig) -> Self {
let stage_i = StageIEncoder::new(&config);
let stage_ii = StageIIEncoder::new(&config);
let stage_iii = StageIIIEncoder::new(&config);
let stage_iv = StageIVEncoder::new(&config);
let stage_v = StageVEngine::new(&config);
let stage_vi = StageVIModeler::new(&config);
Self {
config,
stage_i,
stage_ii,
stage_iii,
stage_iv,
stage_v,
stage_vi,
sessions: HashMap::new(),
}
}
/// Create a new session for a given target coordinate.
pub fn create_session(
&mut self,
session_id: SessionId,
coordinate: TargetCoordinate,
) -> CrvResult<()> {
if self.sessions.contains_key(&session_id) {
return Err(CrvError::EncodingError(format!(
"Session {} already exists",
session_id
)));
}
self.sessions.insert(
session_id.clone(),
Session {
id: session_id,
coordinate,
entries: Vec::new(),
},
);
Ok(())
}
/// Add Stage I data to a session.
pub fn add_stage_i(&mut self, session_id: &str, data: &StageIData) -> CrvResult<Vec<f32>> {
let embedding = self.stage_i.encode(data)?;
self.add_entry(session_id, 1, embedding.clone(), HashMap::new())?;
Ok(embedding)
}
/// Add Stage II data to a session.
pub fn add_stage_ii(&mut self, session_id: &str, data: &StageIIData) -> CrvResult<Vec<f32>> {
let embedding = self.stage_ii.encode(data)?;
self.add_entry(session_id, 2, embedding.clone(), HashMap::new())?;
Ok(embedding)
}
/// Add Stage III data to a session.
pub fn add_stage_iii(&mut self, session_id: &str, data: &StageIIIData) -> CrvResult<Vec<f32>> {
let embedding = self.stage_iii.encode(data)?;
self.add_entry(session_id, 3, embedding.clone(), HashMap::new())?;
Ok(embedding)
}
/// Add Stage IV data to a session.
pub fn add_stage_iv(&mut self, session_id: &str, data: &StageIVData) -> CrvResult<Vec<f32>> {
let embedding = self.stage_iv.encode(data)?;
self.add_entry(session_id, 4, embedding.clone(), HashMap::new())?;
Ok(embedding)
}
/// Run Stage V interrogation on a session.
///
/// Probes the accumulated session data with specified queries.
pub fn run_stage_v(
&mut self,
session_id: &str,
probe_queries: &[(&str, u8, Vec<f32>)], // (query text, target stage, query embedding)
k: usize,
) -> CrvResult<StageVData> {
let session = self
.sessions
.get(session_id)
.ok_or_else(|| CrvError::SessionNotFound(session_id.to_string()))?;
let all_embeddings: Vec<Vec<f32>> = session
.entries
.iter()
.map(|e| e.embedding.clone())
.collect();
let mut probes = Vec::new();
let mut cross_refs = Vec::new();
for (query_text, target_stage, query_emb) in probe_queries {
// Filter candidates to the target stage
let stage_entries: Vec<Vec<f32>> = session
.entries
.iter()
.filter(|e| e.stage == *target_stage)
.map(|e| e.embedding.clone())
.collect();
if stage_entries.is_empty() {
continue;
}
let mut probe = self.stage_v.probe(query_emb, &stage_entries, k)?;
probe.query = query_text.to_string();
probe.target_stage = *target_stage;
probes.push(probe);
}
// Cross-reference between all stage pairs
for from_stage in 1..=4u8 {
for to_stage in (from_stage + 1)..=4u8 {
let from_entries: Vec<Vec<f32>> = session
.entries
.iter()
.filter(|e| e.stage == from_stage)
.map(|e| e.embedding.clone())
.collect();
let to_entries: Vec<Vec<f32>> = session
.entries
.iter()
.filter(|e| e.stage == to_stage)
.map(|e| e.embedding.clone())
.collect();
if !from_entries.is_empty() && !to_entries.is_empty() {
let refs = self.stage_v.cross_reference(
from_stage,
&from_entries,
to_stage,
&to_entries,
self.config.convergence_threshold,
);
cross_refs.extend(refs);
}
}
}
let stage_v_data = StageVData {
probes,
cross_references: cross_refs,
};
// Encode Stage V result and add to session
if !stage_v_data.probes.is_empty() {
let embedding = self.stage_v.encode(&stage_v_data, &all_embeddings)?;
self.add_entry(session_id, 5, embedding, HashMap::new())?;
}
Ok(stage_v_data)
}
/// Run Stage VI composite modeling on a session.
pub fn run_stage_vi(&mut self, session_id: &str) -> CrvResult<StageVIData> {
let session = self
.sessions
.get(session_id)
.ok_or_else(|| CrvError::SessionNotFound(session_id.to_string()))?;
let embeddings: Vec<Vec<f32>> = session
.entries
.iter()
.map(|e| e.embedding.clone())
.collect();
let labels: Vec<(u8, usize)> = session
.entries
.iter()
.map(|e| (e.stage, e.entry_index))
.collect();
let stage_vi_data = self.stage_vi.partition(&embeddings, &labels)?;
// Encode Stage VI result and add to session
let embedding = self.stage_vi.encode(&stage_vi_data)?;
self.add_entry(session_id, 6, embedding, HashMap::new())?;
Ok(stage_vi_data)
}
/// Find convergence across multiple sessions targeting the same coordinate.
///
/// This is the core multi-viewer matching operation: given sessions from
/// different viewers targeting the same coordinate, find which aspects
/// of their signal line data converge (agree).
pub fn find_convergence(
&self,
coordinate: &str,
min_similarity: f32,
) -> CrvResult<ConvergenceResult> {
// Collect all sessions for this coordinate
let relevant_sessions: Vec<&Session> = self
.sessions
.values()
.filter(|s| s.coordinate == coordinate)
.collect();
if relevant_sessions.len() < 2 {
return Err(CrvError::EmptyInput(
"Need at least 2 sessions for convergence analysis".to_string(),
));
}
let mut session_pairs = Vec::new();
let mut scores = Vec::new();
let mut convergent_stages = Vec::new();
// Compare all pairs of sessions
for i in 0..relevant_sessions.len() {
for j in (i + 1)..relevant_sessions.len() {
let sess_a = relevant_sessions[i];
let sess_b = relevant_sessions[j];
// Compare stage-by-stage
for stage in 1..=6u8 {
let entries_a: Vec<&[f32]> = sess_a
.entries
.iter()
.filter(|e| e.stage == stage)
.map(|e| e.embedding.as_slice())
.collect();
let entries_b: Vec<&[f32]> = sess_b
.entries
.iter()
.filter(|e| e.stage == stage)
.map(|e| e.embedding.as_slice())
.collect();
if entries_a.is_empty() || entries_b.is_empty() {
continue;
}
// Find best match for each entry in A against entries in B
for emb_a in &entries_a {
for emb_b in &entries_b {
if emb_a.len() == emb_b.len() && !emb_a.is_empty() {
let sim = cosine_similarity(emb_a, emb_b);
if sim >= min_similarity {
session_pairs.push((sess_a.id.clone(), sess_b.id.clone()));
scores.push(sim);
if !convergent_stages.contains(&stage) {
convergent_stages.push(stage);
}
}
}
}
}
}
}
}
// Compute consensus embedding (mean of all converging embeddings)
let consensus_embedding = if !scores.is_empty() {
let mut consensus = vec![0.0f32; self.config.dimensions];
let mut count = 0usize;
for session in &relevant_sessions {
for entry in &session.entries {
if convergent_stages.contains(&entry.stage) {
for (i, &v) in entry.embedding.iter().enumerate() {
if i < self.config.dimensions {
consensus[i] += v;
}
}
count += 1;
}
}
}
if count > 0 {
for v in &mut consensus {
*v /= count as f32;
}
Some(consensus)
} else {
None
}
} else {
None
};
// Sort convergent stages
convergent_stages.sort();
Ok(ConvergenceResult {
session_pairs,
scores,
convergent_stages,
consensus_embedding,
})
}
/// Get all embeddings for a session.
pub fn get_session_embeddings(&self, session_id: &str) -> CrvResult<Vec<CrvSessionEntry>> {
let session = self
.sessions
.get(session_id)
.ok_or_else(|| CrvError::SessionNotFound(session_id.to_string()))?;
Ok(session
.entries
.iter()
.map(|e| CrvSessionEntry {
session_id: session.id.clone(),
coordinate: session.coordinate.clone(),
stage: e.stage,
embedding: e.embedding.clone(),
metadata: e.metadata.clone(),
timestamp_ms: e.timestamp_ms,
})
.collect())
}
/// Get the number of entries in a session.
pub fn session_entry_count(&self, session_id: &str) -> usize {
self.sessions
.get(session_id)
.map(|s| s.entries.len())
.unwrap_or(0)
}
/// Get the number of active sessions.
pub fn session_count(&self) -> usize {
self.sessions.len()
}
/// Remove a session.
pub fn remove_session(&mut self, session_id: &str) -> bool {
self.sessions.remove(session_id).is_some()
}
/// Get access to the Stage I encoder for direct operations.
pub fn stage_i_encoder(&self) -> &StageIEncoder {
&self.stage_i
}
/// Get access to the Stage II encoder for direct operations.
pub fn stage_ii_encoder(&self) -> &StageIIEncoder {
&self.stage_ii
}
/// Get access to the Stage IV encoder for direct operations.
pub fn stage_iv_encoder(&self) -> &StageIVEncoder {
&self.stage_iv
}
/// Get access to the Stage V engine for direct operations.
pub fn stage_v_engine(&self) -> &StageVEngine {
&self.stage_v
}
/// Get access to the Stage VI modeler for direct operations.
pub fn stage_vi_modeler(&self) -> &StageVIModeler {
&self.stage_vi
}
/// Internal: add an entry to a session.
fn add_entry(
&mut self,
session_id: &str,
stage: u8,
embedding: Vec<f32>,
metadata: HashMap<String, serde_json::Value>,
) -> CrvResult<()> {
let session = self
.sessions
.get_mut(session_id)
.ok_or_else(|| CrvError::SessionNotFound(session_id.to_string()))?;
let entry_index = session.entries.iter().filter(|e| e.stage == stage).count();
session.entries.push(SessionEntry {
embedding,
stage,
entry_index,
metadata,
timestamp_ms: 0,
});
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> CrvConfig {
CrvConfig {
dimensions: 32,
convergence_threshold: 0.5,
..CrvConfig::default()
}
}
#[test]
fn test_session_creation() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "1234-5678".to_string())
.unwrap();
assert_eq!(manager.session_count(), 1);
assert_eq!(manager.session_entry_count("sess-1"), 0);
}
#[test]
fn test_add_stage_i() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "1234-5678".to_string())
.unwrap();
let data = StageIData {
stroke: vec![(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)],
spontaneous_descriptor: "angular".to_string(),
classification: GestaltType::Manmade,
confidence: 0.9,
};
let emb = manager.add_stage_i("sess-1", &data).unwrap();
assert_eq!(emb.len(), 32);
assert_eq!(manager.session_entry_count("sess-1"), 1);
}
#[test]
fn test_add_stage_ii() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "coord-1".to_string())
.unwrap();
let data = StageIIData {
impressions: vec![
(SensoryModality::Texture, "rough".to_string()),
(SensoryModality::Color, "gray".to_string()),
],
feature_vector: None,
};
let emb = manager.add_stage_ii("sess-1", &data).unwrap();
assert_eq!(emb.len(), 32);
}
#[test]
fn test_full_session_flow() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "coord-1".to_string())
.unwrap();
// Stage I
let s1 = StageIData {
stroke: vec![(0.0, 0.0), (1.0, 1.0), (2.0, 0.0)],
spontaneous_descriptor: "angular".to_string(),
classification: GestaltType::Manmade,
confidence: 0.9,
};
manager.add_stage_i("sess-1", &s1).unwrap();
// Stage II
let s2 = StageIIData {
impressions: vec![
(SensoryModality::Texture, "rough stone".to_string()),
(SensoryModality::Temperature, "cold".to_string()),
],
feature_vector: None,
};
manager.add_stage_ii("sess-1", &s2).unwrap();
// Stage IV
let s4 = StageIVData {
emotional_impact: vec![("solemn".to_string(), 0.6)],
tangibles: vec!["stone blocks".to_string()],
intangibles: vec!["ancient".to_string()],
aol_detections: vec![],
};
manager.add_stage_iv("sess-1", &s4).unwrap();
assert_eq!(manager.session_entry_count("sess-1"), 3);
// Get all entries
let entries = manager.get_session_embeddings("sess-1").unwrap();
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].stage, 1);
assert_eq!(entries[1].stage, 2);
assert_eq!(entries[2].stage, 4);
}
#[test]
fn test_duplicate_session() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "coord-1".to_string())
.unwrap();
let result = manager.create_session("sess-1".to_string(), "coord-2".to_string());
assert!(result.is_err());
}
#[test]
fn test_session_not_found() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
let s1 = StageIData {
stroke: vec![(0.0, 0.0), (1.0, 1.0)],
spontaneous_descriptor: "test".to_string(),
classification: GestaltType::Natural,
confidence: 0.5,
};
let result = manager.add_stage_i("nonexistent", &s1);
assert!(result.is_err());
}
#[test]
fn test_remove_session() {
let config = test_config();
let mut manager = CrvSessionManager::new(config);
manager
.create_session("sess-1".to_string(), "coord-1".to_string())
.unwrap();
assert_eq!(manager.session_count(), 1);
assert!(manager.remove_session("sess-1"));
assert_eq!(manager.session_count(), 0);
assert!(!manager.remove_session("sess-1"));
}
}