microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7421e7dd1015dcbd940bf843d33583470de580ea

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/noisy_simulator/src/operation.rs

154lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This module contains the `Operation` struct and the `operation!` macro
5//! to conveniently construct operations from Kraus matrices.
6
7#[cfg(test)]
8mod tests;
9use crate::{Error, SquareMatrix};
10
11/// A helper macro to write operations more conveniently.
12///
13/// Example usage:
14/// ```
15/// // Create operation from two 2x2 Kraus matrices.
16/// use noisy_simulator::{Operation, operation};
17///
18/// let op = operation!(
19/// [1., 0.;
20/// 0., 0.;],
21/// [0., 0.;
22/// 0., 0.;]
23/// ).expect("operation should be valid");
24/// ```
25#[macro_export]
26macro_rules! operation {
27 ($([$($($v:expr),* );*]),*) => {
28 Operation::new(vec![
29 $(nalgebra::dmatrix![
30 $($(num_complex::Complex::<f64>::from($v)),* );*
31 ]),*
32 ])
33 };
34}
35
36#[cfg(test)]
37pub(crate) use operation;
38
39/// This struct represents a quantum operation.
40/// A quantum operation is a linear transformation that maps a valid density
41/// matrix to another valid density matrices.
42#[derive(Clone)]
43pub struct Operation {
44 number_of_qubits: usize,
45 kraus_operators: Vec<SquareMatrix>,
46 #[allow(clippy::struct_field_names)]
47 operation_matrix: SquareMatrix,
48 effect_matrix: SquareMatrix,
49 effect_matrix_transpose: SquareMatrix,
50}
51
52impl Operation {
53 /// Construct an operation from a list of Kraus operators.
54 /// Matrices must be of dimension 2^k x 2^k, where k is an integer.
55 /// Returns `None` if the kraus matrices are ill formed.
56 pub fn new(mut kraus_operators: Vec<SquareMatrix>) -> Result<Self, Error> {
57 let (dim, _) = kraus_operators
58 .first()
59 .ok_or(Error::FailedToConstructOperation(
60 "there should be at least one Kraus Operator".to_string(),
61 ))?
62 .shape();
63
64 let number_of_qubits = dim.ilog2() as usize;
65 if 1 << number_of_qubits != dim {
66 return Err(Error::FailedToConstructOperation(
67 "kraus operators should have dimensions 2^k x 2^k".to_string(),
68 ));
69 }
70
71 for kraus_operator in &kraus_operators {
72 let (rows, cols) = kraus_operator.shape();
73 if rows != dim || cols != dim {
74 return Err(Error::FailedToConstructOperation(
75 "kraus operators should be square matrices and have the same dimensions"
76 .to_string(),
77 ));
78 }
79 }
80
81 // Performance note: Because `nalgebra` stores its matrices in column major
82 // form, we use `gemv_tr` in the `apply_kernel` function when multiplying to avoid
83 // incurring cache misses. That is why we transpose all Kraus operators when they
84 // enter the simulator:
85 // `gemv(1, matrix, vec, 0)` is equivalent to `gemv_tr(1, matrix_tr, vec, 0)`,
86 // but the later has much better performance.
87 //
88 // SAFETY of transposing: all these matrices are only consumed by the
89 // `kernel.rs/apply_kernel` function which effectively transposes them
90 // back when multypling, so it is safe to do this transformation.
91 for kraus_operator in &mut kraus_operators {
92 kraus_operator.transpose_mut();
93 }
94
95 // Performance note: The effect_matrix = Σᵢ (kᵢ† ⋅ k), but due to performance
96 // reasons described above we want to store its transpose. But (A ⋅ B)^T = B^T ⋅ A^T.
97 // Therefore, we have to swap the order of the factors.
98 let effect_matrix: SquareMatrix = kraus_operators.iter().map(|k| k * k.adjoint()).sum();
99
100 // Performance note: (A ⊗ B)^T = A^T ⊗ B^T, so we don't need to change
101 // anything here.
102 let operation_matrix: SquareMatrix = kraus_operators
103 .iter()
104 .map(|k| k.kronecker(&k.conjugate()))
105 .sum();
106
107 let effect_matrix_transpose = effect_matrix.transpose();
108
109 Ok(Self {
110 number_of_qubits,
111 kraus_operators,
112 operation_matrix,
113 effect_matrix,
114 effect_matrix_transpose,
115 })
116 }
117
118 /// Return matrix representation:
119 /// Σᵢ (Kᵢ ⊗ Kᵢ*)
120 /// where Kᵢ are Kraus operators, ⊗ is the Kronecker product
121 /// and * denotes the complex conjugate of the matrix.
122 #[must_use]
123 pub fn matrix(&self) -> &SquareMatrix {
124 &self.operation_matrix
125 }
126
127 /// Returns effect matrix:
128 /// Σᵢ (Kᵢ Kᵢ†)
129 /// where Kᵢ are Kraus operators and † denotes the adjoint of the matrix.
130 #[must_use]
131 pub fn effect_matrix(&self) -> &SquareMatrix {
132 &self.effect_matrix
133 }
134
135 /// Return transpose of effect matrix:
136 /// Σᵢ (Kᵢ Kᵢ†)^T
137 /// where Kᵢ are Kraus operators and † denotes the adjoint of the matrix.
138 #[must_use]
139 pub fn effect_matrix_transpose(&self) -> &SquareMatrix {
140 &self.effect_matrix_transpose
141 }
142
143 /// Return list of Kraus operators.
144 #[must_use]
145 pub fn kraus_operators(&self) -> &Vec<SquareMatrix> {
146 &self.kraus_operators
147 }
148
149 /// Return the number of qubits that the operation acts on.
150 #[must_use]
151 pub fn number_of_qubits(&self) -> usize {
152 self.number_of_qubits
153 }
154}
155