9#ifndef _SIMULATORS_QUANTUM_CHANNEL_H_
10#define _SIMULATORS_QUANTUM_CHANNEL_H_
48 return krausOperators_;
63 return pauliProbabilities_;
81 if (tolerance < 0.0 || !std::isfinite(tolerance))
82 throw std::invalid_argument(
"Quantum-channel comparison tolerance must be finite and nonnegative");
83 if (numQubits_ != other.numQubits_)
88 if (left.rows() != right.rows() || left.cols() != right.cols())
90 return (left - right).cwiseAbs().maxCoeff() <= tolerance;
100 const Eigen::Index dimension = krausOperators_.front().rows();
101 const Eigen::Index choiDimension = dimension * dimension;
102 Matrix choi = Matrix::Zero(choiDimension, choiDimension);
103 for (
const Matrix &krausOperator : krausOperators_)
105 const Eigen::Map<const Eigen::VectorXcd> vectorized(krausOperator.data(), choiDimension);
106 choi.noalias() += vectorized * vectorized.adjoint();
114 return Pauli({1.0 - CheckedProbability(probability), probability, 0.0, 0.0});
120 return Pauli({1.0 - CheckedProbability(probability), 0.0, probability, 0.0});
126 return Pauli({1.0 - CheckedProbability(probability), 0.0, 0.0, probability});
135 CheckedProbability(px);
136 CheckedProbability(py);
137 CheckedProbability(pz);
138 const double total = px + py + pz;
139 if (!std::isfinite(total) || total > 1.0 + kProbabilityTolerance)
140 throw std::invalid_argument(
"Pauli error probabilities must sum to at most one");
141 return Pauli({std::max(0.0, 1.0 - total), px, py, pz});
153 if (probabilities.empty())
154 throw std::invalid_argument(
"A Pauli channel must contain probabilities");
156 size_t count = probabilities.size();
157 size_t numQubits = 0;
158 while (count > 1 && count % 4 == 0)
163 if (count != 1 || numQubits == 0)
164 throw std::invalid_argument(
"A Pauli channel needs exactly 4^n probabilities for n >= 1");
167 for (
const double probability : probabilities)
169 CheckedProbability(probability);
170 total += probability;
172 if (!std::isfinite(total) || std::abs(total - 1.0) > kProbabilityTolerance)
173 throw std::invalid_argument(
"Pauli channel probabilities must sum to one");
176 kraus.reserve(
static_cast<size_t>(std::count_if(probabilities.begin(), probabilities.end(), [](
double probability) { return probability > 0.0; })));
177 for (
size_t index = 0; index < probabilities.size(); ++index)
179 if (probabilities[index] == 0.0)
185 for (
size_t q = 0; q < numQubits; ++q)
187 for (
size_t q = numQubits; q-- > 0;)
190 const size_t pauliId = (index / divisor) % 4;
191 pauli = Kronecker(pauli, PauliMatrix(pauliId));
193 kraus.emplace_back(std::sqrt(probabilities[index]) * pauli);
196 channel.pauliProbabilities_ = probabilities;
206 CheckedProbability(errorProbability);
207 return Pauli({1.0 - errorProbability, errorProbability / 3.0, errorProbability / 3.0, errorProbability / 3.0});
216 CheckedProbability(mixingProbability);
223 CheckedProbability(gamma);
224 Matrix k0 = Matrix::Zero(2, 2);
226 k0(1, 1) = std::sqrt(1.0 - gamma);
227 Matrix k1 = Matrix::Zero(2, 2);
228 k1(0, 1) = std::sqrt(gamma);
238 CheckedProbability(gamma);
239 Matrix k0 = Matrix::Zero(2, 2);
241 k0(1, 1) = std::sqrt(1.0 - gamma);
242 Matrix k1 = Matrix::Zero(2, 2);
243 k1(1, 1) = std::sqrt(gamma);
253 CheckedProbability(gamma);
254 CheckedProbability(excitedStatePopulation);
255 const double groundStatePopulation = 1.0 - excitedStatePopulation;
257 Matrix k0 = Matrix::Zero(2, 2);
258 k0(0, 0) = std::sqrt(groundStatePopulation);
259 k0(1, 1) = std::sqrt(groundStatePopulation * (1.0 - gamma));
260 Matrix k1 = Matrix::Zero(2, 2);
261 k1(0, 1) = std::sqrt(groundStatePopulation * gamma);
262 Matrix k2 = Matrix::Zero(2, 2);
263 k2(0, 0) = std::sqrt(excitedStatePopulation * (1.0 - gamma));
264 k2(1, 1) = std::sqrt(excitedStatePopulation);
265 Matrix k3 = Matrix::Zero(2, 2);
266 k3(1, 0) = std::sqrt(excitedStatePopulation * gamma);
267 return QuantumChannel({std::move(k0), std::move(k1), std::move(k2), std::move(k3)});
280 if (!std::isfinite(duration) || duration < 0.0)
281 throw std::invalid_argument(
"Thermal-relaxation duration must be finite and nonnegative");
282 ValidatePositiveTime(t1,
"T1");
283 ValidatePositiveTime(t2,
"T2");
284 CheckedProbability(excitedStatePopulation);
286 if (std::isinf(t2) && !std::isinf(t1))
287 throw std::invalid_argument(
"Thermal relaxation requires T2 <= 2*T1");
288 if (std::isfinite(t1) && std::isfinite(t2) && t2 > 2.0 * t1 * (1.0 + kProbabilityTolerance))
289 throw std::invalid_argument(
"Thermal relaxation requires T2 <= 2*T1");
291 const double coherenceMultiplier = std::isinf(t2) ? 1.0 : std::exp(-duration / t2);
292 const double gamma = std::isinf(t1) ? 0.0 : -std::expm1(-duration / t1);
293 const double up = gamma * excitedStatePopulation;
294 const double down = gamma * (1.0 - excitedStatePopulation);
299 const double a = 1.0 - up;
300 const double c = 1.0 - down;
301 if (coherenceMultiplier * coherenceMultiplier > a * c + kProbabilityTolerance)
302 throw std::invalid_argument(
"The supplied T1 and T2 do not define a completely-positive map");
304 const double discriminant = std::hypot(a - c, 2.0 * coherenceMultiplier);
305 const double lambdaPlus = 0.5 * (a + c + discriminant);
306 const double lambdaMinus = std::max(0.0, 0.5 * (a + c - discriminant));
307 const double angle = 0.5 * std::atan2(2.0 * coherenceMultiplier, a - c);
308 const double cosine = std::cos(angle);
309 const double sine = std::sin(angle);
312 if (lambdaPlus > kZeroTolerance)
314 Matrix diagonal = Matrix::Zero(2, 2);
315 diagonal(0, 0) = std::sqrt(lambdaPlus) * cosine;
316 diagonal(1, 1) = std::sqrt(lambdaPlus) * sine;
317 kraus.emplace_back(std::move(diagonal));
319 if (lambdaMinus > kZeroTolerance)
321 Matrix diagonal = Matrix::Zero(2, 2);
322 diagonal(0, 0) = -std::sqrt(lambdaMinus) * sine;
323 diagonal(1, 1) = std::sqrt(lambdaMinus) * cosine;
324 kraus.emplace_back(std::move(diagonal));
326 if (up > kZeroTolerance)
328 Matrix excitation = Matrix::Zero(2, 2);
329 excitation(1, 0) = std::sqrt(up);
330 kraus.emplace_back(std::move(excitation));
332 if (down > kZeroTolerance)
334 Matrix decay = Matrix::Zero(2, 2);
335 decay(0, 1) = std::sqrt(down);
336 kraus.emplace_back(std::move(decay));
354 CheckedProbability(probability);
355 CheckedProbability(correlation);
356 std::vector<double> probabilities(16, 0.0);
357 const double independent = 1.0 - correlation;
358 probabilities[0] = independent * (1.0 - probability) * (1.0 - probability) + correlation * (1.0 - probability);
359 probabilities[3] = independent * probability * (1.0 - probability);
360 probabilities[12] = probabilities[3];
361 probabilities[15] = independent * probability * probability + correlation * probability;
362 return Pauli(probabilities);
371 CheckedProbability(errorProbability);
372 std::vector<double> probabilities(16, errorProbability / 15.0);
373 probabilities[0] = 1.0 - errorProbability;
374 return Pauli(probabilities);
380 CheckedProbability(mixingProbability);
385 static constexpr double kProbabilityTolerance = 1e-12;
386 static constexpr double kZeroTolerance = 1e-15;
388 static double CheckedProbability(
double probability)
390 if (!std::isfinite(probability) || probability < 0.0 || probability > 1.0)
391 throw std::invalid_argument(
"Quantum-channel probabilities must be finite and in [0, 1]");
395 static void ValidatePositiveTime(
double value,
const char *name)
397 if (std::isnan(value) || value <= 0.0)
398 throw std::invalid_argument(std::string(name) +
" must be positive (infinity is allowed)");
401 static Matrix PauliMatrix(
size_t pauliId)
403 Matrix matrix = Matrix::Zero(2, 2);
407 matrix.setIdentity();
414 matrix(0, 1) = std::complex<double>(0.0, -1.0);
415 matrix(1, 0) = std::complex<double>(0.0, 1.0);
422 throw std::invalid_argument(
"Invalid Pauli index");
429 Matrix result(left.rows() * right.rows(), left.cols() * right.cols());
430 for (Eigen::Index row = 0; row < left.rows(); ++row)
431 for (Eigen::Index column = 0; column < left.cols(); ++column)
432 result.block(row * right.rows(), column * right.cols(), right.rows(), right.cols()) = left(row, column) * right;
438 if (krausOperators_.empty())
439 throw std::invalid_argument(
"A quantum channel must contain at least one Kraus operator");
441 const Eigen::Index dimension = krausOperators_.front().rows();
442 if (dimension < 2 || krausOperators_.front().cols() != dimension)
443 throw std::invalid_argument(
"Kraus operators must be nonempty square qubit matrices");
445 size_t remainingDimension =
static_cast<size_t>(dimension);
446 while (remainingDimension > 1 && remainingDimension % 2 == 0)
448 remainingDimension /= 2;
451 if (remainingDimension != 1)
452 throw std::invalid_argument(
"Kraus-operator dimensions must be powers of two");
454 Matrix completeness = Matrix::Zero(dimension, dimension);
455 for (
const Matrix &krausOperator : krausOperators_)
457 if (krausOperator.rows() != dimension || krausOperator.cols() != dimension)
458 throw std::invalid_argument(
"All Kraus operators must have the same dimensions");
459 if (!krausOperator.allFinite())
460 throw std::invalid_argument(
"Kraus operators must contain only finite values");
461 completeness.noalias() += krausOperator.adjoint() * krausOperator;
464 const Matrix identity = Matrix::Identity(dimension, dimension);
465 const double tolerance = 1e-10 * std::max<Eigen::Index>(1, dimension);
466 if ((completeness - identity).norm() > tolerance)
467 throw std::invalid_argument(
"Kraus operators do not define a trace-preserving channel");
472 std::vector<double> pauliProbabilities_;
static QuantumChannel Depolarizing(double errorProbability)
Depolarizing channel in the NoiseModel/QCSim convention: (1-p)rho + p/3 (XrhoX + YrhoY + ZrhoZ).
static QuantumChannel PhaseDamping(double gamma)
Phase damping with coherence multiplier sqrt(1-gamma).
static QuantumChannel Pauli(double px, double py, double pz)
Single-qubit Pauli channel with X, Y and Z error probabilities.
const KrausOperators & GetKrausOperators() const
static QuantumChannel AmplitudeDamping(double gamma)
|1> -> |0> relaxation with probability gamma.
Matrix ChoiMatrix() const
Choi matrix of the channel: sum_k vec(E_k) vec(E_k)^dagger, with vec() stacking the operator column b...
static QuantumChannel GeneralizedAmplitudeDamping(double gamma, double excitedStatePopulation)
Finite-temperature amplitude damping.
static QuantumChannel Pauli(const std::vector< double > &probabilities)
Arbitrary local Pauli channel.
static QuantumChannel DepolarizingMixing(double mixingProbability)
Depolarizing channel in the replacement convention: (1-p)rho + p I/2.
static QuantumChannel CorrelatedPhaseFlip(double probability)
(1-p)rho + p (Z (x) Z) rho (Z (x) Z).
static QuantumChannel BitPhaseFlip(double probability)
(1-p) rho + p Y rho Y.
size_t GetNumberOfQubits() const
QuantumChannel(KrausOperators krausOperators)
Construct and validate a CPTP channel from Kraus operators.
static QuantumChannel ThermalRelaxation(double duration, double t1, double t2, double excitedStatePopulation=0.0)
Hardware-style thermal relaxation for a duration, T1 and T2.
static QuantumChannel CorrelatedPhaseFlip(double probability, double correlation)
Interpolate between independent phase flips and a fully correlated ZZ phase flip.
static QuantumChannel TwoQubitDepolarizingMixing(double mixingProbability)
Two-qubit replacement depolarizing, fully mixed at probability one.
const std::vector< double > & GetPauliProbabilities() const
Probabilities of a channel built by Pauli(), in Pauli()'s index order; empty for any other channel.
static QuantumChannel PhaseFlip(double probability)
(1-p) rho + p Z rho Z.
static QuantumChannel TwoQubitDepolarizing(double errorProbability)
Two-qubit depolarizing in the total-Pauli-error convention: identity has probability 1-p and each oth...
std::vector< Matrix > KrausOperators
bool IsApprox(const QuantumChannel &other, double tolerance=1e-12) const
Compare two channels as MAPS, not as Kraus lists.
static QuantumChannel BitFlip(double probability)
(1-p) rho + p X rho X.
Bounded online routing based on current physical bond dimensions.