numerics
Loading...
Searching...
No Matches
statevector.cpp
Go to the documentation of this file.
2#include <cmath>
3
4namespace num {
5namespace quantum {
6
7using cplx = std::complex<real>;
8static constexpr real pi = 3.141592653589793238462643383279502884;
9
10// -- State construction --------------------------------------------------------
11
12Statevector zero_state(int n_qubits) {
13 Statevector sv(1 << n_qubits, cplx{0, 0});
14 sv[0] = cplx{1, 0};
15 return sv;
16}
17
18Statevector basis_state(int n_qubits, int k) {
19 Statevector sv(1 << n_qubits, cplx{0, 0});
20 sv[k] = cplx{1, 0};
21 return sv;
22}
23
24// -- Single-qubit gates --------------------------------------------------------
25
26Gate gate_x() { return {cplx{0,0}, cplx{1,0}, cplx{1,0}, cplx{0,0}}; }
27Gate gate_y() { return {cplx{0,0}, cplx{0,-1}, cplx{0,1}, cplx{0,0}}; }
28Gate gate_z() { return {cplx{1,0}, cplx{0,0}, cplx{0,0}, cplx{-1,0}}; }
29
31 const real s = 1.0 / std::sqrt(real(2));
32 return {cplx{s,0}, cplx{s,0}, cplx{s,0}, cplx{-s,0}};
33}
34
35Gate gate_s() { return {cplx{1,0}, cplx{0,0}, cplx{0,0}, cplx{0,1}}; }
36Gate gate_sdg() { return {cplx{1,0}, cplx{0,0}, cplx{0,0}, cplx{0,-1}}; }
37
39 const real c = std::cos(pi / 4), s = std::sin(pi / 4);
40 return {cplx{1,0}, cplx{0,0}, cplx{0,0}, cplx{c, s}};
41}
43 const real c = std::cos(pi / 4), s = std::sin(pi / 4);
44 return {cplx{1,0}, cplx{0,0}, cplx{0,0}, cplx{c, -s}};
45}
46
48 const real c = std::cos(theta / 2), s = std::sin(theta / 2);
49 return {cplx{c,0}, cplx{0,-s}, cplx{0,-s}, cplx{c,0}};
50}
52 const real c = std::cos(theta / 2), s = std::sin(theta / 2);
53 return {cplx{c,0}, cplx{-s,0}, cplx{s,0}, cplx{c,0}};
54}
56 const real c = std::cos(theta / 2), s = std::sin(theta / 2);
57 return {cplx{c,-s}, cplx{0,0}, cplx{0,0}, cplx{c,s}};
58}
59
60Gate gate_p(real lambda) {
61 return {cplx{1,0}, cplx{0,0}, cplx{0,0},
62 cplx{std::cos(lambda), std::sin(lambda)}};
63}
64
65Gate gate_u(real theta, real phi, real lambda) {
66 const real ct = std::cos(theta / 2), st = std::sin(theta / 2);
67 return {
68 cplx{ct, 0},
69 cplx{-std::cos(lambda) * st, -std::sin(lambda) * st},
70 cplx{ std::cos(phi) * st, std::sin(phi) * st},
71 cplx{ std::cos(phi + lambda) * ct, std::sin(phi + lambda) * ct}
72 };
73}
74
75// -- Two-qubit gate matrices ---------------------------------------------------
76// Basis ordering: |00>, |01>, |10>, |11> (little-endian: q0=LSB, q1=MSB)
77
79 Gate2Q G{}; // zero-init
80 G[0] = cplx{1,0}; // |00> -> |00>
81 G[5] = cplx{1,0}; // |01> -> |01>
82 G[14] = cplx{1,0}; // |10> -> |11> (ctrl=q1=1, flip q0)
83 G[11] = cplx{1,0}; // |11> -> |10>
84 return G;
85}
86
88 Gate2Q G{};
89 G[0] = cplx{1,0};
90 G[5] = cplx{1,0};
91 G[10] = cplx{1,0};
92 G[15] = cplx{-1,0}; // |11> -> -|11>
93 return G;
94}
95
97 Gate2Q G{};
98 G[0] = cplx{1,0}; // |00> -> |00>
99 G[9] = cplx{1,0}; // |01> -> |10>
100 G[6] = cplx{1,0}; // |10> -> |01>
101 G[15] = cplx{1,0}; // |11> -> |11>
102 return G;
103}
104
105// -- Gate application ---------------------------------------------------------
106
107void apply_1q(Statevector& sv, int n_qubits, const Gate& G, int target) {
108 const int stride = 1 << target;
109 const int N = 1 << n_qubits;
110 for (int i = 0; i < N; i += stride << 1) {
111 for (int j = 0; j < stride; ++j) {
112 const int i0 = i + j, i1 = i0 + stride;
113 const cplx a0 = sv[i0], a1 = sv[i1];
114 sv[i0] = G[0] * a0 + G[1] * a1;
115 sv[i1] = G[2] * a0 + G[3] * a1;
116 }
117 }
118}
119
120// Apply a 4x4 gate to qubits q0 (LSB) and q1 (MSB) of the 2-qubit subspace.
121// The gate acts on the 4D subspace spanned by |q1 q0> in {00,01,10,11}.
122void apply_2q(Statevector& sv, int n_qubits, const Gate2Q& G, int q0, int q1) {
123 const int N = 1 << n_qubits;
124 const int m0 = 1 << q0, m1 = 1 << q1;
125
126 for (int i = 0; i < N; ++i) {
127 // Only process each group of 4 once: skip unless both bits are 0
128 if (i & m0 || i & m1) continue;
129 const int i00 = i, i01 = i | m0, i10 = i | m1, i11 = i | m0 | m1;
130 const cplx a00 = sv[i00], a01 = sv[i01],
131 a10 = sv[i10], a11 = sv[i11];
132 sv[i00] = G[0]*a00 + G[1]*a01 + G[2]*a10 + G[3]*a11;
133 sv[i01] = G[4]*a00 + G[5]*a01 + G[6]*a10 + G[7]*a11;
134 sv[i10] = G[8]*a00 + G[9]*a01 + G[10]*a10 + G[11]*a11;
135 sv[i11] = G[12]*a00+ G[13]*a01+ G[14]*a10 + G[15]*a11;
136 }
137}
138
139void apply_cnot(Statevector& sv, int n_qubits, int control, int target) {
140 const int N = 1 << n_qubits;
141 const int ctrl_mask = 1 << control, tgt_mask = 1 << target;
142 for (int i = 0; i < N; ++i) {
143 if ((i & ctrl_mask) && !(i & tgt_mask))
144 std::swap(sv[i], sv[i | tgt_mask]);
145 }
146}
147
148void apply_cz(Statevector& sv, int n_qubits, int control, int target) {
149 const int N = 1 << n_qubits;
150 const int ctrl_mask = 1 << control, tgt_mask = 1 << target;
151 for (int i = 0; i < N; ++i) {
152 if ((i & ctrl_mask) && (i & tgt_mask))
153 sv[i] = -sv[i];
154 }
155}
156
157void apply_swap(Statevector& sv, int n_qubits, int q0, int q1) {
158 const int N = 1 << n_qubits;
159 const int m0 = 1 << q0, m1 = 1 << q1;
160 for (int i = 0; i < N; ++i) {
161 if ((i & m0) && !(i & m1))
162 std::swap(sv[i], sv[(i & ~m0) | m1]);
163 }
164}
165
166void apply_toffoli(Statevector& sv, int n_qubits, int c0, int c1, int target) {
167 const int N = 1 << n_qubits;
168 const int m0 = 1 << c0, m1 = 1 << c1, mt = 1 << target;
169 for (int i = 0; i < N; ++i) {
170 if ((i & m0) && (i & m1) && !(i & mt))
171 std::swap(sv[i], sv[i | mt]);
172 }
173}
174
175// -- Observables ---------------------------------------------------------------
176
178 return num::norm(sv); // delegates to CVector overload
179}
180
182 real n = num::norm(sv);
183 for (auto& a : sv) a /= n;
184}
185
186std::vector<real> probabilities(const Statevector& sv) {
187 std::vector<real> p(sv.size());
188 for (std::size_t i = 0; i < sv.size(); ++i) p[i] = std::norm(sv[i]);
189 return p;
190}
191
193 std::function<void(const Statevector&, Statevector&)> op) {
194 Statevector out(sv.size(), cplx{0, 0});
195 op(sv, out);
196 cplx val{0, 0};
197 for (std::size_t i = 0; i < sv.size(); ++i) val += std::conj(sv[i]) * out[i];
198 return val.real();
199}
200
202 const int mask = 1 << qubit;
203 real val = 0;
204 for (std::size_t i = 0; i < sv.size(); ++i) {
205 real sign = (i & mask) ? real(-1) : real(1);
206 val += sign * std::norm(sv[i]);
207 }
208 return val;
209}
210
211real expectation_x(const Statevector& sv, int n_qubits, int qubit) {
212 // <X_q> = <sv | X_q | sv> computed by applying X then taking inner product
213 Statevector tmp = sv;
214 apply_1q(tmp, n_qubits, gate_x(), qubit);
215 cplx val{0, 0};
216 for (std::size_t i = 0; i < sv.size(); ++i) val += std::conj(sv[i]) * tmp[i];
217 return val.real();
218}
219
220real expectation_y(const Statevector& sv, int n_qubits, int qubit) {
221 Statevector tmp = sv;
222 apply_1q(tmp, n_qubits, gate_y(), qubit);
223 cplx val{0, 0};
224 for (std::size_t i = 0; i < sv.size(); ++i) val += std::conj(sv[i]) * tmp[i];
225 return val.real();
226}
227
228std::array<cplx, 4> reduced_density_matrix(const Statevector& sv,
229 int n_qubits, int qubit) {
230 // Partial trace over all qubits except `qubit`.
231 // rho_q[a,b] = Sigma_{env} alpha_{env|a} * conj(alpha_{env|b})
232 // where env iterates over the N/2 basis states with qubit bit = 0.
233 // This is O(N) -- not O(N^2).
234 const int N = 1 << n_qubits;
235 const int mask = 1 << qubit;
236 std::array<cplx, 4> rho{};
237 for (int i = 0; i < N; ++i) {
238 if (i & mask) continue; // visit each environment state once
239 const int i1 = i | mask; // same env, qubit = 1
240 rho[0] += sv[i] * std::conj(sv[i]); // rho[0,0]
241 rho[1] += sv[i] * std::conj(sv[i1]); // rho[0,1]
242 rho[2] += sv[i1] * std::conj(sv[i]); // rho[1,0]
243 rho[3] += sv[i1] * std::conj(sv[i1]); // rho[1,1]
244 }
245 return rho;
246}
247
248real entanglement_entropy(const Statevector& sv, int n_qubits, int qubit) {
249 auto rho = reduced_density_matrix(sv, n_qubits, qubit);
250 // Eigenvalues of 2x2 Hermitian: lambda = (tr +/- sqrt(tr^2-4det)) / 2
251 real tr = rho[0].real() + rho[3].real();
252 real det = (rho[0] * rho[3] - rho[1] * rho[2]).real();
253 real disc = std::max(real(0), tr * tr / 4 - det);
254 real sq = std::sqrt(disc);
255 real l0 = tr / 2 + sq, l1 = tr / 2 - sq;
256 auto xlogx = [](real x) -> real {
257 return (x > 1e-15) ? -x * std::log2(x) : real(0);
258 };
259 return xlogx(l0) + xlogx(l1);
260}
261
262void apply_cy(Statevector& sv, int n_qubits, int control, int target) {
263 // CY = controlled-Y: apply Y to target when control=1
264 // Y = [[0,-i],[i,0]], so swap amplitudes with phase factor
265 const int N = 1 << n_qubits;
266 const int ctrl_mask = 1 << control, tgt_mask = 1 << target;
267 for (int i = 0; i < N; ++i) {
268 if ((i & ctrl_mask) && !(i & tgt_mask)) {
269 int j = i | tgt_mask;
270 cplx a0 = sv[i], a1 = sv[j];
271 sv[i] = cplx{0, 1} * a1; // i*a1
272 sv[j] = cplx{0,-1} * a0; // -i*a0
273 }
274 }
275}
276
277void apply_cp(Statevector& sv, int n_qubits, real lambda, int control, int target) {
278 // Controlled phase: |11> -> e^{ilambda}|11>, others unchanged
279 const int N = 1 << n_qubits;
280 const int ctrl_mask = 1 << control, tgt_mask = 1 << target;
281 const cplx phase{std::cos(lambda), std::sin(lambda)};
282 for (int i = 0; i < N; ++i) {
283 if ((i & ctrl_mask) && (i & tgt_mask))
284 sv[i] *= phase;
285 }
286}
287
288void apply_cswap(Statevector& sv, int n_qubits, int ctrl, int q0, int q1) {
289 // Fredkin: swap q0 and q1 when ctrl=1
290 const int N = 1 << n_qubits;
291 const int mc = 1 << ctrl, m0 = 1 << q0, m1 = 1 << q1;
292 for (int i = 0; i < N; ++i) {
293 if ((i & mc) && (i & m0) && !(i & m1))
294 std::swap(sv[i], sv[(i & ~m0) | m1]);
295 }
296}
297
298} // namespace quantum
299} // namespace num
Dense vector with optional GPU storage, templated over scalar type T.
Definition vector.hpp:23
constexpr idx size() const noexcept
Definition vector.hpp:77
Gate gate_ry(real theta)
Rotation about Y: R_y(theta) = exp(-ithetaY/2)
void apply_cy(Statevector &sv, int n_qubits, int control, int target)
Controlled-Y.
std::array< cplx, 16 > Gate2Q
4x4 two-qubit gate stored row-major (16 entries)
Gate gate_rx(real theta)
Rotation about X: R_x(theta) = exp(-ithetaX/2)
real norm(const Statevector &sv)
L2 norm of the statevector (should be 1 for a valid quantum state)
std::complex< real > cplx
void apply_cp(Statevector &sv, int n_qubits, real lambda, int control, int target)
Controlled phase: applies phase e^{ilambda} when both control=1 and target=1.
Gate gate_x()
Pauli-X (NOT)
Gate gate_p(real lambda)
Phase gate: diag(1, e^{ilambda})
Gate gate_rz(real theta)
Rotation about Z: R_z(theta) = exp(-ithetaZ/2)
Gate2Q gate2q_cz()
Controlled-Z.
Gate gate_sdg()
S† = diag(1, -i)
void apply_2q(Statevector &sv, int n_qubits, const Gate2Q &G, int q0, int q1)
Apply a general 4x4 two-qubit gate to qubits q0, q1.
void apply_cnot(Statevector &sv, int n_qubits, int control, int target)
CNOT with explicit control and target qubits.
Gate2Q gate2q_cnot()
CNOT (qubit 0 = control, qubit 1 = target in subspace)
Gate gate_u(real theta, real phi, real lambda)
General SU(2) gate.
real expectation(const Statevector &sv, std::function< void(const Statevector &, Statevector &)> op)
Expectation value <psi|O|psi> for a Hermitian operator given as matrix-free matvec.
Gate gate_s()
Phase gate S = diag(1, i)
void apply_toffoli(Statevector &sv, int n_qubits, int c0, int c1, int target)
Toffoli (CCX) gate.
Statevector zero_state(int n_qubits)
Returns |0...0> computational basis state for n_qubits qubits.
Gate gate_tdg()
T† gate.
Gate gate_y()
Pauli-Y.
void apply_cz(Statevector &sv, int n_qubits, int control, int target)
Controlled-Z.
Gate2Q gate2q_swap()
SWAP.
Gate gate_h()
Hadamard.
real expectation_x(const Statevector &sv, int n_qubits, int qubit)
<X_q> for qubit q
void apply_cswap(Statevector &sv, int n_qubits, int ctrl, int q0, int q1)
Fredkin (CSWAP): swaps q0 and q1 when ctrl=1.
void normalize(Statevector &sv)
Normalize statevector in-place.
std::array< std::complex< real >, 4 > reduced_density_matrix(const Statevector &sv, int n_qubits, int qubit)
Reduced density matrix for a single qubit (2x2, returned row-major)
std::vector< real > probabilities(const Statevector &sv)
Measurement probability for each basis state: p_i = |a_i|^2.
real expectation_z(const Statevector &sv, int qubit)
<Z_q> = P(q=0) - P(q=1) for qubit q
void apply_swap(Statevector &sv, int n_qubits, int q0, int q1)
SWAP two qubits.
real entanglement_entropy(const Statevector &sv, int n_qubits, int qubit)
Von Neumann entropy of a single qubit's reduced density matrix.
void apply_1q(Statevector &sv, int n_qubits, const Gate &G, int target)
Apply a 2x2 gate to a single qubit in-place.
std::array< cplx, 4 > Gate
2x2 single-qubit gate stored row-major: { G[0,0], G[0,1], G[1,0], G[1,1] }
real expectation_y(const Statevector &sv, int n_qubits, int qubit)
<Y_q> for qubit q
Gate gate_z()
Pauli-Z.
Statevector basis_state(int n_qubits, int k)
Returns the computational basis state |k> for an n_qubits system.
Gate gate_t()
T gate = diag(1, e^{ipi/4})
double real
Definition types.hpp:10
constexpr real phi
Golden ratio.
Definition math.hpp:42
constexpr T ipow(T x) noexcept
Compute x^N at compile time via repeated squaring.
constexpr real pi
Definition math.hpp:40
constexpr real e
Definition math.hpp:41
real norm(const Vector &x, Backend b=default_backend)
Euclidean norm.
Definition vector.cpp:69
Statevector-based quantum circuit simulator.