1use crate::{
6 circuit::Circuit,
7 error::{KetError, Result},
8 execution::{BatchExecution, ExpValueStrategy, Gradient, QuantumExecution, ResultData},
9 ir::{
10 hamiltonian,
11 qubit::{LogicalQubit, PhysicalQubit, Qubit},
12 },
13 mapping,
14 prelude::{Pauli, PauliTerm, QuantumGate, U2Gates},
15 process::ExecutionStrategy,
16};
17use itertools::Itertools;
18use rand::{
19 distr::{weighted::WeightedIndex, Distribution},
20 rng,
21};
22
23use std::{collections::HashMap, hash::Hash};
24
25use super::{ExecutionProtocol, Process};
26
27fn submit_execution(
28 execution_strategy: &ExecutionStrategy,
29 execution: &mut Box<dyn BatchExecution>,
30 logical_circuit: &Circuit<LogicalQubit>,
31 physical_circuit: Option<&Circuit<PhysicalQubit>>,
32 u2_gates: Option<U2Gates>,
33 parameters: &[f64],
34) -> ResultData {
35 match execution_strategy {
36 ExecutionStrategy::ManagedByTarget => {
37 let circuit = if let Some(physical_circuit) = physical_circuit {
38 let mut circuit = physical_circuit.clone();
39 circuit.gate_map(u2_gates.unwrap(), parameters);
40 circuit.final_instructions()
41 } else {
42 logical_circuit.final_instructions()
43 };
44
45 execution.submit_execution(&circuit, parameters);
46 let result = execution.get_results();
47 execution.clear();
48 result
49 }
50 ExecutionStrategy::DirectSample(shots) => {
51 let mut result = vec![0.0; logical_circuit.exp_value_size()];
52
53 let circuits: Vec<_> = if let Some(physical_circuit) = physical_circuit {
54 direct_sample(physical_circuit, *shots)
55 .drain(..)
56 .map(|(mut c, s, i)| {
57 c.gate_map(u2_gates.unwrap(), parameters);
58 (c.final_instructions(), s, i)
59 })
60 .collect()
61 } else {
62 direct_sample(logical_circuit, *shots)
63 .drain(..)
64 .map(|(c, s, i)| (c.final_instructions(), s, i))
65 .collect()
66 };
67
68 for (circuit, coef, index) in circuits {
69 execution.submit_execution(&circuit, parameters);
70 let data = execution.get_results();
71
72 execution.clear();
73
74 result[index] +=
75 from_sample_to_exp_value(&data.samples[0].0, &data.samples[0].1, *shots) * coef;
76 }
77
78 ResultData {
79 measurements: vec![],
80 exp_values: result,
81 samples: vec![],
82 dumps: vec![],
83 gradients: None,
84 }
85 }
86 ExecutionStrategy::ClassicalShadows {
87 bias,
88 samples,
89 shots,
90 } => {
91 let result: Vec<f64> = if let Some(physical_circuit) = physical_circuit {
92 let hamiltonian = physical_circuit.get_hamiltonian();
93 let qubits = hamiltonian
94 .iter()
95 .flat_map(|h| h.0.qubits().cloned())
96 .unique()
97 .collect_vec();
98
99 let rounds = randomized_classical_shadow(qubits.len(), *samples, bias);
100
101 let mut circuits =
102 classical_shadow_circuits(physical_circuit, &qubits, &rounds, *shots);
103 let measures = circuits
104 .drain(..)
105 .flat_map(|mut c| {
106 c.gate_map(u2_gates.unwrap(), parameters);
107 execution.submit_execution(&c.final_instructions(), parameters);
108 let data = execution.get_results();
109 execution.clear();
110
111 data.samples
112 })
113 .collect::<Vec<_>>();
114
115 physical_circuit
116 .get_hamiltonian()
117 .iter()
118 .map(|(h, index)| {
119 (
120 h.products
121 .iter()
122 .zip(&h.coefficients)
123 .map(|(obs, coef)| {
124 classical_shadow_processing(obs, &measures, *shots, &rounds)
125 * coef
126 })
127 .sum(),
128 *index,
129 )
130 })
131 .sorted_by_key(|(_, index)| *index)
132 .map(|(exp_value, _)| exp_value)
133 .collect()
134 } else {
135 let hamiltonian = logical_circuit.get_hamiltonian();
136 let qubits = hamiltonian
137 .iter()
138 .flat_map(|h| h.0.qubits().cloned())
139 .unique()
140 .collect_vec();
141
142 let rounds = randomized_classical_shadow(qubits.len(), *samples, bias);
143
144 let mut circuits =
145 classical_shadow_circuits(logical_circuit, &qubits, &rounds, *shots);
146 let measures = circuits
147 .drain(..)
148 .flat_map(|c| {
149 execution.submit_execution(&c.final_instructions(), parameters);
150 let data = execution.get_results();
151 execution.clear();
152 data.samples
153 })
154 .collect::<Vec<_>>();
155
156 logical_circuit
157 .get_hamiltonian()
158 .iter()
159 .map(|(h, index)| {
160 (
161 h.products
162 .iter()
163 .zip(&h.coefficients)
164 .map(|(obs, coef)| {
165 classical_shadow_processing(obs, &measures, *shots, &rounds)
166 * coef
167 })
168 .sum(),
169 *index,
170 )
171 })
172 .sorted_by_key(|(_, index)| *index)
173 .map(|(exp_value, _)| exp_value)
174 .collect()
175 };
176
177 ResultData {
178 measurements: vec![],
179 exp_values: result,
180 samples: vec![],
181 dumps: vec![],
182 gradients: None,
183 }
184 }
185 ExecutionStrategy::MeasureFromSample(shots) => {
186 let (data, measures, qubit_map) = if let Some(physical_circuit) = physical_circuit {
187 let measures = physical_circuit.get_measures();
188 let qubits = measures
189 .iter()
190 .flat_map(|(q, _)| q)
191 .cloned()
192 .unique()
193 .collect_vec();
194 let qubit_map: HashMap<_, _> = qubits
195 .iter()
196 .enumerate()
197 .map(|(i, q)| (q.index(), i))
198 .collect();
199 let measures = measures
200 .iter()
201 .map(|(qubits, index)| (qubits.iter().map(Qubit::index).collect_vec(), *index))
202 .collect_vec();
203
204 let mut circuit = physical_circuit.clone();
205 circuit.clear_measure();
206 circuit.sample(&qubits, *shots, 0);
207
208 circuit.gate_map(u2_gates.unwrap(), parameters);
209
210 execution.submit_execution(&circuit.final_instructions(), parameters);
211
212 (execution.get_results(), measures, qubit_map)
213 } else {
214 let measures = logical_circuit.get_measures();
215 let qubits = measures
216 .iter()
217 .flat_map(|(q, _)| q)
218 .cloned()
219 .unique()
220 .collect_vec();
221 let qubit_map: HashMap<_, _> = qubits
222 .iter()
223 .enumerate()
224 .map(|(i, q)| (q.index(), i))
225 .collect();
226 let measures = measures
227 .iter()
228 .map(|(qubits, index)| (qubits.iter().map(Qubit::index).collect_vec(), *index))
229 .collect_vec();
230
231 let mut circuit = logical_circuit.clone();
232 circuit.clear_measure();
233 circuit.sample(&qubits, *shots, 0);
234
235 execution.submit_execution(&circuit.final_instructions(), parameters);
236
237 (execution.get_results(), measures, qubit_map)
238 };
239
240 let data = &data.samples[0];
241 let data = *data
242 .0
243 .iter()
244 .zip(&data.1)
245 .max_by_key(|(_, count)| *count)
246 .unwrap()
247 .0;
248
249 let results: Vec<_> = measures
250 .iter()
251 .map(|(qs, index)| {
252 (
253 qs.iter()
254 .enumerate()
255 .map(|(i, q)| {
256 let data_index = qubit_map[q];
257 let bit = (data >> data_index) & 1;
258 bit << i
259 })
260 .reduce(|a, b| a | b)
261 .unwrap_or(0),
262 index,
263 )
264 })
265 .sorted_by_key(|(_, i)| *i)
266 .map(|(r, _)| r)
267 .collect();
268
269 ResultData {
270 measurements: results,
271 exp_values: vec![],
272 samples: vec![],
273 dumps: vec![],
274 gradients: None,
275 }
276 }
277 }
278}
279
280type CSRound = Vec<Pauli>;
281
282fn randomized_classical_shadow(
283 number_of_qubits: usize,
284 samples: usize,
285 bias: &(u8, u8, u8),
286) -> Vec<CSRound> {
287 let mut rng = rng();
288 let dist = WeightedIndex::new([bias.0, bias.1, bias.2]).unwrap();
289 (0..samples)
290 .map(|_| {
291 (0..number_of_qubits)
292 .map(|_| match dist.sample(&mut rng) {
293 0 => Pauli::PauliX,
294 1 => Pauli::PauliY,
295 2 => Pauli::PauliZ,
296 _ => unreachable!(),
297 })
298 .collect::<CSRound>()
299 })
300 .collect()
301}
302
303fn classical_shadow_circuits<Q>(
304 circuit: &Circuit<Q>,
305 qubits: &[Q],
306 rounds: &[CSRound],
307 shots: usize,
308) -> Vec<Circuit<Q>>
309where
310 Q: Qubit + Eq + Hash + Copy + From<usize> + Sync,
311{
312 rounds
313 .iter()
314 .map(|round| {
315 let mut circuit = circuit.clone();
316 circuit.clear_exp_value();
317 for (i, measure) in round.iter().enumerate() {
318 match measure {
319 Pauli::PauliX => {
320 circuit.gate(QuantumGate::Hadamard, qubits[i], &[]);
321 }
322 Pauli::PauliY => {
323 circuit.gate(QuantumGate::sd(), qubits[i], &[]);
324 circuit.gate(QuantumGate::Hadamard, qubits[i], &[]);
325 }
326 Pauli::PauliZ => {}
327 }
328 }
329 circuit.sample(qubits, shots, 0);
330 circuit
331 })
332 .collect()
333}
334
335fn classical_shadow_processing<Q: Qubit>(
336 obs: &[PauliTerm<Q>],
337 measures: &[(Vec<u64>, Vec<u64>)],
338 shots: usize,
339 rounds: &[CSRound],
340) -> f64 {
341 let matching_measures: Vec<_> = rounds
342 .iter()
343 .enumerate()
344 .filter_map(|(index, round)| {
345 if obs
346 .iter()
347 .all(|term| round[term.qubit.index()] == term.pauli)
348 {
349 Some(index)
350 } else {
351 None
352 }
353 })
354 .collect();
355
356 matching_measures
357 .iter()
358 .map(|index| &measures[*index])
359 .flat_map(|(states, counts)| {
360 states.iter().zip(counts.iter()).map(|(state, count)| {
361 obs.iter()
362 .map(|p| from_u64_to_exp_value(*state, p.qubit.index()))
363 .product::<i32>() as f64
364 * *count as f64
365 })
366 })
367 .sum::<f64>()
368 / (matching_measures.len() * shots) as f64
369}
370
371fn from_u64_to_exp_value(state: u64, qubit: usize) -> i32 {
372 if state & (1 << qubit) == 0 {
373 1
374 } else {
375 -1
376 }
377}
378
379fn from_sample_to_exp_value(state: &[u64], count: &[u64], shots: usize) -> f64 {
380 state
381 .iter()
382 .zip(count)
383 .map(|(state, count)| {
384 (if state.count_ones() % 2 == 0 {
385 1.0
386 } else {
387 -1.0
388 }) * (*count as f64 / shots as f64)
389 })
390 .sum::<f64>()
391}
392
393fn direct_sample<Q>(circuit: &Circuit<Q>, shots: usize) -> Vec<(Circuit<Q>, f64, usize)>
394where
395 Q: Qubit + Eq + Hash + Copy + From<usize> + Sync,
396{
397 let hamiltonian = circuit.get_hamiltonian();
398
399 let mut circuits = Vec::new();
400 for (h, index) in hamiltonian.iter() {
401 for (p, c) in h.products.iter().zip(&h.coefficients) {
402 let mut new_circuit = circuit.clone();
403 new_circuit.clear_exp_value();
404
405 let mut qubits = Vec::new();
406 assert!(!p.is_empty());
407
408 for term in p {
409 match term.pauli {
410 hamiltonian::Pauli::PauliX => {
411 new_circuit.gate(QuantumGate::Hadamard, term.qubit, &[]);
412 }
413 hamiltonian::Pauli::PauliY => {
414 new_circuit.gate(QuantumGate::sd(), term.qubit, &[]);
415 new_circuit.gate(QuantumGate::Hadamard, term.qubit, &[]);
416 }
417 hamiltonian::Pauli::PauliZ => {}
418 }
419 qubits.push(term.qubit);
420 }
421 assert!(!qubits.is_empty());
422 new_circuit.sample(&qubits, shots, 0);
423 circuits.push((new_circuit, *c, *index));
424 }
425 }
426
427 circuits
428}
429
430impl Process {
431 fn map_circuit(&mut self) {
432 if let Some(coupling_graph) = self.coupling_graph.as_ref() {
433 let mapping = mapping::allocation::initial(
434 &self.logical_circuit.interaction_graph(),
435 coupling_graph,
436 );
437 let physical_circuit = mapping::map_circuit(
438 mapping,
439 coupling_graph,
440 &self.logical_circuit,
441 self.execution_target.qpu.as_ref().unwrap().u4_gate,
442 4,
443 );
444 self.physical_circuit = Some(physical_circuit);
445 }
446 }
447
448 fn parameter_shift_rule(&mut self, execution: &mut Box<dyn BatchExecution>) {
449 (0..self.parameters.len())
450 .map(|index| {
451 let mut parameters = self.parameters.clone();
452 parameters[index] += std::f64::consts::FRAC_PI_2;
453
454 let results = submit_execution(
455 self.execution_strategy.as_ref().unwrap(),
456 execution,
457 &self.logical_circuit,
458 self.physical_circuit.as_ref(),
459 self.execution_target.qpu.as_ref().map(|qpu| qpu.u2_gates),
460 ¶meters,
461 );
462
463 let e_plus = results.exp_values[0];
464
465 parameters[index] = self.parameters[index] - std::f64::consts::FRAC_PI_2;
466 let results = submit_execution(
467 self.execution_strategy.as_ref().unwrap(),
468 execution,
469 &self.logical_circuit,
470 self.physical_circuit.as_ref(),
471 self.execution_target.qpu.as_ref().map(|qpu| qpu.u2_gates),
472 ¶meters,
473 );
474
475 let e_minus = results.exp_values[0];
476
477 (e_plus - e_minus) / 2.0
478 })
479 .zip(self.gradients.iter_mut())
480 .for_each(|(result, gradient)| {
481 *gradient = Some(result);
482 });
483 }
484
485 pub fn prepare_for_execution(&mut self) -> Result<()> {
486 if self.execution_strategy.is_none() {
487 self.execution_strategy = Some(match &self.execution_target.execution_protocol {
488 ExecutionProtocol::ManagedByTarget { .. } => ExecutionStrategy::ManagedByTarget,
489 ExecutionProtocol::SampleBased(ExpValueStrategy::ClassicalShadows {
490 bias,
491 samples,
492 shots,
493 }) => {
494 if !self.measurements.is_empty() {
495 ExecutionStrategy::MeasureFromSample(*samples * *shots)
496 } else if !self.samples.is_empty() {
497 ExecutionStrategy::ManagedByTarget
498 } else {
499 ExecutionStrategy::ClassicalShadows {
500 bias: *bias,
501 samples: *samples,
502 shots: *shots,
503 }
504 }
505 }
506 ExecutionProtocol::SampleBased(ExpValueStrategy::DirectSample(shots)) => {
507 if !self.measurements.is_empty() {
508 ExecutionStrategy::MeasureFromSample(*shots)
509 } else if !self.samples.is_empty() {
510 ExecutionStrategy::ManagedByTarget
511 } else {
512 ExecutionStrategy::DirectSample(*shots)
513 }
514 }
515 });
516
517 self.map_circuit();
518 }
519 Ok(())
520 }
521
522 pub fn execute(&mut self) -> Result<()> {
523 self.prepare_for_execution()?;
524
525 let mut results = None;
526
527 if let Some(QuantumExecution::Batch(mut execution)) = self.quantum_execution.take() {
528 results = Some(submit_execution(
529 self.execution_strategy.as_ref().unwrap(),
530 &mut execution,
531 &self.logical_circuit,
532 self.physical_circuit.as_ref(),
533 self.execution_target.qpu.as_ref().map(|qpu| qpu.u2_gates),
534 &self.parameters,
535 ));
536
537 if !self.parameters.is_empty()
538 && matches!(
539 self.execution_target.gradient,
540 Some(Gradient::ParameterShift)
541 )
542 {
543 self.parameter_shift_rule(&mut execution);
544 }
545 }
546
547 if let Some(mut results) = results {
548 if self.measurements.len() != results.measurements.len()
549 || self.exp_values.len() != results.exp_values.len()
550 || self.samples.len() != results.samples.len()
551 || self.dumps.len() != results.dumps.len()
552 || (!self.parameters.is_empty()
553 && matches!(
554 self.execution_target.gradient,
555 Some(Gradient::NativeSupport)
556 )
557 && results
558 .gradients
559 .as_ref()
560 .is_none_or(|gradients| self.gradients.len() != gradients.len()))
561 {
562 return Err(KetError::ResultDataMismatch);
563 }
564
565 results
566 .measurements
567 .drain(..)
568 .zip(self.measurements.iter_mut())
569 .for_each(|(result, measurement)| {
570 *measurement = Some(result);
571 });
572
573 results
574 .exp_values
575 .drain(..)
576 .zip(self.exp_values.iter_mut())
577 .for_each(|(result, exp_value)| {
578 *exp_value = Some(result);
579 });
580
581 results
582 .samples
583 .drain(..)
584 .zip(self.samples.iter_mut())
585 .for_each(|(result, sample)| {
586 assert_eq!(result.0.len(), result.1.len());
587 *sample = Some(result);
588 });
589
590 results
591 .dumps
592 .drain(..)
593 .zip(self.dumps.iter_mut())
594 .for_each(|(result, dump)| {
595 *dump = Some(result);
596 });
597
598 if let Some(result) = results.gradients.as_mut() {
599 result
600 .drain(..)
601 .zip(self.gradients.iter_mut())
602 .for_each(|(result, gradient)| {
603 *gradient = Some(result);
604 });
605 }
606 }
607 Ok(())
608 }
609}