479 lines
17 KiB
Rust
479 lines
17 KiB
Rust
//! Causal Atlas with Coherence Field and Boundary Tracking
|
|
//!
|
|
//! Demonstrates ADR-040 constructs 1-4:
|
|
//! - Windowing: Multi-scale light-curve windows (2h, 12h, 3d, 27d)
|
|
//! - Feature extraction: Flux derivative stats, autocorrelation peaks
|
|
//! - Embedding: Window embeddings stored via ingest_batch()
|
|
//! - Causal edges: Interaction graph with causal/periodicity/shape_similarity types
|
|
//! - Coherence field: Cut pressure and partition entropy over graph subsets
|
|
//! - Boundary tracking: Boundary evolution with alert emission
|
|
//! - Multi-scale memory: S/M/L tier retention with metadata tags
|
|
//! - Witness chain: Every construction step recorded
|
|
//!
|
|
//! RVF segments used: VEC_SEG, MANIFEST_SEG, WITNESS_SEG
|
|
//!
|
|
//! Run: cargo run --example causal_atlas
|
|
|
|
use rvf_runtime::{
|
|
FilterExpr, MetadataEntry, MetadataValue, QueryOptions, RvfOptions, RvfStore, SearchResult,
|
|
};
|
|
use rvf_runtime::filter::FilterValue;
|
|
use rvf_runtime::options::DistanceMetric;
|
|
use rvf_crypto::{create_witness_chain, verify_witness_chain, shake256_256, WitnessEntry};
|
|
use tempfile::TempDir;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// LCG pseudo-random helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn lcg_next(state: &mut u64) -> u64 {
|
|
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
|
*state
|
|
}
|
|
|
|
fn random_vector(dim: usize, seed: u64) -> Vec<f32> {
|
|
let mut v = Vec::with_capacity(dim);
|
|
let mut x = seed.wrapping_add(1);
|
|
for _ in 0..dim {
|
|
x = x.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
|
v.push(((x >> 33) as f32) / (u32::MAX as f32) - 0.5);
|
|
}
|
|
v
|
|
}
|
|
|
|
fn lcg_f64(state: &mut u64) -> f64 {
|
|
lcg_next(state);
|
|
(*state >> 11) as f64 / ((1u64 << 53) as f64)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ADR-040 domain types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[allow(dead_code)]
|
|
struct LightCurveWindow {
|
|
target_id: u64,
|
|
scale: &'static str,
|
|
window_start_epoch: u64,
|
|
flux_mean: f64,
|
|
flux_std: f64,
|
|
derivative_mean: f64,
|
|
autocorr_peak: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct InteractionEdge {
|
|
source: u64,
|
|
target: u64,
|
|
edge_type: &'static str,
|
|
weight: f64,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct CoherenceField {
|
|
cut_pressure: f64,
|
|
partition_entropy: f64,
|
|
num_partitions: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Boundary {
|
|
epoch: u64,
|
|
pressure: f64,
|
|
alert: bool,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Synthetic data generators
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn generate_light_curve_windows(target_id: u64, seed: u64) -> Vec<LightCurveWindow> {
|
|
let scales: &[(&str, u64)] = &[
|
|
("2h", 7200),
|
|
("12h", 43200),
|
|
("3d", 259200),
|
|
("27d", 2332800),
|
|
];
|
|
let mut windows = Vec::new();
|
|
let mut rng = seed.wrapping_add(target_id * 997);
|
|
|
|
for &(scale_name, duration) in scales {
|
|
let num_windows = match scale_name {
|
|
"2h" => 8,
|
|
"12h" => 4,
|
|
"3d" => 2,
|
|
_ => 1,
|
|
};
|
|
for w in 0..num_windows {
|
|
let epoch = 1_600_000_000 + w * duration;
|
|
let flux_mean = 1.0 + lcg_f64(&mut rng) * 0.01;
|
|
let flux_std = 0.001 + lcg_f64(&mut rng) * 0.005;
|
|
let derivative_mean = (lcg_f64(&mut rng) - 0.5) * 0.002;
|
|
let autocorr_peak = lcg_f64(&mut rng) * 0.95;
|
|
|
|
windows.push(LightCurveWindow {
|
|
target_id,
|
|
scale: scale_name,
|
|
window_start_epoch: epoch,
|
|
flux_mean,
|
|
flux_std,
|
|
derivative_mean,
|
|
autocorr_peak,
|
|
});
|
|
}
|
|
}
|
|
windows
|
|
}
|
|
|
|
fn build_causal_edges(windows: &[LightCurveWindow]) -> Vec<InteractionEdge> {
|
|
let mut edges = Vec::new();
|
|
let mut rng: u64 = 0xCAFE_BABE;
|
|
let edge_types = ["causal", "periodicity", "shape_similarity"];
|
|
|
|
for i in 0..windows.len() {
|
|
for j in (i + 1)..windows.len() {
|
|
if windows[i].target_id != windows[j].target_id {
|
|
continue;
|
|
}
|
|
// Connect windows that are temporally adjacent at different scales
|
|
let same_scale = windows[i].scale == windows[j].scale;
|
|
let autocorr_close =
|
|
(windows[i].autocorr_peak - windows[j].autocorr_peak).abs() < 0.3;
|
|
|
|
if same_scale || autocorr_close {
|
|
let etype_idx = (lcg_next(&mut rng) >> 33) as usize % edge_types.len();
|
|
let weight = 0.5 + lcg_f64(&mut rng) * 0.5;
|
|
edges.push(InteractionEdge {
|
|
source: i as u64,
|
|
target: j as u64,
|
|
edge_type: edge_types[etype_idx],
|
|
weight,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
edges
|
|
}
|
|
|
|
fn compute_coherence(edges: &[InteractionEdge], num_nodes: usize) -> CoherenceField {
|
|
// Simulate cut pressure: average edge weight across boundaries
|
|
let total_weight: f64 = edges.iter().map(|e| e.weight).sum();
|
|
let cut_pressure = if edges.is_empty() {
|
|
0.0
|
|
} else {
|
|
total_weight / edges.len() as f64
|
|
};
|
|
|
|
// Partition entropy: simple degree-based partitioning
|
|
let mut degrees = vec![0u32; num_nodes];
|
|
for e in edges {
|
|
if (e.source as usize) < num_nodes {
|
|
degrees[e.source as usize] += 1;
|
|
}
|
|
if (e.target as usize) < num_nodes {
|
|
degrees[e.target as usize] += 1;
|
|
}
|
|
}
|
|
|
|
let num_partitions = 3; // S/M/L tiers
|
|
let total_degree: f64 = degrees.iter().map(|&d| d as f64).sum();
|
|
let mut entropy = 0.0;
|
|
if total_degree > 0.0 {
|
|
// Compute entropy of degree distribution across partitions
|
|
let partition_size = (num_nodes + num_partitions - 1) / num_partitions;
|
|
for p in 0..num_partitions {
|
|
let start = p * partition_size;
|
|
let end = (start + partition_size).min(num_nodes);
|
|
let part_deg: f64 = degrees[start..end].iter().map(|&d| d as f64).sum();
|
|
let prob = part_deg / total_degree;
|
|
if prob > 0.0 {
|
|
entropy -= prob * prob.ln();
|
|
}
|
|
}
|
|
}
|
|
|
|
CoherenceField {
|
|
cut_pressure,
|
|
partition_entropy: entropy,
|
|
num_partitions,
|
|
}
|
|
}
|
|
|
|
fn track_boundaries(edges: &[InteractionEdge], num_epochs: usize) -> Vec<Boundary> {
|
|
let alert_threshold = 0.8;
|
|
let mut boundaries = Vec::new();
|
|
let mut rng: u64 = 0xDEAD;
|
|
|
|
for epoch in 0..num_epochs {
|
|
let pressure = 0.5 + lcg_f64(&mut rng) * 0.4
|
|
+ if epoch > num_epochs / 2 { 0.15 } else { 0.0 };
|
|
let _ = edges; // edges inform the pressure in a real implementation
|
|
boundaries.push(Boundary {
|
|
epoch: epoch as u64,
|
|
pressure,
|
|
alert: pressure > alert_threshold,
|
|
});
|
|
}
|
|
boundaries
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn main() {
|
|
println!("=== Causal Atlas with Coherence Field ===\n");
|
|
|
|
let dim = 64;
|
|
let num_targets = 5;
|
|
let domains = ["transit", "flare", "rotation", "eclipse", "variability"];
|
|
let scales_list = ["2h", "12h", "3d", "27d"];
|
|
|
|
// ====================================================================
|
|
// 1. Generate multi-scale light-curve windows
|
|
// ====================================================================
|
|
println!("--- 1. Multi-Scale Windowing ---");
|
|
|
|
let mut all_windows: Vec<LightCurveWindow> = Vec::new();
|
|
for target_id in 0..num_targets as u64 {
|
|
let windows = generate_light_curve_windows(target_id, 42);
|
|
all_windows.extend(windows);
|
|
}
|
|
|
|
println!(" Targets: {}", num_targets);
|
|
println!(" Scales: {:?}", scales_list);
|
|
println!(" Windows: {} total", all_windows.len());
|
|
|
|
for scale in &scales_list {
|
|
let count = all_windows.iter().filter(|w| w.scale == *scale).count();
|
|
println!(" {}: {} windows", scale, count);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 2. Feature extraction and embedding
|
|
// ====================================================================
|
|
println!("\n--- 2. Feature Extraction & Embedding ---");
|
|
|
|
let tmp_dir = TempDir::new().expect("failed to create temp dir");
|
|
let store_path = tmp_dir.path().join("causal_atlas.rvf");
|
|
|
|
let options = RvfOptions {
|
|
dimension: dim as u16,
|
|
metric: DistanceMetric::L2,
|
|
..Default::default()
|
|
};
|
|
|
|
let mut store = RvfStore::create(&store_path, options).expect("failed to create store");
|
|
|
|
let num_windows = all_windows.len();
|
|
let vectors: Vec<Vec<f32>> = (0..num_windows)
|
|
.map(|i| {
|
|
let w = &all_windows[i];
|
|
// Embed using window features as seed perturbation
|
|
let seed = i as u64 * 31 + w.window_start_epoch;
|
|
random_vector(dim, seed)
|
|
})
|
|
.collect();
|
|
let vec_refs: Vec<&[f32]> = vectors.iter().map(|v| v.as_slice()).collect();
|
|
let ids: Vec<u64> = (0..num_windows as u64).collect();
|
|
|
|
// Metadata: domain (0), scale (1), target_id (2), window_start_epoch (3)
|
|
let mut metadata = Vec::with_capacity(num_windows * 4);
|
|
for (i, w) in all_windows.iter().enumerate() {
|
|
let domain_idx = w.target_id as usize % domains.len();
|
|
metadata.push(MetadataEntry {
|
|
field_id: 0,
|
|
value: MetadataValue::String(domains[domain_idx].to_string()),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 1,
|
|
value: MetadataValue::String(w.scale.to_string()),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 2,
|
|
value: MetadataValue::U64(w.target_id),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 3,
|
|
value: MetadataValue::U64(w.window_start_epoch),
|
|
});
|
|
let _ = i;
|
|
}
|
|
|
|
let ingest = store
|
|
.ingest_batch(&vec_refs, &ids, Some(&metadata))
|
|
.expect("ingest failed");
|
|
println!(" Embeddings: {} ingested ({} dims)", ingest.accepted, dim);
|
|
println!(" Features per window: flux_mean, flux_std, derivative_mean, autocorr_peak");
|
|
|
|
println!("\n Sample windows:");
|
|
for w in all_windows.iter().take(5) {
|
|
println!(
|
|
" target={} scale={:>3} epoch={} flux={:.6} autocorr={:.4}",
|
|
w.target_id, w.scale, w.window_start_epoch, w.flux_mean, w.autocorr_peak
|
|
);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 3. Build causal edge graph
|
|
// ====================================================================
|
|
println!("\n--- 3. Causal Edge Graph ---");
|
|
|
|
let edges = build_causal_edges(&all_windows);
|
|
|
|
let causal_count = edges.iter().filter(|e| e.edge_type == "causal").count();
|
|
let period_count = edges.iter().filter(|e| e.edge_type == "periodicity").count();
|
|
let shape_count = edges.iter().filter(|e| e.edge_type == "shape_similarity").count();
|
|
|
|
println!(" Total edges: {}", edges.len());
|
|
println!(" causal: {}", causal_count);
|
|
println!(" periodicity: {}", period_count);
|
|
println!(" shape_similarity: {}", shape_count);
|
|
|
|
println!("\n Sample edges:");
|
|
for e in edges.iter().take(5) {
|
|
println!(
|
|
" {} -> {} [{}] weight={:.4}",
|
|
e.source, e.target, e.edge_type, e.weight
|
|
);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 4. Coherence field computation
|
|
// ====================================================================
|
|
println!("\n--- 4. Coherence Field ---");
|
|
|
|
let coherence = compute_coherence(&edges, num_windows);
|
|
println!(" Cut pressure: {:.6}", coherence.cut_pressure);
|
|
println!(" Partition entropy: {:.6}", coherence.partition_entropy);
|
|
println!(" Partitions (S/M/L): {}", coherence.num_partitions);
|
|
|
|
// ====================================================================
|
|
// 5. Boundary tracking
|
|
// ====================================================================
|
|
println!("\n--- 5. Boundary Tracking ---");
|
|
|
|
let boundaries = track_boundaries(&edges, 10);
|
|
let alert_count = boundaries.iter().filter(|b| b.alert).count();
|
|
|
|
println!(" Tracked epochs: {}", boundaries.len());
|
|
println!(" Alerts fired: {}", alert_count);
|
|
|
|
println!("\n Boundary evolution:");
|
|
for b in &boundaries {
|
|
let marker = if b.alert { " ** ALERT **" } else { "" };
|
|
println!(" epoch={} pressure={:.4}{}", b.epoch, b.pressure, marker);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 6. Multi-scale memory tiers
|
|
// ====================================================================
|
|
println!("\n--- 6. Multi-Scale Memory (S/M/L Tiers) ---");
|
|
|
|
// Query by scale to demonstrate tier-based retention
|
|
let tiers = [
|
|
("S (short)", "2h"),
|
|
("M (medium)", "12h"),
|
|
("L (long)", "27d"),
|
|
];
|
|
|
|
let query_vec = random_vector(dim, 77);
|
|
for (tier_name, scale) in &tiers {
|
|
let filter = FilterExpr::Eq(1, FilterValue::String(scale.to_string()));
|
|
let opts = QueryOptions {
|
|
filter: Some(filter),
|
|
..Default::default()
|
|
};
|
|
let results = store
|
|
.query(&query_vec, 5, &opts)
|
|
.expect("filtered query failed");
|
|
println!(" Tier {} [scale={}]: {} results", tier_name, scale, results.len());
|
|
print_atlas_results(&results, &all_windows);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 7. Witness chain
|
|
// ====================================================================
|
|
println!("\n--- 7. Witness Chain ---");
|
|
|
|
let chain_steps = [
|
|
("genesis", 0x01u8),
|
|
("windowing", 0x02),
|
|
("feature_extraction", 0x02),
|
|
("embedding_ingest", 0x08),
|
|
("edge_construction", 0x02),
|
|
("coherence_compute", 0x02),
|
|
("boundary_tracking", 0x02),
|
|
("tier_assignment", 0x08),
|
|
("validation", 0x02),
|
|
("atlas_seal", 0x01),
|
|
];
|
|
|
|
let entries: Vec<WitnessEntry> = chain_steps
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, (step, wtype))| {
|
|
let action_data = format!("causal_atlas:{}:step_{}", step, i);
|
|
WitnessEntry {
|
|
prev_hash: [0u8; 32],
|
|
action_hash: shake256_256(action_data.as_bytes()),
|
|
timestamp_ns: 1_700_000_000_000_000_000 + i as u64 * 1_000_000_000,
|
|
witness_type: *wtype,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let chain_bytes = create_witness_chain(&entries);
|
|
let verified = verify_witness_chain(&chain_bytes).expect("chain verification failed");
|
|
|
|
println!(" Chain entries: {}", verified.len());
|
|
println!(" Chain size: {} bytes", chain_bytes.len());
|
|
println!(" Integrity: VALID");
|
|
|
|
println!("\n Construction steps:");
|
|
for (i, (step, _)) in chain_steps.iter().enumerate() {
|
|
let wtype_name = match verified[i].witness_type {
|
|
0x01 => "PROV",
|
|
0x02 => "COMP",
|
|
0x05 => "ATTS",
|
|
0x08 => "DATA",
|
|
_ => "????",
|
|
};
|
|
println!(" [{:>4}] {} -> {}", wtype_name, i, step);
|
|
}
|
|
|
|
// ====================================================================
|
|
// Summary
|
|
// ====================================================================
|
|
println!("\n=== Causal Atlas Summary ===\n");
|
|
println!(" Targets: {}", num_targets);
|
|
println!(" Windows: {}", num_windows);
|
|
println!(" Embedding dims: {}", dim);
|
|
println!(" Causal edges: {}", edges.len());
|
|
println!(" Cut pressure: {:.6}", coherence.cut_pressure);
|
|
println!(" Partition entropy: {:.6}", coherence.partition_entropy);
|
|
println!(" Boundary alerts: {}/{}", alert_count, boundaries.len());
|
|
println!(" Witness entries: {}", verified.len());
|
|
|
|
store.close().expect("failed to close store");
|
|
println!("\nDone.");
|
|
}
|
|
|
|
fn print_atlas_results(results: &[SearchResult], windows: &[LightCurveWindow]) {
|
|
if results.is_empty() {
|
|
println!(" (no results)");
|
|
return;
|
|
}
|
|
for r in results.iter().take(3) {
|
|
let idx = r.id as usize;
|
|
if idx < windows.len() {
|
|
let w = &windows[idx];
|
|
println!(
|
|
" id={:>3} dist={:.6} target={} scale={} epoch={}",
|
|
r.id, r.distance, w.target_id, w.scale, w.window_start_epoch
|
|
);
|
|
}
|
|
}
|
|
}
|