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}