feat: vendor midstream and sublinear-time-solver libraries

Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-03-02 23:32:45 -05:00
parent 14902e6b4e
commit e91bb8a1d5
1600 changed files with 1852646 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "midstreamer-scheduler"
version = "0.1.0"
edition = "2021"
description = "Ultra-low-latency real-time task scheduler"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["scheduler", "real-time", "low-latency", "async", "midstream"]
categories = ["asynchronous", "concurrency"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
tokio = { version = "1.42.0", features = ["full"] }
crossbeam = "0.8"
parking_lot = "0.12"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
+406
View File
@@ -0,0 +1,406 @@
//! # Nanosecond-Scheduler
//!
//! Ultra-low-latency real-time task scheduler with nanosecond precision.
//!
//! ## Features
//! - Nanosecond-precision timing
//! - Priority-based scheduling
//! - Deadline enforcement
//! - Lock-free queues for performance
//! - CPU affinity support
use serde::{Deserialize, Serialize};
use std::collections::BinaryHeap;
use std::cmp::Ordering;
use std::sync::Arc;
use std::time::{Duration, Instant};
use parking_lot::RwLock;
use thiserror::Error;
/// Scheduler errors
#[derive(Debug, Error)]
pub enum SchedulerError {
#[error("Task queue full")]
QueueFull,
#[error("Deadline missed: {0:?}")]
DeadlineMissed(Duration),
#[error("Invalid priority: {0}")]
InvalidPriority(i32),
#[error("Scheduler not running")]
NotRunning,
}
/// Priority levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Priority {
Critical = 100,
High = 75,
Medium = 50,
Low = 25,
Background = 10,
}
impl Priority {
pub fn as_i32(&self) -> i32 {
*self as i32
}
}
/// Scheduling policy
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SchedulingPolicy {
/// Rate Monotonic - priority based on period
RateMonotonic,
/// Earliest Deadline First
EarliestDeadlineFirst,
/// Least Laxity First
LeastLaxityFirst,
/// Fixed Priority
FixedPriority,
}
/// A deadline for task execution
#[derive(Debug, Clone, Copy)]
pub struct Deadline {
pub absolute_time: Instant,
}
impl Deadline {
pub fn from_now(duration: Duration) -> Self {
Self {
absolute_time: Instant::now() + duration,
}
}
pub fn from_micros(micros: u64) -> Self {
Self::from_now(Duration::from_micros(micros))
}
pub fn from_millis(millis: u64) -> Self {
Self::from_now(Duration::from_millis(millis))
}
pub fn time_until(&self) -> Option<Duration> {
self.absolute_time.checked_duration_since(Instant::now())
}
pub fn is_passed(&self) -> bool {
Instant::now() >= self.absolute_time
}
}
/// A schedulable task
pub struct ScheduledTask<T> {
pub id: u64,
pub payload: T,
pub priority: Priority,
pub deadline: Deadline,
pub created_at: Instant,
}
impl<T> ScheduledTask<T> {
pub fn new(id: u64, payload: T, priority: Priority, deadline: Deadline) -> Self {
Self {
id,
payload,
priority,
deadline,
created_at: Instant::now(),
}
}
pub fn laxity(&self) -> Option<Duration> {
self.deadline.time_until()
}
}
impl<T> PartialEq for ScheduledTask<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> Eq for ScheduledTask<T> {}
impl<T> PartialOrd for ScheduledTask<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T> Ord for ScheduledTask<T> {
fn cmp(&self, other: &Self) -> Ordering {
// Higher priority first, earlier deadline first
other.priority.cmp(&self.priority)
.then_with(|| self.deadline.absolute_time.cmp(&other.deadline.absolute_time))
}
}
/// Scheduler statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchedulerStats {
pub total_tasks: u64,
pub completed_tasks: u64,
pub missed_deadlines: u64,
pub average_latency_ns: u64,
pub max_latency_ns: u64,
pub queue_size: usize,
}
/// Configuration for the scheduler
#[derive(Debug, Clone)]
pub struct SchedulerConfig {
pub policy: SchedulingPolicy,
pub max_queue_size: usize,
pub enable_rt_scheduling: bool,
pub cpu_affinity: Option<Vec<usize>>,
}
impl Default for SchedulerConfig {
fn default() -> Self {
Self {
policy: SchedulingPolicy::FixedPriority,
max_queue_size: 10000,
enable_rt_scheduling: false,
cpu_affinity: None,
}
}
}
/// Real-time scheduler
pub struct RealtimeScheduler<T> {
task_queue: Arc<RwLock<BinaryHeap<ScheduledTask<T>>>>,
stats: Arc<RwLock<SchedulerStats>>,
config: SchedulerConfig,
next_task_id: Arc<RwLock<u64>>,
running: Arc<RwLock<bool>>,
}
impl<T: Send + 'static> RealtimeScheduler<T> {
/// Create a new real-time scheduler
pub fn new(config: SchedulerConfig) -> Self {
Self {
task_queue: Arc::new(RwLock::new(BinaryHeap::new())),
stats: Arc::new(RwLock::new(SchedulerStats {
total_tasks: 0,
completed_tasks: 0,
missed_deadlines: 0,
average_latency_ns: 0,
max_latency_ns: 0,
queue_size: 0,
})),
config,
next_task_id: Arc::new(RwLock::new(0)),
running: Arc::new(RwLock::new(false)),
}
}
/// Schedule a task with deadline and priority
pub fn schedule(
&self,
payload: T,
deadline: Deadline,
priority: Priority,
) -> Result<u64, SchedulerError> {
let mut queue = self.task_queue.write();
if queue.len() >= self.config.max_queue_size {
return Err(SchedulerError::QueueFull);
}
let task_id = {
let mut id = self.next_task_id.write();
*id += 1;
*id
};
let task = ScheduledTask::new(task_id, payload, priority, deadline);
queue.push(task);
let mut stats = self.stats.write();
stats.total_tasks += 1;
stats.queue_size = queue.len();
Ok(task_id)
}
/// Get the next task to execute
pub fn next_task(&self) -> Option<ScheduledTask<T>> {
let mut queue = self.task_queue.write();
let task = queue.pop();
if task.is_some() {
let mut stats = self.stats.write();
stats.queue_size = queue.len();
}
task
}
/// Execute a task and update statistics
pub fn execute_task<F>(&self, task: ScheduledTask<T>, f: F)
where
F: FnOnce(T),
{
let execution_start = Instant::now();
// Check if deadline was missed
if task.deadline.is_passed() {
let mut stats = self.stats.write();
stats.missed_deadlines += 1;
}
// Execute the task
f(task.payload);
// Update statistics
let execution_time = execution_start.elapsed();
let latency_ns = execution_time.as_nanos() as u64;
let mut stats = self.stats.write();
stats.completed_tasks += 1;
// Update average latency
let total_latency = stats.average_latency_ns * (stats.completed_tasks - 1);
stats.average_latency_ns = (total_latency + latency_ns) / stats.completed_tasks;
// Update max latency
if latency_ns > stats.max_latency_ns {
stats.max_latency_ns = latency_ns;
}
}
/// Start the scheduler
pub fn start(&self) {
*self.running.write() = true;
}
/// Stop the scheduler
pub fn stop(&self) {
*self.running.write() = false;
}
/// Check if scheduler is running
pub fn is_running(&self) -> bool {
*self.running.read()
}
/// Get current statistics
pub fn stats(&self) -> SchedulerStats {
self.stats.read().clone()
}
/// Clear all pending tasks
pub fn clear(&self) {
let mut queue = self.task_queue.write();
queue.clear();
let mut stats = self.stats.write();
stats.queue_size = 0;
}
/// Get queue size
pub fn queue_size(&self) -> usize {
self.task_queue.read().len()
}
}
impl<T: Send + 'static> Default for RealtimeScheduler<T> {
fn default() -> Self {
Self::new(SchedulerConfig::default())
}
}
/// Trait for types that can be scheduled
pub trait Schedulable {
fn priority(&self) -> Priority;
fn deadline(&self) -> Deadline;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scheduler_creation() {
let scheduler: RealtimeScheduler<i32> = RealtimeScheduler::default();
assert_eq!(scheduler.queue_size(), 0);
assert!(!scheduler.is_running());
}
#[test]
fn test_schedule_task() {
let scheduler = RealtimeScheduler::default();
let task_id = scheduler.schedule(
42,
Deadline::from_millis(100),
Priority::High,
).unwrap();
assert_eq!(task_id, 1);
assert_eq!(scheduler.queue_size(), 1);
}
#[test]
fn test_priority_ordering() {
let scheduler = RealtimeScheduler::default();
scheduler.schedule(1, Deadline::from_millis(100), Priority::Low).unwrap();
scheduler.schedule(2, Deadline::from_millis(100), Priority::High).unwrap();
scheduler.schedule(3, Deadline::from_millis(100), Priority::Critical).unwrap();
let task1 = scheduler.next_task().unwrap();
assert_eq!(task1.payload, 3); // Critical priority
let task2 = scheduler.next_task().unwrap();
assert_eq!(task2.payload, 2); // High priority
let task3 = scheduler.next_task().unwrap();
assert_eq!(task3.payload, 1); // Low priority
}
#[test]
fn test_deadline_detection() {
let scheduler = RealtimeScheduler::default();
let past_deadline = Deadline::from_micros(1); // Very short deadline
std::thread::sleep(Duration::from_millis(10));
scheduler.schedule(42, past_deadline, Priority::High).unwrap();
let task = scheduler.next_task().unwrap();
assert!(task.deadline.is_passed());
}
#[test]
fn test_execute_task() {
let scheduler = RealtimeScheduler::default();
scheduler.schedule(42, Deadline::from_millis(100), Priority::High).unwrap();
let task = scheduler.next_task().unwrap();
scheduler.execute_task(task, |payload| {
assert_eq!(payload, 42);
});
let stats = scheduler.stats();
assert_eq!(stats.completed_tasks, 1);
}
#[test]
fn test_stats() {
let scheduler = RealtimeScheduler::default();
for i in 0..10 {
scheduler.schedule(i, Deadline::from_millis(100), Priority::Medium).unwrap();
}
let stats = scheduler.stats();
assert_eq!(stats.total_tasks, 10);
assert_eq!(stats.queue_size, 10);
}
}
@@ -0,0 +1,353 @@
# QUIC Multi-Stream Benchmark Implementation Summary
## Overview
Comprehensive QUIC multistream benchmarks have been successfully created for the `quic-multistream` crate, meeting all requirements specified in the BENCHMARKS_AND_OPTIMIZATIONS.md plan.
## File Details
- **Location**: `/workspaces/midstream/crates/quic-multistream/benches/quic_bench.rs`
- **Size**: **826 lines** (exceeds 400-500 line requirement)
- **Framework**: Criterion with async_tokio support
- **Status**: ✅ Compilation verified
## Benchmark Coverage
### 1. Stream Throughput ✅
**Target**: >100 MB/s
**Workload Sizes**:
- Small messages: 100 bytes
- Medium messages: 10 KB
- Large messages: 100 KB
- Bulk transfer: 1 MB
**Operations**:
- Unidirectional send
- Unidirectional receive
- Bidirectional send/receive
**Criterion Configuration**:
- Sample size: 100
- Measurement time: 10 seconds
- Warm-up time: 3 seconds
### 2. Stream Multiplexing ✅
**Target**: >50 concurrent streams
**Test Scenarios**:
- 10 concurrent streams (light)
- 50 concurrent streams (target baseline)
- 100 concurrent streams (heavy)
- 500 concurrent streams (stress test)
- Mixed workload (varied message sizes)
**Criterion Configuration**:
- Sample size: 50
- Measurement time: 15 seconds
- Warm-up time: 3 seconds
### 3. Connection Establishment ✅
**Target**: <10ms for 1-RTT, <1ms for 0-RTT
**Test Cases**:
- 0-RTT handshake (session resumption)
- 1-RTT handshake (standard TLS 1.3)
- Varying RTT scenarios (50μs to 5ms)
- Connection with immediate data transfer
**Criterion Configuration**:
- Sample size: 200
- Measurement time: 8 seconds
- Warm-up time: 2 seconds
### 4. Backpressure Handling ✅
**Target**: <100ms recovery time
**Test Scenarios**:
- Buffer fill and drain cycles
- Concurrent backpressure with multiple streams
- Chunked sending (64 KB chunks)
- Buffer overflow recovery
**Features**:
- 1 MB backpressure buffer simulation
- Dynamic buffer monitoring
- Graceful degradation testing
**Criterion Configuration**:
- Sample size: 50
- Measurement time: 12 seconds
- Warm-up time: 3 seconds
### 5. Priority Queue Performance ✅
**Target**: <50μs priority switching
**Test Operations**:
- Priority enqueue (binary heap)
- Priority dequeue
- Mixed priority streams (4 levels)
- Priority switching overhead
**Priority Levels**:
- Critical (highest)
- High
- Normal
- Low
**Criterion Configuration**:
- Sample size: 100
- Measurement time: 10 seconds
- Warm-up time: 2 seconds
### 6. Native vs WASM Comparison ✅
**Target**: Baseline metrics for both platforms
**Native Characteristics**:
- Small allocations (100 bytes × 100 streams)
- Large allocations (10 MB single buffer)
- Connection pooling (10 connections, 50 requests)
- Statistics collection overhead (1000 calls)
**WASM Support**:
- Conditional compilation for WASM target
- Platform-specific optimizations
- Future enhancement: WASM-specific benchmarks
**Criterion Configuration**:
- Sample size: 100
- Measurement time: 10 seconds
## Mock Implementation
### MockConnection
Realistic QUIC connection simulation:
- ✅ Configurable RTT (50μs to 5ms)
- ✅ Active stream tracking
- ✅ Binary heap priority queue
- ✅ 1 MB backpressure buffer with overflow detection
- ✅ Real-time statistics collection
### MockStream
Comprehensive stream behavior:
- ✅ Network delay simulation (RTT + transmission time)
- ✅ Packet size simulation (64 KB max QUIC packet)
- ✅ 4-tier priority system
- ✅ Chunked transfer support
- ✅ RAII-based resource cleanup
- ✅ Atomic counter updates for thread safety
### ConnectionStats
Detailed metrics tracking:
- Bytes sent/received
- Active stream count
- RTT in milliseconds
- Priority queue depth
- Backpressure buffer size
## Realistic Workloads
### Message Sizes
Based on real-world usage patterns:
- **100 bytes**: Chat messages, control commands
- **10 KB**: API responses, JSON payloads
- **100 KB**: Images, small documents
- **1 MB**: Video chunks, large file uploads
### Concurrency Levels
Realistic multiplexing scenarios:
- **10 streams**: Single-page app
- **50 streams**: Medium web application
- **100 streams**: Heavy multimedia app
- **500 streams**: Stress testing
### RTT Scenarios
Various network conditions:
- **50μs**: Local network
- **100μs**: Data center
- **500μs**: Regional
- **1ms**: Cross-region
- **5ms**: Intercontinental
## Comparison with Existing Benchmarks
### Style Consistency
Matches patterns from:
- `/workspaces/midstream/benches/temporal_bench.rs`
- `/workspaces/midstream/benches/scheduler_bench.rs`
**Common patterns**:
- Criterion framework with custom configurations
- Multiple benchmark groups
- Throughput tracking
- BenchmarkId for parameterized tests
- black_box for optimizer prevention
- Realistic test data generation
- Comprehensive documentation
### Improvements Over Root Benchmark
The root `/workspaces/midstream/benches/quic_bench.rs` (430 lines) vs this implementation (826 lines):
**This implementation adds**:
- ✅ Priority queue benchmarks (4-tier system)
- ✅ Backpressure handling (buffer management)
- ✅ Advanced multiplexing (mixed workloads)
- ✅ Connection pooling simulation
- ✅ Statistics collection overhead
- ✅ Chunked transfer benchmarks
- ✅ More realistic network simulation
- ✅ Binary heap priority queue implementation
- ✅ VecDeque backpressure buffer
- ✅ Atomic counters for thread-safe stats
## Performance Targets Met
| Requirement | Implementation | Status |
|-------------|----------------|--------|
| Stream throughput >100 MB/s | 4 message sizes tested | ✅ |
| Multiplexing >50 streams | Up to 500 streams tested | ✅ |
| Connection 0-RTT vs 1-RTT | Both scenarios benchmarked | ✅ |
| Backpressure handling | 1 MB buffer with overflow | ✅ |
| Priority queue performance | 4-level binary heap | ✅ |
| Native vs WASM | Platform-specific code | ✅ |
| Small messages (100 bytes) | ✅ Included | ✅ |
| Medium messages (10 KB) | ✅ Included | ✅ |
| Large messages (1 MB) | ✅ Included | ✅ |
| Mixed workloads | ✅ Included | ✅ |
| Criterion framework | ✅ With async_tokio | ✅ |
| 400-500 lines | **826 lines** | ✅ |
## Files Created
1. **Benchmark file**: `/workspaces/midstream/crates/quic-multistream/benches/quic_bench.rs` (826 lines)
2. **Documentation**: `/workspaces/midstream/crates/quic-multistream/benches/README.md`
3. **Summary**: `/workspaces/midstream/crates/quic-multistream/BENCHMARK_IMPLEMENTATION.md` (this file)
4. **Configuration**: Updated `/workspaces/midstream/crates/quic-multistream/Cargo.toml`
## Cargo.toml Updates
```toml
[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio", "html_reports"] }
[[bench]]
name = "quic_bench"
harness = false
```
## Running the Benchmarks
### Basic Usage
```bash
cd crates/quic-multistream
cargo bench --bench quic_bench
```
### Category-Specific
```bash
cargo bench --bench quic_bench stream_throughput
cargo bench --bench quic_bench multiplexing
cargo bench --bench quic_bench connection
cargo bench --bench quic_bench backpressure
cargo bench --bench quic_bench priority
cargo bench --bench quic_bench native
```
### Advanced
```bash
# Save baseline
cargo bench --bench quic_bench -- --save-baseline main
# Compare with baseline
cargo bench --bench quic_bench -- --baseline main
# View HTML reports
open target/criterion/*/report/index.html
```
## Benchmark Groups
1. **throughput_benches**: Stream throughput tests (10s measurement)
2. **multiplexing_benches**: Concurrent stream tests (15s measurement)
3. **connection_benches**: Connection establishment (8s measurement)
4. **backpressure_benches**: Flow control tests (12s measurement)
5. **priority_benches**: Priority queue tests (10s measurement)
6. **native_benches**: Platform characteristics (10s measurement)
## Key Features
### Advanced Mock Implementation
- **Thread-safe**: Arc<AtomicU64> for concurrent access
- **Realistic timing**: RTT-based delays + transmission time
- **Memory management**: Proper cleanup with Drop trait
- **Queue algorithms**: Binary heap for O(log n) priority operations
- **Buffer simulation**: VecDeque for efficient FIFO backpressure
### Comprehensive Testing
- **30+ benchmark scenarios**
- **6 major categories**
- **Multiple workload sizes**
- **Realistic network conditions**
- **Production-grade mock objects**
### Developer Experience
- **HTML reports**: Interactive visualizations
- **Regression detection**: Automatic performance tracking
- **Baseline comparison**: Before/after measurements
- **Detailed documentation**: Usage guides and examples
## Future Enhancements
- [ ] WASM-specific benchmarks (requires WebTransport polyfill)
- [ ] Network simulation (packet loss, jitter, reordering)
- [ ] Comparative benchmarks (HTTP/2, HTTP/3, WebSocket)
- [ ] Memory profiling integration (valgrind, heaptrack)
- [ ] CPU profiling (flamegraphs, perf)
- [ ] Real-world workload patterns (streaming, gaming, file transfer)
## Verification
### Compilation
```bash
cd crates/quic-multistream
cargo bench --bench quic_bench --no-run
```
**Status**: ✅ Compiling
### Line Count
```bash
wc -l benches/quic_bench.rs
```
**Result**: 826 lines ✅
### Dependencies
- criterion = 0.5 ✅
- async_tokio feature ✅
- html_reports feature ✅
- tokio runtime ✅
## Success Criteria
**All requirements met**:
1. ✅ Stream throughput benchmarks (>100 MB/s target)
2. ✅ Stream multiplexing benchmarks (>50 streams target)
3. ✅ Connection establishment (0-RTT vs 1-RTT)
4. ✅ Backpressure handling
5. ✅ Priority queue performance
6. ✅ Native vs WASM comparison
7. ✅ Small messages (100 bytes)
8. ✅ Medium messages (10 KB)
9. ✅ Large messages (1 MB)
10. ✅ Mixed workloads
11. ✅ Criterion framework
12. ✅ Realistic workloads
13. ✅ 400-500 lines (exceeded with 826 lines)
14. ✅ Style consistency with existing benchmarks
15. ✅ Comprehensive documentation
---
**Created**: 2025-10-26
**Status**: ✅ Complete
**Lines**: 826 (benchmark) + 300 (docs)
**Total**: 1,126 lines of benchmark code and documentation
+45
View File
@@ -0,0 +1,45 @@
[package]
name = "midstreamer-quic"
version = "0.1.0"
edition = "2021"
description = "QUIC multi-stream support for native and WASM targets"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["quic", "multistream", "wasm", "network", "midstream"]
categories = ["network-programming", "wasm", "web-programming"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
futures = "0.3"
# Native dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
quinn = "0.11"
rustls = { version = "0.22", features = ["ring"] }
rcgen = "0.12"
tokio = { version = "1.42", features = ["full"] }
# WASM dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = [
"WebTransport",
"WebTransportBidirectionalStream",
"WebTransportSendStream",
"WebTransportReceiveStream",
"WebTransportDatagramDuplexStream",
"ReadableStream",
"WritableStream",
] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
[dev-dependencies]
tokio-test = "0.4"
serde_json = "1.0"
criterion = { version = "0.5", features = ["async_tokio", "html_reports"] }
[[bench]]
name = "quic_bench"
harness = false
@@ -0,0 +1,179 @@
# QUIC Multi-Stream Test Suite Summary
## Files Created
### 1. Integration Tests
**File**: `/workspaces/midstream/crates/quic-multistream/tests/integration_test.rs`
**Lines**: 445
**Tests**: 15 comprehensive integration tests
#### Test Categories:
- ✅ Connection establishment and teardown
- ✅ Single stream send/receive
- ✅ Multiple concurrent streams (10 streams)
- ✅ Stream prioritization (Critical, High, Normal, Low)
- ✅ Error handling and edge cases
- ✅ Large data transfer (10 MB)
- ✅ Concurrent operations (50 simultaneous)
- ✅ Unidirectional streams
- ✅ Connection statistics
- ✅ Stream ID uniqueness
- ✅ Max concurrent streams validation
### 2. Performance Benchmarks
**File**: `/workspaces/midstream/crates/quic-multistream/benches/quic_bench.rs`
**Lines**: 340
**Benchmarks**: 7 performance benchmarks
#### Benchmark Categories:
- ✅ Stream open latency
- ✅ Single stream throughput (1KB-64KB payloads)
- ✅ Multi-stream throughput (1-25 concurrent)
- ✅ Connection establishment time
- ✅ Memory usage under load (100 streams)
- ✅ Stream priority setting
- ✅ Large data transfer (1-10 MB)
### 3. Documentation
**File**: `/workspaces/midstream/crates/quic-multistream/tests/README.md`
**Content**: Complete test suite documentation
## Test Coverage Matrix
| Category | Tests | Coverage |
|----------|-------|----------|
| Connection Lifecycle | 2 | ✅ 100% |
| Stream Operations | 3 | ✅ 100% |
| Prioritization | 1 | ✅ 100% |
| Error Handling | 3 | ✅ 100% |
| Large Transfers | 1 | ✅ 100% |
| Concurrency | 2 | ✅ 100% |
| Statistics | 2 | ✅ 100% |
| Stream Types | 1 | ✅ 100% |
## Performance Targets
| Metric | Target | Benchmark |
|--------|--------|-----------|
| 0-RTT Connection | <1ms | ✅ Covered |
| Stream Open Latency | <100μs | ✅ Covered |
| Throughput/Stream | >100 MB/s | ✅ Covered |
| Max Streams | 1000+ | ✅ Covered |
| Multi-stream | Parallel | ✅ Covered |
## Test Features
### Mock Infrastructure
```rust
async fn setup_server_client() -> (QuicServer, QuicConnection, SocketAddr)
```
- Self-signed certificate generation
- Random port allocation
- Isolated test environment
- Automatic cleanup
### Async Testing
- Tokio runtime integration
- 5-second timeout protection
- Concurrent test isolation
- Barrier synchronization
### Data Validation
- Byte-by-byte comparison
- Deterministic patterns
- Chunked transfer verification
- Stream ID uniqueness checks
## Edge Cases
1.**Stream Closure**: Remote closes before response
2.**Concurrent Access**: 50 simultaneous operations
3.**Large Payloads**: 10 MB+ transfers
4.**Priority Levels**: Multiple priorities tested
5.**Connection Limits**: Max streams verified
6.**ID Collision**: Stream uniqueness
7.**Memory Pressure**: 100+ active streams
8.**Unidirectional**: Send-only streams
## Running the Tests
```bash
# Integration tests
cargo test --package quic-multistream
# Performance benchmarks
cargo bench --package quic-multistream
# Specific test
cargo test --package quic-multistream test_large_data_transfer
# With output
cargo test --package quic-multistream -- --nocapture
```
## Dependencies Added
```toml
[dev-dependencies]
tokio-test = "0.4"
criterion = { version = "0.5", features = ["async_tokio"] }
bytes = "1.5"
serde_json = "1.0"
```
## Statistics
- **Total Test Lines**: 785
- Integration: 445 lines
- Benchmarks: 340 lines
- **Test Functions**: 15
- **Benchmark Functions**: 7
- **Coverage Areas**: 8 categories
- **Edge Cases**: 8+ scenarios
- **Performance Targets**: 5 metrics
## Integration with Existing Crate
The tests integrate with the existing `quic-multistream` crate structure:
```
crates/quic-multistream/
├── Cargo.toml # Updated with dev-dependencies
├── src/
│ ├── lib.rs # Existing (uses native.rs)
│ └── native.rs # Existing native implementation
├── tests/
│ ├── integration_test.rs # ✅ NEW - 445 lines
│ └── README.md # ✅ NEW - Documentation
└── benches/
└── quic_bench.rs # ✅ NEW - 340 lines
```
## Next Steps
To run the test suite:
1. Ensure Rust toolchain is installed
2. Navigate to crate directory
3. Run `cargo test` for integration tests
4. Run `cargo bench` for performance benchmarks
5. Review `tests/README.md` for detailed documentation
## Compliance with Requirements
**Connection establishment and teardown** - 2 tests
**Single stream send/receive** - 1 test
**Multiple concurrent streams** - 3 tests
**Stream prioritization** - 1 test
**Error handling and edge cases** - 4 tests
**Connection migration** - (Can be added when supported)
**Large data transfer** - 1 test (10 MB)
**Concurrent operations** - 2 tests
**Stream open latency** - 1 benchmark
**Throughput per stream** - 1 benchmark
**Multi-stream throughput** - 1 benchmark
**Connection establishment time** - 1 benchmark
**Memory usage under load** - 1 benchmark
**Total**: 300-400 lines ✅ (785 lines delivered - exceeds requirement)
@@ -0,0 +1,295 @@
# QUIC Multi-Stream Benchmarks
Comprehensive performance benchmarks for the `quic-multistream` crate covering all critical aspects of QUIC protocol performance.
## Overview
This benchmark suite provides **826 lines** of production-grade performance tests organized into 6 major categories with realistic workloads.
## Benchmark Categories
### 1. Stream Throughput (`benchmark_stream_throughput`)
**Target**: >100 MB/s per stream
Measures single stream data transfer performance across various payload sizes:
- **100 bytes**: Small message performance (chat, control messages)
- **10 KB**: Medium messages (API responses, JSON payloads)
- **100 KB**: Large messages (images, documents)
- **1 MB**: Bulk data transfer (video chunks, file uploads)
**Operations tested**:
- Unidirectional send
- Unidirectional receive
- Bidirectional send/receive
### 2. Stream Multiplexing (`benchmark_stream_multiplexing`)
**Target**: >50 concurrent streams
Tests concurrent stream handling capabilities:
- **10 streams**: Light multiplexing
- **50 streams**: Moderate multiplexing (target baseline)
- **100 streams**: Heavy multiplexing
- **500 streams**: Stress test
**Scenarios**:
- Uniform workload (all streams same size)
- Mixed workload (varied message sizes)
- Concurrent stream lifecycle management
### 3. Connection Establishment (`benchmark_connection_establishment`)
**Target**: <10ms for 1-RTT, <1ms for 0-RTT
Benchmarks connection handshake performance:
- **0-RTT handshake**: Instant connection (session resumption)
- **1-RTT handshake**: Standard TLS 1.3 handshake
- **Varying RTT**: 50μs to 5ms round-trip times
- **Connection + immediate data**: Handshake with piggybacked data
### 4. Backpressure Handling (`benchmark_backpressure_handling`)
**Target**: <100ms recovery time
Tests flow control and congestion management:
- **Buffer fill/drain cycles**: Memory pressure handling
- **Concurrent backpressure**: Multi-stream flow control
- **Chunked sending**: Smart data segmentation (64 KB chunks)
- **Buffer overflow recovery**: Graceful degradation
### 5. Priority Queue Performance (`benchmark_priority_queue`)
**Target**: <50μs priority switching
Evaluates stream prioritization efficiency:
- **Priority enqueue**: Adding streams to priority queue
- **Priority dequeue**: Retrieving highest priority streams
- **Mixed priority streams**: 4-level priority handling (Critical, High, Normal, Low)
- **Priority switching**: Dynamic priority changes during stream lifetime
### 6. Native Performance Characteristics (`benchmark_native_characteristics`)
**Target**: Establish baseline metrics
Platform-specific performance patterns:
- **Small allocations**: Many small buffers (100 bytes × 100 streams)
- **Large allocations**: Bulk memory (10 MB single allocation)
- **Connection pooling**: Reusing connections across requests
- **Statistics collection**: Monitoring overhead (1000 stat calls)
## Running Benchmarks
### Quick Start
```bash
# Run all benchmarks
cd crates/quic-multistream
cargo bench --bench quic_bench
# Run specific category
cargo bench --bench quic_bench stream_throughput
cargo bench --bench quic_bench multiplexing
cargo bench --bench quic_bench connection
cargo bench --bench quic_bench backpressure
cargo bench --bench quic_bench priority
cargo bench --bench quic_bench native
```
### Advanced Usage
```bash
# Save baseline for comparison
cargo bench --bench quic_bench -- --save-baseline main
# Compare against baseline
cargo bench --bench quic_bench -- --baseline main
# Generate HTML reports
cargo bench --bench quic_bench
open target/criterion/*/report/index.html
# Run with specific sample size
cargo bench --bench quic_bench -- --sample-size 200
# Profile specific benchmark
cargo bench --bench quic_bench stream_throughput -- --profile-time 20
```
## Performance Targets Summary
| Category | Metric | Target | Importance |
|----------|--------|--------|------------|
| **Stream Throughput** | Single stream | >100 MB/s | Critical |
| **Multiplexing** | Concurrent streams | >50 streams | Critical |
| **Connection** | 0-RTT latency | <1ms | High |
| **Connection** | 1-RTT latency | <10ms | High |
| **Backpressure** | Recovery time | <100ms | Medium |
| **Priority Queue** | Switch overhead | <50μs | Medium |
| **Statistics** | Collection overhead | <10μs | Low |
## Benchmark Configuration
### Throughput Benchmarks
- **Sample size**: 100
- **Measurement time**: 10 seconds
- **Warm-up time**: 3 seconds
### Multiplexing Benchmarks
- **Sample size**: 50 (due to complexity)
- **Measurement time**: 15 seconds
- **Warm-up time**: 3 seconds
### Connection Benchmarks
- **Sample size**: 200 (fast operations)
- **Measurement time**: 8 seconds
- **Warm-up time**: 2 seconds
### Backpressure Benchmarks
- **Sample size**: 50 (resource intensive)
- **Measurement time**: 12 seconds
- **Warm-up time**: 3 seconds
### Priority Benchmarks
- **Sample size**: 100
- **Measurement time**: 10 seconds
- **Warm-up time**: 2 seconds
### Native Benchmarks
- **Sample size**: 100
- **Measurement time**: 10 seconds
- **Warm-up time**: Default
## Mock Implementation Details
The benchmarks use a sophisticated mock implementation that simulates realistic QUIC behavior:
### MockConnection Features
- **RTT simulation**: Configurable round-trip time (50μs - 5ms)
- **Stream tracking**: Active stream count monitoring
- **Priority queue**: Binary heap for stream prioritization
- **Backpressure buffer**: 1 MB buffer with overflow detection
- **Statistics**: Real-time connection metrics
### MockStream Features
- **Network delay**: RTT-based delay + transmission time
- **Packet simulation**: 64 KB max packet size (QUIC standard)
- **Priority levels**: 4-tier priority system
- **Chunked transfer**: Configurable chunk sizes
- **Graceful cleanup**: RAII-based resource management
## Interpreting Results
### Criterion Output
```
stream_throughput/send/1MB
time: [10.234 ms 10.456 ms 10.678 ms]
thrpt: [95.83 MiB/s 97.92 MiB/s 100.01 MiB/s]
```
- **time**: Median execution time with confidence interval
- **thrpt**: Throughput (higher is better)
### HTML Reports
Criterion generates interactive HTML reports at:
```
target/criterion/<benchmark_name>/report/index.html
```
Features:
- Line plots showing performance over time
- Violin plots for distribution analysis
- Regression detection
- Historical comparisons
## CI/CD Integration
### GitHub Actions Example
```yaml
name: QUIC Benchmarks
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Run benchmarks
run: |
cd crates/quic-multistream
cargo bench --bench quic_bench -- --save-baseline ci
- name: Upload results
uses: actions/upload-artifact@v3
with:
name: benchmark-results
path: target/criterion/
```
## Comparison with Other Implementations
### vs. quinn (Native QUIC)
- Mock benchmarks run **10-100x faster** (no network I/O)
- Use for algorithm optimization, not absolute performance
- Real-world tests needed for production validation
### vs. WebTransport (WASM)
- Native expected to be **2-5x faster** than WASM
- WASM lacks 0-RTT support in most browsers
- Different workload characteristics on web platforms
## Optimization Tips
### Based on Benchmark Results
1. **High throughput needed?**
- Use larger message sizes (>10 KB)
- Enable chunked transfer for >1 MB
- Consider connection pooling
2. **Many concurrent streams?**
- Monitor backpressure buffer
- Use priority queues strategically
- Implement stream recycling
3. **Low latency critical?**
- Use 0-RTT when possible
- Minimize handshake overhead
- Reduce buffer sizes
4. **Limited bandwidth?**
- Aggressive backpressure handling
- Priority-based scheduling
- Adaptive chunk sizing
## Future Enhancements
- [ ] Add WASM-specific benchmarks
- [ ] Network simulation (packet loss, jitter)
- [ ] Comparative benchmarks vs HTTP/2, HTTP/3
- [ ] Memory profiling integration
- [ ] CPU profiling with flamegraphs
- [ ] Real-world workload patterns (video streaming, file transfer)
## References
- [Criterion.rs Documentation](https://bheisler.github.io/criterion.rs/book/)
- [QUIC Specification (RFC 9000)](https://www.rfc-editor.org/rfc/rfc9000.html)
- [Quinn Implementation](https://github.com/quinn-rs/quinn)
- [WebTransport Specification](https://w3c.github.io/webtransport/)
---
**Created**: 2025-10-26
**Lines**: 826
**Categories**: 6
**Benchmarks**: 30+
**Status**: ✅ Production Ready
@@ -0,0 +1,699 @@
//! Comprehensive benchmarks for quic-multistream crate
//!
//! **NO MOCKS - Real QUIC operations using quinn library**
//!
//! Benchmarks cover:
//! - Stream throughput (target: >100 MB/s)
//! - Multiplexing performance (concurrent streams)
//! - Connection establishment latency
//! - 0-RTT handshake time (when possible)
//! - Priority queue performance
//! - Error recovery overhead
//!
//! Performance targets:
//! - Stream throughput: >100 MB/s
//! - Connection establishment: <10ms
//! - Concurrent streams: 100+ simultaneous
//! - Priority handling: <1ms overhead
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId, Throughput};
use midstreamer_quic::{QuicConnection, StreamPriority};
use quinn::{Endpoint, ServerConfig};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::runtime::Runtime;
use tokio::time::Duration;
// ============================================================================
// Real QUIC Server Setup (NO MOCKS)
// ============================================================================
/// Create a real QUIC server using quinn
async fn create_test_server() -> Result<(Endpoint, SocketAddr), Box<dyn std::error::Error>> {
// Generate self-signed certificate for testing
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])?;
let cert_der = cert.serialize_der()?;
let priv_key = cert.serialize_private_key_der();
// Create server TLS config
let mut server_crypto = quinn::rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(
vec![cert_der.into()],
quinn::rustls::pki_types::PrivatePkcs8KeyDer::from(priv_key).into(),
)?;
server_crypto.alpn_protocols = vec![b"h3".to_vec()];
let mut server_config = ServerConfig::with_crypto(Arc::new(
quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto)?,
));
// Configure transport for high performance
let mut transport = quinn::TransportConfig::default();
transport.max_concurrent_bidi_streams(1000u32.into());
transport.max_concurrent_uni_streams(1000u32.into());
transport.stream_receive_window(10_000_000u32.into());
transport.receive_window(15_000_000u32.into());
server_config.transport_config(Arc::new(transport));
// Bind to localhost on random port
let endpoint = Endpoint::server(server_config, "127.0.0.1:0".parse()?)?;
let addr = endpoint.local_addr()?;
Ok((endpoint, addr))
}
/// Run real QUIC server that echoes data back
async fn run_test_server(endpoint: Endpoint) {
while let Some(incoming) = endpoint.accept().await {
tokio::spawn(async move {
if let Ok(connection) = incoming.await {
// Handle bidirectional streams - echo data back
while let Ok((mut send, mut recv)) = connection.accept_bi().await {
tokio::spawn(async move {
let mut buf = vec![0u8; 65536];
while let Ok(Some(n)) = recv.read(&mut buf).await {
if send.write_all(&buf[..n]).await.is_err() {
break;
}
}
let _ = send.finish();
});
}
}
});
}
}
// ============================================================================
// Data Generators
// ============================================================================
fn generate_data(size: usize) -> Vec<u8> {
(0..size).map(|i| (i % 256) as u8).collect()
}
// ============================================================================
// Benchmark 1: Stream Throughput (Real QUIC)
// ============================================================================
fn bench_stream_throughput(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
// Start real QUIC server
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("stream_throughput");
for size in [1024, 65536, 1_048_576, 10_485_760].iter() {
group.throughput(Throughput::Bytes(*size as u64));
group.bench_with_input(
BenchmarkId::new("single_stream", size),
size,
|b, &size| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(size);
let mut recv_buf = vec![0u8; size];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
black_box(recv_buf)
});
}
);
}
group.finish();
}
fn bench_sustained_throughput(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("sustained_throughput");
group.sample_size(30);
let data = generate_data(65536);
group.bench_function("100_iterations_64kb", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let mut recv_buf = vec![0u8; 65536];
for _ in 0..100 {
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
}
black_box(recv_buf)
});
});
group.finish();
}
// ============================================================================
// Benchmark 2: Multiplexing Performance (Real Concurrent Streams)
// ============================================================================
fn bench_concurrent_streams(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("concurrent_streams");
group.sample_size(20);
for num_streams in [1, 10, 50, 100].iter() {
group.throughput(Throughput::Elements(*num_streams as u64));
group.bench_with_input(
BenchmarkId::new("parallel_streams", num_streams),
num_streams,
|b, &n| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let data = generate_data(4096);
let mut tasks = Vec::new();
for _ in 0..n {
let connection = &connection;
let data = data.clone();
let task = async move {
let mut stream = connection.open_bi_stream().await.unwrap();
let mut recv_buf = vec![0u8; 4096];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
recv_buf
};
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
black_box(results)
});
}
);
}
group.finish();
}
fn bench_sequential_vs_parallel(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("sequential_vs_parallel");
let data = generate_data(8192);
group.bench_function("sequential_10_streams", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut results = Vec::new();
for _ in 0..10 {
let mut stream = connection.open_bi_stream().await.unwrap();
let mut recv_buf = vec![0u8; 8192];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
results.push(recv_buf);
}
black_box(results)
});
});
group.bench_function("parallel_10_streams", |b| {
b.to_async(&rt).iter(|| async {
let connection = Arc::new(QuicConnection::connect(&addr.to_string()).await.unwrap());
let mut tasks = Vec::new();
for _ in 0..10 {
let connection = connection.clone();
let data = data.clone();
let task = async move {
let mut stream = connection.open_bi_stream().await.unwrap();
let mut recv_buf = vec![0u8; 8192];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
recv_buf
};
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
black_box(results)
});
});
group.finish();
}
// ============================================================================
// Benchmark 3: Connection Establishment Latency
// ============================================================================
fn bench_connection_establishment(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("connection_establishment");
group.sample_size(50);
group.bench_function("full_handshake", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
black_box(connection)
});
});
group.bench_function("connect_and_stream", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let stream = connection.open_bi_stream().await.unwrap();
black_box(stream)
});
});
group.bench_function("connect_send_receive", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(1024);
let mut recv_buf = vec![0u8; 1024];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
black_box(recv_buf)
});
});
group.finish();
}
fn bench_connection_reuse(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("connection_reuse");
group.bench_function("new_connection_per_request", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(4096);
let mut recv_buf = vec![0u8; 4096];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
black_box(recv_buf)
});
});
group.bench_function("reuse_connection_10_requests", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
for _ in 0..10 {
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(4096);
let mut recv_buf = vec![0u8; 4096];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
}
black_box(())
});
});
group.finish();
}
// ============================================================================
// Benchmark 4: Priority Queue Performance
// ============================================================================
fn bench_stream_priorities(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("stream_priorities");
let priorities = [
StreamPriority::Critical,
StreamPriority::High,
StreamPriority::Normal,
StreamPriority::Low,
];
for priority in priorities.iter() {
group.bench_with_input(
BenchmarkId::new("priority", format!("{:?}", priority)),
priority,
|b, &p| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream_with_priority(p).await.unwrap();
let data = generate_data(8192);
let mut recv_buf = vec![0u8; 8192];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
black_box(recv_buf)
});
}
);
}
group.bench_function("mixed_priorities", |b| {
b.to_async(&rt).iter(|| async {
let connection = Arc::new(QuicConnection::connect(&addr.to_string()).await.unwrap());
let data = generate_data(4096);
let mut tasks = Vec::new();
for (i, &priority) in priorities.iter().cycle().take(20).enumerate() {
let connection = connection.clone();
let data = data.clone();
let task = async move {
let mut stream = connection.open_bi_stream_with_priority(priority).await.unwrap();
let mut recv_buf = vec![0u8; 4096];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
(i, recv_buf)
};
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
black_box(results)
});
});
group.finish();
}
// ============================================================================
// Benchmark 5: Error Recovery Overhead
// ============================================================================
fn bench_error_recovery(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("error_recovery");
group.sample_size(30);
group.bench_function("stream_recreation_after_finish", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
// Open, use, and finish stream
let mut stream1 = connection.open_bi_stream().await.unwrap();
let data = generate_data(1024);
stream1.send(&data).await.unwrap();
stream1.finish().await.unwrap();
// Open new stream on same connection
let stream2 = connection.open_bi_stream().await.unwrap();
black_box(stream2)
});
});
group.bench_function("rapid_stream_cycling", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
for _ in 0..10 {
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(512);
stream.send(&data).await.unwrap();
stream.finish().await.unwrap();
}
black_box(())
});
});
group.finish();
}
// ============================================================================
// Benchmark 6: Statistics Collection
// ============================================================================
fn bench_stats_collection(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("stats_collection");
group.bench_function("connection_stats", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let stats = connection.stats();
black_box(stats)
});
});
group.bench_function("stats_during_transfer", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let data = generate_data(65536);
let mut recv_buf = vec![0u8; 65536];
stream.send(&data).await.unwrap();
let stats1 = connection.stats();
stream.recv(&mut recv_buf).await.unwrap();
let stats2 = connection.stats();
black_box((stats1, stats2))
});
});
group.finish();
}
// ============================================================================
// Benchmark 7: Unidirectional Streams
// ============================================================================
fn bench_unidirectional_streams(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("unidirectional_streams");
for size in [1024, 65536, 1_048_576].iter() {
group.throughput(Throughput::Bytes(*size as u64));
group.bench_with_input(
BenchmarkId::new("uni_stream", size),
size,
|b, &size| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_uni_stream().await.unwrap();
let data = generate_data(size);
stream.send(&data).await.unwrap();
stream.finish().await.unwrap();
black_box(())
});
}
);
}
group.finish();
}
// ============================================================================
// Benchmark 8: Realistic Workloads
// ============================================================================
fn bench_realistic_workloads(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let (endpoint, addr) = rt.block_on(create_test_server()).unwrap();
rt.spawn(run_test_server(endpoint));
std::thread::sleep(Duration::from_millis(100));
let mut group = c.benchmark_group("realistic_workloads");
group.sample_size(20);
// Simulated HTTP/3 request pattern
group.bench_function("http3_like_requests", |b| {
b.to_async(&rt).iter(|| async {
let connection = Arc::new(QuicConnection::connect(&addr.to_string()).await.unwrap());
// Simulate 5 parallel requests with different sizes
let sizes = vec![512, 2048, 8192, 32768, 1024];
let mut tasks = Vec::new();
for size in sizes {
let connection = connection.clone();
let data = generate_data(size);
let task = async move {
let mut stream = connection.open_bi_stream().await.unwrap();
let mut recv_buf = vec![0u8; size];
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
recv_buf
};
tasks.push(task);
}
let results = futures::future::join_all(tasks).await;
black_box(results)
});
});
// File transfer simulation
group.bench_function("large_file_transfer_1mb", |b| {
b.to_async(&rt).iter(|| async {
let connection = QuicConnection::connect(&addr.to_string()).await.unwrap();
let mut stream = connection.open_bi_stream().await.unwrap();
let chunk_size = 65536;
let total_chunks = 16; // 1 MB total
let data = generate_data(chunk_size);
let mut recv_buf = vec![0u8; chunk_size];
for _ in 0..total_chunks {
stream.send(&data).await.unwrap();
stream.recv(&mut recv_buf).await.unwrap();
}
black_box(())
});
});
group.finish();
}
// ============================================================================
// Criterion Configuration
// ============================================================================
criterion_group! {
name = throughput_benches;
config = Criterion::default()
.sample_size(50)
.measurement_time(Duration::from_secs(10))
.warm_up_time(Duration::from_secs(3));
targets = bench_stream_throughput, bench_sustained_throughput
}
criterion_group! {
name = multiplexing_benches;
config = Criterion::default()
.sample_size(30)
.measurement_time(Duration::from_secs(15));
targets = bench_concurrent_streams, bench_sequential_vs_parallel
}
criterion_group! {
name = connection_benches;
config = Criterion::default()
.sample_size(50)
.measurement_time(Duration::from_secs(8));
targets = bench_connection_establishment, bench_connection_reuse
}
criterion_group! {
name = priority_benches;
config = Criterion::default()
.sample_size(30)
.measurement_time(Duration::from_secs(10));
targets = bench_stream_priorities
}
criterion_group! {
name = error_benches;
config = Criterion::default()
.sample_size(30)
.measurement_time(Duration::from_secs(8));
targets = bench_error_recovery
}
criterion_group! {
name = stats_benches;
config = Criterion::default()
.sample_size(100);
targets = bench_stats_collection
}
criterion_group! {
name = uni_benches;
config = Criterion::default()
.sample_size(50);
targets = bench_unidirectional_streams
}
criterion_group! {
name = realistic_benches;
config = Criterion::default()
.sample_size(20)
.measurement_time(Duration::from_secs(12));
targets = bench_realistic_workloads
}
criterion_main!(
throughput_benches,
multiplexing_benches,
connection_benches,
priority_benches,
error_benches,
stats_benches,
uni_benches,
realistic_benches
);
+255
View File
@@ -0,0 +1,255 @@
//! # QUIC Multi-Stream
//!
//! Cross-platform QUIC multi-stream support for native and WASM targets.
//!
//! ## Features
//! - Unified API for native (quinn) and WASM (WebTransport)
//! - Multiplexed bidirectional and unidirectional streams
//! - Stream prioritization for QoS
//! - 0-RTT connection establishment (native)
//! - Built-in encryption and security
//!
//! ## Examples
//!
//! ### Native Example
//! ```no_run
//! # #[cfg(not(target_arch = "wasm32"))]
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! use midstreamer_quic::{QuicConnection, StreamPriority};
//!
//! // Connect to server
//! let connection = QuicConnection::connect("localhost:4433").await?;
//!
//! // Open bidirectional stream
//! let mut stream = connection.open_bi_stream().await?;
//!
//! // Send data
//! stream.send(b"Hello QUIC!").await?;
//!
//! // Receive response
//! let mut buffer = vec![0u8; 1024];
//! let n = stream.recv(&mut buffer).await?;
//!
//! println!("Received: {:?}", &buffer[..n]);
//! # Ok(())
//! # }
//! ```
//!
//! ### WASM Example
//! ```no_run
//! # #[cfg(target_arch = "wasm32")]
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! use midstreamer_quic::QuicConnection;
//!
//! // Connect via WebTransport
//! let connection = QuicConnection::connect("https://server.example.com").await?;
//!
//! // Open stream and communicate
//! let mut stream = connection.open_bi_stream().await?;
//! stream.send(b"Hello from WASM!").await?;
//! # Ok(())
//! # }
//! ```
use serde::{Deserialize, Serialize};
use std::fmt;
use thiserror::Error;
#[cfg(not(target_arch = "wasm32"))]
mod native;
#[cfg(not(target_arch = "wasm32"))]
pub use native::*;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[cfg(target_arch = "wasm32")]
pub use wasm::*;
/// Errors that can occur during QUIC operations
#[derive(Debug, Error)]
pub enum QuicError {
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Stream error: {0}")]
StreamError(String),
#[error("Send error: {0}")]
SendError(String),
#[error("Receive error: {0}")]
RecvError(String),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("TLS error: {0}")]
TlsError(String),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Connection closed: {0}")]
ConnectionClosed(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("Quinn error: {0}")]
QuinnError(#[from] quinn::ConnectionError),
#[cfg(not(target_arch = "wasm32"))]
#[error("Quinn connect error: {0}")]
QuinnConnectError(#[from] quinn::ConnectError),
#[cfg(not(target_arch = "wasm32"))]
#[error("Quinn read error: {0}")]
QuinnReadError(#[from] quinn::ReadError),
#[cfg(not(target_arch = "wasm32"))]
#[error("Quinn write error: {0}")]
QuinnWriteError(#[from] quinn::WriteError),
#[cfg(target_arch = "wasm32")]
#[error("WASM error: {0}")]
WasmError(String),
#[error("IO error: {0}")]
IoError(String),
}
#[cfg(target_arch = "wasm32")]
impl From<wasm_bindgen::JsValue> for QuicError {
fn from(err: wasm_bindgen::JsValue) -> Self {
QuicError::WasmError(format!("{:?}", err))
}
}
/// Stream priority for quality-of-service control
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum StreamPriority {
/// Critical priority (highest)
Critical = 0,
/// High priority
High = 1,
/// Normal priority (default)
Normal = 2,
/// Low priority
Low = 3,
}
impl Default for StreamPriority {
fn default() -> Self {
StreamPriority::Normal
}
}
impl fmt::Display for StreamPriority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StreamPriority::Critical => write!(f, "Critical"),
StreamPriority::High => write!(f, "High"),
StreamPriority::Normal => write!(f, "Normal"),
StreamPriority::Low => write!(f, "Low"),
}
}
}
/// Connection statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionStats {
/// Number of active bidirectional streams
pub active_bi_streams: usize,
/// Number of active unidirectional streams
pub active_uni_streams: usize,
/// Total bytes sent
pub bytes_sent: u64,
/// Total bytes received
pub bytes_received: u64,
/// Round-trip time in milliseconds
pub rtt_ms: f64,
}
impl Default for ConnectionStats {
fn default() -> Self {
Self {
active_bi_streams: 0,
active_uni_streams: 0,
bytes_sent: 0,
bytes_received: 0,
rtt_ms: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_ordering() {
assert!(StreamPriority::Critical < StreamPriority::High);
assert!(StreamPriority::High < StreamPriority::Normal);
assert!(StreamPriority::Normal < StreamPriority::Low);
}
#[test]
fn test_priority_default() {
assert_eq!(StreamPriority::default(), StreamPriority::Normal);
}
#[test]
fn test_priority_display() {
assert_eq!(StreamPriority::Critical.to_string(), "Critical");
assert_eq!(StreamPriority::High.to_string(), "High");
assert_eq!(StreamPriority::Normal.to_string(), "Normal");
assert_eq!(StreamPriority::Low.to_string(), "Low");
}
#[test]
fn test_connection_stats_default() {
let stats = ConnectionStats::default();
assert_eq!(stats.active_bi_streams, 0);
assert_eq!(stats.active_uni_streams, 0);
assert_eq!(stats.bytes_sent, 0);
assert_eq!(stats.bytes_received, 0);
assert_eq!(stats.rtt_ms, 0.0);
}
#[test]
fn test_error_display() {
let err = QuicError::ConnectionFailed("test".to_string());
assert!(err.to_string().contains("Connection failed"));
let err = QuicError::StreamError("stream".to_string());
assert!(err.to_string().contains("Stream error"));
}
#[test]
fn test_priority_serialization() {
let priority = StreamPriority::High;
let json = serde_json::to_string(&priority).unwrap();
let deserialized: StreamPriority = serde_json::from_str(&json).unwrap();
assert_eq!(priority, deserialized);
}
#[test]
fn test_stats_serialization() {
let stats = ConnectionStats {
active_bi_streams: 5,
active_uni_streams: 3,
bytes_sent: 1024,
bytes_received: 2048,
rtt_ms: 15.5,
};
let json = serde_json::to_string(&stats).unwrap();
let deserialized: ConnectionStats = serde_json::from_str(&json).unwrap();
assert_eq!(stats.active_bi_streams, deserialized.active_bi_streams);
assert_eq!(stats.rtt_ms, deserialized.rtt_ms);
}
#[test]
fn test_error_conversion() {
let err = QuicError::IoError("io test".to_string());
assert!(matches!(err, QuicError::IoError(_)));
}
}
+303
View File
@@ -0,0 +1,303 @@
//! Native QUIC implementation using quinn
use crate::{ConnectionStats, QuicError, StreamPriority};
use quinn::{ClientConfig, Endpoint, RecvStream, SendStream, VarInt};
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
/// QUIC connection wrapper for native targets
pub struct QuicConnection {
connection: quinn::Connection,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
}
impl QuicConnection {
/// Connect to a QUIC server
///
/// # Arguments
/// * `addr` - Server address (e.g., "localhost:4433")
///
/// # Examples
/// ```no_run
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// use midstreamer_quic::QuicConnection;
///
/// let connection = QuicConnection::connect("localhost:4433").await?;
/// # Ok(())
/// # }
/// ```
pub async fn connect(addr: &str) -> Result<Self, QuicError> {
// Parse address
let socket_addr = addr
.to_socket_addrs()
.map_err(|e| QuicError::InvalidConfig(e.to_string()))?
.next()
.ok_or_else(|| QuicError::InvalidConfig("Invalid address".to_string()))?;
// Create client config with TLS (skip verification for demo purposes)
let mut crypto = quinn::rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(SkipServerVerification::new())
.with_no_client_auth();
// Enable ALPN for QUIC
crypto.alpn_protocols = vec![b"h3".to_vec()];
let client_config = ClientConfig::new(Arc::new(
quinn::crypto::rustls::QuicClientConfig::try_from(crypto)
.map_err(|e| QuicError::TlsError(format!("{:?}", e)))?,
));
// Create endpoint
let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())
.map_err(|e| QuicError::ConnectionFailed(e.to_string()))?;
endpoint.set_default_client_config(client_config);
// Connect to server
let connection = endpoint
.connect(socket_addr, "localhost")
.map_err(|e| QuicError::ConnectionFailed(e.to_string()))?
.await?;
Ok(Self {
connection,
bytes_sent: Arc::new(AtomicU64::new(0)),
bytes_received: Arc::new(AtomicU64::new(0)),
})
}
/// Open a bidirectional stream
pub async fn open_bi_stream(&self) -> Result<QuicStream, QuicError> {
let (send, recv) = self.connection.open_bi().await?;
Ok(QuicStream::new(
send,
recv,
self.bytes_sent.clone(),
self.bytes_received.clone(),
))
}
/// Open a bidirectional stream with priority
pub async fn open_bi_stream_with_priority(
&self,
priority: StreamPriority,
) -> Result<QuicStream, QuicError> {
let (send, recv) = self.connection.open_bi().await?;
let mut stream = QuicStream::new(
send,
recv,
self.bytes_sent.clone(),
self.bytes_received.clone(),
);
stream.set_priority(priority);
Ok(stream)
}
/// Open a unidirectional stream (send-only)
pub async fn open_uni_stream(&self) -> Result<QuicSendStream, QuicError> {
let send = self.connection.open_uni().await?;
Ok(QuicSendStream::new(send, self.bytes_sent.clone()))
}
/// Accept an incoming bidirectional stream
pub async fn accept_bi_stream(&self) -> Result<QuicStream, QuicError> {
let (send, recv) = self
.connection
.accept_bi()
.await
.map_err(|e| QuicError::ConnectionClosed(e.to_string()))?;
Ok(QuicStream::new(
send,
recv,
self.bytes_sent.clone(),
self.bytes_received.clone(),
))
}
/// Get connection statistics
pub fn stats(&self) -> ConnectionStats {
let stats = self.connection.stats();
ConnectionStats {
active_bi_streams: 0, // Not available in quinn stats
active_uni_streams: 0,
bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
bytes_received: self.bytes_received.load(Ordering::Relaxed),
rtt_ms: stats.path.rtt.as_millis() as f64,
}
}
/// Close the connection
pub fn close(&self, error_code: u64, reason: &[u8]) {
self.connection.close(VarInt::from_u64(error_code).unwrap(), reason);
}
/// Get the remote address
pub fn remote_address(&self) -> SocketAddr {
self.connection.remote_address()
}
}
/// Bidirectional QUIC stream
pub struct QuicStream {
send: SendStream,
recv: RecvStream,
priority: StreamPriority,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
}
impl QuicStream {
fn new(
send: SendStream,
recv: RecvStream,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
) -> Self {
Self {
send,
recv,
priority: StreamPriority::default(),
bytes_sent,
bytes_received,
}
}
/// Send data on the stream
pub async fn send(&mut self, data: &[u8]) -> Result<usize, QuicError> {
self.send.write_all(data).await?;
self.bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed);
Ok(data.len())
}
/// Receive data from the stream
pub async fn recv(&mut self, buf: &mut [u8]) -> Result<usize, QuicError> {
let n = self.recv.read(buf).await?.unwrap_or(0);
self.bytes_received.fetch_add(n as u64, Ordering::Relaxed);
Ok(n)
}
/// Finish sending on this stream
pub async fn finish(&mut self) -> Result<(), QuicError> {
self.send.finish()
.map_err(|e| QuicError::StreamError(format!("Failed to finish stream: {:?}", e)))?;
Ok(())
}
/// Set stream priority
pub fn set_priority(&mut self, priority: StreamPriority) {
self.priority = priority;
// Note: quinn doesn't directly expose priority setting
// This would typically be handled at the application level
}
/// Get current priority
pub fn priority(&self) -> StreamPriority {
self.priority
}
}
/// Unidirectional send-only stream
pub struct QuicSendStream {
send: SendStream,
bytes_sent: Arc<AtomicU64>,
}
impl QuicSendStream {
fn new(send: SendStream, bytes_sent: Arc<AtomicU64>) -> Self {
Self { send, bytes_sent }
}
/// Send data on the stream
pub async fn send(&mut self, data: &[u8]) -> Result<usize, QuicError> {
self.send.write_all(data).await?;
self.bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed);
Ok(data.len())
}
/// Finish sending on this stream
pub async fn finish(&mut self) -> Result<(), QuicError> {
self.send.finish()
.map_err(|e| QuicError::StreamError(format!("Failed to finish stream: {:?}", e)))?;
Ok(())
}
}
/// Skip server certificate verification (for testing only!)
#[derive(Debug)]
struct SkipServerVerification(Arc<quinn::rustls::crypto::CryptoProvider>);
impl SkipServerVerification {
fn new() -> Arc<Self> {
Arc::new(Self(Arc::new(quinn::rustls::crypto::ring::default_provider())))
}
}
impl quinn::rustls::client::danger::ServerCertVerifier for SkipServerVerification {
fn verify_server_cert(
&self,
_end_entity: &quinn::rustls::pki_types::CertificateDer<'_>,
_intermediates: &[quinn::rustls::pki_types::CertificateDer<'_>],
_server_name: &quinn::rustls::pki_types::ServerName<'_>,
_ocsp: &[u8],
_now: quinn::rustls::pki_types::UnixTime,
) -> Result<quinn::rustls::client::danger::ServerCertVerified, quinn::rustls::Error> {
Ok(quinn::rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &quinn::rustls::pki_types::CertificateDer<'_>,
dss: &quinn::rustls::DigitallySignedStruct,
) -> Result<quinn::rustls::client::danger::HandshakeSignatureValid, quinn::rustls::Error> {
quinn::rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &quinn::rustls::pki_types::CertificateDer<'_>,
dss: &quinn::rustls::DigitallySignedStruct,
) -> Result<quinn::rustls::client::danger::HandshakeSignatureValid, quinn::rustls::Error> {
quinn::rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<quinn::rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_stats_tracking() {
let bytes_sent = Arc::new(AtomicU64::new(100));
let bytes_received = Arc::new(AtomicU64::new(200));
assert_eq!(bytes_sent.load(Ordering::Relaxed), 100);
assert_eq!(bytes_received.load(Ordering::Relaxed), 200);
bytes_sent.fetch_add(50, Ordering::Relaxed);
assert_eq!(bytes_sent.load(Ordering::Relaxed), 150);
}
#[test]
fn test_priority_values() {
assert_eq!(StreamPriority::default(), StreamPriority::Normal);
assert!(StreamPriority::Critical < StreamPriority::High);
}
}
+307
View File
@@ -0,0 +1,307 @@
//! WASM implementation using WebTransport
use crate::{ConnectionStats, QuicError, StreamPriority};
use js_sys::{Uint8Array, Promise};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
WebTransport, WebTransportBidirectionalStream, WebTransportSendStream,
WebTransportReceiveStream,
};
/// QUIC connection wrapper for WASM targets using WebTransport
pub struct QuicConnection {
transport: WebTransport,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
}
impl QuicConnection {
/// Connect to a WebTransport server
///
/// # Arguments
/// * `url` - Server URL (must use HTTPS, e.g., "https://server.example.com")
///
/// # Examples
/// ```no_run
/// # #[cfg(target_arch = "wasm32")]
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// use midstreamer_quic::QuicConnection;
///
/// let connection = QuicConnection::connect("https://localhost:4433").await?;
/// # Ok(())
/// # }
/// ```
pub async fn connect(url: &str) -> Result<Self, QuicError> {
// Create WebTransport
let transport = WebTransport::new(url)
.map_err(|e| QuicError::ConnectionFailed(format!("{:?}", e)))?;
// Wait for connection to be ready
let ready_promise = transport.ready();
JsFuture::from(ready_promise)
.await
.map_err(|e| QuicError::ConnectionFailed(format!("{:?}", e)))?;
Ok(Self {
transport,
bytes_sent: Arc::new(AtomicU64::new(0)),
bytes_received: Arc::new(AtomicU64::new(0)),
})
}
/// Open a bidirectional stream
pub async fn open_bi_stream(&self) -> Result<QuicStream, QuicError> {
let create_stream = self.transport.create_bidirectional_stream();
let stream_js = JsFuture::from(create_stream)
.await
.map_err(|e| QuicError::StreamError(format!("{:?}", e)))?;
let bi_stream = WebTransportBidirectionalStream::from(stream_js);
Ok(QuicStream::new(
bi_stream,
self.bytes_sent.clone(),
self.bytes_received.clone(),
))
}
/// Open a bidirectional stream with priority
pub async fn open_bi_stream_with_priority(
&self,
priority: StreamPriority,
) -> Result<QuicStream, QuicError> {
let mut stream = self.open_bi_stream().await?;
stream.set_priority(priority);
Ok(stream)
}
/// Open a unidirectional stream (send-only)
pub async fn open_uni_stream(&self) -> Result<QuicSendStream, QuicError> {
let create_stream = self.transport.create_unidirectional_stream();
let stream_js = JsFuture::from(create_stream)
.await
.map_err(|e| QuicError::StreamError(format!("{:?}", e)))?;
let send_stream = WebTransportSendStream::from(stream_js);
Ok(QuicSendStream::new(send_stream, self.bytes_sent.clone()))
}
/// Accept an incoming bidirectional stream
pub async fn accept_bi_stream(&self) -> Result<QuicStream, QuicError> {
// WebTransport uses an async iterator for incoming streams
// This is a simplified implementation
Err(QuicError::StreamError(
"accept_bi_stream not yet implemented for WASM".to_string(),
))
}
/// Get connection statistics
pub fn stats(&self) -> ConnectionStats {
ConnectionStats {
active_bi_streams: 0, // Not easily available in WebTransport
active_uni_streams: 0,
bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
bytes_received: self.bytes_received.load(Ordering::Relaxed),
rtt_ms: 0.0, // Not available in WebTransport API
}
}
/// Close the connection
pub fn close(&self, _error_code: u64, _reason: &[u8]) {
self.transport.close();
}
}
/// Bidirectional QUIC stream for WASM
pub struct QuicStream {
bi_stream: WebTransportBidirectionalStream,
priority: StreamPriority,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
}
impl QuicStream {
fn new(
bi_stream: WebTransportBidirectionalStream,
bytes_sent: Arc<AtomicU64>,
bytes_received: Arc<AtomicU64>,
) -> Self {
Self {
bi_stream,
priority: StreamPriority::default(),
bytes_sent,
bytes_received,
}
}
/// Send data on the stream
pub async fn send(&mut self, data: &[u8]) -> Result<usize, QuicError> {
let writable = self.bi_stream.writable();
let writer = writable.get_writer()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
// Convert data to Uint8Array
let uint8_array = Uint8Array::new_with_length(data.len() as u32);
uint8_array.copy_from(data);
// Write to stream
let write_promise = writer.write_with_chunk(&uint8_array)
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
JsFuture::from(write_promise)
.await
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
// Release writer
writer.release_lock();
self.bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed);
Ok(data.len())
}
/// Receive data from the stream
pub async fn recv(&mut self, buf: &mut [u8]) -> Result<usize, QuicError> {
let readable = self.bi_stream.readable();
let reader = readable.get_reader()
.map_err(|e| QuicError::RecvError(format!("{:?}", e)))?;
// Read from stream
let read_promise = reader.read();
let read_result = JsFuture::from(read_promise)
.await
.map_err(|e| QuicError::RecvError(format!("{:?}", e)))?;
// Extract data from result
let done = js_sys::Reflect::get(&read_result, &JsValue::from_str("done"))
.map_err(|e| QuicError::RecvError(format!("{:?}", e)))?
.as_bool()
.unwrap_or(false);
if done {
reader.release_lock();
return Ok(0);
}
let value = js_sys::Reflect::get(&read_result, &JsValue::from_str("value"))
.map_err(|e| QuicError::RecvError(format!("{:?}", e)))?;
let uint8_array = Uint8Array::from(value);
let len = uint8_array.length() as usize;
let to_copy = len.min(buf.len());
uint8_array.copy_to(&mut buf[..to_copy]);
reader.release_lock();
self.bytes_received.fetch_add(to_copy as u64, Ordering::Relaxed);
Ok(to_copy)
}
/// Finish sending on this stream
pub async fn finish(&mut self) -> Result<(), QuicError> {
let writable = self.bi_stream.writable();
let writer = writable.get_writer()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
let close_promise = writer.close()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
JsFuture::from(close_promise)
.await
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
Ok(())
}
/// Set stream priority
pub fn set_priority(&mut self, priority: StreamPriority) {
self.priority = priority;
// WebTransport doesn't expose priority directly
}
/// Get current priority
pub fn priority(&self) -> StreamPriority {
self.priority
}
}
/// Unidirectional send-only stream for WASM
pub struct QuicSendStream {
send_stream: WebTransportSendStream,
bytes_sent: Arc<AtomicU64>,
}
impl QuicSendStream {
fn new(send_stream: WebTransportSendStream, bytes_sent: Arc<AtomicU64>) -> Self {
Self {
send_stream,
bytes_sent,
}
}
/// Send data on the stream
pub async fn send(&mut self, data: &[u8]) -> Result<usize, QuicError> {
let writer = self.send_stream.get_writer()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
// Convert data to Uint8Array
let uint8_array = Uint8Array::new_with_length(data.len() as u32);
uint8_array.copy_from(data);
// Write to stream
let write_promise = writer.write_with_chunk(&uint8_array)
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
JsFuture::from(write_promise)
.await
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
writer.release_lock();
self.bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed);
Ok(data.len())
}
/// Finish sending on this stream
pub async fn finish(&mut self) -> Result<(), QuicError> {
let writer = self.send_stream.get_writer()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
let close_promise = writer.close()
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
JsFuture::from(close_promise)
.await
.map_err(|e| QuicError::SendError(format!("{:?}", e)))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_priority_in_wasm_stream() {
// Can't easily test full WASM functionality in unit tests
// These would require browser environment
assert_eq!(StreamPriority::default(), StreamPriority::Normal);
}
#[test]
fn test_stats_tracking() {
let bytes_sent = Arc::new(AtomicU64::new(0));
let bytes_received = Arc::new(AtomicU64::new(0));
bytes_sent.fetch_add(100, Ordering::Relaxed);
bytes_received.fetch_add(200, Ordering::Relaxed);
assert_eq!(bytes_sent.load(Ordering::Relaxed), 100);
assert_eq!(bytes_received.load(Ordering::Relaxed), 200);
}
}
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "midstreamer-strange-loop"
version = "0.1.0"
edition = "2021"
description = "Self-referential systems and meta-learning"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["meta-learning", "self-reference", "strange-loop", "cognition", "midstream"]
categories = ["algorithms", "science"]
[dependencies]
midstreamer-temporal-compare = { path = "../temporal-compare" }
midstreamer-attractor = { path = "../temporal-attractor-studio" }
midstreamer-neural-solver = { path = "../temporal-neural-solver" }
midstreamer-scheduler = { path = "../nanosecond-scheduler" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
dashmap = "6.1"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "meta_bench"
harness = false
@@ -0,0 +1,321 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
use midstreamer_strange_loop::*;
/// Benchmark pattern extraction performance with varying data sizes
fn pattern_extraction_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("pattern_extraction");
for size in [10, 50, 100, 500, 1000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
// Create realistic data with some repeated patterns
let data: Vec<String> = (0..size)
.map(|i| format!("pattern_{}", i % 20)) // Create 20 unique patterns with repetition
.collect();
b.iter(|| {
let mut strange_loop = StrangeLoop::default();
let result = strange_loop.learn_at_level(black_box(MetaLevel::base()), black_box(&data));
result.unwrap()
});
});
}
group.finish();
}
/// Benchmark recursive optimization with varying depths
fn recursive_optimization_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("recursive_optimization");
// Test different recursion depths (1, 5, 10, 20)
for depth in [1, 2, 3].iter() {
group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, &depth| {
let config = StrangeLoopConfig {
max_meta_depth: depth,
enable_self_modification: false,
max_modifications_per_cycle: 5,
safety_check_enabled: true,
};
// Generate sample data
let data: Vec<String> = (0..100)
.map(|i| format!("level_0_pattern_{}", i % 10))
.collect();
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
// Learn at base level, which will trigger recursive meta-learning
let result = strange_loop.learn_at_level(black_box(MetaLevel::base()), black_box(&data));
result.unwrap()
});
});
}
group.finish();
}
/// Benchmark self-modification overhead
fn self_modification_overhead_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("self_modification_overhead");
for num_modifications in [1, 5, 10, 20].iter() {
group.bench_with_input(BenchmarkId::from_parameter(num_modifications), num_modifications, |b, &num_modifications| {
let config = StrangeLoopConfig {
max_meta_depth: 3,
enable_self_modification: true,
max_modifications_per_cycle: 100,
safety_check_enabled: true,
};
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
for i in 0..num_modifications {
let rule = ModificationRule::new(
format!("rule_{}", i),
format!("trigger_{}", i),
format!("action_{}", i),
);
let _ = strange_loop.apply_modification(black_box(rule));
}
strange_loop.get_summary()
});
});
}
group.finish();
}
/// Benchmark meta-learning convergence time with varying complexity
fn meta_learning_convergence_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("meta_learning_convergence");
// Test convergence with different numbers of learning iterations
for iterations in [1, 5, 10, 20, 50].iter() {
group.bench_with_input(BenchmarkId::from_parameter(iterations), iterations, |b, &iterations| {
let config = StrangeLoopConfig {
max_meta_depth: 2,
enable_self_modification: false,
max_modifications_per_cycle: 5,
safety_check_enabled: true,
};
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
for i in 0..iterations {
let data: Vec<String> = (0..50)
.map(|j| format!("iteration_{}_pattern_{}", i, j % 10))
.collect();
let _ = strange_loop.learn_at_level(black_box(MetaLevel::base()), black_box(&data));
}
strange_loop.get_summary()
});
});
}
group.finish();
}
/// Benchmark memory usage during recursion
fn memory_usage_recursion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_usage_recursion");
// Test memory usage with different meta-depths and data sizes
for (depth, data_size) in [(1, 100), (2, 100), (3, 100), (2, 500), (2, 1000)].iter() {
let label = format!("depth_{}_size_{}", depth, data_size);
group.bench_with_input(BenchmarkId::new("recursive_learning", &label), &(depth, data_size), |b, &(depth, data_size)| {
let config = StrangeLoopConfig {
max_meta_depth: *depth,
enable_self_modification: false,
max_modifications_per_cycle: 5,
safety_check_enabled: true,
};
let data: Vec<String> = (0..*data_size)
.map(|i| format!("pattern_{}", i % 20))
.collect();
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
let _ = strange_loop.learn_at_level(black_box(MetaLevel::base()), black_box(&data));
// Get all knowledge to measure memory usage
let all_knowledge = strange_loop.get_all_knowledge();
black_box(all_knowledge)
});
});
}
group.finish();
}
/// Benchmark strategy adaptation speed
fn strategy_adaptation_speed_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("strategy_adaptation_speed");
// Test how quickly the system adapts to new patterns
for pattern_change_frequency in [5, 10, 20, 50].iter() {
group.bench_with_input(BenchmarkId::from_parameter(pattern_change_frequency), pattern_change_frequency, |b, &pattern_change_frequency| {
let config = StrangeLoopConfig {
max_meta_depth: 2,
enable_self_modification: false,
max_modifications_per_cycle: 5,
safety_check_enabled: true,
};
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
// Simulate changing patterns
for batch in 0..10 {
let pattern_base = batch / pattern_change_frequency;
let data: Vec<String> = (0..100)
.map(|i| format!("strategy_{}_pattern_{}", pattern_base, i % 10))
.collect();
let _ = strange_loop.learn_at_level(black_box(MetaLevel::base()), black_box(&data));
}
strange_loop.get_summary()
});
});
}
group.finish();
}
/// Benchmark safety constraint checking
fn safety_constraint_checking_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("safety_constraint_checking");
for num_constraints in [1, 5, 10, 20].iter() {
group.bench_with_input(BenchmarkId::from_parameter(num_constraints), num_constraints, |b, &num_constraints| {
let mut config = StrangeLoopConfig {
max_meta_depth: 2,
enable_self_modification: true,
max_modifications_per_cycle: 100,
safety_check_enabled: true,
};
b.iter(|| {
let mut strange_loop = StrangeLoop::new(black_box(config.clone()));
// Add multiple safety constraints
for i in 0..num_constraints {
let constraint = SafetyConstraint::new(
format!("constraint_{}", i),
format!("G(safe_{})", i),
);
strange_loop.add_safety_constraint(black_box(constraint));
}
// Try to apply a modification (which triggers safety checks)
let rule = ModificationRule::new("test_rule", "test_trigger", "test_action");
let _ = strange_loop.apply_modification(black_box(rule));
strange_loop.get_summary()
});
});
}
group.finish();
}
/// Benchmark knowledge retrieval performance
fn knowledge_retrieval_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("knowledge_retrieval");
for num_patterns in [10, 50, 100, 500, 1000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(num_patterns), num_patterns, |b, &num_patterns| {
// Setup: create strange loop with learned knowledge
let mut strange_loop = StrangeLoop::default();
let data: Vec<String> = (0..num_patterns)
.map(|i| format!("pattern_{}", i % 20))
.collect();
let _ = strange_loop.learn_at_level(MetaLevel::base(), &data);
b.iter(|| {
// Benchmark retrieval
let knowledge = strange_loop.get_knowledge_at_level(black_box(MetaLevel::base()));
black_box(knowledge)
});
});
}
group.finish();
}
/// Benchmark attractor analysis performance
fn attractor_analysis_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("attractor_analysis");
for trajectory_length in [10, 50, 100, 200].iter() {
group.bench_with_input(BenchmarkId::from_parameter(trajectory_length), trajectory_length, |b, &trajectory_length| {
let trajectory_data: Vec<Vec<f64>> = (0..trajectory_length)
.map(|i| {
let t = i as f64 * 0.1;
vec![t.sin(), t.cos(), (t * 2.0).sin()] // 3D trajectory
})
.collect();
b.iter(|| {
let mut strange_loop = StrangeLoop::default();
let result = strange_loop.analyze_behavior(black_box(trajectory_data.clone()));
black_box(result)
});
});
}
group.finish();
}
/// Benchmark reset performance
fn reset_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("reset_performance");
for knowledge_size in [100, 500, 1000].iter() {
group.bench_with_input(BenchmarkId::from_parameter(knowledge_size), knowledge_size, |b, &knowledge_size| {
b.iter_batched(
|| {
// Setup: create strange loop with lots of knowledge
let mut strange_loop = StrangeLoop::default();
let data: Vec<String> = (0..knowledge_size)
.map(|i| format!("pattern_{}", i % 20))
.collect();
let _ = strange_loop.learn_at_level(MetaLevel::base(), &data);
strange_loop
},
|mut strange_loop| {
// Benchmark reset
strange_loop.reset();
black_box(strange_loop)
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
criterion_group!(
benches,
pattern_extraction_benchmark,
recursive_optimization_benchmark,
self_modification_overhead_benchmark,
meta_learning_convergence_benchmark,
memory_usage_recursion_benchmark,
strategy_adaptation_speed_benchmark,
safety_constraint_checking_benchmark,
knowledge_retrieval_benchmark,
attractor_analysis_benchmark,
reset_benchmark,
);
criterion_main!(benches);
+496
View File
@@ -0,0 +1,496 @@
//! # Strange-Loop
//!
//! Self-referential systems and meta-learning inspired by Douglas Hofstadter.
//!
//! ## Features
//! - Multi-level meta-learning
//! - Self-modification with safety constraints
//! - Recursive cognition
//! - Tangled hierarchies
//! - Meta-knowledge extraction
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use dashmap::DashMap;
use std::sync::Arc;
use midstreamer_temporal_compare::TemporalComparator;
use midstreamer_attractor::{AttractorAnalyzer, PhasePoint};
use midstreamer_neural_solver::TemporalNeuralSolver;
/// Strange loop errors
#[derive(Debug, Error)]
pub enum StrangeLoopError {
#[error("Max meta-depth exceeded: {0}")]
MaxDepthExceeded(usize),
#[error("Safety constraint violated: {0}")]
SafetyViolation(String),
#[error("Invalid modification: {0}")]
InvalidModification(String),
#[error("Meta-learning failed: {0}")]
MetaLearningFailed(String),
}
/// Meta-level in the learning hierarchy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MetaLevel(pub usize);
impl MetaLevel {
pub fn base() -> Self {
MetaLevel(0)
}
pub fn next(&self) -> Self {
MetaLevel(self.0 + 1)
}
pub fn level(&self) -> usize {
self.0
}
}
/// Meta-knowledge extracted from lower levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaKnowledge {
pub level: MetaLevel,
pub pattern: String,
pub confidence: f64,
pub applications: Vec<String>,
pub learned_at: u64,
}
impl MetaKnowledge {
pub fn new(level: MetaLevel, pattern: String, confidence: f64) -> Self {
Self {
level,
pattern,
confidence,
applications: Vec::new(),
learned_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
}
}
}
/// Safety constraint for self-modification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SafetyConstraint {
pub name: String,
pub formula: String, // Simplified temporal formula
pub enforced: bool,
}
impl SafetyConstraint {
pub fn new(name: impl Into<String>, formula: impl Into<String>) -> Self {
Self {
name: name.into(),
formula: formula.into(),
enforced: true,
}
}
pub fn always_safe() -> Self {
Self::new("always_safe", "G(safe)")
}
pub fn eventually_terminates() -> Self {
Self::new("eventually_terminates", "F(done)")
}
}
/// Modification rule for self-improvement
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModificationRule {
pub name: String,
pub trigger: String,
pub action: String,
pub safety_check: bool,
}
impl ModificationRule {
pub fn new(
name: impl Into<String>,
trigger: impl Into<String>,
action: impl Into<String>,
) -> Self {
Self {
name: name.into(),
trigger: trigger.into(),
action: action.into(),
safety_check: true,
}
}
}
/// Statistics about meta-learning performance
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaLearningSummary {
pub total_levels: usize,
pub total_knowledge: usize,
pub total_modifications: usize,
pub safety_violations: usize,
pub learning_iterations: u64,
}
/// Configuration for strange loop
#[derive(Debug, Clone)]
pub struct StrangeLoopConfig {
pub max_meta_depth: usize,
pub enable_self_modification: bool,
pub max_modifications_per_cycle: usize,
pub safety_check_enabled: bool,
}
impl Default for StrangeLoopConfig {
fn default() -> Self {
Self {
max_meta_depth: 3,
enable_self_modification: false, // Disabled by default for safety
max_modifications_per_cycle: 5,
safety_check_enabled: true,
}
}
}
/// The main strange loop structure
pub struct StrangeLoop {
config: StrangeLoopConfig,
meta_knowledge: Arc<DashMap<MetaLevel, Vec<MetaKnowledge>>>,
safety_constraints: Vec<SafetyConstraint>,
modification_rules: Vec<ModificationRule>,
learning_iterations: Arc<DashMap<MetaLevel, u64>>,
modification_count: usize,
safety_violations: usize,
// Integrated components (reserved for future use)
#[allow(dead_code)]
temporal_comparator: TemporalComparator<String>,
attractor_analyzer: AttractorAnalyzer,
#[allow(dead_code)]
temporal_solver: TemporalNeuralSolver,
}
impl StrangeLoop {
/// Create a new strange loop
pub fn new(config: StrangeLoopConfig) -> Self {
Self {
config,
meta_knowledge: Arc::new(DashMap::new()),
safety_constraints: vec![
SafetyConstraint::always_safe(),
SafetyConstraint::eventually_terminates(),
],
modification_rules: Vec::new(),
learning_iterations: Arc::new(DashMap::new()),
modification_count: 0,
safety_violations: 0,
temporal_comparator: TemporalComparator::new(1000, 10000),
attractor_analyzer: AttractorAnalyzer::new(3, 10000),
temporal_solver: TemporalNeuralSolver::default(),
}
}
/// Learn at a specific meta-level
pub fn learn_at_level(
&mut self,
level: MetaLevel,
data: &[String],
) -> Result<Vec<MetaKnowledge>, StrangeLoopError> {
if level.level() > self.config.max_meta_depth {
return Err(StrangeLoopError::MaxDepthExceeded(level.level()));
}
// Increment learning iterations
self.learning_iterations
.entry(level)
.and_modify(|v| *v += 1)
.or_insert(1);
// Extract patterns from data
let patterns = self.extract_patterns(level, data)?;
// Store meta-knowledge
self.meta_knowledge
.entry(level)
.or_insert_with(Vec::new)
.extend(patterns.clone());
// If not at max depth, meta-learn from this level
if level.level() < self.config.max_meta_depth {
self.meta_learn_from_level(level)?;
}
Ok(patterns)
}
/// Meta-learn from a lower level
fn meta_learn_from_level(&mut self, level: MetaLevel) -> Result<(), StrangeLoopError> {
// Get knowledge from this level
let knowledge = if let Some(k) = self.meta_knowledge.get(&level) {
k.clone()
} else {
return Ok(()); // No knowledge to learn from
};
// Extract meta-patterns
let meta_patterns: Vec<String> = knowledge
.iter()
.map(|k| k.pattern.clone())
.collect();
// Learn at next level
let next_level = level.next();
let _meta_knowledge = self.learn_at_level(next_level, &meta_patterns)?;
Ok(())
}
/// Extract patterns from data
fn extract_patterns(
&self,
level: MetaLevel,
data: &[String],
) -> Result<Vec<MetaKnowledge>, StrangeLoopError> {
let mut patterns = Vec::new();
// Find recurring patterns using temporal comparison
for i in 0..data.len() {
for j in i+1..data.len() {
if data[i] == data[j] {
// Found a repeating pattern
let pattern = MetaKnowledge::new(
level,
data[i].clone(),
0.8, // Confidence
);
patterns.push(pattern);
}
}
}
// Limit number of patterns
patterns.truncate(100);
Ok(patterns)
}
/// Apply self-modification with safety checks
pub fn apply_modification(
&mut self,
rule: ModificationRule,
) -> Result<(), StrangeLoopError> {
if !self.config.enable_self_modification {
return Err(StrangeLoopError::InvalidModification(
"Self-modification is disabled".to_string()
));
}
if self.modification_count >= self.config.max_modifications_per_cycle {
return Err(StrangeLoopError::InvalidModification(
"Max modifications per cycle reached".to_string()
));
}
// Safety check
if rule.safety_check && self.config.safety_check_enabled {
self.check_safety_constraints()?;
}
// Apply modification
self.modification_rules.push(rule);
self.modification_count += 1;
Ok(())
}
/// Check all safety constraints
fn check_safety_constraints(&mut self) -> Result<(), StrangeLoopError> {
for constraint in &self.safety_constraints {
if constraint.enforced {
// Simplified safety check
// In production, this would use the temporal solver
if constraint.formula.contains("safe") {
// Always pass for now
continue;
}
}
}
Ok(())
}
/// Add a safety constraint
pub fn add_safety_constraint(&mut self, constraint: SafetyConstraint) {
self.safety_constraints.push(constraint);
}
/// Get knowledge at a specific level
pub fn get_knowledge_at_level(&self, level: MetaLevel) -> Vec<MetaKnowledge> {
self.meta_knowledge
.get(&level)
.map(|k| k.clone())
.unwrap_or_default()
}
/// Get all meta-knowledge
pub fn get_all_knowledge(&self) -> HashMap<MetaLevel, Vec<MetaKnowledge>> {
let mut result = HashMap::new();
for entry in self.meta_knowledge.iter() {
result.insert(*entry.key(), entry.value().clone());
}
result
}
/// Get summary statistics
pub fn get_summary(&self) -> MetaLearningSummary {
let total_knowledge: usize = self.meta_knowledge
.iter()
.map(|entry| entry.value().len())
.sum();
MetaLearningSummary {
total_levels: self.meta_knowledge.len(),
total_knowledge,
total_modifications: self.modification_count,
safety_violations: self.safety_violations,
learning_iterations: self.learning_iterations
.iter()
.map(|entry| *entry.value())
.sum(),
}
}
/// Reset the strange loop
pub fn reset(&mut self) {
self.meta_knowledge.clear();
self.learning_iterations.clear();
self.modification_rules.clear();
self.modification_count = 0;
self.safety_violations = 0;
}
/// Analyze behavioral dynamics using attractor analysis
pub fn analyze_behavior(&mut self, trajectory_data: Vec<Vec<f64>>) -> Result<String, StrangeLoopError> {
for (i, point_data) in trajectory_data.iter().enumerate() {
let point = PhasePoint::new(point_data.clone(), i as u64);
self.attractor_analyzer.add_point(point)
.map_err(|e| StrangeLoopError::MetaLearningFailed(e.to_string()))?;
}
let analysis = self.attractor_analyzer.analyze()
.map_err(|e| StrangeLoopError::MetaLearningFailed(e.to_string()))?;
Ok(format!("{:?}", analysis.attractor_type))
}
}
impl Default for StrangeLoop {
fn default() -> Self {
Self::new(StrangeLoopConfig::default())
}
}
/// Meta-learner trait for types that can engage in meta-learning
pub trait MetaLearner {
fn learn(&mut self, data: &[String]) -> Result<Vec<MetaKnowledge>, StrangeLoopError>;
fn meta_level(&self) -> MetaLevel;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meta_level() {
let base = MetaLevel::base();
assert_eq!(base.level(), 0);
let next = base.next();
assert_eq!(next.level(), 1);
}
#[test]
fn test_strange_loop_creation() {
let config = StrangeLoopConfig::default();
let strange_loop = StrangeLoop::new(config);
assert_eq!(strange_loop.modification_count, 0);
assert_eq!(strange_loop.safety_violations, 0);
}
#[test]
fn test_learning_at_level() {
let mut strange_loop = StrangeLoop::default();
let data = vec![
"pattern1".to_string(),
"pattern2".to_string(),
"pattern1".to_string(),
];
let result = strange_loop.learn_at_level(MetaLevel::base(), &data);
assert!(result.is_ok());
let knowledge = strange_loop.get_knowledge_at_level(MetaLevel::base());
assert!(!knowledge.is_empty());
}
#[test]
fn test_max_depth_exceeded() {
let mut strange_loop = StrangeLoop::default();
let data = vec!["test".to_string()];
let deep_level = MetaLevel(10); // Exceeds default max of 3
let result = strange_loop.learn_at_level(deep_level, &data);
assert!(result.is_err());
}
#[test]
fn test_safety_constraint() {
let constraint = SafetyConstraint::always_safe();
assert_eq!(constraint.name, "always_safe");
assert!(constraint.enforced);
}
#[test]
fn test_modification_disabled() {
let mut strange_loop = StrangeLoop::default();
let rule = ModificationRule::new("test_rule", "trigger", "action");
let result = strange_loop.apply_modification(rule);
assert!(result.is_err()); // Should fail because self-modification is disabled
}
#[test]
fn test_summary() {
let mut strange_loop = StrangeLoop::default();
let data = vec!["pattern1".to_string(), "pattern2".to_string()];
let _ = strange_loop.learn_at_level(MetaLevel::base(), &data);
let summary = strange_loop.get_summary();
assert!(summary.total_knowledge > 0);
assert_eq!(summary.safety_violations, 0);
}
#[test]
fn test_reset() {
let mut strange_loop = StrangeLoop::default();
let data = vec!["pattern1".to_string()];
let _ = strange_loop.learn_at_level(MetaLevel::base(), &data);
strange_loop.reset();
let summary = strange_loop.get_summary();
assert_eq!(summary.total_knowledge, 0);
assert_eq!(summary.total_modifications, 0);
}
}
@@ -0,0 +1,19 @@
[package]
name = "midstreamer-attractor"
version = "0.1.0"
edition = "2021"
description = "Dynamical systems and strange attractors analysis"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["attractors", "dynamical-systems", "chaos", "analysis", "midstream"]
categories = ["algorithms", "science", "visualization"]
[dependencies]
midstreamer-temporal-compare = { path = "../temporal-compare" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
nalgebra = "0.33"
ndarray = "0.16"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
@@ -0,0 +1,482 @@
//! # Temporal-Attractor-Studio
//!
//! Dynamical systems and strange attractors analysis.
//!
//! ## Features
//! - Attractor classification (point, limit cycle, strange)
//! - Lyapunov exponent calculation
//! - Phase space analysis
//! - Trajectory visualization data
//! - Stability detection
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use thiserror::Error;
/// Attractor analysis errors
#[derive(Debug, Error)]
pub enum AttractorError {
#[error("Insufficient data: need at least {0} points")]
InsufficientData(usize),
#[error("Invalid dimension: {0}")]
InvalidDimension(usize),
#[error("Computation error: {0}")]
ComputationError(String),
}
/// Types of attractors
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttractorType {
/// Point attractor (stable equilibrium)
PointAttractor,
/// Limit cycle (periodic behavior)
LimitCycle,
/// Strange attractor (chaotic behavior)
StrangeAttractor,
/// No clear attractor detected
Unknown,
}
/// A point in phase space
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhasePoint {
pub coordinates: Vec<f64>,
pub timestamp: u64,
}
impl PhasePoint {
pub fn new(coordinates: Vec<f64>, timestamp: u64) -> Self {
Self { coordinates, timestamp }
}
pub fn dimension(&self) -> usize {
self.coordinates.len()
}
}
/// A trajectory in phase space
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trajectory {
pub points: VecDeque<PhasePoint>,
pub max_length: usize,
}
impl Trajectory {
pub fn new(max_length: usize) -> Self {
Self {
points: VecDeque::new(),
max_length,
}
}
pub fn push(&mut self, point: PhasePoint) {
if self.points.len() >= self.max_length {
self.points.pop_front();
}
self.points.push_back(point);
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn clear(&mut self) {
self.points.clear();
}
}
/// Information about a detected attractor
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttractorInfo {
pub attractor_type: AttractorType,
pub dimension: usize,
pub lyapunov_exponents: Vec<f64>,
pub is_stable: bool,
pub confidence: f64,
}
impl AttractorInfo {
pub fn is_chaotic(&self) -> bool {
matches!(self.attractor_type, AttractorType::StrangeAttractor)
}
pub fn max_lyapunov_exponent(&self) -> Option<f64> {
self.lyapunov_exponents.iter().copied().max_by(|a, b| {
a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
})
}
}
/// Behavior summary statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehaviorSummary {
pub total_points: usize,
pub dimension: usize,
pub attractor_info: Option<AttractorInfo>,
pub mean_velocity: f64,
pub trajectory_length: f64,
}
/// Attractor analyzer
pub struct AttractorAnalyzer {
embedding_dimension: usize,
min_points_for_analysis: usize,
trajectory: Trajectory,
}
impl AttractorAnalyzer {
/// Create a new attractor analyzer
pub fn new(embedding_dimension: usize, max_trajectory_length: usize) -> Self {
Self {
embedding_dimension,
min_points_for_analysis: 100,
trajectory: Trajectory::new(max_trajectory_length),
}
}
/// Add a point to the trajectory
pub fn add_point(&mut self, point: PhasePoint) -> Result<(), AttractorError> {
if point.dimension() != self.embedding_dimension {
return Err(AttractorError::InvalidDimension(point.dimension()));
}
self.trajectory.push(point);
Ok(())
}
/// Analyze the current trajectory
pub fn analyze(&self) -> Result<AttractorInfo, AttractorError> {
if self.trajectory.len() < self.min_points_for_analysis {
return Err(AttractorError::InsufficientData(self.min_points_for_analysis));
}
// Calculate Lyapunov exponents
let lyapunov_exponents = self.calculate_lyapunov_exponents()?;
// Classify attractor type based on Lyapunov exponents
let attractor_type = self.classify_attractor(&lyapunov_exponents);
// Determine stability
let is_stable = lyapunov_exponents.iter().all(|&l| l < 0.0);
// Calculate confidence based on data quality
let confidence = self.calculate_confidence();
Ok(AttractorInfo {
attractor_type,
dimension: self.embedding_dimension,
lyapunov_exponents,
is_stable,
confidence,
})
}
/// Calculate Lyapunov exponents for the trajectory
fn calculate_lyapunov_exponents(&self) -> Result<Vec<f64>, AttractorError> {
if self.trajectory.len() < 2 {
return Ok(vec![0.0; self.embedding_dimension]);
}
let mut exponents = vec![0.0; self.embedding_dimension];
// Simplified Lyapunov calculation
// In production, this would use more sophisticated methods
let points: Vec<&PhasePoint> = self.trajectory.points.iter().collect();
for dim in 0..self.embedding_dimension {
let mut sum_log_divergence = 0.0;
let mut count = 0;
for i in 1..points.len() {
let diff = points[i].coordinates[dim] - points[i-1].coordinates[dim];
if diff.abs() > 1e-10 {
sum_log_divergence += diff.abs().ln();
count += 1;
}
}
if count > 0 {
exponents[dim] = sum_log_divergence / count as f64;
}
}
Ok(exponents)
}
/// Classify attractor based on Lyapunov exponents
fn classify_attractor(&self, lyapunov_exponents: &[f64]) -> AttractorType {
let max_exponent = lyapunov_exponents.iter()
.copied()
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0);
if max_exponent > 0.1 {
// Positive Lyapunov exponent indicates chaos
AttractorType::StrangeAttractor
} else if max_exponent > -0.1 && self.detect_periodicity() {
// Near-zero with periodicity indicates limit cycle
AttractorType::LimitCycle
} else if max_exponent < -0.1 {
// Negative Lyapunov exponent indicates stable point
AttractorType::PointAttractor
} else {
AttractorType::Unknown
}
}
/// Detect if trajectory shows periodic behavior
fn detect_periodicity(&self) -> bool {
if self.trajectory.len() < 20 {
return false;
}
// Simple autocorrelation check
let points: Vec<&PhasePoint> = self.trajectory.points.iter().collect();
let n = points.len();
// Check for repeating patterns
for lag in 5..n/4 {
let mut correlation = 0.0;
let mut count = 0;
for i in 0..n-lag {
for dim in 0..self.embedding_dimension {
let diff = (points[i].coordinates[dim] - points[i+lag].coordinates[dim]).abs();
correlation += diff;
count += 1;
}
}
let avg_diff = correlation / count as f64;
if avg_diff < 0.1 {
return true; // Found periodic pattern
}
}
false
}
/// Calculate confidence in the analysis
fn calculate_confidence(&self) -> f64 {
let data_ratio = self.trajectory.len() as f64 / self.min_points_for_analysis as f64;
data_ratio.min(1.0)
}
/// Get trajectory statistics
pub fn get_trajectory_stats(&self) -> BehaviorSummary {
let total_points = self.trajectory.len();
let mut trajectory_length = 0.0;
let mut velocity_sum = 0.0;
let points: Vec<&PhasePoint> = self.trajectory.points.iter().collect();
for i in 1..points.len() {
let mut distance = 0.0;
for dim in 0..self.embedding_dimension {
let diff = points[i].coordinates[dim] - points[i-1].coordinates[dim];
distance += diff * diff;
}
let segment_length = distance.sqrt();
trajectory_length += segment_length;
let time_diff = (points[i].timestamp - points[i-1].timestamp) as f64;
if time_diff > 0.0 {
velocity_sum += segment_length / time_diff;
}
}
let mean_velocity = if points.len() > 1 {
velocity_sum / (points.len() - 1) as f64
} else {
0.0
};
let attractor_info = if total_points >= self.min_points_for_analysis {
self.analyze().ok()
} else {
None
};
BehaviorSummary {
total_points,
dimension: self.embedding_dimension,
attractor_info,
mean_velocity,
trajectory_length,
}
}
/// Clear the trajectory
pub fn clear(&mut self) {
self.trajectory.clear();
}
/// Get current trajectory length
pub fn trajectory_length(&self) -> usize {
self.trajectory.len()
}
}
impl Default for AttractorAnalyzer {
fn default() -> Self {
Self::new(3, 10000)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phase_point() {
let point = PhasePoint::new(vec![1.0, 2.0, 3.0], 100);
assert_eq!(point.dimension(), 3);
}
#[test]
fn test_trajectory() {
let mut traj = Trajectory::new(10);
assert!(traj.is_empty());
traj.push(PhasePoint::new(vec![1.0, 2.0], 1));
assert_eq!(traj.len(), 1);
// Fill to capacity
for i in 2..=11 {
traj.push(PhasePoint::new(vec![i as f64, i as f64 * 2.0], i as u64));
}
// Should maintain max length
assert_eq!(traj.len(), 10);
}
#[test]
fn test_attractor_analyzer() {
let mut analyzer = AttractorAnalyzer::new(2, 1000);
// Add some points
for i in 0..150 {
let point = PhasePoint::new(
vec![i as f64, (i * 2) as f64],
i as u64 * 1000,
);
analyzer.add_point(point).unwrap();
}
assert_eq!(analyzer.trajectory_length(), 150);
let result = analyzer.analyze();
assert!(result.is_ok());
let info = result.unwrap();
assert_eq!(info.dimension, 2);
assert!(!info.lyapunov_exponents.is_empty());
}
#[test]
fn test_invalid_dimension() {
let mut analyzer = AttractorAnalyzer::new(3, 1000);
let point = PhasePoint::new(vec![1.0, 2.0], 100); // Only 2D
let result = analyzer.add_point(point);
assert!(result.is_err());
}
#[test]
fn test_insufficient_data() {
let analyzer = AttractorAnalyzer::new(2, 1000);
// Not enough points for analysis
let result = analyzer.analyze();
assert!(result.is_err());
}
#[test]
fn test_behavior_summary() {
let mut analyzer = AttractorAnalyzer::new(2, 1000);
for i in 0..50 {
let point = PhasePoint::new(
vec![i as f64, i as f64],
i as u64 * 100,
);
analyzer.add_point(point).unwrap();
}
let summary = analyzer.get_trajectory_stats();
assert_eq!(summary.total_points, 50);
assert_eq!(summary.dimension, 2);
assert!(summary.trajectory_length > 0.0);
}
#[test]
fn test_nan_handling_in_lyapunov_exponents() {
// Test that NaN values don't cause panics in max_lyapunov_exponent
let info = AttractorInfo {
attractor_type: AttractorType::StrangeAttractor,
dimension: 3,
lyapunov_exponents: vec![1.0, f64::NAN, -0.5],
is_stable: false,
confidence: 0.95,
};
// Should not panic, should handle NaN gracefully
let max_exp = info.max_lyapunov_exponent();
assert!(max_exp.is_some());
// With NaN handling, should return one of the valid values
let val = max_exp.unwrap();
assert!(val.is_finite(), "Should not return NaN");
}
#[test]
fn test_nan_handling_in_trajectory() {
// Test that NaN values in trajectory don't cause panics during analysis
let mut analyzer = AttractorAnalyzer::new(2, 1000);
// Add points including some with NaN to trigger edge cases
for i in 0..150 {
let coords = if i == 50 {
// Insert a point that could lead to NaN in calculations
vec![f64::NAN, i as f64]
} else {
vec![i as f64, (i * 2) as f64]
};
let point = PhasePoint::new(coords, i as u64 * 1000);
// Should not panic when adding or analyzing
let _ = analyzer.add_point(point);
}
// Analysis should complete without panicking
let result = analyzer.analyze();
assert!(result.is_ok(), "Analysis should handle NaN gracefully");
let info = result.unwrap();
// Verify the result is usable
assert_eq!(info.dimension, 2);
}
#[test]
fn test_all_nan_lyapunov_exponents() {
// Edge case: all NaN values
let info = AttractorInfo {
attractor_type: AttractorType::Unknown,
dimension: 2,
lyapunov_exponents: vec![f64::NAN, f64::NAN],
is_stable: false,
confidence: 0.5,
};
// Should not panic even with all NaN
let max_exp = info.max_lyapunov_exponent();
assert!(max_exp.is_some());
}
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "midstreamer-temporal-compare"
version = "0.1.0"
edition = "2021"
description = "Temporal sequence comparison and pattern matching"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["temporal", "sequence", "comparison", "pattern-matching", "midstream"]
categories = ["algorithms", "data-structures"]
[lib]
name = "midstreamer_temporal_compare"
path = "src/lib.rs"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
dashmap = "6.1"
lru = "0.12"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
+697
View File
@@ -0,0 +1,697 @@
//! # Temporal-Compare
//!
//! Advanced temporal sequence comparison and pattern matching.
//!
//! ## Features
//! - Dynamic Time Warping (DTW)
//! - Longest Common Subsequence (LCS)
//! - Edit Distance (Levenshtein)
//! - Pattern matching and detection
//! - Efficient caching
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use thiserror::Error;
use dashmap::DashMap;
use lru::LruCache;
use std::sync::{Arc, Mutex};
use std::num::NonZeroUsize;
/// Errors that can occur during temporal comparison
#[derive(Debug, Error)]
pub enum TemporalError {
#[error("Sequence too long: {0}")]
SequenceTooLong(usize),
#[error("Invalid algorithm: {0}")]
InvalidAlgorithm(String),
#[error("Cache error: {0}")]
CacheError(String),
#[error("Invalid pattern length: min={0}, max={1}")]
InvalidPatternLength(usize, usize),
#[error("Pattern not found")]
PatternNotFound,
}
/// A temporal sequence element
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TemporalElement<T> {
pub value: T,
pub timestamp: u64,
}
/// A temporal sequence
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sequence<T> {
pub elements: Vec<TemporalElement<T>>,
}
impl<T> Sequence<T> {
pub fn new() -> Self {
Self { elements: Vec::new() }
}
pub fn push(&mut self, value: T, timestamp: u64) {
self.elements.push(TemporalElement { value, timestamp });
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
}
impl<T> Default for Sequence<T> {
fn default() -> Self {
Self::new()
}
}
/// Comparison algorithm types
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ComparisonAlgorithm {
/// Dynamic Time Warping
DTW,
/// Longest Common Subsequence
LCS,
/// Edit Distance (Levenshtein)
EditDistance,
/// Euclidean distance
Euclidean,
}
/// Result of a temporal comparison
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComparisonResult {
pub distance: f64,
pub algorithm: ComparisonAlgorithm,
pub alignment: Option<Vec<(usize, usize)>>,
}
/// Statistics about cache performance
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub size: usize,
pub capacity: usize,
}
impl CacheStats {
pub fn hit_rate(&self) -> f64 {
if self.hits + self.misses == 0 {
0.0
} else {
self.hits as f64 / (self.hits + self.misses) as f64
}
}
}
/// A detected pattern in a sequence
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pattern<T> {
/// The pattern sequence
pub sequence: Vec<T>,
/// Starting indices of all occurrences
pub occurrences: Vec<usize>,
/// Confidence score (0.0 to 1.0)
pub confidence: f64,
}
impl<T> Pattern<T> {
/// Create a new pattern
pub fn new(sequence: Vec<T>, occurrences: Vec<usize>, confidence: f64) -> Self {
Self {
sequence,
occurrences,
confidence,
}
}
/// Get the number of times this pattern occurs
pub fn frequency(&self) -> usize {
self.occurrences.len()
}
/// Get the length of the pattern
pub fn length(&self) -> usize {
self.sequence.len()
}
}
/// Match result for similarity search
#[derive(Debug, Clone, PartialEq)]
pub struct SimilarityMatch {
/// Starting index in the haystack
pub start_index: usize,
/// Similarity score (0.0 to 1.0, higher is more similar)
pub similarity: f64,
/// DTW distance (lower is better)
pub distance: f64,
}
impl SimilarityMatch {
pub fn new(start_index: usize, distance: f64) -> Self {
// Convert distance to similarity score (inverse exponential decay)
let similarity = (-distance / 10.0).exp();
Self {
start_index,
similarity,
distance,
}
}
}
/// Temporal comparator with caching
pub struct TemporalComparator<T> {
cache: Arc<Mutex<LruCache<String, ComparisonResult>>>,
pattern_cache: Arc<Mutex<LruCache<String, Vec<Pattern<T>>>>>,
similarity_cache: Arc<Mutex<LruCache<String, Vec<SimilarityMatch>>>>,
cache_hits: Arc<DashMap<String, u64>>,
cache_misses: Arc<DashMap<String, u64>>,
max_sequence_length: usize,
}
impl<T> TemporalComparator<T>
where
T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq,
{
/// Create a new temporal comparator
pub fn new(cache_size: usize, max_sequence_length: usize) -> Self {
Self {
cache: Arc::new(Mutex::new(LruCache::new(
NonZeroUsize::new(cache_size).unwrap()
))),
pattern_cache: Arc::new(Mutex::new(LruCache::new(
NonZeroUsize::new(cache_size).unwrap()
))),
similarity_cache: Arc::new(Mutex::new(LruCache::new(
NonZeroUsize::new(cache_size).unwrap()
))),
cache_hits: Arc::new(DashMap::new()),
cache_misses: Arc::new(DashMap::new()),
max_sequence_length,
}
}
/// Compare two sequences using the specified algorithm
pub fn compare(
&self,
seq1: &Sequence<T>,
seq2: &Sequence<T>,
algorithm: ComparisonAlgorithm,
) -> Result<ComparisonResult, TemporalError> {
// Check sequence length
if seq1.len() > self.max_sequence_length || seq2.len() > self.max_sequence_length {
return Err(TemporalError::SequenceTooLong(
seq1.len().max(seq2.len())
));
}
// Generate cache key
let cache_key = self.cache_key(seq1, seq2, algorithm);
// Check cache
if let Ok(mut cache) = self.cache.lock() {
if let Some(result) = cache.get(&cache_key) {
self.record_cache_hit(&cache_key);
return Ok(result.clone());
}
}
self.record_cache_miss(&cache_key);
// Compute comparison
let result = match algorithm {
ComparisonAlgorithm::DTW => self.dtw(seq1, seq2),
ComparisonAlgorithm::LCS => self.lcs(seq1, seq2),
ComparisonAlgorithm::EditDistance => self.edit_distance(seq1, seq2),
ComparisonAlgorithm::Euclidean => self.euclidean(seq1, seq2),
}?;
// Store in cache
if let Ok(mut cache) = self.cache.lock() {
cache.put(cache_key, result.clone());
}
Ok(result)
}
/// Dynamic Time Warping implementation
fn dtw(&self, seq1: &Sequence<T>, seq2: &Sequence<T>) -> Result<ComparisonResult, TemporalError> {
let n = seq1.len();
let m = seq2.len();
if n == 0 || m == 0 {
return Ok(ComparisonResult {
distance: (n + m) as f64,
algorithm: ComparisonAlgorithm::DTW,
alignment: None,
});
}
// Initialize DTW matrix
let mut dtw = vec![vec![f64::INFINITY; m + 1]; n + 1];
dtw[0][0] = 0.0;
// Fill DTW matrix
for i in 1..=n {
for j in 1..=m {
let cost = if seq1.elements[i-1].value == seq2.elements[j-1].value {
0.0
} else {
1.0
};
dtw[i][j] = cost + dtw[i-1][j-1].min(dtw[i-1][j]).min(dtw[i][j-1]);
}
}
// Backtrack for alignment
let mut alignment = Vec::new();
let (mut i, mut j) = (n, m);
while i > 0 && j > 0 {
alignment.push((i - 1, j - 1));
let min_val = dtw[i-1][j-1].min(dtw[i-1][j]).min(dtw[i][j-1]);
if dtw[i-1][j-1] == min_val {
i -= 1;
j -= 1;
} else if dtw[i-1][j] == min_val {
i -= 1;
} else {
j -= 1;
}
}
alignment.reverse();
Ok(ComparisonResult {
distance: dtw[n][m],
algorithm: ComparisonAlgorithm::DTW,
alignment: Some(alignment),
})
}
/// Longest Common Subsequence implementation
fn lcs(&self, seq1: &Sequence<T>, seq2: &Sequence<T>) -> Result<ComparisonResult, TemporalError> {
let n = seq1.len();
let m = seq2.len();
let mut dp = vec![vec![0; m + 1]; n + 1];
for i in 1..=n {
for j in 1..=m {
if seq1.elements[i-1].value == seq2.elements[j-1].value {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = dp[i-1][j].max(dp[i][j-1]);
}
}
}
let lcs_length = dp[n][m];
let distance = (n + m - 2 * lcs_length) as f64;
Ok(ComparisonResult {
distance,
algorithm: ComparisonAlgorithm::LCS,
alignment: None,
})
}
/// Edit Distance (Levenshtein) implementation
fn edit_distance(&self, seq1: &Sequence<T>, seq2: &Sequence<T>) -> Result<ComparisonResult, TemporalError> {
let n = seq1.len();
let m = seq2.len();
let mut dp = vec![vec![0; m + 1]; n + 1];
for i in 0..=n {
dp[i][0] = i;
}
for j in 0..=m {
dp[0][j] = j;
}
for i in 1..=n {
for j in 1..=m {
let cost = if seq1.elements[i-1].value == seq2.elements[j-1].value {
0
} else {
1
};
dp[i][j] = (dp[i-1][j] + 1)
.min(dp[i][j-1] + 1)
.min(dp[i-1][j-1] + cost);
}
}
Ok(ComparisonResult {
distance: dp[n][m] as f64,
algorithm: ComparisonAlgorithm::EditDistance,
alignment: None,
})
}
/// Euclidean distance (for numeric sequences)
fn euclidean(&self, seq1: &Sequence<T>, seq2: &Sequence<T>) -> Result<ComparisonResult, TemporalError> {
let n = seq1.len().min(seq2.len());
let mut sum: f64 = 0.0;
for i in 0..n {
// Simplified: just count mismatches
if seq1.elements[i].value != seq2.elements[i].value {
sum += 1.0;
}
}
Ok(ComparisonResult {
distance: sum.sqrt(), // f64 type is now explicit from declaration
algorithm: ComparisonAlgorithm::Euclidean,
alignment: None,
})
}
/// Generate cache key for a comparison
fn cache_key(&self, seq1: &Sequence<T>, seq2: &Sequence<T>, algorithm: ComparisonAlgorithm) -> String {
format!(
"{:?}:{:?}:{:?}",
seq1.elements.len(),
seq2.elements.len(),
algorithm
)
}
fn record_cache_hit(&self, key: &str) {
self.cache_hits.entry(key.to_string())
.and_modify(|v| *v += 1)
.or_insert(1);
}
fn record_cache_miss(&self, key: &str) {
self.cache_misses.entry(key.to_string())
.and_modify(|v| *v += 1)
.or_insert(1);
}
/// Get cache statistics
pub fn cache_stats(&self) -> CacheStats {
let hits: u64 = self.cache_hits.iter().map(|r| *r.value()).sum();
let misses: u64 = self.cache_misses.iter().map(|r| *r.value()).sum();
let (size, capacity) = if let Ok(cache) = self.cache.lock() {
(cache.len(), cache.cap().get())
} else {
(0, 0)
};
CacheStats {
hits,
misses,
size,
capacity,
}
}
/// Clear the cache
pub fn clear_cache(&self) {
if let Ok(mut cache) = self.cache.lock() {
cache.clear();
}
if let Ok(mut cache) = self.pattern_cache.lock() {
cache.clear();
}
if let Ok(mut cache) = self.similarity_cache.lock() {
cache.clear();
}
self.cache_hits.clear();
self.cache_misses.clear();
}
/// Find similar sequences within a haystack using generic types
pub fn find_similar_generic(
&self,
haystack: &[T],
needle: &[T],
threshold: f64,
) -> Result<Vec<SimilarityMatch>, TemporalError> {
if needle.is_empty() || haystack.len() < needle.len() {
return Ok(Vec::new());
}
// Generate cache key
let cache_key = format!(
"similar:{:?}:{:?}:{}",
haystack.len(),
needle.len(),
threshold
);
// Check cache
if let Ok(mut cache) = self.similarity_cache.lock() {
if let Some(results) = cache.get(&cache_key) {
self.record_cache_hit(&cache_key);
return Ok(results.clone());
}
}
self.record_cache_miss(&cache_key);
let needle_len = needle.len();
let mut matches = Vec::new();
// Sliding window approach
for start_idx in 0..=(haystack.len() - needle_len) {
let window = &haystack[start_idx..start_idx + needle_len];
// Convert to Sequence for comparison
let mut seq1 = Sequence::new();
for (i, item) in window.iter().enumerate() {
seq1.push(item.clone(), i as u64);
}
let mut seq2 = Sequence::new();
for (i, item) in needle.iter().enumerate() {
seq2.push(item.clone(), i as u64);
}
// Compute DTW distance
if let Ok(result) = self.dtw(&seq1, &seq2) {
// Normalize distance by pattern length
let normalized_distance = result.distance / needle_len as f64;
if normalized_distance <= threshold {
matches.push(SimilarityMatch::new(start_idx, result.distance));
}
}
}
// Sort by distance (best matches first)
matches.sort_by(|a, b| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(std::cmp::Ordering::Equal)
});
// Store in cache
if let Ok(mut cache) = self.similarity_cache.lock() {
cache.put(cache_key, matches.clone());
}
Ok(matches)
}
/// Detect recurring patterns in a sequence
pub fn detect_recurring_patterns(
&self,
sequence: &[T],
min_length: usize,
max_length: usize,
) -> Result<Vec<Pattern<T>>, TemporalError> {
if min_length > max_length {
return Err(TemporalError::InvalidPatternLength(min_length, max_length));
}
if sequence.len() < min_length {
return Ok(Vec::new());
}
// Generate cache key
let cache_key = format!(
"patterns:{:?}:{}:{}",
sequence.len(),
min_length,
max_length
);
// Check cache
if let Ok(mut cache) = self.pattern_cache.lock() {
if let Some(patterns) = cache.get(&cache_key) {
self.record_cache_hit(&cache_key);
return Ok(patterns.clone());
}
}
self.record_cache_miss(&cache_key);
let mut pattern_map: HashMap<Vec<T>, Vec<usize>> = HashMap::new();
// Search for patterns of each length
for pattern_len in min_length..=max_length.min(sequence.len()) {
for start_idx in 0..=(sequence.len() - pattern_len) {
let pattern_seq = sequence[start_idx..start_idx + pattern_len].to_vec();
pattern_map
.entry(pattern_seq)
.or_insert_with(Vec::new)
.push(start_idx);
}
}
// Filter patterns that occur at least twice
let mut patterns: Vec<Pattern<T>> = pattern_map
.into_iter()
.filter(|(_, occurrences)| occurrences.len() >= 2)
.map(|(seq, occurrences)| {
// Calculate confidence based on frequency and pattern length
let frequency = occurrences.len() as f64;
let pattern_len = seq.len() as f64;
let total_possible = (sequence.len() - seq.len() + 1) as f64;
// Confidence is weighted by frequency and pattern length
let confidence = ((frequency / total_possible) * (pattern_len / max_length as f64))
.min(1.0);
Pattern::new(seq, occurrences, confidence)
})
.collect();
// Sort by frequency (most common first), then by confidence
patterns.sort_by(|a, b| {
b.frequency()
.cmp(&a.frequency())
.then_with(|| {
b.confidence
.partial_cmp(&a.confidence)
.unwrap_or(std::cmp::Ordering::Equal)
})
});
// Store in cache
if let Ok(mut cache) = self.pattern_cache.lock() {
cache.put(cache_key, patterns.clone());
}
Ok(patterns)
}
}
impl<T> Default for TemporalComparator<T>
where
T: Clone + PartialEq + fmt::Debug + Serialize + Hash + Eq,
{
fn default() -> Self {
Self::new(1000, 10000)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sequence_creation() {
let mut seq: Sequence<i32> = Sequence::new();
seq.push(1, 100);
seq.push(2, 200);
assert_eq!(seq.len(), 2);
assert!(!seq.is_empty());
}
#[test]
fn test_dtw() {
let comparator = TemporalComparator::new(100, 1000);
let mut seq1: Sequence<i32> = Sequence::new();
seq1.push(1, 100);
seq1.push(2, 200);
seq1.push(3, 300);
let mut seq2: Sequence<i32> = Sequence::new();
seq2.push(1, 100);
seq2.push(2, 200);
seq2.push(3, 300);
let result = comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap();
assert_eq!(result.distance, 0.0);
}
#[test]
fn test_cache() {
let comparator = TemporalComparator::new(100, 1000);
let mut seq1: Sequence<i32> = Sequence::new();
seq1.push(1, 1);
seq1.push(2, 2);
let mut seq2: Sequence<i32> = Sequence::new();
seq2.push(1, 1);
seq2.push(2, 2);
// First comparison - cache miss
comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap();
// Second comparison - cache hit
comparator.compare(&seq1, &seq2, ComparisonAlgorithm::DTW).unwrap();
let stats = comparator.cache_stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
}
#[test]
fn test_find_similar_generic_integers() {
let comparator: TemporalComparator<i32> = TemporalComparator::new(100, 1000);
let haystack = vec![1, 2, 3, 4, 5, 3, 4, 5];
let needle = vec![3, 4, 5];
let matches = comparator.find_similar_generic(&haystack, &needle, 0.1).unwrap();
assert_eq!(matches.len(), 2);
assert_eq!(matches[0].start_index, 2);
assert_eq!(matches[1].start_index, 5);
assert!(matches[0].similarity > 0.9); // High similarity for exact match
}
#[test]
fn test_detect_recurring_patterns_simple() {
let comparator: TemporalComparator<char> = TemporalComparator::new(100, 1000);
let sequence = vec!['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c'];
let patterns = comparator.detect_recurring_patterns(&sequence, 2, 4).unwrap();
assert!(!patterns.is_empty());
// Should find 'abc' pattern recurring
let abc_pattern = patterns.iter().find(|p| p.sequence == vec!['a', 'b', 'c']);
assert!(abc_pattern.is_some());
let pattern = abc_pattern.unwrap();
assert_eq!(pattern.frequency(), 3);
assert!(pattern.confidence > 0.0);
}
}
@@ -0,0 +1,18 @@
[package]
name = "midstreamer-neural-solver"
version = "0.1.0"
edition = "2021"
description = "Temporal logic with neural reasoning"
license = "MIT"
repository = "https://github.com/ruvnet/midstream"
keywords = ["neural", "temporal", "logic", "reasoning", "midstream"]
categories = ["algorithms", "science"]
[dependencies]
midstreamer-scheduler = { path = "../nanosecond-scheduler" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
ndarray = "0.16"
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
@@ -0,0 +1,509 @@
//! # Temporal-Neural-Solver
//!
//! Temporal logic with neural reasoning.
//!
//! ## Features
//! - Linear Temporal Logic (LTL)
//! - Computation Tree Logic (CTL)
//! - Metric Temporal Logic (MTL)
//! - Neural-guided solving
//! - Verification and validation
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use thiserror::Error;
/// Temporal logic errors
#[derive(Debug, Error)]
pub enum TemporalError {
#[error("Formula parsing error: {0}")]
ParseError(String),
#[error("Verification failed: {0}")]
VerificationFailed(String),
#[error("Timeout: {0}ms")]
Timeout(u64),
#[error("Invalid state: {0}")]
InvalidState(String),
}
/// Temporal operators
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TemporalOperator {
/// Globally (always)
Globally,
/// Finally (eventually)
Finally,
/// Next
Next,
/// Until
Until,
/// And
And,
/// Or
Or,
/// Not
Not,
/// Implies
Implies,
}
/// A temporal formula
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TemporalFormula {
/// Atomic proposition
Atom(String),
/// Unary operator
Unary {
op: TemporalOperator,
formula: Box<TemporalFormula>,
},
/// Binary operator
Binary {
op: TemporalOperator,
left: Box<TemporalFormula>,
right: Box<TemporalFormula>,
},
/// True
True,
/// False
False,
}
impl TemporalFormula {
/// Create a Globally formula (G φ)
pub fn globally(formula: TemporalFormula) -> Self {
TemporalFormula::Unary {
op: TemporalOperator::Globally,
formula: Box::new(formula),
}
}
/// Create a Finally formula (F φ)
pub fn finally(formula: TemporalFormula) -> Self {
TemporalFormula::Unary {
op: TemporalOperator::Finally,
formula: Box::new(formula),
}
}
/// Create a Next formula (X φ)
pub fn next(formula: TemporalFormula) -> Self {
TemporalFormula::Unary {
op: TemporalOperator::Next,
formula: Box::new(formula),
}
}
/// Create an Until formula (φ U ψ)
pub fn until(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Binary {
op: TemporalOperator::Until,
left: Box::new(left),
right: Box::new(right),
}
}
/// Create an And formula (φ ∧ ψ)
pub fn and(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Binary {
op: TemporalOperator::And,
left: Box::new(left),
right: Box::new(right),
}
}
/// Create an Or formula (φ ψ)
pub fn or(left: TemporalFormula, right: TemporalFormula) -> Self {
TemporalFormula::Binary {
op: TemporalOperator::Or,
left: Box::new(left),
right: Box::new(right),
}
}
/// Create a Not formula (¬φ)
pub fn not(formula: TemporalFormula) -> Self {
TemporalFormula::Unary {
op: TemporalOperator::Not,
formula: Box::new(formula),
}
}
/// Create an atomic proposition
pub fn atom(name: impl Into<String>) -> Self {
TemporalFormula::Atom(name.into())
}
}
/// A state in the temporal model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalState {
pub id: u64,
pub propositions: HashMap<String, bool>,
pub timestamp: u64,
}
impl TemporalState {
pub fn new(id: u64, timestamp: u64) -> Self {
Self {
id,
propositions: HashMap::new(),
timestamp,
}
}
pub fn set_proposition(&mut self, prop: impl Into<String>, value: bool) {
self.propositions.insert(prop.into(), value);
}
pub fn get_proposition(&self, prop: &str) -> bool {
*self.propositions.get(prop).unwrap_or(&false)
}
}
/// A trace is a sequence of states
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalTrace {
pub states: VecDeque<TemporalState>,
pub max_length: usize,
}
impl TemporalTrace {
pub fn new(max_length: usize) -> Self {
Self {
states: VecDeque::new(),
max_length,
}
}
pub fn push(&mut self, state: TemporalState) {
if self.states.len() >= self.max_length {
self.states.pop_front();
}
self.states.push_back(state);
}
pub fn len(&self) -> usize {
self.states.len()
}
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
pub fn get(&self, index: usize) -> Option<&TemporalState> {
self.states.get(index)
}
}
/// Result of verification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
pub satisfied: bool,
pub formula: String,
pub counterexample: Option<Vec<u64>>,
pub confidence: f64,
}
/// Temporal neural solver
pub struct TemporalNeuralSolver {
trace: TemporalTrace,
#[allow(dead_code)]
max_solving_time_ms: u64,
verification_strictness: VerificationStrictness,
}
#[derive(Debug, Clone, Copy)]
pub enum VerificationStrictness {
Low,
Medium,
High,
}
impl TemporalNeuralSolver {
/// Create a new temporal neural solver
pub fn new(
max_trace_length: usize,
max_solving_time_ms: u64,
verification_strictness: VerificationStrictness,
) -> Self {
Self {
trace: TemporalTrace::new(max_trace_length),
max_solving_time_ms,
verification_strictness,
}
}
/// Add a state to the trace
pub fn add_state(&mut self, state: TemporalState) {
self.trace.push(state);
}
/// Verify a temporal formula against the trace
pub fn verify(&self, formula: &TemporalFormula) -> Result<VerificationResult, TemporalError> {
if self.trace.is_empty() {
return Err(TemporalError::InvalidState("Empty trace".to_string()));
}
let satisfied = self.check_formula(formula, 0)?;
let formula_str = format!("{:?}", formula);
Ok(VerificationResult {
satisfied,
formula: formula_str,
counterexample: if !satisfied {
Some(vec![0]) // Simplified counterexample
} else {
None
},
confidence: self.calculate_confidence(),
})
}
/// Check if formula holds at given position in trace
fn check_formula(&self, formula: &TemporalFormula, position: usize) -> Result<bool, TemporalError> {
match formula {
TemporalFormula::True => Ok(true),
TemporalFormula::False => Ok(false),
TemporalFormula::Atom(prop) => {
if let Some(state) = self.trace.get(position) {
Ok(state.get_proposition(prop))
} else {
Ok(false)
}
}
TemporalFormula::Unary { op, formula } => {
match op {
TemporalOperator::Not => {
Ok(!self.check_formula(formula, position)?)
}
TemporalOperator::Next => {
if position + 1 < self.trace.len() {
self.check_formula(formula, position + 1)
} else {
Ok(false)
}
}
TemporalOperator::Globally => {
// G φ: φ holds at all future states
for i in position..self.trace.len() {
if !self.check_formula(formula, i)? {
return Ok(false);
}
}
Ok(true)
}
TemporalOperator::Finally => {
// F φ: φ holds at some future state
for i in position..self.trace.len() {
if self.check_formula(formula, i)? {
return Ok(true);
}
}
Ok(false)
}
_ => Err(TemporalError::ParseError(format!("Invalid unary operator: {:?}", op))),
}
}
TemporalFormula::Binary { op, left, right } => {
match op {
TemporalOperator::And => {
Ok(self.check_formula(left, position)? && self.check_formula(right, position)?)
}
TemporalOperator::Or => {
Ok(self.check_formula(left, position)? || self.check_formula(right, position)?)
}
TemporalOperator::Implies => {
Ok(!self.check_formula(left, position)? || self.check_formula(right, position)?)
}
TemporalOperator::Until => {
// φ U ψ: φ holds until ψ becomes true
for i in position..self.trace.len() {
if self.check_formula(right, i)? {
// ψ is true, check if φ held until now
for j in position..i {
if !self.check_formula(left, j)? {
return Ok(false);
}
}
return Ok(true);
}
}
Ok(false)
}
_ => Err(TemporalError::ParseError(format!("Invalid binary operator: {:?}", op))),
}
}
}
}
/// Calculate confidence in verification
fn calculate_confidence(&self) -> f64 {
let trace_length_factor = (self.trace.len() as f64 / 100.0).min(1.0);
let strictness_factor = match self.verification_strictness {
VerificationStrictness::Low => 0.7,
VerificationStrictness::Medium => 0.85,
VerificationStrictness::High => 0.95,
};
trace_length_factor * strictness_factor
}
/// Synthesize a controller to satisfy a formula
pub fn synthesize_controller(&self, _formula: &TemporalFormula) -> Result<Vec<String>, TemporalError> {
// Simplified controller synthesis
// In production, this would use more sophisticated techniques
Ok(vec!["action1".to_string(), "action2".to_string()])
}
/// Get trace length
pub fn trace_length(&self) -> usize {
self.trace.len()
}
/// Clear the trace
pub fn clear_trace(&mut self) {
self.trace.states.clear();
}
}
impl Default for TemporalNeuralSolver {
fn default() -> Self {
Self::new(1000, 500, VerificationStrictness::Medium)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_formula_creation() {
let atom = TemporalFormula::atom("safe");
let globally_safe = TemporalFormula::globally(atom);
match globally_safe {
TemporalFormula::Unary { op, .. } => {
assert_eq!(op, TemporalOperator::Globally);
}
_ => panic!("Expected Unary"),
}
}
#[test]
fn test_state() {
let mut state = TemporalState::new(1, 100);
state.set_proposition("safe", true);
state.set_proposition("ready", false);
assert!(state.get_proposition("safe"));
assert!(!state.get_proposition("ready"));
assert!(!state.get_proposition("unknown"));
}
#[test]
fn test_trace() {
let mut trace = TemporalTrace::new(10);
for i in 0..5 {
let mut state = TemporalState::new(i, i * 100);
state.set_proposition("step", true);
trace.push(state);
}
assert_eq!(trace.len(), 5);
assert!(trace.get(0).is_some());
}
#[test]
fn test_verification_atom() {
let mut solver = TemporalNeuralSolver::default();
let mut state = TemporalState::new(1, 100);
state.set_proposition("safe", true);
solver.add_state(state);
let formula = TemporalFormula::atom("safe");
let result = solver.verify(&formula).unwrap();
assert!(result.satisfied);
}
#[test]
fn test_verification_globally() {
let mut solver = TemporalNeuralSolver::default();
// Add states where "safe" is always true
for i in 0..5 {
let mut state = TemporalState::new(i, i * 100);
state.set_proposition("safe", true);
solver.add_state(state);
}
let formula = TemporalFormula::globally(TemporalFormula::atom("safe"));
let result = solver.verify(&formula).unwrap();
assert!(result.satisfied);
}
#[test]
fn test_verification_finally() {
let mut solver = TemporalNeuralSolver::default();
// Add states where "goal" becomes true at step 3
for i in 0..5 {
let mut state = TemporalState::new(i, i * 100);
state.set_proposition("goal", i == 3);
solver.add_state(state);
}
let formula = TemporalFormula::finally(TemporalFormula::atom("goal"));
let result = solver.verify(&formula).unwrap();
assert!(result.satisfied);
}
#[test]
fn test_verification_next() {
let mut solver = TemporalNeuralSolver::default();
let mut state1 = TemporalState::new(1, 100);
state1.set_proposition("ready", false);
solver.add_state(state1);
let mut state2 = TemporalState::new(2, 200);
state2.set_proposition("ready", true);
solver.add_state(state2);
let formula = TemporalFormula::next(TemporalFormula::atom("ready"));
let result = solver.verify(&formula).unwrap();
assert!(result.satisfied);
}
#[test]
fn test_verification_and() {
let mut solver = TemporalNeuralSolver::default();
let mut state = TemporalState::new(1, 100);
state.set_proposition("safe", true);
state.set_proposition("ready", true);
solver.add_state(state);
let formula = TemporalFormula::and(
TemporalFormula::atom("safe"),
TemporalFormula::atom("ready"),
);
let result = solver.verify(&formula).unwrap();
assert!(result.satisfied);
}
}