Line data Source code
1 : // Copyright © 2021 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 : //! Collection of roqoqo PRAGMA operations.
14 : //!
15 :
16 : use crate::operations::{
17 : InvolveQubits, InvolvedQubits, Operate, OperateMultiQubit, OperatePragma, OperatePragmaNoise,
18 : OperatePragmaNoiseProba, OperateSingleQubit, RoqoqoError, Substitute,
19 : };
20 : use crate::Circuit;
21 : #[cfg(feature = "serialize")]
22 : use bincode::serialize;
23 : use nalgebra::Matrix4;
24 : use ndarray::{array, Array, Array1, Array2};
25 : use num_complex::Complex64;
26 : use qoqo_calculator::{Calculator, CalculatorFloat};
27 : #[cfg(feature = "serialize")]
28 : use serde::{Deserialize, Serialize};
29 : use std::collections::HashMap;
30 : use std::convert::TryFrom;
31 :
32 : /// This PRAGMA Operation sets the number of measurements of the circuit.
33 : ///
34 : /// This is used for backends that allow setting the number of tries. However, setting the number of
35 : /// measurements does not allow access to the underlying wavefunction or density matrix.
36 : ///
37 : #[derive(
38 1 : Debug,
39 1 : Clone,
40 7 : PartialEq,
41 14 : roqoqo_derive::Operate,
42 2 : roqoqo_derive::Substitute,
43 : roqoqo_derive::OperatePragma,
44 : )]
45 : #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
46 : pub struct PragmaSetNumberOfMeasurements {
47 : /// The number of measurements.
48 : number_measurements: usize,
49 : /// The register for the readout.
50 : readout: String,
51 : }
52 :
53 : #[allow(non_upper_case_globals)]
54 : const TAGS_PragmaSetNumberOfMeasurements: &[&str; 3] = &[
55 : "Operation",
56 : "PragmaOperation",
57 : "PragmaSetNumberOfMeasurements",
58 : ];
59 :
60 : // Implementing the InvolveQubits trait for PragmaSetNumberOfMeasurements.
61 : impl InvolveQubits for PragmaSetNumberOfMeasurements {
62 : /// Lists all involved qubits (here, none).
63 1 : fn involved_qubits(&self) -> InvolvedQubits {
64 1 : InvolvedQubits::None
65 1 : }
66 : }
67 :
68 : /// This PRAGMA Operation sets the statevector of a quantum register.
69 : ///
70 : /// The Circuit() module automatically initializes the qubits in the |0> state, so this PRAGMA
71 : /// operation allows you to set the state of the qubits to a state of your choosing.
72 : ///
73 : /// # Example
74 : ///
75 : /// For instance, to initialize the $|\Psi^->$ Bell state, we pass the following `statevec` to
76 : /// the PragmaSetStateVector operation.
77 : ///
78 : /// ```
79 : /// use ndarray::{array, Array1};
80 : /// use num_complex::Complex64;
81 : /// use roqoqo::operations::PragmaSetStateVector;
82 : ///
83 : /// let statevec: Array1<Complex64> = array![
84 : /// Complex64::new(0.0, 0.0),
85 : /// Complex64::new(1.0 / (2.0_f64).sqrt(), 0.0),
86 : /// Complex64::new(-1.0 / (2.0_f64).sqrt(), 0.0),
87 : /// Complex64::new(0.0, 0.0)
88 : /// ];
89 : ///
90 : /// let pragma = PragmaSetStateVector::new(statevec.clone());
91 : /// ```
92 : ///
93 : #[derive(
94 1 : Debug,
95 1 : Clone,
96 7 : PartialEq,
97 13 : roqoqo_derive::Operate,
98 2 : roqoqo_derive::Substitute,
99 : roqoqo_derive::OperatePragma,
100 : )]
101 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
102 : pub struct PragmaSetStateVector {
103 : /// The statevector that is initialized.
104 : statevector: Array1<Complex64>,
105 : }
106 :
107 : #[allow(non_upper_case_globals)]
108 : const TAGS_PragmaSetStateVector: &[&str; 3] =
109 : &["Operation", "PragmaOperation", "PragmaSetStateVector"];
110 :
111 : // Implementing the InvolveQubits trait for PragmaSetStateVector.
112 : impl InvolveQubits for PragmaSetStateVector {
113 : /// Lists all involved qubits (here, all).
114 1 : fn involved_qubits(&self) -> InvolvedQubits {
115 1 : InvolvedQubits::All
116 1 : }
117 : }
118 :
119 : /// This PRAGMA Operation sets the density matrix of a quantum register.
120 : ///
121 : /// The Circuit() module automatically initializes the qubits in the |0> state, so this PRAGMA
122 : /// operation allows you to set the state of the qubits by setting a density matrix of your choosing.
123 : ///
124 : /// # Example
125 : ///
126 : /// ```
127 : /// use ndarray::{array, Array2};
128 : /// use num_complex::Complex64;
129 : /// use roqoqo::operations::PragmaSetDensityMatrix;
130 : ///
131 : /// let matrix: Array2<Complex64> = array![
132 : /// [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
133 : /// [Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0)],
134 : /// ];
135 : ///
136 : /// let pragma = PragmaSetDensityMatrix::new(matrix.clone());
137 : /// ```
138 : ///
139 : #[derive(
140 1 : Debug,
141 1 : Clone,
142 7 : PartialEq,
143 13 : roqoqo_derive::Operate,
144 2 : roqoqo_derive::Substitute,
145 : roqoqo_derive::OperatePragma,
146 : )]
147 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
148 : pub struct PragmaSetDensityMatrix {
149 : /// The density matrix that is initialized.
150 : density_matrix: Array2<Complex64>,
151 : }
152 :
153 : #[allow(non_upper_case_globals)]
154 : const TAGS_PragmaSetDensityMatrix: &[&str; 3] =
155 : &["Operation", "PragmaOperation", "PragmaSetDensityMatrix"];
156 :
157 : // Implementing the InvolveQubits trait for PragmaSetDensityMatrix.
158 : impl InvolveQubits for PragmaSetDensityMatrix {
159 : /// Lists all involved qubits (here, all).
160 1 : fn involved_qubits(&self) -> InvolvedQubits {
161 1 : InvolvedQubits::All
162 1 : }
163 : }
164 :
165 : /// The repeated gate PRAGMA operation.
166 : ///
167 : /// This PRAGMA Operation repeats the next gate in the circuit the given number of times to increase the rate for error mitigation.
168 : ///
169 : #[derive(
170 1 : Debug,
171 1 : Clone,
172 7 : PartialEq,
173 13 : roqoqo_derive::Operate,
174 2 : roqoqo_derive::Substitute,
175 : roqoqo_derive::OperatePragma,
176 : )]
177 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
178 : pub struct PragmaRepeatGate {
179 : /// The number of times the following gate is repeated.
180 : repetition_coefficient: usize,
181 : }
182 :
183 : #[allow(non_upper_case_globals)]
184 : const TAGS_PragmaRepeatGate: &[&str; 3] = &["Operation", "PragmaOperation", "PragmaRepeatGate"];
185 :
186 : // Implementing the InvolveQubits trait for PragmaRepeatGate.
187 : impl InvolveQubits for PragmaRepeatGate {
188 : /// Lists all involved qubits (here, all).
189 1 : fn involved_qubits(&self) -> InvolvedQubits {
190 1 : InvolvedQubits::All
191 1 : }
192 : }
193 :
194 : /// The statistical overrotation PRAGMA operation.
195 : ///
196 : /// This PRAGMA applies a statistical overrotation to the next rotation gate in the circuit, which
197 : /// matches the hqslang name in the `gate` parameter of PragmaOverrotation and the involved qubits in `qubits`.
198 : ///
199 : /// The applied overrotation corresponds to adding a random number to the rotation angle.
200 : /// The random number is drawn from a normal distribution with mean `0`
201 : /// and standard deviation `variance` and is multiplied by the `amplitude`.
202 : ///
203 : #[derive(
204 1 : Debug,
205 1 : Clone,
206 7 : PartialEq,
207 16 : roqoqo_derive::Operate,
208 2 : roqoqo_derive::Substitute,
209 1 : roqoqo_derive::InvolveQubits,
210 : roqoqo_derive::OperatePragma,
211 1 : roqoqo_derive::OperateMultiQubit,
212 : )]
213 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
214 : // #[cfg_attr(feature = "overrotate")]
215 : pub struct PragmaOverrotation {
216 : /// The unique hqslang name of the gate to overrotate.
217 : gate_hqslang: String,
218 : /// The qubits of the gate to overrotate.
219 : qubits: Vec<usize>,
220 : /// The amplitude the random number is multiplied by.
221 : amplitude: f64,
222 : /// The standard deviation of the normal distribution the random number is drawn from.
223 : variance: f64,
224 : }
225 :
226 : #[allow(non_upper_case_globals)]
227 : const TAGS_PragmaOverrotation: &[&str; 4] = &[
228 : "Operation",
229 : "MultiQubitOperation",
230 : "PragmaOperation",
231 : "PragmaOverrotation",
232 : ];
233 :
234 : /// This PRAGMA Operation boosts noise and overrotations in the circuit.
235 : ///
236 : #[derive(
237 1 : Debug,
238 1 : Clone,
239 7 : PartialEq,
240 14 : roqoqo_derive::Operate,
241 1 : roqoqo_derive::Substitute,
242 : roqoqo_derive::OperatePragma,
243 : )]
244 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
245 : pub struct PragmaBoostNoise {
246 : /// The coefficient by which the noise is boosted, i.e. the number by which the gate time is multiplied.
247 : noise_coefficient: CalculatorFloat,
248 : }
249 :
250 : #[allow(non_upper_case_globals)]
251 : const TAGS_PragmaBoostNoise: &[&str; 3] = &["Operation", "PragmaOperation", "PragmaBoostNoise"];
252 :
253 : // Implementing the InvolveQubits trait for PragmaBoostNoise.
254 : impl InvolveQubits for PragmaBoostNoise {
255 : /// Lists all involved qubits (here, none).
256 2 : fn involved_qubits(&self) -> InvolvedQubits {
257 2 : InvolvedQubits::None
258 2 : }
259 : }
260 :
261 : /// This PRAGMA Operation signals the STOP of a parallel execution block.
262 : ///
263 : #[derive(
264 1 : Debug,
265 1 : Clone,
266 7 : PartialEq,
267 1 : roqoqo_derive::InvolveQubits,
268 14 : roqoqo_derive::Operate,
269 4 : roqoqo_derive::Substitute,
270 1 : roqoqo_derive::OperateMultiQubit,
271 : roqoqo_derive::OperatePragma,
272 : )]
273 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
274 : pub struct PragmaStopParallelBlock {
275 : /// The qubits involved in parallel execution block.
276 : qubits: Vec<usize>,
277 : /// The time for the execution of the block in seconds.
278 : execution_time: CalculatorFloat,
279 : }
280 :
281 : #[allow(non_upper_case_globals)]
282 : const TAGS_PragmaStopParallelBlock: &[&str; 4] = &[
283 : "Operation",
284 : "MultiQubitOperation",
285 : "PragmaOperation",
286 : "PragmaStopParallelBlock",
287 : ];
288 :
289 : /// The global phase PRAGMA operation.
290 : ///
291 : /// This PRAGMA Operation signals that the quantum register picks up a global phase,
292 : /// i.e. it provides information that there is a global phase to be considered.
293 : ///
294 : #[derive(
295 1 : Debug,
296 1 : Clone,
297 7 : PartialEq,
298 13 : roqoqo_derive::Operate,
299 1 : roqoqo_derive::Substitute,
300 : roqoqo_derive::OperatePragma,
301 : )]
302 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
303 : pub struct PragmaGlobalPhase {
304 : /// The picked up global phase.
305 : phase: CalculatorFloat,
306 : }
307 :
308 : #[allow(non_upper_case_globals)]
309 : const TAGS_PragmaGlobalPhase: &[&str; 3] = &["Operation", "PragmaOperation", "PragmaGlobalPhase"];
310 :
311 : // Implementing the InvolveQubits trait for PragmaGlobalPhase.
312 : impl InvolveQubits for PragmaGlobalPhase {
313 : /// Lists all involved qubits (here, none).
314 1 : fn involved_qubits(&self) -> InvolvedQubits {
315 1 : InvolvedQubits::None
316 1 : }
317 : }
318 :
319 : /// This PRAGMA Operation makes the quantum hardware wait a given amount of time.
320 : ///
321 : /// This PRAGMA Operation is used for error mitigation reasons, for instance.
322 : /// It can be used to boost the noise on the qubits since it gets worse with time.
323 : ///
324 : #[derive(
325 1 : Debug,
326 1 : Clone,
327 7 : PartialEq,
328 1 : roqoqo_derive::InvolveQubits,
329 14 : roqoqo_derive::Operate,
330 4 : roqoqo_derive::Substitute,
331 1 : roqoqo_derive::OperateMultiQubit,
332 : roqoqo_derive::OperatePragma,
333 : )]
334 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
335 : pub struct PragmaSleep {
336 : /// The qubits involved in the sleep block.
337 : qubits: Vec<usize>,
338 : /// Time for the execution of the operation in seconds.
339 : sleep_time: CalculatorFloat,
340 : }
341 :
342 : #[allow(non_upper_case_globals)]
343 : const TAGS_PragmaSleep: &[&str; 4] = &[
344 : "Operation",
345 : "MultiQubitOperation",
346 : "PragmaOperation",
347 : "PragmaSleep",
348 : ];
349 :
350 : /// This PRAGMA Operation resets the chosen qubit to the zero state.
351 : ///
352 : #[derive(
353 1 : Debug,
354 1 : Clone,
355 7 : PartialEq,
356 1 : roqoqo_derive::InvolveQubits,
357 13 : roqoqo_derive::Operate,
358 2 : roqoqo_derive::Substitute,
359 1 : roqoqo_derive::OperateSingleQubit,
360 : roqoqo_derive::OperatePragma,
361 : )]
362 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
363 : pub struct PragmaActiveReset {
364 : /// The qubit to be reset.
365 : qubit: usize,
366 : }
367 :
368 : #[allow(non_upper_case_globals)]
369 : const TAGS_PragmaActiveReset: &[&str; 4] = &[
370 : "Operation",
371 : "SingleQubitOperation",
372 : "PragmaOperation",
373 : "PragmaActiveReset",
374 : ];
375 :
376 : /// This PRAGMA Operation signals the START of a decomposition block.
377 : ///
378 : #[derive(
379 1 : Debug,
380 2 : Clone,
381 7 : PartialEq,
382 1 : roqoqo_derive::InvolveQubits,
383 13 : roqoqo_derive::Operate,
384 1 : roqoqo_derive::OperateMultiQubit,
385 : roqoqo_derive::OperatePragma,
386 : )]
387 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
388 : pub struct PragmaStartDecompositionBlock {
389 : /// The qubits involved in the decomposition block.
390 : qubits: Vec<usize>,
391 : /// The reordering dictionary of the block.
392 : reordering_dictionary: HashMap<usize, usize>,
393 : }
394 :
395 : #[allow(non_upper_case_globals)]
396 : const TAGS_PragmaStartDecompositionBlock: &[&str; 4] = &[
397 : "Operation",
398 : "MultiQubitOperation",
399 : "PragmaOperation",
400 : "PragmaStartDecompositionBlock",
401 : ];
402 :
403 : /// Substitute trait allowing to replace symbolic parameters and to perform qubit mappings.
404 : impl Substitute for PragmaStartDecompositionBlock {
405 : /// Remaps qubits in clone of the operation.
406 1 : fn remap_qubits(&self, mapping: &HashMap<usize, usize>) -> Result<Self, RoqoqoError> {
407 1 : let mut new_qubits: Vec<usize> = Vec::new();
408 2 : for q in &self.qubits {
409 2 : new_qubits.push(*mapping.get(q).ok_or(Err("")).map_err(
410 2 : |_x: std::result::Result<&usize, &str>| RoqoqoError::QubitMappingError {
411 0 : qubit: *q,
412 2 : },
413 2 : )?)
414 : }
415 :
416 1 : let mut mutable_reordering: HashMap<usize, usize> = HashMap::new();
417 3 : for (old_qubit, new_qubit) in self.reordering_dictionary.clone() {
418 2 : let old_remapped = *mapping.get(&old_qubit).ok_or(Err("")).map_err(
419 2 : |_x: std::result::Result<&usize, &str>| RoqoqoError::QubitMappingError {
420 0 : qubit: old_qubit,
421 2 : },
422 2 : )?;
423 2 : let new_remapped = *mapping.get(&new_qubit).ok_or(Err("")).map_err(
424 2 : |_x: std::result::Result<&usize, &str>| RoqoqoError::QubitMappingError {
425 0 : qubit: new_qubit,
426 2 : },
427 2 : )?;
428 2 : mutable_reordering.insert(old_remapped, new_remapped);
429 : }
430 :
431 1 : Ok(PragmaStartDecompositionBlock::new(
432 1 : new_qubits,
433 1 : mutable_reordering,
434 1 : ))
435 1 : }
436 :
437 : /// Substitutes symbolic parameters in clone of the operation.
438 1 : fn substitute_parameters(&self, _calculator: &mut Calculator) -> Result<Self, RoqoqoError> {
439 1 : Ok(self.clone())
440 1 : }
441 : }
442 :
443 : /// This PRAGMA Operation signals the STOP of a decomposition block.
444 : ///
445 : #[derive(
446 1 : Debug,
447 1 : Clone,
448 7 : PartialEq,
449 1 : roqoqo_derive::InvolveQubits,
450 13 : roqoqo_derive::Operate,
451 3 : roqoqo_derive::Substitute,
452 1 : roqoqo_derive::OperateMultiQubit,
453 : roqoqo_derive::OperatePragma,
454 : )]
455 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
456 : pub struct PragmaStopDecompositionBlock {
457 : /// The qubits involved in the decomposition block.
458 : qubits: Vec<usize>,
459 : }
460 :
461 : #[allow(non_upper_case_globals)]
462 : const TAGS_PragmaStopDecompositionBlock: &[&str; 4] = &[
463 : "Operation",
464 : "MultiQubitOperation",
465 : "PragmaOperation",
466 : "PragmaStopDecompositionBlock",
467 : ];
468 :
469 : /// The damping PRAGMA noise Operation.
470 : ///
471 : /// This PRAGMA Operation applies a pure damping error corresponding to zero temperature environments.
472 : ///
473 : #[derive(
474 1 : Debug,
475 2 : Clone,
476 8 : PartialEq,
477 1 : roqoqo_derive::InvolveQubits,
478 1 : roqoqo_derive::Operate,
479 3 : roqoqo_derive::Substitute,
480 1 : roqoqo_derive::OperateSingleQubit,
481 : roqoqo_derive::OperatePragma,
482 : )]
483 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
484 : pub struct PragmaDamping {
485 : /// The qubit on which to apply the damping.
486 : qubit: usize,
487 : /// The time (in seconds) the gate takes to be applied to the qubit on the (simulated) hardware
488 : gate_time: CalculatorFloat,
489 : /// The error rate of the damping (in 1/second).
490 : rate: CalculatorFloat,
491 : }
492 :
493 : #[allow(non_upper_case_globals)]
494 : const TAGS_PragmaDamping: &[&str; 6] = &[
495 : "Operation",
496 : "SingleQubitOperation",
497 : "PragmaOperation",
498 : "PragmaNoiseOperation",
499 : "PragmaNoiseProbaOperation",
500 : "PragmaDamping",
501 : ];
502 :
503 : /// OperatePragmaNoise trait creating necessary functions for a PRAGMA noise Operation.
504 : impl OperatePragmaNoise for PragmaDamping {
505 : /// Returns the superoperator matrix of the operation.
506 1 : fn superoperator(&self) -> Result<Array2<f64>, RoqoqoError> {
507 1 : let prob: f64 = f64::try_from(self.probability())?;
508 1 : let sqrt: f64 = (1.0 - prob).sqrt();
509 1 :
510 1 : Ok(array![
511 1 : [1.0, 0.0, 0.0, prob],
512 1 : [0.0, sqrt, 0.0, 0.0],
513 1 : [0.0, 0.0, sqrt, 0.0],
514 1 : [0.0, 0.0, 0.0, 1.0 - prob],
515 1 : ])
516 1 : }
517 :
518 : /// Returns the gate to the power of `power`.
519 1 : fn powercf(&self, power: CalculatorFloat) -> Self {
520 1 : let mut new = self.clone();
521 1 : new.gate_time = power * self.gate_time.clone();
522 1 : new
523 1 : }
524 : }
525 :
526 : /// OperatePragmaNoiseProba trait creating necessary functions for a PRAGMA noise Operation.
527 : impl OperatePragmaNoiseProba for PragmaDamping {
528 : /// Returns the probability of the noise gate affecting the qubit, based on its `gate_time` and `rate`.
529 3 : fn probability(&self) -> CalculatorFloat {
530 3 : let prob: CalculatorFloat =
531 3 : ((self.gate_time.clone() * self.rate.clone() * (-2.0)).exp() * (-1.0) + 1.0) * 0.5;
532 3 : prob
533 3 : }
534 : }
535 :
536 : /// The depolarising PRAGMA noise Operation.
537 : ///
538 : /// This PRAGMA Operation applies a depolarising error corresponding to infinite temperature environments.
539 : ///
540 : #[derive(
541 1 : Debug,
542 2 : Clone,
543 8 : PartialEq,
544 1 : roqoqo_derive::InvolveQubits,
545 1 : roqoqo_derive::Operate,
546 3 : roqoqo_derive::Substitute,
547 1 : roqoqo_derive::OperateSingleQubit,
548 : roqoqo_derive::OperatePragma,
549 : )]
550 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
551 : pub struct PragmaDepolarising {
552 : /// The qubit on which to apply the depolarising.
553 : qubit: usize,
554 : /// The time (in seconds) the gate takes to be applied to the qubit on the (simulated) hardware
555 : gate_time: CalculatorFloat,
556 : /// The error rate of the depolarisation (in 1/second).
557 : rate: CalculatorFloat,
558 : }
559 :
560 : #[allow(non_upper_case_globals)]
561 : const TAGS_PragmaDepolarising: &[&str; 6] = &[
562 : "Operation",
563 : "SingleQubitOperation",
564 : "PragmaOperation",
565 : "PragmaNoiseOperation",
566 : "PragmaNoiseProbaOperation",
567 : "PragmaDepolarising",
568 : ];
569 :
570 : /// OperatePragmaNoise trait creating necessary functions for a PRAGMA noise Operation.
571 : impl OperatePragmaNoise for PragmaDepolarising {
572 : /// Returns the superoperator matrix of the operation.
573 1 : fn superoperator(&self) -> Result<Array2<f64>, RoqoqoError> {
574 1 : let gate_time: f64 = f64::try_from(self.gate_time.clone())?;
575 1 : let rate: f64 = f64::try_from(self.rate.clone())?;
576 :
577 1 : let pre_exp: f64 = -1.0 * gate_time * rate;
578 1 : let prob: f64 = (3.0 / 4.0) * (1.0 - pre_exp.exp());
579 1 : let proba1: f64 = 1.0 - (2.0 / 3.0) * prob;
580 1 : let proba2: f64 = 1.0 - (4.0 / 3.0) * prob;
581 1 : let proba3: f64 = (2.0 / 3.0) * prob;
582 1 :
583 1 : Ok(array![
584 1 : [proba1, 0.0, 0.0, proba3],
585 1 : [0.0, proba2, 0.0, 0.0],
586 1 : [0.0, 0.0, proba2, 0.0],
587 1 : [proba3, 0.0, 0.0, proba1],
588 1 : ])
589 1 : }
590 :
591 : /// Returns the gate to the power of `power`.
592 1 : fn powercf(&self, power: CalculatorFloat) -> Self {
593 1 : let mut new = self.clone();
594 1 : new.gate_time = power * self.gate_time.clone();
595 1 : new
596 1 : }
597 : }
598 :
599 : /// OperatePragmaNoiseProba trait creating necessary functions for a PRAGMA noise Operation.
600 : impl OperatePragmaNoiseProba for PragmaDepolarising {
601 : /// Returns the probability of the noise gate affecting the qubit, based on its `gate_time` and `rate`.
602 1 : fn probability(&self) -> CalculatorFloat {
603 1 : let prob: CalculatorFloat =
604 1 : ((self.gate_time.clone() * self.rate.clone() * (-1.0)).exp() * (-1.0) + 1.0) * 0.75;
605 1 : prob
606 1 : }
607 : }
608 :
609 : /// The dephasing PRAGMA noise Operation.
610 : ///
611 : /// This PRAGMA Operation applies a pure dephasing error.
612 : ///
613 : #[derive(
614 1 : Debug,
615 2 : Clone,
616 8 : PartialEq,
617 1 : roqoqo_derive::InvolveQubits,
618 1 : roqoqo_derive::Operate,
619 3 : roqoqo_derive::Substitute,
620 1 : roqoqo_derive::OperateSingleQubit,
621 : roqoqo_derive::OperatePragma,
622 : )]
623 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
624 : pub struct PragmaDephasing {
625 : /// The qubit on which to apply the dephasing.
626 : qubit: usize,
627 : /// The time (in seconds) the gate takes to be applied to the qubit on the (simulated) hardware
628 : gate_time: CalculatorFloat,
629 : /// The error rate of the dephasing (in 1/second).
630 : rate: CalculatorFloat,
631 : }
632 :
633 : #[allow(non_upper_case_globals)]
634 : const TAGS_PragmaDephasing: &[&str; 6] = &[
635 : "Operation",
636 : "SingleQubitOperation",
637 : "PragmaOperation",
638 : "PragmaNoiseOperation",
639 : "PragmaNoiseProbaOperation",
640 : "PragmaDephasing",
641 : ];
642 :
643 : /// OperatePragmaNoise trait creating necessary functions for a PRAGMA noise Operation.
644 : impl OperatePragmaNoise for PragmaDephasing {
645 : /// Returns the superoperator matrix of the operation.
646 1 : fn superoperator(&self) -> Result<Array2<f64>, RoqoqoError> {
647 1 : let gate_time: f64 = f64::try_from(self.gate_time.clone())?;
648 1 : let rate: f64 = f64::try_from(self.rate.clone())?;
649 :
650 1 : let pre_exp: f64 = -2.0 * gate_time * rate;
651 1 : let prob: f64 = (1.0 / 2.0) * (1.0 - pre_exp.exp());
652 1 :
653 1 : Ok(array![
654 1 : [1.0, 0.0, 0.0, 0.0],
655 1 : [0.0, 1.0 - 2.0 * prob, 0.0, 0.0],
656 1 : [0.0, 0.0, 1.0 - 2.0 * prob, 0.0],
657 1 : [0.0, 0.0, 0.0, 1.0],
658 1 : ])
659 1 : }
660 :
661 : /// Returns the gate to the power of `power`.
662 1 : fn powercf(&self, power: CalculatorFloat) -> Self {
663 1 : let mut new = self.clone();
664 1 : new.gate_time = power * self.gate_time.clone();
665 1 : new
666 1 : }
667 : }
668 :
669 : /// OperatePragmaNoiseProba trait creating necessary functions for a PRAGMA noise Operation.
670 : impl OperatePragmaNoiseProba for PragmaDephasing {
671 : /// Returns the probability of the noise gate affecting the qubit, based on its `gate_time` and `rate`.
672 1 : fn probability(&self) -> CalculatorFloat {
673 1 : let prob: CalculatorFloat =
674 1 : ((self.gate_time.clone() * self.rate.clone() * (-2.0)).exp() * (-1.0) + 1.0) * 0.5;
675 1 : prob
676 1 : }
677 : }
678 :
679 : /// The random noise PRAGMA operation.
680 : ///
681 : /// This PRAGMA Operation applies a stochastically unravelled combination of dephasing and depolarising.
682 : ///
683 : #[derive(
684 1 : Debug,
685 2 : Clone,
686 8 : PartialEq,
687 1 : roqoqo_derive::InvolveQubits,
688 1 : roqoqo_derive::Operate,
689 3 : roqoqo_derive::Substitute,
690 1 : roqoqo_derive::OperateSingleQubit,
691 : roqoqo_derive::OperatePragma,
692 : )]
693 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
694 : pub struct PragmaRandomNoise {
695 : /// The qubit the PRAGMA Operation is applied to.
696 : qubit: usize,
697 : /// The time (in seconds) the gate takes to be applied to the qubit on the (simulated) hardware
698 : gate_time: CalculatorFloat,
699 : /// The error rate of the depolarisation (in 1/second).
700 : depolarising_rate: CalculatorFloat,
701 : /// The error rate of the dephasing (in 1/second).
702 : dephasing_rate: CalculatorFloat,
703 : }
704 :
705 : #[allow(non_upper_case_globals)]
706 : const TAGS_PragmaRandomNoise: &[&str; 6] = &[
707 : "Operation",
708 : "SingleQubitOperation",
709 : "PragmaOperation",
710 : "PragmaNoiseOperation",
711 : "PragmaNoiseProbaOperation",
712 : "PragmaRandomNoise",
713 : ];
714 :
715 : /// OperatePragmaNoise trait creating necessary functions for a PRAGMA noise Operation.
716 : impl OperatePragmaNoise for PragmaRandomNoise {
717 : /// Returns the superoperator matrix of the operation. For the RandomNoise pragma, the superoperator
718 : /// is the effective superoperator after averaging over many trajectories: the dephasing superoperator.
719 1 : fn superoperator(&self) -> Result<Array2<f64>, RoqoqoError> {
720 1 : let gate_time: f64 = f64::try_from(self.gate_time.clone())?;
721 1 : let rate: f64 = f64::try_from(self.dephasing_rate.clone())?;
722 :
723 1 : let pre_exp: f64 = -2.0 * gate_time * rate;
724 1 : let prob: f64 = (1.0 / 2.0) * (1.0 - pre_exp.exp());
725 1 :
726 1 : Ok(array![
727 1 : [1.0, 0.0, 0.0, 0.0],
728 1 : [0.0, 1.0 - 2.0 * prob, 0.0, 0.0],
729 1 : [0.0, 0.0, 1.0 - 2.0 * prob, 0.0],
730 1 : [0.0, 0.0, 0.0, 1.0],
731 1 : ])
732 1 : }
733 :
734 : /// Returns the gate to the power of `power`.
735 1 : fn powercf(&self, power: CalculatorFloat) -> Self {
736 1 : let mut new = self.clone();
737 1 : new.gate_time = power * self.gate_time.clone();
738 1 : new
739 1 : }
740 : }
741 :
742 : /// OperatePragmaNoiseProba trait creating necessary functions for a PRAGMA noise Operation.
743 : impl OperatePragmaNoiseProba for PragmaRandomNoise {
744 : /// Returns the probability of the noise gate affecting the qubit, based on its `gate_time`, `depolarising_rate` and `dephasing_rate`.
745 1 : fn probability(&self) -> CalculatorFloat {
746 1 : let rates = [
747 1 : self.depolarising_rate.clone() / 4.0,
748 1 : self.depolarising_rate.clone() / 4.0,
749 1 : (self.depolarising_rate.clone() / 4.0) + self.dephasing_rate.clone(),
750 1 : ];
751 1 : (rates[0].clone() + &rates[1] + &rates[2]) * &self.gate_time
752 1 : }
753 : }
754 :
755 : /// The general noise PRAGMA operation.
756 : ///
757 : /// This PRAGMA operation applies a noise term according to the given rates.
758 : /// The rates are represented by a 3x3 matrix:
759 : /// $$ M = \begin{pmatrix}
760 : /// a & b & c \\\\
761 : /// d & e & f \\\\
762 : /// g & h & j \\\\
763 : /// \end{pmatrix} $$
764 : /// where the coefficients correspond to the following summands
765 : /// expanded from the first term of the non-coherent part of the Lindblad equation:
766 : /// $$ \frac{d}{dt}\rho = \sum_{i,j=0}^{2} M_{i,j} L_{i} \rho L_{j}^{\dagger} - \frac{1}{2} \{ L_{j}^{\dagger} L_i, \rho \} \\\\
767 : /// L_0 = \sigma^{+} \\\\
768 : /// L_1 = \sigma^{-} \\\\
769 : /// L_3 = \sigma^{z}
770 : /// $$
771 : /// result{sigma_z, sigma_minus} = sigma_z (x) sigma_minus.T - 1/2 * (sigma_minus.T * sigma_z) (x) 1 - 1/2 * 1 (x) (sigma_minus.T * sigma_z).T
772 : ///
773 : /// Applying the Pragma with a given `gate_time` corresponds to applying the full time-evolution under the Lindblad equation for `gate_time` time.
774 : ///
775 : /// # Example
776 : ///
777 : /// ```
778 : /// use ndarray::{array, Array2};
779 : /// use roqoqo::operations::PragmaGeneralNoise;
780 : /// use qoqo_calculator::CalculatorFloat;
781 : ///
782 : /// let rates: Array2<f64> = array![
783 : /// [
784 : /// 1.0,
785 : /// 0.0,
786 : /// 0.0
787 : /// ],
788 : /// [
789 : /// 0.0,
790 : /// 1.0,
791 : /// 0.0
792 : /// ],
793 : /// [
794 : /// 0.0,
795 : /// 0.0,
796 : /// 1.0
797 : /// ],
798 : /// ];
799 : /// let pragma = PragmaGeneralNoise::new(
800 : /// 0,
801 : /// CalculatorFloat::from(0.005),
802 : /// rates.clone(),
803 : /// );
804 : /// ```
805 : /// That will result into $.
806 : ///
807 : #[derive(
808 1 : Debug,
809 1 : Clone,
810 7 : PartialEq,
811 1 : roqoqo_derive::InvolveQubits,
812 16 : roqoqo_derive::Operate,
813 3 : roqoqo_derive::Substitute,
814 1 : roqoqo_derive::OperateSingleQubit,
815 : roqoqo_derive::OperatePragma,
816 : )]
817 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
818 : pub struct PragmaGeneralNoise {
819 : /// The qubit the PRAGMA Operation is applied to.
820 : qubit: usize,
821 : /// The time (in seconds) the gate takes to be applied to the qubit on the (simulated) hardware
822 : gate_time: CalculatorFloat,
823 : /// The rates representing the general noise matrix M (a 3x3 matrix).
824 : rates: Array2<f64>,
825 : }
826 :
827 : #[allow(non_upper_case_globals)]
828 : const TAGS_PragmaGeneralNoise: &[&str; 5] = &[
829 : "Operation",
830 : "SingleQubitOperation",
831 : "PragmaOperation",
832 : "PragmaNoiseOperation",
833 : "PragmaGeneralNoise",
834 : ];
835 :
836 : // Collection of superoperators that appear in the Lindblad equation for a single qubit/spin with
837 : // a basis of the form 0: sigma+ 1:sigma- 2: sigmaz
838 : const PGN_SUPEROP: [[[[f64; 4]; 4]; 3]; 3] = [
839 : [
840 : // sigma+ sigma+
841 : [
842 : [0., 0., 0., 4.],
843 : [0., -2., 0., 0.],
844 : [0., 0., -2., 0.],
845 : [0., 0., 0., -4.],
846 : ],
847 : // sigma+ sigma-
848 : [
849 : [0., 0., 0., 0.],
850 : [0., 0., 4., 0.],
851 : [0., 0., 0., 0.],
852 : [0., 0., 0., 0.],
853 : ],
854 : // sigma+ sigmaz
855 : [
856 : [0., 0., 1., 0.],
857 : [-1., 0., 0., -3.],
858 : [0., 0., 0., 0.],
859 : [0., 0., 0., -1.],
860 : ],
861 : ],
862 : [
863 : // sigma- sigma+
864 : [
865 : [0., 0., 0., 0.],
866 : [0., 0., 0., 0.],
867 : [0., 4., 0., 0.],
868 : [0., 0., 0., 0.],
869 : ],
870 : // sigma- sigma-
871 : [
872 : [-4., 0., 0., 0.],
873 : [0., -2., 0., 0.],
874 : [0., 0., -2., 0.],
875 : [4., 0., 0., 0.],
876 : ],
877 : // sigma- sigmaz
878 : [
879 : [0., 1., 0., 0.],
880 : [0., 0., 0., 0.],
881 : [3., 0., 0., 1.],
882 : [0., -1., 0., 0.],
883 : ],
884 : ],
885 : [
886 : // sigmaz sigma+
887 : [
888 : [0., 1., 0., 0.],
889 : [0., 0., 0., 0.],
890 : [-1., 0., 0., -3.],
891 : [0., -1., 0., 0.],
892 : ],
893 : // sigmaz sigma-
894 : [
895 : [0., 0., 1., 0.],
896 : [3., 0., 0., 1.],
897 : [0., 0., 0., 0.],
898 : [0., 0., -1., 0.],
899 : ],
900 : // sigmaz sigmaz
901 : [
902 : [0., 0., 0., 0.],
903 : [0., -2., 0., 0.],
904 : [0., 0., 0., 0.],
905 : [0., 0., 0., -2.],
906 : ],
907 : ],
908 : ];
909 :
910 : /// OperatePragmaNoise trait creating necessary functions for a PRAGMA noise Operation.
911 : impl OperatePragmaNoise for PragmaGeneralNoise {
912 1 : fn superoperator(&self) -> Result<Array2<f64>, RoqoqoError> {
913 1 : let gate_time: f64 = f64::try_from(self.gate_time.clone())?;
914 : // Creating the superoperator that propagates the density matrix in vector form scaled by rate and time
915 1 : let mut superop = Matrix4::<f64>::default();
916 3 : for (i, row) in PGN_SUPEROP.iter().enumerate() {
917 9 : for (j, op) in row.iter().clone().enumerate() {
918 9 : let tmp_superop: Matrix4<f64> = (*op).into();
919 9 : superop += gate_time * self.rates[(i, j)] * tmp_superop;
920 9 : }
921 : }
922 : // Integrate superoperator for infinitesimal time to get superoperator for given rate and gate-time
923 : // Use exponential
924 1 : let mut exp_superop: Matrix4<f64> = superop.exp();
925 1 : // transpose because NAlgebra matrix iter is column major
926 1 : exp_superop.transpose_mut();
927 1 : let mut tmp_iter = exp_superop.iter();
928 1 : // convert to ndarray.
929 16 : let array: Array2<f64> = Array::from_shape_simple_fn((4, 4), || *tmp_iter.next().unwrap());
930 1 :
931 1 : Ok(array)
932 1 : }
933 :
934 : /// Returns the gate to the power of `power`.
935 0 : fn powercf(&self, power: CalculatorFloat) -> Self {
936 0 : let mut new = self.clone();
937 0 : new.gate_time = power * self.gate_time.clone();
938 0 : new
939 0 : }
940 : }
941 :
942 : /// The conditional PRAGMA operation.
943 : ///
944 : /// This PRAGMA executes a circuit when the condition bit/bool stored in a [crate::registers::BitRegister] is true.
945 : ///
946 18 : #[derive(Debug, Clone, PartialEq, roqoqo_derive::Operate, roqoqo_derive::OperatePragma)]
947 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
948 : pub struct PragmaConditional {
949 : /// The name of the [crate::registers::BitRegister] containting the condition bool value.
950 : condition_register: String,
951 : /// The index in the [crate::registers::BitRegister] containting the condition bool value.
952 : condition_index: usize,
953 : /// The circuit executed if the condition is met.
954 : circuit: Circuit,
955 : }
956 :
957 : #[allow(non_upper_case_globals)]
958 : const TAGS_PragmaConditional: &[&str; 3] = &["Operation", "PragmaOperation", "PragmaConditional"];
959 :
960 : // Implementing the InvolveQubits trait for PragmaConditional.
961 : impl InvolveQubits for PragmaConditional {
962 : /// Lists all involved qubits.
963 1 : fn involved_qubits(&self) -> InvolvedQubits {
964 1 : self.circuit.involved_qubits()
965 1 : }
966 : }
967 :
968 : /// Substitute trait allowing to replace symbolic parameters and to perform qubit mappings.
969 : impl Substitute for PragmaConditional {
970 : /// Remaps qubits in clone of the operation.
971 2 : fn remap_qubits(&self, mapping: &HashMap<usize, usize>) -> Result<Self, RoqoqoError> {
972 2 : let new_circuit = self.circuit.remap_qubits(mapping).unwrap();
973 2 : Ok(PragmaConditional::new(
974 2 : self.condition_register.clone(),
975 2 : self.condition_index,
976 2 : new_circuit,
977 2 : ))
978 2 : }
979 :
980 : /// Substitutes symbolic parameters in clone of the operation.
981 1 : fn substitute_parameters(&self, calculator: &mut Calculator) -> Result<Self, RoqoqoError> {
982 1 : let new_circuit = self.circuit.substitute_parameters(calculator).unwrap();
983 1 : Ok(PragmaConditional::new(
984 1 : self.condition_register.clone(),
985 1 : self.condition_index,
986 1 : new_circuit,
987 1 : ))
988 1 : }
989 : }
990 :
991 : /// A wrapper around backend specific PRAGMA operations capable of changing a device.
992 : ///
993 : /// This PRAGMA is a thin wrapper around device specific operations that can change
994 : /// device properties.
995 : ///
996 : /// # NOTE
997 : ///
998 : /// Since this PRAGMA uses serde and bincode to store a representation of the wrapped
999 : /// operation internally it is only available when roqoqo is built with the `serialize` feature
1000 0 : #[derive(Debug, Clone, PartialEq, roqoqo_derive::OperatePragma)]
1001 : #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1002 : pub struct PragmaChangeDevice {
1003 : /// The tags of the wrapped operation.
1004 : pub wrapped_tags: Vec<String>,
1005 : /// The hqslang name of the wrapped operation.
1006 : pub wrapped_hqslang: String,
1007 : /// Binary representation of the wrapped operation using serde and bincode.
1008 : pub wrapped_operation: Vec<u8>,
1009 : }
1010 : #[cfg_attr(feature = "dynamic", typetag::serde)]
1011 : impl Operate for PragmaChangeDevice {
1012 0 : fn tags(&self) -> &'static [&'static str] {
1013 0 : TAGS_PragmaChangeDevice
1014 0 : }
1015 0 : fn hqslang(&self) -> &'static str {
1016 0 : "PragmaChangeDevice"
1017 0 : }
1018 0 : fn is_parametrized(&self) -> bool {
1019 0 : false
1020 0 : }
1021 : }
1022 : impl PragmaChangeDevice {
1023 : #[cfg(feature = "serialize")]
1024 : pub fn new<T>(wrapped_pragma: &T) -> Result<Self, RoqoqoError>
1025 : where
1026 : T: Operate,
1027 : T: Serialize,
1028 : {
1029 : Ok(Self {
1030 : wrapped_tags: wrapped_pragma
1031 : .tags()
1032 : .iter()
1033 : .map(|x| x.to_string())
1034 : .collect(),
1035 : wrapped_hqslang: wrapped_pragma.hqslang().to_string(),
1036 : wrapped_operation: serialize(wrapped_pragma).map_err(|err| {
1037 : RoqoqoError::SerializationError {
1038 : msg: format!("{:?}", err),
1039 : }
1040 : })?,
1041 : })
1042 : }
1043 : }
1044 : #[allow(non_upper_case_globals)]
1045 : const TAGS_PragmaChangeDevice: &[&str; 3] = &["Operation", "PragmaOperation", "PragmaChangeDevice"];
1046 :
1047 : // Implementing the InvolveQubits trait for PragmaConditional.
1048 : impl InvolveQubits for PragmaChangeDevice {
1049 : /// Lists all involved qubits.
1050 0 : fn involved_qubits(&self) -> InvolvedQubits {
1051 0 : InvolvedQubits::All
1052 0 : }
1053 : }
1054 :
1055 : /// Substitute trait allowing to replace symbolic parameters and to perform qubit mappings.
1056 : impl Substitute for PragmaChangeDevice {
1057 : /// Remaps qubits in clone of the operation.
1058 : /// This is not supported for PragmaChangeDevice and should throw and error when a non-trivial remapping
1059 : /// is used
1060 0 : fn remap_qubits(&self, mapping: &HashMap<usize, usize>) -> Result<Self, RoqoqoError> {
1061 0 : match mapping.iter().find(|(x, y)| x != y) {
1062 0 : Some((x, _)) => Err(RoqoqoError::QubitMappingError { qubit: *x }),
1063 0 : None => Ok(self.clone()),
1064 : }
1065 0 : }
1066 :
1067 : #[allow(unused_variables)]
1068 : /// Substitutes symbolic parameters in clone of the operation.
1069 0 : fn substitute_parameters(&self, calculator: &mut Calculator) -> Result<Self, RoqoqoError> {
1070 0 : Ok(self.clone())
1071 0 : }
1072 : }
|