use crate::circuit::gate::circuit_gate::FrozenCircuit;
use crate::circuit::gate::{StandardGate, UnitaryGate};
use crate::circuit::parameter::Parameter;
use crate::circuit::symbolic_matrix::standard_gate_symbolic_matrix;
use crate::circuit::{Circuit, ClassicalExpr, ClassicalType, Instruction, Qubit};
use crate::ir::qasm2::dump::{QasmDumpError, dump, dumps, to_path, to_string};
use crate::ir::qasm2::load::loads;
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
fn unique_temp_path(test_name: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
std::env::temp_dir().join(format!(
"cqlib_qasm2_dump_{}_{}_{}.qasm",
std::process::id(),
test_name,
nonce
))
}
fn assert_qasm_contains_ordered(qasm: &str, expected_lines: &[&str]) {
let mut search_start = 0;
for (i, &line) in expected_lines.iter().enumerate() {
let pos = qasm[search_start..].find(line);
assert!(
pos.is_some(),
"QASM output missing expected line #{}:\n'{}'\n\nActual QASM:\n{}",
i + 1,
line,
qasm
);
search_start += pos.unwrap() + line.len();
}
}
fn verify_qasm_roundtrip(qasm: &str, expected_qubits: usize) {
let parsed = loads(qasm);
assert!(
parsed.is_ok(),
"Generated QASM should be valid and parseable. Error: {:?}\nQASM:\n{}",
parsed.err(),
qasm
);
let circuit = parsed.unwrap();
assert_eq!(
circuit.num_qubits(),
expected_qubits,
"Parsed circuit should have {} qubits",
expected_qubits
);
}
fn assert_qasm_contains(qasm: &str, expected_lines: &[&str]) {
for &line in expected_lines {
assert!(
qasm.contains(line),
"QASM output missing expected line:\n'{}'\n\nActual QASM:\n{}",
line,
qasm
);
}
}
#[test]
fn test_dump_standard_gates() {
let mut circuit = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
circuit.h(q0).unwrap();
circuit.cx(q0, q1).unwrap();
circuit.x(q1).unwrap();
circuit.y(q2).unwrap();
circuit.z(q0).unwrap();
circuit.ccx(q0, q1, q2).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
let expected = &[
concat!(
"// This file is auto-generated by Cqlib v",
env!("CARGO_PKG_VERSION"),
"."
),
"OPENQASM 2.0;",
"include \"qelib1.inc\";",
"qreg q[3];",
"h q[0];",
"cx q[0],q[1];",
"x q[1];",
"y q[2];",
"z q[0];",
"ccx q[0],q[1],q[2];",
];
assert_qasm_contains_ordered(&qasm, expected);
assert!(
!qasm.contains("creg"),
"Should not have creg without conditional ops"
);
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn test_to_string_alias_matches_dumps() {
let mut circuit = Circuit::new(1);
circuit.h(Qubit::new(0)).unwrap();
assert_eq!(to_string(&circuit).unwrap(), dumps(&circuit).unwrap());
}
#[test]
fn test_to_path_alias_writes_file() {
let mut circuit = Circuit::new(1);
circuit.h(Qubit::new(0)).unwrap();
let path = unique_temp_path("to_path");
to_path(&circuit, path.as_path()).unwrap();
assert_eq!(fs::read_to_string(&path).unwrap(), dumps(&circuit).unwrap());
fs::remove_file(path).unwrap();
}
#[test]
fn test_dump_parametric_gates() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
circuit.rx(q0, std::f64::consts::PI / 2.0).unwrap();
let theta = Parameter::try_from("theta").unwrap();
circuit.ry(q0, theta.clone()).unwrap();
let phi = theta + 0.5;
circuit.rz(q0, phi).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"rx(1.5707963267948966) q[0];",
"ry(theta) q[0];",
"rz(0.5 + theta) q[0];",
],
);
}
#[test]
fn test_dump_complex_parameters() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let theta = Parameter::try_from("theta").unwrap();
let phi = Parameter::try_from("phi").unwrap();
circuit.rx(q0, theta.clone() * Parameter::pi()).unwrap();
circuit.ry(q0, phi.clone() / 2.0).unwrap();
circuit.rz(q1, (theta + phi) * 0.5).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert!(
qasm.contains("rx(theta*pi)"),
"Should contain 'rx(theta*pi)', got: {}",
qasm
);
assert!(
qasm.contains("ry(phi/2)"),
"Should contain 'ry(phi/2)', got: {}",
qasm
);
assert!(
qasm.contains("rz(0.5*(phi + theta))"),
"Should contain complex expression, got: {}",
qasm
);
}
#[test]
fn test_dump_directives_no_condition() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
circuit.reset(q0).unwrap();
circuit.barrier(vec![q0, q1]).unwrap();
circuit.measure(q0).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"reset q[0];",
"barrier q[0],q[1];",
"measure q[0] -> v0[0];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_custom_gate() {
let mut sub_circ = Circuit::new(2);
let sq0 = Qubit::new(0);
let sq1 = Qubit::new(1);
sub_circ.h(sq0).unwrap();
sub_circ.cx(sq0, sq1).unwrap();
let gate = sub_circ.to_gate("bell").unwrap();
let mut main_circ = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
main_circ.append(gate, vec![q0, q1], vec![], None).unwrap();
let qasm = dumps(&main_circ).expect("Dump failed");
let expected_def = &["gate bell q0,q1 {", "h q0;", "cx q0,q1;", "}"];
assert_qasm_contains(&qasm, expected_def);
assert_qasm_contains(&qasm, &["bell q[0],q[1];"]);
}
#[test]
fn test_dump_rejects_measurement_in_gate_definition() {
let mut inner = Circuit::new(1);
inner.measure(Qubit::new(0)).unwrap();
let gate = inner.to_gate("measuring_gate").unwrap();
let mut circuit = Circuit::new(1);
circuit
.append(gate, [Qubit::new(0)], std::iter::empty(), None)
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::MeasureInGateNotAllowed)
));
}
#[test]
fn test_dump_rejects_reset_in_circuit_gate_definition() {
let mut inner = Circuit::new(1);
inner.reset(Qubit::new(0)).unwrap();
let gate = inner.to_gate("resetting_gate").unwrap();
let mut circuit = Circuit::new(1);
circuit
.append(gate, [Qubit::new(0)], std::iter::empty(), None)
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::ResetInGateNotAllowed)
));
}
#[test]
fn test_dump_rejects_reset_in_unitary_gate_definition() {
let mut inner = Circuit::new(1);
inner.reset(Qubit::new(0)).unwrap();
let gate = UnitaryGate::new("ResettingUnitary", 1, 0)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
let mut circuit = Circuit::new(1);
circuit.unitary(gate, vec![Qubit::new(0)]).unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::ResetInGateNotAllowed)
));
}
#[test]
fn test_dump_parameterized_custom_gate() {
let mut sub_circ = Circuit::new(1);
let sq0 = Qubit::new(0);
let lambda = Parameter::try_from("lambda").unwrap();
sub_circ.rx(sq0, lambda).unwrap();
let gate = sub_circ.to_gate("my_rot").unwrap();
let mut main_circ = Circuit::new(1);
let q0 = Qubit::new(0);
main_circ
.append(gate.clone(), vec![q0], vec![1.23.into()], None)
.unwrap();
let gamma = Parameter::try_from("gamma").unwrap();
main_circ
.append(gate, vec![q0], vec![gamma.into()], None)
.unwrap();
let qasm = dumps(&main_circ).expect("Dump failed");
let expected_def = &["gate my_rot(lambda) q0 {", "rx(lambda) q0;", "}"];
assert_qasm_contains(&qasm, expected_def);
let expected_usage = &["my_rot(1.23) q[0];", "my_rot(gamma) q[0];"];
assert_qasm_contains(&qasm, expected_usage);
}
#[test]
fn test_global_phase() {
let circuit = Circuit::new(1);
let qasm = dumps(&circuit).expect("Dump failed");
assert!(!qasm.contains("// Global Phase: 0"));
}
#[test]
fn test_dump_mc_gate() {
let mut circuit = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
circuit
.multi_control(StandardGate::X, vec![q0, q1], vec![q2], vec![])
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains(&qasm, &["ccx q[0],q[1],q[2];"]);
}
#[test]
fn test_dump_gate_collision_same_interface() {
let mut c1 = Circuit::new(1);
c1.h(Qubit::new(0)).unwrap();
let g1 = c1.to_gate("my_gate").unwrap();
let mut c2 = Circuit::new(1);
c2.x(Qubit::new(0)).unwrap();
let g2 = c2.to_gate("my_gate").unwrap();
let mut main = Circuit::new(1);
let q0 = Qubit::new(0);
main.append(g1, vec![q0], vec![], None).unwrap();
main.append(g2, vec![q0], vec![], None).unwrap();
assert!(matches!(
dumps(&main),
Err(QasmDumpError::ConflictingGateDefinition { name, .. }) if name == "my_gate"
));
}
#[test]
fn test_dump_nested_custom_gates() {
let mut leaf = Circuit::new(1);
let p = Parameter::try_from("p").unwrap();
leaf.rz(Qubit::new(0), p).unwrap();
let gate_leaf = leaf.to_gate("gate_leaf").unwrap();
let mut mid = Circuit::new(1);
let m = Parameter::try_from("m").unwrap();
let param_expr: Parameter = m.clone() * 2.0;
mid.append(
gate_leaf,
vec![Qubit::new(0)],
vec![param_expr.into()],
None,
)
.unwrap();
let gate_mid = mid.to_gate("gate_mid").unwrap();
let mut main = Circuit::new(1);
let theta = Parameter::try_from("theta").unwrap();
main.append(gate_mid, vec![Qubit::new(0)], vec![theta.into()], None)
.unwrap();
let qasm = dumps(&main).expect("Dump failed");
assert_qasm_contains(
&qasm,
&[
"gate gate_leaf(p) q0 {",
"rz(p) q0;",
"}",
"gate gate_mid(m) q0 {",
"gate_leaf(2*m) q0;", "}",
"gate_mid(theta) q[0];",
],
);
}
#[test]
fn test_dump_unitary_gate_with_circuit() {
let mut inner_circuit = Circuit::new(2);
inner_circuit.h(Qubit::new(0)).unwrap();
inner_circuit.cx(Qubit::new(0), Qubit::new(1)).unwrap();
let frozen_circuit = FrozenCircuit::new(inner_circuit);
let u_gate = UnitaryGate::new("MyBell", 2, 0)
.with_circuit(Arc::new(frozen_circuit))
.unwrap();
let mut main_circuit = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
main_circuit.x(q0).unwrap();
main_circuit.unitary(u_gate, vec![q0, q1]).unwrap();
main_circuit.z(q1).unwrap();
let qasm = dumps(&main_circuit).expect("Dump failed");
println!("{}", qasm);
assert_qasm_contains(&qasm, &["gate MyBell q0,q1 {", "h q0;", "cx q0,q1;", "}"]);
assert_qasm_contains(&qasm, &["x q[0];", "MyBell q[0],q[1];", "z q[1];"]);
}
#[test]
fn test_dump_parameterized_unitary_gate_with_circuit() {
let mut inner = Circuit::new(1);
inner.rx(Qubit::new(0), Parameter::symbol("theta")).unwrap();
let gate = UnitaryGate::new("param_unitary", 1, 1)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
let mut circuit = Circuit::new(1);
circuit
.unitary_with_params(gate, vec![Qubit::new(0)], [1.25.into()])
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains(
&qasm,
&[
"gate param_unitary(theta) q0 {",
"rx(theta) q0;",
"param_unitary(1.25) q[0];",
],
);
verify_qasm_roundtrip(&qasm, 1);
}
#[test]
fn test_dump_parameterized_symbolic_unitary_as_opaque() {
let gate = UnitaryGate::new("symbolic_opaque", 1, 1)
.with_symbolic_matrix(
["angle"],
standard_gate_symbolic_matrix(StandardGate::RX, &[Parameter::symbol("angle")]).unwrap(),
)
.unwrap();
let mut circuit = Circuit::new(1);
circuit
.unitary_with_params(gate, vec![Qubit::new(0)], [0.5.into()])
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains(
&qasm,
&[
"opaque symbolic_opaque(angle) q0;",
"symbolic_opaque(0.5) q[0];",
],
);
}
#[test]
fn test_dump_parameterized_opaque_unitary_generates_parameter_names() {
let gate = UnitaryGate::new("opaque_params", 1, 2);
let mut circuit = Circuit::new(1);
circuit
.unitary_with_params(
gate,
vec![Qubit::new(0)],
[Parameter::from(0.25).into(), Parameter::from(0.75).into()],
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains(
&qasm,
&[
"opaque opaque_params(p0,p1) q0;",
"opaque_params(0.25,0.75) q[0];",
],
);
}
#[test]
fn test_dump_parameterized_unitary_call_inside_circuit_gate() {
let mut leaf_body = Circuit::new(1);
leaf_body
.rx(Qubit::new(0), Parameter::symbol("alpha"))
.unwrap();
let leaf = UnitaryGate::new("param_leaf", 1, 1)
.with_circuit(Arc::new(FrozenCircuit::new(leaf_body)))
.unwrap();
let mut wrapper_body = Circuit::new(1);
wrapper_body
.unitary_with_params(
leaf,
vec![Qubit::new(0)],
[Parameter::symbol("theta").into()],
)
.unwrap();
let wrapper = wrapper_body.to_gate("unitary_wrapper").unwrap();
let mut circuit = Circuit::new(1);
circuit
.append(wrapper, [Qubit::new(0)], [0.3.into()], None)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains(
&qasm,
&[
"gate unitary_wrapper(theta) q0 {",
"param_leaf(theta) q0;",
"gate param_leaf(alpha) q0 {",
"unitary_wrapper(0.3) q[0];",
],
);
verify_qasm_roundtrip(&qasm, 1);
}
#[test]
fn test_dump_parameterized_unitary_call_inside_condition() {
let mut inner = Circuit::new(1);
inner.rx(Qubit::new(0), Parameter::symbol("theta")).unwrap();
let gate = UnitaryGate::new("conditional_unitary", 1, 1)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
let mut circuit = Circuit::new(2);
let measured = circuit.measure(Qubit::new(0)).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_(condition, move |body| {
body.unitary_with_params(gate, vec![Qubit::new(1)], [0.75.into()])?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains(
&qasm,
&[
"gate conditional_unitary(theta) q0 {",
"if (v0 == 1) conditional_unitary(0.75) q[1];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_unitary_gate_with_circuit_nested() {
let mut sub_circ = Circuit::new(1);
sub_circ.h(Qubit::new(0)).unwrap();
sub_circ.s(Qubit::new(0)).unwrap();
let gate_def = sub_circ.to_gate("hs_gate").unwrap();
let mut inner_circuit = Circuit::new(2);
inner_circuit
.append(gate_def.clone(), vec![Qubit::new(0)], vec![], None)
.unwrap();
inner_circuit.cx(Qubit::new(0), Qubit::new(1)).unwrap();
inner_circuit
.append(gate_def, vec![Qubit::new(1)], vec![], None)
.unwrap();
let frozen_circuit = FrozenCircuit::new(inner_circuit);
let u_gate = UnitaryGate::new("CustomOp", 2, 0)
.with_circuit(Arc::new(frozen_circuit))
.unwrap();
let mut main_circuit = Circuit::new(2);
main_circuit
.unitary(u_gate, vec![Qubit::new(0), Qubit::new(1)])
.unwrap();
let qasm = dumps(&main_circuit).expect("Dump failed");
assert_qasm_contains(&qasm, &["gate hs_gate q0 {", "h q0;", "s q0;", "}"]);
assert_qasm_contains(
&qasm,
&[
"gate CustomOp q0,q1 {",
"hs_gate q0;",
"cx q0,q1;",
"hs_gate q1;",
"}",
],
);
assert_qasm_contains(&qasm, &["CustomOp q[0],q[1];"]);
}
#[test]
fn test_dump_unitary_gate_without_circuit() {
use ndarray::Array2;
use num_complex::Complex64;
let mat = Array2::eye(2).mapv(|x| Complex64::new(x, 0.0));
let u_gate = UnitaryGate::new("MatrixOnly", 1, 0)
.with_matrix(mat)
.unwrap();
let mut circuit = Circuit::new(1);
circuit.unitary(u_gate, vec![Qubit::new(0)]).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
println!("{}", qasm);
assert_qasm_contains(&qasm, &["opaque MatrixOnly q0;", "MatrixOnly q[0];"]);
}
#[test]
fn test_dump_extended_gates() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let theta = 1.0;
circuit.crx(q0, q1, theta).unwrap();
circuit.cry(q0, q1, theta).unwrap();
circuit.rzz(q0, q1, theta).unwrap();
circuit.rxx(q0, q1, theta).unwrap();
circuit.ryy(q0, q1, theta).unwrap();
circuit.rzx(q0, q1, theta).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"gate crx(theta) a,b { h b;rz(theta/2) b; cx a,b; rz(-theta/2) b; cx a,b; h b;}",
"gate cry(theta) a,b { ry(theta/2) b; cx a,b; ry(-theta/2) b; cx a,b; }",
"gate rzz(theta) a,b { cx a,b; rz(theta) b; cx a,b; }",
"gate rxx(theta) a,b { h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; }",
"gate ryy(theta) a,b { rx(pi/2) a; rx(pi/2) b; cx a,b; rz(theta) b; cx a,b; rx(-pi/2) a; rx(-pi/2) b; }",
"gate rzx(theta) a,b { h b; cx a,b; rz(theta) b; cx a,b; h b; }",
],
);
assert_qasm_contains_ordered(
&qasm,
&[
"crx(1) q[0],q[1];",
"cry(1) q[0],q[1];",
"rzz(1) q[0],q[1];",
"rxx(1) q[0],q[1];",
"ryy(1) q[0],q[1];",
"rzx(1) q[0],q[1];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_if_statement_simple() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_(condition, |body| {
body.x(q1)?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump should succeed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"h q[0];",
"measure q[0] -> v0[0];",
"if (v0 == 1) x q[1];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_conditional_measurement() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let target = circuit.var(ClassicalType::Bit);
let measured = circuit.measure(q0).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_(condition, |body| {
body.measure_into(q1, target)?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump should succeed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg c0[1];",
"creg v0[1];",
"measure q[0] -> v0[0];",
"if (v0 == 1) measure q[1] -> c0[0];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_conditional_barrier_is_rejected() {
let mut circuit = Circuit::new(2);
let measured = circuit.measure(Qubit::new(0)).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_(condition, |body| {
body.barrier(vec![Qubit::new(1)])?;
Ok(())
})
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalControl(_))
));
}
#[test]
fn test_dump_measure_into_bitvec_and_multibit_condition() {
let mut circuit = Circuit::new(3);
let register = circuit.var(ClassicalType::bit_vec(3).unwrap());
circuit
.measure_bits_into([Qubit::new(0), Qubit::new(1), Qubit::new(2)], register)
.unwrap();
let condition = ClassicalExpr::eq(
ClassicalExpr::bit_vec_to_uint(register.expr()).unwrap(),
ClassicalExpr::uint_literal(3, 5).unwrap(),
)
.unwrap();
circuit
.if_(condition, |body| {
body.x(Qubit::new(0))?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump should succeed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg c0[3];",
"measure q[0] -> c0[0];",
"measure q[1] -> c0[1];",
"measure q[2] -> c0[2];",
"if (c0 == 5) x q[0];",
],
);
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn test_dynamic_qasm_load_dump_roundtrip() {
let circuit = loads(
r#"
OPENQASM 2.0;
qreg q[3];
creg c[3];
measure q -> c;
if (c == 5) x q[0];
"#,
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains_ordered(
&qasm,
&[
"creg c0[3];",
"measure q[0] -> c0[0];",
"measure q[1] -> c0[1];",
"measure q[2] -> c0[2];",
"if (c0 == 5) x q[0];",
],
);
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn test_dynamic_qasm_roundtrip_does_not_grow_unused_cregs() {
let circuit = loads(
r#"
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
measure q[0] -> c[0];
if (c == 1) x q[1];
"#,
)
.unwrap();
let first_dump = dumps(&circuit).unwrap();
let restored = loads(&first_dump).unwrap();
let second_dump = dumps(&restored).unwrap();
let restored_again = loads(&second_dump).unwrap();
let third_dump = dumps(&restored_again).unwrap();
assert_eq!(second_dump, first_dump);
assert_eq!(third_dump, second_dump);
assert_qasm_contains_ordered(
&first_dump,
&[
"creg c0[2];",
"h q[0];",
"measure q[0] -> c0[0];",
"if (c0 == 1) x q[1];",
],
);
assert!(!first_dump.contains("creg v0[1];"), "got:\n{first_dump}");
}
#[test]
fn test_dump_if_else_is_rejected() {
let mut circuit = Circuit::new(2);
let measured = circuit.measure(Qubit::new(0)).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_else(
condition,
|body| {
body.x(Qubit::new(1))?;
Ok(())
},
|body| {
body.z(Qubit::new(1))?;
Ok(())
},
)
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalControl(_))
));
}
#[test]
fn test_dump_while_loop_error() {
let mut circuit = Circuit::new(1);
circuit
.while_(ClassicalExpr::bool_literal(true), |body| {
body.h(Qubit::new(0))?;
Ok(())
})
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalControl(_))
));
}
#[test]
fn test_dump_general_store_is_rejected() {
let mut circuit = Circuit::new(1);
let register = circuit.var(ClassicalType::bit_vec(2).unwrap());
circuit
.store(register, ClassicalExpr::bit_vec_literal(2, 1).unwrap())
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalData(_))
));
}
#[test]
fn test_dump_rejects_reusing_fused_measurement_value() {
let mut circuit = Circuit::new(1);
let register = circuit.var(ClassicalType::bit_vec(1).unwrap());
let measured = circuit
.measure_bits_into([Qubit::new(0)], register)
.unwrap();
let condition = ClassicalExpr::eq(
ClassicalExpr::bit_vec_to_uint(measured.expr()).unwrap(),
ClassicalExpr::uint_literal(1, 1).unwrap(),
)
.unwrap();
circuit.if_(condition, |_| Ok(())).unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalData(_))
));
}
#[test]
fn test_dump_empty_circuit() {
let circuit = Circuit::new(2);
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&["OPENQASM 2.0;", "include \"qelib1.inc\";", "qreg q[2];"],
);
assert!(!qasm.contains("h q["), "Empty circuit should have no gates");
assert!(!qasm.contains("creg"), "Empty circuit should have no creg");
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_multiple_conditional_qubits() {
let mut circuit = Circuit::new(4);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
let q3 = Qubit::new(3);
circuit.h(q0).unwrap();
circuit.h(q1).unwrap();
let measured0 = circuit.measure(q0).unwrap();
let measured1 = circuit.measure(q1).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured0.expr()).unwrap(),
|body| {
body.x(q2)?;
Ok(())
},
)
.unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured1.expr()).unwrap(),
|body| {
body.z(q3)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"creg v1[1];",
"h q[0];",
"h q[1];",
"measure q[0] -> v0[0];",
"measure q[1] -> v1[0];",
"if (v0 == 1) x q[2];",
"if (v1 == 1) z q[3];",
],
);
verify_qasm_roundtrip(&qasm, 4);
}
#[test]
fn test_dump_if_body_with_mcgate() {
let mut circuit = Circuit::new(4);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
let q3 = Qubit::new(3);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.append(
Instruction::McGate(Box::new(crate::circuit::gate::MCGate::new(
2,
StandardGate::X,
))),
[q1, q2, q3],
std::iter::empty(),
None,
)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"h q[0];",
"measure q[0] -> v0[0];",
"if (v0 == 1) ccx q[1],q[2],q[3];",
],
);
verify_qasm_roundtrip(&qasm, 4);
}
#[test]
fn test_dump_if_body_with_parameterized_mcgate() {
let mut circuit = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.append(
Instruction::McGate(Box::new(crate::circuit::gate::MCGate::new(
1,
StandardGate::RX,
))),
[q1, q2],
[crate::circuit::ParameterValue::from(0.5)],
None,
)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"h q[0];",
"measure q[0] -> v0[0];",
"if (v0 == 1) crx(0.5) q[1],q[2];",
],
);
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn test_dump_if_body_with_custom_gate() {
let mut sub_circ = Circuit::new(2);
sub_circ.h(Qubit::new(0)).unwrap();
sub_circ.cx(Qubit::new(0), Qubit::new(1)).unwrap();
let mut circuit = Circuit::new(4);
let q0 = Qubit::new(0);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
let custom_gate = sub_circ.to_gate("my_bell").unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.append(
custom_gate,
[Qubit::new(1), Qubit::new(2)],
std::iter::empty(),
None,
)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"gate my_bell q0,q1 {",
"h q[0];",
"measure q[0] -> v0[0];",
"if (v0 == 1) my_bell q[1],q[2];",
],
);
}
#[test]
fn test_dump_nested_control_flow_error() {
let mut circuit = Circuit::new(2);
let outer = circuit.measure(Qubit::new(0)).unwrap();
let inner = circuit.measure(Qubit::new(1)).unwrap();
circuit
.if_(ClassicalExpr::bit_to_bool(outer.expr()).unwrap(), |body| {
body.if_(ClassicalExpr::bit_to_bool(inner.expr())?, |inner_body| {
inner_body.z(Qubit::new(1))?;
Ok(())
})?;
Ok(())
})
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::UnsupportedClassicalControl(_))
));
}
#[test]
fn test_dump_empty_if_body() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
circuit
.if_(ClassicalExpr::bit_to_bool(measured.expr()).unwrap(), |_| {
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(&qasm, &["creg v0[1];", "h q[0];", "measure q[0] -> v0[0];"]);
assert!(
!qasm.contains("if ("),
"Empty body should not generate if statement"
);
verify_qasm_roundtrip(&qasm, 1);
}
#[test]
fn test_dump_multi_operation_if_body() {
let mut circuit = Circuit::new(4);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
let q3 = Qubit::new(3);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.x(q1)?;
body.y(q2)?;
body.z(q3)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"creg v0[1];",
"h q[0];",
"measure q[0] -> v0[0];",
"if (v0 == 1) x q[1];",
"if (v0 == 1) y q[2];",
"if (v0 == 1) z q[3];",
],
);
verify_qasm_roundtrip(&qasm, 4);
}
#[test]
fn test_dump_unsupported_mcgate_error() {
let mut circuit = Circuit::new(4);
circuit
.append(
Instruction::McGate(Box::new(crate::circuit::gate::MCGate::new(
3,
StandardGate::X,
))),
[Qubit::new(0), Qubit::new(1), Qubit::new(2), Qubit::new(3)],
std::iter::empty(),
None,
)
.unwrap();
let result = dumps(&circuit);
assert!(result.is_err(), "Unsupported McGate should fail");
match result.err().unwrap() {
QasmDumpError::FormatError(msg) => {
assert!(
msg.contains("Unsupported"),
"Error should mention unsupported: {}",
msg
);
}
other => panic!("Expected FormatError, got {:?}", other),
}
}
#[test]
fn test_dump_delay_gate() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
use crate::circuit::circuit_param::ParameterValue;
circuit.delay(q0, ParameterValue::Fixed(100.0)).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(&qasm, &["opaque delay(t) q;", "delay((100)) q[0];"]);
}
#[test]
fn test_dump_gate_collision_different_interface() {
let mut c1 = Circuit::new(1);
c1.h(Qubit::new(0)).unwrap();
let g1 = c1.to_gate("collision_gate").unwrap();
let mut c2 = Circuit::new(2);
c2.cx(Qubit::new(0), Qubit::new(1)).unwrap();
let g2 = c2.to_gate("collision_gate").unwrap();
let mut main = Circuit::new(2);
main.append(g1, vec![Qubit::new(0)], vec![], None).unwrap();
main.append(g2, vec![Qubit::new(0), Qubit::new(1)], vec![], None)
.unwrap();
assert!(matches!(
dumps(&main),
Err(QasmDumpError::ConflictingGateDefinition {
name,
existing_qubits: 1,
existing_params: 0,
conflicting_qubits: 2,
conflicting_params: 0,
}) if name == "collision_gate"
));
}
#[test]
fn test_dump_all_standard_gates() {
let mut circuit = Circuit::new(3);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
let q2 = Qubit::new(2);
circuit.i(q0).unwrap();
circuit.x(q0).unwrap();
circuit.y(q0).unwrap();
circuit.z(q0).unwrap();
circuit.h(q0).unwrap();
circuit.s(q0).unwrap();
circuit.sdg(q0).unwrap();
circuit.t(q0).unwrap();
circuit.tdg(q0).unwrap();
circuit.rx(q0, 0.1).unwrap();
circuit.ry(q0, 0.2).unwrap();
circuit.rz(q0, 0.3).unwrap();
circuit.cx(q0, q1).unwrap();
circuit.cy(q0, q1).unwrap();
circuit.cz(q0, q1).unwrap();
circuit.swap(q0, q1).unwrap();
circuit.ccx(q0, q1, q2).unwrap();
circuit.u(q0, 0.1, 0.2, 0.3).unwrap();
circuit.crz(q0, q1, 0.4).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
let expected_gates = [
"id q[0];",
"x q[0];",
"y q[0];",
"z q[0];",
"h q[0];",
"s q[0];",
"sdg q[0];",
"t q[0];",
"tdg q[0];",
"rx(0.1) q[0];",
"ry(0.2) q[0];",
"rz(0.3) q[0];",
"cx q[0],q[1];",
"cy q[0],q[1];",
"cz q[0],q[1];",
"swap q[0],q[1];",
"ccx q[0],q[1],q[2];",
"u3(0.1,0.2,0.3) q[0];",
"crz(0.4) q[0],q[1];",
];
for gate in &expected_gates {
assert!(
qasm.contains(gate),
"QASM should contain '{}',\ngot:\n{}",
gate,
qasm
);
}
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn test_dump_qelib1_cu1_without_redefinition_roundtrips() {
let circuit = loads(
r#"
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
cu1(pi/2) q[0],q[1];
"#,
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert!(!qasm.contains("gate cu1"), "got:\n{qasm}");
assert!(
qasm.contains("cu1(1.5707963267948966) q[0],q[1];"),
"got:\n{qasm}"
);
loads(&qasm).expect("dumped qelib1 cu1 call should reload");
}
#[test]
fn test_dump_fsim_gate_roundtrips_as_cqlib_extension() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
circuit.fsim(q0, q1, 0.1, 0.2).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert!(qasm.contains("fsim"), "QASM should contain fsim gate");
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_xy_and_rxy_as_u3_decompositions() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
circuit.xy(q0, 0.5).unwrap();
circuit.rxy(q0, 0.3, 0.4).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"// xy(0.5) q[0]",
"u3(pi,0.5 - pi/2,-0.5 + pi/2) q[0];",
"// rxy(0.3,0.4) q[0]",
"u3(0.3,0.4 - pi/2,-0.4 + pi/2) q[0];",
],
);
verify_qasm_roundtrip(&qasm, 1);
}
#[test]
fn test_dump_conditional_rxy_as_u3_decomposition() {
let mut circuit = Circuit::new(2);
let measured = circuit.measure(Qubit::new(0)).unwrap();
let condition = ClassicalExpr::bit_to_bool(measured.expr()).unwrap();
circuit
.if_(condition, |body| {
body.rxy(Qubit::new(1), 0.3, 0.4)?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"measure q[0] -> v0[0];",
"if (v0 == 1) u3(0.3,0.4 - pi/2,-0.4 + pi/2) q[1];",
],
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_x2p_x2m_y2p_y2m() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
circuit.x2p(q0).unwrap();
circuit.x2m(q0).unwrap();
circuit.y2p(q0).unwrap();
circuit.y2m(q0).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert_qasm_contains_ordered(
&qasm,
&[
"// x2p q[0]",
"rx(pi/2) q[0]",
"// x2m q[0]",
"rx(-pi/2) q[0]",
"// y2p q[0]",
"ry(pi/2) q[0]",
"// y2m q[0]",
"ry(-pi/2) q[0]",
],
);
}
#[test]
fn test_dump_phase_gate_outputs_u1() {
let mut circuit = Circuit::new(1);
let q0 = Qubit::new(0);
circuit.phase(q0, 0.5).unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert!(
qasm.contains("u1(0.5)"),
"Phase gate should output as u1: {}",
qasm
);
}
#[test]
fn test_dump_gphase_outputs_lossy_comment() {
let mut circuit = Circuit::new(1);
circuit
.append(
Instruction::Standard(StandardGate::GPhase),
Vec::<Qubit>::new(),
vec![0.5.into()],
None,
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert!(qasm.contains("// gphase(0.5)"), "got:\n{qasm}");
let parsed = loads(&qasm).expect("Generated QASM should still parse");
assert_eq!(parsed.global_phase().evaluate(&None).unwrap(), 0.0);
}
#[test]
fn test_dump_global_phase_outputs_lossy_comment() {
let mut circuit = Circuit::new(1);
circuit.set_global_phase(Parameter::from(0.25));
let qasm = dumps(&circuit).expect("Dump failed");
assert!(qasm.contains("// Global Phase: 0.25"), "got:\n{qasm}");
let parsed = loads(&qasm).expect("Generated QASM should still parse");
assert_eq!(parsed.global_phase().evaluate(&None).unwrap(), 0.0);
}
#[test]
fn test_dump_file_io_error_preserves_source() {
let circuit = Circuit::new(0);
let path = std::env::temp_dir()
.join("cqlib_qasm2_missing_parent_for_io_source")
.join("out.qasm");
let error = dump(&circuit, path).unwrap_err();
assert!(matches!(error, QasmDumpError::IoError(_)));
assert!(std::error::Error::source(&error).is_some());
}
#[test]
fn test_dump_condition_value_zero() {
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
let condition =
ClassicalExpr::try_not(ClassicalExpr::bit_to_bool(measured.expr()).unwrap()).unwrap();
circuit
.if_(condition, |body| {
body.x(q1)?;
Ok(())
})
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
assert!(
qasm.contains("if (v0 == 0)"),
"Should have condition with value 0: {}",
qasm
);
verify_qasm_roundtrip(&qasm, 2);
}
#[test]
fn test_dump_qasm_ordering() {
let mut sub_circ = Circuit::new(1);
sub_circ.h(Qubit::new(0)).unwrap();
let custom_gate = sub_circ.to_gate("my_h").unwrap();
let mut circuit = Circuit::new(2);
let q0 = Qubit::new(0);
let q1 = Qubit::new(1);
circuit.h(q0).unwrap();
let measured = circuit.measure(q0).unwrap();
circuit.append(custom_gate, vec![q1], vec![], None).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.x(q1)?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).expect("Dump failed");
let lines: Vec<_> = qasm
.lines()
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.collect();
let header_pos = lines.iter().position(|l| l.contains("OPENQASM")).unwrap();
let qreg_pos = lines.iter().position(|l| l.starts_with("qreg")).unwrap();
let creg_pos = lines.iter().position(|l| l.starts_with("creg")).unwrap();
let gate_def_pos = lines.iter().position(|l| l.starts_with("gate ")).unwrap();
let h_pos = lines.iter().position(|l| *l == "h q[0];").unwrap();
let measure_pos = lines.iter().position(|l| l.contains("measure")).unwrap();
let if_pos = lines.iter().position(|l| l.contains("if (")).unwrap();
assert!(header_pos < qreg_pos, "Header before qreg");
assert!(qreg_pos < creg_pos, "Qreg before creg");
assert!(creg_pos < gate_def_pos, "Creg before gate definitions");
assert!(gate_def_pos < h_pos, "Gate definitions before operations");
assert!(h_pos < measure_pos, "H before measure");
assert!(measure_pos < if_pos, "Measure before if");
}
#[test]
fn parameter_errors_propagate_from_gate_export_with_context() {
use crate::circuit::{CircuitParam, ParameterValue};
use std::collections::HashMap;
let mut circuit = Circuit::new(1);
let qubit = Qubit::new(0);
let qubit_map = HashMap::from([(qubit, "q[0]".to_string())]);
for (gate, params) in [
(StandardGate::RX, vec![ParameterValue::Fixed(0.5)]),
(
StandardGate::RXY,
vec![ParameterValue::Fixed(0.5), ParameterValue::Fixed(0.25)],
),
] {
circuit
.append(Instruction::Standard(gate), [qubit], params, None)
.unwrap();
let valid = circuit.operations().last().unwrap();
let param_index = valid.params.len() - 1;
for param in [
CircuitParam::Index(99),
CircuitParam::Fixed(f64::NAN),
CircuitParam::Fixed(f64::INFINITY),
CircuitParam::Fixed(f64::NEG_INFINITY),
] {
let mut operation = valid.clone();
operation.params[param_index] = param;
let mut output = String::new();
let error = super::dump_standard_gate(
&gate,
&operation,
&circuit,
&mut output,
&qubit_map,
&HashMap::new(),
)
.unwrap_err();
assert!(
matches!(error, QasmDumpError::FormatError(ref message)
if message.contains(&format!("parameter {param_index}"))
&& message.contains(&format!("{gate:?}"))),
"{error}"
);
let error = super::operation_to_qasm(&operation, &circuit, &qubit_map, &HashMap::new())
.unwrap_err();
assert!(matches!(error, QasmDumpError::FormatError(_)));
}
}
}
#[test]
fn export_preserves_indexed_constants_and_symbol_substitution() {
use crate::circuit::ParameterValue;
use std::collections::HashMap;
let mut circuit = Circuit::new(1);
circuit
.rx(Qubit::new(0), ParameterValue::Param(Parameter::pi()))
.unwrap();
circuit
.rx(
Qubit::new(0),
ParameterValue::Param(Parameter::symbol("theta")),
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert!(qasm.contains("rx(pi)"), "{qasm}");
let replacements = HashMap::from([("theta".to_string(), Parameter::symbol("angle0"))]);
assert_eq!(
super::format_params(&circuit.operations()[1], &circuit, &replacements).unwrap(),
"(angle0)"
);
}