From 04f65f091c3d3815d928a1ee16aac9699e0de91e Mon Sep 17 00:00:00 2001 From: hachem Date: Sun, 13 Sep 2026 07:43:24 +0200 Subject: feat: add horizontal circuit visualizer --- src/visualizer/horizontal_cli.c | 345 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 src/visualizer/horizontal_cli.c (limited to 'src/visualizer/horizontal_cli.c') diff --git a/src/visualizer/horizontal_cli.c b/src/visualizer/horizontal_cli.c new file mode 100644 index 0000000..e8d0570 --- /dev/null +++ b/src/visualizer/horizontal_cli.c @@ -0,0 +1,345 @@ +#include "visualizer/renderer.h" + +#include +#include +#include +#include +#include + +#include "visualizer/grid.h" + +static void append_repeat(struct PsiStringBuilder *sb, const char *glyph, size_t n) +{ + for (size_t i = 0; i < n; i++) + psi_string_builder_append(sb, glyph); +} + +static bool is_single_target(enum PsiGateKind kind) +{ + switch (kind) + { + case PSI_GATE_H: + case PSI_GATE_X: + case PSI_GATE_Y: + case PSI_GATE_Z: + case PSI_GATE_S: + case PSI_GATE_T: + case PSI_GATE_SDG: + case PSI_GATE_TDG: + case PSI_GATE_SX: + case PSI_GATE_SXDG: + case PSI_GATE_RX: + case PSI_GATE_RY: + case PSI_GATE_RZ: + case PSI_GATE_P: + case PSI_GATE_U1: + case PSI_GATE_U2: + case PSI_GATE_U3: + return true; + default: + return false; + } +} + +static void single_label(struct PsiGateOp op, char *out, size_t cap) +{ + switch (op.kind) + { + case PSI_GATE_H: snprintf(out, cap, "[H]"); break; + case PSI_GATE_X: snprintf(out, cap, "[X]"); break; + case PSI_GATE_Y: snprintf(out, cap, "[Y]"); break; + case PSI_GATE_Z: snprintf(out, cap, "[Z]"); break; + case PSI_GATE_S: snprintf(out, cap, "[S]"); break; + case PSI_GATE_T: snprintf(out, cap, "[T]"); break; + case PSI_GATE_SDG: snprintf(out, cap, "[S†]"); break; + case PSI_GATE_TDG: snprintf(out, cap, "[T†]"); break; + case PSI_GATE_SX: snprintf(out, cap, "[√X]"); break; + case PSI_GATE_SXDG: snprintf(out, cap, "[√X†]"); break; + case PSI_GATE_RX: snprintf(out, cap, "[Rx(%.2f)]", op.params[0]); break; + case PSI_GATE_RY: snprintf(out, cap, "[Ry(%.2f)]", op.params[0]); break; + case PSI_GATE_RZ: snprintf(out, cap, "[Rz(%.2f)]", op.params[0]); break; + case PSI_GATE_P: snprintf(out, cap, "[P(%.2f)]", op.params[0]); break; + case PSI_GATE_U1: snprintf(out, cap, "[U1(%.2f)]", op.params[0]); break; + case PSI_GATE_U2: snprintf(out, cap, "[U2]"); break; + case PSI_GATE_U3: snprintf(out, cap, "[U3]"); break; + default: snprintf(out, cap, "[?]"); break; + } +} + +static void controlled_label(struct PsiGateOp op, char *out, size_t cap) +{ + switch (op.kind) + { + case PSI_GATE_CRX: snprintf(out, cap, "[CRx(%.2f)]", op.params[0]); break; + case PSI_GATE_CRY: snprintf(out, cap, "[CRy(%.2f)]", op.params[0]); break; + case PSI_GATE_CRZ: snprintf(out, cap, "[CRz(%.2f)]", op.params[0]); break; + case PSI_GATE_CP: snprintf(out, cap, "[CP(%.2f)]", op.params[0]); break; + default: snprintf(out, cap, "[?]"); break; + } +} + +static bool is_param_controlled(enum PsiGateKind kind) +{ + switch (kind) + { + case PSI_GATE_CRX: + case PSI_GATE_CRY: + case PSI_GATE_CRZ: + case PSI_GATE_CP: + return true; + default: + return false; + } +} + +char *psi_render_circuit_horizontal(const struct PsiQuantumCircuit *circuit) +{ + size_t nq = circuit->num_qubits; + size_t nc = circuit->num_classical; + + struct PsiStringBuilder *q = nq > 0 ? malloc(nq * sizeof(struct PsiStringBuilder)) : NULL; + struct PsiStringBuilder *c = nc > 0 ? malloc(nc * sizeof(struct PsiStringBuilder)) : NULL; + assert(q != NULL || nq == 0); + assert(c != NULL || nc == 0); + + size_t max_label = 3; + for (size_t i = 0; i < nq; i++) + { + char lbl[16]; + snprintf(lbl, sizeof lbl, "q%zu: ", i); + if (strlen(lbl) > max_label) + max_label = strlen(lbl); + } + for (size_t i = 0; i < nc; i++) + { + char lbl[16]; + snprintf(lbl, sizeof lbl, "c%zu: ", i); + if (strlen(lbl) > max_label) + max_label = strlen(lbl); + } + + for (size_t i = 0; i < nq; i++) + { + q[i] = psi_new_string_builder(); + char lbl[16]; + snprintf(lbl, sizeof lbl, "q%zu: ", i); + append_repeat(&q[i], " ", max_label - strlen(lbl)); + psi_string_builder_append(&q[i], lbl); + } + for (size_t i = 0; i < nc; i++) + { + c[i] = psi_new_string_builder(); + char lbl[16]; + snprintf(lbl, sizeof lbl, "c%zu: ", i); + append_repeat(&c[i], " ", max_label - strlen(lbl)); + psi_string_builder_append(&c[i], lbl); + } + + struct PsiStringBuilder gap = psi_new_string_builder(); + append_repeat(&gap, " ", max_label); + + if (circuit->operation_count == 0) + { + for (size_t i = 0; i < nq; i++) + psi_string_builder_append(&q[i], "───"); + for (size_t i = 0; i < nc; i++) + psi_string_builder_append(&c[i], "═══"); + psi_string_builder_append(&gap, " "); + } + + for (size_t oi = 0; oi < circuit->operation_count; oi++) + { + struct PsiGateOp op = circuit->operations[oi]; + + size_t tc; + const size_t *targets = psi_gate_op_quantum_targets(&op, &tc); + size_t min_q = targets[0]; + size_t max_q = targets[0]; + for (size_t k = 1; k < tc; k++) + { + if (targets[k] < min_q) + min_q = targets[k]; + if (targets[k] > max_q) + max_q = targets[k]; + } + + if (is_single_target(op.kind)) + { + char label[64]; + single_label(op, label, sizeof label); + size_t w = psi_utf8_count(label); + size_t t = op.qubits[0]; + + for (size_t i = 0; i < nq; i++) + { + if (i == t) + { + psi_string_builder_append(&q[i], "─"); + psi_string_builder_append(&q[i], label); + psi_string_builder_append(&q[i], "─"); + } + else + append_repeat(&q[i], "─", w + 2); + } + for (size_t i = 0; i < nc; i++) + append_repeat(&c[i], "═", w + 2); + append_repeat(&gap, " ", w + 2); + } + else if (is_param_controlled(op.kind)) + { + char label[64]; + controlled_label(op, label, sizeof label); + size_t w = psi_utf8_count(label); + size_t control = op.qubits[0]; + size_t target = op.qubits[1]; + + for (size_t i = 0; i < nq; i++) + { + psi_string_builder_append(&q[i], "─"); + if (i == control) + { + psi_string_builder_append(&q[i], "●"); + append_repeat(&q[i], "─", w - 1); + } + else if (i == target) + psi_string_builder_append(&q[i], label); + else if (i > min_q && i < max_q) + { + psi_string_builder_append(&q[i], "│"); + append_repeat(&q[i], "─", w - 1); + } + else + append_repeat(&q[i], "─", w); + psi_string_builder_append(&q[i], "─"); + } + for (size_t i = 0; i < nc; i++) + append_repeat(&c[i], "═", w + 2); + append_repeat(&gap, " ", w + 2); + } + else if (op.kind == PSI_GATE_CNOT || op.kind == PSI_GATE_CZ || op.kind == PSI_GATE_SWAP + || op.kind == PSI_GATE_CCNOT || op.kind == PSI_GATE_CSWAP) + { + for (size_t i = 0; i < nq; i++) + { + const char *seg = "─────"; + + switch (op.kind) + { + case PSI_GATE_CNOT: + if (i == op.qubits[0]) seg = "──●──"; + else if (i == op.qubits[1]) seg = "──⊕──"; + else if (i > min_q && i < max_q) seg = "──│──"; + break; + case PSI_GATE_CZ: + if (i == op.qubits[0] || i == op.qubits[1]) seg = "──●──"; + else if (i > min_q && i < max_q) seg = "──│──"; + break; + case PSI_GATE_SWAP: + if (i == op.qubits[0] || i == op.qubits[1]) seg = "──╳──"; + else if (i > min_q && i < max_q) seg = "──│──"; + break; + case PSI_GATE_CCNOT: + if (i == op.qubits[0] || i == op.qubits[1]) seg = "──●──"; + else if (i == op.qubits[2]) seg = "──⊕──"; + else if (i > min_q && i < max_q) seg = "──│──"; + break; + case PSI_GATE_CSWAP: + if (i == op.qubits[0]) seg = "──●──"; + else if (i == op.qubits[1] || i == op.qubits[2]) seg = "──╳──"; + else if (i > min_q && i < max_q) seg = "──│──"; + break; + default: + break; + } + + psi_string_builder_append(&q[i], seg); + } + for (size_t i = 0; i < nc; i++) + psi_string_builder_append(&c[i], "═════"); + psi_string_builder_append(&gap, " "); + } + else if (op.kind == PSI_GATE_MEASURE) + { + size_t mq = op.qubits[0]; + size_t mc = op.classical; + + for (size_t i = 0; i < nq; i++) + { + if (i == mq) + psi_string_builder_append(&q[i], "─[M]─"); + else if (i > mq) + psi_string_builder_append(&q[i], "──║──"); + else + psi_string_builder_append(&q[i], "─────"); + } + for (size_t i = 0; i < nc; i++) + { + if (i == mc) + psi_string_builder_append(&c[i], "══╩══"); + else if (i < mc) + psi_string_builder_append(&c[i], "══║══"); + else + psi_string_builder_append(&c[i], "═════"); + } + psi_string_builder_append(&gap, " ║ "); + } + else + { + char label[64]; + snprintf(label, sizeof label, "[%s]", op.custom->name); + size_t w = psi_utf8_count(label); + + for (size_t i = 0; i < nq; i++) + { + bool is_target = false; + for (size_t k = 0; k < tc; k++) + if (targets[k] == i) + is_target = true; + + psi_string_builder_append(&q[i], "─"); + if (is_target && i == targets[0]) + psi_string_builder_append(&q[i], label); + else if (is_target) + append_repeat(&q[i], "─", w); + else if (i > min_q && i < max_q) + { + psi_string_builder_append(&q[i], "│"); + append_repeat(&q[i], "─", w - 1); + } + else + append_repeat(&q[i], "─", w); + psi_string_builder_append(&q[i], "─"); + } + for (size_t i = 0; i < nc; i++) + append_repeat(&c[i], "═", w + 2); + append_repeat(&gap, " ", w + 2); + } + } + + struct PsiStringBuilder out = psi_new_string_builder(); + + for (size_t i = 0; i < nq; i++) + { + psi_string_builder_append(&out, psi_string_builder_finish(&q[i])); + psi_string_builder_append(&out, "░\n"); + free(q[i].data); + } + + if (nc > 0) + { + psi_string_builder_append(&out, psi_string_builder_finish(&gap)); + psi_string_builder_append(&out, "░\n"); + for (size_t i = 0; i < nc; i++) + { + psi_string_builder_append(&out, psi_string_builder_finish(&c[i])); + psi_string_builder_append(&out, "░\n"); + free(c[i].data); + } + } + + free(gap.data); + free(q); + free(c); + + return psi_string_builder_finish(&out); +} -- cgit v1.3