quantrs2_tytan/
auto_array.rs

1//! Automatic conversion of sample results to multi-dimensional arrays.
2//!
3//! This module provides utilities for converting sample results from
4//! quantum annealing to multi-dimensional arrays, which are easier to
5//! manipulate and visualize.
6
7// We don't use these imports directly in the non-dwave version
8#[cfg(feature = "dwave")]
9use quantrs2_symengine::Expression as SymEngineExpression;
10#[cfg(feature = "dwave")]
11use regex::Regex;
12#[cfg(feature = "dwave")]
13use scirs2_core::ndarray::{Array, ArrayD, IxDyn};
14#[cfg(feature = "dwave")]
15use std::collections::HashMap;
16use thiserror::Error;
17
18#[cfg(feature = "dwave")]
19use crate::sampler::SampleResult;
20
21/// Errors that can occur during array conversion
22#[derive(Error, Debug)]
23pub enum AutoArrayError {
24    /// Error when the format string is invalid
25    #[error("Invalid format string: {0}")]
26    InvalidFormat(String),
27
28    /// Error when the dimension is unsupported
29    #[error("Unsupported dimension: {0}")]
30    UnsupportedDimension(usize),
31
32    /// Error when parsing indices
33    #[error("Failed to parse indices: {0}")]
34    ParseError(String),
35}
36
37/// Result type for array conversion operations
38pub type AutoArrayResult<T> = Result<T, AutoArrayError>;
39
40/// Automatic converter for quantum annealing results
41///
42/// This struct provides methods for converting SampleResult objects
43/// into multi-dimensional arrays, which are easier to manipulate and visualize.
44#[cfg(feature = "dwave")]
45pub struct AutoArray<'a> {
46    /// The sample result to convert
47    result: &'a SampleResult,
48}
49
50#[cfg(feature = "dwave")]
51impl<'a> AutoArray<'a> {
52    /// Create a new automatic array converter
53    ///
54    /// # Arguments
55    ///
56    /// * `result` - The sample result to convert
57    pub const fn new(result: &'a SampleResult) -> Self {
58        Self { result }
59    }
60
61    /// Convert to an n-dimensional array
62    ///
63    /// This method converts the sample result to an n-dimensional array
64    /// based on the specified format string.
65    ///
66    /// # Arguments
67    ///
68    /// * `format` - The format string with {} placeholders for indices
69    ///
70    /// # Returns
71    ///
72    /// A tuple containing:
73    /// - The n-dimensional array of values
74    /// - A vector of indices for each dimension
75    pub fn get_ndarray(&self, format: &str) -> AutoArrayResult<(ArrayD<i32>, Vec<Vec<String>>)> {
76        // Count the number of dimensions from format placeholders
77        let dim_count = format.matches("{}").count();
78
79        if dim_count == 0 {
80            return Err(AutoArrayError::InvalidFormat(
81                "Format string must contain at least one {} placeholder".to_string(),
82            ));
83        }
84
85        if dim_count > 5 {
86            return Err(AutoArrayError::UnsupportedDimension(dim_count));
87        }
88
89        // Create a regex to extract indices
90        let re_str = format.replace("{}", "(\\d+|\\w+)");
91        #[cfg(feature = "dwave")]
92        let re = Regex::new(&re_str)
93            .map_err(|e| AutoArrayError::InvalidFormat(format!("Invalid regex: {e}")))?;
94
95        // Extract all indices from variable names
96        let mut indices_by_dim: Vec<Vec<String>> = vec![Vec::new(); dim_count];
97
98        for var_name in self.result.assignments.keys() {
99            if let Some(captures) = re.captures(var_name) {
100                if captures.len() > 1 {
101                    for i in 1..=dim_count {
102                        if let Some(m) = captures.get(i) {
103                            indices_by_dim[i - 1].push(m.as_str().to_string());
104                        }
105                    }
106                }
107            }
108        }
109
110        // Deduplicate and sort indices naturally (1, 2, 10 instead of 1, 10, 2)
111        for dim_indices in &mut indices_by_dim {
112            // Try to parse as numbers for natural sorting
113            dim_indices.sort_by(|a, b| {
114                match (a.parse::<i32>(), b.parse::<i32>()) {
115                    (Ok(na), Ok(nb)) => na.cmp(&nb),
116                    _ => a.cmp(b), // Fall back to lexicographic for non-numeric
117                }
118            });
119            dim_indices.dedup();
120        }
121
122        // Determine array shape
123        let shape: Vec<usize> = indices_by_dim.iter().map(|indices| indices.len()).collect();
124        let shape_dim = IxDyn(&shape);
125
126        // Create array filled with -1 (representing missing values)
127        let mut array = Array::from_elem(shape_dim, -1);
128
129        // Fill the array with values from the result
130        for (var_name, &value) in &self.result.assignments {
131            if let Some(captures) = re.captures(var_name) {
132                if captures.len() > 1 {
133                    // Extract indices
134                    let mut index_values = Vec::new();
135                    for i in 1..=dim_count {
136                        if let Some(m) = captures.get(i) {
137                            let idx_str = m.as_str();
138                            let dim_indices = &indices_by_dim[i - 1];
139                            if let Some(pos) = dim_indices.iter().position(|x| x == idx_str) {
140                                index_values.push(pos);
141                            }
142                        }
143                    }
144
145                    // Set array value
146                    if index_values.len() == dim_count {
147                        let mut idx = IxDyn(&index_values);
148                        array[idx] = i32::from(value);
149                    }
150                }
151            }
152        }
153
154        Ok((array, indices_by_dim))
155    }
156
157    /// Convert to a pandas-like DataFrame
158    ///
159    /// This method converts the sample result to a 2D array
160    /// that can be easily displayed as a table.
161    ///
162    /// # Arguments
163    ///
164    /// * `format` - The format string with {} placeholders for indices
165    ///
166    /// # Returns
167    ///
168    /// A tuple containing:
169    /// - The 2D array of values
170    /// - A vector of indices for each dimension
171    pub fn get_dframe(
172        &self,
173        format: &str,
174    ) -> AutoArrayResult<(Array<i32, scirs2_core::ndarray::Ix2>, Vec<Vec<String>>)> {
175        // Count the number of dimensions from format placeholders
176        let dim_count = format.matches("{}").count();
177
178        if dim_count == 0 || dim_count > 2 {
179            return Err(AutoArrayError::UnsupportedDimension(dim_count));
180        }
181
182        // Get the n-dimensional array
183        let (nd_array, indices) = self.get_ndarray(format)?;
184
185        // If 1D, convert to 2D
186        if dim_count == 1 {
187            let shape = nd_array.shape();
188            let mut array = Array::zeros((1, shape[0]));
189            for i in 0..shape[0] {
190                array[[0, i]] = nd_array[IxDyn(&[i])];
191            }
192            Ok((array, indices))
193        } else {
194            // If 2D, convert to Array2
195            let shape = nd_array.shape();
196            let mut array = Array::zeros((shape[0], shape[1]));
197            for i in 0..shape[0] {
198                for j in 0..shape[1] {
199                    array[[i, j]] = nd_array[IxDyn(&[i, j])];
200                }
201            }
202            Ok((array, indices))
203        }
204    }
205
206    /// Convert to an image
207    ///
208    /// This method converts the sample result to a 2D array
209    /// that can be displayed as an image.
210    ///
211    /// # Arguments
212    ///
213    /// * `format` - The format string with {} placeholders for indices
214    ///
215    /// # Returns
216    ///
217    /// A tuple containing:
218    /// - The 2D array of values (0 or 255)
219    /// - A vector of indices for each dimension
220    pub fn get_image(
221        &self,
222        format: &str,
223    ) -> AutoArrayResult<(Array<u8, scirs2_core::ndarray::Ix2>, Vec<Vec<String>>)> {
224        // Count the number of dimensions from format placeholders
225        let dim_count = format.matches("{}").count();
226
227        if dim_count != 2 {
228            return Err(AutoArrayError::UnsupportedDimension(dim_count));
229        }
230
231        // Get the 2D array
232        let (array, indices) = self.get_dframe(format)?;
233
234        // Convert to u8 image (0 or 255)
235        let mut image = Array::zeros(array.dim());
236        for i in 0..array.shape()[0] {
237            for j in 0..array.shape()[1] {
238                image[[i, j]] = if array[[i, j]] > 0 { 255 } else { 0 };
239            }
240        }
241
242        Ok((image, indices))
243    }
244
245    /// Get the value of an n-bit encoded variable
246    ///
247    /// This method calculates the value of an n-bit encoded variable
248    /// from the sample result.
249    ///
250    /// # Arguments
251    ///
252    /// * `expr` - The symbolic expression representing the n-bit variable
253    ///
254    /// # Returns
255    ///
256    /// The calculated value of the n-bit variable
257    #[cfg(feature = "dwave")]
258    pub const fn get_nbit_value(&self, expr: &SymEngineExpression) -> AutoArrayResult<f64> {
259        // TODO: Implement n-bit value calculation
260        // This will require evaluating the symbolic expression with
261        // the sample values substituted in.
262
263        // For now, return a placeholder
264        Ok(0.0)
265    }
266}