43 throw std::invalid_argument(
44 "DensityMatrix: dimension must be power of 2");
60 throw std::invalid_argument(
"DensityMatrix: qbit_num must be >= 1");
76 if (state_vector.
cols != 1) {
77 throw std::invalid_argument(
78 "DensityMatrix: state_vector must be column vector (cols=1)");
82 int dim = state_vector.
rows;
83 for (
int i = 0; i < dim; i++) {
84 for (
int j = 0; j < dim; j++) {
104 throw std::invalid_argument(
105 "DensityMatrix: dimension must be power of 2");
125 if (
this != &other) {
133 if (
this != &other) {
142 if (i < 0 || i >=
rows || j < 0 || j >=
cols) {
143 throw std::out_of_range(
"DensityMatrix: index out of bounds");
149 if (i < 0 || i >=
rows || j < 0 || j >=
cols) {
150 throw std::out_of_range(
"DensityMatrix: index out of bounds");
165 for (
int i = 0; i <
rows; i++) {
178 Matrix rho_squared =
dot(rho_matrix, rho_matrix);
182 for (
int i = 0; i <
rows; i++) {
193 for (
double lambda : eigs) {
194 if (lambda > 1e-14) {
195 S -= lambda * std::log2(lambda);
210 if (std::abs(tr.
real - 1.0) > tol || std::abs(tr.
imag) > tol) {
216 for (
double eig : eigs) {
227 for (
int i = 0; i <
rows; i++) {
228 for (
int j = i; j <
cols; j++) {
233 if (std::abs(rho_ij.
real - rho_ji.
real) > tol) {
238 if (std::abs(rho_ij.
imag + rho_ji.
imag) > tol) {
250 std::vector<double> eigs(
rows);
258 std::vector<QGD_Complex16> work(lwork);
259 std::vector<double> rwork(3 * n);
263 zheev_(&jobz, &uplo, &n, rho_copy.
data, &lda, eigs.data(), work.data(),
264 &lwork, rwork.data(), &info);
267 throw std::runtime_error(
268 "DensityMatrix::eigenvalues: LAPACK zheev failed with info=" +
269 std::to_string(info));
273 std::sort(eigs.rbegin(), eigs.rend());
284 throw std::runtime_error(
285 "DensityMatrix::apply_unitary: dimension mismatch. " +
286 std::string(
"Expected ") + std::to_string(
rows) +
"x" +
287 std::to_string(
cols) +
", got " + std::to_string(U.
rows) +
"x" +
288 std::to_string(U.
cols));
316 if (target_qbit < 0 || target_qbit >=
qbit_num_) {
317 throw std::runtime_error(
318 "DensityMatrix::apply_single_qubit_unitary: target_qbit out of range");
321 if (u_2x2.
rows != 2 || u_2x2.
cols != 2) {
322 throw std::runtime_error(
323 "DensityMatrix::apply_single_qubit_unitary: kernel must be 2x2");
330 std::vector<QGD_Complex16> temp(dim * dim);
346 for (
int i = 0; i < dim; i++) {
348 int i_partner = i ^ target_step;
350 for (
int j = 0; j < dim; j++) {
352 int j_partner = j ^ target_step;
357 if (i_bit == 0 && j_bit == 0) {
362 }
else if (i_bit == 0 && j_bit == 1) {
367 }
else if (i_bit == 1 && j_bit == 0) {
407 if (i_bit == 0 && j_bit == 0) {
413 }
else if (i_bit == 0 && j_bit == 1) {
419 }
else if (i_bit == 1 && j_bit == 0) {
433 temp[i * dim + j] = result_elem;
443 if (target_qbit < 0 || target_qbit >=
qbit_num_) {
444 throw std::runtime_error(
445 "DensityMatrix::apply_two_qubit_unitary: target_qbit out of range");
447 if (control_qbit < 0 || control_qbit >=
qbit_num_) {
448 throw std::runtime_error(
449 "DensityMatrix::apply_two_qubit_unitary: control_qbit out of range");
451 if (target_qbit == control_qbit) {
452 throw std::runtime_error(
453 "DensityMatrix::apply_two_qubit_unitary: target and control must differ");
456 if (u_2x2.
rows != 2 || u_2x2.
cols != 2) {
457 throw std::runtime_error(
458 "DensityMatrix::apply_two_qubit_unitary: kernel must be 2x2");
465 std::vector<QGD_Complex16> temp(dim * dim);
477 for (
int i = 0; i < dim; i++) {
480 for (
int j = 0; j < dim; j++) {
489 for (
int a = 0; a < 2; a++) {
490 for (
int b = 0; b < 2; b++) {
498 if (i_control == 1) {
499 if (i_target == 0 && a == 0)
501 else if (i_target == 0 && a == 1)
503 else if (i_target == 1 && a == 0)
514 if (j_control == 1) {
515 if (j_target == 0 && b == 0)
517 else if (j_target == 0 && b == 1)
519 else if (j_target == 1 && b == 0)
529 int i_prime = (i & ~target_step) | (a << target_qbit);
530 int j_prime = (j & ~target_step) | (b << target_qbit);
546 temp[i * dim + j] =
result;
555 const std::vector<int> &target_qbits) {
557 int k =
static_cast<int>(target_qbits.size());
569 int kernel_dim = 1 <<
k;
570 if (u_kernel.
rows != kernel_dim || u_kernel.
cols != kernel_dim) {
571 throw std::runtime_error(
572 "DensityMatrix::apply_local_unitary: kernel size mismatch");
575 for (
int q : target_qbits) {
577 throw std::runtime_error(
578 "DensityMatrix::apply_local_unitary: qubit index out of range");
583 std::vector<QGD_Complex16> temp(dim * dim);
588 for (
int i = 0; i < dim; i++) {
589 for (
int j = 0; j < dim; j++) {
593 int i_target = 0, j_target = 0;
594 for (
int ki = 0; ki <
k; ki++) {
595 if ((i >> target_qbits[ki]) & 1) {
596 i_target |= (1 << ki);
598 if ((j >> target_qbits[ki]) & 1) {
599 j_target |= (1 << ki);
604 for (
int a = 0; a < kernel_dim; a++) {
605 for (
int b = 0; b < kernel_dim; b++) {
609 for (
int ki = 0; ki <
k; ki++) {
611 i_prime &= ~(1 << target_qbits[ki]);
612 j_prime &= ~(1 << target_qbits[ki]);
615 i_prime |= (1 << target_qbits[ki]);
618 j_prime |= (1 << target_qbits[ki]);
643 temp[i * dim + j] =
result;
653 if (trace_out.empty()) {
657 for (
int q : trace_out) {
659 throw std::invalid_argument(
660 "DensityMatrix::partial_trace: qubit index out of range");
665 int trace_out_num =
static_cast<int>(trace_out.size());
666 int keep_num =
qbit_num_ - trace_out_num;
669 throw std::invalid_argument(
670 "DensityMatrix::partial_trace: cannot trace out all qubits");
673 int dim_reduced = 1 << keep_num;
674 int dim_traced = 1 << trace_out_num;
677 std::vector<int> keep_qubits;
679 bool should_trace =
false;
680 for (
int t : trace_out) {
687 keep_qubits.push_back(q);
693 memset(rho_reduced.
data, 0,
699 for (
int i = 0; i < dim_reduced; i++) {
700 for (
int j = 0; j < dim_reduced; j++) {
707 for (
int k = 0;
k < dim_traced;
k++) {
712 for (
int q_idx = 0; q_idx < keep_num; q_idx++) {
713 int q = keep_qubits[q_idx];
714 if ((i >> q_idx) & 1) {
717 if ((j >> q_idx) & 1) {
722 for (
int t_idx = 0; t_idx < trace_out_num; t_idx++) {
723 int q = trace_out[t_idx];
724 if ((
k >> t_idx) & 1) {
735 rho_reduced.
data[i * dim_reduced + j] = sum;
756 double prob = 1.0 / dim;
760 for (
int i = 0; i < dim; i++) {
773 std::cout <<
"\n=== Density Matrix ===" << std::endl;
774 std::cout <<
"Qubits: " <<
qbit_num_ << std::endl;
775 std::cout <<
"Dimension: " <<
rows <<
" Ã " <<
cols << std::endl;
778 std::cout <<
"Trace: " << tr.
real;
779 if (std::abs(tr.
imag) > 1e-10) {
780 std::cout <<
" + " << tr.
imag <<
"i";
782 std::cout << std::endl;
784 std::cout <<
"Purity: " <<
purity() << std::endl;
785 std::cout <<
"Entropy: " <<
entropy() << std::endl;
786 std::cout <<
"Valid: " << (
is_valid() ?
"Yes" :
"No") << std::endl;
788 std::cout <<
"\nMatrix elements:" << std::endl;
789 for (
int i = 0; i < std::min(
rows, 8);
791 for (
int j = 0; j < std::min(
cols, 8); j++) {
793 std::cout <<
"(" << val.
real <<
"," << val.
imag <<
") ";
797 std::cout << std::endl;
800 std::cout <<
"..." << std::endl;
801 std::cout << std::endl;
806 throw std::runtime_error(
"DensityMatrix: matrix must be square");
810 if (
rows != expected_dim) {
811 throw std::runtime_error(
"DensityMatrix: dimension mismatch. Expected " +
812 std::to_string(expected_dim) +
", got " +
813 std::to_string(
rows));
Matrix dot(Matrix &A, Matrix &B)
Call to calculate the product of two complex matrices by calling method zgemm3m from the CBLAS librar...
void apply_local_unitary(const matrix_base< QGD_Complex16 > &u_kernel, const std::vector< int > &target_qbits)
Apply k-qubit unitary using local kernel (general case)
int stride
The column stride of the array. (The array elements in one row are a_0, a_1, ... a_{cols-1}, 0, 0, 0, 0. The number of zeros is stride-cols)
matrix_base< QGD_Complex16 > copy() const
Call to create a copy of the matrix.
QGD_Complex16 * data
pointer to the stored data
void validate_dimensions() const
DensityMatrix(int qbit_num)
Create density matrix for n qubits.
scalar * get_data() const
Call to get the pointer to the stored data.
DensityMatrix clone() const
Create deep copy.
void zheev_(char *jobz, char *uplo, int *n, QGD_Complex16 *a, int *lda, double *w, QGD_Complex16 *work, int *lwork, double *rwork, int *info)
bool is_valid(double tol=1e-10) const
Check if valid density matrix.
double entropy() const
von Neumann entropy: S(Ï) = -Tr(Ï logâ Ï)
double purity() const
Calculate purity: Tr(ϲ)
Base Class to store data of arrays and its properties.
DensityMatrix partial_trace(const std::vector< int > &trace_out) const
Compute partial trace over specified qubits.
void operator=(const matrix_base &mtx)
Assignment operator.
int rows
The number of rows.
int cols
The number of columns.
DensityMatrix & operator=(const DensityMatrix &other)
std::vector< double > eigenvalues() const
Get eigenvalues (sorted descending)
bool is_hermitian(double tol) const
Quantum density matrix Ï for mixed-state representation.
QGD_Complex16 trace() const
Calculate trace: Tr(Ï)
void apply_unitary(const matrix_base< QGD_Complex16 > &U)
Apply unitary transformation: Ï â UÏUâ
Structure type representing complex numbers in the SQUANDER package.
void print() const
Print matrix with properties.
Matrix copy() const
Call to create a copy of the matrix.
Double-precision complex matrix (float64).
void transpose()
Call to transpose (or un-transpose) the matrix for CBLAS functions.
QGD_Complex16 & operator()(int i, int j)
Element access: Ï(i,j)
int qbit_num_
Number of qubits.
double real
the real part of a complex number
void conjugate()
Call to conjugate (or un-conjugate) the matrix for CBLAS functions.
static int calculate_qbit_num(int dim)
void apply_single_qubit_unitary(const matrix_base< QGD_Complex16 > &u_2x2, int target_qbit)
Apply single-qubit unitary using local kernel (optimized)
void apply_two_qubit_unitary(const matrix_base< QGD_Complex16 > &u_2x2, int target_qbit, int control_qbit)
Apply two-qubit controlled unitary using local kernel (optimized)
double imag
the imaginary part of a complex number
static DensityMatrix maximally_mixed(int qbit_num)
Create maximally mixed state: Ï = I/2^n.