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}