numerics
Loading...
Searching...
No Matches
circuit.cpp
Go to the documentation of this file.
1#include "quantum/circuit.hpp"
2#include <algorithm>
3#include <cmath>
4#include <cstdio>
5#include <iostream>
6#include <random>
7
8namespace num {
9
10static constexpr real PI = 3.141592653589793238462643383279502884;
11
12// -- Result --------------------------------------------------------------------
13
14void Result::print() const {
15 // Sort by count descending
16 std::vector<std::pair<std::string,int>> sorted(counts.begin(), counts.end());
17 std::sort(sorted.begin(), sorted.end(),
18 [](const auto& a, const auto& b){ return a.second > b.second; });
19
20 const int bar_width = 30;
21 for (const auto& [label, cnt] : sorted) {
22 if (cnt == 0) continue;
23 double p = static_cast<double>(cnt) / shots;
24 int filled = static_cast<int>(p * bar_width + 0.5);
25 std::string bar(filled, '#');
26 bar += std::string(bar_width - filled, '.');
27 std::printf("|%s> [%s] %5.1f%% (%d)\n",
28 label.c_str(), bar.c_str(), p * 100.0, cnt);
29 }
30}
31
32std::string Result::most_likely() const {
33 std::string best;
34 int best_cnt = -1;
35 for (const auto& [label, cnt] : counts)
36 if (cnt > best_cnt) { best_cnt = cnt; best = label; }
37 return best;
38}
39
40real Result::probability(const std::string& label) const {
41 auto it = counts.find(label);
42 if (it == counts.end()) return 0;
43 return static_cast<real>(it->second) / shots;
44}
45
46// -- Circuit constructor -------------------------------------------------------
47
48Circuit::Circuit(int n_qubits) : n_qubits_(n_qubits) {}
49
50// -- Single-qubit gate methods -------------------------------------------------
51
52Circuit& Circuit::h (int q) { ops_.push_back({Op::H, q}); return *this; }
53Circuit& Circuit::x (int q) { ops_.push_back({Op::X, q}); return *this; }
54Circuit& Circuit::y (int q) { ops_.push_back({Op::Y, q}); return *this; }
55Circuit& Circuit::z (int q) { ops_.push_back({Op::Z, q}); return *this; }
56Circuit& Circuit::s (int q) { ops_.push_back({Op::S, q}); return *this; }
57Circuit& Circuit::sdg(int q) { ops_.push_back({Op::Sdg, q}); return *this; }
58Circuit& Circuit::t (int q) { ops_.push_back({Op::T, q}); return *this; }
59Circuit& Circuit::tdg(int q) { ops_.push_back({Op::Tdg, q}); return *this; }
60
61Circuit& Circuit::rx(real theta, int q) {
62 Op op{Op::RX, q}; op.theta = theta; ops_.push_back(op); return *this;
63}
64Circuit& Circuit::ry(real theta, int q) {
65 Op op{Op::RY, q}; op.theta = theta; ops_.push_back(op); return *this;
66}
67Circuit& Circuit::rz(real theta, int q) {
68 Op op{Op::RZ, q}; op.theta = theta; ops_.push_back(op); return *this;
69}
70Circuit& Circuit::p(real lambda, int q) {
71 Op op{Op::P, q}; op.lambda = lambda; ops_.push_back(op); return *this;
72}
73Circuit& Circuit::u(real theta, real phi, real lambda, int q) {
74 Op op{Op::U, q}; op.theta = theta; op.phi = phi; op.lambda = lambda;
75 ops_.push_back(op); return *this;
76}
77
78// -- Two-qubit gate methods ----------------------------------------------------
79
81 Op op{Op::CX, ctrl, tgt}; ops_.push_back(op); return *this;
82}
84 Op op{Op::CY, ctrl, tgt}; ops_.push_back(op); return *this;
85}
87 Op op{Op::CZ, ctrl, tgt}; ops_.push_back(op); return *this;
88}
89Circuit& Circuit::cp (real lambda, int ctrl, int tgt) {
90 Op op{Op::CP, ctrl, tgt}; op.lambda = lambda;
91 ops_.push_back(op); return *this;
92}
93Circuit& Circuit::swap(int q0, int q1) {
94 Op op{Op::SWAP, q0, q1}; ops_.push_back(op); return *this;
95}
96
97// -- Three-qubit gate methods --------------------------------------------------
98
99Circuit& Circuit::ccx(int c0, int c1, int tgt) {
100 Op op{Op::CCX, c0, c1, tgt}; ops_.push_back(op); return *this;
101}
102Circuit& Circuit::cswap(int ctrl, int q0, int q1) {
103 Op op{Op::CSWAP, ctrl, q0, q1}; ops_.push_back(op); return *this;
104}
105
106// -- Custom gates --------------------------------------------------------------
107
108Circuit& Circuit::gate(const quantum::Gate& G, int q, std::string label) {
109 Op op{Op::Custom1Q, q};
110 op.g1 = G; op.label = std::move(label);
111 ops_.push_back(op); return *this;
112}
113Circuit& Circuit::gate(const quantum::Gate2Q& G, int q0, int q1, std::string label) {
114 Op op{Op::Custom2Q, q0, q1};
115 op.g2 = G; op.label = std::move(label);
116 ops_.push_back(op); return *this;
117}
118
119// -- Gate dispatch -------------------------------------------------------------
120
121void Circuit::apply_op(const Op& op, quantum::Statevector& sv) const {
122 using namespace quantum;
123 switch (op.type) {
124 case Op::H: apply_1q(sv, n_qubits_, gate_h(), op.q0); break;
125 case Op::X: apply_1q(sv, n_qubits_, gate_x(), op.q0); break;
126 case Op::Y: apply_1q(sv, n_qubits_, gate_y(), op.q0); break;
127 case Op::Z: apply_1q(sv, n_qubits_, gate_z(), op.q0); break;
128 case Op::S: apply_1q(sv, n_qubits_, gate_s(), op.q0); break;
129 case Op::Sdg: apply_1q(sv, n_qubits_, gate_sdg(), op.q0); break;
130 case Op::T: apply_1q(sv, n_qubits_, gate_t(), op.q0); break;
131 case Op::Tdg: apply_1q(sv, n_qubits_, gate_tdg(), op.q0); break;
132 case Op::RX: apply_1q(sv, n_qubits_, gate_rx(op.theta), op.q0); break;
133 case Op::RY: apply_1q(sv, n_qubits_, gate_ry(op.theta), op.q0); break;
134 case Op::RZ: apply_1q(sv, n_qubits_, gate_rz(op.theta), op.q0); break;
135 case Op::P: apply_1q(sv, n_qubits_, gate_p(op.lambda), op.q0); break;
136 case Op::U: apply_1q(sv, n_qubits_,
137 gate_u(op.theta, op.phi, op.lambda), op.q0); break;
138
139 case Op::CX: apply_cnot (sv, n_qubits_, op.q0, op.q1); break;
140 case Op::CY: apply_cy (sv, n_qubits_, op.q0, op.q1); break;
141 case Op::CZ: apply_cz (sv, n_qubits_, op.q0, op.q1); break;
142 case Op::CP: apply_cp (sv, n_qubits_, op.lambda, op.q0, op.q1); break;
143 case Op::SWAP: apply_swap (sv, n_qubits_, op.q0, op.q1); break;
144
145 case Op::CCX: apply_toffoli(sv, n_qubits_, op.q0, op.q1, op.q2); break;
146 case Op::CSWAP: apply_cswap (sv, n_qubits_, op.q0, op.q1, op.q2); break;
147
148 case Op::Custom1Q: apply_1q(sv, n_qubits_, op.g1, op.q0); break;
149 case Op::Custom2Q: apply_2q(sv, n_qubits_, op.g2, op.q0, op.q1); break;
150 }
151}
152
153// -- Execution -----------------------------------------------------------------
154
156 return statevector_at(static_cast<int>(ops_.size()));
157}
158
160 auto sv = quantum::zero_state(n_qubits_);
161 int limit = std::min(n, static_cast<int>(ops_.size()));
162 for (int i = 0; i < limit; ++i)
163 apply_op(ops_[i], sv);
164 return sv;
165}
166
167Result Circuit::run(int shots, unsigned seed) const {
168 auto sv = statevector();
169 auto probs = quantum::probabilities(sv);
170
171 std::mt19937 rng(seed);
172 std::discrete_distribution<int> dist(probs.begin(), probs.end());
173
174 // Build zero-count map for all states so Result::print shows full histogram
176 result.sv = sv;
177 result.shots = shots;
178 for (int k = 0; k < (1 << n_qubits_); ++k) {
179 std::string label(n_qubits_, '0');
180 for (int b = 0; b < n_qubits_; ++b)
181 label[n_qubits_ - 1 - b] = ((k >> b) & 1) ? '1' : '0';
182 result.counts[label] = 0;
183 }
184 for (int i = 0; i < shots; ++i) {
185 int k = dist(rng);
186 std::string label(n_qubits_, '0');
187 for (int b = 0; b < n_qubits_; ++b)
188 label[n_qubits_ - 1 - b] = ((k >> b) & 1) ? '1' : '0';
189 result.counts[label]++;
190 }
191 return result;
192}
193
194// -- Layout pass ---------------------------------------------------------------
195
196std::pair<std::vector<int>, int> Circuit::compute_layout() const {
197 std::vector<int> last(n_qubits_, -1);
198 std::vector<int> gate_col(ops_.size(), 0);
199 int total = 0;
200
201 for (int i = 0; i < (int)ops_.size(); ++i) {
202 const auto& op = ops_[i];
203 int c = last[op.q0];
204 if (op.q1 >= 0) c = std::max(c, last[op.q1]);
205 if (op.q2 >= 0) c = std::max(c, last[op.q2]);
206 gate_col[i] = c + 1;
207 total = std::max(total, gate_col[i] + 1);
208 last[op.q0] = gate_col[i];
209 if (op.q1 >= 0) last[op.q1] = gate_col[i];
210 if (op.q2 >= 0) last[op.q2] = gate_col[i];
211 }
212 return {gate_col, total};
213}
214
215// -- views() ------------------------------------------------------------------
216
217std::vector<GateView> Circuit::views() const {
218 auto [gate_col, total_cols] = compute_layout();
219
220 auto fmt_angle = [](real theta) {
221 char buf[16];
222 std::snprintf(buf, sizeof(buf), "%.2f", theta);
223 return std::string(buf);
224 };
225
226 std::vector<GateView> out;
227 out.reserve(ops_.size());
228
229 for (int i = 0; i < (int)ops_.size(); ++i) {
230 const auto& op = ops_[i];
231 GateView v;
232 v.q0 = op.q0; v.q1 = op.q1; v.q2 = op.q2;
233 v.col = gate_col[i];
234
235 switch (op.type) {
236 case Op::H: v.label = "H"; v.kind = 0; break;
237 case Op::X: v.label = "X"; v.kind = 0; break;
238 case Op::Y: v.label = "Y"; v.kind = 0; break;
239 case Op::Z: v.label = "Z"; v.kind = 0; break;
240 case Op::S: v.label = "S"; v.kind = 0; break;
241 case Op::Sdg: v.label = "S†"; v.kind = 0; break;
242 case Op::T: v.label = "T"; v.kind = 0; break;
243 case Op::Tdg: v.label = "T†"; v.kind = 0; break;
244 case Op::RX: v.label = "Rx(" + fmt_angle(op.theta) + ")"; v.kind = 0; break;
245 case Op::RY: v.label = "Ry(" + fmt_angle(op.theta) + ")"; v.kind = 0; break;
246 case Op::RZ: v.label = "Rz(" + fmt_angle(op.theta) + ")"; v.kind = 0; break;
247 case Op::P: v.label = "P(" + fmt_angle(op.lambda) + ")"; v.kind = 0; break;
248 case Op::U: v.label = "U"; v.kind = 0; break;
249 case Op::CX: v.label = "CX"; v.kind = 1; break;
250 case Op::CY: v.label = "CY"; v.kind = 1; break;
251 case Op::CZ: v.label = "CZ"; v.kind = 1; break;
252 case Op::CP: v.label = "CP(" + fmt_angle(op.lambda) + ")"; v.kind = 1; break;
253 case Op::SWAP: v.label = "SWAP"; v.kind = 2; break;
254 case Op::CCX: v.label = "CCX"; v.kind = 3; break;
255 case Op::CSWAP:v.label = "CSWAP"; v.kind = 3; break;
256 case Op::Custom1Q: v.label = op.label; v.kind = 0; break;
257 case Op::Custom2Q: v.label = op.label; v.kind = 1; break;
258 }
259 out.push_back(v);
260 }
261 return out;
262}
263
264// -- ASCII diagram -------------------------------------------------------------
265
266std::string Circuit::diagram() const {
267 if (ops_.empty()) {
268 std::string out;
269 for (int q = 0; q < n_qubits_; ++q)
270 out += "q[" + std::to_string(q) + "]: ---\n";
271 return out;
272 }
273
274 auto [gate_col, total_cols] = compute_layout();
275
276 // Grid: 2*n_qubits-1 display rows (qubit rows interleaved with connector rows)
277 // Each cell is a fixed 3-char string
278 const int disp_rows = 2 * n_qubits_ - 1;
279 std::vector<std::vector<std::string>> grid(
280 disp_rows, std::vector<std::string>(total_cols, "---"));
281 // Connector rows default to empty space
282 for (int r = 1; r < disp_rows; r += 2)
283 for (int c = 0; c < total_cols; ++c)
284 grid[r][c] = " ";
285
286 // Helper: add vertical line through intermediate qubits
287 auto add_vertical = [&](int q_lo, int q_hi, int col) {
288 for (int q = q_lo; q < q_hi; ++q)
289 grid[2*q + 1][col] = " | "; // connector row
290 for (int q = q_lo + 1; q < q_hi; ++q)
291 grid[2*q][col] = "-+-"; // crossing wire
292 };
293
294 for (int i = 0; i < (int)ops_.size(); ++i) {
295 const auto& op = ops_[i];
296 int c = gate_col[i];
297
298 auto set = [&](int q, std::string sym) { grid[2*q][c] = sym; };
299
300 switch (op.type) {
301 case Op::H: set(op.q0, "-H-"); break;
302 case Op::X: set(op.q0, "-X-"); break;
303 case Op::Y: set(op.q0, "-Y-"); break;
304 case Op::Z: set(op.q0, "-Z-"); break;
305 case Op::S: set(op.q0, "-S-"); break;
306 case Op::Sdg: set(op.q0, "S†-"); break;
307 case Op::T: set(op.q0, "-T-"); break;
308 case Op::Tdg: set(op.q0, "T†-"); break;
309 case Op::RX: set(op.q0, "Rx-"); break;
310 case Op::RY: set(op.q0, "Ry-"); break;
311 case Op::RZ: set(op.q0, "Rz-"); break;
312 case Op::P: set(op.q0, "-P-"); break;
313 case Op::U: set(op.q0, "-U-"); break;
314 case Op::CX:
315 set(op.q0, "-*-");
316 set(op.q1, "-(+)-");
317 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
318 break;
319 case Op::CY:
320 set(op.q0, "-*-");
321 set(op.q1, "-Y-");
322 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
323 break;
324 case Op::CZ:
325 set(op.q0, "-*-");
326 set(op.q1, "-*-");
327 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
328 break;
329 case Op::CP:
330 set(op.q0, "-*-");
331 set(op.q1, "-P-");
332 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
333 break;
334 case Op::SWAP: {
335 set(op.q0, "-x-");
336 set(op.q1, "-x-");
337 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
338 break;
339 }
340 case Op::CCX:
341 set(op.q0, "-*-");
342 set(op.q1, "-*-");
343 set(op.q2, "-(+)-");
344 add_vertical(std::min({op.q0,op.q1,op.q2}),
345 std::max({op.q0,op.q1,op.q2}), c);
346 break;
347 case Op::CSWAP:
348 set(op.q0, "-*-");
349 set(op.q1, "-x-");
350 set(op.q2, "-x-");
351 add_vertical(std::min({op.q0,op.q1,op.q2}),
352 std::max({op.q0,op.q1,op.q2}), c);
353 break;
354 case Op::Custom1Q: {
355 std::string sym = op.label.substr(0, 1);
356 set(op.q0, "-" + sym + "-");
357 break;
358 }
359 case Op::Custom2Q:
360 set(op.q0, "-*-");
361 set(op.q1, "-" + op.label.substr(0,1) + "-");
362 add_vertical(std::min(op.q0,op.q1), std::max(op.q0,op.q1), c);
363 break;
364 }
365 }
366
367 std::string out;
368 for (int q = 0; q < n_qubits_; ++q) {
369 std::string prefix = "q[" + std::to_string(q) + "]: ";
370 out += prefix;
371 for (int c = 0; c < total_cols; ++c)
372 out += grid[2*q][c];
373 out += "-\n";
374
375 if (q < n_qubits_ - 1) {
376 out += std::string(prefix.size(), ' ');
377 for (int c = 0; c < total_cols; ++c)
378 out += grid[2*q + 1][c];
379 out += "\n";
380 }
381 }
382 return out;
383}
384
385void Circuit::print() const { std::cout << diagram(); }
386
387// -- Factory functions ---------------------------------------------------------
388
389Circuit bell_pair(int q0, int q1) {
390 int n = std::max(q0, q1) + 1;
391 Circuit c(n);
392 return c.h(q0).cx(q0, q1);
393}
394
395Circuit ghz_state(int n_qubits) {
396 Circuit c(n_qubits);
397 c.h(0);
398 for (int q = 1; q < n_qubits; ++q)
399 c.cx(0, q);
400 return c;
401}
402
403Circuit qft_circuit(int n_qubits) {
404 Circuit c(n_qubits);
405 for (int k = n_qubits - 1; k >= 0; --k) {
406 c.h(k);
407 for (int j = k - 1; j >= 0; --j)
408 c.cp(PI / (1 << (k - j)), j, k);
409 }
410 // Bit-reversal swap network
411 for (int i = 0; i < n_qubits / 2; ++i)
412 c.swap(i, n_qubits - 1 - i);
413 return c;
414}
415
416void print_state(const quantum::Statevector& sv, int n_qubits, real threshold) {
417 int N = 1 << n_qubits;
418 for (int k = 0; k < N; ++k) {
419 if (std::abs(sv[k]) < threshold) continue;
420 // Build label (MSB=q[n-1], LSB=q[0])
421 std::string label(n_qubits, '0');
422 for (int b = 0; b < n_qubits; ++b)
423 label[n_qubits - 1 - b] = ((k >> b) & 1) ? '1' : '0';
424 double p = std::norm(sv[k]);
425 std::printf("|%s> %+.4f%+.4fi (%.1f%%)\n",
426 label.c_str(), sv[k].real(), sv[k].imag(), p * 100.0);
427 }
428}
429
430} // namespace num
Fluent quantum circuit builder and statevector simulator.
Dense vector with optional GPU storage, templated over scalar type T.
Definition vector.hpp:23
Chainable quantum circuit builder.
Definition circuit.hpp:78
std::vector< GateView > views() const
Gate descriptions with layout columns – for renderers.
Definition circuit.cpp:217
std::string diagram() const
ASCII wire diagram (Unicode box-drawing characters)
Definition circuit.cpp:266
Circuit & cx(int ctrl, int tgt)
CNOT.
Definition circuit.cpp:80
Circuit & swap(int q0, int q1)
SWAP.
Definition circuit.cpp:93
Circuit & cp(real lambda, int ctrl, int tgt)
Controlled phase.
Definition circuit.cpp:89
Circuit & h(int q)
Hadamard.
Definition circuit.cpp:52
Circuit & rz(real theta, int q)
R_z(theta) = e^{-ithetaZ/2}.
Definition circuit.cpp:67
Circuit & rx(real theta, int q)
R_x(theta) = e^{-ithetaX/2}.
Definition circuit.cpp:61
Circuit & s(int q)
Phase S = sqrtZ.
Definition circuit.cpp:56
Circuit & ry(real theta, int q)
R_y(theta) = e^{-ithetaY/2}.
Definition circuit.cpp:64
quantum::Statevector statevector() const
Run full circuit -> statevector (no measurement collapse)
Definition circuit.cpp:155
Circuit(int n_qubits)
Definition circuit.cpp:48
Circuit & sdg(int q)
S†
Definition circuit.cpp:57
Circuit & t(int q)
T = sqrtS.
Definition circuit.cpp:58
quantum::Statevector statevector_at(int n) const
Apply first n gates only (0 = bare |0...0>). Useful for stepping through the circuit in a visualiser.
Definition circuit.cpp:159
Circuit & z(int q)
Pauli-Z.
Definition circuit.cpp:55
Circuit & p(real lambda, int q)
Phase P(lambda)
Definition circuit.cpp:70
Circuit & x(int q)
Pauli-X (NOT)
Definition circuit.cpp:53
Circuit & u(real theta, real phi, real lambda, int q)
General SU(2)
Definition circuit.cpp:73
Circuit & cz(int ctrl, int tgt)
Controlled-Z.
Definition circuit.cpp:86
Circuit & ccx(int c0, int c1, int tgt)
Toffoli (CCX)
Definition circuit.cpp:99
void print() const
Print diagram to stdout.
Definition circuit.cpp:385
Result run(int shots=1024, unsigned seed=42) const
Sample shots measurements from the ideal distribution. Uses pre-computed statevector probabilities – ...
Definition circuit.cpp:167
Circuit & tdg(int q)
T†
Definition circuit.cpp:59
Circuit & cy(int ctrl, int tgt)
Controlled-Y.
Definition circuit.cpp:83
Circuit & cswap(int ctrl, int q0, int q1)
Fredkin (CSWAP)
Definition circuit.cpp:102
Circuit & y(int q)
Pauli-Y.
Definition circuit.cpp:54
Circuit & gate(const quantum::Gate &G, int q, std::string label="U")
Definition circuit.cpp:108
std::array< cplx, 16 > Gate2Q
4x4 two-qubit gate stored row-major (16 entries)
Statevector zero_state(int n_qubits)
Returns |0...0> computational basis state for n_qubits qubits.
std::vector< real > probabilities(const Statevector &sv)
Measurement probability for each basis state: p_i = |a_i|^2.
std::array< cplx, 4 > Gate
2x2 single-qubit gate stored row-major: { G[0,0], G[0,1], G[1,0], G[1,1] }
double real
Definition types.hpp:10
constexpr real phi
Golden ratio.
Definition math.hpp:42
Circuit ghz_state(int n_qubits)
n-qubit GHZ state (|00...0> + |11...1>)/sqrt2
Definition circuit.cpp:395
constexpr T ipow(T x) noexcept
Compute x^N at compile time via repeated squaring.
Circuit qft_circuit(int n_qubits)
n-qubit Quantum Fourier Transform circuit Applied to |0...0> unless you prepend state-preparation gat...
Definition circuit.cpp:403
void print_state(const quantum::Statevector &sv, int n_qubits, real threshold=1e-6)
Pretty-print a statevector, hiding amplitudes below threshold.
Definition circuit.cpp:416
Circuit bell_pair(int q0=0, int q1=1)
Bell state |Phi+> = (|00> + |11>)/sqrt2 on qubits q0, q1.
Definition circuit.cpp:389
Compact gate description used by visualisation code.
Definition circuit.hpp:61
int q1
secondary qubit (-1 if 1Q)
Definition circuit.hpp:64
int q2
tertiary qubit (-1 if not 3Q)
Definition circuit.hpp:65
std::string label
e.g. "H", "CX", "RY(1.57)", "SWAP"
Definition circuit.hpp:62
int q0
primary qubit (target for 1Q; control for 2/3Q)
Definition circuit.hpp:63
Measurement outcome from Circuit::run()
Definition circuit.hpp:43
void print() const
Print counts sorted by count (descending)
Definition circuit.cpp:14
std::string most_likely() const
Basis label with the highest count.
Definition circuit.cpp:32
quantum::Statevector sv
pre-measurement statevector
Definition circuit.hpp:45
real probability(const std::string &label) const
Empirical probability for a given label (e.g. "11")
Definition circuit.cpp:40
std::map< std::string, int > counts
basis label (e.g. "011") -> shot count
Definition circuit.hpp:44