ket/process/
mod.rs

1// SPDX-FileCopyrightText: 2024 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5mod aux;
6mod ctrl;
7mod execution;
8mod measure;
9mod util;
10
11use crate::{
12    circuit::Circuit,
13    decompose::{AuxMode, Registry, Schema, State},
14    error::{KetError, Result},
15    execution::{Capability, ExecutionProtocol, QuantumExecution},
16    graph::GraphMatrix,
17    ir::{
18        gate::{Param, QuantumGate},
19        instructions::Instruction,
20        qubit::{LogicalQubit, PhysicalQubit},
21    },
22    prelude::ExecutionTarget,
23    process::ctrl::CtrlEngine,
24};
25use serde::{Deserialize, Serialize};
26use std::{
27    collections::{HashMap, HashSet, VecDeque},
28    vec,
29};
30
31/// Ket quantum process.
32#[derive(Debug, Default)]
33pub struct Process {
34    ctrl: CtrlEngine,
35
36    /// List of gates to be applied when inverse scopes ends.
37    adj_stack: Vec<Vec<GateInstruction>>,
38
39    /// Logical circuit with decomposed gates, if necessary.
40    logical_circuit: Circuit<LogicalQubit>,
41    /// Physical circuit, if coupling graph is available.
42    physical_circuit: Option<Circuit<PhysicalQubit>>,
43
44    /// List of measurement results.
45    measurements: Vec<Option<u64>>,
46    /// List of sample results.
47    samples: Vec<Option<Sample>>,
48    /// List of expected value result.
49    exp_values: Vec<Option<f64>>,
50    /// List of dump results.
51    dumps: Vec<Option<DumpData>>,
52
53    /// Quantum execution target configuration.
54    execution_target: ExecutionTarget,
55    /// Quantum execution target.
56    quantum_execution: Option<QuantumExecution>,
57    /// QPU qubit coupling graph
58    coupling_graph: Option<GraphMatrix<PhysicalQubit>>,
59
60    /// Number of qubits allocated by the user.
61    allocated_qubits: usize,
62    // /// Number of qubits allocated by the user or the runtime lib.
63    // qubit_count: usize,
64    /// Allocated qubits that has been measured.
65    valid_qubit: HashMap<LogicalQubit, bool>,
66    // Qubits ready to be allocated.
67    // free_qubits: Vec<LogicalQubit>,
68    /// Qubits that can be used as clean auxiliary.
69    clean_qubits: HashSet<LogicalQubit>,
70
71    gate_queue: VecDeque<usize>,
72
73    approximated_decomposition: usize,
74
75    /// Gradient results.
76    gradients: Vec<Option<f64>>,
77    /// Quantum gates parameters for gradient computation.
78    parameters: Vec<f64>,
79
80    /// Number of U4 gates (value) generated from each decomposition algorithm (key).
81    decomposition_stats: HashMap<String, i64>,
82
83    /// Measurement features enabled.
84    features: FeaturesAvailable,
85
86    /// Defined execution strategy
87    execution_strategy: Option<ExecutionStrategy>,
88
89    /// Aux qubit data
90    aux: aux::AuxQubit,
91}
92
93#[derive(Debug, Clone, Copy, Default)]
94enum ExecutionStrategy {
95    #[default]
96    ManagedByTarget,
97    MeasureFromSample(usize),
98    ClassicalShadows {
99        /// Weights for selecting the random measurement basis (X, Y,Z).
100        bias: (u8, u8, u8),
101        /// Number of measurement rounds.
102        samples: usize,
103        /// Number of shorts for each measurement round.
104        shots: usize,
105    },
106    DirectSample(usize),
107}
108
109#[derive(Debug)]
110enum GateInstruction {
111    Gate {
112        gate: QuantumGate,
113        target: LogicalQubit,
114        control: Vec<LogicalQubit>,
115    },
116    AuxRegistry(std::rc::Rc<std::cell::RefCell<Registry>>),
117}
118
119impl GateInstruction {
120    fn inverse(self) -> Self {
121        match self {
122            Self::Gate {
123                gate,
124                target,
125                control,
126            } => Self::Gate {
127                gate: gate.inverse(),
128                target,
129                control,
130            },
131            Self::AuxRegistry(registry) => Self::AuxRegistry(registry),
132        }
133    }
134}
135
136#[derive(Debug, Default, Clone)]
137struct FeaturesAvailable {
138    measure: bool,
139    sample: bool,
140    dump: bool,
141    exp_value: bool,
142    gradient: bool,
143}
144
145pub type Sample = (Vec<u64>, Vec<u64>);
146
147#[derive(Debug, Clone, Default, Deserialize, Serialize)]
148pub struct DumpData {
149    pub basis_states: Vec<Vec<u64>>,
150    pub amplitudes_real: Vec<f64>,
151    pub amplitudes_imag: Vec<f64>,
152}
153
154#[derive(Debug, Serialize)]
155pub struct Metadata {
156    pub logical_gate_count: HashMap<usize, i64>,
157    pub logical_circuit_depth: usize,
158    pub physical_gate_count: Option<HashMap<usize, i64>>,
159    pub physical_circuit_depth: Option<usize>,
160    pub allocated_qubits: usize,
161    pub terminated: bool,
162    pub decomposition: HashMap<String, i64>,
163}
164
165impl Process {
166    pub fn new(
167        execution_target: ExecutionTarget,
168        quantum_execution: Option<QuantumExecution>,
169    ) -> Self {
170        let features = match &execution_target.execution_protocol {
171            ExecutionProtocol::ManagedByTarget {
172                measure,
173                sample,
174                exp_value,
175                dump,
176            } => {
177                if execution_target.gradient.is_some()
178                    && !matches!(exp_value, Capability::Unsupported)
179                {
180                    FeaturesAvailable {
181                        exp_value: true,
182                        gradient: true,
183                        ..Default::default()
184                    }
185                } else {
186                    FeaturesAvailable {
187                        measure: !matches!(measure, Capability::Unsupported),
188                        sample: !matches!(sample, Capability::Unsupported),
189                        dump: !matches!(dump, Capability::Unsupported),
190                        exp_value: !matches!(exp_value, Capability::Unsupported),
191                        gradient: false,
192                    }
193                }
194            }
195            ExecutionProtocol::SampleBased(_) => {
196                if execution_target.gradient.is_some() {
197                    FeaturesAvailable {
198                        exp_value: true,
199                        gradient: true,
200                        ..Default::default()
201                    }
202                } else {
203                    FeaturesAvailable {
204                        measure: true,
205                        sample: true,
206                        exp_value: true,
207                        ..Default::default()
208                    }
209                }
210            }
211        };
212
213        let coupling_graph = execution_target.qpu.as_ref().and_then(|qpu| {
214            qpu.coupling_graph.as_ref().map(|graph| {
215                let mut cq = GraphMatrix::<PhysicalQubit>::new(0);
216                for (i, j) in graph {
217                    cq.set_edge((*i).into(), (*j).into(), 1);
218                }
219                cq.calculate_distance();
220                cq
221            })
222        });
223
224        Self {
225            execution_target,
226            quantum_execution,
227            coupling_graph,
228            features,
229            ..Default::default()
230        }
231    }
232
233    pub fn alloc(&mut self) -> Result<LogicalQubit> {
234        self.non_gate_checks(None, true)?;
235
236        if self.allocated_qubits < self.execution_target.num_qubits {
237            let index = self.allocated_qubits;
238            self.allocated_qubits += 1;
239            Ok(LogicalQubit::Main { index })
240        } else {
241            Err(KetError::MaxQubitsReached)
242        }
243    }
244
245    pub fn approximated_decomposition_begin(&mut self) {
246        self.approximated_decomposition += 1;
247    }
248
249    pub fn approximated_decomposition_end(&mut self) {
250        self.approximated_decomposition -= 1;
251    }
252
253    pub(crate) fn execute_gate_queue(&mut self, until: usize) {
254        while self.gate_queue.len() > until {
255            let gate = self
256                .logical_circuit
257                .instruction(self.gate_queue.pop_front().unwrap());
258
259            if let Instruction::Gate {
260                gate,
261                target,
262                control,
263            } = gate
264            {
265                if let Some(QuantumExecution::Live(execution)) = self.quantum_execution.as_mut() {
266                    execution.gate(*gate, *target, control);
267                }
268            }
269        }
270    }
271
272    pub fn gate(&mut self, mut gate: QuantumGate, target: LogicalQubit) -> Result<()> {
273        if gate.is_identity() {
274            return Ok(());
275        }
276
277        let parameter_gate = matches!(
278            gate,
279            QuantumGate::RotationX(Param::Ref { .. })
280                | QuantumGate::RotationY(Param::Ref { .. })
281                | QuantumGate::RotationZ(Param::Ref { .. })
282                | QuantumGate::Phase(Param::Ref { .. })
283        );
284
285        if parameter_gate {
286            if !self.ctrl.get_list().is_empty() {
287                return Err(KetError::ControlledParameter);
288            } else if let QuantumGate::RotationX(param)
289            | QuantumGate::RotationY(param)
290            | QuantumGate::RotationZ(param)
291            | QuantumGate::Phase(param) = &mut gate
292            {
293                param.update_ref(self.parameters[param.index()]);
294            }
295        }
296
297        self.gate_checks(target)?;
298
299        for qubit in self.ctrl.get_list().iter().chain([&target]) {
300            self.clean_qubits.remove(qubit);
301        }
302
303        self.aux
304            .validate_gate(&gate, &target, self.ctrl.get_list())?;
305
306        if !self.ctrl.get_list().is_empty() && self.execution_target.qpu.is_some() {
307            let mut schema = None; // Schema::default();
308            let interacting_qubits: Vec<_> = self
309                .ctrl
310                .get_list()
311                .iter()
312                .cloned()
313                .chain([target])
314                .collect();
315
316            for algorithm in gate.decomposition_list(self.ctrl.get_list().len()) {
317                if !algorithm.need_aux() {
318                    schema = Some(Schema {
319                        algorithm,
320                        aux_qubits: None,
321                        approximated: self.approximated_decomposition > 0,
322                    });
323                    break;
324                }
325
326                let ctrl_size = self.ctrl.get_list().len();
327                if let Ok((aux_qubits, id)) = self.alloc_aux(
328                    algorithm.aux_needed(ctrl_size),
329                    if matches!(algorithm.aux_mode(), AuxMode::Dirty) {
330                        Some(&interacting_qubits)
331                    } else {
332                        None
333                    },
334                ) {
335                    schema = Some(Schema {
336                        algorithm,
337                        aux_qubits: Some((id, aux_qubits)),
338                        approximated: self.approximated_decomposition > 0,
339                    });
340                    break;
341                }
342            }
343            let schema = schema.unwrap();
344
345            let registry: std::rc::Rc<std::cell::RefCell<Registry>> =
346                std::rc::Rc::new(std::cell::RefCell::new(Registry {
347                    algorithm: schema.algorithm,
348                    aux_qubits_id: schema.aux_qubits.as_ref().map(|q| q.0),
349                    ..Default::default()
350                }));
351
352            self.push_gate(GateInstruction::AuxRegistry(registry.clone()));
353
354            for (gate, target, control) in gate.decompose(
355                target,
356                self.ctrl.get_list(),
357                schema,
358                self.execution_target.qpu.as_ref().unwrap().u4_gate,
359            ) {
360                let control = control.map_or(vec![], |control| vec![control]);
361                self.push_gate(GateInstruction::Gate {
362                    gate,
363                    target,
364                    control,
365                });
366            }
367
368            self.push_gate(GateInstruction::AuxRegistry(registry));
369        } else {
370            let control = self.ctrl.get_list().to_owned();
371            self.push_gate(GateInstruction::Gate {
372                gate,
373                target,
374                control,
375            });
376        }
377
378        Ok(())
379    }
380
381    fn push_gate(&mut self, gate: GateInstruction) {
382        if let Some(ajd_stack) = self.adj_stack.last_mut() {
383            ajd_stack.push(gate);
384        } else {
385            match gate {
386                GateInstruction::Gate {
387                    gate,
388                    target,
389                    control,
390                } => {
391                    if let Some(index) = self.logical_circuit.gate(gate, target, &control) {
392                        self.gate_queue.push_back(index);
393                    }
394                }
395                GateInstruction::AuxRegistry(registry) => {
396                    let mut registry = registry.borrow_mut();
397                    match registry.state {
398                        State::Begin => {
399                            registry.num_u4 =
400                                *self.logical_circuit.gate_count.entry(2).or_default();
401                            registry.state = State::End;
402                        }
403                        State::End => {
404                            *self
405                                .decomposition_stats
406                                .entry(registry.algorithm.to_string())
407                                .or_default() +=
408                                *self.logical_circuit.gate_count.entry(2).or_default()
409                                    - registry.num_u4;
410                            if let Some(groupe_id) = registry.aux_qubits_id {
411                                self.free_aux(groupe_id);
412                            }
413                        }
414                    }
415                }
416            }
417        }
418    }
419
420    pub fn global_phase(&mut self, angle: f64) -> Result<()> {
421        self.adj_ctrl_checks(None)?;
422
423        if self.ctrl.get_list().is_empty() {
424            return Ok(());
425        }
426
427        let qubits = self.ctrl.get_list().to_owned();
428
429        self.ctrl.begin()?;
430        self.ctrl.push(&qubits[1..])?;
431        self.gate(QuantumGate::Phase(angle.into()), qubits[0])?;
432        self.ctrl.pop()?;
433        self.ctrl.end()?;
434        Ok(())
435    }
436
437    pub fn get_measure(&self, index: usize) -> Option<u64> {
438        self.measurements.get(index).copied().flatten()
439    }
440
441    pub fn get_sample(&self, index: usize) -> Option<&Sample> {
442        self.samples.get(index).and_then(|s| s.as_ref())
443    }
444
445    pub fn get_exp_value(&self, index: usize) -> Option<f64> {
446        self.exp_values.get(index).copied().flatten()
447    }
448
449    pub fn get_dump(&self, index: usize) -> Option<&DumpData> {
450        self.dumps.get(index).and_then(|d| d.as_ref())
451    }
452
453    pub fn instructions(&self) -> &[Instruction<LogicalQubit>] {
454        &self.logical_circuit.instructions
455    }
456
457    pub fn instructions_json(&self) -> String {
458        serde_json::to_string(&self.instructions()).unwrap()
459    }
460
461    pub fn isa_instructions(&self) -> Option<&[Instruction<PhysicalQubit>]> {
462        self.physical_circuit
463            .as_ref()
464            .map(|c| c.instructions.as_ref())
465    }
466
467    pub fn isa_instructions_json(&self) -> String {
468        serde_json::to_string(&self.isa_instructions()).unwrap()
469    }
470
471    pub fn metadata(&self) -> Metadata {
472        Metadata {
473            logical_gate_count: self.logical_circuit.gate_count.clone(),
474            logical_circuit_depth: self.logical_circuit.depth(),
475            physical_gate_count: self
476                .physical_circuit
477                .as_ref()
478                .map(|circuit| circuit.gate_count.clone()),
479            physical_circuit_depth: self
480                .physical_circuit
481                .as_ref()
482                .map(|circuit| circuit.depth()),
483            allocated_qubits: self.allocated_qubits,
484            terminated: self.execution_strategy.is_some(),
485            decomposition: self.decomposition_stats.clone(),
486        }
487    }
488
489    pub fn parameter(&mut self, param: f64) -> Result<usize> {
490        if !self.features.gradient {
491            return Err(KetError::GradientDisabled);
492        }
493
494        let parameter_index = self.gradients.len();
495        self.gradients.push(None);
496        self.parameters.push(param);
497
498        Ok(parameter_index)
499    }
500
501    pub fn gradient(&self, index: usize) -> Option<f64> {
502        self.gradients[index]
503    }
504
505    pub fn save_sim_state(&self) -> Vec<u8> {
506        if let Some(QuantumExecution::Live(simulator)) = self.quantum_execution.as_ref() {
507            simulator.save()
508        } else {
509            vec![]
510        }
511    }
512
513    pub fn load_sim_state(&mut self, data: &[u8]) {
514        if let Some(QuantumExecution::Live(simulator)) = self.quantum_execution.as_mut() {
515            simulator.load(data);
516        }
517    }
518}