1use crate::error::{DDAError, Result};
2
3pub fn parse_dda_output(content: &str, column_stride: Option<usize>) -> Result<Vec<Vec<f64>>> {
14 let stride = column_stride.unwrap_or(4);
15 let mut matrix: Vec<Vec<f64>> = Vec::new();
16
17 for line in content.lines() {
19 if line.trim().is_empty() || line.trim().starts_with('#') {
21 continue;
22 }
23
24 let values: Vec<f64> = line
26 .split_whitespace()
27 .filter_map(|s| s.parse::<f64>().ok())
28 .filter(|v| v.is_finite())
29 .collect();
30
31 if !values.is_empty() {
32 matrix.push(values);
33 }
34 }
35
36 if matrix.is_empty() {
37 return Err(DDAError::ParseError(
38 "No valid data found in DDA output".to_string(),
39 ));
40 }
41
42 log::info!(
43 "Loaded DDA output shape: {} rows × {} columns",
44 matrix.len(),
45 matrix[0].len()
46 );
47
48 if !matrix.is_empty() && matrix[0].len() >= 10 {
50 log::debug!(
51 "First row sample (first 10 values): {:?}",
52 &matrix[0][0..10]
53 );
54 }
55
56 if matrix[0].len() > 2 {
60 let mut after_skip: Vec<Vec<f64>> = Vec::new();
62 for row in &matrix {
63 let skipped: Vec<f64> = row.iter().skip(2).copied().collect();
64 after_skip.push(skipped);
65 }
66
67 log::debug!(
68 "After skipping first 2 columns: {} rows × {} columns",
69 after_skip.len(),
70 after_skip[0].len()
71 );
72
73 if !after_skip.is_empty() && after_skip[0].len() >= 10 {
75 log::debug!(
76 "After skip, first row (first 10 values): {:?}",
77 &after_skip[0][0..10]
78 );
79 }
80
81 let mut extracted: Vec<Vec<f64>> = Vec::new();
85
86 for row in &after_skip {
87 let mut row_values = Vec::new();
88 let mut col_idx = 0; while col_idx < row.len() {
90 row_values.push(row[col_idx]);
91 col_idx += stride;
92 }
93 extracted.push(row_values);
94 }
95
96 if !extracted.is_empty() && extracted[0].len() >= 5 {
98 log::debug!(
99 "First extracted row sample (first 5 values): {:?}",
100 &extracted[0][0..5]
101 );
102 }
103
104 if extracted.is_empty() || extracted[0].is_empty() {
105 return Err(DDAError::ParseError(
106 "No data after column extraction".to_string(),
107 ));
108 }
109
110 let num_rows = extracted.len();
111 let num_cols = extracted[0].len();
112
113 log::info!(
114 "Extracted matrix shape: {} rows × {} columns (time windows × delays/scales)",
115 num_rows,
116 num_cols
117 );
118
119 let mut transposed: Vec<Vec<f64>> = vec![Vec::new(); num_cols];
122
123 for (row_idx, row) in extracted.iter().enumerate() {
124 if row.len() != num_cols {
125 log::warn!(
126 "Row {} has {} columns, expected {}. Skipping this row.",
127 row_idx,
128 row.len(),
129 num_cols
130 );
131 continue;
132 }
133 for (col_idx, &value) in row.iter().enumerate() {
134 transposed[col_idx].push(value);
135 }
136 }
137
138 if transposed.is_empty() || transposed[0].is_empty() {
139 return Err(DDAError::ParseError(
140 "Transpose resulted in empty data".to_string(),
141 ));
142 }
143
144 log::info!(
145 "Transposed to: {} channels × {} timepoints",
146 transposed.len(),
147 transposed[0].len()
148 );
149
150 Ok(transposed)
151 } else {
152 Ok(vec![matrix.into_iter().flatten().collect()])
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_parse_dda_output_basic() {
163 let content = "# Comment line\n\
164 1.0 2.0 3.0 4.0 5.0 6.0\n\
165 7.0 8.0 9.0 10.0 11.0 12.0\n";
166
167 let result = parse_dda_output(content, None).unwrap();
168 assert!(!result.is_empty());
169 }
170
171 #[test]
172 fn test_parse_empty_content() {
173 let content = "# Only comments\n# More comments\n";
174 let result = parse_dda_output(content, None);
175 assert!(result.is_err());
176 }
177}