1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! # rsnn-eta
//!
//! A biologically-inspired ETA estimator using a Recurrent Spiking Neural Network (RSNN)
//! with Spike-Timing-Dependent Plasticity (STDP) to learn correction factors for
//! time-remaining predictions.
//!
//! The core idea: a pluggable base estimator (default: exponential moving average) provides
//! a naive ETA, and an RSNN learns a correction factor from prediction errors. The network
//! adapts online via STDP, detecting phase transitions, bursts, and non-linear progress
//! patterns that defeat simple smoothing.
//!
//! ## Quick start
//!
//! ```rust
//! use std::time::{Duration, Instant};
//! use rsnn_eta::RsnnEta;
//!
//! let mut eta = RsnnEta::new();
//! let start = Instant::now();
//!
//! for i in 1..=100 {
//! let elapsed = Duration::from_millis(i * 50);
//! if let Some(remaining) = eta.tick(i * 10, 10_000, elapsed, start + elapsed) {
//! // `remaining` is the corrected ETA as a Duration
//! }
//! }
//! ```
//!
//! ## Builder
//!
//! ```rust
//! use rsnn_eta::RsnnEta;
//!
//! let mut eta = RsnnEta::builder()
//! .neurons(100) // reservoir size (default: 50)
//! .steps_per_tick(30) // simulation steps per tick (default: 20)
//! .burn_in_ticks(15) // ticks before STDP learning starts (default: 10)
//! .ema_alpha(0.03) // base EMA smoothing (default: 0.05)
//! .seed(123) // RNG seed for reproducibility
//! .persistence("./weights.bin") // optional save/load path
//! .build();
//! ```
//!
//! ## Custom base estimator
//!
//! ```rust
//! use std::time::Duration;
//! use rsnn_eta::BaseEstimator;
//!
//! struct MyEstimator { /* ... */ }
//!
//! impl BaseEstimator for MyEstimator {
//! fn update(&mut self, position: u64, length: u64, elapsed: Duration) { /* ... */ }
//! fn estimate(&self) -> Option<Duration> { None }
//! fn is_warm(&self) -> bool { false }
//! fn reset(&mut self) {}
//! fn steps_per_sec(&self) -> f64 { 0.0 }
//! fn clone_box(&self) -> Box<dyn BaseEstimator> { Box::new(MyEstimator {}) }
//! }
//!
//! let eta = rsnn_eta::RsnnEta::builder()
//! .base_estimator(Box::new(MyEstimator {}))
//! .build();
//! ```
//!
//! ## Side-channel signals
//!
//! Inject additional features (e.g., batch size, phase indicator) beyond what the
//! progress bar exposes:
//!
//! ```rust
//! let (mut eta, tx) = rsnn_eta::RsnnEta::builder().build_with_signals();
//! tx.send(vec![0.8, 1.2]).unwrap(); // e.g., batch_size_ratio, phase_indicator
//! ```
//!
//! ## Architecture
//!
//! ```text
//! tick(pos, len, elapsed, now)
//! │
//! ├─► Base Estimator (EMA) ──► base_eta
//! │
//! ├─► Encoder (rate + temporal coding)
//! │ │
//! │ ▼
//! │ RSNN Reservoir (LIF neurons, sparse E/I, STDP)
//! │ │
//! │ ▼
//! │ Decoder ──► correction_factor
//! │
//! └─► final_eta = base_eta × (confidence × factor + (1-confidence) × 1.0)
//! ```
pub use ;
pub use ;
pub use ;