1mod 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#[derive(Debug, Default)]
33pub struct Process {
34 ctrl: CtrlEngine,
35
36 adj_stack: Vec<Vec<GateInstruction>>,
38
39 logical_circuit: Circuit<LogicalQubit>,
41 physical_circuit: Option<Circuit<PhysicalQubit>>,
43
44 measurements: Vec<Option<u64>>,
46 samples: Vec<Option<Sample>>,
48 exp_values: Vec<Option<f64>>,
50 dumps: Vec<Option<DumpData>>,
52
53 execution_target: ExecutionTarget,
55 quantum_execution: Option<QuantumExecution>,
57 coupling_graph: Option<GraphMatrix<PhysicalQubit>>,
59
60 allocated_qubits: usize,
62 valid_qubit: HashMap<LogicalQubit, bool>,
66 clean_qubits: HashSet<LogicalQubit>,
70
71 gate_queue: VecDeque<usize>,
72
73 approximated_decomposition: usize,
74
75 gradients: Vec<Option<f64>>,
77 parameters: Vec<f64>,
79
80 decomposition_stats: HashMap<String, i64>,
82
83 features: FeaturesAvailable,
85
86 execution_strategy: Option<ExecutionStrategy>,
88
89 aux: aux::AuxQubit,
91}
92
93#[derive(Debug, Clone, Copy, Default)]
94enum ExecutionStrategy {
95 #[default]
96 ManagedByTarget,
97 MeasureFromSample(usize),
98 ClassicalShadows {
99 bias: (u8, u8, u8),
101 samples: usize,
103 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; 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}