roqoqo_qiskit_devices/devices/
ibm_nairobi.rs

1// Copyright © 2023-2025 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
13use std::collections::HashMap;
14
15use roqoqo::{devices::QoqoDevice, RoqoqoError};
16
17use ndarray::{array, Array2};
18
19use crate::IBMDevice;
20
21#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
22pub struct IBMNairobiDevice {
23    /// The number of qubits
24    number_qubits: usize,
25    /// Gate times for all single qubit gates
26    single_qubit_gates: HashMap<String, HashMap<usize, f64>>,
27    /// Gate times for all two qubit gates
28    two_qubit_gates: HashMap<String, TwoQubitGates>,
29    /// Decoherence rates for all qubits
30    decoherence_rates: HashMap<usize, Array2<f64>>,
31}
32
33type TwoQubitGates = HashMap<(usize, usize), f64>;
34
35impl IBMNairobiDevice {
36    /// Creates a new IBMNairobiDevice.
37    ///
38    /// # Returns
39    ///
40    /// An initiated IBMNairobiDevice with single and two-qubit gates and decoherence rates set to zero.
41    ///
42    pub fn new() -> Self {
43        let mut device = Self {
44            number_qubits: 7,
45            single_qubit_gates: HashMap::new(),
46            two_qubit_gates: HashMap::new(),
47            decoherence_rates: HashMap::new(),
48        };
49
50        for qubit in 0..device.number_qubits() {
51            for gate in device.single_qubit_gate_names() {
52                device
53                    .set_single_qubit_gate_time(&gate, qubit, 1.0)
54                    .unwrap();
55            }
56        }
57        for edge in device.two_qubit_edges() {
58            for gate in device.two_qubit_gate_names() {
59                device
60                    .set_two_qubit_gate_time(&gate, edge.0, edge.1, 1.0)
61                    .unwrap();
62                device
63                    .set_two_qubit_gate_time(&gate, edge.1, edge.0, 1.0)
64                    .unwrap();
65            }
66        }
67
68        device
69    }
70
71    /// Returns the IBM's identifier.
72    ///
73    /// # Returns
74    ///
75    /// A str of the name IBM uses as identifier.
76    pub fn name(&self) -> &'static str {
77        "ibm_nairobi"
78    }
79}
80
81impl Default for IBMNairobiDevice {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl From<&IBMNairobiDevice> for IBMDevice {
88    fn from(input: &IBMNairobiDevice) -> Self {
89        Self::IBMNairobiDevice(input.clone())
90    }
91}
92
93impl From<IBMNairobiDevice> for IBMDevice {
94    fn from(input: IBMNairobiDevice) -> Self {
95        Self::IBMNairobiDevice(input)
96    }
97}
98
99impl IBMNairobiDevice {
100    /// Setting the gate time of a single qubit gate.
101    ///
102    /// # Arguments
103    ///
104    /// * `gate` - hqslang name of the single-qubit-gate.
105    /// * `qubit` - The qubit for which the gate time is set.
106    /// * `gate_time` - gate time for the given gate.
107    pub fn set_single_qubit_gate_time(
108        &mut self,
109        gate: &str,
110        qubit: usize,
111        gate_time: f64,
112    ) -> Result<(), RoqoqoError> {
113        if qubit >= self.number_qubits {
114            return Err(RoqoqoError::GenericError {
115                msg: format!(
116                    "Qubit {} larger than number qubits {}",
117                    qubit, self.number_qubits
118                ),
119            });
120        }
121        match self.single_qubit_gates.get_mut(gate) {
122            Some(gate_times) => {
123                let gatetime = gate_times.entry(qubit).or_insert(gate_time);
124                *gatetime = gate_time;
125            }
126            None => {
127                let mut new_map = HashMap::new();
128                new_map.insert(qubit, gate_time);
129                self.single_qubit_gates.insert(gate.to_string(), new_map);
130            }
131        }
132        Ok(())
133    }
134
135    /// Setting the gate time of a two qubit gate.
136    ///
137    /// # Arguments
138    ///
139    /// * `gate` - hqslang name of the two-qubit-gate.
140    /// * `control` - The control qubit for which the gate time is set.
141    /// * `target` - The target qubit for which the gate time is set.
142    /// * `gate_time` - gate time for the given gate.
143    pub fn set_two_qubit_gate_time(
144        &mut self,
145        gate: &str,
146        control: usize,
147        target: usize,
148        gate_time: f64,
149    ) -> Result<(), RoqoqoError> {
150        if control >= self.number_qubits {
151            return Err(RoqoqoError::GenericError {
152                msg: format!(
153                    "Qubit {} larger than number qubits {}",
154                    control, self.number_qubits
155                ),
156            });
157        }
158        if target >= self.number_qubits {
159            return Err(RoqoqoError::GenericError {
160                msg: format!(
161                    "Qubit {} larger than number qubits {}",
162                    target, self.number_qubits
163                ),
164            });
165        }
166        if !self
167            .two_qubit_edges()
168            .iter()
169            .any(|&(a, b)| (a, b) == (control, target) || (a, b) == (target, control))
170        {
171            return Err(RoqoqoError::GenericError {
172                msg: format!(
173                    "Qubits {} and {} are not connected in the device",
174                    control, target
175                ),
176            });
177        }
178
179        match self.two_qubit_gates.get_mut(gate) {
180            Some(gate_times) => {
181                let gatetime = gate_times.entry((control, target)).or_insert(gate_time);
182                *gatetime = gate_time;
183            }
184            None => {
185                let mut new_map = HashMap::new();
186                new_map.insert((control, target), gate_time);
187                self.two_qubit_gates.insert(gate.to_string(), new_map);
188            }
189        }
190        Ok(())
191    }
192
193    /// Adds qubit damping to noise rates.
194    ///
195    /// # Arguments
196    ///
197    /// * `qubit` - The qubit for which the dampins is added.
198    /// * `daming` - The damping rates.
199    pub fn add_damping(&mut self, qubit: usize, damping: f64) -> Result<(), RoqoqoError> {
200        if qubit > self.number_qubits {
201            return Err(RoqoqoError::GenericError {
202                msg: format!(
203                    "Qubit {} out of range for device of size {}",
204                    qubit, self.number_qubits
205                ),
206            });
207        }
208        let aa = self
209            .decoherence_rates
210            .entry(qubit)
211            .or_insert_with(|| Array2::zeros((3, 3)));
212        *aa = aa.clone() + array![[damping, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]];
213        Ok(())
214    }
215
216    /// Adds qubit dephasing to noise rates.
217    ///
218    /// # Arguments
219    ///
220    /// * `qubit` - The qubit for which the dephasing is added.
221    /// * `dephasing` - The dephasing rates.
222    pub fn add_dephasing(&mut self, qubit: usize, dephasing: f64) -> Result<(), RoqoqoError> {
223        if qubit > self.number_qubits {
224            return Err(RoqoqoError::GenericError {
225                msg: format!(
226                    "Qubit {} out of range for device of size {}",
227                    qubit, self.number_qubits
228                ),
229            });
230        }
231        let aa = self
232            .decoherence_rates
233            .entry(qubit)
234            .or_insert_with(|| Array2::zeros((3, 3)));
235        *aa = aa.clone() + array![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, dephasing]];
236        Ok(())
237    }
238}
239
240/// Implements QoqoDevice trait for IBMNairobiDevice.
241///
242/// The QoqoDevice trait defines standard functions available for roqoqo devices.
243///
244impl QoqoDevice for IBMNairobiDevice {
245    /// Returns the gate time of a single qubit operation if the single qubit operation is available on device.
246    ///
247    /// # Arguments
248    ///
249    /// * `hqslang` - The hqslang name of a single qubit gate.
250    /// * `qubit` - The qubit the gate acts on.
251    ///
252    /// # Returns
253    ///
254    /// * `Some<f64>` - The gate time.
255    /// * `None` - The gate is not available on the device.
256    ///
257    #[allow(unused_variables)]
258    fn single_qubit_gate_time(&self, hqslang: &str, qubit: &usize) -> Option<f64> {
259        match self.single_qubit_gates.get(hqslang) {
260            Some(x) => x.get(qubit).copied(),
261            None => None,
262        }
263    }
264
265    /// Returns the names of a single qubit operations available on the device.
266    ///
267    /// # Returns
268    ///
269    /// * `Vec<String>` - The list of gate names.
270    ///
271    fn single_qubit_gate_names(&self) -> Vec<String> {
272        vec![
273            "PauliX".to_string(),
274            "RotateZ".to_string(),
275            "SqrtPauliX".to_string(),
276            "Identity".to_string(),
277        ]
278    }
279
280    /// Returns the gate time of a two qubit operation if the two qubit operation is available on device.
281    ///
282    /// # Arguments
283    ///
284    /// * `hqslang` - The hqslang name of a two qubit gate.
285    /// * `control` - The control qubit the gate acts on.
286    /// * `target` - The target qubit the gate acts on.
287    ///
288    /// # Returns
289    ///
290    /// * `Some<f64>` - The gate time.
291    /// * `None` - The gate is not available on the device.
292    ///
293    #[allow(unused_variables)]
294    fn two_qubit_gate_time(&self, hqslang: &str, control: &usize, target: &usize) -> Option<f64> {
295        match self.two_qubit_gates.get(hqslang) {
296            Some(x) => x.get(&(*control, *target)).copied(),
297            None => None,
298        }
299    }
300
301    /// Returns the names of a two qubit operations available on the device.
302    ///
303    /// # Returns
304    ///
305    /// * `Vec<String>` - The list of gate names.
306    ///
307    fn two_qubit_gate_names(&self) -> Vec<String> {
308        vec!["CNOT".to_string()]
309    }
310
311    /// Returns the gate time of a three qubit operation if the three qubit operation is available on device.
312    ///
313    /// # Arguments
314    ///
315    /// * `hqslang` - The hqslang name of a two qubit gate.
316    /// * `control_0` - The control_0 qubit the gate acts on.
317    /// * `control_1` - The control_1 qubit the gate acts on.
318    /// * `target` - The target qubit the gate acts on.
319    ///
320    /// # Returns
321    ///
322    /// * `Some<f64>` - The gate time.
323    /// * `None` - The gate is not available on the device.
324    ///
325    #[allow(unused_variables)]
326    fn three_qubit_gate_time(
327        &self,
328        hqslang: &str,
329        control_0: &usize,
330        control_1: &usize,
331        target: &usize,
332    ) -> Option<f64> {
333        None
334    }
335
336    /// Returns the gate time of a multi qubit operation if the multi qubit operation is available on device.
337    ///
338    /// # Arguments
339    ///
340    /// * `hqslang` - The hqslang name of a multi qubit gate.
341    /// * `qubits` - The qubits the gate acts on.
342    ///
343    /// # Returns
344    ///
345    /// * `Some<f64>` - The gate time.
346    /// * `None` - The gate is not available on the device.
347    ///
348    #[allow(unused_variables)]
349    fn multi_qubit_gate_time(&self, hqslang: &str, qubits: &[usize]) -> Option<f64> {
350        None
351    }
352
353    /// Returns the names of a multi qubit operations available on the device.
354    ///
355    /// The list of names also includes the three qubit gate operations.
356    ///
357    /// # Returns
358    ///
359    /// * `Vec<String>` - The list of gate names.
360    ///
361    fn multi_qubit_gate_names(&self) -> Vec<String> {
362        vec![]
363    }
364
365    /// Returns the matrix of the decoherence rates of the Lindblad equation.
366    ///
367    /// # Arguments
368    ///
369    /// * `qubit` - The qubit for which the rate matrix is returned.
370    ///
371    /// # Returns
372    ///
373    /// * `Some<Array2<f64>>` - The decoherence rates.
374    /// * `None` - The qubit is not part of the device.
375    ///
376    #[allow(unused_variables)]
377    fn qubit_decoherence_rates(&self, qubit: &usize) -> Option<Array2<f64>> {
378        self.decoherence_rates.get(qubit).cloned()
379    }
380
381    /// Returns the number of qubits the device supports.
382    ///
383    /// # Returns
384    ///
385    /// `usize` - The number of qubits in the device.
386    ///
387    fn number_qubits(&self) -> usize {
388        self.number_qubits
389    }
390
391    /// Return a list of longest linear chains through the device.
392    ///
393    /// Returns at least one chain of qubits with linear connectivity in the device,
394    /// that has the maximum possible number of qubits with linear connectivity in the device.
395    /// Can return more that one of the possible chains but is not guaranteed to return
396    /// all possible chains. (For example for all-to-all connectivity only one chain will be returned).
397    ///
398    /// # Returns
399    ///
400    /// * `Vec<Vec<usize>>` - A list of the longest chains given by vectors of qubits in the chain.
401    ///
402    fn longest_chains(&self) -> Vec<Vec<usize>> {
403        vec![
404            vec![0, 1, 3, 5, 6],
405            vec![0, 1, 3, 5, 4],
406            vec![2, 1, 3, 5, 4],
407            vec![2, 1, 3, 5, 6],
408        ]
409    }
410
411    /// Return a list of longest closed linear chains through the device.
412    ///
413    /// Returns at least one chain of qubits with linear connectivity in the device ,
414    /// that has the maximum possible number of qubits with linear connectivity in the device.
415    /// The chain must be closed, the first qubit needs to be connected to the last qubit.
416    /// Can return more that one of the possible chains but is not guaranteed to return
417    /// all possible chains. (For example for all-to-all connectivity only one chain will be returned).
418    ///
419    /// # Returns
420    ///
421    /// * `Vec<Vec<usize>>` - A list of the longest chains given by vectors of qubits in the chain.
422    ///
423    fn longest_closed_chains(&self) -> Vec<Vec<usize>> {
424        vec![
425            vec![0, 1],
426            vec![1, 0],
427            vec![1, 2],
428            vec![2, 1],
429            vec![1, 3],
430            vec![3, 1],
431            vec![3, 5],
432            vec![5, 3],
433            vec![4, 5],
434            vec![5, 4],
435            vec![5, 6],
436            vec![6, 5],
437        ]
438    }
439
440    /// Returns the list of pairs of qubits linked with a native two-qubit-gate in the device.
441    ///
442    /// A pair of qubits is considered linked by a native two-qubit-gate if the device
443    /// can implement a two-qubit-gate between the two qubits without decomposing it
444    /// into a sequence of gates that involves a third qubit of the device.
445    /// The two-qubit-gate also has to form a universal set together with the available
446    /// single qubit gates.
447    ///
448    /// The returned vectors is a simple, graph-library independent, representation of
449    /// the undirected connectivity graph of the device.
450    /// It can be used to construct the connectivity graph in a graph library of the users
451    /// choice from a list of edges and can be used for applications like routing in quantum algorithms.
452    ///
453    /// # Returns
454    ///
455    /// A list (Vec) of pairs of qubits linked with a native two-qubit-gate in the device.
456    ///
457    fn two_qubit_edges(&self) -> Vec<(usize, usize)> {
458        vec![(0, 1), (1, 2), (1, 3), (3, 5), (4, 5), (5, 6)]
459    }
460}