Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
noise_channel.cpp
Go to the documentation of this file.
1 /*
2 Copyright 2025 SQUANDER Contributors
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 
17 #include "noise_channel.h"
18 #include <cmath>
19 
20 namespace squander {
21 namespace density {
22 
23 // ================================================================
24 // Depolarizing Channel
25 // ================================================================
26 
28  : qbit_num_(qbit_num), error_rate_(error_rate) {
29  if (error_rate < 0.0 || error_rate > 1.0) {
30  throw std::invalid_argument(
31  "DepolarizingChannel: error_rate must be in [0,1]");
32  }
33 }
34 
36  int dim = rho.get_dim();
37 
38  if (rho.get_qbit_num() != qbit_num_) {
39  throw std::runtime_error(
40  "DepolarizingChannel::apply: qubit number mismatch");
41  }
42 
43  // Compute trace (for normalization check)
44  QGD_Complex16 tr = rho.trace();
45  double trace_val = tr.real;
46 
47  // Apply: ρ → (1-p)ρ + (p/dim)Tr(ρ)·I
48  double depol_prob = error_rate_ / dim;
49  double identity_factor = depol_prob * trace_val;
50 
51  for (int i = 0; i < dim; i++) {
52  for (int j = 0; j < dim; j++) {
53  QGD_Complex16 &element = rho(i, j);
54 
55  if (i == j) {
56  // Diagonal: (1-p)ρ(i,i) + p/dim
57  element.real = (1.0 - error_rate_) * element.real + identity_factor;
58  element.imag = (1.0 - error_rate_) * element.imag;
59  } else {
60  // Off-diagonal: (1-p)ρ(i,j)
61  element.real *= (1.0 - error_rate_);
62  element.imag *= (1.0 - error_rate_);
63  }
64  }
65  }
66 }
67 
68 // ================================================================
69 // Amplitude Damping Channel
70 // ================================================================
71 
73  : target_qbit_(target_qbit), gamma_(gamma) {
74  if (gamma < 0.0 || gamma > 1.0) {
75  throw std::invalid_argument(
76  "AmplitudeDampingChannel: gamma must be in [0,1]");
77  }
78 }
79 
81  int dim = rho.get_dim();
82  int qbit_num = rho.get_qbit_num();
83 
84  if (target_qbit_ < 0 || target_qbit_ >= qbit_num) {
85  throw std::runtime_error(
86  "AmplitudeDampingChannel::apply: target_qbit out of range");
87  }
88 
89  // Kraus operators for amplitude damping:
90  // K₀ = [[1, 0], [0, √(1-γ)]]
91  // K₁ = [[0, √γ], [0, 0]]
92 
93  // Create temporary storage for new density matrix
94  DensityMatrix rho_new(qbit_num);
95  memset(rho_new.data, 0, dim * dim * sizeof(QGD_Complex16));
96 
97  int target_step = 1 << target_qbit_;
98 
99  // Apply Kraus operators
100  for (int i = 0; i < dim; i++) {
101  for (int j = 0; j < dim; j++) {
102 
103  // Determine states of target qubit
104  bool i_target = (i & target_step) != 0;
105  bool j_target = (j & target_step) != 0;
106 
107  // Apply K₀ρK₀†
108  if (i_target == j_target) {
109  double factor = i_target ? (1.0 - gamma_) : 1.0;
110  rho_new(i, j).real += factor * rho(i, j).real;
111  rho_new(i, j).imag += factor * rho(i, j).imag;
112  }
113 
114  // Apply K₁ρK₁†: [[0, √γ], [0, 0]] ρ [[0, 0], [√γ, 0]]
115  if (!i_target && !j_target) {
116  int i_flip = i | target_step;
117  int j_flip = j | target_step;
118  rho_new(i, j).real += gamma_ * rho(i_flip, j_flip).real;
119  rho_new(i, j).imag += gamma_ * rho(i_flip, j_flip).imag;
120  }
121  }
122  }
123 
124  // Copy result back
125  memcpy(rho.data, rho_new.data, dim * dim * sizeof(QGD_Complex16));
126 }
127 
128 // ================================================================
129 // Phase Damping Channel
130 // ================================================================
131 
133  : target_qbit_(target_qbit), lambda_(lambda) {
134  if (lambda < 0.0 || lambda > 1.0) {
135  throw std::invalid_argument("PhaseDampingChannel: lambda must be in [0,1]");
136  }
137 }
138 
140  int dim = rho.get_dim();
141  int qbit_num = rho.get_qbit_num();
142 
143  if (target_qbit_ < 0 || target_qbit_ >= qbit_num) {
144  throw std::runtime_error(
145  "PhaseDampingChannel::apply: target_qbit out of range");
146  }
147 
148  // Kraus operators for phase damping:
149  // K₀ = [[1, 0], [0, √(1-λ)]]
150  // K₁ = [[0, 0], [0, √λ]]
151 
152  int target_step = 1 << target_qbit_;
153 
154  // Phase damping only affects off-diagonal elements between |0⟩ and |1⟩
155  for (int i = 0; i < dim; i++) {
156  for (int j = 0; j < dim; j++) {
157  bool i_target = (i & target_step) != 0;
158  bool j_target = (j & target_step) != 0;
159 
160  // If target qubit states differ, reduce coherence
161  if (i_target != j_target) {
162  rho(i, j).real *= std::sqrt(1.0 - lambda_);
163  rho(i, j).imag *= std::sqrt(1.0 - lambda_);
164  }
165  // Diagonal elements unchanged
166  }
167  }
168 }
169 
170 } // namespace density
171 } // namespace squander
void apply(DensityMatrix &rho) override
Apply noise channel to density matrix.
PhaseDampingChannel(int target_qbit, double lambda)
Constructor.
void apply(DensityMatrix &rho) override
Apply noise channel to density matrix.
scalar * data
pointer to the stored data
Definition: matrix_base.hpp:48
DepolarizingChannel(int qbit_num, double error_rate)
Constructor.
double lambda_
λ = 1 - exp(-t/T2)
AmplitudeDampingChannel(int target_qbit, double gamma)
Constructor.
Quantum density matrix ρ for mixed-state representation.
QGD_Complex16 trace() const
Calculate trace: Tr(ρ)
Structure type representing complex numbers in the SQUANDER package.
Definition: QGDTypes.h:38
double gamma_
γ = 1 - exp(-t/T1)
int get_qbit_num() const
Get number of qubits.
void apply(DensityMatrix &rho) override
Apply noise channel to density matrix.
int get_dim() const
Get matrix dimension (2^qbit_num)
double real
the real part of a complex number
Definition: QGDTypes.h:40
double imag
the imaginary part of a complex number
Definition: QGDTypes.h:42