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::test_utils::assert_matrix_approx_eq;
use crate::circuit::{Circuit, ClassicalExpr, ClassicalType, Instruction, ParameterValue, 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
);
}
const HALF_ROTATION_GATES: [StandardGate; 6] = [
StandardGate::X2P,
StandardGate::X2M,
StandardGate::Y2P,
StandardGate::Y2M,
StandardGate::XY2P,
StandardGate::XY2M,
];
fn half_rotation_circuit(gate: StandardGate, axis: Parameter) -> Circuit {
let mut circuit = Circuit::new(1);
let params: Vec<ParameterValue> = if gate.num_params() == 0 {
vec![]
} else {
vec![axis.into()]
};
circuit
.append(Instruction::Standard(gate), [Qubit::new(0)], params, None)
.unwrap();
circuit
}
fn assert_qasm_unitary_matches(qasm: &str, circuit: &Circuit, allow_global_phase: bool) {
assert!(qasm.is_ascii(), "Expected ASCII QASM:\n{qasm}");
let parsed = loads(qasm).unwrap_or_else(|error| panic!("{error}\n{qasm}"));
assert_eq!(parsed.num_qubits(), circuit.num_qubits());
let mut expected = circuit.to_matrix(None).unwrap();
let actual = parsed.to_matrix(None).unwrap();
if allow_global_phase {
let (index, pivot) = expected
.indexed_iter()
.find(|(_, z)| z.norm() > 0.1)
.unwrap();
let ratio = actual[index] / *pivot;
assert!((ratio.norm() - 1.0).abs() < 1e-10, "{qasm}");
expected.mapv_inplace(|z| z * ratio);
}
assert_matrix_approx_eq(&actual, &expected, 1e-10);
}
fn without_implicit_extension_gates(qasm: &str) -> String {
regex::Regex::new(r"\b(crx|cry|rxx|ryy|rzz|rzx|swap|cswap|fsim)\b")
.unwrap()
.replace_all(qasm, "exported_$1")
.into_owned()
}
fn extension_test_circuit(gate: StandardGate, controlled: bool, values: [f64; 2]) -> Circuit {
let instruction = if controlled {
Instruction::McGate(Box::new(crate::circuit::gate::MCGate::new(1, gate)))
} else {
Instruction::Standard(gate)
};
let mut circuit = Circuit::new(gate.num_qubits() + usize::from(controlled));
let params: Vec<ParameterValue> = values[..gate.num_params()]
.iter()
.copied()
.map(Into::into)
.collect();
circuit
.append(instruction, circuit.qubits(), params, None)
.unwrap();
circuit
}
fn assert_portable_gate_definition(gate: StandardGate, controlled: bool) {
for values in [
[0.0, 0.0],
[0.731, -1.219],
[-std::f64::consts::PI, std::f64::consts::FRAC_PI_2],
] {
let circuit = extension_test_circuit(gate, controlled, values);
let qasm = dumps(&circuit).unwrap();
assert_qasm_unitary_matches(&without_implicit_extension_gates(&qasm), &circuit, false);
}
}
#[test]
fn portable_qasm2_standard_swap_definition() {
assert_portable_gate_definition(StandardGate::SWAP, false);
}
#[test]
fn portable_qasm2_standard_fsim_definition() {
assert_portable_gate_definition(StandardGate::FSIM, false);
}
#[test]
fn portable_qasm2_controlled_rx_definition() {
assert_portable_gate_definition(StandardGate::RX, true);
}
#[test]
fn portable_qasm2_controlled_ry_definition() {
assert_portable_gate_definition(StandardGate::RY, true);
}
#[test]
fn portable_qasm2_controlled_swap_definition() {
assert_portable_gate_definition(StandardGate::SWAP, true);
}
#[test]
fn portable_qasm2_standard_controlled_rotations_still_define_gates() {
assert_portable_gate_definition(StandardGate::CRX, false);
assert_portable_gate_definition(StandardGate::CRY, false);
}
#[test]
fn portable_qasm2_sparse_qubits_preserve_numeric_basis_and_width() {
let qubits = [9, 2, 5].map(Qubit::new);
let mut circuit = Circuit::from_qubits(qubits.to_vec()).unwrap();
circuit.x(qubits[0]).unwrap();
circuit.cx(qubits[0], qubits[2]).unwrap();
circuit.validate().unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_unitary_matches(&qasm, &circuit, false);
let path = unique_temp_path("sparse_qubits");
dump(&circuit, &path).unwrap();
assert_eq!(fs::read_to_string(&path).unwrap(), qasm);
fs::remove_file(path).unwrap();
}
#[test]
fn portable_qasm2_dense_qubits_in_storage_order_keep_numeric_basis() {
let qubits = [2, 0, 1].map(Qubit::new);
let mut circuit = Circuit::from_qubits(qubits.to_vec()).unwrap();
circuit.x(qubits[0]).unwrap();
circuit.cx(qubits[0], qubits[2]).unwrap();
assert_qasm_unitary_matches(&dumps(&circuit).unwrap(), &circuit, false);
}
#[test]
fn portable_qasm2_sparse_qubits_map_measurement_reset_and_if() {
let qubits = [9, 2, 5].map(Qubit::new);
let mut circuit = Circuit::from_qubits(qubits.to_vec()).unwrap();
circuit.barrier(qubits.to_vec()).unwrap();
circuit.reset(qubits[0]).unwrap();
let measured = circuit.measure(qubits[2]).unwrap();
circuit
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.x(qubits[1])?;
Ok(())
},
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
assert_qasm_contains_ordered(
&qasm,
&[
"qreg q[3];",
"barrier q[2],q[0],q[1];",
"reset q[2];",
"measure q[1] -> v0[0];",
"if (v0 == 1) x q[0];",
],
);
verify_qasm_roundtrip(&qasm, 3);
}
#[test]
fn portable_qasm2_definitions_in_custom_gate_bodies() {
for (gate, controlled) in [
(StandardGate::RX, true),
(StandardGate::RY, true),
(StandardGate::SWAP, true),
(StandardGate::SWAP, false),
(StandardGate::FSIM, false),
] {
let inner = extension_test_circuit(gate, controlled, [0.731, -1.219]);
let width = inner.num_qubits();
let custom = inner.clone().to_gate("wrapped_custom").unwrap();
let unitary = UnitaryGate::new("wrapped_unitary", width as u16, 0)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
for instruction in [custom, Instruction::UnitaryGate(Box::new(unitary))] {
let mut outer = Circuit::new(width);
outer
.append(instruction, outer.qubits(), std::iter::empty(), None)
.unwrap();
let qasm = dumps(&outer).unwrap();
assert_qasm_unitary_matches(&without_implicit_extension_gates(&qasm), &outer, false);
}
}
}
#[test]
fn portable_qasm2_definitions_in_if_bodies() {
for (gate, controlled) in [
(StandardGate::RX, true),
(StandardGate::RY, true),
(StandardGate::SWAP, true),
(StandardGate::SWAP, false),
(StandardGate::FSIM, false),
] {
let inner = extension_test_circuit(gate, controlled, [0.731, -1.219]);
let operation = &inner.operations()[0];
let mut outer = Circuit::new(inner.num_qubits());
let measured = outer.measure(Qubit::new(0)).unwrap();
outer
.if_(
ClassicalExpr::bit_to_bool(measured.expr()).unwrap(),
|body| {
body.append(
operation.instruction.clone(),
operation.qubits.iter().copied(),
[0.731, -1.219][..gate.num_params()]
.iter()
.copied()
.map(ParameterValue::from),
None,
)?;
Ok(())
},
)
.unwrap();
let qasm = without_implicit_extension_gates(&dumps(&outer).unwrap());
verify_qasm_roundtrip(&qasm, outer.num_qubits());
assert_qasm_contains_ordered(&qasm, &["gate exported_", "if (v0 == 1) exported_"]);
}
}
#[test]
fn portable_qasm2_mixed_standard_and_mcgate_define_rotation_once() {
let mut circuit = extension_test_circuit(StandardGate::RX, true, [0.731, 0.0]);
circuit.crx(Qubit::new(0), Qubit::new(1), -0.2).unwrap();
let qasm = dumps(&circuit).unwrap();
assert_eq!(qasm.matches("gate crx(").count(), 1, "{qasm}");
assert_qasm_unitary_matches(&without_implicit_extension_gates(&qasm), &circuit, false);
}
#[test]
fn portable_qasm2_extension_definition_conflicts_are_rejected() {
for (name, gate, controlled) in [
("swap", StandardGate::SWAP, false),
("fsim", StandardGate::FSIM, false),
("cswap", StandardGate::SWAP, true),
] {
let mut inner = Circuit::new(1);
inner.x(Qubit::new(0)).unwrap();
let custom = inner.clone().to_gate(name).unwrap();
let unitary = UnitaryGate::new(name, 1, 0)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
let opaque = UnitaryGate::new(name, 1, 0);
for instruction in [
custom,
Instruction::UnitaryGate(Box::new(unitary)),
Instruction::UnitaryGate(Box::new(opaque)),
] {
let mut custom_only = Circuit::new(1);
custom_only
.append(
instruction.clone(),
[Qubit::new(0)],
std::iter::empty(),
None,
)
.unwrap();
assert!(dumps(&custom_only).is_ok());
let mut circuit = extension_test_circuit(gate, controlled, [0.731, -1.219]);
circuit
.append(instruction, [Qubit::new(0)], std::iter::empty(), None)
.unwrap();
assert!(matches!(
dumps(&circuit),
Err(QasmDumpError::FormatError(message)) if message.contains(name)
));
}
}
}
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];",
],
);
assert_qasm_unitary_matches(&qasm, &circuit, false);
}
#[test]
fn half_rotations_roundtrip_individually_with_numeric_and_pi_axes() {
let axes = [
Parameter::from(0.0),
Parameter::from(0.731),
Parameter::from(-1.219),
Parameter::pi() / 2.0,
-Parameter::pi() / 3.0,
Parameter::pi() * 4.0 + 0.25,
];
for gate in HALF_ROTATION_GATES {
for axis in &axes[..if gate.num_params() == 0 {
1
} else {
axes.len()
}] {
let circuit = half_rotation_circuit(gate, axis.clone());
let qasm = dumps(&circuit).unwrap();
assert_qasm_unitary_matches(&qasm, &circuit, false);
let statements: Vec<_> = qasm
.lines()
.filter(|line| {
line.starts_with("rx(") || line.starts_with("ry(") || line.starts_with("rz(")
})
.collect();
assert_eq!(statements.len(), if gate.num_params() == 0 { 1 } else { 3 });
assert!(
statements.iter().all(|line| line.ends_with(';')),
"{gate:?}: {qasm}"
);
}
}
}
#[test]
fn half_rotations_roundtrip_in_entangled_circuit() {
let mut circuit = Circuit::new(3);
circuit.h(Qubit::new(0)).unwrap();
circuit.cx(Qubit::new(0), Qubit::new(2)).unwrap();
for (index, gate) in HALF_ROTATION_GATES.into_iter().enumerate() {
let qubit = Qubit::new((index % 3) as u32);
circuit
.compose(
&half_rotation_circuit(gate, Parameter::pi() / 7.0),
Some(&[qubit]),
)
.unwrap();
circuit
.cx(qubit, Qubit::new(((index + 1) % 3) as u32))
.unwrap();
}
circuit.h(Qubit::new(1)).unwrap();
circuit.rz(Qubit::new(2), -0.43).unwrap();
assert_qasm_unitary_matches(&dumps(&circuit).unwrap(), &circuit, false);
}
#[test]
fn half_rotations_roundtrip_in_parameterized_custom_and_unitary_gates() {
for gate in HALF_ROTATION_GATES {
let inner = half_rotation_circuit(
gate,
Parameter::symbol("theta") / 2.0 + Parameter::pi() / 3.0,
);
let custom = inner.clone().to_gate("half_rotation").unwrap();
let unitary = UnitaryGate::new("half_unitary", 1, gate.num_params() as u16)
.with_circuit(Arc::new(FrozenCircuit::new(inner)))
.unwrap();
for value in [-1.219, 0.731, std::f64::consts::FRAC_PI_2] {
let params: Vec<ParameterValue> = if gate.num_params() == 0 {
vec![]
} else {
vec![value.into()]
};
for instruction in [
custom.clone(),
Instruction::UnitaryGate(Box::new(unitary.clone())),
] {
let mut circuit = Circuit::new(2);
circuit.h(Qubit::new(0)).unwrap();
circuit.cx(Qubit::new(0), Qubit::new(1)).unwrap();
circuit
.append(instruction, [Qubit::new(1)], params.clone(), None)
.unwrap();
circuit.h(Qubit::new(1)).unwrap();
assert_qasm_unitary_matches(&dumps(&circuit).unwrap(), &circuit, false);
}
}
}
}
#[test]
fn half_rotations_roundtrip_through_file_and_string_aliases() {
use crate::ir::qasm2::load::{from_path, load};
for gate in HALF_ROTATION_GATES {
let circuit = half_rotation_circuit(gate, Parameter::pi() / 3.0);
let qasm = dumps(&circuit).unwrap();
assert_eq!(to_string(&circuit).unwrap(), qasm);
let path = unique_temp_path(&format!("half_rotation_{gate:?}"));
dump(&circuit, &path).unwrap();
let saved = fs::read_to_string(&path).unwrap();
assert_eq!(saved, qasm);
assert_qasm_unitary_matches(&saved, &circuit, false);
to_path(&circuit, &path).unwrap();
for parsed in [load(&path).unwrap(), from_path(&path).unwrap()] {
assert_matrix_approx_eq(
&parsed.to_matrix(None).unwrap(),
&circuit.to_matrix(None).unwrap(),
1e-10,
);
}
fs::remove_file(path).unwrap();
}
}
#[test]
fn xy_half_rotations_resolve_parameters_and_report_invalid_values() {
use crate::circuit::CircuitParam;
use std::collections::HashMap;
let qubit = Qubit::new(0);
let qubit_map = HashMap::from([(qubit, "q[0]".to_string())]);
let replacements = HashMap::from([("theta".to_string(), Parameter::pi() / 3.0)]);
for gate in [StandardGate::XY2P, StandardGate::XY2M] {
let circuit = half_rotation_circuit(gate, Parameter::symbol("theta"));
let operation = &circuit.operations()[0];
let mut qasm = "OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[1];\n".to_string();
super::dump_standard_gate(
&gate,
operation,
&circuit,
&mut qasm,
&qubit_map,
&replacements,
)
.unwrap();
assert_qasm_unitary_matches(
&qasm,
&half_rotation_circuit(gate, Parameter::pi() / 3.0),
false,
);
for invalid in [
CircuitParam::Index(u32::MAX),
CircuitParam::Fixed(f64::NAN),
CircuitParam::Fixed(f64::INFINITY),
CircuitParam::Fixed(f64::NEG_INFINITY),
] {
let mut operation = operation.clone();
operation.params[0] = invalid;
let error = super::dump_standard_gate(
&gate,
&operation,
&circuit,
&mut String::new(),
&qubit_map,
&replacements,
)
.unwrap_err();
assert!(matches!(error, QasmDumpError::FormatError(message)
if message.contains("parameter 0") && message.contains(&format!("{gate:?}"))));
}
}
}
#[test]
fn half_rotations_in_conditional_bodies_remain_explicitly_unsupported() {
for gate in HALF_ROTATION_GATES {
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.compose(
&half_rotation_circuit(gate, Parameter::pi() / 3.0),
Some(&[Qubit::new(1)]),
)?;
Ok(())
})
.unwrap();
assert!(
matches!(dumps(&circuit), Err(QasmDumpError::UnsupportedClassicalControl(message))
if message.contains(&format!("{gate:?}")))
);
}
}
#[test]
fn every_standard_gate_qasm2_roundtrips_with_documented_phase_policy() {
let parameter_sets = [
[0.0, 0.0, 0.0],
[0.731, -1.219, 2.031],
[-std::f64::consts::PI, std::f64::consts::FRAC_PI_2, -0.25],
];
for &gate in StandardGate::all() {
for values in parameter_sets {
let mut circuit = Circuit::new(3);
let params: Vec<ParameterValue> = values[..gate.num_params()]
.iter()
.copied()
.map(Into::into)
.collect();
circuit
.append(
Instruction::Standard(gate),
(0..gate.num_qubits()).map(|index| Qubit::new(index as u32)),
params,
None,
)
.unwrap();
let qasm = dumps(&circuit).unwrap();
if gate == StandardGate::GPhase {
assert!(qasm.contains("// gphase("), "{qasm}");
let parsed = loads(&qasm).unwrap();
assert!(parsed.operations().is_empty());
assert_eq!(parsed.global_phase().evaluate(&None).unwrap(), 0.0);
} else {
assert_qasm_unitary_matches(&qasm, &circuit, !HALF_ROTATION_GATES.contains(&gate));
}
}
}
}
#[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)"
);
}