ket/process/
execution.rs

1// SPDX-FileCopyrightText: 2025 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use 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                    &parameters,
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                    &parameters,
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}