use crate::circuit::gate::{
CircuitGate, ClassicalDataOp, Directive, Instruction, StandardGate, UnitaryGate,
};
use crate::circuit::operation::Operation;
use crate::circuit::parameter::Parameter;
use crate::circuit::{
Circuit, ClassicalCast, ClassicalCompareOp, ClassicalControlOp, ClassicalExpr,
ClassicalExprKind, ClassicalType, ClassicalUnaryOp, ClassicalValue, ClassicalVar, Qubit,
};
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::fs::File;
use std::io::{self, Write as IoWrite};
use std::path::Path;
use std::sync::Arc;
const QELIB1_CUSTOM_GATES: &[&str] = &["ch", "cu1", "cu3"];
#[derive(Debug)]
pub enum QasmDumpError {
IoError(io::Error),
FormatError(String),
MeasureInGateNotAllowed,
ResetInGateNotAllowed,
InvalidQubitIndex(String),
UnsupportedClassicalType(String),
UnsupportedClassicalData(String),
UnsupportedClassicalControl(String),
ConflictingGateDefinition {
name: String,
existing_qubits: usize,
existing_params: usize,
conflicting_qubits: usize,
conflicting_params: usize,
},
}
impl std::fmt::Display for QasmDumpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QasmDumpError::IoError(s) => write!(f, "IO error: {}", s),
QasmDumpError::FormatError(s) => write!(f, "Format error: {}", s),
QasmDumpError::MeasureInGateNotAllowed => {
write!(f, "Measure inside gate definition is not allowed")
}
QasmDumpError::ResetInGateNotAllowed => {
write!(f, "Reset inside gate definition is not allowed")
}
QasmDumpError::InvalidQubitIndex(s) => write!(f, "Invalid qubit index: {}", s),
QasmDumpError::UnsupportedClassicalType(s) => {
write!(f, "Unsupported OpenQASM 2.0 classical type: {s}")
}
QasmDumpError::UnsupportedClassicalData(s) => {
write!(f, "Unsupported OpenQASM 2.0 classical data operation: {s}")
}
QasmDumpError::UnsupportedClassicalControl(s) => {
write!(
f,
"Unsupported OpenQASM 2.0 classical control operation: {s}"
)
}
QasmDumpError::ConflictingGateDefinition {
name,
existing_qubits,
existing_params,
conflicting_qubits,
conflicting_params,
} => write!(
f,
"Conflicting definitions for gate '{name}': existing definition has {existing_qubits} qubits and {existing_params} parameters, conflicting definition has {conflicting_qubits} qubits and {conflicting_params} parameters"
),
}
}
}
#[derive(Debug, Default)]
struct ClassicalRegisterMap {
vars: HashMap<ClassicalVar, String>,
values: HashMap<ClassicalValue, String>,
}
impl ClassicalRegisterMap {
fn new(
circuit: &Circuit,
output: &mut String,
skipped_values: &HashSet<ClassicalValue>,
) -> Result<Self, QasmDumpError> {
let mut registers = Self::default();
for (id, ty) in circuit.classical_vars().iter().copied().enumerate() {
let width = qasm_register_width(ty)?;
let name = format!("c{id}");
writeln!(output, "creg {name}[{width}];")?;
registers
.vars
.insert(ClassicalVar::new(circuit.id(), id as u32, ty), name);
}
for (id, ty) in circuit.classical_values().iter().copied().enumerate() {
let value = ClassicalValue::new(circuit.id(), id as u32, ty);
if skipped_values.contains(&value) {
continue;
}
let width = qasm_register_width(ty)?;
let name = format!("v{id}");
writeln!(output, "creg {name}[{width}];")?;
registers.values.insert(value, name);
}
Ok(registers)
}
fn var(&self, var: ClassicalVar) -> Result<&str, QasmDumpError> {
self.vars.get(&var).map(String::as_str).ok_or_else(|| {
QasmDumpError::UnsupportedClassicalData(format!(
"classical variable {} is not owned by the circuit",
var.index()
))
})
}
fn value(&self, value: ClassicalValue) -> Result<&str, QasmDumpError> {
self.values.get(&value).map(String::as_str).ok_or_else(|| {
QasmDumpError::UnsupportedClassicalData(format!(
"classical value {} is not owned by the circuit",
value.index()
))
})
}
}
fn qasm_register_width(ty: ClassicalType) -> Result<u32, QasmDumpError> {
match ty {
ClassicalType::Bit => Ok(1),
ClassicalType::BitVec(width) => Ok(width.get()),
ClassicalType::Bool | ClassicalType::UInt(_) => {
Err(QasmDumpError::UnsupportedClassicalType(format!("{ty:?}")))
}
}
}
impl std::error::Error for QasmDumpError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
QasmDumpError::IoError(error) => Some(error),
_ => None,
}
}
}
impl From<std::fmt::Error> for QasmDumpError {
fn from(e: std::fmt::Error) -> Self {
QasmDumpError::FormatError(e.to_string())
}
}
impl From<io::Error> for QasmDumpError {
fn from(e: io::Error) -> Self {
QasmDumpError::IoError(e)
}
}
pub fn dump<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QasmDumpError> {
let qasm = dumps(circuit)?;
let mut file = File::create(path)?;
file.write_all(qasm.as_bytes())?;
Ok(())
}
pub fn to_path<P: AsRef<Path>>(circuit: &Circuit, path: P) -> Result<(), QasmDumpError> {
dump(circuit, path)
}
pub fn dumps(circuit: &Circuit) -> Result<String, QasmDumpError> {
let mut output = String::new();
writeln!(
&mut output,
"// This file is auto-generated by Cqlib v{}.",
env!("CARGO_PKG_VERSION")
)?;
writeln!(&mut output, "OPENQASM 2.0;")?;
writeln!(&mut output, "include \"qelib1.inc\";\n")?;
writeln!(&mut output, "qreg q[{}];", circuit.num_qubits())?;
let skipped_values = skipped_classical_value_declarations(circuit.operations())?;
let classical_registers = ClassicalRegisterMap::new(circuit, &mut output, &skipped_values)?;
writeln!(&mut output)?;
let global_phase = circuit.global_phase();
match global_phase.evaluate(&None) {
Ok(val) if val.abs() < 1e-10 => {} Ok(val) => writeln!(&mut output, "// Global Phase: {}\n", val)?,
Err(_) => writeln!(&mut output, "// Global Phase: {}\n", global_phase)?,
}
writeln!(&mut output)?;
let mut used_standard_gates = HashSet::new();
collect_used_standard_gates(circuit, &mut used_standard_gates);
let extended_gates = [
StandardGate::CRX,
StandardGate::CRY,
StandardGate::RZZ,
StandardGate::RXX,
StandardGate::RYY,
StandardGate::RZX,
];
for gate in extended_gates {
if used_standard_gates.contains(&gate)
&& let Some(def) = get_extended_gate_definition(gate)
{
writeln!(&mut output, "{}", def)?;
}
}
writeln!(&mut output)?;
let mut defined_gates: IndexMap<String, CircuitGate> = IndexMap::new();
let mut unitary_gate_defs: IndexMap<String, UnitaryGate> = IndexMap::new();
let mut opaque_unitary_gates: IndexMap<String, UnitaryGate> = IndexMap::new();
let mut has_delay = false;
collect_gates(
circuit,
&mut defined_gates,
&mut unitary_gate_defs,
&mut opaque_unitary_gates,
&mut has_delay,
)?;
for gate in defined_gates.values() {
dump_gate_definition(gate, &mut output)?;
writeln!(&mut output)?;
}
for gate in unitary_gate_defs.values() {
dump_unitary_gate_definition(gate, &mut output)?;
writeln!(&mut output)?;
}
for gate in opaque_unitary_gates.values() {
dump_opaque_declaration(gate, &mut output)?;
writeln!(&mut output)?;
}
if has_delay {
writeln!(&mut output, "opaque delay(t) q;")?;
writeln!(&mut output)?;
}
let mut qubit_map: HashMap<Qubit, String> = HashMap::new();
for q in circuit.qubits() {
qubit_map.insert(q, format!("q[{}]", q.index()));
}
let param_map: HashMap<String, Parameter> = HashMap::new();
process_circuit_operations(
circuit,
&mut output,
&qubit_map,
¶m_map,
&classical_registers,
)?;
Ok(output)
}
pub fn to_string(circuit: &Circuit) -> Result<String, QasmDumpError> {
dumps(circuit)
}
fn collect_gates(
circuit: &Circuit,
defined_gates: &mut IndexMap<String, CircuitGate>,
unitary_gate_defs: &mut IndexMap<String, UnitaryGate>,
opaque_unitary_gates: &mut IndexMap<String, UnitaryGate>,
has_delay: &mut bool,
) -> Result<(), QasmDumpError> {
collect_gates_from_operations(
circuit.operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)
}
fn collect_gates_from_operations(
operations: &[Operation],
defined_gates: &mut IndexMap<String, CircuitGate>,
unitary_gate_defs: &mut IndexMap<String, UnitaryGate>,
opaque_unitary_gates: &mut IndexMap<String, UnitaryGate>,
has_delay: &mut bool,
) -> Result<(), QasmDumpError> {
for op in operations {
match &op.instruction {
Instruction::CircuitGate(cg) => {
if QELIB1_CUSTOM_GATES.contains(&cg.name.as_str()) {
continue;
}
collect_gates(
&cg.circuit.circuit,
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
if let Some(existing) = defined_gates.get(cg.name.as_str()) {
if !Arc::ptr_eq(&existing.circuit, &cg.circuit) {
return Err(QasmDumpError::ConflictingGateDefinition {
name: cg.name.to_string(),
existing_qubits: existing.num_qubits(),
existing_params: existing.num_params(),
conflicting_qubits: cg.num_qubits(),
conflicting_params: cg.num_params(),
});
}
} else {
defined_gates.insert(cg.name.to_string(), *cg.clone());
}
}
Instruction::UnitaryGate(ug) => {
if let Some(fc) = ug.circuit() {
collect_gates(
&fc.circuit,
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
let label = ug.label().to_string();
if !unitary_gate_defs.contains_key(&label) {
unitary_gate_defs.insert(label, ug.as_ref().clone());
}
} else {
let label = ug.label().to_string();
if !opaque_unitary_gates.contains_key(&label) {
opaque_unitary_gates.insert(label, ug.as_ref().clone());
}
}
}
Instruction::Delay => {
*has_delay = true;
}
Instruction::ClassicalControl(control) => match control {
ClassicalControlOp::If(if_op) => {
collect_gates_from_operations(
if_op.then_body().operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
if let Some(body) = if_op.else_body() {
collect_gates_from_operations(
body.operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
}
}
ClassicalControlOp::While(while_op) => {
collect_gates_from_operations(
while_op.body().operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
}
ClassicalControlOp::For(for_op) => {
collect_gates_from_operations(
for_op.body().operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
}
ClassicalControlOp::Switch(switch_op) => {
for case in switch_op.cases() {
collect_gates_from_operations(
case.body().operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
}
if let Some(body) = switch_op.default() {
collect_gates_from_operations(
body.operations(),
defined_gates,
unitary_gate_defs,
opaque_unitary_gates,
has_delay,
)?;
}
}
ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
},
_ => {}
}
}
Ok(())
}
fn dump_gate_definition(cg: &CircuitGate, output: &mut String) -> Result<(), QasmDumpError> {
validate_gate_body_operations(cg.circuit.circuit.operations())?;
let params: Vec<String> = cg.signature_params().iter().cloned().collect();
let params_str = if params.is_empty() {
String::new()
} else {
format!("({})", params.join(","))
};
let qubits: Vec<String> = (0..cg.num_qubits()).map(|i| format!("q{}", i)).collect();
let qubits_str = qubits.join(",");
writeln!(output, "gate {}{} {} {{", cg.name, params_str, qubits_str)?;
let mut body_qubit_map = HashMap::new();
let inner_circuit = &cg.circuit.circuit;
for (i, q) in inner_circuit.qubits().iter().enumerate() {
body_qubit_map.insert(*q, format!("q{}", i));
}
let body_param_map = HashMap::new();
let classical_registers = ClassicalRegisterMap::default();
process_circuit_operations(
inner_circuit,
output,
&body_qubit_map,
&body_param_map,
&classical_registers,
)?;
writeln!(output, "}}")?;
Ok(())
}
fn dump_unitary_gate_definition(
gate: &UnitaryGate,
output: &mut String,
) -> Result<(), QasmDumpError> {
let frozen_circuit = gate.circuit().as_ref().ok_or_else(|| {
QasmDumpError::FormatError(format!(
"Unitary gate '{}' has no circuit definition",
gate.label()
))
})?;
validate_gate_body_operations(frozen_circuit.circuit.operations())?;
let num_qubits = frozen_circuit.circuit.qubits().len();
let qubits: Vec<String> = (0..num_qubits).map(|i| format!("q{}", i)).collect();
let qubits_str = qubits.join(",");
let params_str = format_parameter_signature(&unitary_gate_parameter_names(gate));
writeln!(
output,
"gate {}{} {} {{",
gate.label(),
params_str,
qubits_str
)?;
let mut body_qubit_map = HashMap::new();
let inner_circuit = &frozen_circuit.circuit;
for (i, q) in inner_circuit.qubits().iter().enumerate() {
body_qubit_map.insert(*q, format!("q{}", i));
}
let body_param_map = HashMap::new();
let classical_registers = ClassicalRegisterMap::default();
process_circuit_operations(
inner_circuit,
output,
&body_qubit_map,
&body_param_map,
&classical_registers,
)?;
writeln!(output, "}}")?;
Ok(())
}
fn validate_gate_body_operations(operations: &[Operation]) -> Result<(), QasmDumpError> {
if operations
.iter()
.any(|operation| operation.instruction.has_measurement())
{
return Err(QasmDumpError::MeasureInGateNotAllowed);
}
if operations.iter().any(|operation| {
matches!(
&operation.instruction,
Instruction::Directive(Directive::Reset)
)
}) {
return Err(QasmDumpError::ResetInGateNotAllowed);
}
Ok(())
}
fn unitary_gate_parameter_names(gate: &UnitaryGate) -> Vec<String> {
if let Some(circuit) = gate.circuit() {
return circuit.circuit().symbols().iter().cloned().collect();
}
if let Some(params) = gate.matrix_params() {
return params.to_vec();
}
(0..gate.num_params())
.map(|index| format!("p{index}"))
.collect()
}
fn format_parameter_signature(params: &[String]) -> String {
if params.is_empty() {
String::new()
} else {
format!("({})", params.join(","))
}
}
fn dump_opaque_declaration(gate: &UnitaryGate, output: &mut String) -> Result<(), QasmDumpError> {
let qubits: Vec<String> = (0..gate.num_qubits()).map(|i| format!("q{}", i)).collect();
let qubits_str = qubits.join(",");
let params_str = format_parameter_signature(&unitary_gate_parameter_names(gate));
writeln!(
output,
"opaque {}{} {};",
gate.label(),
params_str,
qubits_str
)?;
Ok(())
}
fn process_circuit_operations(
circuit: &Circuit,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
classical_registers: &ClassicalRegisterMap,
) -> Result<(), QasmDumpError> {
let operations = circuit.operations();
let mut index = 0;
let mut accepting_initializers = true;
while index < operations.len() {
let op = &operations[index];
if let Instruction::ClassicalData(ClassicalDataOp::Store { target, value }) =
&op.instruction
&& accepting_initializers
&& is_zero_initializer(*target, value)
{
index += 1;
continue;
}
accepting_initializers = false;
match &op.instruction {
Instruction::Standard(gate) => {
dump_standard_gate(gate, op, circuit, output, qubit_map, param_map)?;
}
Instruction::McGate(mc_gate) => {
dump_mc_gate(mc_gate, op, circuit, output, qubit_map, param_map)?;
}
Instruction::Directive(directive) => {
dump_directive(directive, op, output, qubit_map)?;
}
Instruction::CircuitGate(cg) => {
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
writeln!(output, "{}{} {};", cg.name, params_str, mapped_qs.join(","))?;
}
Instruction::UnitaryGate(unitary_gate) => {
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
writeln!(
output,
"{}{} {};",
unitary_gate.label(),
params_str,
mapped_qs.join(",")
)?;
}
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
| Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
let next = operations.get(index + 1);
let (destination, consumes_store) =
measurement_destination(&op.instruction, *result, next)?;
if consumes_store
&& operations[index + 2..]
.iter()
.any(|operation| operation.instruction.reads_value(*result))
{
return Err(QasmDumpError::UnsupportedClassicalData(format!(
"measurement value {} is read after being stored into a mutable register",
result.index()
)));
}
dump_classical_measurement(
op,
*result,
destination,
output,
qubit_map,
classical_registers,
)?;
if consumes_store {
index += 1;
}
}
Instruction::ClassicalData(ClassicalDataOp::Store { .. }) => {
return Err(QasmDumpError::UnsupportedClassicalData(
"general store assignment".to_string(),
));
}
Instruction::ClassicalControl(control) => {
dump_control_flow(
control,
circuit,
output,
qubit_map,
param_map,
classical_registers,
)?;
}
Instruction::Delay => {
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
writeln!(output, "delay({}) {};", params_str, mapped_qs[0])?;
}
}
index += 1;
}
Ok(())
}
fn is_zero_initializer(target: ClassicalVar, value: &ClassicalExpr) -> bool {
match (target.ty(), value.kind()) {
(ClassicalType::Bit, ClassicalExprKind::BitLiteral(false)) => true,
(
ClassicalType::BitVec(target_width),
ClassicalExprKind::BitVecLiteral { width, value: 0 },
) => target_width == *width,
(ClassicalType::BitVec(target_width), ClassicalExprKind::PackBits { bits }) => {
bits.len() == target_width.get() as usize
&& bits
.iter()
.all(|bit| matches!(bit.kind(), ClassicalExprKind::BitLiteral(false)))
}
_ => false,
}
}
#[derive(Debug, Clone, Copy)]
enum MeasurementDestination {
Value(ClassicalValue),
VarWhole(ClassicalVar),
VarBit(ClassicalVar, u32),
}
fn skipped_classical_value_declarations(
operations: &[Operation],
) -> Result<HashSet<ClassicalValue>, QasmDumpError> {
let mut skipped = HashSet::new();
collect_skipped_classical_values(operations, &mut skipped)?;
Ok(skipped)
}
fn collect_skipped_classical_values(
operations: &[Operation],
skipped: &mut HashSet<ClassicalValue>,
) -> Result<(), QasmDumpError> {
let mut index = 0;
while index < operations.len() {
let op = &operations[index];
match &op.instruction {
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
| Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
let next = operations.get(index + 1);
let (_, consumes_store) = measurement_destination(&op.instruction, *result, next)?;
if consumes_store {
let read_later = operations[index + 2..]
.iter()
.any(|operation| operation.instruction.reads_value(*result));
if !read_later {
skipped.insert(*result);
}
index += 1;
}
}
Instruction::ClassicalControl(control) => {
collect_skipped_classical_values_in_control(control, skipped)?;
}
_ => {}
}
index += 1;
}
Ok(())
}
fn collect_skipped_classical_values_in_control(
control: &ClassicalControlOp,
skipped: &mut HashSet<ClassicalValue>,
) -> Result<(), QasmDumpError> {
match control {
ClassicalControlOp::If(if_op) => {
collect_skipped_classical_values(if_op.then_body().operations(), skipped)?;
if let Some(body) = if_op.else_body() {
collect_skipped_classical_values(body.operations(), skipped)?;
}
}
ClassicalControlOp::While(while_op) => {
collect_skipped_classical_values(while_op.body().operations(), skipped)?;
}
ClassicalControlOp::For(for_op) => {
collect_skipped_classical_values(for_op.body().operations(), skipped)?;
}
ClassicalControlOp::Switch(switch_op) => {
for case in switch_op.cases() {
collect_skipped_classical_values(case.body().operations(), skipped)?;
}
if let Some(body) = switch_op.default() {
collect_skipped_classical_values(body.operations(), skipped)?;
}
}
ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
}
Ok(())
}
fn measurement_destination(
instruction: &Instruction,
result: ClassicalValue,
next: Option<&Operation>,
) -> Result<(MeasurementDestination, bool), QasmDumpError> {
let Some(Operation {
instruction: Instruction::ClassicalData(ClassicalDataOp::Store { target, value }),
..
}) = next
else {
return Ok((MeasurementDestination::Value(result), false));
};
if matches!(value.kind(), ClassicalExprKind::Value(value) if *value == result) {
return match instruction {
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
if target.ty() == ClassicalType::Bit =>
{
Ok((MeasurementDestination::VarWhole(*target), true))
}
Instruction::ClassicalData(ClassicalDataOp::MeasureBits { .. })
if target.ty() == result.ty() =>
{
Ok((MeasurementDestination::VarWhole(*target), true))
}
_ => Err(QasmDumpError::UnsupportedClassicalData(
"measurement store has incompatible target type".to_string(),
)),
};
}
if matches!(
instruction,
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { .. })
) && let Some(bit) = value.measurement_store_bit(*target, result)
{
return Ok((MeasurementDestination::VarBit(*target, bit), true));
}
Err(QasmDumpError::UnsupportedClassicalData(
"measurement is followed by a store that cannot be represented by OpenQASM 2.0".to_string(),
))
}
fn dump_classical_measurement(
op: &Operation,
result: ClassicalValue,
destination: MeasurementDestination,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
registers: &ClassicalRegisterMap,
) -> Result<(), QasmDumpError> {
for line in measurement_qasm_lines(op, result, destination, qubit_map, registers)? {
writeln!(output, "{line};")?;
}
Ok(())
}
fn measurement_qasm_lines(
op: &Operation,
result: ClassicalValue,
destination: MeasurementDestination,
qubit_map: &HashMap<Qubit, String>,
registers: &ClassicalRegisterMap,
) -> Result<Vec<String>, QasmDumpError> {
let mapped_qubits = map_qubits(op, qubit_map);
let (register, first_bit, width) = match destination {
MeasurementDestination::Value(value) => (registers.value(value)?, 0, value.ty().width()),
MeasurementDestination::VarWhole(var) => (registers.var(var)?, 0, var.ty().width()),
MeasurementDestination::VarBit(var, bit) => (registers.var(var)?, bit, 1),
};
if mapped_qubits.len() != width as usize || result.ty().width() != width {
return Err(QasmDumpError::UnsupportedClassicalData(format!(
"measurement width {} does not match destination width {width}",
mapped_qubits.len()
)));
}
Ok(mapped_qubits
.iter()
.enumerate()
.map(|(offset, qubit)| {
format!(
"measure {qubit} -> {register}[{}]",
first_bit + offset as u32
)
})
.collect())
}
fn resolve_param(
op: &Operation,
param_index: usize,
circuit: &Circuit,
param_map: &HashMap<String, Parameter>,
) -> Result<Parameter, QasmDumpError> {
let mut param = circuit
.resolve_parameter(&op.params[param_index])
.map_err(|error| {
let error = match error {
crate::circuit::CircuitError::InvalidParameterValue(_, value) => {
crate::circuit::CircuitError::InvalidParameterValue(param_index, value)
}
error => error,
};
QasmDumpError::FormatError(format!(
"invalid parameter {param_index} for {:?}: {error}",
op.instruction
))
})?;
for (symbol, replacement) in param_map {
param = param.replace(symbol, replacement.clone());
}
Ok(param)
}
fn format_params(
op: &Operation,
circuit: &Circuit,
param_map: &HashMap<String, Parameter>,
) -> Result<String, QasmDumpError> {
if op.params.is_empty() {
return Ok(String::new());
}
let params = (0..op.params.len())
.map(|index| {
resolve_param(op, index, circuit, param_map)
.map(|param| param.to_string().replace("Ï€", "pi"))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(format!("({})", params.join(",")))
}
fn map_qubits(op: &Operation, qubit_map: &HashMap<Qubit, String>) -> Vec<String> {
op.qubits
.iter()
.map(|q| {
qubit_map
.get(q)
.cloned()
.unwrap_or_else(|| format!("unknown[{}]", q.index()))
})
.collect()
}
fn qasm2_builtin_gate_name(gate: StandardGate) -> Option<&'static str> {
match gate {
StandardGate::I => Some("id"),
StandardGate::X => Some("x"),
StandardGate::Y => Some("y"),
StandardGate::Z => Some("z"),
StandardGate::H => Some("h"),
StandardGate::S => Some("s"),
StandardGate::SDG => Some("sdg"),
StandardGate::T => Some("t"),
StandardGate::TDG => Some("tdg"),
StandardGate::RX => Some("rx"),
StandardGate::RY => Some("ry"),
StandardGate::RZ => Some("rz"),
StandardGate::Phase => Some("u1"),
StandardGate::CX => Some("cx"),
StandardGate::CY => Some("cy"),
StandardGate::CZ => Some("cz"),
StandardGate::SWAP => Some("swap"),
StandardGate::CCX => Some("ccx"),
StandardGate::U => Some("u3"),
StandardGate::CRZ => Some("crz"),
StandardGate::FSIM => Some("fsim"),
StandardGate::CRX => Some("crx"),
StandardGate::CRY => Some("cry"),
StandardGate::RXX => Some("rxx"),
StandardGate::RYY => Some("ryy"),
StandardGate::RZZ => Some("rzz"),
StandardGate::RZX => Some("rzx"),
_ => None,
}
}
fn dump_standard_gate(
gate: &StandardGate,
op: &Operation,
circuit: &Circuit,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
) -> Result<(), QasmDumpError> {
let mapped_qs = map_qubits(op, qubit_map);
match gate {
StandardGate::X2P => {
writeln!(output, "// x2p {}", mapped_qs[0])?;
writeln!(output, "rx(pi/2) {}", mapped_qs[0])?;
return Ok(());
}
StandardGate::X2M => {
writeln!(output, "// x2m {}", mapped_qs[0])?;
writeln!(output, "rx(-pi/2) {}", mapped_qs[0])?;
return Ok(());
}
StandardGate::Y2P => {
writeln!(output, "// y2p {}", mapped_qs[0])?;
writeln!(output, "ry(pi/2) {}", mapped_qs[0])?;
return Ok(());
}
StandardGate::Y2M => {
writeln!(output, "// y2m {}", mapped_qs[0])?;
writeln!(output, "ry(-pi/2) {}", mapped_qs[0])?;
return Ok(());
}
StandardGate::XY2P => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let p1 = Parameter::pi() / 2.0 - theta.clone();
let p3 = theta.clone() - Parameter::pi() / 2.0;
let q_str = &mapped_qs[0];
writeln!(output, "// xy2p({}) {}", theta, q_str)?;
writeln!(output, "rz({}) {}", p1, q_str)?;
writeln!(output, "ry(pi/2) {}", q_str)?;
writeln!(output, "rz({}) {}", p3, q_str)?;
return Ok(());
}
StandardGate::XY2M => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let m1 = Parameter::from(0.0) - (Parameter::pi() / 2.0 + theta.clone());
let m3 = theta.clone() + Parameter::pi() / 2.0;
let q_str = &mapped_qs[0];
writeln!(output, "// xy2m({}) {}", theta, q_str)?;
writeln!(output, "rz({}) {}", m1, q_str)?;
writeln!(output, "ry(pi/2) {}", q_str)?;
writeln!(output, "rz({}) {}", m3, q_str)?;
return Ok(());
}
StandardGate::XY => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let phase: Parameter = theta.clone() - Parameter::pi() / 2.0;
let lambda: Parameter = Parameter::pi() / 2.0 - theta.clone();
let q_str = &mapped_qs[0];
writeln!(
output,
"// xy({}) {}",
theta.to_string().replace("Ï€", "pi"),
q_str
)?;
writeln!(
output,
"u3({},{},{}) {};",
Parameter::pi().to_string().replace("Ï€", "pi"),
phase.to_string().replace("Ï€", "pi"),
lambda.to_string().replace("Ï€", "pi"),
q_str
)?;
return Ok(());
}
StandardGate::RXY => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let phi = resolve_param(op, 1, circuit, param_map)?;
let phase: Parameter = phi.clone() - Parameter::pi() / 2.0;
let lambda: Parameter = Parameter::pi() / 2.0 - phi.clone();
let q_str = &mapped_qs[0];
writeln!(
output,
"// rxy({},{}) {}",
theta.to_string().replace("Ï€", "pi"),
phi.to_string().replace("Ï€", "pi"),
q_str
)?;
writeln!(
output,
"u3({},{},{}) {};",
theta.to_string().replace("Ï€", "pi"),
phase.to_string().replace("Ï€", "pi"),
lambda.to_string().replace("Ï€", "pi"),
q_str
)?;
return Ok(());
}
StandardGate::GPhase => {
let param = resolve_param(op, 0, circuit, param_map)?;
writeln!(output, "// gphase({})", param)?;
return Ok(());
}
_ => {}
}
let name = qasm2_builtin_gate_name(*gate).ok_or_else(|| {
QasmDumpError::FormatError(format!(
"Gate {gate:?} is not supported by the OpenQASM 2.0 dumper"
))
})?;
let params_str = format_params(op, circuit, param_map)?;
writeln!(output, "{}{} {};", name, params_str, mapped_qs.join(","))?;
Ok(())
}
fn dump_directive(
directive: &Directive,
op: &Operation,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
) -> Result<(), QasmDumpError> {
let mapped_qs = map_qubits(op, qubit_map);
match directive {
Directive::Measure => {
return Err(QasmDumpError::UnsupportedClassicalData(
"legacy measurement has no classical destination".to_string(),
));
}
Directive::Barrier => {
writeln!(output, "barrier {};", mapped_qs.join(","))?;
}
Directive::Reset => {
for q in mapped_qs {
writeln!(output, "reset {};", q)?;
}
}
}
Ok(())
}
fn dump_mc_gate(
mc_gate: &crate::circuit::gate::MCGate,
op: &Operation,
circuit: &Circuit,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
) -> Result<(), QasmDumpError> {
let num_ctrl = mc_gate.num_ctrl_qubits();
let base = mc_gate.base_gate();
let name = match (num_ctrl, base) {
(1, StandardGate::X) => "cx",
(1, StandardGate::Z) => "cz",
(1, StandardGate::Y) => "cy",
(1, StandardGate::H) => "ch",
(1, StandardGate::RX) => "crx",
(1, StandardGate::RY) => "cry",
(1, StandardGate::RZ) => "crz",
(1, StandardGate::Phase) => "cu1",
(1, StandardGate::CX) => "ccx",
(1, StandardGate::SWAP) => "cswap",
(2, StandardGate::X) => "ccx",
_ => {
return Err(QasmDumpError::FormatError(format!(
"Unsupported MCGate: {} controls on {}",
num_ctrl, base
)));
}
};
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
writeln!(output, "{}{} {};", name, params_str, mapped_qs.join(","))?;
Ok(())
}
fn collect_used_standard_gates(circuit: &Circuit, used_gates: &mut HashSet<StandardGate>) {
collect_used_standard_gates_from_operations(circuit.operations(), used_gates);
}
fn collect_used_standard_gates_from_operations(
operations: &[Operation],
used_gates: &mut HashSet<StandardGate>,
) {
for op in operations {
match &op.instruction {
Instruction::Standard(sg) => {
used_gates.insert(*sg);
}
Instruction::CircuitGate(cg) => {
collect_used_standard_gates(&cg.circuit.circuit, used_gates);
}
Instruction::UnitaryGate(ug) => {
if let Some(fc) = ug.circuit() {
collect_used_standard_gates(&fc.circuit, used_gates);
}
}
Instruction::ClassicalControl(control) => match control {
ClassicalControlOp::If(if_op) => {
collect_used_standard_gates_from_operations(
if_op.then_body().operations(),
used_gates,
);
if let Some(body) = if_op.else_body() {
collect_used_standard_gates_from_operations(body.operations(), used_gates);
}
}
ClassicalControlOp::While(while_op) => {
collect_used_standard_gates_from_operations(
while_op.body().operations(),
used_gates,
);
}
ClassicalControlOp::For(for_op) => {
collect_used_standard_gates_from_operations(
for_op.body().operations(),
used_gates,
);
}
ClassicalControlOp::Switch(switch_op) => {
for case in switch_op.cases() {
collect_used_standard_gates_from_operations(
case.body().operations(),
used_gates,
);
}
if let Some(body) = switch_op.default() {
collect_used_standard_gates_from_operations(body.operations(), used_gates);
}
}
ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
},
_ => {}
}
}
}
fn dump_control_flow(
control: &ClassicalControlOp,
circuit: &Circuit,
output: &mut String,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
registers: &ClassicalRegisterMap,
) -> Result<(), QasmDumpError> {
match control {
ClassicalControlOp::If(if_op) => {
if if_op.else_body().is_some() {
return Err(QasmDumpError::UnsupportedClassicalControl(
"if/else has no lossless OpenQASM 2.0 representation".to_string(),
));
}
let (register, value) = qasm_condition(if_op.condition(), registers)?;
let body_ops = if_op.then_body().operations();
let mut index = 0;
while index < body_ops.len() {
let (body_qasms, consumed) = conditional_operation_qasms(
body_ops, index, circuit, qubit_map, param_map, registers,
)?;
for body_qasm in body_qasms {
writeln!(output, "if ({register} == {value}) {body_qasm};")?;
}
index += consumed;
}
}
ClassicalControlOp::While(_) => return unsupported_control("while"),
ClassicalControlOp::For(_) => return unsupported_control("for"),
ClassicalControlOp::Switch(_) => return unsupported_control("switch"),
ClassicalControlOp::Break => return unsupported_control("break"),
ClassicalControlOp::Continue => return unsupported_control("continue"),
}
Ok(())
}
fn conditional_operation_qasms(
operations: &[Operation],
index: usize,
circuit: &Circuit,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
registers: &ClassicalRegisterMap,
) -> Result<(Vec<String>, usize), QasmDumpError> {
let op = &operations[index];
match &op.instruction {
Instruction::ClassicalData(ClassicalDataOp::MeasureBit { result })
| Instruction::ClassicalData(ClassicalDataOp::MeasureBits { result }) => {
let next = operations.get(index + 1);
let (destination, consumes_store) =
measurement_destination(&op.instruction, *result, next)?;
if consumes_store
&& operations[index + 2..]
.iter()
.any(|operation| operation.instruction.reads_value(*result))
{
return Err(QasmDumpError::UnsupportedClassicalData(format!(
"measurement value {} is read after being stored into a mutable register",
result.index()
)));
}
let lines = measurement_qasm_lines(op, *result, destination, qubit_map, registers)?;
Ok((lines, if consumes_store { 2 } else { 1 }))
}
_ => Ok((
vec![operation_to_qasm(op, circuit, qubit_map, param_map)?],
1,
)),
}
}
fn unsupported_control<T>(name: &str) -> Result<T, QasmDumpError> {
Err(QasmDumpError::UnsupportedClassicalControl(format!(
"{name} is not part of OpenQASM 2.0"
)))
}
fn qasm_condition<'a>(
expression: &ClassicalExpr,
registers: &'a ClassicalRegisterMap,
) -> Result<(&'a str, u128), QasmDumpError> {
match expression.kind() {
ClassicalExprKind::Cast {
cast: ClassicalCast::BitToBool,
expr,
} => Ok((classical_source(expr, registers, ClassicalType::Bit)?, 1)),
ClassicalExprKind::Unary {
op: ClassicalUnaryOp::Not,
expr,
} => match expr.kind() {
ClassicalExprKind::Cast {
cast: ClassicalCast::BitToBool,
expr,
} => Ok((classical_source(expr, registers, ClassicalType::Bit)?, 0)),
_ => unsupported_condition(expression),
},
ClassicalExprKind::Compare {
op: ClassicalCompareOp::Eq,
lhs,
rhs,
} => qasm_equality_condition(lhs, rhs, registers)
.or_else(|_| qasm_equality_condition(rhs, lhs, registers)),
_ => unsupported_condition(expression),
}
}
fn qasm_equality_condition<'a>(
source: &ClassicalExpr,
literal: &ClassicalExpr,
registers: &'a ClassicalRegisterMap,
) -> Result<(&'a str, u128), QasmDumpError> {
match (source.kind(), literal.kind()) {
(
ClassicalExprKind::Cast {
cast: ClassicalCast::BitVecToUInt,
expr,
},
ClassicalExprKind::UIntLiteral { width, value },
) if expr.ty().width() == width.get() => {
Ok((classical_source(expr, registers, expr.ty())?, *value))
}
(
ClassicalExprKind::Var(_) | ClassicalExprKind::Value(_),
ClassicalExprKind::BitLiteral(value),
) if source.ty() == ClassicalType::Bit => Ok((
classical_source(source, registers, ClassicalType::Bit)?,
u128::from(*value),
)),
(ClassicalExprKind::ExtractBit { value, index: 0 }, ClassicalExprKind::BitLiteral(bit))
if value.ty().width() == 1 =>
{
Ok((
classical_source(value, registers, value.ty())?,
u128::from(*bit),
))
}
_ => unsupported_condition(source),
}
}
fn classical_source<'a>(
expression: &ClassicalExpr,
registers: &'a ClassicalRegisterMap,
expected_type: ClassicalType,
) -> Result<&'a str, QasmDumpError> {
if expression.ty() != expected_type {
return unsupported_condition(expression);
}
match expression.kind() {
ClassicalExprKind::Var(var) => registers.var(*var),
ClassicalExprKind::Value(value) => registers.value(*value),
_ => unsupported_condition(expression),
}
}
fn unsupported_condition<T>(expression: &ClassicalExpr) -> Result<T, QasmDumpError> {
Err(QasmDumpError::UnsupportedClassicalControl(format!(
"condition {:?} cannot be reduced to 'creg == integer'",
expression.kind()
)))
}
fn operation_to_qasm(
op: &Operation,
circuit: &Circuit,
qubit_map: &HashMap<Qubit, String>,
param_map: &HashMap<String, Parameter>,
) -> Result<String, QasmDumpError> {
match &op.instruction {
Instruction::Standard(gate) => {
let mapped_qs = map_qubits(op, qubit_map);
match gate {
StandardGate::XY => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let phase: Parameter = theta.clone() - Parameter::pi() / 2.0;
let lambda: Parameter = Parameter::pi() / 2.0 - theta;
return Ok(format!(
"u3({},{},{}) {}",
Parameter::pi().to_string().replace("Ï€", "pi"),
phase.to_string().replace("Ï€", "pi"),
lambda.to_string().replace("Ï€", "pi"),
mapped_qs[0]
));
}
StandardGate::RXY => {
let theta = resolve_param(op, 0, circuit, param_map)?;
let phi = resolve_param(op, 1, circuit, param_map)?;
let phase: Parameter = phi.clone() - Parameter::pi() / 2.0;
let lambda: Parameter = Parameter::pi() / 2.0 - phi;
return Ok(format!(
"u3({},{},{}) {}",
theta.to_string().replace("Ï€", "pi"),
phase.to_string().replace("Ï€", "pi"),
lambda.to_string().replace("Ï€", "pi"),
mapped_qs[0]
));
}
_ => {}
}
let name = qasm2_builtin_gate_name(*gate).ok_or_else(|| {
QasmDumpError::UnsupportedClassicalControl(format!(
"gate {gate:?} cannot be used in an OpenQASM 2.0 conditional body"
))
})?;
let params_str = format_params(op, circuit, param_map)?;
Ok(format!("{}{} {}", name, params_str, mapped_qs.join(",")))
}
Instruction::McGate(mc_gate) => {
let num_ctrl = mc_gate.num_ctrl_qubits();
let base = mc_gate.base_gate();
let name = match (num_ctrl, base) {
(1, StandardGate::X) => "cx",
(2, StandardGate::X) => "ccx",
(1, StandardGate::Z) => "cz",
(1, StandardGate::Y) => "cy",
(1, StandardGate::H) => "ch",
(1, StandardGate::CX) => "ccx",
(1, StandardGate::RX) => "crx",
(1, StandardGate::RY) => "cry",
(1, StandardGate::RZ) => "crz",
(1, StandardGate::SWAP) => "cswap",
(1, StandardGate::Phase) => "cu1",
_ => {
return Err(QasmDumpError::FormatError(format!(
"Unsupported multi-controlled gate: {}-controlled {:?}",
num_ctrl, base
)));
}
};
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
Ok(format!("{}{} {}", name, params_str, mapped_qs.join(",")))
}
Instruction::Directive(directive) => match directive {
Directive::Barrier => Err(QasmDumpError::UnsupportedClassicalControl(
"barrier cannot be conditional in OpenQASM 2.0".to_string(),
)),
Directive::Reset => {
let mapped_qs = map_qubits(op, qubit_map);
Ok(format!("reset {}", mapped_qs[0]))
}
Directive::Measure => Err(QasmDumpError::MeasureInGateNotAllowed),
},
Instruction::CircuitGate(cg) => {
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
Ok(format!("{}{} {}", cg.name, params_str, mapped_qs.join(",")))
}
Instruction::UnitaryGate(unitary_gate) => {
let params_str = format_params(op, circuit, param_map)?;
let mapped_qs = map_qubits(op, qubit_map);
Ok(format!(
"{}{} {}",
unitary_gate.label(),
params_str,
mapped_qs.join(",")
))
}
Instruction::ClassicalData(_) => Err(QasmDumpError::UnsupportedClassicalData(
"classical data operation inside conditional body".to_string(),
)),
Instruction::ClassicalControl(_) => Err(QasmDumpError::UnsupportedClassicalControl(
"nested control flow".to_string(),
)),
Instruction::Delay => Err(QasmDumpError::UnsupportedClassicalControl(
"delay cannot be conditional in OpenQASM 2.0".to_string(),
)),
}
}
fn get_extended_gate_definition(gate: StandardGate) -> Option<&'static str> {
match gate {
StandardGate::CRX => {
Some("gate crx(theta) a,b { h b;rz(theta/2) b; cx a,b; rz(-theta/2) b; cx a,b; h b;}")
}
StandardGate::CRY => {
Some("gate cry(theta) a,b { ry(theta/2) b; cx a,b; ry(-theta/2) b; cx a,b; }")
}
StandardGate::RZZ => Some("gate rzz(theta) a,b { cx a,b; rz(theta) b; cx a,b; }"),
StandardGate::RXX => {
Some("gate rxx(theta) a,b { h a; h b; cx a,b; rz(theta) b; cx a,b; h a; h b; }")
}
StandardGate::RYY => Some(
"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; }",
),
StandardGate::RZX => Some("gate rzx(theta) a,b { h b; cx a,b; rz(theta) b; cx a,b; h b; }"),
_ => None,
}
}
#[cfg(test)]
#[path = "./dump_test.rs"]
mod dump_test;