git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
554 lines
18 KiB
Rust
554 lines
18 KiB
Rust
//! # Application 4: Self-Stabilizing World Models
|
|
//!
|
|
//! The world model is allowed to update only if the global structure remains intact.
|
|
//!
|
|
//! ## What Breaks Today
|
|
//! World models drift until they are no longer useful.
|
|
//!
|
|
//! ## Exotic Effect
|
|
//! The model stops learning when the world becomes incoherent
|
|
//! instead of hallucinating structure.
|
|
//!
|
|
//! ## Critical For
|
|
//! - Always-on perception
|
|
//! - Autonomous exploration
|
|
//! - Robotics in unknown environments
|
|
|
|
use std::collections::HashMap;
|
|
|
|
/// A world model that refuses to learn incoherent updates
|
|
pub struct SelfStabilizingWorldModel {
|
|
/// Entities in the world
|
|
entities: HashMap<EntityId, Entity>,
|
|
|
|
/// Relationships between entities
|
|
relationships: Vec<Relationship>,
|
|
|
|
/// Physical laws the model believes
|
|
laws: Vec<PhysicalLaw>,
|
|
|
|
/// Current coherence of the model
|
|
coherence: f64,
|
|
|
|
/// History of coherence for trend detection
|
|
coherence_history: Vec<f64>,
|
|
|
|
/// Learning rate (decreases under low coherence)
|
|
base_learning_rate: f64,
|
|
|
|
/// Minimum coherence to allow updates
|
|
min_update_coherence: f64,
|
|
|
|
/// Updates that were rejected
|
|
rejected_updates: Vec<RejectedUpdate>,
|
|
}
|
|
|
|
type EntityId = u64;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Entity {
|
|
pub id: EntityId,
|
|
pub properties: HashMap<String, PropertyValue>,
|
|
pub position: Option<(f64, f64, f64)>,
|
|
pub last_observed: u64,
|
|
pub confidence: f64,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum PropertyValue {
|
|
Boolean(bool),
|
|
Number(f64),
|
|
String(String),
|
|
Vector(Vec<f64>),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Relationship {
|
|
pub subject: EntityId,
|
|
pub predicate: String,
|
|
pub object: EntityId,
|
|
pub confidence: f64,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PhysicalLaw {
|
|
pub name: String,
|
|
pub confidence: f64,
|
|
/// Number of observations supporting this law
|
|
pub support_count: u64,
|
|
/// Number of observations violating this law
|
|
pub violation_count: u64,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Observation {
|
|
pub entity_id: EntityId,
|
|
pub properties: HashMap<String, PropertyValue>,
|
|
pub position: Option<(f64, f64, f64)>,
|
|
pub timestamp: u64,
|
|
pub source_confidence: f64,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum UpdateResult {
|
|
/// Update applied successfully
|
|
Applied { coherence_change: f64 },
|
|
/// Update rejected to preserve coherence
|
|
Rejected { reason: RejectionReason },
|
|
/// Update partially applied with modifications
|
|
Modified { changes: Vec<String>, coherence_change: f64 },
|
|
/// Model entered "uncertain" mode - no updates allowed
|
|
Frozen { coherence: f64, threshold: f64 },
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RejectedUpdate {
|
|
pub observation: String,
|
|
pub reason: RejectionReason,
|
|
pub timestamp: u64,
|
|
pub coherence_at_rejection: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum RejectionReason {
|
|
/// Would violate established physical laws
|
|
ViolatesPhysicalLaw(String),
|
|
/// Would create logical contradiction
|
|
LogicalContradiction(String),
|
|
/// Would cause excessive coherence drop
|
|
ExcessiveCoherenceDrop { predicted: f64, threshold: f64 },
|
|
/// Source confidence too low for this change
|
|
InsufficientConfidence { required: f64, provided: f64 },
|
|
/// Model is in frozen state
|
|
ModelFrozen,
|
|
/// Would fragment world structure
|
|
StructuralFragmentation,
|
|
}
|
|
|
|
impl SelfStabilizingWorldModel {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
entities: HashMap::new(),
|
|
relationships: Vec::new(),
|
|
laws: vec![
|
|
PhysicalLaw {
|
|
name: "conservation_of_matter".to_string(),
|
|
confidence: 0.99,
|
|
support_count: 1000,
|
|
violation_count: 0,
|
|
},
|
|
PhysicalLaw {
|
|
name: "locality".to_string(),
|
|
confidence: 0.95,
|
|
support_count: 500,
|
|
violation_count: 5,
|
|
},
|
|
PhysicalLaw {
|
|
name: "temporal_consistency".to_string(),
|
|
confidence: 0.98,
|
|
support_count: 800,
|
|
violation_count: 2,
|
|
},
|
|
],
|
|
coherence: 1.0,
|
|
coherence_history: vec![1.0],
|
|
base_learning_rate: 0.1,
|
|
min_update_coherence: 0.4,
|
|
rejected_updates: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Current effective learning rate (decreases with low coherence)
|
|
pub fn effective_learning_rate(&self) -> f64 {
|
|
self.base_learning_rate * self.coherence.powi(2)
|
|
}
|
|
|
|
/// Is the model currently accepting updates?
|
|
pub fn is_learning(&self) -> bool {
|
|
self.coherence >= self.min_update_coherence
|
|
}
|
|
|
|
/// Attempt to integrate an observation into the world model
|
|
pub fn observe(&mut self, observation: Observation, timestamp: u64) -> UpdateResult {
|
|
// Check if model is frozen
|
|
if !self.is_learning() {
|
|
return UpdateResult::Frozen {
|
|
coherence: self.coherence,
|
|
threshold: self.min_update_coherence,
|
|
};
|
|
}
|
|
|
|
// Predict coherence impact
|
|
let predicted_coherence = self.predict_coherence_after(&observation);
|
|
|
|
// Would this drop coherence too much?
|
|
let coherence_drop = self.coherence - predicted_coherence;
|
|
if coherence_drop > 0.2 {
|
|
self.rejected_updates.push(RejectedUpdate {
|
|
observation: format!("Entity {} update", observation.entity_id),
|
|
reason: RejectionReason::ExcessiveCoherenceDrop {
|
|
predicted: predicted_coherence,
|
|
threshold: self.coherence - 0.2,
|
|
},
|
|
timestamp,
|
|
coherence_at_rejection: self.coherence,
|
|
});
|
|
return UpdateResult::Rejected {
|
|
reason: RejectionReason::ExcessiveCoherenceDrop {
|
|
predicted: predicted_coherence,
|
|
threshold: self.coherence - 0.2,
|
|
},
|
|
};
|
|
}
|
|
|
|
// Check physical law violations
|
|
if let Some(violation) = self.check_law_violations(&observation) {
|
|
self.rejected_updates.push(RejectedUpdate {
|
|
observation: format!("Entity {} update", observation.entity_id),
|
|
reason: violation.clone(),
|
|
timestamp,
|
|
coherence_at_rejection: self.coherence,
|
|
});
|
|
return UpdateResult::Rejected { reason: violation };
|
|
}
|
|
|
|
// Check logical consistency
|
|
if let Some(contradiction) = self.check_contradictions(&observation) {
|
|
self.rejected_updates.push(RejectedUpdate {
|
|
observation: format!("Entity {} update", observation.entity_id),
|
|
reason: contradiction.clone(),
|
|
timestamp,
|
|
coherence_at_rejection: self.coherence,
|
|
});
|
|
return UpdateResult::Rejected { reason: contradiction };
|
|
}
|
|
|
|
// Apply the update
|
|
self.apply_observation(observation, timestamp);
|
|
|
|
// Recalculate coherence
|
|
let old_coherence = self.coherence;
|
|
self.coherence = self.calculate_coherence();
|
|
self.coherence_history.push(self.coherence);
|
|
|
|
// Trim history
|
|
if self.coherence_history.len() > 100 {
|
|
self.coherence_history.remove(0);
|
|
}
|
|
|
|
UpdateResult::Applied {
|
|
coherence_change: self.coherence - old_coherence,
|
|
}
|
|
}
|
|
|
|
fn predict_coherence_after(&self, observation: &Observation) -> f64 {
|
|
// Simulate the update's impact on coherence
|
|
let mut consistency_score = 1.0;
|
|
|
|
if let Some(existing) = self.entities.get(&observation.entity_id) {
|
|
// How much does this differ from existing knowledge?
|
|
for (key, new_value) in &observation.properties {
|
|
if let Some(old_value) = existing.properties.get(key) {
|
|
let diff = self.property_difference(old_value, new_value);
|
|
consistency_score *= 1.0 - (diff * 0.5);
|
|
}
|
|
}
|
|
|
|
// Position change check (locality)
|
|
if let (Some(old_pos), Some(new_pos)) = (&existing.position, &observation.position) {
|
|
let distance = ((new_pos.0 - old_pos.0).powi(2)
|
|
+ (new_pos.1 - old_pos.1).powi(2)
|
|
+ (new_pos.2 - old_pos.2).powi(2))
|
|
.sqrt();
|
|
|
|
// Large sudden movements are suspicious
|
|
if distance > 10.0 {
|
|
consistency_score *= 0.7;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.coherence * consistency_score
|
|
}
|
|
|
|
fn property_difference(&self, old: &PropertyValue, new: &PropertyValue) -> f64 {
|
|
match (old, new) {
|
|
(PropertyValue::Number(a), PropertyValue::Number(b)) => {
|
|
let max = a.abs().max(b.abs()).max(1.0);
|
|
((a - b).abs() / max).min(1.0)
|
|
}
|
|
(PropertyValue::Boolean(a), PropertyValue::Boolean(b)) => {
|
|
if a == b { 0.0 } else { 1.0 }
|
|
}
|
|
(PropertyValue::String(a), PropertyValue::String(b)) => {
|
|
if a == b { 0.0 } else { 0.5 }
|
|
}
|
|
_ => 0.5, // Different types
|
|
}
|
|
}
|
|
|
|
fn check_law_violations(&self, observation: &Observation) -> Option<RejectionReason> {
|
|
if let Some(existing) = self.entities.get(&observation.entity_id) {
|
|
// Check locality violation (teleportation)
|
|
if let (Some(old_pos), Some(new_pos)) = (&existing.position, &observation.position) {
|
|
let distance = ((new_pos.0 - old_pos.0).powi(2)
|
|
+ (new_pos.1 - old_pos.1).powi(2)
|
|
+ (new_pos.2 - old_pos.2).powi(2))
|
|
.sqrt();
|
|
|
|
// If object moved impossibly fast
|
|
let max_speed = 100.0; // units per timestamp
|
|
if distance > max_speed {
|
|
return Some(RejectionReason::ViolatesPhysicalLaw(
|
|
format!("locality: object moved {} units instantaneously", distance)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn check_contradictions(&self, observation: &Observation) -> Option<RejectionReason> {
|
|
// Check for direct contradictions with high-confidence existing data
|
|
if let Some(existing) = self.entities.get(&observation.entity_id) {
|
|
if existing.confidence > 0.9 {
|
|
for (key, new_value) in &observation.properties {
|
|
if let Some(old_value) = existing.properties.get(key) {
|
|
let diff = self.property_difference(old_value, new_value);
|
|
if diff > 0.9 && observation.source_confidence < existing.confidence {
|
|
return Some(RejectionReason::LogicalContradiction(
|
|
format!("Property {} contradicts high-confidence existing data", key)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn apply_observation(&mut self, observation: Observation, timestamp: u64) {
|
|
let learning_rate = self.effective_learning_rate();
|
|
|
|
// Pre-compute blended values to avoid borrow conflict
|
|
let blended_properties: Vec<(String, PropertyValue)> = observation.properties
|
|
.into_iter()
|
|
.map(|(key, new_value)| {
|
|
let blended = if let Some(entity) = self.entities.get(&observation.entity_id) {
|
|
if let Some(old_value) = entity.properties.get(&key) {
|
|
self.blend_values(old_value, &new_value, learning_rate)
|
|
} else {
|
|
new_value
|
|
}
|
|
} else {
|
|
new_value
|
|
};
|
|
(key, blended)
|
|
})
|
|
.collect();
|
|
|
|
let entity = self.entities.entry(observation.entity_id).or_insert(Entity {
|
|
id: observation.entity_id,
|
|
properties: HashMap::new(),
|
|
position: None,
|
|
last_observed: 0,
|
|
confidence: 0.5,
|
|
});
|
|
|
|
// Apply pre-computed blended values
|
|
for (key, blended) in blended_properties {
|
|
entity.properties.insert(key, blended);
|
|
}
|
|
|
|
// Update position
|
|
if let Some(new_pos) = observation.position {
|
|
if let Some(old_pos) = entity.position {
|
|
// Smooth position update
|
|
entity.position = Some((
|
|
old_pos.0 + learning_rate * (new_pos.0 - old_pos.0),
|
|
old_pos.1 + learning_rate * (new_pos.1 - old_pos.1),
|
|
old_pos.2 + learning_rate * (new_pos.2 - old_pos.2),
|
|
));
|
|
} else {
|
|
entity.position = Some(new_pos);
|
|
}
|
|
}
|
|
|
|
entity.last_observed = timestamp;
|
|
// Update confidence
|
|
entity.confidence = entity.confidence * 0.9 + observation.source_confidence * 0.1;
|
|
}
|
|
|
|
fn blend_values(&self, old: &PropertyValue, new: &PropertyValue, rate: f64) -> PropertyValue {
|
|
match (old, new) {
|
|
(PropertyValue::Number(a), PropertyValue::Number(b)) => {
|
|
PropertyValue::Number(a + rate * (b - a))
|
|
}
|
|
_ => new.clone(), // For non-numeric, just use new if rate > 0.5
|
|
}
|
|
}
|
|
|
|
fn calculate_coherence(&self) -> f64 {
|
|
if self.entities.is_empty() {
|
|
return 1.0;
|
|
}
|
|
|
|
let mut scores = Vec::new();
|
|
|
|
// 1. Internal consistency of entities
|
|
for entity in self.entities.values() {
|
|
scores.push(entity.confidence);
|
|
}
|
|
|
|
// 2. Relationship consistency
|
|
for rel in &self.relationships {
|
|
if self.entities.contains_key(&rel.subject) && self.entities.contains_key(&rel.object) {
|
|
scores.push(rel.confidence);
|
|
} else {
|
|
scores.push(0.0); // Dangling relationship
|
|
}
|
|
}
|
|
|
|
// 3. Physical law confidence
|
|
for law in &self.laws {
|
|
scores.push(law.confidence);
|
|
}
|
|
|
|
// 4. Temporal coherence (recent observations should be consistent)
|
|
let recent_variance = self.calculate_recent_variance();
|
|
scores.push(1.0 - recent_variance);
|
|
|
|
// Geometric mean of all scores
|
|
if scores.is_empty() {
|
|
1.0
|
|
} else {
|
|
let product: f64 = scores.iter().product();
|
|
product.powf(1.0 / scores.len() as f64)
|
|
}
|
|
}
|
|
|
|
fn calculate_recent_variance(&self) -> f64 {
|
|
if self.coherence_history.len() < 2 {
|
|
return 0.0;
|
|
}
|
|
|
|
let recent: Vec<f64> = self.coherence_history.iter().rev().take(10).cloned().collect();
|
|
let mean: f64 = recent.iter().sum::<f64>() / recent.len() as f64;
|
|
let variance: f64 = recent.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / recent.len() as f64;
|
|
variance.sqrt().min(1.0)
|
|
}
|
|
|
|
/// Get count of rejected updates
|
|
pub fn rejection_count(&self) -> usize {
|
|
self.rejected_updates.len()
|
|
}
|
|
|
|
/// Get model status
|
|
pub fn status(&self) -> String {
|
|
format!(
|
|
"WorldModel | Coherence: {:.3} | Entities: {} | Learning: {} | Rejections: {}",
|
|
self.coherence,
|
|
self.entities.len(),
|
|
if self.is_learning() { "ON" } else { "FROZEN" },
|
|
self.rejected_updates.len()
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_coherent_learning() {
|
|
let mut model = SelfStabilizingWorldModel::new();
|
|
|
|
// Feed consistent observations
|
|
for i in 0..10 {
|
|
let obs = Observation {
|
|
entity_id: 1,
|
|
properties: [("temperature".to_string(), PropertyValue::Number(20.0 + i as f64 * 0.1))].into(),
|
|
position: Some((i as f64, 0.0, 0.0)),
|
|
timestamp: i as u64,
|
|
source_confidence: 0.9,
|
|
};
|
|
|
|
let result = model.observe(obs, i as u64);
|
|
assert!(matches!(result, UpdateResult::Applied { .. }));
|
|
}
|
|
|
|
println!("{}", model.status());
|
|
assert!(model.coherence > 0.8);
|
|
}
|
|
|
|
#[test]
|
|
fn test_rejects_incoherent_update() {
|
|
let mut model = SelfStabilizingWorldModel::new();
|
|
|
|
// Establish entity at position
|
|
let obs1 = Observation {
|
|
entity_id: 1,
|
|
properties: HashMap::new(),
|
|
position: Some((0.0, 0.0, 0.0)),
|
|
timestamp: 0,
|
|
source_confidence: 0.95,
|
|
};
|
|
model.observe(obs1, 0);
|
|
|
|
// Try to teleport it (violates locality)
|
|
let obs2 = Observation {
|
|
entity_id: 1,
|
|
properties: HashMap::new(),
|
|
position: Some((1000.0, 0.0, 0.0)), // Impossibly far
|
|
timestamp: 1,
|
|
source_confidence: 0.5,
|
|
};
|
|
|
|
let result = model.observe(obs2, 1);
|
|
println!("Teleport result: {:?}", result);
|
|
|
|
// Should be rejected
|
|
assert!(matches!(result, UpdateResult::Rejected { .. }));
|
|
println!("{}", model.status());
|
|
}
|
|
|
|
#[test]
|
|
fn test_freezes_under_chaos() {
|
|
let mut model = SelfStabilizingWorldModel::new();
|
|
|
|
// Feed chaotic, contradictory observations
|
|
for i in 0..100 {
|
|
let obs = Observation {
|
|
entity_id: (i % 5) as u64,
|
|
properties: [
|
|
("value".to_string(), PropertyValue::Number(if i % 2 == 0 { 100.0 } else { -100.0 }))
|
|
].into(),
|
|
position: Some((
|
|
(i as f64 * 10.0) % 50.0 - 25.0,
|
|
(i as f64 * 7.0) % 50.0 - 25.0,
|
|
0.0
|
|
)),
|
|
timestamp: i as u64,
|
|
source_confidence: 0.3,
|
|
};
|
|
|
|
let result = model.observe(obs, i as u64);
|
|
|
|
if matches!(result, UpdateResult::Frozen { .. }) {
|
|
println!("Model FROZE at step {} - stopped hallucinating!", i);
|
|
println!("{}", model.status());
|
|
return; // Test passes - model stopped itself
|
|
}
|
|
}
|
|
|
|
println!("Final: {}", model.status());
|
|
// Model should have either frozen or heavily rejected updates
|
|
assert!(
|
|
model.rejection_count() > 20 || model.coherence < 0.5,
|
|
"Model should resist chaotic input"
|
|
);
|
|
}
|
|
}
|