quantrs2_tytan/sampler/hardware/
hitachi.rs1use crate::sampler::{SampleResult, Sampler, SamplerError, SamplerResult};
7use scirs2_core::ndarray::Array2;
8use std::cell::RefCell;
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
13pub struct HitachiConfig {
14 pub endpoint: String,
16 pub auth_token: String,
18 pub annealing_params: AnnealingParameters,
20 pub hardware_version: HardwareVersion,
22}
23
24#[derive(Debug, Clone)]
25pub struct AnnealingParameters {
26 pub num_steps: u32,
28 pub initial_config: InitialConfig,
30 pub magnetic_field: f64,
32 pub temperature_coefficient: f64,
34 pub convergence_threshold: f64,
36}
37
38#[derive(Debug, Clone)]
39pub enum InitialConfig {
40 Random,
42 AllUp,
44 AllDown,
46 Custom(Vec<i8>),
48}
49
50#[derive(Debug, Clone)]
51pub enum HardwareVersion {
52 Gen4 { king_graph_size: usize },
54 Gen5 {
56 king_graph_size: usize,
57 long_range_connections: bool,
58 },
59}
60
61impl Default for HitachiConfig {
62 fn default() -> Self {
63 Self {
64 endpoint: "https://annealing.hitachi.com/api/v1".to_string(),
65 auth_token: String::new(),
66 annealing_params: AnnealingParameters {
67 num_steps: 100_000,
68 initial_config: InitialConfig::Random,
69 magnetic_field: 0.0,
70 temperature_coefficient: 1.0,
71 convergence_threshold: 1e-6,
72 },
73 hardware_version: HardwareVersion::Gen4 {
74 king_graph_size: 512,
75 },
76 }
77 }
78}
79
80pub struct HitachiCMOSSampler {
82 config: HitachiConfig,
83 embedding_cache: RefCell<HashMap<String, KingGraphEmbedding>>,
85}
86
87#[derive(Debug, Clone)]
89struct KingGraphEmbedding {
90 logical_to_physical: HashMap<usize, Vec<usize>>,
92 chain_strengths: Vec<f64>,
94 quality_score: f64,
96}
97
98impl HitachiCMOSSampler {
99 pub fn new(config: HitachiConfig) -> Self {
101 Self {
102 config,
103 embedding_cache: RefCell::new(HashMap::new()),
104 }
105 }
106
107 fn find_embedding(&self, qubo: &Array2<f64>) -> Result<KingGraphEmbedding, SamplerError> {
109 let n = qubo.shape()[0];
110
111 let cache_key = format!("embed_{}_{}", n, qubo.sum());
113 if let Some(embedding) = self.embedding_cache.borrow().get(&cache_key) {
114 return Ok(embedding.clone());
115 }
116
117 let embedding = self.create_king_graph_embedding(qubo)?;
119
120 self.embedding_cache
122 .borrow_mut()
123 .insert(cache_key, embedding.clone());
124
125 Ok(embedding)
126 }
127
128 fn create_king_graph_embedding(
130 &self,
131 qubo: &Array2<f64>,
132 ) -> Result<KingGraphEmbedding, SamplerError> {
133 let n = qubo.shape()[0];
134 let king_size = match &self.config.hardware_version {
135 HardwareVersion::Gen4 { king_graph_size } => *king_graph_size,
136 HardwareVersion::Gen5 {
137 king_graph_size, ..
138 } => *king_graph_size,
139 };
140
141 if n > king_size {
142 return Err(SamplerError::InvalidModel(format!(
143 "Problem size {n} exceeds hardware limit {king_size}"
144 )));
145 }
146
147 let mut logical_to_physical = HashMap::new();
149 for i in 0..n {
150 logical_to_physical.insert(i, vec![i]);
151 }
152
153 Ok(KingGraphEmbedding {
154 logical_to_physical,
155 chain_strengths: vec![1.0; n],
156 quality_score: 1.0,
157 })
158 }
159
160 fn submit_job(&self, _embedded_qubo: &Array2<f64>) -> Result<String, SamplerError> {
162 Ok("hitachi_job_123".to_string())
164 }
165
166 fn get_job_results(&self, _job_id: &str) -> Result<Vec<CMOSResult>, SamplerError> {
168 Ok(vec![CMOSResult {
170 spins: vec![1; 512],
171 energy: -50.0,
172 converged: true,
173 iterations: 50000,
174 }])
175 }
176
177 fn unembed_solution(
179 &self,
180 cmos_result: &CMOSResult,
181 embedding: &KingGraphEmbedding,
182 var_map: &HashMap<String, usize>,
183 ) -> SampleResult {
184 let mut assignments = HashMap::new();
185
186 for (var_name, &logical_idx) in var_map {
188 if let Some(physical_qubits) = embedding.logical_to_physical.get(&logical_idx) {
189 let spin_sum: i32 = physical_qubits
191 .iter()
192 .map(|&p| cmos_result.spins[p] as i32)
193 .sum();
194
195 let value = spin_sum > 0;
196 assignments.insert(var_name.clone(), value);
197 }
198 }
199
200 SampleResult {
201 assignments,
202 energy: cmos_result.energy,
203 occurrences: 1,
204 }
205 }
206}
207
208#[derive(Debug, Clone)]
209struct CMOSResult {
210 spins: Vec<i8>,
212 energy: f64,
214 converged: bool,
216 iterations: u32,
218}
219
220impl Sampler for HitachiCMOSSampler {
221 fn run_qubo(
222 &self,
223 model: &(Array2<f64>, HashMap<String, usize>),
224 shots: usize,
225 ) -> SamplerResult<Vec<SampleResult>> {
226 let (qubo, var_map) = model;
227
228 let embedding = self.find_embedding(qubo)?;
230
231 let king_size = match &self.config.hardware_version {
233 HardwareVersion::Gen4 { king_graph_size } => *king_graph_size,
234 HardwareVersion::Gen5 {
235 king_graph_size, ..
236 } => *king_graph_size,
237 };
238
239 let mut embedded_qubo = Array2::zeros((king_size, king_size));
240
241 for i in 0..qubo.shape()[0] {
243 for j in 0..qubo.shape()[1] {
244 if let (Some(phys_i), Some(phys_j)) = (
245 embedding.logical_to_physical.get(&i),
246 embedding.logical_to_physical.get(&j),
247 ) {
248 embedded_qubo[[phys_i[0], phys_j[0]]] = qubo[[i, j]];
250 }
251 }
252 }
253
254 for (logical_idx, physical_chain) in &embedding.logical_to_physical {
256 for i in 1..physical_chain.len() {
257 let strength = embedding.chain_strengths[*logical_idx];
258 embedded_qubo[[physical_chain[i - 1], physical_chain[i]]] = -strength;
259 embedded_qubo[[physical_chain[i], physical_chain[i - 1]]] = -strength;
260 }
261 }
262
263 let mut all_results = Vec::new();
265 let jobs_needed = shots.div_ceil(100); for _ in 0..jobs_needed {
268 let job_id = self.submit_job(&embedded_qubo)?;
269 let cmos_results = self.get_job_results(&job_id)?;
270
271 for cmos_result in cmos_results {
272 let sample = self.unembed_solution(&cmos_result, &embedding, var_map);
273 all_results.push(sample);
274 }
275 }
276
277 all_results.sort_by(|a, b| {
279 a.energy
280 .partial_cmp(&b.energy)
281 .unwrap_or(std::cmp::Ordering::Equal)
282 });
283 all_results.truncate(shots);
284
285 Ok(all_results)
286 }
287
288 fn run_hobo(
289 &self,
290 _hobo: &(scirs2_core::ndarray::ArrayD<f64>, HashMap<String, usize>),
291 _shots: usize,
292 ) -> SamplerResult<Vec<SampleResult>> {
293 Err(SamplerError::NotImplemented(
294 "HOBO not supported by Hitachi hardware".to_string(),
295 ))
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_hitachi_config() {
305 let mut config = HitachiConfig::default();
306 assert_eq!(config.annealing_params.num_steps, 100_000);
307
308 match config.hardware_version {
309 HardwareVersion::Gen4 { king_graph_size } => {
310 assert_eq!(king_graph_size, 512);
311 }
312 _ => panic!("Wrong hardware version"),
313 }
314 }
315
316 #[test]
317 fn test_embedding_cache() {
318 let sampler = HitachiCMOSSampler::new(HitachiConfig::default());
319 let qubo = Array2::eye(4);
320
321 let embedding1 = sampler
322 .find_embedding(&qubo)
323 .expect("Failed to find embedding for first call");
324 let embedding2 = sampler
325 .find_embedding(&qubo)
326 .expect("Failed to find embedding for second call");
327
328 assert_eq!(embedding1.quality_score, embedding2.quality_score);
330 }
331}