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}