Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
noise_operation.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_operation.h"
18 #include "density_matrix.h"
19 #include "matrix.h"
20 #include <cmath>
21 #include <cstring>
22 #include <stdexcept>
23 
24 namespace squander {
25 namespace density {
26 
27 // ================================================================
28 // DepolarizingOp
29 // ================================================================
30 
32  : qbit_num_(qbit_num), fixed_error_rate_(error_rate), is_parametric_(false) {
33  if (qbit_num < 1) {
34  throw std::invalid_argument("DepolarizingOp: qbit_num must be >= 1");
35  }
36  if (error_rate < 0.0 || error_rate > 1.0) {
37  throw std::invalid_argument("DepolarizingOp: error_rate must be in [0, 1]");
38  }
39 }
40 
42  : qbit_num_(qbit_num), fixed_error_rate_(0.0), is_parametric_(true) {
43  if (qbit_num < 1) {
44  throw std::invalid_argument("DepolarizingOp: qbit_num must be >= 1");
45  }
46 }
47 
48 void DepolarizingOp::apply_to_density(const double *params, int param_count,
49  DensityMatrix &rho) {
50  double error_rate;
51  if (is_parametric_) {
52  if (param_count < 1 || params == nullptr) {
53  throw std::runtime_error(
54  "DepolarizingOp: parametric mode requires 1 parameter");
55  }
56  error_rate = params[0];
57  // Clamp to valid range
58  if (error_rate < 0.0)
59  error_rate = 0.0;
60  if (error_rate > 1.0)
61  error_rate = 1.0;
62  } else {
63  error_rate = fixed_error_rate_;
64  }
65 
66  apply_depolarizing(rho, error_rate);
67 }
68 
70  if (rho.get_qbit_num() != qbit_num_) {
71  throw std::runtime_error(
72  "DepolarizingOp: qubit number mismatch with density matrix");
73  }
74 
75  int dim = rho.get_dim();
76 
77  // Compute trace for normalization
78  QGD_Complex16 tr = rho.trace();
79  double trace_val = tr.real;
80 
81  // Apply: ρ → (1-p)ρ + (p/dim)·Tr(ρ)·I
82  double identity_factor = (p / dim) * trace_val;
83 
84  for (int i = 0; i < dim; i++) {
85  for (int j = 0; j < dim; j++) {
86  QGD_Complex16 &element = rho(i, j);
87 
88  if (i == j) {
89  // Diagonal: (1-p)ρ(i,i) + p/dim
90  element.real = (1.0 - p) * element.real + identity_factor;
91  element.imag = (1.0 - p) * element.imag;
92  } else {
93  // Off-diagonal: (1-p)ρ(i,j)
94  element.real *= (1.0 - p);
95  element.imag *= (1.0 - p);
96  }
97  }
98  }
99 }
100 
101 std::unique_ptr<IDensityOperation> DepolarizingOp::clone() const {
102  if (is_parametric_) {
103  return std::unique_ptr<DepolarizingOp>(new DepolarizingOp(qbit_num_));
104  } else {
105  return std::unique_ptr<DepolarizingOp>(new DepolarizingOp(qbit_num_, fixed_error_rate_));
106  }
107 }
108 
109 // ================================================================
110 // LocalDepolarizingOp
111 // ================================================================
112 
114  : target_qbit_(target_qbit), fixed_error_rate_(error_rate),
115  is_parametric_(false) {
116  if (target_qbit < 0) {
117  throw std::invalid_argument(
118  "LocalDepolarizingOp: target_qbit must be >= 0");
119  }
120  if (error_rate < 0.0 || error_rate > 1.0) {
121  throw std::invalid_argument(
122  "LocalDepolarizingOp: error_rate must be in [0, 1]");
123  }
124 }
125 
127  : target_qbit_(target_qbit), fixed_error_rate_(0.0), is_parametric_(true) {
128  if (target_qbit < 0) {
129  throw std::invalid_argument(
130  "LocalDepolarizingOp: target_qbit must be >= 0");
131  }
132 }
133 
134 void LocalDepolarizingOp::apply_to_density(const double *params, int param_count,
135  DensityMatrix &rho) {
136  double error_rate;
137  if (is_parametric_) {
138  if (param_count < 1 || params == nullptr) {
139  throw std::runtime_error(
140  "LocalDepolarizingOp: parametric mode requires 1 parameter");
141  }
142  error_rate = params[0];
143  if (error_rate < 0.0)
144  error_rate = 0.0;
145  if (error_rate > 1.0)
146  error_rate = 1.0;
147  } else {
148  error_rate = fixed_error_rate_;
149  }
150 
151  apply_local_depolarizing(rho, error_rate);
152 }
153 
155  double error_rate) {
156  int qbit_num = rho.get_qbit_num();
157  if (target_qbit_ >= qbit_num) {
158  throw std::runtime_error(
159  "LocalDepolarizingOp: target_qbit out of range for density matrix");
160  }
161 
162  Matrix x_kernel(2, 2);
163  Matrix y_kernel(2, 2);
164  Matrix z_kernel(2, 2);
165  memset(x_kernel.get_data(), 0, 4 * sizeof(QGD_Complex16));
166  memset(y_kernel.get_data(), 0, 4 * sizeof(QGD_Complex16));
167  memset(z_kernel.get_data(), 0, 4 * sizeof(QGD_Complex16));
168 
169  x_kernel.get_data()[1].real = 1.0;
170  x_kernel.get_data()[2].real = 1.0;
171 
172  y_kernel.get_data()[1].imag = -1.0;
173  y_kernel.get_data()[2].imag = 1.0;
174 
175  z_kernel.get_data()[0].real = 1.0;
176  z_kernel.get_data()[3].real = -1.0;
177 
178  DensityMatrix rho_x = rho.clone();
179  DensityMatrix rho_y = rho.clone();
180  DensityMatrix rho_z = rho.clone();
181  rho_x.apply_single_qubit_unitary(x_kernel, target_qbit_);
182  rho_y.apply_single_qubit_unitary(y_kernel, target_qbit_);
183  rho_z.apply_single_qubit_unitary(z_kernel, target_qbit_);
184 
185  const double identity_weight = 1.0 - 0.75 * error_rate;
186  const double pauli_weight = 0.25 * error_rate;
187  int dim = rho.get_dim();
188  for (int i = 0; i < dim * dim; ++i) {
189  rho.data[i].real =
190  identity_weight * rho.data[i].real +
191  pauli_weight *
192  (rho_x.data[i].real + rho_y.data[i].real + rho_z.data[i].real);
193  rho.data[i].imag =
194  identity_weight * rho.data[i].imag +
195  pauli_weight *
196  (rho_x.data[i].imag + rho_y.data[i].imag + rho_z.data[i].imag);
197  }
198 }
199 
200 std::unique_ptr<IDensityOperation> LocalDepolarizingOp::clone() const {
201  if (is_parametric_) {
202  return std::unique_ptr<LocalDepolarizingOp>(
204  } else {
205  return std::unique_ptr<LocalDepolarizingOp>(
207  }
208 }
209 
210 // ================================================================
211 // AmplitudeDampingOp
212 // ================================================================
213 
215  : target_qbit_(target_qbit), fixed_gamma_(gamma), is_parametric_(false) {
216  if (target_qbit < 0) {
217  throw std::invalid_argument(
218  "AmplitudeDampingOp: target_qbit must be >= 0");
219  }
220  if (gamma < 0.0 || gamma > 1.0) {
221  throw std::invalid_argument("AmplitudeDampingOp: gamma must be in [0, 1]");
222  }
223 }
224 
226  : target_qbit_(target_qbit), fixed_gamma_(0.0), is_parametric_(true) {
227  if (target_qbit < 0) {
228  throw std::invalid_argument(
229  "AmplitudeDampingOp: target_qbit must be >= 0");
230  }
231 }
232 
233 void AmplitudeDampingOp::apply_to_density(const double *params, int param_count,
234  DensityMatrix &rho) {
235  double gamma;
236  if (is_parametric_) {
237  if (param_count < 1 || params == nullptr) {
238  throw std::runtime_error(
239  "AmplitudeDampingOp: parametric mode requires 1 parameter");
240  }
241  gamma = params[0];
242  // Clamp to valid range
243  if (gamma < 0.0)
244  gamma = 0.0;
245  if (gamma > 1.0)
246  gamma = 1.0;
247  } else {
248  gamma = fixed_gamma_;
249  }
250 
251  apply_amplitude_damping(rho, gamma);
252 }
253 
255  double gamma) {
256  int dim = rho.get_dim();
257  int qbit_num = rho.get_qbit_num();
258 
259  if (target_qbit_ >= qbit_num) {
260  throw std::runtime_error(
261  "AmplitudeDampingOp: target_qbit out of range for density matrix");
262  }
263 
264  // Kraus operators for amplitude damping on target qubit:
265  // K₀ = [[1, 0], [0, √(1-γ)]]
266  // K₁ = [[0, √γ], [0, 0]]
267  //
268  // ρ' = K₀ρK₀† + K₁ρK₁†
269 
270  // Create temporary storage
271  DensityMatrix rho_new(qbit_num);
272  memset(rho_new.data, 0, dim * dim * sizeof(QGD_Complex16));
273 
274  int target_step = 1 << target_qbit_;
275  double sqrt_one_minus_gamma = std::sqrt(1.0 - gamma);
276 
277  for (int i = 0; i < dim; i++) {
278  for (int j = 0; j < dim; j++) {
279  bool i_target = (i & target_step) != 0;
280  bool j_target = (j & target_step) != 0;
281 
282  // Apply K₀ρK₀†
283  if (i_target == j_target) {
284  double factor = i_target ? (1.0 - gamma) : 1.0;
285  rho_new(i, j).real += factor * rho(i, j).real;
286  rho_new(i, j).imag += factor * rho(i, j).imag;
287  } else {
288  // Off-diagonal between |0⟩ and |1⟩ states: multiply by √(1-γ)
289  double factor = sqrt_one_minus_gamma;
290  rho_new(i, j).real += factor * rho(i, j).real;
291  rho_new(i, j).imag += factor * rho(i, j).imag;
292  }
293 
294  // Apply K₁ρK₁†: maps |1⟩⟨1| → γ|0⟩⟨0|
295  if (!i_target && !j_target) {
296  int i_flip = i | target_step;
297  int j_flip = j | target_step;
298  rho_new(i, j).real += gamma * rho(i_flip, j_flip).real;
299  rho_new(i, j).imag += gamma * rho(i_flip, j_flip).imag;
300  }
301  }
302  }
303 
304  // Copy result back
305  memcpy(rho.data, rho_new.data, dim * dim * sizeof(QGD_Complex16));
306 }
307 
308 std::unique_ptr<IDensityOperation> AmplitudeDampingOp::clone() const {
309  if (is_parametric_) {
310  return std::unique_ptr<AmplitudeDampingOp>(new AmplitudeDampingOp(target_qbit_));
311  } else {
312  return std::unique_ptr<AmplitudeDampingOp>(new AmplitudeDampingOp(target_qbit_, fixed_gamma_));
313  }
314 }
315 
316 // ================================================================
317 // PhaseDampingOp
318 // ================================================================
319 
321  : target_qbit_(target_qbit), fixed_lambda_(lambda), is_parametric_(false) {
322  if (target_qbit < 0) {
323  throw std::invalid_argument("PhaseDampingOp: target_qbit must be >= 0");
324  }
325  if (lambda < 0.0 || lambda > 1.0) {
326  throw std::invalid_argument("PhaseDampingOp: lambda must be in [0, 1]");
327  }
328 }
329 
331  : target_qbit_(target_qbit), fixed_lambda_(0.0), is_parametric_(true) {
332  if (target_qbit < 0) {
333  throw std::invalid_argument("PhaseDampingOp: target_qbit must be >= 0");
334  }
335 }
336 
337 void PhaseDampingOp::apply_to_density(const double *params, int param_count,
338  DensityMatrix &rho) {
339  double lambda;
340  if (is_parametric_) {
341  if (param_count < 1 || params == nullptr) {
342  throw std::runtime_error(
343  "PhaseDampingOp: parametric mode requires 1 parameter");
344  }
345  lambda = params[0];
346  // Clamp to valid range
347  if (lambda < 0.0)
348  lambda = 0.0;
349  if (lambda > 1.0)
350  lambda = 1.0;
351  } else {
352  lambda = fixed_lambda_;
353  }
354 
355  apply_phase_damping(rho, lambda);
356 }
357 
359  int dim = rho.get_dim();
360  int qbit_num = rho.get_qbit_num();
361 
362  if (target_qbit_ >= qbit_num) {
363  throw std::runtime_error(
364  "PhaseDampingOp: target_qbit out of range for density matrix");
365  }
366 
367  // Phase damping only affects off-diagonal elements between |0⟩ and |1⟩
368  // ρ(i,j) → √(1-λ) · ρ(i,j) when target qubit states differ
369 
370  int target_step = 1 << target_qbit_;
371  double sqrt_one_minus_lambda = std::sqrt(1.0 - lambda);
372 
373  for (int i = 0; i < dim; i++) {
374  for (int j = 0; j < dim; j++) {
375  bool i_target = (i & target_step) != 0;
376  bool j_target = (j & target_step) != 0;
377 
378  // If target qubit states differ, reduce coherence
379  if (i_target != j_target) {
380  rho(i, j).real *= sqrt_one_minus_lambda;
381  rho(i, j).imag *= sqrt_one_minus_lambda;
382  }
383  // Diagonal elements unchanged
384  }
385  }
386 }
387 
388 std::unique_ptr<IDensityOperation> PhaseDampingOp::clone() const {
389  if (is_parametric_) {
390  return std::unique_ptr<PhaseDampingOp>(new PhaseDampingOp(target_qbit_));
391  } else {
392  return std::unique_ptr<PhaseDampingOp>(new PhaseDampingOp(target_qbit_, fixed_lambda_));
393  }
394 }
395 
396 } // namespace density
397 } // namespace squander
398 
std::unique_ptr< IDensityOperation > clone() const override
Create a deep copy of this operation.
std::unique_ptr< IDensityOperation > clone() const override
Create a deep copy of this operation.
void apply_amplitude_damping(DensityMatrix &rho, double gamma)
DepolarizingOp(int qbit_num, double error_rate)
Create depolarizing noise with fixed error rate.
scalar * data
pointer to the stored data
Definition: matrix_base.hpp:48
AmplitudeDampingOp(int target_qbit, double gamma)
Create amplitude damping with fixed gamma.
void apply_phase_damping(DensityMatrix &rho, double lambda)
void apply_to_density(const double *params, int param_count, DensityMatrix &rho) override
Apply this operation to the density matrix.
scalar * get_data() const
Call to get the pointer to the stored data.
void apply_to_density(const double *params, int param_count, DensityMatrix &rho) override
Apply this operation to the density matrix.
DensityMatrix clone() const
Create deep copy.
void apply_local_depolarizing(DensityMatrix &rho, double error_rate)
PhaseDampingOp(int target_qbit, double lambda)
Create phase damping with fixed lambda.
std::unique_ptr< IDensityOperation > clone() const override
Create a deep copy of this operation.
Header file of complex array storage array with automatic and thread safe reference counting...
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
int get_qbit_num() const
Get number of qubits.
Double-precision complex matrix (float64).
Definition: matrix.h:38
void apply_to_density(const double *params, int param_count, DensityMatrix &rho) override
Apply this operation to the density matrix.
int get_dim() const
Get matrix dimension (2^qbit_num)
std::unique_ptr< IDensityOperation > clone() const override
Create a deep copy of this operation.
double real
the real part of a complex number
Definition: QGDTypes.h:40
void apply_to_density(const double *params, int param_count, DensityMatrix &rho) override
Apply this operation to the density matrix.
LocalDepolarizingOp(int target_qbit, double error_rate)
void apply_depolarizing(DensityMatrix &rho, double error_rate)
void apply_single_qubit_unitary(const matrix_base< QGD_Complex16 > &u_2x2, int target_qbit)
Apply single-qubit unitary using local kernel (optimized)
double imag
the imaginary part of a complex number
Definition: QGDTypes.h:42