temporal_attractor_studio/
lib.rs1use thiserror::Error;
41
42pub mod ftle;
44
45pub mod echo_state;
47
48pub mod attractor;
50
51pub mod time_expansion_bridge;
53
54pub use ftle::*;
56
57#[derive(Error, Debug)]
59pub enum TemporalStudioError {
60 #[error("FTLE calculation error: {0}")]
61 Ftle(String),
62
63 #[error("VP-tree construction error: {0}")]
64 VpTree(String),
65
66 #[error("Delay embedding error: {0}")]
67 Embedding(String),
68
69 #[error("Data processing error: {0}")]
70 DataProcessing(String),
71
72 #[error("Configuration error: {0}")]
73 Configuration(String),
74
75 #[error("IO error: {0}")]
76 Io(#[from] std::io::Error),
77
78 #[error("Anyhow error: {0}")]
79 Anyhow(#[from] anyhow::Error),
80}
81
82pub type StudioResult<T> = Result<T, TemporalStudioError>;
84
85pub mod prelude {
87 pub use crate::{
88 TemporalStudioError, StudioResult,
89 VpTree, FtleParams, LyapunovResult,
90 estimate_lyapunov, estimate_lyapunov_default, estimate_lyapunov_with_params,
91 delay_embed, mean, dist, theiler_exclude,
92 calculate_ftle_segment, calculate_ftle_field,
93 };
94 pub use anyhow::Result;
95}
96
97pub fn init() -> StudioResult<()> {
99 #[cfg(feature = "tracing")]
101 {
102 let _ = tracing_subscriber::fmt()
103 .with_max_level(tracing::Level::INFO)
104 .try_init();
105 tracing::info!("Temporal Attractor Studio initialized");
106 }
107
108 #[cfg(not(feature = "tracing"))]
109 {
110 println!("Temporal Attractor Studio initialized");
111 }
112
113 Ok(())
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn test_ftle_calculation() {
122 let trajectory = vec![
123 vec![1.0, 2.0],
124 vec![1.1, 2.1],
125 vec![1.2, 2.2],
126 vec![1.3, 2.3],
127 vec![1.4, 2.4],
128 vec![1.5, 2.5],
129 vec![1.6, 2.6],
130 vec![1.7, 2.7],
131 vec![1.8, 2.8],
132 vec![1.9, 2.9],
133 vec![2.0, 3.0],
134 vec![2.1, 3.1],
135 vec![2.2, 3.2],
136 vec![2.3, 3.3],
137 vec![2.4, 3.4],
138 ];
139
140 let result = estimate_lyapunov_default(&trajectory);
141 match result {
142 Ok(lyap_result) => {
143 assert!(lyap_result.lambda.is_finite());
144 assert!(lyap_result.doubling_time > 0.0);
145 assert!(lyap_result.lyapunov_time > 0.0);
146 assert!(lyap_result.pairs_found > 0);
147 }
148 Err(e) => {
149 println!("Expected error for simple linear data: {}", e);
152 assert!(true);
154 }
155 }
156 }
157
158 #[test]
159 fn test_delay_embedding() {
160 let series = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
161 let embedded = delay_embed(&series, 3, 1).unwrap();
162
163 assert_eq!(embedded.len(), 4);
164 assert_eq!(embedded[0], vec![1.0, 2.0, 3.0]);
165 assert_eq!(embedded[1], vec![2.0, 3.0, 4.0]);
166 assert_eq!(embedded[2], vec![3.0, 4.0, 5.0]);
167 assert_eq!(embedded[3], vec![4.0, 5.0, 6.0]);
168 }
169
170 #[test]
171 fn test_ftle_params() {
172 let params = FtleParams::default();
173 assert_eq!(params.dt, 0.01);
174 assert_eq!(params.k_fit, 12);
175 assert_eq!(params.theiler, 20);
176 assert_eq!(params.max_pairs, 4000);
177 assert_eq!(params.min_init_sep, 1e-12);
178 }
179}