use std::fmt::Write;
use crate::circuit::QuantumCircuit;
use crate::gate::Gate;
use crate::types::Complex;
struct ZyzAngles {
theta: f64,
phi: f64,
lambda: f64,
}
fn decompose_zyz(u: &[[Complex; 2]; 2]) -> ZyzAngles {
let abs00 = u[0][0].norm();
let abs10 = u[1][0].norm();
let cos_half_theta = abs00.clamp(0.0, 1.0);
let theta = 2.0 * cos_half_theta.acos();
let eps = 1e-12;
if abs00 > eps && abs10 > eps {
let phi_complex = u[0][0].conj() * u[1][0];
let lambda_complex = u[1][1] * u[1][0].conj();
ZyzAngles {
theta,
phi: phi_complex.arg(),
lambda: lambda_complex.arg(),
}
} else if abs10 < eps {
let diag_product = u[1][1] * u[0][0].conj();
ZyzAngles {
theta: 0.0,
phi: diag_product.arg(),
lambda: 0.0,
}
} else {
let neg_01 = -u[0][1];
let anti_product = u[1][0] * neg_01.conj();
ZyzAngles {
theta: std::f64::consts::PI,
phi: anti_product.arg(),
lambda: 0.0,
}
}
}
fn fmt_angle(angle: f64) -> String {
let s = format!("{:.15e}", angle);
let abs = angle.abs();
if abs == 0.0 {
return "0".to_string();
}
if abs >= 1e-4 && abs < 1e6 {
let s = format!("{:.15}", angle);
let trimmed = s.trim_end_matches('0');
let trimmed = trimmed.trim_end_matches('.');
trimmed.to_string()
} else {
s
}
}
pub fn to_qasm3(circuit: &QuantumCircuit) -> String {
let n = circuit.num_qubits();
let mut out = String::with_capacity(256 + circuit.gates().len() * 30);
out.push_str("OPENQASM 3.0;\n");
out.push_str("include \"stdgates.inc\";\n");
let _ = writeln!(out, "qubit[{}] q;", n);
let _ = writeln!(out, "bit[{}] c;", n);
for gate in circuit.gates() {
emit_gate(&mut out, gate);
}
out
}
fn emit_gate(out: &mut String, gate: &Gate) {
match gate {
Gate::H(q) => {
let _ = writeln!(out, "h q[{}];", q);
}
Gate::X(q) => {
let _ = writeln!(out, "x q[{}];", q);
}
Gate::Y(q) => {
let _ = writeln!(out, "y q[{}];", q);
}
Gate::Z(q) => {
let _ = writeln!(out, "z q[{}];", q);
}
Gate::S(q) => {
let _ = writeln!(out, "s q[{}];", q);
}
Gate::Sdg(q) => {
let _ = writeln!(out, "sdg q[{}];", q);
}
Gate::T(q) => {
let _ = writeln!(out, "t q[{}];", q);
}
Gate::Tdg(q) => {
let _ = writeln!(out, "tdg q[{}];", q);
}
Gate::Rx(q, angle) => {
let _ = writeln!(out, "rx({}) q[{}];", fmt_angle(*angle), q);
}
Gate::Ry(q, angle) => {
let _ = writeln!(out, "ry({}) q[{}];", fmt_angle(*angle), q);
}
Gate::Rz(q, angle) => {
let _ = writeln!(out, "rz({}) q[{}];", fmt_angle(*angle), q);
}
Gate::Phase(q, angle) => {
let _ = writeln!(out, "p({}) q[{}];", fmt_angle(*angle), q);
}
Gate::CNOT(ctrl, tgt) => {
let _ = writeln!(out, "cx q[{}], q[{}];", ctrl, tgt);
}
Gate::CZ(q1, q2) => {
let _ = writeln!(out, "cz q[{}], q[{}];", q1, q2);
}
Gate::SWAP(q1, q2) => {
let _ = writeln!(out, "swap q[{}], q[{}];", q1, q2);
}
Gate::Rzz(q1, q2, angle) => {
let _ = writeln!(out, "rzz({}) q[{}], q[{}];", fmt_angle(*angle), q1, q2);
}
Gate::Measure(q) => {
let _ = writeln!(out, "c[{}] = measure q[{}];", q, q);
}
Gate::Reset(q) => {
let _ = writeln!(out, "reset q[{}];", q);
}
Gate::Barrier => {
out.push_str("barrier q;\n");
}
Gate::Unitary1Q(q, matrix) => {
let angles = decompose_zyz(matrix);
let _ = writeln!(
out,
"U({}, {}, {}) q[{}];",
fmt_angle(angles.theta),
fmt_angle(angles.phi),
fmt_angle(angles.lambda),
q,
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::QuantumCircuit;
use crate::gate::Gate;
use crate::types::Complex;
use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_4, PI};
fn assert_valid_header(qasm: &str) {
let lines: Vec<&str> = qasm.lines().collect();
assert!(lines.len() >= 4, "QASM output should have at least 4 lines");
assert_eq!(lines[0], "OPENQASM 3.0;");
assert_eq!(lines[1], "include \"stdgates.inc\";");
assert!(lines[2].starts_with("qubit["));
assert!(lines[3].starts_with("bit["));
}
fn gate_lines(qasm: &str) -> Vec<String> {
qasm.lines()
.skip(4)
.map(|l| l.to_string())
.filter(|l| !l.is_empty())
.collect()
}
#[test]
fn test_bell_state() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0).cnot(0, 1);
let qasm = to_qasm3(&circuit);
assert_valid_header(&qasm);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "cx q[0], q[1];");
assert!(qasm.contains("qubit[2] q;"));
assert!(qasm.contains("bit[2] c;"));
}
#[test]
fn test_bell_state_with_measurement() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0).cnot(0, 1).measure(0).measure(1);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 4);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "cx q[0], q[1];");
assert_eq!(lines[2], "c[0] = measure q[0];");
assert_eq!(lines[3], "c[1] = measure q[1];");
}
#[test]
fn test_ghz_3_qubit() {
let mut circuit = QuantumCircuit::new(3);
circuit.h(0).cnot(0, 1).cnot(0, 2);
let qasm = to_qasm3(&circuit);
assert_valid_header(&qasm);
assert!(qasm.contains("qubit[3] q;"));
assert!(qasm.contains("bit[3] c;"));
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "cx q[0], q[1];");
assert_eq!(lines[2], "cx q[0], q[2];");
}
#[test]
fn test_ghz_5_qubit() {
let mut circuit = QuantumCircuit::new(5);
circuit.h(0);
for i in 1..5 {
circuit.cnot(0, i);
}
let qasm = to_qasm3(&circuit);
assert!(qasm.contains("qubit[5] q;"));
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 5);
assert_eq!(lines[0], "h q[0];");
for i in 1..5u32 {
assert_eq!(lines[i as usize], format!("cx q[0], q[{}];", i));
}
}
#[test]
fn test_parametric_rx_ry_rz() {
let mut circuit = QuantumCircuit::new(1);
circuit.rx(0, PI).ry(0, FRAC_PI_2).rz(0, FRAC_PI_4);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 3);
assert!(lines[0].starts_with("rx("));
assert!(lines[0].ends_with(") q[0];"));
assert!(lines[1].starts_with("ry("));
assert!(lines[2].starts_with("rz("));
let rx_angle: f64 = extract_angle(&lines[0]);
let ry_angle: f64 = extract_angle(&lines[1]);
let rz_angle: f64 = extract_angle(&lines[2]);
assert!((rx_angle - PI).abs() < 1e-10, "rx angle mismatch");
assert!((ry_angle - FRAC_PI_2).abs() < 1e-10, "ry angle mismatch");
assert!((rz_angle - FRAC_PI_4).abs() < 1e-10, "rz angle mismatch");
}
#[test]
fn test_phase_gate() {
let mut circuit = QuantumCircuit::new(1);
circuit.phase(0, PI / 3.0);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("p("));
assert!(lines[0].ends_with(") q[0];"));
let angle = extract_angle(&lines[0]);
assert!((angle - PI / 3.0).abs() < 1e-10);
}
#[test]
fn test_rzz_gate() {
let mut circuit = QuantumCircuit::new(2);
circuit.rzz(0, 1, PI / 6.0);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("rzz("));
assert!(lines[0].contains("q[0], q[1]"));
let angle = extract_angle(&lines[0]);
assert!((angle - PI / 6.0).abs() < 1e-10);
}
#[test]
fn test_all_single_qubit_standard_gates() {
let mut circuit = QuantumCircuit::new(1);
circuit.h(0);
circuit.x(0);
circuit.y(0);
circuit.z(0);
circuit.s(0);
circuit.add_gate(Gate::Sdg(0));
circuit.t(0);
circuit.add_gate(Gate::Tdg(0));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 8);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "x q[0];");
assert_eq!(lines[2], "y q[0];");
assert_eq!(lines[3], "z q[0];");
assert_eq!(lines[4], "s q[0];");
assert_eq!(lines[5], "sdg q[0];");
assert_eq!(lines[6], "t q[0];");
assert_eq!(lines[7], "tdg q[0];");
}
#[test]
fn test_two_qubit_gates() {
let mut circuit = QuantumCircuit::new(3);
circuit.cnot(0, 1);
circuit.cz(1, 2);
circuit.swap(0, 2);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "cx q[0], q[1];");
assert_eq!(lines[1], "cz q[1], q[2];");
assert_eq!(lines[2], "swap q[0], q[2];");
}
#[test]
fn test_reset() {
let mut circuit = QuantumCircuit::new(1);
circuit.reset(0);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert_eq!(lines[0], "reset q[0];");
}
#[test]
fn test_barrier() {
let mut circuit = QuantumCircuit::new(3);
circuit.h(0).barrier().cnot(0, 1);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "barrier q;");
assert_eq!(lines[2], "cx q[0], q[1];");
}
#[test]
fn test_measure_all() {
let mut circuit = QuantumCircuit::new(3);
circuit.h(0).measure_all();
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 4);
assert_eq!(lines[0], "h q[0];");
assert_eq!(lines[1], "c[0] = measure q[0];");
assert_eq!(lines[2], "c[1] = measure q[1];");
assert_eq!(lines[3], "c[2] = measure q[2];");
}
#[test]
fn test_unitary1q_identity() {
let identity = [
[Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, identity));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("U("));
assert!(lines[0].ends_with(") q[0];"));
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
assert!(theta.abs() < 1e-10, "Identity theta should be ~0, got {}", theta);
let sum = phi + lambda;
let sum_mod = ((sum % (2.0 * PI)) + 2.0 * PI) % (2.0 * PI);
assert!(
sum_mod.abs() < 1e-10 || (sum_mod - 2.0 * PI).abs() < 1e-10,
"Identity phi+lambda should be ~0 mod 2pi, got {}",
sum
);
}
#[test]
fn test_unitary1q_hadamard() {
let h = FRAC_1_SQRT_2;
let hadamard = [
[Complex::new(h, 0.0), Complex::new(h, 0.0)],
[Complex::new(h, 0.0), Complex::new(-h, 0.0)],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, hadamard));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("U("));
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
let reconstructed = reconstruct_zyz(theta, phi, lambda);
assert_unitaries_equal_up_to_phase(&hadamard, &reconstructed);
}
#[test]
fn test_unitary1q_x_gate() {
let x_matrix = [
[Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)],
[Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, x_matrix));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
let reconstructed = reconstruct_zyz(theta, phi, lambda);
assert_unitaries_equal_up_to_phase(&x_matrix, &reconstructed);
}
#[test]
fn test_unitary1q_s_gate() {
let s_matrix = [
[Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)],
[Complex::new(0.0, 0.0), Complex::new(0.0, 1.0)],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, s_matrix));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
assert!(theta.abs() < 1e-10, "S gate theta should be ~0, got {}", theta);
let reconstructed = reconstruct_zyz(theta, phi, lambda);
assert_unitaries_equal_up_to_phase(&s_matrix, &reconstructed);
}
#[test]
fn test_unitary1q_arbitrary() {
let half = PI / 6.0;
let cos_h = half.cos();
let sin_h = half.sin();
let arb_matrix = [
[
Complex::new(cos_h, 0.0),
Complex::new(0.0, -sin_h),
],
[
Complex::new(0.0, -sin_h),
Complex::new(cos_h, 0.0),
],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, arb_matrix));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
let reconstructed = reconstruct_zyz(theta, phi, lambda);
assert_unitaries_equal_up_to_phase(&arb_matrix, &reconstructed);
}
#[test]
fn test_unitary1q_y_gate() {
let y_matrix = [
[Complex::new(0.0, 0.0), Complex::new(0.0, -1.0)],
[Complex::new(0.0, 1.0), Complex::new(0.0, 0.0)],
];
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Unitary1Q(0, y_matrix));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
let (theta, phi, lambda) = extract_u_angles(&lines[0]);
let reconstructed = reconstruct_zyz(theta, phi, lambda);
assert_unitaries_equal_up_to_phase(&y_matrix, &reconstructed);
}
#[test]
fn test_round_trip_text_validity() {
let mut circuit = QuantumCircuit::new(4);
circuit
.h(0)
.x(1)
.y(2)
.z(3)
.s(0)
.t(1)
.rx(2, 1.234)
.ry(3, 2.345)
.rz(0, 0.567)
.phase(1, PI / 5.0)
.cnot(0, 1)
.cz(2, 3)
.swap(0, 3)
.rzz(1, 2, PI / 7.0)
.barrier()
.reset(0)
.measure(0)
.measure(1)
.measure(2)
.measure(3);
let qasm = to_qasm3(&circuit);
assert_valid_header(&qasm);
assert!(qasm.contains("qubit[4] q;"));
assert!(qasm.contains("bit[4] c;"));
for line in qasm.lines().skip(4) {
if line.is_empty() {
continue;
}
assert!(
line.ends_with(';'),
"Line should end with semicolon: '{}'",
line
);
let valid_starts = [
"h ", "x ", "y ", "z ", "s ", "sdg ", "t ", "tdg ",
"rx(", "ry(", "rz(", "p(", "rzz(",
"cx ", "cz ", "swap ",
"c[", "reset ", "barrier ", "U(",
];
assert!(
valid_starts.iter().any(|prefix| line.starts_with(prefix)),
"Line has unexpected format: '{}'",
line
);
}
}
#[test]
fn test_round_trip_gate_count() {
let mut circuit = QuantumCircuit::new(2);
circuit.h(0).cnot(0, 1).measure(0).measure(1);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(
lines.len(),
circuit.gate_count(),
"Gate line count should match circuit gate count"
);
}
#[test]
fn test_empty_circuit() {
let circuit = QuantumCircuit::new(1);
let qasm = to_qasm3(&circuit);
assert_valid_header(&qasm);
assert!(qasm.contains("qubit[1] q;"));
assert!(qasm.contains("bit[1] c;"));
let lines = gate_lines(&qasm);
assert!(lines.is_empty());
}
#[test]
fn test_qubit_indices_in_bounds() {
let mut circuit = QuantumCircuit::new(4);
circuit.h(0).cnot(0, 3).swap(1, 2).measure(3);
let qasm = to_qasm3(&circuit);
for line in qasm.lines().skip(4) {
let mut remaining = line;
while let Some(start) = remaining.find("q[") {
let after_q = &remaining[start + 2..];
if let Some(end) = after_q.find(']') {
let idx_str = &after_q[..end];
let idx: u32 = idx_str
.parse()
.unwrap_or_else(|_| panic!("Invalid qubit index in: '{}'", line));
assert!(idx < 4, "Qubit index {} out of bounds in: '{}'", idx, line);
remaining = &after_q[end + 1..];
} else {
break;
}
}
}
}
#[test]
fn test_negative_angle() {
let mut circuit = QuantumCircuit::new(1);
circuit.rx(0, -PI / 4.0);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
let angle = extract_angle(&lines[0]);
assert!((angle - (-PI / 4.0)).abs() < 1e-10);
}
#[test]
fn test_zero_angle() {
let mut circuit = QuantumCircuit::new(1);
circuit.rx(0, 0.0);
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 1);
assert!(lines[0].starts_with("rx("));
}
#[test]
fn test_sdg_and_tdg_gates() {
let mut circuit = QuantumCircuit::new(1);
circuit.add_gate(Gate::Sdg(0));
circuit.add_gate(Gate::Tdg(0));
let qasm = to_qasm3(&circuit);
let lines = gate_lines(&qasm);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "sdg q[0];");
assert_eq!(lines[1], "tdg q[0];");
}
#[test]
fn test_large_circuit_structure() {
let mut circuit = QuantumCircuit::new(4);
for i in 0..4u32 {
circuit.h(i);
for j in (i + 1)..4 {
let angle = PI / (1u32 << (j - i)) as f64;
circuit.phase(j, angle);
circuit.cnot(j, i);
}
}
circuit.measure_all();
let qasm = to_qasm3(&circuit);
assert_valid_header(&qasm);
assert!(qasm.contains("qubit[4] q;"));
let lines = gate_lines(&qasm);
let h_count = lines.iter().filter(|l| l.starts_with("h ")).count();
let measure_count = lines
.iter()
.filter(|l| l.contains("measure"))
.count();
assert_eq!(h_count, 4);
assert_eq!(measure_count, 4);
}
fn extract_angle(line: &str) -> f64 {
let open = line.find('(').expect("No opening parenthesis");
let close = line.find(')').expect("No closing parenthesis");
let angle_str = &line[open + 1..close];
let first = angle_str.split(',').next().unwrap().trim();
first.parse::<f64>().unwrap_or_else(|e| {
panic!("Failed to parse angle '{}': {}", first, e)
})
}
fn extract_u_angles(line: &str) -> (f64, f64, f64) {
let open = line.find('(').expect("No opening parenthesis");
let close = line.find(')').expect("No closing parenthesis");
let inside = &line[open + 1..close];
let parts: Vec<&str> = inside.split(',').map(|s| s.trim()).collect();
assert_eq!(parts.len(), 3, "U gate should have 3 angles, got: {:?}", parts);
let theta: f64 = parts[0].parse().unwrap();
let phi: f64 = parts[1].parse().unwrap();
let lambda: f64 = parts[2].parse().unwrap();
(theta, phi, lambda)
}
fn reconstruct_zyz(theta: f64, phi: f64, lambda: f64) -> [[Complex; 2]; 2] {
let rz = |a: f64| -> [[Complex; 2]; 2] {
[
[Complex::from_polar(1.0, -a / 2.0), Complex::ZERO],
[Complex::ZERO, Complex::from_polar(1.0, a / 2.0)],
]
};
let ct = (theta / 2.0).cos();
let st = (theta / 2.0).sin();
let ry_theta: [[Complex; 2]; 2] = [
[Complex::new(ct, 0.0), Complex::new(-st, 0.0)],
[Complex::new(st, 0.0), Complex::new(ct, 0.0)],
];
let rz_phi = rz(phi);
let rz_lambda = rz(lambda);
let temp = mat_mul(&rz_phi, &ry_theta);
mat_mul(&temp, &rz_lambda)
}
fn mat_mul(a: &[[Complex; 2]; 2], b: &[[Complex; 2]; 2]) -> [[Complex; 2]; 2] {
[
[
a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1],
],
]
}
fn assert_unitaries_equal_up_to_phase(
expected: &[[Complex; 2]; 2],
actual: &[[Complex; 2]; 2],
) {
let eps = 1e-8;
let mut phase = Complex::ZERO;
let mut found = false;
for i in 0..2 {
for j in 0..2 {
if expected[i][j].norm() > eps {
let denom = expected[i][j].norm_sq();
phase = actual[i][j] * expected[i][j].conj() * (1.0 / denom);
found = true;
break;
}
}
if found {
break;
}
}
assert!(found, "Expected matrix is all zeros");
assert!(
(phase.norm() - 1.0).abs() < eps,
"Phase factor should have unit magnitude, got {}",
phase.norm()
);
for i in 0..2 {
for j in 0..2 {
let scaled = expected[i][j] * phase;
let diff = (actual[i][j] - scaled).norm();
assert!(
diff < eps,
"Mismatch at [{},{}]: expected {} (scaled), got {}. diff={}",
i,
j,
scaled,
actual[i][j],
diff,
);
}
}
}
}