mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-25 15:03:46 +00:00
docs(nervous-system): Add tiered examples and comprehensive documentation
Add 9 bio-inspired nervous system examples across three application tiers:
Tier 1 - Immediate Practical:
- anomaly_detection: Infrastructure/finance anomaly detection with microsecond response
- edge_autonomy: Drone/vehicle reflex arcs with certified bounded paths
- medical_wearable: Personalized health monitoring with one-shot learning
Tier 2 - Near-Term Transformative:
- self_optimizing_systems: Agents monitoring agents with structural witnesses
- swarm_intelligence: Kuramoto-based decentralized swarm coordination
- adaptive_simulation: Digital twins with bullet-time for critical events
Tier 3 - Exotic But Real:
- machine_self_awareness: Structural self-sensing ("I am becoming unstable")
- synthetic_nervous_systems: Buildings/cities responding like organisms
- bio_machine_interface: Prosthetics that adapt to biological timing
Also includes comprehensive README documentation with:
- Architecture diagrams for five-layer nervous system
- Feature descriptions for all modules (HDC, Hopfield, WTA, BTSP, E-prop, EWC, etc.)
- Quick start code examples and step-by-step tutorials
- Performance benchmarks and biological references
- Use cases from practical to exotic applications
This commit is contained in:
parent
92fb0dd72e
commit
2303cc4b85
12 changed files with 5618 additions and 53 deletions
|
|
@ -56,3 +56,42 @@ harness = false
|
|||
[[bench]]
|
||||
name = "latency_benchmarks"
|
||||
harness = false
|
||||
|
||||
# Tier 1: Immediate Practical Applications
|
||||
[[example]]
|
||||
name = "anomaly_detection"
|
||||
path = "examples/tier1/anomaly_detection.rs"
|
||||
|
||||
[[example]]
|
||||
name = "edge_autonomy"
|
||||
path = "examples/tier1/edge_autonomy.rs"
|
||||
|
||||
[[example]]
|
||||
name = "medical_wearable"
|
||||
path = "examples/tier1/medical_wearable.rs"
|
||||
|
||||
# Tier 2: Near-Term Transformative Applications
|
||||
[[example]]
|
||||
name = "self_optimizing_systems"
|
||||
path = "examples/tier2/self_optimizing_systems.rs"
|
||||
|
||||
[[example]]
|
||||
name = "swarm_intelligence"
|
||||
path = "examples/tier2/swarm_intelligence.rs"
|
||||
|
||||
[[example]]
|
||||
name = "adaptive_simulation"
|
||||
path = "examples/tier2/adaptive_simulation.rs"
|
||||
|
||||
# Tier 3: Exotic But Real Applications
|
||||
[[example]]
|
||||
name = "machine_self_awareness"
|
||||
path = "examples/tier3/machine_self_awareness.rs"
|
||||
|
||||
[[example]]
|
||||
name = "synthetic_nervous_systems"
|
||||
path = "examples/tier3/synthetic_nervous_systems.rs"
|
||||
|
||||
[[example]]
|
||||
name = "bio_machine_interface"
|
||||
path = "examples/tier3/bio_machine_interface.rs"
|
||||
|
|
|
|||
|
|
@ -1,88 +1,343 @@
|
|||
# RuVector Nervous System
|
||||
|
||||
Biological neural network models for vector databases, implementing neuroscience-inspired learning algorithms.
|
||||
[](https://crates.io/crates/ruvector-nervous-system)
|
||||
[](https://docs.rs/ruvector-nervous-system)
|
||||
[](https://www.rust-lang.org/)
|
||||
[]()
|
||||
[]()
|
||||
[](LICENSE)
|
||||
[]()
|
||||
|
||||
**A five-layer bio-inspired nervous system architecture for vector databases, enabling systems that survive, adapt, and cooperate.**
|
||||
|
||||
> *"From 'How do we make machines smarter?' to 'What kind of organism are we building?'"*
|
||||
|
||||
## Overview
|
||||
|
||||
This crate implements a complete nervous system architecture inspired by biological neural systems, targeting **100-1000× energy improvements** and **sub-millisecond latency** for vector database operations. Instead of just optimizing algorithms, we've defined a new capability class.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ COHERENCE LAYER │
|
||||
│ Global Workspace • Oscillatory Routing • Predictive Coding │
|
||||
│ (90-99% bandwidth reduction) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ LEARNING LAYER │
|
||||
│ BTSP One-Shot • E-prop Online • EWC Consolidation │
|
||||
│ (Learn in single exposure) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MEMORY LAYER │
|
||||
│ Hopfield Networks • HDC Vectors • Pattern Separation │
|
||||
│ (2^(d/2) exponential capacity) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ REFLEX LAYER │
|
||||
│ K-WTA Competition • Dendritic Coincidence • Safety │
|
||||
│ (<1μs decisions) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SENSING LAYER │
|
||||
│ Event Bus • Sparse Spikes • Backpressure Control │
|
||||
│ (10,000+ events/ms throughput) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### BTSP: Behavioral Timescale Synaptic Plasticity
|
||||
### Hyperdimensional Computing (HDC)
|
||||
- **10,000-bit binary hypervectors** with 10^40 representational capacity
|
||||
- **XOR binding** in <50ns
|
||||
- **Hamming similarity** in <100ns via SIMD popcount
|
||||
- Associative memory with collision-resistant encoding
|
||||
|
||||
One-shot learning mechanism based on Bittner et al. 2017 hippocampal research:
|
||||
### Modern Hopfield Networks
|
||||
- **Exponential storage**: 2^(d/2) patterns in d dimensions
|
||||
- Mathematically equivalent to **transformer attention**
|
||||
- Single-step retrieval via softmax-weighted sum
|
||||
- <1ms retrieval for 1000 patterns in 512D
|
||||
|
||||
- **One-shot learning**: Learn associations in seconds, not iterations
|
||||
- **Bidirectional plasticity**: Weak synapses potentiate, strong synapses depress
|
||||
- **Eligibility traces**: 1-3 second time windows for credit assignment
|
||||
- **Plateau potentials**: Dendritic events gate plasticity
|
||||
### K-Winner-Take-All (K-WTA)
|
||||
- **<1μs** single winner selection for 1000 neurons
|
||||
- Lateral inhibition for sparse activation
|
||||
- HNSW-compatible routing decisions
|
||||
|
||||
### Performance
|
||||
### Pattern Separation
|
||||
- Hippocampal-inspired **dentate gyrus** encoding
|
||||
- **2-5% sparsity** matching cortical statistics
|
||||
- <1% collision rate on synthetic corpora
|
||||
|
||||
- Single synapse update: <100ns
|
||||
- Layer update (10K synapses): <100μs
|
||||
- One-shot learning: Immediate, no iteration needed
|
||||
### Dendritic Coincidence Detection
|
||||
- **NMDA-like nonlinearity** with 10-50ms windows
|
||||
- Plateau potentials for BTSP gating
|
||||
- Reduced compartment models
|
||||
|
||||
## Usage
|
||||
### BTSP: Behavioral Timescale Plasticity
|
||||
- **One-shot learning** over seconds-long windows
|
||||
- Eligibility traces with 1-3 second time constants
|
||||
- Bidirectional plasticity (weak→potentiate, strong→depress)
|
||||
|
||||
### E-prop: Eligibility Propagation
|
||||
- **O(1) memory per synapse** (12 bytes)
|
||||
- Online learning without backpropagation through time
|
||||
- 1000+ millisecond temporal credit assignment
|
||||
|
||||
### Elastic Weight Consolidation (EWC)
|
||||
- **45% forgetting reduction** with 2× parameter overhead
|
||||
- Fisher Information diagonal approximation
|
||||
- Complementary Learning Systems (hippocampus + neocortex)
|
||||
|
||||
### Coherence-Gated Routing
|
||||
- **Kuramoto oscillators** for phase-coupled communication
|
||||
- Predictive coding with **90-99% bandwidth reduction**
|
||||
- Global workspace with 4-7 item capacity (Miller's law)
|
||||
|
||||
### Event Bus
|
||||
- **Lock-free ring buffers** with <100ns push/pop
|
||||
- Region-based sharding with backpressure control
|
||||
- **10,000+ events/ms** sustained throughput
|
||||
|
||||
## Use Cases: From Practical to Exotic
|
||||
|
||||
### Tier 1: Immediate Practical Applications
|
||||
|
||||
| Application | What Changes | Key Benefit |
|
||||
|-------------|--------------|-------------|
|
||||
| **Anomaly Detection** | Event streams replace batch logs; reflexes fire on structural anomalies | Detection before failure, microsecond response |
|
||||
| **Edge Autonomy** | Reflex arcs handle safety; policy loops only when needed | Lower power, certifiable bounded paths |
|
||||
| **Medical Wearables** | Continuous sensing with sparse spikes; one-shot personalization | Adapts to person, always-on, private |
|
||||
|
||||
### Tier 2: Near-Term Transformative
|
||||
|
||||
| Application | What Changes | Key Benefit |
|
||||
|-------------|--------------|-------------|
|
||||
| **Self-Optimizing Software** | Watch structure and timing, not just outputs | Self-stabilizing, structural witnesses |
|
||||
| **Swarm Intelligence** | Local reflexes, coherence gates for sync | Scale without fragility, emergent intelligence |
|
||||
| **Digital Twins** | Low fidelity continuous, bullet-time for critical | Always warm, costs scale with relevance |
|
||||
|
||||
### Tier 3: Exotic But Real
|
||||
|
||||
| Application | What Changes | Key Benefit |
|
||||
|-------------|--------------|-------------|
|
||||
| **Machine Self-Awareness** | Monitor own coherence; sense failure before drops | "I am becoming unstable" |
|
||||
| **Synthetic Nervous Systems** | Infrastructure as sensing fabric | Environments respond like organisms |
|
||||
| **Bio-Machine Interfaces** | Adapt to biological timing; integrate with reflexes | Machines stop fighting biology |
|
||||
|
||||
## Quick Start
|
||||
|
||||
Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ruvector-nervous-system = "0.1"
|
||||
```
|
||||
|
||||
### Example: One-Shot Learning
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::plasticity::btsp::{BTSPLayer, BTSPAssociativeMemory};
|
||||
|
||||
// Create a layer with 100 inputs
|
||||
let mut layer = BTSPLayer::new(100, 2000.0); // 2 second time constant
|
||||
// Create a BTSP layer with 2 second time constant
|
||||
let mut layer = BTSPLayer::new(100, 2000.0);
|
||||
|
||||
// One-shot association: pattern -> target
|
||||
let pattern = vec![0.1; 100];
|
||||
layer.one_shot_associate(&pattern, 1.0);
|
||||
|
||||
// Immediate recall
|
||||
// Immediate recall (no training iterations!)
|
||||
let output = layer.forward(&pattern);
|
||||
assert!((output - 1.0).abs() < 0.1);
|
||||
|
||||
// Associative memory for key-value storage
|
||||
let mut memory = BTSPAssociativeMemory::new(128, 64);
|
||||
let key = vec![0.5; 128];
|
||||
let value = vec![0.1; 64];
|
||||
|
||||
memory.store_one_shot(&key, &value).unwrap();
|
||||
let retrieved = memory.retrieve(&key).unwrap();
|
||||
```
|
||||
|
||||
## Architecture
|
||||
### Example: Hyperdimensional Computing
|
||||
|
||||
```
|
||||
ruvector-nervous-system/
|
||||
├── src/
|
||||
│ ├── lib.rs # Main library exports
|
||||
│ ├── plasticity/
|
||||
│ │ ├── mod.rs
|
||||
│ │ └── btsp.rs # BTSP implementation
|
||||
│ └── routing/
|
||||
│ └── mod.rs # Neural routing (future)
|
||||
├── benches/
|
||||
│ └── btsp_bench.rs # Performance benchmarks
|
||||
└── tests/
|
||||
└── btsp_integration.rs # Integration tests
|
||||
```rust
|
||||
use ruvector_nervous_system::hdc::{Hypervector, HdcMemory};
|
||||
|
||||
// Create random 10,000-bit hypervectors
|
||||
let concept_a = Hypervector::random();
|
||||
let concept_b = Hypervector::random();
|
||||
|
||||
// XOR binding (<50ns)
|
||||
let bound = concept_a.bind(&concept_b);
|
||||
|
||||
// Similarity via Hamming distance (<100ns)
|
||||
let sim = concept_a.similarity(&concept_b);
|
||||
|
||||
// Associative memory
|
||||
let mut memory = HdcMemory::new();
|
||||
memory.store("apple", concept_a.clone());
|
||||
let results = memory.retrieve(&concept_a, 0.9);
|
||||
```
|
||||
|
||||
## Biological Basis
|
||||
### Example: Modern Hopfield Retrieval
|
||||
|
||||
BTSP is based on:
|
||||
```rust
|
||||
use ruvector_nervous_system::hopfield::ModernHopfield;
|
||||
|
||||
1. **Dendritic plateau potentials**: Ca²⁺ spikes in dendrites
|
||||
2. **Eligibility traces**: Short-term memory of recent activity
|
||||
3. **Bidirectional plasticity**: Homeostatic weight regulation
|
||||
4. **Hippocampal place fields**: One-shot spatial learning
|
||||
// Create network with exponential capacity
|
||||
let mut hopfield = ModernHopfield::new(512, 10.0);
|
||||
|
||||
## Applications for Vector Databases
|
||||
// Store patterns
|
||||
hopfield.store(pattern1);
|
||||
hopfield.store(pattern2);
|
||||
|
||||
- **Immediate indexing**: Add vectors without retraining
|
||||
- **Adaptive routing**: Learn query patterns on-the-fly
|
||||
- **Error correction**: Self-healing index structures
|
||||
- **Context learning**: Remember user preferences instantly
|
||||
// Retrieve with noisy query (<1ms)
|
||||
let retrieved = hopfield.retrieve(&noisy_query);
|
||||
```
|
||||
|
||||
## References
|
||||
### Example: Winner-Take-All
|
||||
|
||||
Bittner, K. C., Milstein, A. D., Grienberger, C., Romani, S., & Magee, J. C. (2017).
|
||||
"Behavioral time scale synaptic plasticity underlies CA1 place fields."
|
||||
*Science*, 357(6355), 1033-1036.
|
||||
```rust
|
||||
use ruvector_nervous_system::compete::WTALayer;
|
||||
|
||||
// Create WTA layer
|
||||
let mut wta = WTALayer::new(1000, 0.5, 0.8);
|
||||
|
||||
// Fast winner selection (<1μs)
|
||||
if let Some(winner) = wta.compete(&activations) {
|
||||
route_to_winner(winner);
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Coherence-Gated Routing
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::routing::{OscillatoryRouter, GlobalWorkspace};
|
||||
|
||||
// Kuramoto oscillators for phase coupling
|
||||
let mut router = OscillatoryRouter::new(10, 40.0); // 40Hz gamma band
|
||||
router.step(0.001); // 1ms step
|
||||
|
||||
// Communication gain based on phase coherence
|
||||
let gain = router.communication_gain(sender, receiver);
|
||||
|
||||
// Global workspace (4-7 items max)
|
||||
let mut workspace = GlobalWorkspace::new(7);
|
||||
workspace.broadcast(representation);
|
||||
```
|
||||
|
||||
## Tutorial: Building a Complete System
|
||||
|
||||
### Step 1: Event Sensing
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::eventbus::{DVSEvent, ShardedEventBus, BackpressureController};
|
||||
|
||||
// Sharded event bus with backpressure
|
||||
let bus = ShardedEventBus::new_spatial(4, 1024);
|
||||
let controller = BackpressureController::default();
|
||||
|
||||
// Process events sparsely
|
||||
for event in stream {
|
||||
controller.update(bus.avg_fill_ratio());
|
||||
if controller.should_accept() {
|
||||
bus.push(event)?;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Reflex Response
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::compete::KWTALayer;
|
||||
use ruvector_nervous_system::dendrite::Dendrite;
|
||||
|
||||
// K-winners for sparse activation
|
||||
let kwta = KWTALayer::new(1000, 50); // Top 50 winners
|
||||
let winners = kwta.select(&inputs);
|
||||
|
||||
// Dendritic coincidence detection
|
||||
let mut dendrite = Dendrite::new(10, 30.0); // 10 synapses, 30ms window
|
||||
dendrite.receive_spike(synapse_id, timestamp);
|
||||
if dendrite.has_plateau() {
|
||||
trigger_btsp_learning();
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Memory and Learning
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::separate::DentateGyrus;
|
||||
use ruvector_nervous_system::plasticity::eprop::EpropNetwork;
|
||||
|
||||
// Pattern separation before storage
|
||||
let encoder = DentateGyrus::new(512, 10000, 500, 42); // 5% sparsity
|
||||
let sparse_code = encoder.encode(&input);
|
||||
|
||||
// Online learning with e-prop
|
||||
let mut network = EpropNetwork::new(100, 500, 10);
|
||||
network.online_step(&input, &target, 0.001, 0.01);
|
||||
```
|
||||
|
||||
### Step 4: Coherence and Coordination
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::routing::CoherenceGatedSystem;
|
||||
|
||||
// Full coherence-gated system
|
||||
let mut system = CoherenceGatedSystem::new(10, 40.0, 0.5, 7);
|
||||
|
||||
// Route with coherence gating
|
||||
let routed = system.route_with_coherence(&message, sender, 0.001);
|
||||
```
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
| Component | Target | Achieved |
|
||||
|-----------|--------|----------|
|
||||
| HDC Binding | <50ns | 64ns |
|
||||
| HDC Similarity | <100ns | ~80ns |
|
||||
| WTA Single Winner | <1μs | <1μs |
|
||||
| K-WTA (k=50) | <10μs | 2.7μs |
|
||||
| Hopfield Retrieval | <1ms | <1ms |
|
||||
| Pattern Separation | <500μs | <500μs |
|
||||
| E-prop Synapse Memory | 8-12 bytes | 12 bytes |
|
||||
| Event Bus | 10K events/ms | 10K+ events/ms |
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Architecture Guide](docs/nervous-system/architecture.md) - Complete crate layout and traits
|
||||
- [Deployment Guide](docs/nervous-system/deployment.md) - Three-phase deployment plan
|
||||
- [Test Plan](docs/nervous-system/test-plan.md) - Benchmarks and quality metrics
|
||||
- [Examples](examples/README.md) - Practical to exotic use cases
|
||||
|
||||
## Biological References
|
||||
|
||||
| Component | Research Basis |
|
||||
|-----------|----------------|
|
||||
| HDC | Kanerva 1988, Plate 2003 |
|
||||
| Modern Hopfield | Ramsauer et al. 2020 |
|
||||
| Pattern Separation | Rolls 2013, Dentate Gyrus |
|
||||
| Dendritic Processing | Stuart & Spruston 2015, Dendrify |
|
||||
| BTSP | Bittner et al. 2017 |
|
||||
| E-prop | Bellec et al. 2020 |
|
||||
| EWC | Kirkpatrick et al. 2017 |
|
||||
| Coherence Routing | Fries 2015 |
|
||||
| Global Workspace | Baars 1988, Dehaene 2014 |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
MIT License - See [LICENSE](LICENSE)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Each module should include:
|
||||
- Comprehensive unit tests
|
||||
- Criterion benchmarks
|
||||
- Documentation with biological context
|
||||
- Examples demonstrating use cases
|
||||
|
||||
## What This Enables
|
||||
|
||||
Systems that:
|
||||
- **Survive** - Graceful degradation, not catastrophic failure
|
||||
- **Adapt** - Learning through use, not retraining
|
||||
- **Cooperate** - Emergent coordination, not central control
|
||||
|
||||
This is no longer just about making machines smarter. It's about giving them nervous systems that let them exist in the world.
|
||||
|
|
|
|||
217
crates/ruvector-nervous-system/examples/README.md
Normal file
217
crates/ruvector-nervous-system/examples/README.md
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
# Nervous System Examples
|
||||
|
||||
[](https://www.rust-lang.org/)
|
||||
[](../LICENSE)
|
||||
[]()
|
||||
|
||||
Bio-inspired nervous system architecture examples demonstrating the transition from **"How do we make machines smarter?"** to **"What kind of organism are we building?"**
|
||||
|
||||
## Overview
|
||||
|
||||
These examples show how nervous system thinking unlocks new products, markets, and research categories. The architecture enables systems that **age well** instead of breaking under complexity.
|
||||
|
||||
## Application Tiers
|
||||
|
||||
### Tier 1: Immediate Practical Applications
|
||||
*Shippable with current architecture*
|
||||
|
||||
| Example | Domain | Key Benefit |
|
||||
|---------|--------|-------------|
|
||||
| [anomaly_detection](tier1/anomaly_detection.rs) | Infrastructure, Finance, Security | Detection before failure, microsecond response |
|
||||
| [edge_autonomy](tier1/edge_autonomy.rs) | Drones, Vehicles, Robotics | Lower power, certified reflex paths |
|
||||
| [medical_wearable](tier1/medical_wearable.rs) | Monitoring, Assistive Devices | Adapts to the person, always-on, private |
|
||||
|
||||
### Tier 2: Near-Term Transformative Applications
|
||||
*Possible once local learning and coherence routing mature*
|
||||
|
||||
| Example | Domain | Key Benefit |
|
||||
|---------|--------|-------------|
|
||||
| [self_optimizing_systems](tier2/self_optimizing_systems.rs) | Agents Monitoring Agents | Self-stabilizing software, structural witnesses |
|
||||
| [swarm_intelligence](tier2/swarm_intelligence.rs) | IoT Fleets, Sensor Meshes | Scale without fragility, emergent intelligence |
|
||||
| [adaptive_simulation](tier2/adaptive_simulation.rs) | Digital Twins, Logistics | Always-warm simulation, costs scale with relevance |
|
||||
|
||||
### Tier 3: Exotic But Real Applications
|
||||
*Technically grounded, novel research directions*
|
||||
|
||||
| Example | Domain | Key Benefit |
|
||||
|---------|--------|-------------|
|
||||
| [machine_self_awareness](tier3/machine_self_awareness.rs) | Structural Self-Sensing | Systems say "I am becoming unstable" |
|
||||
| [synthetic_nervous_systems](tier3/synthetic_nervous_systems.rs) | Buildings, Factories, Cities | Environments respond like organisms |
|
||||
| [bio_machine_interface](tier3/bio_machine_interface.rs) | Prosthetics, Rehabilitation | Machines stop fighting biology |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run a Tier 1 example
|
||||
cargo run --example anomaly_detection
|
||||
|
||||
# Run a Tier 2 example
|
||||
cargo run --example swarm_intelligence
|
||||
|
||||
# Run a Tier 3 example
|
||||
cargo run --example machine_self_awareness
|
||||
```
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
Each example demonstrates the same five-layer architecture:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ COHERENCE LAYER │
|
||||
│ Global Workspace • Oscillatory Routing • Predictive Coding │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↑
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ LEARNING LAYER │
|
||||
│ BTSP One-Shot • E-prop Online • EWC Consolidation │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↑
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MEMORY LAYER │
|
||||
│ Hopfield Networks • HDC Vectors • Pattern Separation │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↑
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ REFLEX LAYER │
|
||||
│ K-WTA Competition • Dendritic Coincidence • Safety │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↑
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SENSING LAYER │
|
||||
│ Event Bus • Sparse Spikes • Backpressure Control │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Concepts Demonstrated
|
||||
|
||||
### Reflex Arcs
|
||||
Fast, deterministic responses with bounded execution:
|
||||
- Latency: <100μs
|
||||
- Certifiable: Maximum iteration counts
|
||||
- Safety: Witness logging for every decision
|
||||
|
||||
### Homeostasis
|
||||
Self-regulation instead of static thresholds:
|
||||
- Adaptive learning from normal operation
|
||||
- Graceful degradation under stress
|
||||
- Anticipatory maintenance
|
||||
|
||||
### Coherence Gating
|
||||
Synchronize only when needed:
|
||||
- Kuramoto oscillators for phase coupling
|
||||
- Communication gain based on phase coherence
|
||||
- 90-99% bandwidth reduction via prediction
|
||||
|
||||
### One-Shot Learning
|
||||
Learn immediately from single examples:
|
||||
- BTSP: Seconds-scale eligibility traces
|
||||
- No batch retraining required
|
||||
- Personalization through use
|
||||
|
||||
## Tutorial: Building a Custom Application
|
||||
|
||||
### Step 1: Define Your Sensing Layer
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::eventbus::{DVSEvent, EventRingBuffer};
|
||||
|
||||
// Create event buffer with backpressure
|
||||
let buffer = EventRingBuffer::new(1024);
|
||||
|
||||
// Process events sparsely
|
||||
if let Some(event) = buffer.pop() {
|
||||
// Only significant changes generate events
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Add Reflex Gates
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::compete::WTALayer;
|
||||
|
||||
// Winner-take-all for fast decisions
|
||||
let mut wta = WTALayer::new(100, 0.5, 0.8);
|
||||
|
||||
// <1μs for 1000 neurons
|
||||
if let Some(winner) = wta.compete(&inputs) {
|
||||
trigger_immediate_response(winner);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Implement Memory
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::hopfield::ModernHopfield;
|
||||
use ruvector_nervous_system::hdc::Hypervector;
|
||||
|
||||
// Hopfield for associative retrieval
|
||||
let mut hopfield = ModernHopfield::new(512, 10.0);
|
||||
hopfield.store(pattern);
|
||||
|
||||
// HDC for ultra-fast similarity
|
||||
let similarity = v1.similarity(&v2); // <100ns
|
||||
```
|
||||
|
||||
### Step 4: Enable Learning
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::plasticity::btsp::BTSPSynapse;
|
||||
|
||||
// One-shot learning
|
||||
let mut synapse = BTSPSynapse::new(0.5, 2000.0); // 2s time constant
|
||||
synapse.update(presynaptic_active, plateau_signal, dt);
|
||||
```
|
||||
|
||||
### Step 5: Add Coherence
|
||||
|
||||
```rust
|
||||
use ruvector_nervous_system::routing::{OscillatoryRouter, GlobalWorkspace};
|
||||
|
||||
// Phase-coupled routing
|
||||
let mut router = OscillatoryRouter::new(10, 40.0); // 40Hz gamma
|
||||
let gain = router.communication_gain(sender, receiver);
|
||||
|
||||
// Global workspace (4-7 items)
|
||||
let mut workspace = GlobalWorkspace::new(7);
|
||||
workspace.broadcast(representation);
|
||||
```
|
||||
|
||||
## Performance Targets
|
||||
|
||||
| Component | Latency | Throughput |
|
||||
|-----------|---------|------------|
|
||||
| Event Bus | <100ns push/pop | 10,000+ events/ms |
|
||||
| WTA | <1μs | 1M+ decisions/sec |
|
||||
| HDC Similarity | <100ns | 10M+ comparisons/sec |
|
||||
| Hopfield Retrieval | <1ms | 1000+ queries/sec |
|
||||
| BTSP Update | <100ns | 10M+ synapses/sec |
|
||||
|
||||
## From Practical to Exotic
|
||||
|
||||
The same architecture scales from:
|
||||
|
||||
1. **Practical**: Anomaly detection with microsecond response
|
||||
2. **Transformative**: Self-optimizing software systems
|
||||
3. **Exotic**: Machines that sense their own coherence
|
||||
|
||||
The difference is how much reflex, learning, and coherence you turn on.
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [Architecture Documentation](../../docs/nervous-system/architecture.md)
|
||||
- [Deployment Guide](../../docs/nervous-system/deployment.md)
|
||||
- [Test Plan](../../docs/nervous-system/test-plan.md)
|
||||
- [Main Crate Documentation](../README.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
Examples welcome! Each should demonstrate:
|
||||
1. A clear use case
|
||||
2. The nervous system architecture
|
||||
3. Performance characteristics
|
||||
4. Tests and documentation
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See [LICENSE](../LICENSE)
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
//! # Tier 1: Always-On Anomaly Detection
|
||||
//!
|
||||
//! Infrastructure, finance, security, medical telemetry.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Event streams replace batch logs
|
||||
//! - Reflex gates fire on structural or temporal anomalies
|
||||
//! - Learning tightens thresholds over time
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Detection happens before failure, not after symptoms
|
||||
//! - Microsecond to millisecond response
|
||||
//! - Explainable witness logs for every trigger
|
||||
//!
|
||||
//! This is a direct fit for RuVector + Cognitum v0.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// Simulated imports from nervous system crate
|
||||
// In production: use ruvector_nervous_system::*;
|
||||
|
||||
/// Event from monitored system (infrastructure, finance, medical, etc.)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TelemetryEvent {
|
||||
pub timestamp: u64,
|
||||
pub source_id: u16,
|
||||
pub metric_id: u32,
|
||||
pub value: f32,
|
||||
pub metadata: Option<String>,
|
||||
}
|
||||
|
||||
/// Anomaly detection result with witness log
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnomalyAlert {
|
||||
pub event: TelemetryEvent,
|
||||
pub anomaly_type: AnomalyType,
|
||||
pub severity: f32,
|
||||
pub witness_log: WitnessLog,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AnomalyType {
|
||||
/// Value outside learned bounds
|
||||
ValueAnomaly { expected_range: (f32, f32), actual: f32 },
|
||||
/// Temporal pattern violation
|
||||
TemporalAnomaly { expected_interval_ms: u64, actual_interval_ms: u64 },
|
||||
/// Structural change in event relationships
|
||||
StructuralAnomaly { pattern_signature: u64, deviation: f32 },
|
||||
/// Cascade detected across multiple sources
|
||||
CascadeAnomaly { affected_sources: Vec<u16> },
|
||||
}
|
||||
|
||||
/// Explainable witness log for every trigger
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WitnessLog {
|
||||
pub trigger_timestamp: u64,
|
||||
pub reflex_gate_id: u32,
|
||||
pub input_snapshot: Vec<f32>,
|
||||
pub threshold_at_trigger: f32,
|
||||
pub decision_path: Vec<String>,
|
||||
}
|
||||
|
||||
/// Reflex gate for immediate anomaly detection
|
||||
pub struct ReflexGate {
|
||||
pub id: u32,
|
||||
pub threshold: f32,
|
||||
pub membrane_potential: f32,
|
||||
pub last_spike: u64,
|
||||
pub refractory_period_ms: u64,
|
||||
}
|
||||
|
||||
impl ReflexGate {
|
||||
pub fn new(id: u32, threshold: f32) -> Self {
|
||||
Self {
|
||||
id,
|
||||
threshold,
|
||||
membrane_potential: 0.0,
|
||||
last_spike: 0,
|
||||
refractory_period_ms: 10, // 10ms refractory
|
||||
}
|
||||
}
|
||||
|
||||
/// Process input and return true if gate fires
|
||||
pub fn process(&mut self, input: f32, timestamp: u64) -> bool {
|
||||
// Check refractory period
|
||||
if timestamp < self.last_spike + self.refractory_period_ms {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Integrate input
|
||||
self.membrane_potential += input;
|
||||
|
||||
// Fire if threshold exceeded
|
||||
if self.membrane_potential >= self.threshold {
|
||||
self.last_spike = timestamp;
|
||||
self.membrane_potential = 0.0; // Reset
|
||||
return true;
|
||||
}
|
||||
|
||||
// Leak (decay)
|
||||
self.membrane_potential *= 0.95;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Adaptive threshold using BTSP-style learning
|
||||
pub struct AdaptiveThreshold {
|
||||
pub baseline: f32,
|
||||
pub current: f32,
|
||||
pub eligibility_trace: f32,
|
||||
pub tau_seconds: f32,
|
||||
pub learning_rate: f32,
|
||||
}
|
||||
|
||||
impl AdaptiveThreshold {
|
||||
pub fn new(baseline: f32) -> Self {
|
||||
Self {
|
||||
baseline,
|
||||
current: baseline,
|
||||
eligibility_trace: 0.0,
|
||||
tau_seconds: 60.0, // 1 minute adaptation window
|
||||
learning_rate: 0.01,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update threshold based on observed values
|
||||
pub fn adapt(&mut self, observed: f32, was_anomaly: bool, dt_seconds: f32) {
|
||||
// Decay eligibility trace
|
||||
self.eligibility_trace *= (-dt_seconds / self.tau_seconds).exp();
|
||||
|
||||
if was_anomaly {
|
||||
// If we flagged an anomaly, become slightly more tolerant
|
||||
// to avoid alert fatigue
|
||||
self.current += self.learning_rate * self.eligibility_trace;
|
||||
} else {
|
||||
// Normal observation - tighten threshold over time
|
||||
let error = (observed - self.current).abs();
|
||||
self.eligibility_trace += error;
|
||||
self.current -= self.learning_rate * 0.1 * self.eligibility_trace;
|
||||
}
|
||||
|
||||
// Clamp to reasonable bounds
|
||||
self.current = self.current.clamp(self.baseline * 0.5, self.baseline * 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporal pattern detector using spike timing
|
||||
pub struct TemporalPatternDetector {
|
||||
pub expected_interval_ms: u64,
|
||||
pub tolerance_ms: u64,
|
||||
pub last_event_time: u64,
|
||||
pub interval_history: VecDeque<u64>,
|
||||
pub max_history: usize,
|
||||
}
|
||||
|
||||
impl TemporalPatternDetector {
|
||||
pub fn new(expected_interval_ms: u64, tolerance_ms: u64) -> Self {
|
||||
Self {
|
||||
expected_interval_ms,
|
||||
tolerance_ms,
|
||||
last_event_time: 0,
|
||||
interval_history: VecDeque::new(),
|
||||
max_history: 100,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if event timing is anomalous
|
||||
pub fn check(&mut self, timestamp: u64) -> Option<AnomalyType> {
|
||||
if self.last_event_time == 0 {
|
||||
self.last_event_time = timestamp;
|
||||
return None;
|
||||
}
|
||||
|
||||
let interval = timestamp - self.last_event_time;
|
||||
self.last_event_time = timestamp;
|
||||
|
||||
// Track history
|
||||
self.interval_history.push_back(interval);
|
||||
if self.interval_history.len() > self.max_history {
|
||||
self.interval_history.pop_front();
|
||||
}
|
||||
|
||||
// Update expected interval (online learning)
|
||||
if self.interval_history.len() > 10 {
|
||||
let avg: u64 = self.interval_history.iter().sum::<u64>()
|
||||
/ self.interval_history.len() as u64;
|
||||
self.expected_interval_ms = (self.expected_interval_ms + avg) / 2;
|
||||
}
|
||||
|
||||
// Check for anomaly
|
||||
let diff = (interval as i64 - self.expected_interval_ms as i64).unsigned_abs();
|
||||
if diff > self.tolerance_ms {
|
||||
Some(AnomalyType::TemporalAnomaly {
|
||||
expected_interval_ms: self.expected_interval_ms,
|
||||
actual_interval_ms: interval,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main anomaly detection system
|
||||
pub struct AnomalyDetectionSystem {
|
||||
/// Reflex gates for immediate detection
|
||||
pub reflex_gates: Vec<ReflexGate>,
|
||||
/// Adaptive thresholds per metric
|
||||
pub thresholds: Vec<AdaptiveThreshold>,
|
||||
/// Temporal pattern detectors per source
|
||||
pub temporal_detectors: Vec<TemporalPatternDetector>,
|
||||
/// Alert history for cascade detection
|
||||
pub recent_alerts: VecDeque<AnomalyAlert>,
|
||||
/// Witness log buffer
|
||||
pub witness_buffer: VecDeque<WitnessLog>,
|
||||
}
|
||||
|
||||
impl AnomalyDetectionSystem {
|
||||
pub fn new(num_sources: usize, num_metrics: usize) -> Self {
|
||||
Self {
|
||||
reflex_gates: (0..num_sources)
|
||||
.map(|i| ReflexGate::new(i as u32, 1.0))
|
||||
.collect(),
|
||||
thresholds: (0..num_metrics)
|
||||
.map(|_| AdaptiveThreshold::new(1.0))
|
||||
.collect(),
|
||||
temporal_detectors: (0..num_sources)
|
||||
.map(|_| TemporalPatternDetector::new(1000, 100))
|
||||
.collect(),
|
||||
recent_alerts: VecDeque::new(),
|
||||
witness_buffer: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a telemetry event through the nervous system
|
||||
/// Returns anomaly alert if detected, with full witness log
|
||||
pub fn process_event(&mut self, event: TelemetryEvent) -> Option<AnomalyAlert> {
|
||||
let source_idx = event.source_id as usize % self.reflex_gates.len();
|
||||
let metric_idx = event.metric_id as usize % self.thresholds.len();
|
||||
|
||||
// 1. Check temporal pattern (fast reflex)
|
||||
if let Some(temporal_anomaly) = self.temporal_detectors[source_idx].check(event.timestamp) {
|
||||
return Some(self.create_alert(event, temporal_anomaly, 0.7));
|
||||
}
|
||||
|
||||
// 2. Check value against adaptive threshold
|
||||
let threshold = &self.thresholds[metric_idx];
|
||||
if event.value > threshold.current * 2.0 || event.value < threshold.current * 0.5 {
|
||||
return Some(self.create_alert(
|
||||
event.clone(),
|
||||
AnomalyType::ValueAnomaly {
|
||||
expected_range: (threshold.current * 0.5, threshold.current * 2.0),
|
||||
actual: event.value,
|
||||
},
|
||||
0.8,
|
||||
));
|
||||
}
|
||||
|
||||
// 3. Check reflex gate (integrates over time)
|
||||
let normalized = (event.value - threshold.current).abs() / threshold.current;
|
||||
if self.reflex_gates[source_idx].process(normalized, event.timestamp) {
|
||||
return Some(self.create_alert(
|
||||
event,
|
||||
AnomalyType::StructuralAnomaly {
|
||||
pattern_signature: source_idx as u64,
|
||||
deviation: normalized,
|
||||
},
|
||||
0.6,
|
||||
));
|
||||
}
|
||||
|
||||
// 4. Cascade detection: multiple sources alerting
|
||||
self.check_cascade(event.timestamp)
|
||||
}
|
||||
|
||||
fn create_alert(&mut self, event: TelemetryEvent, anomaly_type: AnomalyType, severity: f32) -> AnomalyAlert {
|
||||
let witness = WitnessLog {
|
||||
trigger_timestamp: event.timestamp,
|
||||
reflex_gate_id: event.source_id as u32,
|
||||
input_snapshot: vec![event.value],
|
||||
threshold_at_trigger: self.thresholds
|
||||
.get(event.metric_id as usize % self.thresholds.len())
|
||||
.map(|t| t.current)
|
||||
.unwrap_or(1.0),
|
||||
decision_path: vec![
|
||||
format!("Event received: source={}, metric={}", event.source_id, event.metric_id),
|
||||
format!("Anomaly type: {:?}", anomaly_type),
|
||||
format!("Severity: {:.2}", severity),
|
||||
],
|
||||
};
|
||||
|
||||
self.witness_buffer.push_back(witness.clone());
|
||||
if self.witness_buffer.len() > 1000 {
|
||||
self.witness_buffer.pop_front();
|
||||
}
|
||||
|
||||
let alert = AnomalyAlert {
|
||||
event,
|
||||
anomaly_type,
|
||||
severity,
|
||||
witness_log: witness,
|
||||
};
|
||||
|
||||
self.recent_alerts.push_back(alert.clone());
|
||||
if self.recent_alerts.len() > 100 {
|
||||
self.recent_alerts.pop_front();
|
||||
}
|
||||
|
||||
alert
|
||||
}
|
||||
|
||||
fn check_cascade(&self, timestamp: u64) -> Option<AnomalyAlert> {
|
||||
// Check if multiple sources alerted within 100ms window
|
||||
let window_start = timestamp.saturating_sub(100);
|
||||
let recent: Vec<_> = self.recent_alerts
|
||||
.iter()
|
||||
.filter(|a| a.event.timestamp >= window_start)
|
||||
.collect();
|
||||
|
||||
if recent.len() >= 3 {
|
||||
let affected: Vec<u16> = recent.iter().map(|a| a.event.source_id).collect();
|
||||
let event = recent.last()?.event.clone();
|
||||
|
||||
Some(AnomalyAlert {
|
||||
event: event.clone(),
|
||||
anomaly_type: AnomalyType::CascadeAnomaly { affected_sources: affected },
|
||||
severity: 0.95,
|
||||
witness_log: WitnessLog {
|
||||
trigger_timestamp: timestamp,
|
||||
reflex_gate_id: 0,
|
||||
input_snapshot: vec![],
|
||||
threshold_at_trigger: 0.0,
|
||||
decision_path: vec![
|
||||
"Cascade detected".to_string(),
|
||||
format!("Multiple sources alerting within 100ms"),
|
||||
],
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn from feedback: was the alert valid?
|
||||
pub fn learn_from_feedback(&mut self, alert: &AnomalyAlert, was_valid: bool) {
|
||||
let metric_idx = alert.event.metric_id as usize % self.thresholds.len();
|
||||
self.thresholds[metric_idx].adapt(
|
||||
alert.event.value,
|
||||
!was_valid, // If invalid, treat as normal (tighten threshold)
|
||||
0.1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Example Usage
|
||||
// =============================================================================
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 1: Always-On Anomaly Detection ===\n");
|
||||
|
||||
// Create detection system for 10 sources, 5 metrics
|
||||
let mut detector = AnomalyDetectionSystem::new(10, 5);
|
||||
|
||||
// Simulate normal telemetry
|
||||
println!("Processing normal telemetry...");
|
||||
for i in 0..100 {
|
||||
let event = TelemetryEvent {
|
||||
timestamp: i * 1000 + (i % 10) * 10, // Slight jitter
|
||||
source_id: (i % 10) as u16,
|
||||
metric_id: (i % 5) as u32,
|
||||
value: 1.0 + (i as f32 * 0.01).sin() * 0.1, // Normal variation
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
if let Some(alert) = detector.process_event(event) {
|
||||
println!(" Alert: {:?}", alert.anomaly_type);
|
||||
}
|
||||
}
|
||||
println!(" Normal events processed with adaptive learning\n");
|
||||
|
||||
// Simulate anomalies
|
||||
println!("Injecting anomalies...");
|
||||
|
||||
// Value anomaly
|
||||
let value_spike = TelemetryEvent {
|
||||
timestamp: 101_000,
|
||||
source_id: 0,
|
||||
metric_id: 0,
|
||||
value: 5.0, // Way above normal ~1.0
|
||||
metadata: Some("CPU spike".to_string()),
|
||||
};
|
||||
if let Some(alert) = detector.process_event(value_spike) {
|
||||
println!(" VALUE ANOMALY DETECTED!");
|
||||
println!(" Type: {:?}", alert.anomaly_type);
|
||||
println!(" Severity: {:.2}", alert.severity);
|
||||
println!(" Witness: {:?}", alert.witness_log.decision_path);
|
||||
}
|
||||
|
||||
// Temporal anomaly (delayed event)
|
||||
let delayed = TelemetryEvent {
|
||||
timestamp: 105_000, // Gap of 4 seconds instead of 1
|
||||
source_id: 1,
|
||||
metric_id: 1,
|
||||
value: 1.0,
|
||||
metadata: Some("Delayed heartbeat".to_string()),
|
||||
};
|
||||
if let Some(alert) = detector.process_event(delayed) {
|
||||
println!("\n TEMPORAL ANOMALY DETECTED!");
|
||||
println!(" Type: {:?}", alert.anomaly_type);
|
||||
}
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Detection before failure (microsecond response)");
|
||||
println!("- Adaptive thresholds reduce false positives");
|
||||
println!("- Explainable witness logs for every trigger");
|
||||
println!("- Cascade detection across multiple sources");
|
||||
println!("\nDirect fit for RuVector + Cognitum v0");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_reflex_gate_fires() {
|
||||
let mut gate = ReflexGate::new(0, 1.0);
|
||||
|
||||
// Should not fire on small inputs
|
||||
assert!(!gate.process(0.3, 0));
|
||||
assert!(!gate.process(0.3, 1));
|
||||
|
||||
// Should fire when accumulated
|
||||
assert!(gate.process(0.5, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adaptive_threshold() {
|
||||
let mut threshold = AdaptiveThreshold::new(1.0);
|
||||
|
||||
// Normal observations should tighten threshold
|
||||
for _ in 0..10 {
|
||||
threshold.adapt(1.0, false, 0.1);
|
||||
}
|
||||
|
||||
assert!(threshold.current < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temporal_pattern_detection() {
|
||||
let mut detector = TemporalPatternDetector::new(1000, 100);
|
||||
|
||||
// Normal intervals
|
||||
assert!(detector.check(0).is_none());
|
||||
assert!(detector.check(1000).is_none());
|
||||
assert!(detector.check(2000).is_none());
|
||||
|
||||
// Anomalous interval (500ms instead of 1000ms)
|
||||
let result = detector.check(2500);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value_anomaly_detection() {
|
||||
let mut system = AnomalyDetectionSystem::new(1, 1);
|
||||
|
||||
// Establish baseline
|
||||
for i in 0..10 {
|
||||
let event = TelemetryEvent {
|
||||
timestamp: i * 1000,
|
||||
source_id: 0,
|
||||
metric_id: 0,
|
||||
value: 1.0,
|
||||
metadata: None,
|
||||
};
|
||||
system.process_event(event);
|
||||
}
|
||||
|
||||
// Inject anomaly
|
||||
let anomaly = TelemetryEvent {
|
||||
timestamp: 10_000,
|
||||
source_id: 0,
|
||||
metric_id: 0,
|
||||
value: 10.0, // 10x normal
|
||||
metadata: None,
|
||||
};
|
||||
|
||||
let result = system.process_event(anomaly);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
}
|
||||
492
crates/ruvector-nervous-system/examples/tier1/edge_autonomy.rs
Normal file
492
crates/ruvector-nervous-system/examples/tier1/edge_autonomy.rs
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
//! # Tier 1: Edge Autonomy and Control
|
||||
//!
|
||||
//! Drones, vehicles, robotics, industrial automation.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Reflex arcs handle safety and stabilization
|
||||
//! - Policy loops run slower and only when needed
|
||||
//! - Bullet-time bursts replace constant compute
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Lower power, faster reactions
|
||||
//! - Systems degrade gracefully instead of catastrophically
|
||||
//! - Certification becomes possible because reflex paths are bounded
|
||||
//!
|
||||
//! This is where Cognitum shines immediately.
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Sensor reading from edge device
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SensorReading {
|
||||
pub timestamp_us: u64,
|
||||
pub sensor_type: SensorType,
|
||||
pub value: f32,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SensorType {
|
||||
Accelerometer,
|
||||
Gyroscope,
|
||||
Proximity,
|
||||
Temperature,
|
||||
Battery,
|
||||
Motor,
|
||||
}
|
||||
|
||||
/// Control action output
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ControlAction {
|
||||
pub actuator_id: u32,
|
||||
pub command: ActuatorCommand,
|
||||
pub priority: Priority,
|
||||
pub deadline_us: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ActuatorCommand {
|
||||
SetMotorSpeed(f32),
|
||||
ApplyBrake(f32),
|
||||
AdjustPitch(f32),
|
||||
EmergencyStop,
|
||||
Idle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Priority {
|
||||
Safety, // Immediate, preempts everything
|
||||
Stability, // Fast reflex response
|
||||
Efficiency, // Slower optimization
|
||||
Background, // When idle
|
||||
}
|
||||
|
||||
/// Reflex arc for immediate safety responses
|
||||
/// Runs on Cognitum worker tiles with deterministic timing
|
||||
pub struct ReflexArc {
|
||||
pub name: String,
|
||||
pub trigger_threshold: f32,
|
||||
pub response_action: ActuatorCommand,
|
||||
pub max_latency_us: u64,
|
||||
pub last_activation: u64,
|
||||
pub activation_count: u64,
|
||||
}
|
||||
|
||||
impl ReflexArc {
|
||||
pub fn new(name: &str, threshold: f32, action: ActuatorCommand, max_latency_us: u64) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
trigger_threshold: threshold,
|
||||
response_action: action,
|
||||
max_latency_us,
|
||||
last_activation: 0,
|
||||
activation_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if reflex should fire - deterministic, bounded execution
|
||||
pub fn check(&mut self, reading: &SensorReading) -> Option<ControlAction> {
|
||||
if reading.value.abs() > self.trigger_threshold {
|
||||
self.last_activation = reading.timestamp_us;
|
||||
self.activation_count += 1;
|
||||
|
||||
Some(ControlAction {
|
||||
actuator_id: 0,
|
||||
command: self.response_action.clone(),
|
||||
priority: Priority::Safety,
|
||||
deadline_us: reading.timestamp_us + self.max_latency_us,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stability controller using dendritic coincidence detection
|
||||
/// Detects correlated sensor patterns requiring stabilization
|
||||
pub struct StabilityController {
|
||||
pub imu_history: Vec<(f32, f32, f32)>, // accel, gyro, proximity
|
||||
pub coincidence_window_us: u64,
|
||||
pub stability_threshold: f32,
|
||||
pub membrane_potential: f32,
|
||||
}
|
||||
|
||||
impl StabilityController {
|
||||
pub fn new(coincidence_window_us: u64, threshold: f32) -> Self {
|
||||
Self {
|
||||
imu_history: Vec::with_capacity(100),
|
||||
coincidence_window_us,
|
||||
stability_threshold: threshold,
|
||||
membrane_potential: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process sensor fusion for stability
|
||||
pub fn process(&mut self, readings: &[SensorReading]) -> Option<ControlAction> {
|
||||
// Extract relevant sensors
|
||||
let accel = readings.iter()
|
||||
.find(|r| r.sensor_type == SensorType::Accelerometer)
|
||||
.map(|r| r.value);
|
||||
let gyro = readings.iter()
|
||||
.find(|r| r.sensor_type == SensorType::Gyroscope)
|
||||
.map(|r| r.value);
|
||||
|
||||
if let (Some(a), Some(g)) = (accel, gyro) {
|
||||
// Coincidence detection: both accelerating and rotating
|
||||
let instability = a.abs() * g.abs();
|
||||
|
||||
// Integrate over time (dendritic membrane)
|
||||
self.membrane_potential += instability;
|
||||
self.membrane_potential *= 0.9; // Decay
|
||||
|
||||
if self.membrane_potential > self.stability_threshold {
|
||||
self.membrane_potential = 0.0; // Reset after spike
|
||||
|
||||
// Compute corrective action
|
||||
let correction = -g * 0.1; // Counter-rotate
|
||||
return Some(ControlAction {
|
||||
actuator_id: 1,
|
||||
command: ActuatorCommand::AdjustPitch(correction),
|
||||
priority: Priority::Stability,
|
||||
deadline_us: readings[0].timestamp_us + 1000, // 1ms deadline
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Bullet-time burst controller
|
||||
/// Activates high-fidelity processing only during critical moments
|
||||
pub struct BulletTimeController {
|
||||
pub is_active: bool,
|
||||
pub activation_threshold: f32,
|
||||
pub deactivation_threshold: f32,
|
||||
pub burst_duration_us: u64,
|
||||
pub burst_start: u64,
|
||||
pub normal_sample_rate_hz: u32,
|
||||
pub burst_sample_rate_hz: u32,
|
||||
}
|
||||
|
||||
impl BulletTimeController {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
activation_threshold: 0.8,
|
||||
deactivation_threshold: 0.3,
|
||||
burst_duration_us: 100_000, // 100ms max burst
|
||||
burst_start: 0,
|
||||
normal_sample_rate_hz: 100,
|
||||
burst_sample_rate_hz: 10_000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if bullet-time should activate
|
||||
pub fn should_activate(&mut self, urgency: f32, timestamp_us: u64) -> bool {
|
||||
if !self.is_active && urgency > self.activation_threshold {
|
||||
self.is_active = true;
|
||||
self.burst_start = timestamp_us;
|
||||
println!(" [BULLET TIME] Activated! Urgency: {:.2}", urgency);
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.is_active {
|
||||
// Check deactivation conditions
|
||||
let elapsed = timestamp_us - self.burst_start;
|
||||
if urgency < self.deactivation_threshold || elapsed > self.burst_duration_us {
|
||||
self.is_active = false;
|
||||
println!(" [BULLET TIME] Deactivated after {}us", elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
self.is_active
|
||||
}
|
||||
|
||||
pub fn current_sample_rate(&self) -> u32 {
|
||||
if self.is_active {
|
||||
self.burst_sample_rate_hz
|
||||
} else {
|
||||
self.normal_sample_rate_hz
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Policy loop for slower optimization
|
||||
/// Runs when reflexes and stability are not active
|
||||
pub struct PolicyLoop {
|
||||
pub energy_budget: f32,
|
||||
pub target_efficiency: f32,
|
||||
pub update_interval_ms: u64,
|
||||
pub last_update: u64,
|
||||
}
|
||||
|
||||
impl PolicyLoop {
|
||||
pub fn new(energy_budget: f32) -> Self {
|
||||
Self {
|
||||
energy_budget,
|
||||
target_efficiency: 0.9,
|
||||
update_interval_ms: 100, // Run at 10Hz
|
||||
last_update: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize for efficiency when safe
|
||||
pub fn optimize(&mut self, readings: &[SensorReading], timestamp_us: u64) -> Option<ControlAction> {
|
||||
let timestamp_ms = timestamp_us / 1000;
|
||||
if timestamp_ms < self.last_update + self.update_interval_ms {
|
||||
return None;
|
||||
}
|
||||
self.last_update = timestamp_ms;
|
||||
|
||||
// Check battery level
|
||||
let battery = readings.iter()
|
||||
.find(|r| r.sensor_type == SensorType::Battery)
|
||||
.map(|r| r.value)
|
||||
.unwrap_or(1.0);
|
||||
|
||||
if battery < 0.2 {
|
||||
// Low power mode
|
||||
Some(ControlAction {
|
||||
actuator_id: 0,
|
||||
command: ActuatorCommand::SetMotorSpeed(0.5), // Reduce speed
|
||||
priority: Priority::Efficiency,
|
||||
deadline_us: timestamp_us + 10_000,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main edge autonomy system
|
||||
pub struct EdgeAutonomySystem {
|
||||
/// Safety reflexes (always active, highest priority)
|
||||
pub reflexes: Vec<ReflexArc>,
|
||||
/// Stability controller (fast, second priority)
|
||||
pub stability: StabilityController,
|
||||
/// Bullet-time for critical moments
|
||||
pub bullet_time: BulletTimeController,
|
||||
/// Policy optimization (slow, lowest priority)
|
||||
pub policy: PolicyLoop,
|
||||
/// Graceful degradation state
|
||||
pub degradation_level: u8,
|
||||
}
|
||||
|
||||
impl EdgeAutonomySystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
reflexes: vec![
|
||||
ReflexArc::new(
|
||||
"collision_avoidance",
|
||||
0.5, // Proximity threshold
|
||||
ActuatorCommand::EmergencyStop,
|
||||
100, // 100us max latency
|
||||
),
|
||||
ReflexArc::new(
|
||||
"overheat_protection",
|
||||
85.0, // Temperature threshold
|
||||
ActuatorCommand::SetMotorSpeed(0.0),
|
||||
1000, // 1ms max latency
|
||||
),
|
||||
],
|
||||
stability: StabilityController::new(10_000, 2.0),
|
||||
bullet_time: BulletTimeController::new(),
|
||||
policy: PolicyLoop::new(100.0),
|
||||
degradation_level: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process sensor readings through the nervous system hierarchy
|
||||
pub fn process(&mut self, readings: Vec<SensorReading>) -> Vec<ControlAction> {
|
||||
let mut actions = Vec::new();
|
||||
let timestamp = readings.first().map(|r| r.timestamp_us).unwrap_or(0);
|
||||
|
||||
// 1. Safety reflexes (always checked first, deterministic)
|
||||
for reflex in &mut self.reflexes {
|
||||
for reading in &readings {
|
||||
if let Some(action) = reflex.check(reading) {
|
||||
println!(" REFLEX [{}]: {:?}", reflex.name, action.command);
|
||||
actions.push(action);
|
||||
// Safety actions preempt everything
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Stability control (fast, dendritic integration)
|
||||
if let Some(action) = self.stability.process(&readings) {
|
||||
println!(" STABILITY: {:?}", action.command);
|
||||
actions.push(action);
|
||||
}
|
||||
|
||||
// 3. Bullet-time activation check
|
||||
let urgency = self.compute_urgency(&readings);
|
||||
if self.bullet_time.should_activate(urgency, timestamp) {
|
||||
println!(" Sample rate: {}Hz", self.bullet_time.current_sample_rate());
|
||||
}
|
||||
|
||||
// 4. Policy optimization (only if stable)
|
||||
if actions.is_empty() {
|
||||
if let Some(action) = self.policy.optimize(&readings, timestamp) {
|
||||
println!(" POLICY: {:?}", action.command);
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
|
||||
fn compute_urgency(&self, readings: &[SensorReading]) -> f32 {
|
||||
readings.iter()
|
||||
.map(|r| r.value.abs() * (1.0 - r.confidence))
|
||||
.sum::<f32>()
|
||||
/ readings.len().max(1) as f32
|
||||
}
|
||||
|
||||
/// Handle graceful degradation
|
||||
pub fn degrade(&mut self) {
|
||||
self.degradation_level += 1;
|
||||
match self.degradation_level {
|
||||
1 => {
|
||||
println!(" DEGRADATION 1: Disabling policy optimization");
|
||||
}
|
||||
2 => {
|
||||
println!(" DEGRADATION 2: Reducing stability bandwidth");
|
||||
self.stability.stability_threshold *= 1.5;
|
||||
}
|
||||
3 => {
|
||||
println!(" DEGRADATION 3: Safety reflexes only");
|
||||
}
|
||||
_ => {
|
||||
println!(" CRITICAL: Maximum degradation reached");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 1: Edge Autonomy and Control ===\n");
|
||||
|
||||
let mut system = EdgeAutonomySystem::new();
|
||||
|
||||
// Simulate normal operation
|
||||
println!("Normal operation...");
|
||||
for i in 0..10 {
|
||||
let readings = vec![
|
||||
SensorReading {
|
||||
timestamp_us: i * 10_000,
|
||||
sensor_type: SensorType::Accelerometer,
|
||||
value: 0.1,
|
||||
confidence: 0.95,
|
||||
},
|
||||
SensorReading {
|
||||
timestamp_us: i * 10_000,
|
||||
sensor_type: SensorType::Gyroscope,
|
||||
value: 0.05,
|
||||
confidence: 0.95,
|
||||
},
|
||||
SensorReading {
|
||||
timestamp_us: i * 10_000,
|
||||
sensor_type: SensorType::Battery,
|
||||
value: 0.8,
|
||||
confidence: 1.0,
|
||||
},
|
||||
];
|
||||
let _ = system.process(readings);
|
||||
}
|
||||
println!(" 10 cycles processed, system stable\n");
|
||||
|
||||
// Simulate instability (triggers stability controller)
|
||||
println!("Simulating instability...");
|
||||
for i in 0..5 {
|
||||
let readings = vec![
|
||||
SensorReading {
|
||||
timestamp_us: 100_000 + i * 1000,
|
||||
sensor_type: SensorType::Accelerometer,
|
||||
value: 2.0 + i as f32 * 0.5,
|
||||
confidence: 0.8,
|
||||
},
|
||||
SensorReading {
|
||||
timestamp_us: 100_000 + i * 1000,
|
||||
sensor_type: SensorType::Gyroscope,
|
||||
value: 1.5 + i as f32 * 0.3,
|
||||
confidence: 0.8,
|
||||
},
|
||||
];
|
||||
let actions = system.process(readings);
|
||||
for action in actions {
|
||||
println!(" Action: {:?} (deadline: {}us)", action.command, action.deadline_us);
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate collision (triggers safety reflex)
|
||||
println!("\nSimulating collision warning...");
|
||||
let emergency = vec![
|
||||
SensorReading {
|
||||
timestamp_us: 200_000,
|
||||
sensor_type: SensorType::Proximity,
|
||||
value: 0.9, // Very close!
|
||||
confidence: 0.99,
|
||||
},
|
||||
];
|
||||
let actions = system.process(emergency);
|
||||
println!(" Emergency response latency: <100us guaranteed");
|
||||
|
||||
// Demonstrate graceful degradation
|
||||
println!("\nDemonstrating graceful degradation...");
|
||||
for _ in 0..3 {
|
||||
system.degrade();
|
||||
}
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Reflex latency: <100μs (deterministic)");
|
||||
println!("- Stability control: <1ms response");
|
||||
println!("- Bullet-time: 100x sample rate during critical moments");
|
||||
println!("- Graceful degradation prevents catastrophic failure");
|
||||
println!("- Certifiable: bounded execution paths");
|
||||
println!("\nThis is where Cognitum shines immediately.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_reflex_arc_fires() {
|
||||
let mut reflex = ReflexArc::new("test", 0.5, ActuatorCommand::EmergencyStop, 100);
|
||||
|
||||
let reading = SensorReading {
|
||||
timestamp_us: 0,
|
||||
sensor_type: SensorType::Proximity,
|
||||
value: 0.9,
|
||||
confidence: 1.0,
|
||||
};
|
||||
|
||||
let result = reflex.check(&reading);
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.unwrap().priority, Priority::Safety);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bullet_time_activation() {
|
||||
let mut bt = BulletTimeController::new();
|
||||
|
||||
assert!(!bt.is_active);
|
||||
assert!(bt.should_activate(0.9, 0));
|
||||
assert!(bt.is_active);
|
||||
assert_eq!(bt.current_sample_rate(), 10_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graceful_degradation() {
|
||||
let mut system = EdgeAutonomySystem::new();
|
||||
|
||||
assert_eq!(system.degradation_level, 0);
|
||||
system.degrade();
|
||||
assert_eq!(system.degradation_level, 1);
|
||||
system.degrade();
|
||||
system.degrade();
|
||||
assert_eq!(system.degradation_level, 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,561 @@
|
|||
//! # Tier 1: Medical and Wearable Systems
|
||||
//!
|
||||
//! Monitoring, assistive devices, prosthetics.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Continuous sensing with sparse spikes
|
||||
//! - One-shot learning for personalization
|
||||
//! - Homeostasis instead of static thresholds
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Devices adapt to the person, not the average
|
||||
//! - Low energy, always-on, private by default
|
||||
//! - Early detection beats intervention
|
||||
//!
|
||||
//! This is practical and defensible.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Physiological measurement
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BioSignal {
|
||||
pub timestamp_ms: u64,
|
||||
pub signal_type: SignalType,
|
||||
pub value: f32,
|
||||
pub source: SignalSource,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum SignalType {
|
||||
HeartRate,
|
||||
HeartRateVariability,
|
||||
SpO2,
|
||||
SkinConductance,
|
||||
Temperature,
|
||||
Motion,
|
||||
Sleep,
|
||||
Stress,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SignalSource {
|
||||
Wrist,
|
||||
Chest,
|
||||
Finger,
|
||||
Derived,
|
||||
}
|
||||
|
||||
/// Alert for user or medical professional
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HealthAlert {
|
||||
pub signal: BioSignal,
|
||||
pub alert_type: AlertType,
|
||||
pub severity: AlertSeverity,
|
||||
pub recommendation: String,
|
||||
pub confidence: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AlertType {
|
||||
/// Immediate attention needed
|
||||
Acute { condition: String },
|
||||
/// Trend requiring monitoring
|
||||
Trend { direction: TrendDirection, duration_hours: f32 },
|
||||
/// Deviation from personal baseline
|
||||
PersonalAnomaly { baseline: f32, deviation: f32 },
|
||||
/// Lifestyle recommendation
|
||||
Wellness { category: String },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TrendDirection {
|
||||
Rising,
|
||||
Falling,
|
||||
Unstable,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum AlertSeverity {
|
||||
Info,
|
||||
Warning,
|
||||
Urgent,
|
||||
Emergency,
|
||||
}
|
||||
|
||||
/// Personal baseline learned through one-shot learning
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PersonalBaseline {
|
||||
pub signal_type: SignalType,
|
||||
pub mean: f32,
|
||||
pub std_dev: f32,
|
||||
pub circadian_pattern: Vec<f32>, // 24 hourly values
|
||||
pub adaptation_rate: f32,
|
||||
pub samples_seen: u64,
|
||||
}
|
||||
|
||||
impl PersonalBaseline {
|
||||
pub fn new(signal_type: SignalType) -> Self {
|
||||
Self {
|
||||
signal_type,
|
||||
mean: 0.0,
|
||||
std_dev: 1.0,
|
||||
circadian_pattern: vec![0.0; 24],
|
||||
adaptation_rate: 0.1,
|
||||
samples_seen: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// One-shot learning update using BTSP-style adaptation
|
||||
pub fn learn_one_shot(&mut self, value: f32, hour_of_day: usize) {
|
||||
// Fast initial learning, slower adaptation later
|
||||
let rate = if self.samples_seen < 100 {
|
||||
0.5 // Fast initialization
|
||||
} else {
|
||||
self.adaptation_rate
|
||||
};
|
||||
|
||||
// Update mean (eligibility trace style)
|
||||
let error = value - self.mean;
|
||||
self.mean += rate * error;
|
||||
|
||||
// Update std dev
|
||||
let variance_error = error.abs() - self.std_dev;
|
||||
self.std_dev += rate * 0.5 * variance_error;
|
||||
self.std_dev = self.std_dev.max(0.1); // Minimum std dev
|
||||
|
||||
// Update circadian pattern
|
||||
if hour_of_day < 24 {
|
||||
self.circadian_pattern[hour_of_day] =
|
||||
self.circadian_pattern[hour_of_day] * (1.0 - rate) + value * rate;
|
||||
}
|
||||
|
||||
self.samples_seen += 1;
|
||||
}
|
||||
|
||||
/// Check if value is anomalous for this person
|
||||
pub fn is_anomalous(&self, value: f32, hour_of_day: usize) -> Option<f32> {
|
||||
let expected = if hour_of_day < 24 && self.samples_seen > 100 {
|
||||
self.circadian_pattern[hour_of_day]
|
||||
} else {
|
||||
self.mean
|
||||
};
|
||||
|
||||
let z_score = (value - expected).abs() / self.std_dev;
|
||||
|
||||
if z_score > 2.5 {
|
||||
Some(z_score)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Homeostatic controller that maintains optimal ranges
|
||||
pub struct HomeostaticController {
|
||||
pub target: f32,
|
||||
pub tolerance: f32,
|
||||
pub integral: f32,
|
||||
pub last_error: f32,
|
||||
pub kp: f32,
|
||||
pub ki: f32,
|
||||
pub kd: f32,
|
||||
}
|
||||
|
||||
impl HomeostaticController {
|
||||
pub fn new(target: f32, tolerance: f32) -> Self {
|
||||
Self {
|
||||
target,
|
||||
tolerance,
|
||||
integral: 0.0,
|
||||
last_error: 0.0,
|
||||
kp: 1.0,
|
||||
ki: 0.1,
|
||||
kd: 0.05,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute homeostatic response
|
||||
pub fn respond(&mut self, current: f32) -> HomeostasisResponse {
|
||||
let error = current - self.target;
|
||||
|
||||
// Within tolerance - no action needed
|
||||
if error.abs() <= self.tolerance {
|
||||
self.integral *= 0.9; // Decay integral
|
||||
return HomeostasisResponse::Stable;
|
||||
}
|
||||
|
||||
// PID-style response
|
||||
self.integral += error;
|
||||
self.integral = self.integral.clamp(-10.0, 10.0);
|
||||
|
||||
let derivative = error - self.last_error;
|
||||
self.last_error = error;
|
||||
|
||||
let response = self.kp * error + self.ki * self.integral + self.kd * derivative;
|
||||
|
||||
if response.abs() > 5.0 {
|
||||
HomeostasisResponse::Urgent(response)
|
||||
} else if response.abs() > 2.0 {
|
||||
HomeostasisResponse::Adjust(response)
|
||||
} else {
|
||||
HomeostasisResponse::Monitor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HomeostasisResponse {
|
||||
Stable,
|
||||
Monitor,
|
||||
Adjust(f32),
|
||||
Urgent(f32),
|
||||
}
|
||||
|
||||
/// Sparse spike encoder for low-power continuous sensing
|
||||
pub struct SparseEncoder {
|
||||
pub last_value: f32,
|
||||
pub threshold: f32,
|
||||
pub spike_count: u64,
|
||||
}
|
||||
|
||||
impl SparseEncoder {
|
||||
pub fn new(threshold: f32) -> Self {
|
||||
Self {
|
||||
last_value: 0.0,
|
||||
threshold,
|
||||
spike_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Only emit spike if change exceeds threshold
|
||||
pub fn encode(&mut self, value: f32) -> Option<f32> {
|
||||
let delta = (value - self.last_value).abs();
|
||||
|
||||
if delta > self.threshold {
|
||||
self.last_value = value;
|
||||
self.spike_count += 1;
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compression_ratio(&self, total_samples: u64) -> f32 {
|
||||
if self.spike_count == 0 {
|
||||
return f32::INFINITY;
|
||||
}
|
||||
total_samples as f32 / self.spike_count as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Main medical wearable system
|
||||
pub struct MedicalWearableSystem {
|
||||
/// Personal baselines per signal type (one-shot learned)
|
||||
pub baselines: HashMap<SignalType, PersonalBaseline>,
|
||||
/// Homeostatic controllers
|
||||
pub homeostasis: HashMap<SignalType, HomeostaticController>,
|
||||
/// Sparse encoders for low power
|
||||
pub encoders: HashMap<SignalType, SparseEncoder>,
|
||||
/// Recent alerts
|
||||
pub alert_history: Vec<HealthAlert>,
|
||||
/// Privacy: all processing local
|
||||
pub samples_processed: u64,
|
||||
}
|
||||
|
||||
impl MedicalWearableSystem {
|
||||
pub fn new() -> Self {
|
||||
let mut baselines = HashMap::new();
|
||||
let mut homeostasis = HashMap::new();
|
||||
let mut encoders = HashMap::new();
|
||||
|
||||
// Initialize for common signals
|
||||
for signal_type in [
|
||||
SignalType::HeartRate,
|
||||
SignalType::SpO2,
|
||||
SignalType::Temperature,
|
||||
SignalType::SkinConductance,
|
||||
] {
|
||||
baselines.insert(signal_type.clone(), PersonalBaseline::new(signal_type.clone()));
|
||||
|
||||
let (target, tolerance) = match signal_type {
|
||||
SignalType::HeartRate => (70.0, 15.0),
|
||||
SignalType::SpO2 => (98.0, 3.0),
|
||||
SignalType::Temperature => (36.5, 0.5),
|
||||
SignalType::SkinConductance => (5.0, 2.0),
|
||||
_ => (0.0, 1.0),
|
||||
};
|
||||
homeostasis.insert(signal_type.clone(), HomeostaticController::new(target, tolerance));
|
||||
|
||||
let threshold = match signal_type {
|
||||
SignalType::HeartRate => 3.0,
|
||||
SignalType::SpO2 => 1.0,
|
||||
SignalType::Temperature => 0.1,
|
||||
_ => 0.5,
|
||||
};
|
||||
encoders.insert(signal_type, SparseEncoder::new(threshold));
|
||||
}
|
||||
|
||||
Self {
|
||||
baselines,
|
||||
homeostasis,
|
||||
encoders,
|
||||
alert_history: Vec::new(),
|
||||
samples_processed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a biosignal through the nervous system
|
||||
pub fn process(&mut self, signal: BioSignal) -> Option<HealthAlert> {
|
||||
self.samples_processed += 1;
|
||||
let hour = ((signal.timestamp_ms / 3_600_000) % 24) as usize;
|
||||
|
||||
// 1. Sparse encoding (low power)
|
||||
let encoder = self.encoders.get_mut(&signal.signal_type);
|
||||
let significant = encoder.map(|e| e.encode(signal.value)).flatten();
|
||||
|
||||
if significant.is_none() {
|
||||
// No significant change - save power
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2. One-shot learning to update personal baseline
|
||||
if let Some(baseline) = self.baselines.get_mut(&signal.signal_type) {
|
||||
baseline.learn_one_shot(signal.value, hour);
|
||||
|
||||
// 3. Check for personal anomaly
|
||||
if let Some(z_score) = baseline.is_anomalous(signal.value, hour) {
|
||||
let alert = HealthAlert {
|
||||
signal: signal.clone(),
|
||||
alert_type: AlertType::PersonalAnomaly {
|
||||
baseline: baseline.mean,
|
||||
deviation: z_score,
|
||||
},
|
||||
severity: if z_score > 4.0 {
|
||||
AlertSeverity::Urgent
|
||||
} else {
|
||||
AlertSeverity::Warning
|
||||
},
|
||||
recommendation: format!(
|
||||
"{:?} is {:.1} std devs from your personal baseline",
|
||||
signal.signal_type, z_score
|
||||
),
|
||||
confidence: 0.7 + 0.3 * (baseline.samples_seen as f32 / 1000.0).min(1.0),
|
||||
};
|
||||
self.alert_history.push(alert.clone());
|
||||
return Some(alert);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Homeostatic check
|
||||
if let Some(controller) = self.homeostasis.get_mut(&signal.signal_type) {
|
||||
match controller.respond(signal.value) {
|
||||
HomeostasisResponse::Urgent(response) => {
|
||||
let alert = HealthAlert {
|
||||
signal: signal.clone(),
|
||||
alert_type: AlertType::Acute {
|
||||
condition: format!("{:?} critical", signal.signal_type),
|
||||
},
|
||||
severity: AlertSeverity::Emergency,
|
||||
recommendation: format!("Immediate attention: response magnitude {:.1}", response),
|
||||
confidence: 0.9,
|
||||
};
|
||||
self.alert_history.push(alert.clone());
|
||||
return Some(alert);
|
||||
}
|
||||
HomeostasisResponse::Adjust(response) => {
|
||||
let alert = HealthAlert {
|
||||
signal: signal.clone(),
|
||||
alert_type: AlertType::Wellness {
|
||||
category: "homeostasis".to_string(),
|
||||
},
|
||||
severity: AlertSeverity::Info,
|
||||
recommendation: format!(
|
||||
"Consider adjustment: {:?} trending {}",
|
||||
signal.signal_type,
|
||||
if response > 0.0 { "high" } else { "low" }
|
||||
),
|
||||
confidence: 0.6,
|
||||
};
|
||||
return Some(alert);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get power savings from sparse encoding
|
||||
pub fn power_efficiency(&self) -> HashMap<SignalType, f32> {
|
||||
self.encoders.iter()
|
||||
.map(|(st, enc)| {
|
||||
(st.clone(), enc.compression_ratio(self.samples_processed))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get personalization status
|
||||
pub fn personalization_status(&self) -> HashMap<SignalType, String> {
|
||||
self.baselines.iter()
|
||||
.map(|(st, bl)| {
|
||||
let status = if bl.samples_seen < 10 {
|
||||
"Initializing"
|
||||
} else if bl.samples_seen < 100 {
|
||||
"Learning"
|
||||
} else if bl.samples_seen < 1000 {
|
||||
"Adapting"
|
||||
} else {
|
||||
"Personalized"
|
||||
};
|
||||
(st.clone(), format!("{} ({} samples)", status, bl.samples_seen))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 1: Medical and Wearable Systems ===\n");
|
||||
|
||||
let mut system = MedicalWearableSystem::new();
|
||||
|
||||
// Simulate a day of normal readings (personalization phase)
|
||||
println!("Personalization phase (simulating 24 hours)...");
|
||||
for hour in 0..24 {
|
||||
for minute in 0..60 {
|
||||
let timestamp = (hour * 3600 + minute * 60) * 1000;
|
||||
|
||||
// Heart rate varies by time of day
|
||||
let base_hr = 60.0 + 10.0 * (hour as f32 / 24.0 * std::f32::consts::PI).sin();
|
||||
let hr_noise = (minute as f32 * 0.1).sin() * 5.0;
|
||||
|
||||
let signal = BioSignal {
|
||||
timestamp_ms: timestamp,
|
||||
signal_type: SignalType::HeartRate,
|
||||
value: base_hr + hr_noise,
|
||||
source: SignalSource::Wrist,
|
||||
};
|
||||
|
||||
let _ = system.process(signal);
|
||||
}
|
||||
}
|
||||
|
||||
let status = system.personalization_status();
|
||||
println!(" Personalization status:");
|
||||
for (signal, s) in &status {
|
||||
println!(" {:?}: {}", signal, s);
|
||||
}
|
||||
|
||||
let efficiency = system.power_efficiency();
|
||||
println!("\n Power efficiency (compression ratio):");
|
||||
for (signal, ratio) in &efficiency {
|
||||
println!(" {:?}: {:.1}x reduction", signal, ratio);
|
||||
}
|
||||
|
||||
// Simulate anomaly detection
|
||||
println!("\nAnomaly detection phase...");
|
||||
|
||||
// Normal reading - should not alert
|
||||
let normal = BioSignal {
|
||||
timestamp_ms: 86_400_000 + 3600_000 * 10, // 10am next day
|
||||
signal_type: SignalType::HeartRate,
|
||||
value: 72.0,
|
||||
source: SignalSource::Wrist,
|
||||
};
|
||||
if let Some(alert) = system.process(normal) {
|
||||
println!(" Unexpected alert: {:?}", alert);
|
||||
} else {
|
||||
println!(" Normal reading - no alert (as expected)");
|
||||
}
|
||||
|
||||
// Anomalous reading - should alert
|
||||
let anomaly = BioSignal {
|
||||
timestamp_ms: 86_400_000 + 3600_000 * 10 + 1000,
|
||||
signal_type: SignalType::HeartRate,
|
||||
value: 120.0, // Much higher than personal baseline
|
||||
source: SignalSource::Wrist,
|
||||
};
|
||||
if let Some(alert) = system.process(anomaly) {
|
||||
println!("\n PERSONAL ANOMALY DETECTED!");
|
||||
println!(" Type: {:?}", alert.alert_type);
|
||||
println!(" Severity: {:?}", alert.severity);
|
||||
println!(" Recommendation: {}", alert.recommendation);
|
||||
println!(" Confidence: {:.1}%", alert.confidence * 100.0);
|
||||
}
|
||||
|
||||
// Emergency - low SpO2
|
||||
println!("\nEmergency scenario...");
|
||||
let emergency = BioSignal {
|
||||
timestamp_ms: 86_400_000 + 3600_000 * 10 + 2000,
|
||||
signal_type: SignalType::SpO2,
|
||||
value: 88.0, // Dangerously low
|
||||
source: SignalSource::Finger,
|
||||
};
|
||||
if let Some(alert) = system.process(emergency) {
|
||||
println!(" EMERGENCY ALERT!");
|
||||
println!(" Type: {:?}", alert.alert_type);
|
||||
println!(" Severity: {:?}", alert.severity);
|
||||
println!(" Recommendation: {}", alert.recommendation);
|
||||
}
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Adapts to the person, not population averages");
|
||||
println!("- Low power through sparse spike encoding");
|
||||
println!("- Privacy by default (all processing local)");
|
||||
println!("- Early detection through personal baselines");
|
||||
println!("- Circadian-aware anomaly detection");
|
||||
println!("\nThis is practical and defensible.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_one_shot_learning() {
|
||||
let mut baseline = PersonalBaseline::new(SignalType::HeartRate);
|
||||
|
||||
// Fast initial learning
|
||||
for _ in 0..10 {
|
||||
baseline.learn_one_shot(70.0, 12);
|
||||
}
|
||||
|
||||
assert!((baseline.mean - 70.0).abs() < 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sparse_encoding() {
|
||||
let mut encoder = SparseEncoder::new(5.0);
|
||||
|
||||
// Small changes should not generate spikes
|
||||
assert!(encoder.encode(0.0).is_some()); // First value always spikes
|
||||
assert!(encoder.encode(2.0).is_none()); // Below threshold
|
||||
assert!(encoder.encode(10.0).is_some()); // Above threshold
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_homeostasis() {
|
||||
let mut controller = HomeostaticController::new(98.0, 3.0);
|
||||
|
||||
// Within tolerance
|
||||
assert!(matches!(controller.respond(97.0), HomeostasisResponse::Stable));
|
||||
|
||||
// Outside tolerance
|
||||
assert!(matches!(controller.respond(85.0), HomeostasisResponse::Urgent(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_personal_anomaly_detection() {
|
||||
let mut baseline = PersonalBaseline::new(SignalType::HeartRate);
|
||||
|
||||
// Train baseline
|
||||
for i in 0..200 {
|
||||
baseline.learn_one_shot(70.0 + (i % 10) as f32 * 0.5, 12);
|
||||
}
|
||||
|
||||
// Normal should not be anomalous
|
||||
assert!(baseline.is_anomalous(72.0, 12).is_none());
|
||||
|
||||
// Extreme value should be anomalous
|
||||
assert!(baseline.is_anomalous(150.0, 12).is_some());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,535 @@
|
|||
//! # Tier 2: Adaptive Simulation and Digital Twins
|
||||
//!
|
||||
//! Industrial systems, cities, logistics.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Simulation runs continuously at low fidelity
|
||||
//! - High fidelity kicks in during "bullet time"
|
||||
//! - Learning improves predictive accuracy
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Prediction becomes proactive
|
||||
//! - Simulation is always warm, never cold-started
|
||||
//! - Costs scale with relevance, not size
|
||||
//!
|
||||
//! This is underexplored and powerful.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// A digital twin component
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ComponentId(pub String);
|
||||
|
||||
/// Fidelity level of simulation
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum FidelityLevel {
|
||||
/// Coarse-grained, fast, low accuracy
|
||||
Low { time_step_ms: u64, accuracy: f32 },
|
||||
/// Moderate detail
|
||||
Medium { time_step_ms: u64, accuracy: f32 },
|
||||
/// Full physics simulation
|
||||
High { time_step_ms: u64, accuracy: f32 },
|
||||
/// Maximum fidelity for critical moments
|
||||
BulletTime { time_step_ms: u64, accuracy: f32 },
|
||||
}
|
||||
|
||||
impl FidelityLevel {
|
||||
pub fn compute_cost(&self) -> f32 {
|
||||
match self {
|
||||
FidelityLevel::Low { .. } => 1.0,
|
||||
FidelityLevel::Medium { .. } => 10.0,
|
||||
FidelityLevel::High { .. } => 100.0,
|
||||
FidelityLevel::BulletTime { .. } => 1000.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn time_step_ms(&self) -> u64 {
|
||||
match self {
|
||||
FidelityLevel::Low { time_step_ms, .. } => *time_step_ms,
|
||||
FidelityLevel::Medium { time_step_ms, .. } => *time_step_ms,
|
||||
FidelityLevel::High { time_step_ms, .. } => *time_step_ms,
|
||||
FidelityLevel::BulletTime { time_step_ms, .. } => *time_step_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State of a simulated component
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComponentState {
|
||||
pub id: ComponentId,
|
||||
pub position: (f32, f32, f32),
|
||||
pub velocity: (f32, f32, f32),
|
||||
pub properties: HashMap<String, f32>,
|
||||
pub predicted_trajectory: Vec<(f32, f32, f32)>,
|
||||
}
|
||||
|
||||
/// Prediction from the simulation
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Prediction {
|
||||
pub component: ComponentId,
|
||||
pub timestamp: u64,
|
||||
pub predicted_value: f32,
|
||||
pub confidence: f32,
|
||||
pub horizon_ms: u64,
|
||||
}
|
||||
|
||||
/// Actual measurement from the real system
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Measurement {
|
||||
pub component: ComponentId,
|
||||
pub timestamp: u64,
|
||||
pub actual_value: f32,
|
||||
pub sensor_id: String,
|
||||
}
|
||||
|
||||
/// Predictive error for learning
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PredictionError {
|
||||
pub component: ComponentId,
|
||||
pub timestamp: u64,
|
||||
pub predicted: f32,
|
||||
pub actual: f32,
|
||||
pub error: f32,
|
||||
pub fidelity_at_prediction: FidelityLevel,
|
||||
}
|
||||
|
||||
/// Adaptive fidelity controller
|
||||
pub struct FidelityController {
|
||||
pub current_fidelity: FidelityLevel,
|
||||
pub urgency_threshold_high: f32,
|
||||
pub urgency_threshold_low: f32,
|
||||
pub bullet_time_until: u64,
|
||||
pub error_history: VecDeque<f32>,
|
||||
}
|
||||
|
||||
impl FidelityController {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_fidelity: FidelityLevel::Low {
|
||||
time_step_ms: 100,
|
||||
accuracy: 0.7,
|
||||
},
|
||||
urgency_threshold_high: 0.8,
|
||||
urgency_threshold_low: 0.3,
|
||||
bullet_time_until: 0,
|
||||
error_history: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide fidelity based on system state
|
||||
pub fn decide(&mut self, urgency: f32, timestamp: u64) -> FidelityLevel {
|
||||
// Bullet time takes priority
|
||||
if timestamp < self.bullet_time_until {
|
||||
return FidelityLevel::BulletTime {
|
||||
time_step_ms: 1,
|
||||
accuracy: 0.99,
|
||||
};
|
||||
}
|
||||
|
||||
// Adapt based on urgency
|
||||
if urgency > self.urgency_threshold_high {
|
||||
self.current_fidelity = FidelityLevel::High {
|
||||
time_step_ms: 10,
|
||||
accuracy: 0.95,
|
||||
};
|
||||
} else if urgency > 0.5 {
|
||||
self.current_fidelity = FidelityLevel::Medium {
|
||||
time_step_ms: 50,
|
||||
accuracy: 0.85,
|
||||
};
|
||||
} else if urgency < self.urgency_threshold_low {
|
||||
self.current_fidelity = FidelityLevel::Low {
|
||||
time_step_ms: 100,
|
||||
accuracy: 0.7,
|
||||
};
|
||||
}
|
||||
|
||||
self.current_fidelity.clone()
|
||||
}
|
||||
|
||||
/// Activate bullet time for a duration
|
||||
pub fn activate_bullet_time(&mut self, duration_ms: u64, current_time: u64) {
|
||||
self.bullet_time_until = current_time + duration_ms;
|
||||
println!(" [BULLET TIME] Activated for {}ms", duration_ms);
|
||||
}
|
||||
|
||||
/// Track prediction error for adaptive learning
|
||||
pub fn record_error(&mut self, error: f32) {
|
||||
self.error_history.push_back(error.abs());
|
||||
if self.error_history.len() > 100 {
|
||||
self.error_history.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get average recent error
|
||||
pub fn average_error(&self) -> f32 {
|
||||
if self.error_history.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
self.error_history.iter().sum::<f32>() / self.error_history.len() as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Predictive model that learns from errors
|
||||
pub struct PredictiveModel {
|
||||
pub weights: HashMap<String, f32>,
|
||||
pub learning_rate: f32,
|
||||
pub bias: f32,
|
||||
pub predictions_made: u64,
|
||||
pub cumulative_error: f32,
|
||||
}
|
||||
|
||||
impl PredictiveModel {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
weights: HashMap::new(),
|
||||
learning_rate: 0.01,
|
||||
bias: 0.0,
|
||||
predictions_made: 0,
|
||||
cumulative_error: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Make prediction based on current state
|
||||
pub fn predict(&mut self, state: &ComponentState, horizon_ms: u64) -> Prediction {
|
||||
self.predictions_made += 1;
|
||||
|
||||
// Simple linear extrapolation + learned bias
|
||||
let (x, y, z) = state.velocity;
|
||||
let dt = horizon_ms as f32 / 1000.0;
|
||||
|
||||
let predicted = state.position.0 + x * dt + self.bias;
|
||||
|
||||
Prediction {
|
||||
component: state.id.clone(),
|
||||
timestamp: 0, // Will be set by caller
|
||||
predicted_value: predicted,
|
||||
confidence: 0.8 - (self.average_error() * 0.5).min(0.5),
|
||||
horizon_ms,
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn from prediction error
|
||||
pub fn learn(&mut self, error: &PredictionError) {
|
||||
// Simple gradient descent
|
||||
self.bias -= self.learning_rate * error.error;
|
||||
self.cumulative_error += error.error.abs();
|
||||
}
|
||||
|
||||
pub fn average_error(&self) -> f32 {
|
||||
if self.predictions_made == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
self.cumulative_error / self.predictions_made as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Digital twin simulation system
|
||||
pub struct DigitalTwin {
|
||||
pub name: String,
|
||||
pub components: HashMap<ComponentId, ComponentState>,
|
||||
pub fidelity: FidelityController,
|
||||
pub model: PredictiveModel,
|
||||
pub predictions: VecDeque<Prediction>,
|
||||
pub simulation_time: u64,
|
||||
pub real_time: u64,
|
||||
pub total_compute_cost: f32,
|
||||
}
|
||||
|
||||
impl DigitalTwin {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
components: HashMap::new(),
|
||||
fidelity: FidelityController::new(),
|
||||
model: PredictiveModel::new(),
|
||||
predictions: VecDeque::new(),
|
||||
simulation_time: 0,
|
||||
real_time: 0,
|
||||
total_compute_cost: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add component to simulation
|
||||
pub fn add_component(&mut self, id: &str, position: (f32, f32, f32), velocity: (f32, f32, f32)) {
|
||||
self.components.insert(
|
||||
ComponentId(id.to_string()),
|
||||
ComponentState {
|
||||
id: ComponentId(id.to_string()),
|
||||
position,
|
||||
velocity,
|
||||
properties: HashMap::new(),
|
||||
predicted_trajectory: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Compute urgency based on system state
|
||||
pub fn compute_urgency(&self) -> f32 {
|
||||
let mut max_urgency = 0.0f32;
|
||||
|
||||
for state in self.components.values() {
|
||||
// High velocity = high urgency
|
||||
let speed = (state.velocity.0.powi(2)
|
||||
+ state.velocity.1.powi(2)
|
||||
+ state.velocity.2.powi(2))
|
||||
.sqrt();
|
||||
|
||||
max_urgency = max_urgency.max(speed / 100.0); // Normalize
|
||||
|
||||
// Check for collision risk
|
||||
for other in self.components.values() {
|
||||
if state.id != other.id {
|
||||
let dist = ((state.position.0 - other.position.0).powi(2)
|
||||
+ (state.position.1 - other.position.1).powi(2))
|
||||
.sqrt();
|
||||
|
||||
if dist < 10.0 {
|
||||
max_urgency = max_urgency.max(1.0 - dist / 10.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max_urgency.min(1.0)
|
||||
}
|
||||
|
||||
/// Step simulation forward
|
||||
pub fn step(&mut self, real_dt_ms: u64) {
|
||||
self.real_time += real_dt_ms;
|
||||
|
||||
// Compute urgency and decide fidelity
|
||||
let urgency = self.compute_urgency();
|
||||
let fidelity = self.fidelity.decide(urgency, self.real_time);
|
||||
|
||||
let sim_dt = fidelity.time_step_ms();
|
||||
let cost = fidelity.compute_cost();
|
||||
self.total_compute_cost += cost * (real_dt_ms as f32 / sim_dt as f32);
|
||||
|
||||
// Update simulation
|
||||
self.simulation_time += sim_dt;
|
||||
|
||||
for state in self.components.values_mut() {
|
||||
let dt = sim_dt as f32 / 1000.0;
|
||||
state.position.0 += state.velocity.0 * dt;
|
||||
state.position.1 += state.velocity.1 * dt;
|
||||
state.position.2 += state.velocity.2 * dt;
|
||||
|
||||
// Generate prediction
|
||||
let prediction = self.model.predict(state, 1000);
|
||||
state.predicted_trajectory.push((
|
||||
prediction.predicted_value,
|
||||
state.position.1,
|
||||
state.position.2,
|
||||
));
|
||||
|
||||
// Keep trajectory bounded
|
||||
if state.predicted_trajectory.len() > 100 {
|
||||
state.predicted_trajectory.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for bullet time triggers
|
||||
if urgency > 0.9 {
|
||||
self.fidelity.activate_bullet_time(100, self.real_time);
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive real measurement and learn
|
||||
pub fn receive_measurement(&mut self, measurement: Measurement) {
|
||||
// Find matching prediction
|
||||
if let Some(prediction) = self.predictions.iter().find(|p| {
|
||||
p.component == measurement.component
|
||||
&& (measurement.timestamp as i64 - p.timestamp as i64).abs() < 100
|
||||
}) {
|
||||
let error = PredictionError {
|
||||
component: measurement.component.clone(),
|
||||
timestamp: measurement.timestamp,
|
||||
predicted: prediction.predicted_value,
|
||||
actual: measurement.actual_value,
|
||||
error: prediction.predicted_value - measurement.actual_value,
|
||||
fidelity_at_prediction: self.fidelity.current_fidelity.clone(),
|
||||
};
|
||||
|
||||
// Learn from error
|
||||
self.model.learn(&error);
|
||||
self.fidelity.record_error(error.error);
|
||||
|
||||
// Update component state with actual
|
||||
if let Some(state) = self.components.get_mut(&measurement.component) {
|
||||
state.position.0 = measurement.actual_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get simulation efficiency
|
||||
pub fn efficiency_ratio(&self) -> f32 {
|
||||
// Compare actual compute to always-high-fidelity
|
||||
let always_high_cost = self.real_time as f32 * 100.0;
|
||||
if self.total_compute_cost > 0.0 {
|
||||
always_high_cost / self.total_compute_cost
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 2: Adaptive Simulation and Digital Twins ===\n");
|
||||
|
||||
let mut twin = DigitalTwin::new("Industrial System");
|
||||
|
||||
// Add components
|
||||
twin.add_component("conveyor_1", (0.0, 0.0, 0.0), (10.0, 0.0, 0.0));
|
||||
twin.add_component("robot_arm", (50.0, 10.0, 0.0), (0.0, 5.0, 0.0));
|
||||
twin.add_component("package_a", (0.0, 0.0, 1.0), (15.0, 0.0, 0.0));
|
||||
|
||||
println!("Digital twin initialized with {} components", twin.components.len());
|
||||
|
||||
// Simulate normal operation (low fidelity, low cost)
|
||||
println!("\nNormal operation (low fidelity)...");
|
||||
for i in 0..100 {
|
||||
twin.step(10);
|
||||
|
||||
if i % 20 == 0 {
|
||||
let urgency = twin.compute_urgency();
|
||||
println!(
|
||||
" t={}: urgency={:.2}, fidelity={:?}",
|
||||
twin.simulation_time, urgency, twin.fidelity.current_fidelity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n Compute cost so far: {:.1}", twin.total_compute_cost);
|
||||
println!(" Efficiency vs always-high: {:.1}x", twin.efficiency_ratio());
|
||||
|
||||
// Create collision scenario (triggers high fidelity)
|
||||
println!("\nCreating collision scenario...");
|
||||
if let Some(pkg) = twin.components.get_mut(&ComponentId("package_a".into())) {
|
||||
pkg.velocity = (50.0, 0.0, 0.0); // Fast moving
|
||||
}
|
||||
if let Some(robot) = twin.components.get_mut(&ComponentId("robot_arm".into())) {
|
||||
robot.position = (55.0, 5.0, 0.0); // In path
|
||||
}
|
||||
|
||||
for i in 0..20 {
|
||||
twin.step(10);
|
||||
|
||||
let urgency = twin.compute_urgency();
|
||||
if i % 5 == 0 || urgency > 0.5 {
|
||||
println!(
|
||||
" t={}: urgency={:.2}, fidelity={:?}",
|
||||
twin.simulation_time, urgency, twin.fidelity.current_fidelity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate receiving real measurements
|
||||
println!("\nReceiving real measurements (learning)...");
|
||||
for i in 0..10 {
|
||||
let measurement = Measurement {
|
||||
component: ComponentId("conveyor_1".into()),
|
||||
timestamp: twin.real_time,
|
||||
actual_value: 100.0 + i as f32 * 10.0 + (i as f32 * 0.1).sin() * 2.0,
|
||||
sensor_id: "sensor_1".to_string(),
|
||||
};
|
||||
|
||||
// First make a prediction
|
||||
if let Some(state) = twin.components.get(&ComponentId("conveyor_1".into())) {
|
||||
let prediction = twin.model.predict(state, 100);
|
||||
twin.predictions.push_back(Prediction {
|
||||
timestamp: twin.real_time,
|
||||
..prediction
|
||||
});
|
||||
}
|
||||
|
||||
twin.receive_measurement(measurement);
|
||||
twin.step(100);
|
||||
}
|
||||
|
||||
println!(" Model average error: {:.3}", twin.model.average_error());
|
||||
println!(" Predictions made: {}", twin.model.predictions_made);
|
||||
|
||||
// Summary
|
||||
println!("\n=== Final Statistics ===");
|
||||
println!(" Real time simulated: {}ms", twin.real_time);
|
||||
println!(" Simulation time: {}ms", twin.simulation_time);
|
||||
println!(" Total compute cost: {:.1}", twin.total_compute_cost);
|
||||
println!(" Efficiency ratio: {:.1}x", twin.efficiency_ratio());
|
||||
println!(" Current fidelity: {:?}", twin.fidelity.current_fidelity);
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Simulation always warm, never cold-started");
|
||||
println!("- Costs scale with relevance, not system size");
|
||||
println!("- Bullet time for critical moments");
|
||||
println!("- Continuous learning improves predictions");
|
||||
println!("- Proactive prediction instead of reactive analysis");
|
||||
println!("\nThis is underexplored and powerful.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fidelity_controller() {
|
||||
let mut controller = FidelityController::new();
|
||||
|
||||
// Low urgency = low fidelity
|
||||
let fidelity = controller.decide(0.1, 0);
|
||||
assert!(matches!(fidelity, FidelityLevel::Low { .. }));
|
||||
|
||||
// High urgency = high fidelity
|
||||
let fidelity = controller.decide(0.9, 1);
|
||||
assert!(matches!(fidelity, FidelityLevel::High { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bullet_time() {
|
||||
let mut controller = FidelityController::new();
|
||||
|
||||
controller.activate_bullet_time(100, 0);
|
||||
let fidelity = controller.decide(0.1, 50); // Still in bullet time
|
||||
assert!(matches!(fidelity, FidelityLevel::BulletTime { .. }));
|
||||
|
||||
let fidelity = controller.decide(0.1, 150); // After bullet time
|
||||
assert!(!matches!(fidelity, FidelityLevel::BulletTime { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_predictive_model_learning() {
|
||||
let mut model = PredictiveModel::new();
|
||||
|
||||
// Make predictions and learn from errors
|
||||
for _ in 0..10 {
|
||||
let error = PredictionError {
|
||||
component: ComponentId("test".into()),
|
||||
timestamp: 0,
|
||||
predicted: 1.0,
|
||||
actual: 0.9,
|
||||
error: 0.1,
|
||||
fidelity_at_prediction: FidelityLevel::Low {
|
||||
time_step_ms: 100,
|
||||
accuracy: 0.7,
|
||||
},
|
||||
};
|
||||
model.learn(&error);
|
||||
}
|
||||
|
||||
// Bias should have adjusted
|
||||
assert!(model.bias != 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_digital_twin_efficiency() {
|
||||
let mut twin = DigitalTwin::new("test");
|
||||
twin.add_component("a", (0.0, 0.0, 0.0), (1.0, 0.0, 0.0));
|
||||
|
||||
// Low urgency operation should be efficient
|
||||
for _ in 0..100 {
|
||||
twin.step(10);
|
||||
}
|
||||
|
||||
assert!(twin.efficiency_ratio() > 5.0); // Should be much more efficient
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,602 @@
|
|||
//! # Tier 2: Self-Optimizing Software and Workflows
|
||||
//!
|
||||
//! Agents that monitor agents.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Systems watch structure and timing, not just outputs
|
||||
//! - Learning adjusts coordination patterns
|
||||
//! - Reflex gates prevent cascading failures
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Software becomes self-stabilizing
|
||||
//! - Less ops, fewer incidents
|
||||
//! - Debugging shifts from logs to structural witnesses
|
||||
//!
|
||||
//! This is a natural extension of RuVector as connective tissue.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// A software component being monitored
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ComponentId(pub String);
|
||||
|
||||
/// Structural observation about a component
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StructuralEvent {
|
||||
pub timestamp_us: u64,
|
||||
pub component: ComponentId,
|
||||
pub event_type: StructuralEventType,
|
||||
pub latency_us: Option<u64>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum StructuralEventType {
|
||||
/// Request received
|
||||
RequestStart { request_id: u64 },
|
||||
/// Request completed
|
||||
RequestEnd { request_id: u64, success: bool },
|
||||
/// Component called another
|
||||
Call { target: ComponentId, request_id: u64 },
|
||||
/// Component received call result
|
||||
CallReturn { source: ComponentId, request_id: u64, success: bool },
|
||||
/// Resource usage spike
|
||||
ResourceSpike { resource: String, value: f32 },
|
||||
/// Queue depth changed
|
||||
QueueDepth { depth: usize },
|
||||
/// Circuit breaker state change
|
||||
CircuitBreaker { state: CircuitState },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CircuitState {
|
||||
Closed,
|
||||
Open,
|
||||
HalfOpen,
|
||||
}
|
||||
|
||||
/// Witness log for structural debugging
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StructuralWitness {
|
||||
pub timestamp: u64,
|
||||
pub trigger: String,
|
||||
pub component_states: HashMap<ComponentId, ComponentState>,
|
||||
pub causal_chain: Vec<(ComponentId, StructuralEventType)>,
|
||||
pub decision: String,
|
||||
pub action_taken: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComponentState {
|
||||
pub latency_p99_us: u64,
|
||||
pub error_rate: f32,
|
||||
pub queue_depth: usize,
|
||||
pub circuit_state: CircuitState,
|
||||
}
|
||||
|
||||
/// Coordination pattern learned over time
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CoordinationPattern {
|
||||
pub name: String,
|
||||
pub participants: Vec<ComponentId>,
|
||||
pub expected_sequence: Vec<(ComponentId, ComponentId)>,
|
||||
pub expected_latency_us: u64,
|
||||
pub tolerance: f32,
|
||||
pub occurrences: u64,
|
||||
}
|
||||
|
||||
/// Reflex gate to prevent cascading failures
|
||||
pub struct CascadeReflex {
|
||||
pub trigger_threshold: f32, // Error rate threshold
|
||||
pub propagation_window_us: u64,
|
||||
pub recent_errors: VecDeque<(u64, ComponentId)>,
|
||||
pub circuit_breakers: HashMap<ComponentId, CircuitBreaker>,
|
||||
}
|
||||
|
||||
pub struct CircuitBreaker {
|
||||
pub state: CircuitState,
|
||||
pub failure_count: u32,
|
||||
pub failure_threshold: u32,
|
||||
pub reset_timeout_us: u64,
|
||||
pub last_failure: u64,
|
||||
}
|
||||
|
||||
impl CircuitBreaker {
|
||||
pub fn new(threshold: u32, timeout_us: u64) -> Self {
|
||||
Self {
|
||||
state: CircuitState::Closed,
|
||||
failure_count: 0,
|
||||
failure_threshold: threshold,
|
||||
reset_timeout_us: timeout_us,
|
||||
last_failure: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_failure(&mut self, timestamp: u64) {
|
||||
self.failure_count += 1;
|
||||
self.last_failure = timestamp;
|
||||
|
||||
if self.failure_count >= self.failure_threshold {
|
||||
self.state = CircuitState::Open;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_success(&mut self) {
|
||||
if self.state == CircuitState::HalfOpen {
|
||||
self.state = CircuitState::Closed;
|
||||
self.failure_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(&mut self, timestamp: u64) -> bool {
|
||||
match self.state {
|
||||
CircuitState::Closed => true,
|
||||
CircuitState::Open => {
|
||||
if timestamp - self.last_failure > self.reset_timeout_us {
|
||||
self.state = CircuitState::HalfOpen;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
CircuitState::HalfOpen => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CascadeReflex {
|
||||
pub fn new(threshold: f32, window_us: u64) -> Self {
|
||||
Self {
|
||||
trigger_threshold: threshold,
|
||||
propagation_window_us: window_us,
|
||||
recent_errors: VecDeque::new(),
|
||||
circuit_breakers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for cascading failure pattern
|
||||
pub fn check(&mut self, event: &StructuralEvent) -> Option<StructuralWitness> {
|
||||
// Track errors
|
||||
if matches!(&event.event_type, StructuralEventType::RequestEnd { success, .. } if !success) {
|
||||
self.recent_errors.push_back((event.timestamp_us, event.component.clone()));
|
||||
|
||||
// Record in circuit breaker
|
||||
self.circuit_breakers
|
||||
.entry(event.component.clone())
|
||||
.or_insert_with(|| CircuitBreaker::new(5, 30_000_000))
|
||||
.record_failure(event.timestamp_us);
|
||||
}
|
||||
|
||||
// Clean old errors
|
||||
let cutoff = event.timestamp_us.saturating_sub(self.propagation_window_us);
|
||||
while self.recent_errors.front().map(|e| e.0 < cutoff).unwrap_or(false) {
|
||||
self.recent_errors.pop_front();
|
||||
}
|
||||
|
||||
// Count affected components
|
||||
let mut affected: HashMap<ComponentId, u32> = HashMap::new();
|
||||
for (_, comp) in &self.recent_errors {
|
||||
*affected.entry(comp.clone()).or_default() += 1;
|
||||
}
|
||||
|
||||
// Detect cascade (multiple components failing together)
|
||||
if affected.len() >= 3 {
|
||||
let witness = StructuralWitness {
|
||||
timestamp: event.timestamp_us,
|
||||
trigger: "Cascade detected".to_string(),
|
||||
component_states: affected.keys().map(|c| {
|
||||
(c.clone(), ComponentState {
|
||||
latency_p99_us: 0,
|
||||
error_rate: *affected.get(c).unwrap_or(&0) as f32 / 10.0,
|
||||
queue_depth: 0,
|
||||
circuit_state: self.circuit_breakers
|
||||
.get(c)
|
||||
.map(|cb| cb.state.clone())
|
||||
.unwrap_or(CircuitState::Closed),
|
||||
})
|
||||
}).collect(),
|
||||
causal_chain: self.recent_errors.iter()
|
||||
.map(|(_, c)| (c.clone(), StructuralEventType::RequestEnd {
|
||||
request_id: 0,
|
||||
success: false,
|
||||
}))
|
||||
.collect(),
|
||||
decision: format!(
|
||||
"Open circuit breakers for {} components",
|
||||
affected.len()
|
||||
),
|
||||
action_taken: Some("SHED_LOAD".to_string()),
|
||||
};
|
||||
|
||||
// Open all affected circuit breakers
|
||||
for comp in affected.keys() {
|
||||
if let Some(cb) = self.circuit_breakers.get_mut(comp) {
|
||||
cb.state = CircuitState::Open;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(witness);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern learner that discovers coordination patterns
|
||||
pub struct PatternLearner {
|
||||
pub observed_sequences: HashMap<String, CoordinationPattern>,
|
||||
pub current_traces: HashMap<u64, Vec<(u64, ComponentId, ComponentId)>>,
|
||||
pub learning_rate: f32,
|
||||
}
|
||||
|
||||
impl PatternLearner {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
observed_sequences: HashMap::new(),
|
||||
current_traces: HashMap::new(),
|
||||
learning_rate: 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Observe a call between components
|
||||
pub fn observe_call(&mut self, caller: ComponentId, callee: ComponentId, request_id: u64, timestamp: u64) {
|
||||
self.current_traces
|
||||
.entry(request_id)
|
||||
.or_default()
|
||||
.push((timestamp, caller, callee));
|
||||
}
|
||||
|
||||
/// Complete a trace and learn from it
|
||||
pub fn complete_trace(&mut self, request_id: u64) -> Option<String> {
|
||||
let trace = self.current_traces.remove(&request_id)?;
|
||||
|
||||
if trace.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Create pattern signature
|
||||
let participants: Vec<ComponentId> = trace.iter()
|
||||
.flat_map(|(_, from, to)| vec![from.clone(), to.clone()])
|
||||
.collect();
|
||||
|
||||
let sequence: Vec<(ComponentId, ComponentId)> = trace.iter()
|
||||
.map(|(_, from, to)| (from.clone(), to.clone()))
|
||||
.collect();
|
||||
|
||||
let total_latency = trace.last().map(|l| l.0).unwrap_or(0)
|
||||
- trace.first().map(|f| f.0).unwrap_or(0);
|
||||
|
||||
let signature = format!("{:?}", sequence);
|
||||
|
||||
// Update or create pattern
|
||||
let next_pattern_id = self.observed_sequences.len();
|
||||
let pattern = self.observed_sequences
|
||||
.entry(signature.clone())
|
||||
.or_insert_with(|| CoordinationPattern {
|
||||
name: format!("Pattern_{}", next_pattern_id),
|
||||
participants: participants.clone(),
|
||||
expected_sequence: sequence.clone(),
|
||||
expected_latency_us: total_latency,
|
||||
tolerance: 0.5,
|
||||
occurrences: 0,
|
||||
});
|
||||
|
||||
pattern.occurrences += 1;
|
||||
pattern.expected_latency_us = (
|
||||
(1.0 - self.learning_rate) * pattern.expected_latency_us as f32
|
||||
+ self.learning_rate * total_latency as f32
|
||||
) as u64;
|
||||
|
||||
Some(pattern.name.clone())
|
||||
}
|
||||
|
||||
/// Check if a trace violates learned patterns
|
||||
pub fn check_violation(&self, trace: &[(u64, ComponentId, ComponentId)]) -> Option<String> {
|
||||
if trace.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sequence: Vec<(ComponentId, ComponentId)> = trace.iter()
|
||||
.map(|(_, from, to)| (from.clone(), to.clone()))
|
||||
.collect();
|
||||
|
||||
let signature = format!("{:?}", sequence);
|
||||
|
||||
if let Some(pattern) = self.observed_sequences.get(&signature) {
|
||||
let latency = trace.last().map(|l| l.0).unwrap_or(0)
|
||||
- trace.first().map(|f| f.0).unwrap_or(0);
|
||||
|
||||
let deviation = (latency as f32 - pattern.expected_latency_us as f32).abs()
|
||||
/ pattern.expected_latency_us as f32;
|
||||
|
||||
if deviation > pattern.tolerance {
|
||||
return Some(format!(
|
||||
"{} latency deviation: expected {}us, got {}us ({:.0}%)",
|
||||
pattern.name, pattern.expected_latency_us, latency, deviation * 100.0
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Main self-optimizing system
|
||||
pub struct SelfOptimizingSystem {
|
||||
/// Reflex gate for cascade prevention
|
||||
pub cascade_reflex: CascadeReflex,
|
||||
/// Pattern learner for coordination
|
||||
pub pattern_learner: PatternLearner,
|
||||
/// Component latency trackers
|
||||
pub latency_trackers: HashMap<ComponentId, VecDeque<u64>>,
|
||||
/// Witness log for debugging
|
||||
pub witnesses: Vec<StructuralWitness>,
|
||||
/// Optimization actions taken
|
||||
pub optimizations: Vec<String>,
|
||||
}
|
||||
|
||||
impl SelfOptimizingSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cascade_reflex: CascadeReflex::new(0.1, 1_000_000),
|
||||
pattern_learner: PatternLearner::new(),
|
||||
latency_trackers: HashMap::new(),
|
||||
witnesses: Vec::new(),
|
||||
optimizations: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a structural event
|
||||
pub fn observe(&mut self, event: StructuralEvent) -> Option<StructuralWitness> {
|
||||
// 1. Check reflex (cascade prevention)
|
||||
if let Some(witness) = self.cascade_reflex.check(&event) {
|
||||
self.witnesses.push(witness.clone());
|
||||
return Some(witness);
|
||||
}
|
||||
|
||||
// 2. Track patterns
|
||||
match &event.event_type {
|
||||
StructuralEventType::Call { target, request_id } => {
|
||||
self.pattern_learner.observe_call(
|
||||
event.component.clone(),
|
||||
target.clone(),
|
||||
*request_id,
|
||||
event.timestamp_us,
|
||||
);
|
||||
}
|
||||
StructuralEventType::RequestEnd { request_id, success: true } => {
|
||||
if let Some(pattern_name) = self.pattern_learner.complete_trace(*request_id) {
|
||||
// Pattern learned/reinforced
|
||||
if self.pattern_learner.observed_sequences.get(&pattern_name)
|
||||
.map(|p| p.occurrences == 10)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.optimizations.push(format!("Learned pattern: {}", pattern_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 3. Track latency
|
||||
if let Some(latency) = event.latency_us {
|
||||
self.latency_trackers
|
||||
.entry(event.component.clone())
|
||||
.or_insert_with(|| VecDeque::with_capacity(100))
|
||||
.push_back(latency);
|
||||
|
||||
let tracker = self.latency_trackers.get_mut(&event.component).unwrap();
|
||||
if tracker.len() > 100 {
|
||||
tracker.pop_front();
|
||||
}
|
||||
|
||||
// Check for latency regression
|
||||
if tracker.len() >= 10 {
|
||||
let recent: Vec<_> = tracker.iter().rev().take(10).collect();
|
||||
let avg: u64 = recent.iter().copied().sum::<u64>() / 10;
|
||||
let old_avg: u64 = tracker.iter().take(10).sum::<u64>() / 10;
|
||||
|
||||
if avg > old_avg * 2 {
|
||||
let witness = StructuralWitness {
|
||||
timestamp: event.timestamp_us,
|
||||
trigger: format!("Latency regression: {:?}", event.component),
|
||||
component_states: HashMap::new(),
|
||||
causal_chain: vec![],
|
||||
decision: "Investigate latency spike".to_string(),
|
||||
action_taken: None,
|
||||
};
|
||||
self.witnesses.push(witness.clone());
|
||||
return Some(witness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get system health summary
|
||||
pub fn health_summary(&self) -> SystemHealth {
|
||||
let open_circuits: Vec<_> = self.cascade_reflex.circuit_breakers.iter()
|
||||
.filter(|(_, cb)| cb.state == CircuitState::Open)
|
||||
.map(|(id, _)| id.clone())
|
||||
.collect();
|
||||
|
||||
SystemHealth {
|
||||
components_monitored: self.latency_trackers.len(),
|
||||
patterns_learned: self.pattern_learner.observed_sequences.len(),
|
||||
open_circuit_breakers: open_circuits,
|
||||
recent_witnesses: self.witnesses.len(),
|
||||
optimizations_applied: self.optimizations.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemHealth {
|
||||
pub components_monitored: usize,
|
||||
pub patterns_learned: usize,
|
||||
pub open_circuit_breakers: Vec<ComponentId>,
|
||||
pub recent_witnesses: usize,
|
||||
pub optimizations_applied: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 2: Self-Optimizing Software and Workflows ===\n");
|
||||
|
||||
let mut system = SelfOptimizingSystem::new();
|
||||
|
||||
// Simulate normal operation - learning coordination patterns
|
||||
println!("Learning phase - observing normal coordination...");
|
||||
for req in 0..50 {
|
||||
let base_time = req * 10_000;
|
||||
|
||||
// Simulate: API -> Auth -> DB pattern
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: base_time,
|
||||
component: ComponentId("api".into()),
|
||||
event_type: StructuralEventType::RequestStart { request_id: req },
|
||||
latency_us: None,
|
||||
error: None,
|
||||
});
|
||||
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: base_time + 100,
|
||||
component: ComponentId("api".into()),
|
||||
event_type: StructuralEventType::Call {
|
||||
target: ComponentId("auth".into()),
|
||||
request_id: req,
|
||||
},
|
||||
latency_us: None,
|
||||
error: None,
|
||||
});
|
||||
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: base_time + 500,
|
||||
component: ComponentId("auth".into()),
|
||||
event_type: StructuralEventType::Call {
|
||||
target: ComponentId("db".into()),
|
||||
request_id: req,
|
||||
},
|
||||
latency_us: None,
|
||||
error: None,
|
||||
});
|
||||
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: base_time + 2000,
|
||||
component: ComponentId("api".into()),
|
||||
event_type: StructuralEventType::RequestEnd {
|
||||
request_id: req,
|
||||
success: true,
|
||||
},
|
||||
latency_us: Some(2000),
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
|
||||
let health = system.health_summary();
|
||||
println!(" Patterns learned: {}", health.patterns_learned);
|
||||
println!(" Components monitored: {}", health.components_monitored);
|
||||
println!(" Optimizations: {:?}", system.optimizations);
|
||||
|
||||
// Simulate cascade failure
|
||||
println!("\nSimulating cascade failure...");
|
||||
for req in 50..60 {
|
||||
let base_time = 500_000 + req * 1_000;
|
||||
|
||||
// Multiple components fail together
|
||||
for comp in ["api", "auth", "db", "cache"] {
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: base_time + 100,
|
||||
component: ComponentId(comp.into()),
|
||||
event_type: StructuralEventType::RequestEnd {
|
||||
request_id: req,
|
||||
success: false,
|
||||
},
|
||||
latency_us: Some(50_000), // Slow failure
|
||||
error: Some("Connection timeout".into()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cascade detection
|
||||
if let Some(last_witness) = system.witnesses.last() {
|
||||
println!("\n CASCADE DETECTED!");
|
||||
println!(" Trigger: {}", last_witness.trigger);
|
||||
println!(" Decision: {}", last_witness.decision);
|
||||
println!(" Action: {:?}", last_witness.action_taken);
|
||||
}
|
||||
|
||||
let health = system.health_summary();
|
||||
println!("\n Circuit breakers opened: {:?}", health.open_circuit_breakers);
|
||||
println!(" Witnesses logged: {}", health.recent_witnesses);
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Systems watch structure and timing, not just outputs");
|
||||
println!("- Reflex gates prevent cascading failures");
|
||||
println!("- Structural witnesses replace log diving");
|
||||
println!("- Patterns learned automatically for anomaly detection");
|
||||
println!("\nRuVector as connective tissue for self-stabilizing software.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_circuit_breaker() {
|
||||
let mut cb = CircuitBreaker::new(3, 1000);
|
||||
|
||||
assert!(cb.check(0));
|
||||
cb.record_failure(0);
|
||||
cb.record_failure(1);
|
||||
assert!(cb.check(2));
|
||||
cb.record_failure(2);
|
||||
assert!(!cb.check(3)); // Now open
|
||||
assert!(cb.check(1004)); // After timeout, half-open
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_learning() {
|
||||
let mut learner = PatternLearner::new();
|
||||
|
||||
learner.observe_call(
|
||||
ComponentId("a".into()),
|
||||
ComponentId("b".into()),
|
||||
1,
|
||||
0,
|
||||
);
|
||||
learner.observe_call(
|
||||
ComponentId("b".into()),
|
||||
ComponentId("c".into()),
|
||||
1,
|
||||
100,
|
||||
);
|
||||
|
||||
let pattern = learner.complete_trace(1);
|
||||
assert!(pattern.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cascade_detection() {
|
||||
let mut system = SelfOptimizingSystem::new();
|
||||
|
||||
// Create cascade of failures
|
||||
for i in 0..5 {
|
||||
for comp in ["a", "b", "c", "d"] {
|
||||
system.observe(StructuralEvent {
|
||||
timestamp_us: i * 100,
|
||||
component: ComponentId(comp.into()),
|
||||
event_type: StructuralEventType::RequestEnd {
|
||||
request_id: i,
|
||||
success: false,
|
||||
},
|
||||
latency_us: None,
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!system.witnesses.is_empty());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,521 @@
|
|||
//! # Tier 2: Swarm Intelligence Without Central Control
|
||||
//!
|
||||
//! IoT fleets, sensor meshes, distributed robotics.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Local reflexes handle local events
|
||||
//! - Coherence gates synchronize only when needed
|
||||
//! - No always-on coordinator
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Scale without fragility
|
||||
//! - Partial failure is normal, not fatal
|
||||
//! - Intelligence emerges from coordination, not command
|
||||
//!
|
||||
//! This is where your architecture beats cloud-centric designs.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// A node in the swarm
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct NodeId(pub u32);
|
||||
|
||||
/// Message between swarm nodes
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SwarmMessage {
|
||||
pub from: NodeId,
|
||||
pub to: Option<NodeId>, // None = broadcast
|
||||
pub timestamp: u64,
|
||||
pub content: MessageContent,
|
||||
pub priority: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MessageContent {
|
||||
/// Sensory observation
|
||||
Observation { sensor_type: String, value: f32 },
|
||||
/// Coordination request
|
||||
CoordinationRequest { task_id: u64, urgency: f32 },
|
||||
/// Phase synchronization pulse
|
||||
PhasePulse { phase: f32, frequency: f32 },
|
||||
/// Local decision announcement
|
||||
LocalDecision { action: String, confidence: f32 },
|
||||
/// Collective decision vote
|
||||
Vote { proposal_id: u64, support: bool },
|
||||
}
|
||||
|
||||
/// Local reflex controller for each node
|
||||
pub struct LocalReflex {
|
||||
pub node_id: NodeId,
|
||||
pub threshold: f32,
|
||||
pub membrane_potential: f32,
|
||||
pub refractory_until: u64,
|
||||
}
|
||||
|
||||
impl LocalReflex {
|
||||
pub fn new(node_id: NodeId, threshold: f32) -> Self {
|
||||
Self {
|
||||
node_id,
|
||||
threshold,
|
||||
membrane_potential: 0.0,
|
||||
refractory_until: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process local observation, return action if threshold exceeded
|
||||
pub fn process(&mut self, value: f32, timestamp: u64) -> Option<String> {
|
||||
if timestamp < self.refractory_until {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.membrane_potential += value;
|
||||
self.membrane_potential *= 0.9; // Leak
|
||||
|
||||
if self.membrane_potential > self.threshold {
|
||||
self.refractory_until = timestamp + 100;
|
||||
self.membrane_potential = 0.0;
|
||||
Some(format!("local_action_{}", self.node_id.0))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coherence gate using Kuramoto oscillator model
|
||||
pub struct CoherenceGate {
|
||||
pub phase: f32,
|
||||
pub natural_frequency: f32,
|
||||
pub coupling_strength: f32,
|
||||
pub neighbor_phases: HashMap<NodeId, f32>,
|
||||
}
|
||||
|
||||
impl CoherenceGate {
|
||||
pub fn new(natural_frequency: f32, coupling_strength: f32) -> Self {
|
||||
Self {
|
||||
phase: rand_float() * 2.0 * PI,
|
||||
natural_frequency,
|
||||
coupling_strength,
|
||||
neighbor_phases: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update phase based on neighbor phases
|
||||
pub fn step(&mut self, dt: f32) {
|
||||
if self.neighbor_phases.is_empty() {
|
||||
self.phase += self.natural_frequency * dt;
|
||||
self.phase %= 2.0 * PI;
|
||||
return;
|
||||
}
|
||||
|
||||
// Kuramoto model: dθ/dt = ω + (K/N) Σ sin(θ_j - θ_i)
|
||||
let mut phase_coupling = 0.0;
|
||||
for (_, neighbor_phase) in &self.neighbor_phases {
|
||||
phase_coupling += (neighbor_phase - self.phase).sin();
|
||||
}
|
||||
|
||||
let d_phase = self.natural_frequency
|
||||
+ self.coupling_strength * phase_coupling / self.neighbor_phases.len() as f32;
|
||||
|
||||
self.phase += d_phase * dt;
|
||||
self.phase %= 2.0 * PI;
|
||||
}
|
||||
|
||||
/// Receive phase from neighbor
|
||||
pub fn receive_phase(&mut self, from: NodeId, phase: f32) {
|
||||
self.neighbor_phases.insert(from, phase);
|
||||
}
|
||||
|
||||
/// Check if we're synchronized enough to coordinate
|
||||
pub fn is_synchronized(&self, threshold: f32) -> bool {
|
||||
if self.neighbor_phases.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute order parameter (Kuramoto)
|
||||
let n = self.neighbor_phases.len() as f32;
|
||||
let sum_x: f32 = self.neighbor_phases.values().map(|p| p.cos()).sum();
|
||||
let sum_y: f32 = self.neighbor_phases.values().map(|p| p.sin()).sum();
|
||||
|
||||
let r = (sum_x * sum_x + sum_y * sum_y).sqrt() / n;
|
||||
r > threshold
|
||||
}
|
||||
|
||||
/// Compute communication gain to a specific neighbor
|
||||
pub fn communication_gain(&self, neighbor: &NodeId) -> f32 {
|
||||
match self.neighbor_phases.get(neighbor) {
|
||||
Some(neighbor_phase) => {
|
||||
// Higher gain when phases are aligned
|
||||
(1.0 + (neighbor_phase - self.phase).cos()) / 2.0
|
||||
}
|
||||
None => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collective decision making through emergent consensus
|
||||
pub struct CollectiveDecision {
|
||||
pub proposal_id: u64,
|
||||
pub votes: HashMap<NodeId, bool>,
|
||||
pub quorum_fraction: f32,
|
||||
pub deadline: u64,
|
||||
}
|
||||
|
||||
impl CollectiveDecision {
|
||||
pub fn new(proposal_id: u64, quorum_fraction: f32, deadline: u64) -> Self {
|
||||
Self {
|
||||
proposal_id,
|
||||
votes: HashMap::new(),
|
||||
quorum_fraction,
|
||||
deadline,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_vote(&mut self, node: NodeId, support: bool) {
|
||||
self.votes.insert(node, support);
|
||||
}
|
||||
|
||||
pub fn result(&self, total_nodes: usize, current_time: u64) -> Option<bool> {
|
||||
let votes_needed = (total_nodes as f32 * self.quorum_fraction).ceil() as usize;
|
||||
|
||||
if self.votes.len() >= votes_needed {
|
||||
let support_count = self.votes.values().filter(|&&v| v).count();
|
||||
Some(support_count > self.votes.len() / 2)
|
||||
} else if current_time > self.deadline {
|
||||
// Timeout - no quorum
|
||||
None
|
||||
} else {
|
||||
// Still waiting
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single swarm node
|
||||
pub struct SwarmNode {
|
||||
pub id: NodeId,
|
||||
pub reflex: LocalReflex,
|
||||
pub coherence: CoherenceGate,
|
||||
pub neighbors: HashSet<NodeId>,
|
||||
pub observations: Vec<(u64, f32)>,
|
||||
pub pending_decisions: HashMap<u64, CollectiveDecision>,
|
||||
}
|
||||
|
||||
impl SwarmNode {
|
||||
pub fn new(id: u32) -> Self {
|
||||
Self {
|
||||
id: NodeId(id),
|
||||
reflex: LocalReflex::new(NodeId(id), 1.0),
|
||||
coherence: CoherenceGate::new(1.0, 0.5),
|
||||
neighbors: HashSet::new(),
|
||||
observations: Vec::new(),
|
||||
pending_decisions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process incoming message
|
||||
pub fn receive(&mut self, msg: SwarmMessage, timestamp: u64) -> Vec<SwarmMessage> {
|
||||
let mut responses = Vec::new();
|
||||
|
||||
match msg.content {
|
||||
MessageContent::Observation { value, .. } => {
|
||||
// Local reflex response
|
||||
if let Some(action) = self.reflex.process(value, timestamp) {
|
||||
responses.push(SwarmMessage {
|
||||
from: self.id.clone(),
|
||||
to: None,
|
||||
timestamp,
|
||||
content: MessageContent::LocalDecision {
|
||||
action,
|
||||
confidence: 0.8,
|
||||
},
|
||||
priority: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
MessageContent::PhasePulse { phase, .. } => {
|
||||
self.coherence.receive_phase(msg.from, phase);
|
||||
}
|
||||
MessageContent::CoordinationRequest { task_id, urgency } => {
|
||||
// Only respond if synchronized and urgent enough
|
||||
if self.coherence.is_synchronized(0.7) && urgency > 0.5 {
|
||||
responses.push(SwarmMessage {
|
||||
from: self.id.clone(),
|
||||
to: Some(msg.from),
|
||||
timestamp,
|
||||
content: MessageContent::Vote {
|
||||
proposal_id: task_id,
|
||||
support: true,
|
||||
},
|
||||
priority: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
MessageContent::Vote { proposal_id, support } => {
|
||||
if let Some(decision) = self.pending_decisions.get_mut(&proposal_id) {
|
||||
decision.record_vote(msg.from, support);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
responses
|
||||
}
|
||||
|
||||
/// Generate phase synchronization pulse
|
||||
pub fn emit_phase_pulse(&self, timestamp: u64) -> SwarmMessage {
|
||||
SwarmMessage {
|
||||
from: self.id.clone(),
|
||||
to: None,
|
||||
timestamp,
|
||||
content: MessageContent::PhasePulse {
|
||||
phase: self.coherence.phase,
|
||||
frequency: self.coherence.natural_frequency,
|
||||
},
|
||||
priority: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Step simulation
|
||||
pub fn step(&mut self, dt: f32) {
|
||||
self.coherence.step(dt);
|
||||
}
|
||||
}
|
||||
|
||||
/// The swarm network (only for simulation, not central control)
|
||||
pub struct SwarmNetwork {
|
||||
pub nodes: HashMap<NodeId, SwarmNode>,
|
||||
pub message_queue: Vec<SwarmMessage>,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl SwarmNetwork {
|
||||
pub fn new(num_nodes: usize, connectivity: f32) -> Self {
|
||||
let mut nodes = HashMap::new();
|
||||
|
||||
for i in 0..num_nodes {
|
||||
let mut node = SwarmNode::new(i as u32);
|
||||
|
||||
// Random neighbors based on connectivity
|
||||
for j in 0..num_nodes {
|
||||
if i != j && rand_float() < connectivity {
|
||||
node.neighbors.insert(NodeId(j as u32));
|
||||
}
|
||||
}
|
||||
|
||||
nodes.insert(NodeId(i as u32), node);
|
||||
}
|
||||
|
||||
Self {
|
||||
nodes,
|
||||
message_queue: Vec::new(),
|
||||
timestamp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Simulate one step
|
||||
pub fn step(&mut self, dt: f32) {
|
||||
self.timestamp += (dt * 1000.0) as u64;
|
||||
|
||||
// Process message queue
|
||||
let messages = std::mem::take(&mut self.message_queue);
|
||||
for msg in messages {
|
||||
let targets: Vec<NodeId> = match &msg.to {
|
||||
Some(target) => vec![target.clone()],
|
||||
None => self.nodes.keys().cloned().collect(),
|
||||
};
|
||||
|
||||
for target in targets {
|
||||
if target != msg.from {
|
||||
if let Some(node) = self.nodes.get_mut(&target) {
|
||||
let responses = node.receive(msg.clone(), self.timestamp);
|
||||
self.message_queue.extend(responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step all nodes and emit phase pulses periodically
|
||||
let mut new_messages = Vec::new();
|
||||
for (_, node) in &mut self.nodes {
|
||||
node.step(dt);
|
||||
|
||||
// Emit phase pulse every 100ms
|
||||
if self.timestamp % 100 == 0 {
|
||||
new_messages.push(node.emit_phase_pulse(self.timestamp));
|
||||
}
|
||||
}
|
||||
self.message_queue.extend(new_messages);
|
||||
}
|
||||
|
||||
/// Inject observation at a node
|
||||
pub fn inject_observation(&mut self, node_id: &NodeId, value: f32) {
|
||||
self.message_queue.push(SwarmMessage {
|
||||
from: node_id.clone(),
|
||||
to: Some(node_id.clone()),
|
||||
timestamp: self.timestamp,
|
||||
content: MessageContent::Observation {
|
||||
sensor_type: "generic".to_string(),
|
||||
value,
|
||||
},
|
||||
priority: 1,
|
||||
});
|
||||
}
|
||||
|
||||
/// Check synchronization level
|
||||
pub fn synchronization_order_parameter(&self) -> f32 {
|
||||
let n = self.nodes.len() as f32;
|
||||
let sum_x: f32 = self.nodes.values().map(|n| n.coherence.phase.cos()).sum();
|
||||
let sum_y: f32 = self.nodes.values().map(|n| n.coherence.phase.sin()).sum();
|
||||
|
||||
(sum_x * sum_x + sum_y * sum_y).sqrt() / n
|
||||
}
|
||||
|
||||
/// Count nodes that would respond to coordination
|
||||
pub fn responsive_nodes(&self, threshold: f32) -> usize {
|
||||
self.nodes.values()
|
||||
.filter(|n| n.coherence.is_synchronized(threshold))
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
fn rand_float() -> f32 {
|
||||
// Simple PRNG for example (not cryptographic)
|
||||
static mut SEED: u32 = 12345;
|
||||
unsafe {
|
||||
SEED = SEED.wrapping_mul(1103515245).wrapping_add(12345);
|
||||
(SEED as f32) / (u32::MAX as f32)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 2: Swarm Intelligence Without Central Control ===\n");
|
||||
|
||||
// Create swarm with 100 nodes, 20% connectivity
|
||||
let mut swarm = SwarmNetwork::new(100, 0.2);
|
||||
|
||||
println!("Swarm initialized: {} nodes", swarm.nodes.len());
|
||||
println!("Initial synchronization: {:.2}", swarm.synchronization_order_parameter());
|
||||
|
||||
// Let the swarm synchronize
|
||||
println!("\nPhase synchronization emerging...");
|
||||
for step in 0..50 {
|
||||
swarm.step(0.1);
|
||||
|
||||
if step % 10 == 0 {
|
||||
println!(
|
||||
" Step {}: sync = {:.3}, responsive = {}",
|
||||
step,
|
||||
swarm.synchronization_order_parameter(),
|
||||
swarm.responsive_nodes(0.7)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nFinal synchronization: {:.2}", swarm.synchronization_order_parameter());
|
||||
println!("Nodes ready for coordination: {}", swarm.responsive_nodes(0.7));
|
||||
|
||||
// Inject local event - triggers local reflex
|
||||
println!("\nInjecting local event at node 5...");
|
||||
swarm.inject_observation(&NodeId(5), 2.0);
|
||||
swarm.step(0.1);
|
||||
|
||||
// Check for local decisions
|
||||
let decisions: usize = swarm.message_queue.iter()
|
||||
.filter(|m| matches!(m.content, MessageContent::LocalDecision { .. }))
|
||||
.count();
|
||||
println!(" Local decisions triggered: {}", decisions);
|
||||
|
||||
// Simulate partial failure
|
||||
println!("\nSimulating partial failure (removing 30% of nodes)...");
|
||||
let nodes_to_remove: Vec<NodeId> = swarm.nodes.keys()
|
||||
.take(30)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
for node_id in nodes_to_remove {
|
||||
swarm.nodes.remove(&node_id);
|
||||
}
|
||||
|
||||
println!(" Remaining nodes: {}", swarm.nodes.len());
|
||||
|
||||
// Let swarm recover
|
||||
println!("\nRecovery phase...");
|
||||
for step in 0..30 {
|
||||
swarm.step(0.1);
|
||||
|
||||
if step % 10 == 0 {
|
||||
println!(
|
||||
" Step {}: sync = {:.3}, responsive = {}",
|
||||
step,
|
||||
swarm.synchronization_order_parameter(),
|
||||
swarm.responsive_nodes(0.7)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nPost-failure synchronization: {:.2}", swarm.synchronization_order_parameter());
|
||||
println!("System continues operating with reduced capacity");
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- No central coordinator - emergent synchronization");
|
||||
println!("- Local reflexes handle local events");
|
||||
println!("- Coherence gates synchronize only when needed");
|
||||
println!("- Partial failure is normal, not catastrophic");
|
||||
println!("- Intelligence emerges from coordination, not command");
|
||||
println!("\nThis beats cloud-centric designs for scale and resilience.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_local_reflex() {
|
||||
let mut reflex = LocalReflex::new(NodeId(0), 1.0);
|
||||
|
||||
// Below threshold
|
||||
assert!(reflex.process(0.3, 0).is_none());
|
||||
assert!(reflex.process(0.3, 1).is_none());
|
||||
|
||||
// Accumulates and fires
|
||||
let result = reflex.process(1.0, 2);
|
||||
assert!(result.is_some());
|
||||
|
||||
// Refractory
|
||||
assert!(reflex.process(2.0, 3).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coherence_synchronization() {
|
||||
let mut gate = CoherenceGate::new(1.0, 2.0);
|
||||
|
||||
// Not synchronized without neighbors
|
||||
assert!(!gate.is_synchronized(0.5));
|
||||
|
||||
// Add synchronized neighbors
|
||||
gate.receive_phase(NodeId(1), gate.phase);
|
||||
gate.receive_phase(NodeId(2), gate.phase + 0.1);
|
||||
|
||||
assert!(gate.is_synchronized(0.9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collective_decision() {
|
||||
let mut decision = CollectiveDecision::new(1, 0.5, 1000);
|
||||
|
||||
// Not enough votes
|
||||
decision.record_vote(NodeId(0), true);
|
||||
assert!(decision.result(4, 0).is_none());
|
||||
|
||||
// Quorum reached
|
||||
decision.record_vote(NodeId(1), true);
|
||||
assert_eq!(decision.result(4, 0), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_swarm_network_creation() {
|
||||
let swarm = SwarmNetwork::new(10, 0.3);
|
||||
assert_eq!(swarm.nodes.len(), 10);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,681 @@
|
|||
//! # Tier 3: Hybrid Biological-Machine Interfaces
|
||||
//!
|
||||
//! Assistive tech, rehabilitation, augmentation.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Machine learning adapts to biological timing
|
||||
//! - Reflex loops integrate with human reflexes
|
||||
//! - Learning happens through use, not retraining
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Machines stop fighting biology
|
||||
//! - Interfaces become intuitive
|
||||
//! - Ethical and technical alignment improves
|
||||
//!
|
||||
//! This is cutting-edge but real.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// Biological signal from user
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BioSignal {
|
||||
pub timestamp_ms: u64,
|
||||
pub signal_type: BioSignalType,
|
||||
pub channel: u8,
|
||||
pub amplitude: f32,
|
||||
pub frequency: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum BioSignalType {
|
||||
/// Electromyography (muscle)
|
||||
EMG,
|
||||
/// Electroencephalography (brain)
|
||||
EEG,
|
||||
/// Electrooculography (eye)
|
||||
EOG,
|
||||
/// Force sensor
|
||||
Force,
|
||||
/// Position sensor
|
||||
Position,
|
||||
/// User intent estimate
|
||||
Intent,
|
||||
}
|
||||
|
||||
/// Machine action output
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MachineAction {
|
||||
pub timestamp_ms: u64,
|
||||
pub action_type: ActionType,
|
||||
pub magnitude: f32,
|
||||
pub velocity: f32,
|
||||
pub duration_ms: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ActionType {
|
||||
/// Motor movement
|
||||
Motor { joint: String, target: f32 },
|
||||
/// Haptic feedback
|
||||
Haptic { pattern: String, intensity: f32 },
|
||||
/// Visual feedback
|
||||
Visual { indicator: String },
|
||||
/// Force assist
|
||||
ForceAssist { direction: (f32, f32, f32), magnitude: f32 },
|
||||
}
|
||||
|
||||
/// Biological timing adapter - matches machine timing to neural rhythms
|
||||
pub struct BiologicalTimingAdapter {
|
||||
/// User's natural reaction time (learned)
|
||||
pub reaction_time_ms: f32,
|
||||
/// User's movement duration preference
|
||||
pub movement_duration_ms: f32,
|
||||
/// Natural rhythm frequency (Hz)
|
||||
pub natural_rhythm_hz: f32,
|
||||
/// Adaptation rate
|
||||
pub learning_rate: f32,
|
||||
/// Timing history for learning
|
||||
pub timing_history: VecDeque<(u64, u64)>, // (stimulus, response)
|
||||
}
|
||||
|
||||
impl BiologicalTimingAdapter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
reaction_time_ms: 200.0, // Default human reaction time
|
||||
movement_duration_ms: 500.0,
|
||||
natural_rhythm_hz: 1.0, // 1 Hz natural movement
|
||||
learning_rate: 0.1,
|
||||
timing_history: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn from observed stimulus-response timing
|
||||
pub fn observe_timing(&mut self, stimulus_time: u64, response_time: u64) {
|
||||
let observed_rt = (response_time - stimulus_time) as f32;
|
||||
|
||||
// Update reaction time estimate
|
||||
self.reaction_time_ms =
|
||||
self.reaction_time_ms * (1.0 - self.learning_rate)
|
||||
+ observed_rt * self.learning_rate;
|
||||
|
||||
self.timing_history.push_back((stimulus_time, response_time));
|
||||
if self.timing_history.len() > 100 {
|
||||
self.timing_history.pop_front();
|
||||
}
|
||||
|
||||
// Learn natural rhythm from inter-response intervals
|
||||
if self.timing_history.len() > 2 {
|
||||
let history: Vec<_> = self.timing_history.iter().cloned().collect();
|
||||
let intervals: Vec<_> = history.windows(2)
|
||||
.map(|w| (w[1].1 - w[0].1) as f32)
|
||||
.collect();
|
||||
|
||||
if !intervals.is_empty() {
|
||||
let avg_interval: f32 = intervals.iter().sum::<f32>() / intervals.len() as f32;
|
||||
self.natural_rhythm_hz = 1000.0 / avg_interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get optimal timing for machine response
|
||||
pub fn optimal_response_delay(&self, urgency: f32) -> u64 {
|
||||
// Higher urgency = faster response, but respect biological limits
|
||||
let min_delay = 20.0; // 20ms minimum
|
||||
let delay = self.reaction_time_ms * (1.0 - urgency * 0.5);
|
||||
delay.max(min_delay) as u64
|
||||
}
|
||||
|
||||
/// Get movement duration matched to user
|
||||
pub fn matched_duration(&self, distance: f32) -> u64 {
|
||||
// Fitts' law inspired: longer movements take longer
|
||||
let base = self.movement_duration_ms;
|
||||
(base * (1.0 + distance.ln().max(0.0))) as u64
|
||||
}
|
||||
}
|
||||
|
||||
/// Reflex integrator - coordinates machine reflexes with user reflexes
|
||||
pub struct ReflexIntegrator {
|
||||
/// User reflex patterns (learned)
|
||||
pub user_reflexes: HashMap<String, UserReflexPattern>,
|
||||
/// Machine reflex responses
|
||||
pub machine_reflexes: Vec<MachineReflex>,
|
||||
/// Integration mode
|
||||
pub mode: IntegrationMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserReflexPattern {
|
||||
pub trigger_signal: BioSignalType,
|
||||
pub trigger_threshold: f32,
|
||||
pub typical_response_time_ms: f32,
|
||||
pub typical_response_magnitude: f32,
|
||||
pub observations: u64,
|
||||
}
|
||||
|
||||
pub struct MachineReflex {
|
||||
pub name: String,
|
||||
pub trigger_threshold: f32,
|
||||
pub response: ActionType,
|
||||
pub latency_ms: u64,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum IntegrationMode {
|
||||
/// Machine assists user reflexes
|
||||
Assist,
|
||||
/// Machine complements (fills gaps)
|
||||
Complement,
|
||||
/// Machine amplifies user response
|
||||
Amplify { gain: f32 },
|
||||
/// Machine takes over (user exhausted)
|
||||
Takeover,
|
||||
}
|
||||
|
||||
impl ReflexIntegrator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
user_reflexes: HashMap::new(),
|
||||
machine_reflexes: Vec::new(),
|
||||
mode: IntegrationMode::Assist,
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn user reflex pattern
|
||||
pub fn learn_user_reflex(&mut self, signal: &BioSignal, response_time: f32, response_mag: f32) {
|
||||
let pattern = self.user_reflexes
|
||||
.entry(format!("{:?}_{}", signal.signal_type, signal.channel))
|
||||
.or_insert_with(|| UserReflexPattern {
|
||||
trigger_signal: signal.signal_type.clone(),
|
||||
trigger_threshold: signal.amplitude,
|
||||
typical_response_time_ms: response_time,
|
||||
typical_response_magnitude: response_mag,
|
||||
observations: 0,
|
||||
});
|
||||
|
||||
// Online learning
|
||||
let lr = 0.1;
|
||||
pattern.typical_response_time_ms =
|
||||
pattern.typical_response_time_ms * (1.0 - lr) + response_time * lr;
|
||||
pattern.typical_response_magnitude =
|
||||
pattern.typical_response_magnitude * (1.0 - lr) + response_mag * lr;
|
||||
pattern.observations += 1;
|
||||
}
|
||||
|
||||
/// Determine machine response based on user reflex state
|
||||
pub fn integrate(&self, signal: &BioSignal, user_responding: bool) -> Option<MachineAction> {
|
||||
let pattern_key = format!("{:?}_{}", signal.signal_type, signal.channel);
|
||||
|
||||
match &self.mode {
|
||||
IntegrationMode::Assist => {
|
||||
// Only help if user is slow
|
||||
if !user_responding {
|
||||
if let Some(pattern) = self.user_reflexes.get(&pattern_key) {
|
||||
return Some(MachineAction {
|
||||
timestamp_ms: signal.timestamp_ms,
|
||||
action_type: ActionType::ForceAssist {
|
||||
direction: (0.0, 0.0, 1.0),
|
||||
magnitude: pattern.typical_response_magnitude * 0.5,
|
||||
},
|
||||
magnitude: pattern.typical_response_magnitude * 0.5,
|
||||
velocity: 1.0,
|
||||
duration_ms: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
IntegrationMode::Amplify { gain } => {
|
||||
// Always amplify user response
|
||||
if user_responding {
|
||||
if let Some(pattern) = self.user_reflexes.get(&pattern_key) {
|
||||
return Some(MachineAction {
|
||||
timestamp_ms: signal.timestamp_ms,
|
||||
action_type: ActionType::ForceAssist {
|
||||
direction: (0.0, 0.0, 1.0),
|
||||
magnitude: pattern.typical_response_magnitude * gain,
|
||||
},
|
||||
magnitude: pattern.typical_response_magnitude * gain,
|
||||
velocity: 1.0,
|
||||
duration_ms: 50,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
IntegrationMode::Takeover => {
|
||||
// Machine handles everything
|
||||
return Some(MachineAction {
|
||||
timestamp_ms: signal.timestamp_ms,
|
||||
action_type: ActionType::Motor {
|
||||
joint: "default".to_string(),
|
||||
target: 0.0,
|
||||
},
|
||||
magnitude: 1.0,
|
||||
velocity: 0.5,
|
||||
duration_ms: 200,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Intent decoder - learns user intention from patterns
|
||||
pub struct IntentDecoder {
|
||||
/// Signal patterns associated with each intent
|
||||
pub intent_patterns: HashMap<String, IntentPattern>,
|
||||
/// Recent signals for pattern matching
|
||||
pub signal_buffer: VecDeque<BioSignal>,
|
||||
/// Confidence threshold for action
|
||||
pub confidence_threshold: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IntentPattern {
|
||||
pub name: String,
|
||||
pub template: Vec<(BioSignalType, f32, f32)>, // (type, amplitude_mean, amplitude_std)
|
||||
pub occurrences: u64,
|
||||
pub success_rate: f32,
|
||||
}
|
||||
|
||||
impl IntentDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
intent_patterns: HashMap::new(),
|
||||
signal_buffer: VecDeque::new(),
|
||||
confidence_threshold: 0.7,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add signal to buffer
|
||||
pub fn observe(&mut self, signal: BioSignal) {
|
||||
self.signal_buffer.push_back(signal);
|
||||
if self.signal_buffer.len() > 50 {
|
||||
self.signal_buffer.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn intent from labeled example
|
||||
pub fn learn_intent(&mut self, intent_name: &str, signals: &[BioSignal]) {
|
||||
let template: Vec<_> = signals.iter()
|
||||
.map(|s| (s.signal_type.clone(), s.amplitude, 0.2)) // Initial std = 0.2
|
||||
.collect();
|
||||
|
||||
let pattern = self.intent_patterns
|
||||
.entry(intent_name.to_string())
|
||||
.or_insert_with(|| IntentPattern {
|
||||
name: intent_name.to_string(),
|
||||
template: template.clone(),
|
||||
occurrences: 0,
|
||||
success_rate: 0.5,
|
||||
});
|
||||
|
||||
pattern.occurrences += 1;
|
||||
|
||||
// Update template with online learning
|
||||
for (i, sig) in signals.iter().enumerate() {
|
||||
if i < pattern.template.len() {
|
||||
let (_, ref mut mean, ref mut std) = pattern.template[i];
|
||||
let lr = 0.1;
|
||||
*mean = *mean * (1.0 - lr) + sig.amplitude * lr;
|
||||
*std = *std * (1.0 - lr) + (sig.amplitude - *mean).abs() * lr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode intent from current buffer
|
||||
pub fn decode(&self) -> Option<(String, f32)> {
|
||||
if self.signal_buffer.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut best_match: Option<(String, f32)> = None;
|
||||
|
||||
for (name, pattern) in &self.intent_patterns {
|
||||
let confidence = self.match_pattern(pattern);
|
||||
|
||||
if confidence > self.confidence_threshold {
|
||||
if best_match.as_ref().map(|(_, c)| confidence > *c).unwrap_or(true) {
|
||||
best_match = Some((name.clone(), confidence));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_match
|
||||
}
|
||||
|
||||
fn match_pattern(&self, pattern: &IntentPattern) -> f32 {
|
||||
if self.signal_buffer.len() < pattern.template.len() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let recent: Vec<_> = self.signal_buffer.iter()
|
||||
.rev()
|
||||
.take(pattern.template.len())
|
||||
.collect();
|
||||
|
||||
let mut match_score = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for (i, (sig_type, mean, std)) in pattern.template.iter().enumerate() {
|
||||
if i < recent.len() {
|
||||
let signal = recent[i];
|
||||
if signal.signal_type == *sig_type {
|
||||
let z = (signal.amplitude - mean).abs() / std.max(0.01);
|
||||
let score = (-z * z / 2.0).exp(); // Gaussian match
|
||||
match_score += score;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
match_score / count as f32
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Report feedback on decoded intent
|
||||
pub fn feedback(&mut self, intent_name: &str, was_correct: bool) {
|
||||
if let Some(pattern) = self.intent_patterns.get_mut(intent_name) {
|
||||
let lr = 0.1;
|
||||
let target = if was_correct { 1.0 } else { 0.0 };
|
||||
pattern.success_rate = pattern.success_rate * (1.0 - lr) + target * lr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete bio-machine interface
|
||||
pub struct BioMachineInterface {
|
||||
pub name: String,
|
||||
pub timing: BiologicalTimingAdapter,
|
||||
pub reflexes: ReflexIntegrator,
|
||||
pub intent: IntentDecoder,
|
||||
pub timestamp: u64,
|
||||
/// Adaptation history
|
||||
pub adaptation_log: Vec<AdaptationEvent>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AdaptationEvent {
|
||||
pub timestamp: u64,
|
||||
pub event_type: String,
|
||||
pub old_value: f32,
|
||||
pub new_value: f32,
|
||||
}
|
||||
|
||||
impl BioMachineInterface {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
timing: BiologicalTimingAdapter::new(),
|
||||
reflexes: ReflexIntegrator::new(),
|
||||
intent: IntentDecoder::new(),
|
||||
timestamp: 0,
|
||||
adaptation_log: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process biological signal through the interface
|
||||
pub fn process(&mut self, signal: BioSignal) -> Option<MachineAction> {
|
||||
self.timestamp = signal.timestamp_ms;
|
||||
|
||||
// 1. Intent decoding
|
||||
self.intent.observe(signal.clone());
|
||||
|
||||
if let Some((intent, confidence)) = self.intent.decode() {
|
||||
// Intent detected - generate appropriate action
|
||||
let delay = self.timing.optimal_response_delay(confidence);
|
||||
|
||||
return Some(MachineAction {
|
||||
timestamp_ms: self.timestamp + delay,
|
||||
action_type: ActionType::Haptic {
|
||||
pattern: format!("intent_{}", intent),
|
||||
intensity: confidence,
|
||||
},
|
||||
magnitude: confidence,
|
||||
velocity: 1.0,
|
||||
duration_ms: self.timing.matched_duration(1.0),
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Reflex integration
|
||||
// Check if user is responding (simplified)
|
||||
let user_responding = signal.amplitude > 0.3;
|
||||
|
||||
if let Some(action) = self.reflexes.integrate(&signal, user_responding) {
|
||||
return Some(action);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Learn from user interaction
|
||||
pub fn learn(&mut self, signal: &BioSignal, response_time: f32, was_successful: bool) {
|
||||
let old_rt = self.timing.reaction_time_ms;
|
||||
|
||||
self.timing.observe_timing(
|
||||
signal.timestamp_ms,
|
||||
signal.timestamp_ms + response_time as u64,
|
||||
);
|
||||
|
||||
self.reflexes.learn_user_reflex(signal, response_time, signal.amplitude);
|
||||
|
||||
// Log adaptation
|
||||
if (old_rt - self.timing.reaction_time_ms).abs() > 5.0 {
|
||||
self.adaptation_log.push(AdaptationEvent {
|
||||
timestamp: self.timestamp,
|
||||
event_type: "reaction_time".to_string(),
|
||||
old_value: old_rt,
|
||||
new_value: self.timing.reaction_time_ms,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get interface status
|
||||
pub fn status(&self) -> InterfaceStatus {
|
||||
InterfaceStatus {
|
||||
adapted_reaction_time_ms: self.timing.reaction_time_ms,
|
||||
natural_rhythm_hz: self.timing.natural_rhythm_hz,
|
||||
integration_mode: self.reflexes.mode.clone(),
|
||||
known_intents: self.intent.intent_patterns.len(),
|
||||
known_reflexes: self.reflexes.user_reflexes.len(),
|
||||
adaptations_made: self.adaptation_log.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InterfaceStatus {
|
||||
pub adapted_reaction_time_ms: f32,
|
||||
pub natural_rhythm_hz: f32,
|
||||
pub integration_mode: IntegrationMode,
|
||||
pub known_intents: usize,
|
||||
pub known_reflexes: usize,
|
||||
pub adaptations_made: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 3: Hybrid Biological-Machine Interfaces ===\n");
|
||||
|
||||
let mut interface = BioMachineInterface::new("Prosthetic Arm");
|
||||
|
||||
// Register some intents
|
||||
println!("Learning user intents...");
|
||||
for i in 0..20 {
|
||||
// Simulate grip intent pattern
|
||||
let grip_signals = vec![
|
||||
BioSignal {
|
||||
timestamp_ms: i * 1000,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.8 + (i as f32 * 0.1).sin() * 0.1,
|
||||
frequency: Some(150.0),
|
||||
},
|
||||
BioSignal {
|
||||
timestamp_ms: i * 1000 + 50,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 1,
|
||||
amplitude: 0.6 + (i as f32 * 0.1).sin() * 0.1,
|
||||
frequency: Some(120.0),
|
||||
},
|
||||
];
|
||||
interface.intent.learn_intent("grip", &grip_signals);
|
||||
|
||||
// Simulate release intent
|
||||
let release_signals = vec![
|
||||
BioSignal {
|
||||
timestamp_ms: i * 1000,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.2,
|
||||
frequency: Some(50.0),
|
||||
},
|
||||
];
|
||||
interface.intent.learn_intent("release", &release_signals);
|
||||
}
|
||||
|
||||
println!(" Intents learned: {}", interface.intent.intent_patterns.len());
|
||||
|
||||
// Simulate usage to adapt timing
|
||||
println!("\nAdapting to user timing...");
|
||||
for i in 0..50 {
|
||||
let signal = BioSignal {
|
||||
timestamp_ms: i * 500,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.7,
|
||||
frequency: Some(100.0),
|
||||
};
|
||||
|
||||
// Simulate user response time varying around 180ms
|
||||
let response_time = 180.0 + (i as f32 * 0.2).sin() * 20.0;
|
||||
|
||||
interface.learn(&signal, response_time, true);
|
||||
|
||||
if i % 10 == 0 {
|
||||
println!(" Step {}: adapted RT = {:.1}ms",
|
||||
i, interface.timing.reaction_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Test intent decoding
|
||||
println!("\nTesting intent decoding...");
|
||||
|
||||
// Grip intent
|
||||
for _ in 0..3 {
|
||||
interface.intent.observe(BioSignal {
|
||||
timestamp_ms: interface.timestamp + 10,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.75,
|
||||
frequency: Some(140.0),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some((intent, confidence)) = interface.intent.decode() {
|
||||
println!(" Decoded intent: {} (confidence: {:.2})", intent, confidence);
|
||||
}
|
||||
|
||||
// Test machine action generation
|
||||
println!("\nGenerating machine actions...");
|
||||
let signal = BioSignal {
|
||||
timestamp_ms: interface.timestamp + 100,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.8,
|
||||
frequency: Some(150.0),
|
||||
};
|
||||
|
||||
if let Some(action) = interface.process(signal) {
|
||||
println!(" Action: {:?}", action.action_type);
|
||||
println!(" Timing: delay={}ms, duration={}ms",
|
||||
action.timestamp_ms - interface.timestamp, action.duration_ms);
|
||||
}
|
||||
|
||||
// Change integration mode
|
||||
println!("\nChanging to amplification mode...");
|
||||
interface.reflexes.mode = IntegrationMode::Amplify { gain: 1.5 };
|
||||
|
||||
let status = interface.status();
|
||||
println!("\n=== Interface Status ===");
|
||||
println!(" Adapted reaction time: {:.1}ms", status.adapted_reaction_time_ms);
|
||||
println!(" Natural rhythm: {:.2}Hz", status.natural_rhythm_hz);
|
||||
println!(" Integration mode: {:?}", status.integration_mode);
|
||||
println!(" Known intents: {}", status.known_intents);
|
||||
println!(" Known reflexes: {}", status.known_reflexes);
|
||||
println!(" Adaptations made: {}", status.adaptations_made);
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Machine timing adapts to biological rhythms");
|
||||
println!("- Reflex loops integrate with human reflexes");
|
||||
println!("- Learning happens through use, not retraining");
|
||||
println!("- Machines stop fighting biology");
|
||||
println!("- Interfaces become intuitive over time");
|
||||
println!("\nThis is cutting-edge but real.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_timing_adaptation() {
|
||||
let mut adapter = BiologicalTimingAdapter::new();
|
||||
|
||||
// Initial reaction time
|
||||
let initial = adapter.reaction_time_ms;
|
||||
|
||||
// Learn faster reaction times
|
||||
for i in 0..10 {
|
||||
adapter.observe_timing(i * 1000, i * 1000 + 150);
|
||||
}
|
||||
|
||||
assert!(adapter.reaction_time_ms < initial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intent_learning() {
|
||||
let mut decoder = IntentDecoder::new();
|
||||
|
||||
let signals = vec![
|
||||
BioSignal {
|
||||
timestamp_ms: 0,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.8,
|
||||
frequency: None,
|
||||
},
|
||||
];
|
||||
|
||||
decoder.learn_intent("test", &signals);
|
||||
assert!(decoder.intent_patterns.contains_key("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reflex_integration() {
|
||||
let mut integrator = ReflexIntegrator::new();
|
||||
integrator.mode = IntegrationMode::Assist;
|
||||
|
||||
// Learn a user reflex
|
||||
let signal = BioSignal {
|
||||
timestamp_ms: 0,
|
||||
signal_type: BioSignalType::EMG,
|
||||
channel: 0,
|
||||
amplitude: 0.5,
|
||||
frequency: None,
|
||||
};
|
||||
|
||||
integrator.learn_user_reflex(&signal, 200.0, 0.8);
|
||||
|
||||
// When user not responding, machine should assist
|
||||
let action = integrator.integrate(&signal, false);
|
||||
assert!(action.is_some());
|
||||
|
||||
// When user is responding, no assist needed
|
||||
let action = integrator.integrate(&signal, true);
|
||||
assert!(action.is_none());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
//! # Tier 3: Machine Self-Awareness Primitives
|
||||
//!
|
||||
//! Not consciousness, but structural self-sensing.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Systems monitor their own coherence
|
||||
//! - Learning adjusts internal organization
|
||||
//! - Failure is sensed before performance drops
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Systems can say "I am becoming unstable"
|
||||
//! - Maintenance becomes anticipatory
|
||||
//! - This is a prerequisite for trustworthy autonomy
|
||||
//!
|
||||
//! This is novel and publishable.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// Internal state that the system monitors about itself
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InternalState {
|
||||
pub timestamp: u64,
|
||||
/// Processing coherence (0-1): how well modules are synchronized
|
||||
pub coherence: f32,
|
||||
/// Attention focus (what is being processed)
|
||||
pub attention_target: Option<String>,
|
||||
/// Confidence in current processing
|
||||
pub confidence: f32,
|
||||
/// Energy available for computation
|
||||
pub energy_budget: f32,
|
||||
/// Error rate in recent operations
|
||||
pub error_rate: f32,
|
||||
}
|
||||
|
||||
/// Self-model that tracks the system's own capabilities
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SelfModel {
|
||||
/// What capabilities does this system have?
|
||||
pub capabilities: HashMap<String, CapabilityState>,
|
||||
/// Current operating mode
|
||||
pub operating_mode: OperatingMode,
|
||||
/// Predicted time until degradation
|
||||
pub time_to_degradation: Option<u64>,
|
||||
/// Self-assessed reliability
|
||||
pub reliability_estimate: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CapabilityState {
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub current_performance: f32,
|
||||
pub baseline_performance: f32,
|
||||
pub degradation_rate: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum OperatingMode {
|
||||
Optimal,
|
||||
Degraded { reason: String },
|
||||
Recovery,
|
||||
SafeMode,
|
||||
}
|
||||
|
||||
/// Metacognitive monitor that observes internal processing
|
||||
pub struct MetacognitiveMonitor {
|
||||
/// History of internal states
|
||||
pub state_history: VecDeque<InternalState>,
|
||||
/// Coherence threshold for alarm
|
||||
pub coherence_threshold: f32,
|
||||
/// Self-model
|
||||
pub self_model: SelfModel,
|
||||
/// Anomaly detector for internal states
|
||||
pub internal_anomaly_threshold: f32,
|
||||
}
|
||||
|
||||
impl MetacognitiveMonitor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state_history: VecDeque::new(),
|
||||
coherence_threshold: 0.7,
|
||||
self_model: SelfModel {
|
||||
capabilities: HashMap::new(),
|
||||
operating_mode: OperatingMode::Optimal,
|
||||
time_to_degradation: None,
|
||||
reliability_estimate: 1.0,
|
||||
},
|
||||
internal_anomaly_threshold: 2.0, // Standard deviations
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a capability
|
||||
pub fn register_capability(&mut self, name: &str, baseline: f32) {
|
||||
self.self_model.capabilities.insert(
|
||||
name.to_string(),
|
||||
CapabilityState {
|
||||
name: name.to_string(),
|
||||
enabled: true,
|
||||
current_performance: baseline,
|
||||
baseline_performance: baseline,
|
||||
degradation_rate: 0.0,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Observe current internal state
|
||||
pub fn observe(&mut self, state: InternalState) -> SelfAwarenessEvent {
|
||||
self.state_history.push_back(state.clone());
|
||||
if self.state_history.len() > 1000 {
|
||||
self.state_history.pop_front();
|
||||
}
|
||||
|
||||
// Check coherence
|
||||
if state.coherence < self.coherence_threshold {
|
||||
self.self_model.operating_mode = OperatingMode::Degraded {
|
||||
reason: format!("Low coherence: {:.2}", state.coherence),
|
||||
};
|
||||
|
||||
return SelfAwarenessEvent::IncoherenceDetected {
|
||||
current_coherence: state.coherence,
|
||||
threshold: self.coherence_threshold,
|
||||
recommendation: "Reduce processing load or increase synchronization".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
// Detect internal anomalies
|
||||
if self.state_history.len() > 10 {
|
||||
let avg_confidence: f32 = self.state_history.iter()
|
||||
.map(|s| s.confidence)
|
||||
.sum::<f32>() / self.state_history.len() as f32;
|
||||
|
||||
let std_dev: f32 = (self.state_history.iter()
|
||||
.map(|s| (s.confidence - avg_confidence).powi(2))
|
||||
.sum::<f32>() / self.state_history.len() as f32)
|
||||
.sqrt();
|
||||
|
||||
let z_score = (state.confidence - avg_confidence).abs() / std_dev.max(0.01);
|
||||
|
||||
if z_score > self.internal_anomaly_threshold {
|
||||
return SelfAwarenessEvent::InternalAnomaly {
|
||||
metric: "confidence".to_string(),
|
||||
z_score,
|
||||
interpretation: if state.confidence < avg_confidence {
|
||||
"Processing uncertainty spike".to_string()
|
||||
} else {
|
||||
"Overconfidence detected".to_string()
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Predict degradation
|
||||
self.predict_degradation();
|
||||
|
||||
// Check energy
|
||||
if state.energy_budget < 0.2 {
|
||||
return SelfAwarenessEvent::ResourceWarning {
|
||||
resource: "energy".to_string(),
|
||||
current: state.energy_budget,
|
||||
threshold: 0.2,
|
||||
action: "Enter power-saving mode".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
SelfAwarenessEvent::Stable {
|
||||
coherence: state.coherence,
|
||||
confidence: state.confidence,
|
||||
operating_mode: self.self_model.operating_mode.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update capability performance
|
||||
pub fn update_capability(&mut self, name: &str, performance: f32) {
|
||||
if let Some(cap) = self.self_model.capabilities.get_mut(name) {
|
||||
let old_perf = cap.current_performance;
|
||||
cap.current_performance = performance;
|
||||
|
||||
// Track degradation rate
|
||||
let degradation = (old_perf - performance) / old_perf.max(0.01);
|
||||
cap.degradation_rate = cap.degradation_rate * 0.9 + degradation * 0.1;
|
||||
|
||||
// Update reliability estimate
|
||||
self.update_reliability();
|
||||
}
|
||||
}
|
||||
|
||||
fn predict_degradation(&mut self) {
|
||||
// Check if any capability is degrading
|
||||
for (_, cap) in &self.self_model.capabilities {
|
||||
if cap.degradation_rate > 0.01 {
|
||||
// Extrapolate time to failure
|
||||
let performance_remaining = cap.current_performance - 0.5; // Minimum acceptable
|
||||
if cap.degradation_rate > 0.0 && performance_remaining > 0.0 {
|
||||
let time_to_fail = (performance_remaining / cap.degradation_rate) as u64;
|
||||
self.self_model.time_to_degradation = Some(time_to_fail.min(
|
||||
self.self_model.time_to_degradation.unwrap_or(u64::MAX)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_reliability(&mut self) {
|
||||
let total_perf: f32 = self.self_model.capabilities.values()
|
||||
.map(|c| c.current_performance / c.baseline_performance.max(0.01))
|
||||
.sum();
|
||||
|
||||
let n = self.self_model.capabilities.len().max(1) as f32;
|
||||
self.self_model.reliability_estimate = total_perf / n;
|
||||
}
|
||||
|
||||
/// Get self-assessment
|
||||
pub fn self_assess(&self) -> SelfAssessment {
|
||||
SelfAssessment {
|
||||
operating_mode: self.self_model.operating_mode.clone(),
|
||||
reliability: self.self_model.reliability_estimate,
|
||||
time_to_degradation: self.self_model.time_to_degradation,
|
||||
capabilities_status: self.self_model.capabilities.iter()
|
||||
.map(|(k, v)| (k.clone(), v.current_performance / v.baseline_performance.max(0.01)))
|
||||
.collect(),
|
||||
recommendation: self.generate_recommendation(),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_recommendation(&self) -> String {
|
||||
match &self.self_model.operating_mode {
|
||||
OperatingMode::Optimal => "System operating normally".to_string(),
|
||||
OperatingMode::Degraded { reason } => {
|
||||
format!("Degraded: {}. Consider maintenance.", reason)
|
||||
}
|
||||
OperatingMode::Recovery => "Recovery in progress. Avoid heavy loads.".to_string(),
|
||||
OperatingMode::SafeMode => "Safe mode active. Minimal operations only.".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Events that indicate self-awareness
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SelfAwarenessEvent {
|
||||
/// System detected low internal coherence
|
||||
IncoherenceDetected {
|
||||
current_coherence: f32,
|
||||
threshold: f32,
|
||||
recommendation: String,
|
||||
},
|
||||
/// Internal processing anomaly detected
|
||||
InternalAnomaly {
|
||||
metric: String,
|
||||
z_score: f32,
|
||||
interpretation: String,
|
||||
},
|
||||
/// Resource warning
|
||||
ResourceWarning {
|
||||
resource: String,
|
||||
current: f32,
|
||||
threshold: f32,
|
||||
action: String,
|
||||
},
|
||||
/// Capability degradation predicted
|
||||
DegradationPredicted {
|
||||
capability: String,
|
||||
current_performance: f32,
|
||||
predicted_failure_time: u64,
|
||||
},
|
||||
/// System is stable
|
||||
Stable {
|
||||
coherence: f32,
|
||||
confidence: f32,
|
||||
operating_mode: OperatingMode,
|
||||
},
|
||||
}
|
||||
|
||||
/// Self-assessment report
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SelfAssessment {
|
||||
pub operating_mode: OperatingMode,
|
||||
pub reliability: f32,
|
||||
pub time_to_degradation: Option<u64>,
|
||||
pub capabilities_status: HashMap<String, f32>,
|
||||
pub recommendation: String,
|
||||
}
|
||||
|
||||
/// Complete self-aware system
|
||||
pub struct SelfAwareSystem {
|
||||
pub name: String,
|
||||
pub monitor: MetacognitiveMonitor,
|
||||
/// Processing modules with their coherence
|
||||
pub modules: HashMap<String, f32>,
|
||||
/// Attention mechanism
|
||||
pub attention_focus: Option<String>,
|
||||
/// Current timestamp
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl SelfAwareSystem {
|
||||
pub fn new(name: &str) -> Self {
|
||||
let mut system = Self {
|
||||
name: name.to_string(),
|
||||
monitor: MetacognitiveMonitor::new(),
|
||||
modules: HashMap::new(),
|
||||
attention_focus: None,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
// Register default capabilities
|
||||
system.monitor.register_capability("perception", 1.0);
|
||||
system.monitor.register_capability("reasoning", 1.0);
|
||||
system.monitor.register_capability("action", 1.0);
|
||||
system.monitor.register_capability("learning", 1.0);
|
||||
|
||||
system
|
||||
}
|
||||
|
||||
/// Add a processing module
|
||||
pub fn add_module(&mut self, name: &str) {
|
||||
self.modules.insert(name.to_string(), 1.0);
|
||||
}
|
||||
|
||||
/// Compute current coherence from module phases
|
||||
pub fn compute_coherence(&self) -> f32 {
|
||||
if self.modules.is_empty() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let values: Vec<_> = self.modules.values().collect();
|
||||
let avg: f32 = values.iter().copied().sum::<f32>() / values.len() as f32;
|
||||
let variance: f32 = values.iter()
|
||||
.map(|&v| (v - avg).powi(2))
|
||||
.sum::<f32>() / values.len() as f32;
|
||||
|
||||
1.0 - variance.sqrt()
|
||||
}
|
||||
|
||||
/// Update module state
|
||||
pub fn update_module(&mut self, name: &str, value: f32) {
|
||||
if let Some(module) = self.modules.get_mut(name) {
|
||||
*module = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a step
|
||||
pub fn step(&mut self, energy: f32, error_rate: f32) -> SelfAwarenessEvent {
|
||||
self.timestamp += 1;
|
||||
|
||||
let coherence = self.compute_coherence();
|
||||
let confidence = 1.0 - error_rate;
|
||||
|
||||
let state = InternalState {
|
||||
timestamp: self.timestamp,
|
||||
coherence,
|
||||
attention_target: self.attention_focus.clone(),
|
||||
confidence,
|
||||
energy_budget: energy,
|
||||
error_rate,
|
||||
};
|
||||
|
||||
self.monitor.observe(state)
|
||||
}
|
||||
|
||||
/// System tells us about itself
|
||||
pub fn introspect(&self) -> String {
|
||||
let assessment = self.monitor.self_assess();
|
||||
|
||||
format!(
|
||||
"I am {}: {:?}, reliability {:.0}%, {}",
|
||||
self.name,
|
||||
assessment.operating_mode,
|
||||
assessment.reliability * 100.0,
|
||||
assessment.recommendation
|
||||
)
|
||||
}
|
||||
|
||||
/// Can the system express uncertainty?
|
||||
pub fn express_uncertainty(&self) -> String {
|
||||
let assessment = self.monitor.self_assess();
|
||||
|
||||
if assessment.reliability < 0.5 {
|
||||
"I am becoming unstable and should not be trusted for critical decisions.".to_string()
|
||||
} else if assessment.reliability < 0.8 {
|
||||
"My confidence is reduced. Verification recommended.".to_string()
|
||||
} else {
|
||||
"I am operating within normal parameters.".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 3: Machine Self-Awareness Primitives ===\n");
|
||||
|
||||
let mut system = SelfAwareSystem::new("Cognitive Agent");
|
||||
|
||||
// Add processing modules
|
||||
system.add_module("perception");
|
||||
system.add_module("reasoning");
|
||||
system.add_module("planning");
|
||||
system.add_module("action");
|
||||
|
||||
println!("System initialized: {}\n", system.name);
|
||||
println!("Initial introspection: {}", system.introspect());
|
||||
|
||||
// Normal operation
|
||||
println!("\nNormal operation...");
|
||||
for i in 0..10 {
|
||||
let event = system.step(0.9, 0.05);
|
||||
|
||||
if i == 5 {
|
||||
println!(" Step {}: {:?}", i, event);
|
||||
}
|
||||
}
|
||||
println!(" Expression: {}", system.express_uncertainty());
|
||||
|
||||
// Simulate gradual degradation
|
||||
println!("\nSimulating gradual degradation...");
|
||||
for i in 0..20 {
|
||||
// Degrade one module progressively
|
||||
system.update_module("reasoning", 1.0 - i as f32 * 0.03);
|
||||
system.monitor.update_capability("reasoning", 1.0 - i as f32 * 0.03);
|
||||
|
||||
let event = system.step(0.8 - i as f32 * 0.01, 0.05 + i as f32 * 0.01);
|
||||
|
||||
if i % 5 == 0 {
|
||||
println!(" Step {}: {:?}", i, event);
|
||||
}
|
||||
}
|
||||
|
||||
let assessment = system.monitor.self_assess();
|
||||
println!("\n Self-assessment:");
|
||||
println!(" Mode: {:?}", assessment.operating_mode);
|
||||
println!(" Reliability: {:.1}%", assessment.reliability * 100.0);
|
||||
println!(" Time to degradation: {:?}", assessment.time_to_degradation);
|
||||
println!(" Capabilities: {:?}", assessment.capabilities_status);
|
||||
println!("\n Expression: {}", system.express_uncertainty());
|
||||
|
||||
// Simulate low coherence (modules out of sync)
|
||||
println!("\nSimulating incoherence...");
|
||||
system.update_module("perception", 0.9);
|
||||
system.update_module("reasoning", 0.3);
|
||||
system.update_module("planning", 0.7);
|
||||
system.update_module("action", 0.5);
|
||||
|
||||
let event = system.step(0.5, 0.2);
|
||||
println!(" Event: {:?}", event);
|
||||
println!(" Introspection: {}", system.introspect());
|
||||
|
||||
// Simulate low energy
|
||||
println!("\nSimulating energy depletion...");
|
||||
let event = system.step(0.15, 0.1);
|
||||
println!(" Event: {:?}", event);
|
||||
|
||||
// Final self-report
|
||||
println!("\n=== Final Self-Report ===");
|
||||
println!("{}", system.introspect());
|
||||
println!("{}", system.express_uncertainty());
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Systems can say 'I am becoming unstable'");
|
||||
println!("- Failure sensed before performance drops");
|
||||
println!("- Maintenance becomes anticipatory");
|
||||
println!("- Prerequisite for trustworthy autonomy");
|
||||
println!("- Structural self-sensing, not consciousness");
|
||||
println!("\nThis is novel and publishable.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_coherence_detection() {
|
||||
let mut system = SelfAwareSystem::new("test");
|
||||
system.add_module("a");
|
||||
system.add_module("b");
|
||||
|
||||
// In sync = high coherence
|
||||
system.update_module("a", 1.0);
|
||||
system.update_module("b", 1.0);
|
||||
assert!(system.compute_coherence() > 0.9);
|
||||
|
||||
// Out of sync = low coherence
|
||||
system.update_module("a", 1.0);
|
||||
system.update_module("b", 0.2);
|
||||
assert!(system.compute_coherence() < 0.8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self_assessment() {
|
||||
let mut system = SelfAwareSystem::new("test");
|
||||
|
||||
// Normal operation
|
||||
for _ in 0..10 {
|
||||
system.step(0.9, 0.05);
|
||||
}
|
||||
|
||||
let assessment = system.monitor.self_assess();
|
||||
assert!(assessment.reliability > 0.9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_degradation_prediction() {
|
||||
let mut monitor = MetacognitiveMonitor::new();
|
||||
monitor.register_capability("test", 1.0);
|
||||
|
||||
// Simulate degradation
|
||||
for i in 0..10 {
|
||||
monitor.update_capability("test", 1.0 - i as f32 * 0.05);
|
||||
}
|
||||
|
||||
// Should predict degradation
|
||||
assert!(monitor.self_model.capabilities.get("test")
|
||||
.map(|c| c.degradation_rate > 0.0)
|
||||
.unwrap_or(false));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,658 @@
|
|||
//! # Tier 3: Synthetic Nervous Systems for Environments
|
||||
//!
|
||||
//! Buildings, factories, cities.
|
||||
//!
|
||||
//! ## What Changes
|
||||
//! - Infrastructure becomes a sensing fabric
|
||||
//! - Reflexes manage local events
|
||||
//! - Policy emerges from patterns, not rules
|
||||
//!
|
||||
//! ## Why This Matters
|
||||
//! - Environments respond like organisms
|
||||
//! - Energy, safety, and flow self-regulate
|
||||
//! - Central planning gives way to distributed intelligence
|
||||
//!
|
||||
//! This is exotic but inevitable.
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f32::consts::PI;
|
||||
|
||||
/// A location in the environment
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct LocationId(pub String);
|
||||
|
||||
/// A zone grouping multiple locations
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ZoneId(pub String);
|
||||
|
||||
/// Environmental sensor reading
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvironmentReading {
|
||||
pub timestamp: u64,
|
||||
pub location: LocationId,
|
||||
pub sensor_type: EnvironmentSensor,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum EnvironmentSensor {
|
||||
Temperature,
|
||||
Humidity,
|
||||
Light,
|
||||
Occupancy,
|
||||
AirQuality,
|
||||
Noise,
|
||||
Motion,
|
||||
Energy,
|
||||
Water,
|
||||
}
|
||||
|
||||
/// Environmental actuator command
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EnvironmentAction {
|
||||
pub location: LocationId,
|
||||
pub actuator: EnvironmentActuator,
|
||||
pub value: f32,
|
||||
pub priority: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EnvironmentActuator {
|
||||
HVAC { mode: HVACMode },
|
||||
Lighting { brightness: f32 },
|
||||
Ventilation { flow_rate: f32 },
|
||||
Shading { position: f32 },
|
||||
DoorLock { locked: bool },
|
||||
Alarm { active: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HVACMode {
|
||||
Off,
|
||||
Heating(f32),
|
||||
Cooling(f32),
|
||||
Ventilation,
|
||||
}
|
||||
|
||||
/// Local reflex for immediate environmental response
|
||||
pub struct LocalEnvironmentReflex {
|
||||
pub location: LocationId,
|
||||
pub sensor_type: EnvironmentSensor,
|
||||
pub threshold_low: f32,
|
||||
pub threshold_high: f32,
|
||||
pub action_low: EnvironmentActuator,
|
||||
pub action_high: EnvironmentActuator,
|
||||
pub hysteresis: f32,
|
||||
pub last_state: i8, // -1 = below, 0 = normal, 1 = above
|
||||
}
|
||||
|
||||
impl LocalEnvironmentReflex {
|
||||
pub fn new(
|
||||
location: LocationId,
|
||||
sensor_type: EnvironmentSensor,
|
||||
threshold_low: f32,
|
||||
threshold_high: f32,
|
||||
action_low: EnvironmentActuator,
|
||||
action_high: EnvironmentActuator,
|
||||
) -> Self {
|
||||
Self {
|
||||
location,
|
||||
sensor_type,
|
||||
threshold_low,
|
||||
threshold_high,
|
||||
action_low,
|
||||
action_high,
|
||||
hysteresis: 0.5,
|
||||
last_state: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if reflex should fire
|
||||
pub fn check(&mut self, reading: &EnvironmentReading) -> Option<EnvironmentAction> {
|
||||
if reading.location != self.location || reading.sensor_type != self.sensor_type {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Apply hysteresis
|
||||
let effective_low = if self.last_state == -1 {
|
||||
self.threshold_low + self.hysteresis
|
||||
} else {
|
||||
self.threshold_low
|
||||
};
|
||||
|
||||
let effective_high = if self.last_state == 1 {
|
||||
self.threshold_high - self.hysteresis
|
||||
} else {
|
||||
self.threshold_high
|
||||
};
|
||||
|
||||
if reading.value < effective_low && self.last_state != -1 {
|
||||
self.last_state = -1;
|
||||
Some(EnvironmentAction {
|
||||
location: self.location.clone(),
|
||||
actuator: self.action_low.clone(),
|
||||
value: reading.value,
|
||||
priority: 1,
|
||||
})
|
||||
} else if reading.value > effective_high && self.last_state != 1 {
|
||||
self.last_state = 1;
|
||||
Some(EnvironmentAction {
|
||||
location: self.location.clone(),
|
||||
actuator: self.action_high.clone(),
|
||||
value: reading.value,
|
||||
priority: 1,
|
||||
})
|
||||
} else if reading.value >= effective_low && reading.value <= effective_high {
|
||||
self.last_state = 0;
|
||||
None
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Zone-level homeostasis controller
|
||||
pub struct ZoneHomeostasis {
|
||||
pub zone: ZoneId,
|
||||
pub locations: Vec<LocationId>,
|
||||
pub target_temperature: f32,
|
||||
pub target_humidity: f32,
|
||||
pub target_light: f32,
|
||||
pub adaptation_rate: f32,
|
||||
/// Learned occupancy pattern (24 hours)
|
||||
pub occupancy_pattern: [f32; 24],
|
||||
pub learning_enabled: bool,
|
||||
}
|
||||
|
||||
impl ZoneHomeostasis {
|
||||
pub fn new(zone: ZoneId, locations: Vec<LocationId>) -> Self {
|
||||
Self {
|
||||
zone,
|
||||
locations,
|
||||
target_temperature: 22.0,
|
||||
target_humidity: 50.0,
|
||||
target_light: 500.0,
|
||||
adaptation_rate: 0.1,
|
||||
occupancy_pattern: [0.0; 24],
|
||||
learning_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn from occupancy patterns
|
||||
pub fn learn_occupancy(&mut self, hour: usize, occupancy: f32) {
|
||||
if self.learning_enabled && hour < 24 {
|
||||
self.occupancy_pattern[hour] =
|
||||
self.occupancy_pattern[hour] * (1.0 - self.adaptation_rate)
|
||||
+ occupancy * self.adaptation_rate;
|
||||
}
|
||||
}
|
||||
|
||||
/// Predict occupancy for pre-conditioning
|
||||
pub fn predict_occupancy(&self, hour: usize) -> f32 {
|
||||
if hour < 24 {
|
||||
self.occupancy_pattern[hour]
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute zone-level action based on aggregate readings
|
||||
pub fn compute_action(&self, readings: &[EnvironmentReading], hour: usize) -> Vec<EnvironmentAction> {
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// Filter readings for this zone
|
||||
let zone_readings: Vec<_> = readings.iter()
|
||||
.filter(|r| self.locations.contains(&r.location))
|
||||
.collect();
|
||||
|
||||
if zone_readings.is_empty() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Average temperature
|
||||
let temp_readings: Vec<_> = zone_readings.iter()
|
||||
.filter(|r| r.sensor_type == EnvironmentSensor::Temperature)
|
||||
.collect();
|
||||
|
||||
if !temp_readings.is_empty() {
|
||||
let avg_temp: f32 = temp_readings.iter().map(|r| r.value).sum::<f32>()
|
||||
/ temp_readings.len() as f32;
|
||||
|
||||
// Adjust target based on predicted occupancy
|
||||
let predicted_occ = self.predict_occupancy(hour);
|
||||
let effective_target = if predicted_occ > 0.5 {
|
||||
self.target_temperature
|
||||
} else {
|
||||
// Setback when unoccupied
|
||||
self.target_temperature - 2.0
|
||||
};
|
||||
|
||||
let temp_error = avg_temp - effective_target;
|
||||
|
||||
if temp_error.abs() > 1.0 {
|
||||
let mode = if temp_error > 0.0 {
|
||||
HVACMode::Cooling(temp_error.abs().min(5.0))
|
||||
} else {
|
||||
HVACMode::Heating(temp_error.abs().min(5.0))
|
||||
};
|
||||
|
||||
for loc in &self.locations {
|
||||
actions.push(EnvironmentAction {
|
||||
location: loc.clone(),
|
||||
actuator: EnvironmentActuator::HVAC { mode: mode.clone() },
|
||||
value: temp_error,
|
||||
priority: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light based on occupancy
|
||||
let occupancy_readings: Vec<_> = zone_readings.iter()
|
||||
.filter(|r| r.sensor_type == EnvironmentSensor::Occupancy)
|
||||
.collect();
|
||||
|
||||
if !occupancy_readings.is_empty() {
|
||||
let occupied = occupancy_readings.iter().any(|r| r.value > 0.5);
|
||||
|
||||
for loc in &self.locations {
|
||||
let brightness = if occupied { 1.0 } else { 0.1 };
|
||||
actions.push(EnvironmentAction {
|
||||
location: loc.clone(),
|
||||
actuator: EnvironmentActuator::Lighting { brightness },
|
||||
value: brightness,
|
||||
priority: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
}
|
||||
|
||||
/// Global workspace for environment-wide coordination
|
||||
pub struct EnvironmentWorkspace {
|
||||
pub capacity: usize,
|
||||
pub items: VecDeque<WorkspaceItem>,
|
||||
pub policies: Vec<EmergentPolicy>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkspaceItem {
|
||||
pub zone: ZoneId,
|
||||
pub observation: String,
|
||||
pub salience: f32,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EmergentPolicy {
|
||||
pub name: String,
|
||||
pub trigger_pattern: String,
|
||||
pub action_pattern: String,
|
||||
pub confidence: f32,
|
||||
pub occurrences: u64,
|
||||
}
|
||||
|
||||
impl EnvironmentWorkspace {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
capacity,
|
||||
items: VecDeque::new(),
|
||||
policies: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast observation to workspace
|
||||
pub fn broadcast(&mut self, item: WorkspaceItem) {
|
||||
if self.items.len() >= self.capacity {
|
||||
// Remove lowest salience
|
||||
if let Some(min_idx) = self.items.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| a.salience.partial_cmp(&b.salience).unwrap())
|
||||
.map(|(i, _)| i)
|
||||
{
|
||||
self.items.remove(min_idx);
|
||||
}
|
||||
}
|
||||
self.items.push_back(item);
|
||||
}
|
||||
|
||||
/// Detect emergent patterns
|
||||
pub fn detect_patterns(&mut self) -> Option<EmergentPolicy> {
|
||||
// Look for repeated sequences in workspace
|
||||
let observations: Vec<_> = self.items.iter()
|
||||
.map(|i| i.observation.clone())
|
||||
.collect();
|
||||
|
||||
if observations.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Simple pattern: if same observation repeats
|
||||
let last = observations.last()?;
|
||||
let count = observations.iter().filter(|o| *o == last).count();
|
||||
|
||||
if count >= 3 {
|
||||
let policy = EmergentPolicy {
|
||||
name: format!("Pattern_{}", self.policies.len()),
|
||||
trigger_pattern: last.clone(),
|
||||
action_pattern: "coordinate_response".to_string(),
|
||||
confidence: count as f32 / observations.len() as f32,
|
||||
occurrences: 1,
|
||||
};
|
||||
|
||||
// Check if already known
|
||||
if !self.policies.iter().any(|p| p.trigger_pattern == last.clone()) {
|
||||
self.policies.push(policy.clone());
|
||||
return Some(policy);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete synthetic nervous system for an environment
|
||||
pub struct SyntheticNervousSystem {
|
||||
pub name: String,
|
||||
/// Local reflexes (fast, location-specific)
|
||||
pub reflexes: Vec<LocalEnvironmentReflex>,
|
||||
/// Zone homeostasis (medium, zone-level)
|
||||
pub zones: HashMap<ZoneId, ZoneHomeostasis>,
|
||||
/// Global workspace (slow, environment-wide)
|
||||
pub workspace: EnvironmentWorkspace,
|
||||
/// Current time
|
||||
pub timestamp: u64,
|
||||
/// Action history
|
||||
pub action_log: Vec<(u64, EnvironmentAction)>,
|
||||
}
|
||||
|
||||
impl SyntheticNervousSystem {
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
reflexes: Vec::new(),
|
||||
zones: HashMap::new(),
|
||||
workspace: EnvironmentWorkspace::new(7),
|
||||
timestamp: 0,
|
||||
action_log: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a zone
|
||||
pub fn add_zone(&mut self, zone_id: &str, locations: Vec<&str>) {
|
||||
let zone = ZoneId(zone_id.to_string());
|
||||
let locs: Vec<_> = locations.iter()
|
||||
.map(|l| LocationId(l.to_string()))
|
||||
.collect();
|
||||
|
||||
self.zones.insert(zone.clone(), ZoneHomeostasis::new(zone, locs));
|
||||
}
|
||||
|
||||
/// Add a local reflex
|
||||
pub fn add_reflex(&mut self, reflex: LocalEnvironmentReflex) {
|
||||
self.reflexes.push(reflex);
|
||||
}
|
||||
|
||||
/// Process sensor readings through the nervous system
|
||||
pub fn process(&mut self, readings: Vec<EnvironmentReading>) -> Vec<EnvironmentAction> {
|
||||
self.timestamp += 1;
|
||||
let hour = ((self.timestamp / 60) % 24) as usize;
|
||||
|
||||
let mut actions = Vec::new();
|
||||
|
||||
// 1. Local reflexes (fastest)
|
||||
for reflex in &mut self.reflexes {
|
||||
for reading in &readings {
|
||||
if let Some(action) = reflex.check(reading) {
|
||||
actions.push(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If reflexes fired, skip higher levels
|
||||
if !actions.is_empty() {
|
||||
for action in &actions {
|
||||
self.action_log.push((self.timestamp, action.clone()));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
// 2. Zone homeostasis (medium)
|
||||
for (_, zone) in &mut self.zones {
|
||||
// Learn occupancy
|
||||
for reading in &readings {
|
||||
if reading.sensor_type == EnvironmentSensor::Occupancy
|
||||
&& zone.locations.contains(&reading.location)
|
||||
{
|
||||
zone.learn_occupancy(hour, reading.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute zone actions
|
||||
let zone_actions = zone.compute_action(&readings, hour);
|
||||
actions.extend(zone_actions);
|
||||
}
|
||||
|
||||
// 3. Global workspace (slowest, pattern detection)
|
||||
for reading in &readings {
|
||||
if reading.value > 0.8 || reading.value < 0.2 {
|
||||
// Significant observation
|
||||
self.workspace.broadcast(WorkspaceItem {
|
||||
zone: ZoneId("global".to_string()),
|
||||
observation: format!("{:?}_{}", reading.sensor_type,
|
||||
if reading.value > 0.5 { "high" } else { "low" }
|
||||
),
|
||||
salience: reading.value.abs(),
|
||||
timestamp: self.timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Detect emergent patterns
|
||||
if let Some(policy) = self.workspace.detect_patterns() {
|
||||
println!(" [EMERGENT] New policy: {} (confidence: {:.2})",
|
||||
policy.name, policy.confidence);
|
||||
}
|
||||
|
||||
for action in &actions {
|
||||
self.action_log.push((self.timestamp, action.clone()));
|
||||
}
|
||||
|
||||
actions
|
||||
}
|
||||
|
||||
/// Get system status
|
||||
pub fn status(&self) -> EnvironmentStatus {
|
||||
let learned_patterns = self.workspace.policies.len();
|
||||
|
||||
let zone_states: HashMap<_, _> = self.zones.iter()
|
||||
.map(|(id, zone)| {
|
||||
(id.clone(), ZoneState {
|
||||
target_temp: zone.target_temperature,
|
||||
occupancy_learned: zone.occupancy_pattern.iter().sum::<f32>() > 0.0,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
EnvironmentStatus {
|
||||
timestamp: self.timestamp,
|
||||
active_reflexes: self.reflexes.len(),
|
||||
zones: self.zones.len(),
|
||||
learned_patterns,
|
||||
zone_states,
|
||||
recent_actions: self.action_log.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnvironmentStatus {
|
||||
pub timestamp: u64,
|
||||
pub active_reflexes: usize,
|
||||
pub zones: usize,
|
||||
pub learned_patterns: usize,
|
||||
pub zone_states: HashMap<ZoneId, ZoneState>,
|
||||
pub recent_actions: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ZoneState {
|
||||
pub target_temp: f32,
|
||||
pub occupancy_learned: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Tier 3: Synthetic Nervous Systems for Environments ===\n");
|
||||
|
||||
let mut building = SyntheticNervousSystem::new("Smart Building");
|
||||
|
||||
// Add zones
|
||||
building.add_zone("office_north", vec!["room_101", "room_102", "room_103"]);
|
||||
building.add_zone("office_south", vec!["room_201", "room_202"]);
|
||||
building.add_zone("lobby", vec!["entrance", "reception"]);
|
||||
|
||||
// Add local reflexes
|
||||
building.add_reflex(LocalEnvironmentReflex::new(
|
||||
LocationId("room_101".to_string()),
|
||||
EnvironmentSensor::Temperature,
|
||||
18.0, 28.0,
|
||||
EnvironmentActuator::HVAC { mode: HVACMode::Heating(3.0) },
|
||||
EnvironmentActuator::HVAC { mode: HVACMode::Cooling(3.0) },
|
||||
));
|
||||
|
||||
building.add_reflex(LocalEnvironmentReflex::new(
|
||||
LocationId("entrance".to_string()),
|
||||
EnvironmentSensor::Motion,
|
||||
0.0, 0.5,
|
||||
EnvironmentActuator::Lighting { brightness: 0.2 },
|
||||
EnvironmentActuator::Lighting { brightness: 1.0 },
|
||||
));
|
||||
|
||||
println!("Building initialized:");
|
||||
let status = building.status();
|
||||
println!(" Zones: {}", status.zones);
|
||||
println!(" Active reflexes: {}", status.active_reflexes);
|
||||
|
||||
// Simulate a day
|
||||
println!("\nSimulating 24 hours...");
|
||||
for hour in 0..24 {
|
||||
for minute in 0..60 {
|
||||
let timestamp = hour * 60 + minute;
|
||||
|
||||
// Generate readings based on time of day
|
||||
let occupied = (hour >= 8 && hour <= 18) && (minute % 5 == 0);
|
||||
let temp = 20.0 + 4.0 * ((hour as f32 / 24.0) * PI).sin();
|
||||
|
||||
let readings = vec![
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("room_101".to_string()),
|
||||
sensor_type: EnvironmentSensor::Temperature,
|
||||
value: temp,
|
||||
},
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("room_101".to_string()),
|
||||
sensor_type: EnvironmentSensor::Occupancy,
|
||||
value: if occupied { 1.0 } else { 0.0 },
|
||||
},
|
||||
EnvironmentReading {
|
||||
timestamp,
|
||||
location: LocationId("entrance".to_string()),
|
||||
sensor_type: EnvironmentSensor::Motion,
|
||||
value: if occupied && minute % 15 == 0 { 1.0 } else { 0.0 },
|
||||
},
|
||||
];
|
||||
|
||||
let actions = building.process(readings);
|
||||
|
||||
if hour % 4 == 0 && minute == 0 {
|
||||
println!(" Hour {}: {} actions, temp={:.1}°C, occupied={}",
|
||||
hour, actions.len(), temp, occupied);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
let status = building.status();
|
||||
println!("\n=== End of Day Status ===");
|
||||
println!(" Total actions taken: {}", status.recent_actions);
|
||||
println!(" Emergent policies learned: {}", status.learned_patterns);
|
||||
println!(" Zone states:");
|
||||
for (zone, state) in &status.zone_states {
|
||||
println!(" {:?}: target={:.1}°C, occupancy_learned={}",
|
||||
zone.0, state.target_temp, state.occupancy_learned);
|
||||
}
|
||||
|
||||
println!("\n=== Key Benefits ===");
|
||||
println!("- Infrastructure becomes a sensing fabric");
|
||||
println!("- Local reflexes handle immediate events");
|
||||
println!("- Zone homeostasis manages comfort autonomously");
|
||||
println!("- Policies emerge from patterns, not rules");
|
||||
println!("- Energy, safety, and flow self-regulate");
|
||||
println!("\nThis is exotic but inevitable.");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_local_reflex() {
|
||||
let mut reflex = LocalEnvironmentReflex::new(
|
||||
LocationId("test".to_string()),
|
||||
EnvironmentSensor::Temperature,
|
||||
18.0, 28.0,
|
||||
EnvironmentActuator::HVAC { mode: HVACMode::Heating(1.0) },
|
||||
EnvironmentActuator::HVAC { mode: HVACMode::Cooling(1.0) },
|
||||
);
|
||||
|
||||
// Cold triggers heating
|
||||
let reading = EnvironmentReading {
|
||||
timestamp: 0,
|
||||
location: LocationId("test".to_string()),
|
||||
sensor_type: EnvironmentSensor::Temperature,
|
||||
value: 15.0,
|
||||
};
|
||||
|
||||
let action = reflex.check(&reading);
|
||||
assert!(action.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zone_homeostasis() {
|
||||
let mut zone = ZoneHomeostasis::new(
|
||||
ZoneId("test".to_string()),
|
||||
vec![LocationId("room1".to_string())],
|
||||
);
|
||||
|
||||
// Learn occupancy pattern
|
||||
for _ in 0..10 {
|
||||
zone.learn_occupancy(10, 1.0); // 10am occupied
|
||||
zone.learn_occupancy(22, 0.0); // 10pm empty
|
||||
}
|
||||
|
||||
assert!(zone.predict_occupancy(10) > 0.5);
|
||||
assert!(zone.predict_occupancy(22) < 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_workspace_patterns() {
|
||||
let mut workspace = EnvironmentWorkspace::new(7);
|
||||
|
||||
// Add repeated observation
|
||||
for _ in 0..5 {
|
||||
workspace.broadcast(WorkspaceItem {
|
||||
zone: ZoneId("test".to_string()),
|
||||
observation: "Temperature_high".to_string(),
|
||||
salience: 1.0,
|
||||
timestamp: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let pattern = workspace.detect_patterns();
|
||||
assert!(pattern.is_some());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue