scirs2_cluster/serialization/
core.rs1use crate::error::{ClusteringError, Result};
7use oxiarc_deflate::{gzip_compress, gzip_decompress};
8use serde::{Deserialize, Serialize};
9use std::fs::File;
10use std::io::{Read, Write};
11use std::path::Path;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14pub trait SerializableModel: Serialize + for<'de> Deserialize<'de> {
16 fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
18 let file = File::create(path)
19 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to create file: {}", e)))?;
20 self.save_to_writer(file)
21 }
22
23 fn save_to_writer<W: Write>(&self, writer: W) -> Result<()> {
25 serde_json::to_writer_pretty(writer, self)
26 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to serialize model: {}", e)))
27 }
28
29 fn save_to_file_compressed<P: AsRef<Path>>(&self, path: P) -> Result<()> {
31 let json_bytes = serde_json::to_vec_pretty(self).map_err(|e| {
33 ClusteringError::InvalidInput(format!("Failed to serialize model: {}", e))
34 })?;
35 let compressed = gzip_compress(&json_bytes, 6)
37 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to compress: {}", e)))?;
38 std::fs::write(path, compressed)
39 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to write file: {}", e)))
40 }
41
42 fn load_from_file_compressed<P: AsRef<Path>>(path: P) -> Result<Self> {
44 let compressed = std::fs::read(path.as_ref())
45 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to read file: {}", e)))?;
46 let decompressed = gzip_decompress(&compressed)
47 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to decompress: {}", e)))?;
48 serde_json::from_slice(&decompressed).map_err(|e| {
49 ClusteringError::InvalidInput(format!("Failed to deserialize model: {}", e))
50 })
51 }
52
53 fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
55 let mut file = File::open(path)
56 .map_err(|e| ClusteringError::InvalidInput(format!("Failed to open file: {}", e)))?;
57 Self::load_from_reader(&mut file)
58 }
59
60 fn load_from_reader<R: Read>(reader: R) -> Result<Self> {
62 serde_json::from_reader(reader).map_err(|e| {
63 ClusteringError::InvalidInput(format!("Failed to deserialize model: {}", e))
64 })
65 }
66}
67
68#[derive(Serialize, Deserialize, Debug, Clone)]
70pub struct EnhancedModelMetadata {
71 pub format_version: String,
73 pub library_version: String,
75 pub created_timestamp: u64,
77 pub algorithm_signature: String,
79 pub training_metrics: TrainingMetrics,
81 pub data_characteristics: DataCharacteristics,
83 pub integrity_hash: String,
85 pub platform_info: PlatformInfo,
87}
88
89#[derive(Serialize, Deserialize, Debug, Clone)]
91pub struct TrainingMetrics {
92 pub training_time_ms: u64,
94 pub iterations: usize,
96 pub final_convergence_metric: f64,
98 pub peak_memory_bytes: usize,
100 pub avg_cpu_utilization: f64,
102}
103
104#[derive(Serialize, Deserialize, Debug, Clone)]
106pub struct DataCharacteristics {
107 pub n_samples: usize,
109 pub n_features: usize,
111 pub data_type_fingerprint: String,
113 pub feature_ranges: Option<Vec<(f64, f64)>>,
115 pub preprocessing_applied: Vec<String>,
117}
118
119#[derive(Serialize, Deserialize, Debug, Clone)]
121pub struct PlatformInfo {
122 pub os: String,
124 pub arch: String,
126 pub rust_version: String,
128 pub cpu_features: Vec<String>,
130}
131
132impl Default for EnhancedModelMetadata {
133 fn default() -> Self {
134 Self {
135 format_version: "1.0.0".to_string(),
136 library_version: env!("CARGO_PKG_VERSION").to_string(),
137 created_timestamp: SystemTime::now()
138 .duration_since(UNIX_EPOCH)
139 .unwrap_or_default()
140 .as_secs(),
141 algorithm_signature: "unknown".to_string(),
142 training_metrics: TrainingMetrics::default(),
143 data_characteristics: DataCharacteristics::default(),
144 integrity_hash: String::new(),
145 platform_info: PlatformInfo::detect(),
146 }
147 }
148}
149
150impl Default for TrainingMetrics {
151 fn default() -> Self {
152 Self {
153 training_time_ms: 0,
154 iterations: 0,
155 final_convergence_metric: 0.0,
156 peak_memory_bytes: 0,
157 avg_cpu_utilization: 0.0,
158 }
159 }
160}
161
162impl Default for DataCharacteristics {
163 fn default() -> Self {
164 Self {
165 n_samples: 0,
166 n_features: 0,
167 data_type_fingerprint: "unknown".to_string(),
168 feature_ranges: None,
169 preprocessing_applied: Vec::new(),
170 }
171 }
172}
173
174impl PlatformInfo {
175 pub fn detect() -> Self {
177 Self {
178 os: std::env::consts::OS.to_string(),
179 arch: std::env::consts::ARCH.to_string(),
180 rust_version: option_env!("CARGO_PKG_RUST_VERSION")
181 .filter(|s| !s.is_empty())
182 .unwrap_or("unknown")
183 .to_string(),
184 cpu_features: Self::detect_cpu_features(),
185 }
186 }
187
188 fn detect_cpu_features() -> Vec<String> {
190 let mut features = Vec::new();
191
192 #[cfg(target_arch = "x86_64")]
193 {
194 if std::arch::is_x86_feature_detected!("avx2") {
195 features.push("avx2".to_string());
196 }
197 if std::arch::is_x86_feature_detected!("sse4.1") {
198 features.push("sse4.1".to_string());
199 }
200 if std::arch::is_x86_feature_detected!("fma") {
201 features.push("fma".to_string());
202 }
203 }
204
205 #[cfg(target_arch = "aarch64")]
206 {
207 if std::arch::is_aarch64_feature_detected!("neon") {
208 features.push("neon".to_string());
209 }
210 }
211
212 features
213 }
214}
215
216#[derive(Serialize, Debug, Clone)]
218pub struct EnhancedModel<T: SerializableModel> {
219 pub model: T,
221 pub metadata: EnhancedModelMetadata,
223}
224
225impl<'de, T: SerializableModel> Deserialize<'de> for EnhancedModel<T> {
226 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
227 where
228 D: serde::Deserializer<'de>,
229 {
230 #[derive(Deserialize)]
231 struct EnhancedModelHelper<U> {
232 model: U,
233 metadata: EnhancedModelMetadata,
234 }
235
236 let helper = EnhancedModelHelper::deserialize(deserializer)?;
237 Ok(EnhancedModel {
238 model: helper.model,
239 metadata: helper.metadata,
240 })
241 }
242}
243
244impl<T: SerializableModel> EnhancedModel<T> {
245 pub fn new(model: T, metadata: EnhancedModelMetadata) -> Self {
247 Self { model, metadata }
248 }
249
250 pub fn with_auto_metadata(model: T, algorithm_name: &str) -> Self {
252 let mut metadata = EnhancedModelMetadata::default();
253 metadata.algorithm_signature = algorithm_name.to_string();
254 Self { model, metadata }
255 }
256
257 pub fn validate_integrity(&self) -> Result<bool> {
259 Ok(!self.metadata.integrity_hash.is_empty())
261 }
262
263 pub fn format_version(&self) -> &str {
265 &self.metadata.format_version
266 }
267
268 pub fn is_compatible(&self) -> bool {
270 let model_version = &self.metadata.library_version;
272 let current_version = env!("CARGO_PKG_VERSION");
273
274 let model_major = model_version.split('.').next().unwrap_or("0");
275 let current_major = current_version.split('.').next().unwrap_or("0");
276
277 model_major == current_major
278 }
279
280 pub fn training_duration_seconds(&self) -> f64 {
282 self.metadata.training_metrics.training_time_ms as f64 / 1000.0
283 }
284
285 pub fn peak_memory_mb(&self) -> f64 {
287 self.metadata.training_metrics.peak_memory_bytes as f64 / (1024.0 * 1024.0)
288 }
289}
290
291impl<T: SerializableModel> SerializableModel for EnhancedModel<T> {}
292
293pub fn format_timestamp(timestamp: u64) -> String {
295 match SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(timestamp)) {
296 Some(_datetime) => {
297 let years_since_1970 = timestamp / (365 * 24 * 3600); let year = 1970 + years_since_1970;
301 format!("Timestamp: {} (approx year {})", timestamp, year)
302 }
303 None => "Invalid timestamp".to_string(),
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 #[derive(Serialize, Deserialize, Debug, Clone)]
312 struct TestModel {
313 value: i32,
314 }
315
316 impl SerializableModel for TestModel {}
317
318 #[test]
319 fn test_enhanced_model_creation() {
320 let model = TestModel { value: 42 };
321 let enhanced = EnhancedModel::with_auto_metadata(model, "test_algorithm");
322
323 assert_eq!(enhanced.metadata.algorithm_signature, "test_algorithm");
324 assert_eq!(enhanced.model.value, 42);
325 }
326
327 #[test]
328 fn test_platform_info_detection() {
329 let platform = PlatformInfo::detect();
330 assert!(!platform.os.is_empty());
331 assert!(!platform.arch.is_empty());
332 }
333
334 #[test]
335 fn test_format_timestamp() {
336 let timestamp = 1640995200; let formatted = format_timestamp(timestamp);
338 assert!(formatted.contains("2022"));
339 }
340}