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}