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