roqoqo/devices/mod.rs
1// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the
9// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10// express or implied. See the License for the specific language governing permissions and
11// limitations under the License.
12
13//! Traits defining the standard functions for roqoqo devices.
14//!
15//! Devices in roqoqo have two use cases:
16//!
17//! * Abstract devices: Contain abstract information for the model of a quantum computer and its parameters.
18//! They can be used to determine which Operations are available on a specific device model.
19//! A typical example are abstract linear chains of square lattices in which two-qubit operations are only
20//! available between neighbouring qubits.
21//!
22//! The abstract devices can also encode a noise model. Roqoqo noise models are in general based on a (pseudo) time
23//! needed to execute a quantum operation and Lindblad rates for the qubits in the device.
24//!
25//! Note that as long as gate times and decoherence rates are scaled inversely any kind of units can be used,
26//! but we recommend using nanoseconds and inverse nanosecconds as units for gate times and decoherence rates.
27//!
28//! Specifically in the noise model each qubit undergoes a continuous Lindblad-type decoherence time evolution.
29//!
30//! * Actual hardware devices: These devices are provided by roqoqo backends and contain the necessary information for
31//! accessing the quantum computing hardware. The devices also encode a connectivity model
32//!
33
34#[cfg(feature = "unstable_chain_with_environment")]
35use std::collections::HashMap;
36#[cfg(feature = "unstable_qoqo_devices")]
37use std::collections::HashSet;
38
39use crate::RoqoqoBackendError;
40#[cfg(feature = "unstable_qoqo_devices")]
41use crate::{prelude::InvolveQubits, Circuit};
42use ndarray::Array2;
43mod generic_device;
44pub use generic_device::GenericDevice;
45mod all_to_all;
46pub use all_to_all::AllToAllDevice;
47mod square_lattice;
48pub use square_lattice::SquareLatticeDevice;
49// use crate::RoqoqoError;
50// use std::collections::HashMap;
51
52/// Trait for roqoqo devices.
53///
54/// Defines standard functions available for roqoqo devices.
55///
56pub trait Device {
57 /// Returns the gate time of a single qubit operation if the single qubit operation is available on device.
58 ///
59 /// # Arguments
60 ///
61 /// * `hqslang` - The hqslang name of a single qubit gate.
62 /// * `qubit` - The qubit the gate acts on
63 ///
64 /// # Returns
65 ///
66 /// * `Some<f64>` - The gate time.
67 /// * `None` - The gate is not available on the device.
68 ///
69 fn single_qubit_gate_time(&self, hqslang: &str, qubit: &usize) -> Option<f64>;
70
71 /// Returns the names of a single qubit operations available on the device.
72 ///
73 /// # Returns
74 ///
75 /// * `Vec<String>` - The list of gate names.
76 ///
77 fn single_qubit_gate_names(&self) -> Vec<String> {
78 self.to_generic_device().single_qubit_gate_names()
79 }
80
81 /// Returns the names of a two qubit operations available on the device.
82 ///
83 /// # Returns
84 ///
85 /// * `Vec<String>` - The list of gate names.
86 ///
87 fn two_qubit_gate_names(&self) -> Vec<String> {
88 self.to_generic_device().two_qubit_gate_names()
89 }
90
91 /// Returns the names of a mutli qubit operations available on the device.
92 ///
93 /// The list of names also includes the three qubit gate operations.
94 ///
95 /// # Returns
96 ///
97 /// * `Vec<String>` - The list of gate names.
98 ///
99 fn multi_qubit_gate_names(&self) -> Vec<String> {
100 self.to_generic_device().multi_qubit_gate_names()
101 }
102
103 /// Returns the gate time of a two qubit operation if the two qubit operation is available on device.
104 ///
105 /// # Arguments
106 ///
107 /// * `hqslang` - The hqslang name of a two qubit gate.
108 /// * `control` - The control qubit the gate acts on.
109 /// * `target` - The target qubit the gate acts on.
110 ///
111 /// # Returns
112 ///
113 /// * `Some<f64>` - The gate time.
114 /// * `None` - The gate is not available on the device.
115 ///
116 fn two_qubit_gate_time(&self, hqslang: &str, control: &usize, target: &usize) -> Option<f64>;
117
118 /// Returns the gate time of a three qubit operation if the three qubit operation is available on device.
119 ///
120 /// # Arguments
121 ///
122 /// * `hqslang` - The hqslang name of a two qubit gate.
123 /// * `control_0` - The control_0 qubit the gate acts on.
124 /// * `control_1` - The control_1 qubit the gate acts on.
125 /// * `target` - The target qubit the gate acts on.
126 ///
127 /// # Returns
128 ///
129 /// * `Some<f64>` - The gate time.
130 /// * `None` - The gate is not available on the device.
131 ///
132 fn three_qubit_gate_time(
133 &self,
134 hqslang: &str,
135 control_0: &usize,
136 control_1: &usize,
137 target: &usize,
138 ) -> Option<f64>;
139
140 /// Returns the gate time of a multi qubit operation if the multi qubit operation is available on device.
141 ///
142 /// # Arguments
143 ///
144 /// * `hqslang` - The hqslang name of a multi qubit gate.
145 /// * `qubits` - The qubits the gate acts on
146 ///
147 /// # Returns
148 ///
149 /// * `Some<f64>` - The gate time.
150 /// * `None` - The gate is not available on the device.
151 ///
152 fn multi_qubit_gate_time(&self, hqslang: &str, qubits: &[usize]) -> Option<f64>;
153
154 /// Returns the matrix of the decoherence rates of the Lindblad equation.
155 ///
156 /// # Arguments
157 ///
158 /// * `qubit` - The qubit for which the rate matrix is returned.
159 ///
160 /// # Returns
161 ///
162 /// * `Some<Array2<f64>>` - The decoherence rates.
163 /// * `None` - The qubit is not part of the device.
164 ///
165 fn qubit_decoherence_rates(&self, qubit: &usize) -> Option<Array2<f64>>;
166
167 /// Returns the number of qubits the device supports.
168 ///
169 /// # Returns
170 ///
171 /// The number of qubits in the device.
172 ///
173 fn number_qubits(&self) -> usize;
174
175 /// Returns the list of pairs of qubits linked with a native two-qubit-gate in the device.
176 ///
177 /// A pair of qubits is considered linked by a native two-qubit-gate if the device
178 /// can implement a two-qubit-gate between the two qubits without decomposing it
179 /// into a sequence of gates that involves a third qubit of the device.
180 /// The two-qubit-gate also has to form a universal set together with the available
181 /// single qubit gates.
182 ///
183 /// The returned vectors is a simple, graph-library independent, representation of
184 /// the undirected connectivity graph of the device.
185 /// It can be used to construct the connectivity graph in a graph library of the users
186 /// choice from a list of edges and can be used for applications like routing in quantum algorithms.
187 ///
188 /// # Returns
189 ///
190 /// A list (Vec) of pairs of qubits linked with a native two-qubit-gate in the device.
191 ///
192 fn two_qubit_edges(&self) -> Vec<(usize, usize)>;
193
194 /// Changes the device topology based on a Pragma operation.
195 ///
196 /// Specific devices and backends can allow changes to the device topology.
197 /// These changes are represented by Pragma operations that are only available for
198 /// the corresponding backend.
199 /// This function provides a generic interface for changing the devices with the help of
200 /// these Pragma operations.
201 /// In normal operation the backend specific Pragma operations are wrapped in a [crate::operations::PragmaChangeDevice]
202 /// wrapper operation and encoded in binary form with the [bincode] crate.
203 /// This function takes the encoded binary representation, tries to deserialize it internally
204 /// and applies the corresponding changes.
205 ///
206 /// For most devices the default behaviour is that the device cannot be changed
207 /// and the function returns a corresponding RoqoqoBackendError
208 ///
209 /// # Arguments
210 ///
211 /// * `hqslang` - The hqslang name of the wrapped operation
212 /// * `operation` - The Pragma operation encoded in binary form using the [bincode] crate
213 ///
214 /// # Returns
215 ///
216 /// Result of changing the device.
217 /// In case the device is not allowed to be changed a generic RoqoqoBackendError is returned.
218 /// If not implemented, a default `method has not been implemented` error is returned.
219 ///
220 #[allow(unused_variables)]
221 #[allow(unused_mut)]
222 fn change_device(&mut self, hqslang: &str, operation: &[u8]) -> Result<(), RoqoqoBackendError> {
223 Err(RoqoqoBackendError::GenericError {
224 msg: "The `change_device()` method has not been implemented.".to_string(),
225 })
226 }
227
228 /// Turns Device into GenericDevice
229 ///
230 /// Can be used as a generic interface for devices when a boxed dyn trait object cannot be used
231 /// (for example when the interface needs to be serialized)
232 ///
233 /// # Note
234 ///
235 /// [crate::devices::GenericDevice] uses nested HashMaps to represent the most general device connectivity.
236 /// The memory usage will be inefficient for devices with large qubit numbers.
237 fn to_generic_device(&self) -> GenericDevice;
238}
239
240#[cfg(feature = "unstable_qoqo_devices")]
241/// Trait for new qoqo devices.
242///
243/// Defines standard functions available for roqoqo devices.
244///
245pub trait QoqoDevice {
246 /// Returns the gate time of a single qubit operation if the single qubit operation is available on device.
247 ///
248 /// # Arguments
249 ///
250 /// * `hqslang` - The hqslang name of a single qubit gate.
251 /// * `qubit` - The qubit the gate acts on
252 ///
253 /// # Returns
254 ///
255 /// * `Some<f64>` - The gate time.
256 /// * `None` - The gate is not available on the device.
257 ///
258 fn single_qubit_gate_time(&self, hqslang: &str, qubit: &usize) -> Option<f64>;
259
260 /// Returns the names of a single qubit operations available on the device.
261 ///
262 /// # Returns
263 ///
264 /// * `Vec<String>` - The list of gate names.
265 ///
266 fn single_qubit_gate_names(&self) -> Vec<String>;
267
268 /// Returns the names of a two qubit operations available on the device.
269 ///
270 /// # Returns
271 ///
272 /// * `Vec<String>` - The list of gate names.
273 ///
274 fn two_qubit_gate_names(&self) -> Vec<String>;
275
276 /// Returns the names of a mutli qubit operations available on the device.
277 ///
278 /// The list of names also includes the three qubit gate operations.
279 ///
280 /// # Returns
281 ///
282 /// * `Vec<String>` - The list of gate names.
283 ///
284 fn multi_qubit_gate_names(&self) -> Vec<String>;
285
286 /// Returns the gate time of a two qubit operation if the two qubit operation is available on device.
287 ///
288 /// # Arguments
289 ///
290 /// * `hqslang` - The hqslang name of a two qubit gate.
291 /// * `control` - The control qubit the gate acts on.
292 /// * `target` - The target qubit the gate acts on.
293 ///
294 /// # Returns
295 ///
296 /// * `Some<f64>` - The gate time.
297 /// * `None` - The gate is not available on the device.
298 ///
299 fn two_qubit_gate_time(&self, hqslang: &str, control: &usize, target: &usize) -> Option<f64>;
300
301 /// Returns the gate time of a three qubit operation if the three qubit operation is available on device.
302 ///
303 /// # Arguments
304 ///
305 /// * `hqslang` - The hqslang name of a two qubit gate.
306 /// * `control_0` - The control_0 qubit the gate acts on.
307 /// * `control_1` - The control_1 qubit the gate acts on.
308 /// * `target` - The target qubit the gate acts on.
309 ///
310 /// # Returns
311 ///
312 /// * `Some<f64>` - The gate time.
313 /// * `None` - The gate is not available on the device.
314 ///
315 #[allow(unused_variables)]
316 fn three_qubit_gate_time(
317 &self,
318 hqslang: &str,
319 control_0: &usize,
320 control_1: &usize,
321 target: &usize,
322 ) -> Option<f64> {
323 None
324 }
325
326 /// Returns the gate time of a multi qubit operation if the multi qubit operation is available on device.
327 ///
328 /// # Arguments
329 ///
330 /// * `hqslang` - The hqslang name of a multi qubit gate.
331 /// * `qubits` - The qubits the gate acts on
332 ///
333 /// # Returns
334 ///
335 /// * `Some<f64>` - The gate time.
336 /// * `None` - The gate is not available on the device.
337 ///
338 fn multi_qubit_gate_time(&self, hqslang: &str, qubits: &[usize]) -> Option<f64>;
339
340 /// Returns the matrix of the decoherence rates of the Lindblad equation.
341 ///
342 /// # Arguments
343 ///
344 /// * `qubit` - The qubit for which the rate matrix is returned.
345 ///
346 /// # Returns
347 ///
348 /// * `Some<Array2<f64>>` - The decoherence rates.
349 /// * `None` - The qubit is not part of the device.
350 ///
351 fn qubit_decoherence_rates(&self, qubit: &usize) -> Option<Array2<f64>>;
352
353 /// Returns the number of qubits the device supports.
354 ///
355 /// # Returns
356 ///
357 /// The number of qubits in the device.
358 ///
359 fn number_qubits(&self) -> usize;
360
361 /// Return a list of longest linear chains through the device.
362 ///
363 /// Returns at least one chain of qubits with linear connectivity in the device,
364 /// that has the maximum possible number of qubits with linear connectivity in the device.
365 /// Can return more that one of the possible chains but is not guaranteed to return
366 /// all possible chains. (For example for all-to-all connectivity only one chain will be returned).
367 ///
368 /// # Returns
369 ///
370 /// Vec<Vec<usize>> - A list of the longest chains given by vectors of qubits in the chain.
371 ///
372 fn longest_chains(&self) -> Vec<Vec<usize>>;
373
374 /// Return a list of longest closed linear chains through the device.
375 ///
376 /// Returns at least one chain of qubits with linear connectivity in the device ,
377 /// that has the maximum possible number of qubits with linear connectivity in the device.
378 /// The chain must be closed, the first qubit needs to be connected to the last qubit.
379 /// Can return more that one of the possible chains but is not guaranteed to return
380 /// all possible chains. (For example for all-to-all connectivity only one chain will be returned).
381 ///
382 /// # Returns
383 ///
384 /// Vec<Vec<usize>> - A list of the longest chains given by vectors of qubits in the chain.
385 ///
386 fn longest_closed_chains(&self) -> Vec<Vec<usize>>;
387
388 /// Adds the noise contributions due to qubit gates being applied
389 ///
390 /// This functions adds additional noise that occurs in the device,
391 /// when one or more unitary gates are applied in parallel.
392 /// This does NOT include the background noise that is defined by the
393 /// gate time and the constant decoherence rates on all qubits.
394 /// The noise is inserted in the form of roqoqo noise Pragmas.
395 ///
396 /// # Arguments
397 ///
398 /// `circuit` - The circuit of parallel native operations for which the noise
399 /// is inserted
400 ///
401 /// # Returns
402 ///
403 /// `Ok(Circuit)` - The circuit of parallel operations containing additional noise Pragmas
404 /// `Err` - Error applying the noise. Usually occurs when the gates in the Circuit cannot
405 /// be executed or cannot be executed in parallel.
406 fn add_active_gate_noise(&self, circuit: &Circuit) -> Result<Circuit, RoqoqoBackendError> {
407 use crate::operations::GateOperation;
408 use crate::operations::InvolvedQubits;
409 let mut invovlved_qubits = HashSet::<usize>::new();
410 for op in circuit.iter() {
411 if let Ok(operation) = GateOperation::try_from(op) {
412 if let InvolvedQubits::Set(involved_set) = operation.involved_qubits() {
413 if invovlved_qubits.is_disjoint(&involved_set) {
414 invovlved_qubits.extend(involved_set)
415 } else {
416 return Err(RoqoqoBackendError::GenericError { msg: "Error add_active_gate_noise: Several unitary gates operate on same qubit in a parallel set of operations".to_string()});
417 }
418 }
419 }
420 }
421 Ok(circuit.clone())
422 }
423
424 /// Returns the list of pairs of qubits linked with a native two-qubit-gate in the device.
425 ///
426 /// A pair of qubits is considered linked by a native two-qubit-gate if the device
427 /// can implement a two-qubit-gate between the two qubits without decomposing it
428 /// into a sequence of gates that involves a third qubit of the device.
429 /// The two-qubit-gate also has to form a universal set together with the available
430 /// single qubit gates.
431 ///
432 /// The returned vectors is a simple, graph-library independent, representation of
433 /// the undirected connectivity graph of the device.
434 /// It can be used to construct the connectivity graph in a graph library of the users
435 /// choice from a list of edges and can be used for applications like routing in quantum algorithms.
436 ///
437 /// # Returns
438 ///
439 /// A list (Vec) of pairs of qubits linked with a native two-qubit-gate in the device.
440 ///
441 fn two_qubit_edges(&self) -> Vec<(usize, usize)>;
442
443 /// Changes the device topology based on a Pragma operation.
444 ///
445 /// Specific devices and backends can allow changes to the device topology.
446 /// These changes are represented by Pragma operations that are only available for
447 /// the corresponding backend.
448 /// This function provides a generic interface for changing the devices with the help of
449 /// these Pragma operations.
450 /// In normal operation the backend specific Pragma operations are wrapped in a [crate::operations::PragmaChangeDevice]
451 /// wrapper operation and encoded in binary form with the [bincode] crate.
452 /// This function takes the encoded binary representation, tries to deserialize it internally
453 /// and applies the corresponding changes.
454 ///
455 /// For most devices the default behaviour is that the device cannot be changed
456 /// and the function returns a corresponding RoqoqoBackendError
457 ///
458 /// # Arguments
459 ///
460 /// * `hqslang` - The hqslang name of the wrapped operation
461 /// * `operation` - The Pragma operation encoded in binary form using the [bincode] crate
462 ///
463 /// # Returns
464 ///
465 /// Result of changing the device.
466 /// In case the device is not allowed to be changed a generic RoqoqoBackendError is returned.
467 ///
468 #[allow(unused_variables)]
469 #[allow(unused_mut)]
470 fn change_device(&mut self, hqslang: &str, operation: &[u8]) -> Result<(), RoqoqoBackendError> {
471 Err(RoqoqoBackendError::GenericError {
472 msg: "The `change_device()` method has not been implemented.".to_string(),
473 })
474 }
475}
476
477#[cfg(feature = "unstable_chain_with_environment")]
478/// The description of a chain and environment.
479/// The first list contains all the qubits in the chain,
480/// the second entry the HashMap contains mapps each qubit in the chain
481/// to the qubits of the environment it is connected to.
482pub type ChainAndEnvironment = (Vec<usize>, HashMap<usize, Vec<usize>>);
483
484#[cfg(feature = "unstable_chain_with_environment")]
485/// Trait implemented by devices that can return a list of chains
486///
487pub trait ChainWithEnvironmentDevice {
488 /// Return a list of linear chains with an environment through the device.
489 ///
490 /// Returns at least one chain of qubits with linear connectivity and an environment in the device.
491 /// An environment is defined as at least one qubit that is connected to at least one qubit of the chain
492 /// but not part of the chain.
493 /// For each ratio of environment qubits to chain qubits, the list contains at least one of the longest chains
494 /// in the devive with that ratio. (Unless that chain and environment is simply a subset
495 /// of a chain with equal or longer length and equal or higher ratio).
496 ///
497 /// For example, take a device with the connectivity graph:
498 /// ```ignore
499 /// 0 - 3 - 6
500 /// | | |
501 /// 1 - 4 - 7
502 /// | |
503 /// 2 - 5
504 /// ```
505 /// It would have one chain of length 1 and environment with ratio 4 to 1:
506 ///
507 /// ```ignore
508 /// ([4], {4: [1,4,7,5]})
509 /// ```
510 ///
511 /// One with length 2 and ratio 5 to 2:
512 /// ```ignore
513 /// ([3,4], {3:[0,6], 4: [1,7,5]})
514 /// ```
515 ///
516 /// The chain and environment with length 2 and ratio 2 to 1 is a subset of the one above
517 /// and does not need to be listed separately.
518 ///
519 /// The longest chain with ratio 1 to 1 is:
520 /// ```ignore
521 /// ([0,1,4,3], {1:[2], 4: [5,7], 3: [6]})
522 /// ```
523 /// One of the longest chains with ratio 2 to 6 is
524 /// ```ignore
525 /// ([0,1,2,5,4,3], {4: [7], 3: [6]})
526 /// ```
527 /// And one of the possible chains with just one environment qubit is:
528 /// ```ignore
529 /// ([0,1,2,5,4,3,6], {6: [7], 4: [7]})
530 /// ```
531 ///
532 /// # Returns
533 ///
534 /// Vec<(Vec<usize>, HashMap<usize, Vec<usize>>)> - A list of the chains and environments.
535 ///
536 fn environment_chains(&self) -> Vec<ChainAndEnvironment>;
537}