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