Q2NS dev
ns-3 module
Loading...
Searching...
No Matches
q2ns-qstate-dm.cc
Go to the documentation of this file.
1/*-----------------------------------------------------------------------------
2 * Q2NS - Quantum Network Simulator
3 * Copyright (c) 2026 quantuminternet.it
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *---------------------------------------------------------------------------*/
9/**
10 * @file q2ns-qstate-dm.cc
11 * @brief Defines q2ns::QStateDM.
12 */
13
14#include "ns3/q2ns-qstate-dm.h"
15
16#include "ns3/assert.h"
17
18#include <cmath>
19#include <cstdint>
20#include <random>
21#include <stdexcept>
22
23namespace q2ns {
24
25void QStateDM::ValidateDensityMatrix(const qpp::cmat& rho) {
26 constexpr double dimTol = 1e-9;
27 constexpr double hermTol = 1e-12;
28 constexpr double traceTol = 1e-9;
29 constexpr double psdTol = 1e-12;
30
31 const auto rows = rho.rows();
32 const auto cols = rho.cols();
33
34 if (rows != cols) {
35 throw std::invalid_argument("QStateDM: density matrix must be square");
36 }
37
38 if (rows <= 0) {
39 throw std::invalid_argument("QStateDM: density matrix must be non-empty");
40 }
41
42 const double log2dim = std::log2(static_cast<double>(rows));
43 if (std::fabs(std::round(log2dim) - log2dim) > dimTol) {
44 throw std::invalid_argument("QStateDM: dim(rho) must be 2^N for some integer N");
45 }
46
47 // Density matrices must be Hermitian.
48 if (!rho.isApprox(rho.adjoint(), hermTol)) {
49 throw std::invalid_argument("QStateDM: density matrix must be Hermitian");
50 }
51
52 // Trace must be real and equal to 1 up to tolerance.
53 const std::complex<double> tr = qpp::trace(rho);
54 if (std::fabs(std::imag(tr)) > traceTol) {
55 throw std::invalid_argument("QStateDM: Tr(rho) must be real");
56 }
57 if (std::fabs(std::real(tr) - 1.0) > traceTol) {
58 throw std::invalid_argument("QStateDM: Tr(rho) must be 1");
59 }
60
61 // Positive semidefinite check. Symmetrize first to suppress tiny
62 // anti-Hermitian numerical noise before the self-adjoint eigensolve.
63 const qpp::cmat H = 0.5 * (rho + rho.adjoint());
64
65 Eigen::SelfAdjointEigenSolver<qpp::cmat> es(H);
66 if (es.info() != Eigen::Success) {
67 throw std::runtime_error("QStateDM: eigensolver failed while validating density matrix");
68 }
69
70 const auto& evals = es.eigenvalues();
71 for (int i = 0; i < evals.size(); ++i) {
72 if (evals[i] < -psdTol) {
73 throw std::invalid_argument("QStateDM: density matrix must be positive semidefinite");
74 }
75 }
76}
77
78
79
80QStateDM::QStateDM(std::size_t numQubits) {
81 std::vector<qpp::idx> zeros(numQubits, 0);
82 qpp::ket k = qpp::mket(zeros);
83 rho_ = qpp::prj(k);
85}
86
87
88
89QStateDM::QStateDM(qpp::cmat rho) : rho_(std::move(rho)) {
91}
92
93
94
95int64_t QStateDM::AssignStreams(int64_t stream) {
96 constexpr uint64_t QPPD_SALT = 0x51505044ULL; // "QPPD"
97
100 qpp::RandomDevices::get_instance().get_prng().seed(seq);
101 });
102}
103
104
105
106std::size_t QStateDM::NumQubits() const {
107 const auto dim = static_cast<double>(rho_.rows());
108 return static_cast<std::size_t>(std::llround(std::log2(dim)));
109}
110
111
112
113void QStateDM::Print(std::ostream& os) const {
114 PrintHeader(os, "DM");
115 os << rho_ << "\n";
116}
117
118
119
120void QStateDM::Apply(const QGate& g, const std::vector<q2ns::Index>& targets) {
121 std::vector<qpp::idx> t;
122 t.reserve(targets.size());
123 for (auto i : targets) {
124 t.push_back(static_cast<qpp::idx>(i));
125 }
126
127 qpp::cmat U;
128 if (g.Kind() == QGateKind::Custom) {
129 U = g.Unitary();
130 } else {
131 U = MatrixOf(g.Kind());
132 }
133
134 const std::size_t k = targets.size();
135 const std::size_t dim = 1ull << k;
136 NS_ABORT_MSG_IF(U.rows() != static_cast<int>(dim) || U.cols() != static_cast<int>(dim),
137 "QStateDM::Apply: wrong gate dimension");
138
139 rho_ = qpp::apply(rho_, U, t);
140}
141
142
143
144std::shared_ptr<QState> QStateDM::MergeDisjoint(const QState& other) const {
145 const auto* dm = dynamic_cast<const QStateDM*>(&other);
146 if (!dm) {
147 throw std::runtime_error("QStateDM::MergeDisjoint: incompatible backend");
148 }
149
150 qpp::cmat combined = qpp::kron(rho_, dm->rho_);
151 return std::make_shared<QStateDM>(combined);
152}
153
154
155
157 const auto n = NumQubits();
158 if (target >= n) {
159 throw std::out_of_range("QStateDM::Measure: target out of range");
160 }
161
162 switch (basis) {
163 case Basis::Z:
164 break;
165 case Basis::X:
166 rho_ = qpp::apply(rho_, qpp::gt.H, {static_cast<qpp::idx>(target)});
167 break;
168 case Basis::Y:
169 rho_ = qpp::apply(rho_, qpp::adjoint(qpp::gt.S), {static_cast<qpp::idx>(target)});
170 rho_ = qpp::apply(rho_, qpp::gt.H, {static_cast<qpp::idx>(target)});
171 break;
172 }
173
174 std::vector<qpp::idx> dims(n, 2);
175 std::vector<qpp::idx> subsys{static_cast<qpp::idx>(target)};
176 auto meas_res = qpp::measure(rho_, qpp::gt.Z, subsys, dims);
177
178 const qpp::idx outcome = std::get<0>(meas_res);
179 auto states = std::get<2>(meas_res);
180
181 // qpp returns post-measurement survivor states in the rotated Z-basis
182 // frame, so reconstruct the measured 1-qubit state in the requested basis.
183 qpp::ket oneQ = (outcome == 0 ? qpp::mket({0}) : qpp::mket({1}));
184 switch (basis) {
185 case Basis::Z:
186 break;
187 case Basis::X:
188 oneQ = qpp::apply(oneQ, qpp::gt.H, {0});
189 break;
190 case Basis::Y:
191 oneQ = qpp::apply(oneQ, qpp::adjoint(qpp::gt.S), {0});
192 oneQ = qpp::apply(oneQ, qpp::gt.H, {0});
193 break;
194 }
195
196 auto measured1q = std::make_shared<QStateDM>(qpp::prj(oneQ));
197 auto survivors = std::make_shared<QStateDM>(std::move(states[outcome]));
198
199 return MeasureResult{static_cast<int>(outcome), measured1q, survivors};
200}
201
202
203
204std::shared_ptr<QState> QStateDM::PartialTrace(const std::vector<q2ns::Index>& subsystemA) {
205 const auto n = NumQubits();
206 if (n == 0) {
207 throw std::runtime_error("QStateDM::PartialTrace: empty state");
208 }
209 if (subsystemA.empty() || subsystemA.size() >= n) {
210 throw std::invalid_argument(
211 "QStateDM::PartialTrace: subsystemA must be a non-empty proper subset");
212 }
213
214 std::vector<bool> used(n, false);
215 std::vector<qpp::idx> A;
216 A.reserve(subsystemA.size());
217
218 for (auto idxUser : subsystemA) {
219 if (idxUser >= n) {
220 throw std::out_of_range("QStateDM::PartialTrace: index out of range");
221 }
222 if (used[idxUser]) {
223 throw std::invalid_argument("QStateDM::PartialTrace: duplicate index");
224 }
225 used[idxUser] = true;
226 A.push_back(static_cast<qpp::idx>(idxUser));
227 }
228
229 std::vector<qpp::idx> B;
230 B.reserve(n - A.size());
231 for (qpp::idx i = 0; i < static_cast<qpp::idx>(n); ++i) {
232 if (!used[i]) {
233 B.push_back(i);
234 }
235 }
236
237 std::vector<qpp::idx> dims(n, 2);
238
239 // qpp::ptrace(rho, subsys, dims) traces out the listed subsystems.
240 // rhoA = Tr_B[rho] and rhoB = Tr_A[rho].
241 qpp::cmat rhoA = qpp::ptrace(rho_, B, dims);
242 qpp::cmat rhoB = qpp::ptrace(rho_, A, dims);
243
244 rho_ = std::move(rhoB);
246
247 return std::make_shared<QStateDM>(rhoA);
248}
249
250
251
252void QStateDM::SetRho(const qpp::cmat& rho) {
254 rho_ = rho;
255}
256
257} // namespace q2ns
Lightweight gate descriptor used by QState backends.
Definition q2ns-qgate.h:68
Density-matrix concrete QState backend using qpp::cmat.
MeasureResult Measure(q2ns::Index target, q2ns::Basis basis=q2ns::Basis::Z) override
Measure one qubit in the requested basis and split the result.
QStateDM(std::size_t numQubits)
Construct the |0...0><0...0| state on numQubits qubits.
static void ValidateDensityMatrix(const qpp::cmat &rho)
Validate basic density-matrix structure.
std::size_t NumQubits() const override
Return the number of logical qubits in the state.
std::shared_ptr< QState > PartialTrace(const std::vector< q2ns::Index > &subsystemA)
Extract a subsystem by partial trace.
int64_t AssignStreams(int64_t stream) override
Assign RNG streams for deterministic randomness.
void Print(std::ostream &os) const override
Print a human-readable representation of the state.
void Apply(const QGate &g, const std::vector< q2ns::Index > &targets) override
Apply a gate to the given target qubits.
qpp::cmat rho_
Backend density matrix.
std::shared_ptr< QState > MergeDisjoint(const QState &other) const override
Return the disjoint merge of this state and another density-matrix backend.
void SetRho(const qpp::cmat &rho)
Replace the underlying density matrix after validation.
Backend-agnostic interface for a quantum state object.
Definition q2ns-qstate.h:51
static std::seed_seq MakeSeedSeq(uint64_t s64)
Build a std::seed_seq from a 64-bit seed.
void PrintHeader(std::ostream &os, const char *backendName) const
Print a standard backend header.
static int64_t AssignStreamsGlobal(int64_t stream, ReseedFn reseed_fn)
Helper for backends that reseed a global RNG source.
Basis
Measurement basis for single-qubit projective measurement.
Definition q2ns-types.h:88
std::size_t Index
Generic qubit index type within a backend state.
Definition q2ns-types.h:49
@ Custom
Caller-supplied matrix.
const Matrix & MatrixOf(QGateKind k)
Return the built-in matrix for a non-custom gate kind.
Definition q2ns-qgate.h:313
Result of measuring one qubit and splitting the state.