From 8f3ee103dc37799c419a7cd8f5bcd42efedb0056 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 15 Jan 2025 15:43:46 +0900 Subject: [PATCH 1/2] draft for qasm3exporter --- Cargo.lock | 5 + crates/qasm3/Cargo.toml | 5 + crates/qasm3/src/ast.rs | 438 ++++++++++++++++ crates/qasm3/src/exporter.rs | 974 +++++++++++++++++++++++++++++++++++ crates/qasm3/src/lib.rs | 21 + crates/qasm3/src/printer.rs | 577 +++++++++++++++++++++ crates/qasm3/src/symbols.rs | 455 ++++++++++++++++ 7 files changed, 2475 insertions(+) create mode 100644 crates/qasm3/src/ast.rs create mode 100644 crates/qasm3/src/exporter.rs create mode 100644 crates/qasm3/src/printer.rs create mode 100644 crates/qasm3/src/symbols.rs diff --git a/Cargo.lock b/Cargo.lock index 74ef042e64d3..1055e6319c07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1228,8 +1228,13 @@ dependencies = [ "ahash 0.8.11", "hashbrown 0.14.5", "indexmap", + "lazy_static", "oq3_semantics", + "oq3_syntax", "pyo3", + "qiskit-circuit", + "regex", + "thiserror", ] [[package]] diff --git a/crates/qasm3/Cargo.toml b/crates/qasm3/Cargo.toml index a7be442f67e3..d255b8854d01 100644 --- a/crates/qasm3/Cargo.toml +++ b/crates/qasm3/Cargo.toml @@ -13,7 +13,12 @@ doctest = false indexmap.workspace = true hashbrown.workspace = true oq3_semantics = "0.7.0" +oq3_syntax = "0.7.0" +qiskit-circuit.workspace = true ahash.workspace = true +regex = "1.11" +lazy_static = "1.5" +thiserror = "1.0" [dependencies.pyo3] workspace = true diff --git a/crates/qasm3/src/ast.rs b/crates/qasm3/src/ast.rs new file mode 100644 index 000000000000..d2b9361765cb --- /dev/null +++ b/crates/qasm3/src/ast.rs @@ -0,0 +1,438 @@ +use std::fmt::{self, Debug, Display, Formatter}; + +pub enum Node<'a> { + Program(&'a Program), + Pragma(&'a Pragma), + Header(&'a Header), + Include(&'a Include), + Version(&'a Version), + Expression(&'a Expression), + ProgramBlock(&'a ProgramBlock), + QuantumBlock(&'a QuantumBlock), + QuantumMeasurement(&'a QuantumMeasurement), + QuantumGateModifier(&'a QuantumGateModifier), + QuantumGateSignature(&'a QuantumGateSignature), + ClassicalType(&'a ClassicalType), + Statement(&'a Statement), + IndexSet(&'a IndexSet), +} + +#[derive(Debug)] +pub struct Program { + pub header: Header, + pub statements: Vec, +} + +#[derive(Debug)] +pub struct Pragma { + pub content: String, +} + +#[derive(Debug)] +pub struct Include { + pub filename: String, +} + +#[derive(Debug)] +pub struct Header { + pub version: Option, + pub includes: Vec, +} + +#[derive(Debug)] +pub struct Version { + pub version_number: String, +} + +#[derive(Debug)] +pub struct ProgramBlock { + pub statements: Vec, +} + +#[derive(Debug)] +pub struct QuantumBlock { + pub statements: Vec, +} + +#[derive(Debug, Clone)] +pub enum ClassicalType { + Float(Float), + Bool, + Int(Int), + Uint(Uint), + Bit, + BitArray(BitArray), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Float { + Half = 16, + Single = 32, + Double = 64, + Quad = 128, + Oct = 256, +} + +impl Float { + pub fn iter() -> impl Iterator { + [ + Float::Half, + Float::Single, + Float::Double, + Float::Quad, + Float::Oct, + ] + .iter() + .copied() + } +} + +impl Display for Float { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let float_str = match self { + Float::Half => "16".to_string(), + Float::Single => "32".to_string(), + Float::Double => "64".to_string(), + Float::Quad => "128".to_string(), + Float::Oct => "256".to_string(), + }; + write!(f, "{}", float_str) + } +} + +#[derive(Debug, Clone)] +pub struct Int { + pub size: Option, +} + +#[derive(Debug, Clone)] + +pub struct Uint { + pub size: Option, +} + +#[derive(Debug, Clone)] +pub struct BitArray { + pub size: u32, +} + +#[derive(Debug)] +pub struct DurationLiteral { + pub value: f64, + pub unit: DurationUnit, +} + +#[derive(Debug, Clone)] +pub enum DurationUnit { + Nanosecond, + Microsecond, + Millisecond, + Second, + Sample, +} + +impl Display for DurationUnit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let unit_str = match self { + DurationUnit::Nanosecond => "ns", + DurationUnit::Microsecond => "us", + DurationUnit::Millisecond => "us", + DurationUnit::Second => "s", + DurationUnit::Sample => "dt", + }; + write!(f, "{}", unit_str) + } +} + +#[derive(Debug, Clone)] +pub enum IOModifier { + Input, + Output, +} + +#[derive(Debug, Clone)] +pub struct Identifier { + pub string: String, +} + +#[derive(Debug, Clone)] +pub struct ClassicalDeclaration { + pub type_: ClassicalType, + pub identifier: Identifier, + // pub initializer: Option>, +} + +#[derive(Debug, Clone)] +pub struct IODeclaration { + pub modifier: IOModifier, + pub type_: ClassicalType, + pub identifier: Identifier, +} + +#[derive(Debug)] +pub struct QuantumDeclaration { + pub identifier: Identifier, + pub designator: Option, +} + +#[derive(Debug)] +pub enum Designator { + Literal(usize), + Expression(String), +} + +#[derive(Debug)] +pub struct Delay { + pub duration: DurationLiteral, + pub qubits: Vec, +} + +#[derive(Debug)] +pub enum Statement { + QuantumDeclaration(QuantumDeclaration), + ClassicalDeclaration(ClassicalDeclaration), + IODeclaration(IODeclaration), + QuantumInstruction(QuantumInstruction), + QuantumMeasurementAssignment(QuantumMeasurementAssignment), + Assignment(Assignment), + QuantumGateDefinition(QuantumGateDefinition), + // Branch(Branch), + // ForLoop(ForLoop), + // WhileLoop(WhileLoop), + // Switch(Switch), + Break(Break), + Continue(Continue), +} + +#[derive(Debug)] +pub enum QuantumInstruction { + GateCall(GateCall), + Reset(Reset), + Barrier(Barrier), + Delay(Delay), +} + +#[derive(Debug)] +pub struct GateCall { + pub quantum_gate_name: Identifier, + pub index_identifier_list: Vec, + pub parameters: Vec, + pub modifiers: Option>, +} + +#[derive(Debug)] +pub struct Barrier { + pub index_identifier_list: Vec, +} + +#[derive(Debug)] +pub struct Reset { + pub identifier: Identifier, +} + +#[derive(Debug)] +pub struct QuantumMeasurement { + pub identifier_list: Vec, +} + +#[derive(Debug)] +pub struct QuantumMeasurementAssignment { + pub identifier: Identifier, + pub quantum_measurement: QuantumMeasurement, +} + +#[derive(Debug)] +pub struct QuantumGateSignature { + pub name: Identifier, + pub qarg_list: Vec, + pub params: Option>, +} + +#[derive(Debug)] +pub struct QuantumGateDefinition { + pub quantum_gate_signature: QuantumGateSignature, + pub quantum_block: QuantumBlock, +} + +#[derive(Debug)] +pub struct QuantumGateCall { + pub quantum_gate_name: Identifier, + pub index_identifier_list: Vec, + pub parameters: Vec, + pub modifiers: Option>, +} + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum QuantumGateModifierName { + Ctrl, + Negctrl, + Inv, + Pow, +} + +#[derive(Debug)] +pub struct QuantumGateModifier { + pub modifier: QuantumGateModifierName, + pub argument: Option, +} + +#[derive(Debug)] +pub struct Assignment { + pub lvalue: Identifier, + pub rvalue: Vec, +} + +#[derive(Debug)] +pub struct Break {} + +#[derive(Debug)] +pub struct Continue {} + +#[derive(Debug)] +pub enum Expression { + Constant(Constant), + Parameter(Parameter), + Range(Range), + Identifier(Identifier), + SubscriptedIdentifier(SubscriptedIdentifier), + IntegerLiteral(IntegerLiteral), + BooleanLiteral(BooleanLiteral), + BitstringLiteral(BitstringLiteral), + DurationLiteral(DurationLiteral), + Unary(Unary), + Binary(Binary), + Cast(Cast), + Index(Index), +} + +#[derive(Debug)] +pub struct Parameter { + pub obj: String, +} + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum Constant { + PI, + Euler, + Tau, +} + +#[derive(Debug)] +pub struct Range { + pub start: Option>, + pub end: Option>, + pub step: Option>, +} + +#[derive(Debug)] +pub struct SubscriptedIdentifier { + pub string: String, + pub subscript: Box, +} + +#[derive(Debug, Clone)] +pub struct IntegerLiteral { + pub value: i32, +} + +#[derive(Debug)] +pub struct BooleanLiteral { + pub value: bool, +} + +#[derive(Debug)] +pub struct BitstringLiteral { + pub value: String, + pub width: u32, +} + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum OP<'a> { + UnaryOp(&'a UnaryOp), + BinaryOp(&'a BinaryOp), +} +#[derive(Debug)] +pub struct Unary { + pub op: UnaryOp, + pub operand: Box, +} + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum UnaryOp { + LogicNot, + BitNot, + Default, +} + +impl Display for UnaryOp { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let unit_str = match self { + UnaryOp::LogicNot => "!", + UnaryOp::BitNot => "~", + UnaryOp::Default => "", + }; + write!(f, "{}", unit_str) + } +} + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum BinaryOp { + BitAnd, + BitOr, + BitXor, + LogicAnd, + LogicOr, + Less, + LessEqual, + Greater, + GreaterEqual, + Equal, + NotEqual, + ShiftLeft, + ShiftRight, +} + +impl Display for BinaryOp { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let unit_str = match self { + BinaryOp::BitAnd => "&", + BinaryOp::BitOr => "|", + BinaryOp::BitXor => "^", + BinaryOp::LogicAnd => "&&", + BinaryOp::LogicOr => "||", + BinaryOp::Less => "<", + BinaryOp::LessEqual => "<=", + BinaryOp::Greater => ">", + BinaryOp::GreaterEqual => ">=", + BinaryOp::Equal => "==", + BinaryOp::NotEqual => "!=", + BinaryOp::ShiftLeft => "<<", + BinaryOp::ShiftRight => ">>", + }; + write!(f, "{}", unit_str) + } +} + +#[derive(Debug)] +pub struct Binary { + pub op: BinaryOp, + pub left: Box, + pub right: Box, +} + +#[derive(Debug)] +pub struct Cast { + pub type_: ClassicalType, + pub operand: Box, +} + +#[derive(Debug)] +pub struct Index { + pub target: Box, + pub index: Box, +} + +#[derive(Debug)] +pub struct IndexSet { + pub values: Vec, +} diff --git a/crates/qasm3/src/exporter.rs b/crates/qasm3/src/exporter.rs new file mode 100644 index 000000000000..c139b7e71908 --- /dev/null +++ b/crates/qasm3/src/exporter.rs @@ -0,0 +1,974 @@ +use crate::ast::{ + Barrier, Break, ClassicalDeclaration, ClassicalType, Constant, Continue, Delay, + DurationLiteral, DurationUnit, Expression, Float, GateCall, Header, IODeclaration, IOModifier, + Identifier, Include, IntegerLiteral, Node, Parameter, Program, QuantumBlock, + QuantumDeclaration, QuantumGateDefinition, QuantumGateModifier, QuantumGateModifierName, + QuantumGateSignature, QuantumInstruction, QuantumMeasurement, QuantumMeasurementAssignment, + Reset, Statement, Version, +}; +use crate::printer::BasicPrinter; +use crate::symbols::SymbolTable; +use hashbrown::{HashMap, HashSet}; +use oq3_semantics::types::{IsConst, Type}; +use pyo3::prelude::*; +use pyo3::Python; +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::operations::{Operation, Param}; +use qiskit_circuit::packed_instruction::PackedInstruction; +use qiskit_circuit::{Clbit, Qubit}; +use std::sync::Mutex; +use thiserror::Error; + +use lazy_static::lazy_static; +use regex::Regex; + +type ExporterResult = std::result::Result; + +#[derive(Error, Debug)] +pub enum QASM3ExporterError { + #[error("Error: {0}")] + Error(String), + #[error("Symbol '{0}' is not found in the table")] + SymbolNotFound(String), + #[error("Failed to rename symbol: {0}")] + RenameFailed(String), + #[error("PyError: {0}")] + PyErr(PyErr), + #[error("Not in global scope")] + NotInGlobalScopeError, +} + +impl From for QASM3ExporterError { + fn from(err: PyErr) -> Self { + QASM3ExporterError::PyErr(err) + } +} + +lazy_static! { + static ref GLOBAL_COUNTER: Mutex = Mutex::new(0); +} + +fn get_next_counter_value() -> usize { + let mut counter = GLOBAL_COUNTER.lock().unwrap(); + let value = *counter; + *counter += 1; + value +} + +lazy_static! { + static ref RESERVED_KEYWORDS: HashSet<&'static str> = [ + "OPENQASM", + "angle", + "array", + "barrier", + "bit", + "bool", + "box", + "break", + "cal", + "complex", + "const", + "continue", + "creg", + "ctrl", + "def", + "defcal", + "defcalgrammar", + "delay", + "duration", + "durationof", + "else", + "end", + "extern", + "float", + "for", + "gate", + "gphase", + "if", + "in", + "include", + "input", + "int", + "inv", + "let", + "measure", + "mutable", + "negctrl", + "output", + "pow", + "qreg", + "qubit", + "reset", + "return", + "sizeof", + "stretch", + "uint", + "while" + ] + .into_iter() + .collect(); +} + +lazy_static! { + static ref VALID_IDENTIFIER: Regex = Regex::new(r"(^[\w][\w\d]*$|^\$\d+$)").unwrap(); +} + +pub struct Exporter { + includes: Vec<&'static str>, + basis_gates: Vec<&'static str>, + disable_constants: bool, + alias_classical_registers: bool, + allow_aliasing: bool, + indent: &'static str, +} + +impl Exporter { + pub fn new( + includes: Vec<&'static str>, + basis_gates: Vec<&'static str>, + disable_constants: bool, + alias_classical_registers: bool, + allow_aliasing: bool, + indent: &'static str, + ) -> Self { + Exporter { + includes, + basis_gates, + disable_constants, + alias_classical_registers, + allow_aliasing, + indent, + } + } + + pub fn dump( + &self, + circuit_data: &CircuitData, + islayout: bool, + ) -> String { + let mut stream = String::new(); + let mut builder = QASM3Builder::new( + circuit_data, + islayout, + self.includes.clone(), + self.basis_gates.clone(), + self.disable_constants, + self.allow_aliasing, + ); + match builder.build_program() { + Ok(program) => { + BasicPrinter::new(&mut stream, self.indent.to_string(), false) + .visit(&Node::Program(&program)); + stream + } + Err(e) => format!("Error: {:?}", e), + } + } +} +#[derive(Debug, Clone)] +struct BuildScope<'a> { + circuit_data: &'a CircuitData, + qubit_map: HashMap, + clbit_map: HashMap, +} + +impl<'a> BuildScope<'a> { + fn new(circuit_data: &'a CircuitData) -> Self { + let qubit_map: HashMap = Python::with_gil(|py| { + let qubits = circuit_data.qubits(); + qubits + .bits() + .iter() + .map(|bit| { + let bound_bit = bit.bind(py); + let qubit = qubits.find(bound_bit).unwrap_or_else(|| { + panic!("Qubit not found"); + }); + (qubit, qubit) + }) + .collect() + }); + + let clbit_map: HashMap = Python::with_gil(|py| { + let clbits = circuit_data.clbits(); + clbits + .bits() + .iter() + .map(|bit| { + let bound_bit = bit.bind(py); + let clbit = clbits.find(bound_bit).unwrap_or_else(|| { + panic!("Clbit not found"); + }); + (clbit, clbit) + }) + .collect() + }); + + BuildScope { + circuit_data, + qubit_map, + clbit_map, + } + } +} + +#[derive(Debug, Clone)] +struct Counter { + current: usize, +} + +impl Counter { + fn new() -> Self { + Self { current: 0 } + } +} + +impl Iterator for Counter { + type Item = usize; + + fn next(&mut self) -> Option { + let next_value = self.current; + self.current = self.current.wrapping_add(1); + Some(next_value) + } +} + +pub struct QASM3Builder<'a> { + buildins: HashSet<&'static str>, + loose_bit_prefix: &'static str, + loose_qubit_prefix: &'static str, + gate_parameter_prefix: &'static str, + gate_qubit_prefix: &'static str, + circuit_scope: BuildScope<'a>, + islayout: bool, + symbol_table: SymbolTable, + global_io_declarations: Vec, + global_classical_forward_declarations: Vec, + includes: Vec<&'static str>, + basis_gates: Vec<&'static str>, + disable_constants: bool, + allow_aliasing: bool, + counter: Counter, +} + +impl<'a> QASM3Builder<'a> { + pub fn new( + circuit_data: &'a CircuitData, + islayout: bool, + includes: Vec<&'static str>, + basis_gates: Vec<&'static str>, + disable_constants: bool, + allow_aliasing: bool, + ) -> Self { + QASM3Builder { + buildins: [ + "barrier", + "measure", + "reset", + "delay", + "break_loop", + "continue_loop", + "store", + ] + .into_iter() + .collect(), + loose_bit_prefix: "_bit", + loose_qubit_prefix: "_qubit", + gate_parameter_prefix: "_gate_p", + gate_qubit_prefix: "_gate_q", + circuit_scope: BuildScope::new(circuit_data), + islayout, + symbol_table: SymbolTable::new(), + global_io_declarations: Vec::new(), + global_classical_forward_declarations: Vec::new(), + includes, + basis_gates, + disable_constants, + allow_aliasing, + counter: Counter::new(), + } + } + + #[allow(dead_code)] + fn define_gate( + &mut self, + instruction: &PackedInstruction, + ) -> ExporterResult { + let operation = &instruction.op; + if operation.name() == "cx" && operation.num_qubits() == 2 { + let (control, target) = ( + Identifier { + string: "c".to_string(), + }, + Identifier { + string: "t".to_string(), + }, + ); + let call = GateCall { + quantum_gate_name: Identifier { + string: "U".to_string(), + }, + index_identifier_list: vec![control.clone(), target.clone()], + parameters: vec![ + Expression::Constant(Constant::PI), + Expression::IntegerLiteral(IntegerLiteral { value: 0 }), + Expression::Constant(Constant::PI), + ], + modifiers: Some(vec![QuantumGateModifier { + modifier: QuantumGateModifierName::Ctrl, + argument: None, + }]), + }; + + Ok(QuantumGateDefinition { + quantum_gate_signature: QuantumGateSignature { + name: Identifier { + string: "cx".to_string(), + }, + qarg_list: vec![control, target], + params: None, + }, + quantum_block: QuantumBlock { + statements: vec![Statement::QuantumInstruction(QuantumInstruction::GateCall( + call, + ))], + }, + }) + } else { + let qubit_map: HashMap = Python::with_gil(|py| { + let qubits = self.circuit_scope.circuit_data.qubits(); + qubits + .bits() + .iter() + .map(|bit| { + let bound_bit = bit.bind(py); + let qubit = qubits.find(bound_bit).unwrap_or_else(|| { + panic!("Qubit not found"); + }); + (qubit, qubit) + }) + .collect() + }); + + let clbit_map: HashMap = Python::with_gil(|py| { + let clbits = self.circuit_scope.circuit_data.clbits(); + clbits + .bits() + .iter() + .map(|bit| { + let bound_bit = bit.bind(py); + let clbit = clbits.find(bound_bit).unwrap_or_else(|| { + panic!("Clbit not found"); + }); + (clbit, clbit) + }) + .collect() + }); + + let operation_def_owned = operation.definition(instruction.params_view()).ok_or( + QASM3ExporterError::Error("Failed to get operation definition".to_string()), + )?; + + let body = { + let tmp_scope = BuildScope { + circuit_data: unsafe { std::mem::transmute::<&_, &'a _>(&operation_def_owned) }, + qubit_map, + clbit_map, + }; + + let old_scope = std::mem::replace(&mut self.circuit_scope, tmp_scope); + let body = QuantumBlock { + statements: self.build_current_scope()?, + }; + self.circuit_scope = old_scope; + body + }; + + let defn = &operation_def_owned; + let params: Vec = (0..defn.num_parameters()) + .map(|i| { + let name = format!("{}_{}", self.gate_parameter_prefix, i); + let symbol = if self + .symbol_table + .new_binding(&name, &Type::Float(Some(64), IsConst::False)) + .is_ok() + { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + } else { + let rename = + escape_invalid_identifier(&self.symbol_table, &name, true, false); + if self + .symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) + .is_err() + { + return Err(QASM3ExporterError::RenameFailed(name)); + } + + self.symbol_table + .lookup(&rename) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + }; + Ok(Expression::Parameter(Parameter { + obj: symbol.symbol().name().to_string(), + })) + }) + .collect::>>()?; + + let qubits: Vec = (0..defn.num_parameters()) + .map(|i| { + let name = format!("{}_{}", self.gate_qubit_prefix, i); + let symbol = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + } else { + let rename = + escape_invalid_identifier(&self.symbol_table, &name, true, false); + if self + .symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) + .is_err() + { + return Err(QASM3ExporterError::RenameFailed(name)); + } + + self.symbol_table + .lookup(&rename) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + }; + + Ok(Identifier { + string: symbol.symbol().name().to_string(), + }) + }) + .collect::>>()?; + if self + .symbol_table + .new_binding( + operation.name(), + &Type::Gate( + operation.num_clbits() as usize, + operation.num_qubits() as usize, + ), + ) + .is_ok() + {} + Ok(QuantumGateDefinition { + quantum_gate_signature: QuantumGateSignature { + name: Identifier { + string: operation.name().to_string(), + }, + qarg_list: qubits, + params: Some(params), + }, + quantum_block: body, + }) + } + } + + pub fn build_program(&mut self) -> ExporterResult { + for builtin in self.basis_gates.iter() { + let _ = self.symbol_table.new_binding(builtin, &Type::Gate(0, 1)); + } + let header = Header { + version: Some(Version { + version_number: "3.0".to_string(), + }), + includes: self.build_includes(), + }; + let _ = self.hoist_global_parameter_declaration(); + let _ = self.hoist_classical_register_declarations(); + let qubit_declarations = self.build_quantum_declarations()?; + + let main_statements = self.build_current_scope()?; + + let mut statements = Vec::new(); + for global_io_declaration in self.global_io_declarations.iter() { + statements.push(Statement::IODeclaration(global_io_declaration.to_owned())); + } + for global_classical_forward_declaration in + self.global_classical_forward_declarations.iter() + { + statements.push(Statement::ClassicalDeclaration( + global_classical_forward_declaration.to_owned(), + )); + } + for qubit_declaration in qubit_declarations.into_iter() { + statements.push(Statement::QuantumDeclaration(qubit_declaration)); + } + statements.extend(main_statements); + Ok(Program { header, statements }) + } + + fn build_includes(&mut self) -> Vec { + let mut includes = Vec::new(); + for filename in &self.includes { + if *filename == "stdgates.inc" { + self.symbol_table.standard_library_gates(); + } + includes.push(Include { + filename: filename.to_string(), + }); + } + includes + } + + fn assert_global_scope(&self) -> ExporterResult<()> { + if !self.symbol_table.in_global_scope() { + drop(QASM3ExporterError::NotInGlobalScopeError); + } + Ok(()) + } + + fn hoist_global_parameter_declaration(&mut self) -> ExporterResult<()> { + let _ = self.assert_global_scope(); + let circuit_data = self.circuit_scope.circuit_data; + + Python::with_gil(|py| { + for parameter in circuit_data.get_parameters(py) { + let name: String = parameter.getattr("name")?.extract()?; + + let parameter = if self + .symbol_table + .new_binding(&name, &Type::Float(Some(64), IsConst::False)) + .is_ok() + { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) + } else { + let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); + if self + .symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) + .is_err() + { + return Err(QASM3ExporterError::RenameFailed(name)); + } + + self.symbol_table + .lookup(&rename) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) + }; + + self.global_io_declarations.push(IODeclaration { + modifier: IOModifier::Input, + type_: ClassicalType::Float(Float::Double), + identifier: Identifier { + string: parameter?.symbol().name().to_string(), + }, + }); + } + Ok(()) + }) + } + + fn build_instruction( + &mut self, + instruction: &PackedInstruction, + statements: &mut Vec, + ) -> ExporterResult<()> { + let op_name = instruction.op.name(); + if instruction.op.control_flow() { + if op_name == "for_loop" { + return Err(QASM3ExporterError::Error(format!( + "Unsupported Python interface of control flow condition: {}", + op_name + ))); + } else if op_name == "while_loop" { + return Err(QASM3ExporterError::Error(format!( + "Unsupported Python interface of control flow condition: {}", + op_name + ))); + } else if op_name == "if_else" { + return Err(QASM3ExporterError::Error(format!( + "Unsupported Python interface of control flow condition: {}", + op_name + ))); + } else if op_name == "switch_case" { + return Err(QASM3ExporterError::Error(format!( + "Unsupported Python interface of control flow condition: {}", + op_name + ))); + } else { + return Err(QASM3ExporterError::Error(format!( + "Unsupported Python interface of control flow condition: {}", + op_name + ))); + } + } else { + let qubits = self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instruction.qubits); + if instruction.op.name() == "barrier" { + let mut barrier_qubits = Vec::new(); + for qubit in qubits { + let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); + let qubit_symbol = self + .symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + barrier_qubits.push(Identifier { + string: qubit_symbol.symbol().name().to_string(), + }); + } + statements.push(Statement::QuantumInstruction(QuantumInstruction::Barrier( + Barrier { + index_identifier_list: barrier_qubits, + }, + ))); + } else if instruction.op.name() == "measure" { + let mut measured_qubits = Vec::new(); + for qubit in qubits { + let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); + let qubit_symbol = self + .symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + measured_qubits.push(Identifier { + string: qubit_symbol.symbol().name().to_string(), + }); + } + let measurement = QuantumMeasurement { + identifier_list: measured_qubits, + }; + let clbits = self + .circuit_scope + .circuit_data + .cargs_interner() + .get(instruction.clbits); + let name = format!("{}{}", self.loose_bit_prefix, clbits[0].0); + let clbit_symbol = self + .symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + statements.push(Statement::QuantumMeasurementAssignment( + QuantumMeasurementAssignment { + identifier: Identifier { + string: clbit_symbol.symbol().name().to_string(), + }, + quantum_measurement: measurement, + }, + )); + } else if instruction.op.name() == "reset" { + for qubit in qubits { + let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); + let qubit_symbol = self + .symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + statements.push(Statement::QuantumInstruction(QuantumInstruction::Reset( + Reset { + identifier: Identifier { + string: qubit_symbol.symbol().name().to_string(), + }, + }, + ))); + } + } else if instruction.op.name() == "delay" { + let delay = self.build_delay(instruction)?; + statements.push(Statement::QuantumInstruction(QuantumInstruction::Delay( + delay, + ))); + } else if instruction.op.name() == "break_loop" { + statements.push(Statement::Break(Break {})); + } else if instruction.op.name() == "continue_loop" { + statements.push(Statement::Continue(Continue {})); + } else if instruction.op.name() == "store" { + panic!("Store is not yet supported"); + } else { + statements.push(Statement::QuantumInstruction(QuantumInstruction::GateCall( + self.build_gate_call(instruction)?, + ))); + } + } + Ok(()) + } + + fn build_gate_call(&mut self, instruction: &PackedInstruction) -> ExporterResult { + let gate_identifier = if self + .symbol_table + .standard_library_gates() + .contains(&instruction.op.name()) + { + Identifier { + string: instruction.op.name().to_string(), + } + } else { + panic!( + "Non-standard gate calls are not yet supported, but received: {}", + instruction.op.name() + ); + }; + let qubits = self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instruction.qubits); + let parameters: Vec = if self.disable_constants { + if instruction.params_view().is_empty() { + Vec::::new() + } else { + instruction + .params_view() + .iter() + .map(|param| match param { + Param::Float(value) => Expression::Parameter(Parameter { + obj: value.to_string(), + }), + Param::ParameterExpression(_) => { + panic!("Parameter expressions are not yet supported") + } + Param::Obj(_) => panic!("Objects are not yet supported"), + }) + .collect() + } + } else { + panic!("'disable_constant = true' are not yet supported"); + }; + + let index_identifier_list: Vec = qubits + .iter() + .map(|qubit| { + let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) + .map(|qubit_symbol| Identifier { + string: qubit_symbol.symbol().name().to_string(), + }) + }) + .collect::>>()?; + + Ok(GateCall { + quantum_gate_name: gate_identifier, + index_identifier_list, + parameters, + modifiers: None, + }) + } + + fn hoist_classical_register_declarations(&mut self) -> ExporterResult<()> { + let _ = self.assert_global_scope(); + let circuit_data = self.circuit_scope.circuit_data; + let mut classical_declarations: Vec = Vec::new(); + for i in 0..circuit_data.num_clbits() { + let name = format!("{}{}", self.loose_bit_prefix, i); + let clbit = if self + .symbol_table + .new_binding(&name, &Type::Bit(IsConst::False)) + .is_ok() + { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + } else { + let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); + if self + .symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) + .is_err() + { + return Err(QASM3ExporterError::RenameFailed(rename)); + } + + self.symbol_table + .lookup(&rename) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + }; + + classical_declarations.push(ClassicalDeclaration { + type_: ClassicalType::Bit, + identifier: Identifier { + string: clbit.symbol().name().to_string(), + }, + }); + } + self.global_classical_forward_declarations + .extend(classical_declarations); + Ok(()) + } + + fn build_quantum_declarations(&mut self) -> ExporterResult> { + let _ = self.assert_global_scope(); + let circuit_data = self.circuit_scope.circuit_data; + let mut qubit_declarations: Vec = Vec::new(); + if self.islayout { + self.loose_qubit_prefix = "$"; + for i in 0..circuit_data.num_qubits() { + let name = format!("{}{}", self.loose_qubit_prefix, i); + let qubit = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + } else { + return Err(QASM3ExporterError::SymbolNotFound(name)); + }; + + qubit_declarations.push(QuantumDeclaration { + identifier: Identifier { + string: qubit.symbol().name().to_string(), + }, + designator: None, + }); + } + return Ok(qubit_declarations); + } + for i in 0..circuit_data.num_qubits() { + let name = format!("{}{}", self.loose_qubit_prefix, i); + let qubit = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { + self.symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + } else { + let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); + if self + .symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) + .is_err() + { + return Err(QASM3ExporterError::RenameFailed(rename)); + } + + self.symbol_table + .lookup(&rename) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? + }; + + qubit_declarations.push(QuantumDeclaration { + identifier: Identifier { + string: qubit.symbol().name().to_string(), + }, + designator: None, + }); + } + Ok(qubit_declarations) + } + + fn build_current_scope(&mut self) -> ExporterResult> { + let mut statements: Vec = Vec::::new(); + for instruction in self.circuit_scope.circuit_data.data() { + self.build_instruction(instruction, &mut statements)?; + } + Ok(statements) + } + + fn build_delay(&self, instruction: &PackedInstruction) -> ExporterResult { + if instruction.op.num_clbits() > 0 { + return Err(QASM3ExporterError::Error( + "Delay instruction cannot have classical bits".to_string(), + )); + } + + let duration_value = Python::with_gil(|py| { + if let Some(duration) = instruction.extra_attrs.duration() { + match duration.bind(py).extract::() { + Ok(value) => Ok(value), + Err(_) => Err(QASM3ExporterError::Error( + "Failed to extract duration value".to_string(), + )), + } + } else { + Err(QASM3ExporterError::Error( + "Failed to extract duration value".to_string(), + )) + } + })?; + let default_duration_unit = "us"; + let duration_unit = instruction + .extra_attrs + .unit() + .unwrap_or(default_duration_unit); + + let duration_literal = if duration_unit == "ps" { + DurationLiteral { + value: duration_value * 1000.0, + unit: DurationUnit::Nanosecond, + } + } else { + let unit_map: HashMap<&str, DurationUnit> = HashMap::from([ + ("ns", DurationUnit::Nanosecond), + ("us", DurationUnit::Microsecond), + ("ms", DurationUnit::Millisecond), + ("s", DurationUnit::Second), + ("dt", DurationUnit::Sample), + ]); + + match unit_map.get(duration_unit) { + Some(mapped_unit) => DurationLiteral { + value: duration_value, + unit: mapped_unit.clone(), + }, + None => { + return Err(QASM3ExporterError::Error(format!( + "Unknown duration unit: {}", + duration_unit + ))) + } + } + }; + + let mut qubits = Vec::new(); + + for qubit in self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instruction.qubits) + { + let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); + let qubit_symbol = self + .symbol_table + .lookup(&name) + .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + qubits.push(Identifier { + string: qubit_symbol.symbol().name().to_string(), + }); + } + + Ok(Delay { + duration: duration_literal, + qubits, + }) + } +} + +fn name_allowed(symbol_table: &SymbolTable, name: &str, unique: bool) -> bool { + if unique { + RESERVED_KEYWORDS.contains(name) || symbol_table.all_scopes_contains_name(name) + } else { + RESERVED_KEYWORDS.contains(name) + } +} + +fn escape_invalid_identifier( + symbol_table: &SymbolTable, + name: &str, + allow_rename: bool, + unique: bool, +) -> String { + let base = if allow_rename { + format!( + "_{}", + name.chars() + .map(|c| if c.is_alphanumeric() || c == '_' { + c + } else { + '_' + }) + .collect::() + ) + } else { + name.to_string() + }; + let mut updated_name = base.clone(); + while !name_allowed(symbol_table, &base, unique) { + updated_name = format!("{}_{}", base, get_next_counter_value()); + } + updated_name +} diff --git a/crates/qasm3/src/lib.rs b/crates/qasm3/src/lib.rs index 14ab9525e5bd..c50e92e4c8a3 100644 --- a/crates/qasm3/src/lib.rs +++ b/crates/qasm3/src/lib.rs @@ -10,10 +10,14 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +mod ast; mod build; mod circuit; mod error; +mod exporter; mod expr; +mod printer; +mod symbols; use std::ffi::OsString; use std::ops::Deref; @@ -26,6 +30,7 @@ use pyo3::types::PyModule; use oq3_semantics::syntax_to_semantics::parse_source_string; use pyo3::pybacked::PyBackedStr; +use qiskit_circuit::circuit_data::CircuitData; use crate::error::QASM3ImporterError; @@ -151,11 +156,27 @@ pub fn load( loads(py, source, custom_gates, include_path) } +#[pyfunction] +#[pyo3(signature = (circuit,/))] +pub fn dumps(_py: Python, circuit: &Bound) -> PyResult { + let mut result = String::new(); + let circuit_data = circuit + .getattr("_data")? + .downcast::()? + .borrow(); + let islayout = !circuit.getattr("layout")?.is_none(); + let exporter = + exporter::Exporter::new(vec!["stdgates.inc"], vec!["U"], true, false, false, " "); + let stream = exporter.dump(&circuit_data, islayout); + Ok(stream) +} + /// Internal module supplying the OpenQASM 3 import capabilities. The entries in it should largely /// be re-exposed directly to public Python space. pub fn qasm3(module: &Bound) -> PyResult<()> { module.add_function(wrap_pyfunction!(loads, module)?)?; module.add_function(wrap_pyfunction!(load, module)?)?; + module.add_function(wrap_pyfunction!(dumps, module)?)?; module.add_class::()?; Ok(()) } diff --git a/crates/qasm3/src/printer.rs b/crates/qasm3/src/printer.rs new file mode 100644 index 000000000000..ed14f2aaedba --- /dev/null +++ b/crates/qasm3/src/printer.rs @@ -0,0 +1,577 @@ +use hashbrown::HashMap; + +use std::fmt::Write; + +use crate::ast::{ + Assignment, Barrier, Binary, BinaryOp, BitArray, BitstringLiteral, BooleanLiteral, Cast, + ClassicalDeclaration, ClassicalType, Constant, Delay, DurationLiteral, Expression, Float, + GateCall, Header, Identifier, Include, Index, IndexSet, Int, IntegerLiteral, Node, Parameter, + Pragma, Program, ProgramBlock, QuantumBlock, QuantumDeclaration, QuantumGateDefinition, + QuantumGateModifier, QuantumGateModifierName, QuantumGateSignature, QuantumInstruction, + QuantumMeasurement, QuantumMeasurementAssignment, Range, Reset, Statement, + SubscriptedIdentifier, Uint, Unary, UnaryOp, Version, OP, +}; + +#[derive(Debug)] +struct BindingPower { + left: u8, + right: u8, +} + +impl BindingPower { + fn new(left: u8, right: u8) -> Self { + BindingPower { left, right } + } +} + +pub struct BasicPrinter<'a> { + stream: &'a mut String, + indent: String, + current_indent: usize, + chain_else_if: bool, + constant_lookup: HashMap, + modifier_lookup: HashMap, + float_width_lookup: HashMap, + binding_power: HashMap, BindingPower>, +} + +impl<'a> BasicPrinter<'a> { + pub fn new(stream: &'a mut String, indent: String, chain_else_if: bool) -> Self { + let mut constant_lookup = HashMap::new(); + constant_lookup.insert(Constant::PI, "pi"); + constant_lookup.insert(Constant::Euler, "euler"); + constant_lookup.insert(Constant::Tau, "tau"); + + let mut modifier_lookup = HashMap::new(); + modifier_lookup.insert(QuantumGateModifierName::Ctrl, "ctrl"); + modifier_lookup.insert(QuantumGateModifierName::Negctrl, "negctrl"); + modifier_lookup.insert(QuantumGateModifierName::Inv, "inv"); + modifier_lookup.insert(QuantumGateModifierName::Pow, "pow"); + + let float_width_lookup = Float::iter().map(|t| (t, t.to_string())).collect(); + + let mut binding_power = HashMap::new(); + binding_power.insert(OP::UnaryOp(&UnaryOp::LogicNot), BindingPower::new(0, 22)); + binding_power.insert(OP::UnaryOp(&UnaryOp::BitNot), BindingPower::new(0, 22)); + binding_power.insert( + OP::BinaryOp(&BinaryOp::ShiftLeft), + BindingPower::new(15, 16), + ); + binding_power.insert( + OP::BinaryOp(&BinaryOp::ShiftRight), + BindingPower::new(15, 16), + ); + binding_power.insert(OP::BinaryOp(&BinaryOp::Less), BindingPower::new(13, 14)); + binding_power.insert( + OP::BinaryOp(&BinaryOp::LessEqual), + BindingPower::new(13, 14), + ); + binding_power.insert(OP::BinaryOp(&BinaryOp::Greater), BindingPower::new(13, 14)); + binding_power.insert( + OP::BinaryOp(&BinaryOp::GreaterEqual), + BindingPower::new(13, 14), + ); + binding_power.insert(OP::BinaryOp(&BinaryOp::Equal), BindingPower::new(11, 12)); + binding_power.insert(OP::BinaryOp(&BinaryOp::NotEqual), BindingPower::new(11, 12)); + binding_power.insert(OP::BinaryOp(&BinaryOp::BitAnd), BindingPower::new(9, 10)); + binding_power.insert(OP::BinaryOp(&BinaryOp::BitXor), BindingPower::new(7, 8)); + binding_power.insert(OP::BinaryOp(&BinaryOp::BitOr), BindingPower::new(5, 6)); + binding_power.insert(OP::BinaryOp(&BinaryOp::LogicAnd), BindingPower::new(3, 4)); + binding_power.insert(OP::BinaryOp(&BinaryOp::LogicOr), BindingPower::new(1, 2)); + + BasicPrinter { + stream, + indent, + current_indent: 0, + chain_else_if, + constant_lookup, + modifier_lookup, + float_width_lookup, + binding_power, + } + } + + pub fn visit(&mut self, node: &Node) { + match node { + Node::Program(node) => self.visit_program(node), + Node::Header(node) => self.visit_header(node), + Node::Statement(node) => self.visit_statement(node), + Node::Version(node) => self.visit_version(node), + Node::Include(node) => self.visit_include(node), + Node::Pragma(node) => self.visit_pragma(node), + Node::ClassicalType(node) => self.visit_classical_type(node), + Node::Expression(node) => self.visit_expression(node), + Node::QuantumGateModifier(node) => self.visit_quantum_gate_modifier(node), + Node::IndexSet(node) => self.visit_index_set(node), + Node::QuantumMeasurement(node) => self.visit_quantum_measurement(node), + Node::ProgramBlock(node) => self.visit_program_block(node), + Node::QuantumBlock(node) => self.visit_quantum_block(node), + Node::QuantumGateSignature(node) => self.visit_quantum_gate_signature(node), + } + } + + fn start_line(&mut self) { + write!(self.stream, "{}", self.indent.repeat(self.current_indent)).unwrap(); + } + + fn end_statement(&mut self) { + writeln!(self.stream, ";").unwrap(); + } + + fn end_line(&mut self) { + writeln!(self.stream).unwrap(); + } + + fn write_statement(&mut self, line: &str) { + self.start_line(); + write!(self.stream, "{}", line).unwrap(); + self.end_statement(); + } + fn visit_modifier_sequence( + &mut self, + nodes: &[QuantumGateModifier], + start: &str, + end: &str, + separator: &str, + ) { + if !start.is_empty() { + write!(self.stream, "{}", start).unwrap(); + } + for node in nodes.iter().take(nodes.len() - 1) { + self.visit_quantum_gate_modifier(node); + write!(self.stream, "{}", separator).unwrap(); + } + if let Some(last) = nodes.last() { + self.visit_quantum_gate_modifier(last); + } + if !end.is_empty() { + write!(self.stream, "{}", end).unwrap(); + } + } + fn visit_expression_sequence( + &mut self, + nodes: &[Expression], + start: &str, + end: &str, + separator: &str, + ) { + if !start.is_empty() { + write!(self.stream, "{}", start).unwrap(); + } + for node in nodes.iter().take(nodes.len() - 1) { + self.visit_expression(node); + write!(self.stream, "{}", separator).unwrap(); + } + if let Some(last) = nodes.last() { + self.visit_expression(last); + } + if !end.is_empty() { + write!(self.stream, "{}", end).unwrap(); + } + } + + fn visit_program(&mut self, node: &Program) { + self.visit(&Node::Header(&node.header)); + for statement in node.statements.iter() { + self.visit_statement(statement); + } + } + + fn visit_header(&mut self, node: &Header) { + if let Some(version) = &node.version { + self.visit(&Node::Version(version)) + }; + for include in node.includes.iter() { + self.visit(&Node::Include(include)); + } + } + + fn visit_version(&mut self, node: &Version) { + self.write_statement(&format!("OPENQASM {}", node.version_number)); + } + + fn visit_include(&mut self, node: &Include) { + self.write_statement(&format!("include \"{}\"", node.filename)); + } + + fn visit_pragma(&mut self, node: &Pragma) { + self.write_statement(&format!("#pragma {}", node.content)); + } + + fn visit_statement(&mut self, statement: &Statement) { + match statement { + Statement::QuantumMeasurementAssignment(statement) => { + self.visit_quantum_measurement_assignment(statement) + } + Statement::ClassicalDeclaration(statement) => { + self.visit_classical_declaration(statement) + } + Statement::Assignment(statement) => self.visit_assignment_statement(statement), + Statement::QuantumGateDefinition(statement) => { + self.visit_quantum_gate_definition(statement) + } + Statement::QuantumInstruction(statement) => self.visit_quantum_instruction(statement), + Statement::QuantumDeclaration(statement) => self.visit_quantum_declaration(statement), + // Statement::Branch(statement) => self.visit_branching_statement(statement, false), + // Statement::ForLoop(statement) => self.visit_for_loop_statement(statement), + // Statement::WhileLoop(statement) => self.visit_while_loop_statement(statement), + // Statement::Switch(statement) => self.visit_switch_statement(statement), + Statement::Break(_) => self.visit_break_statement(), + Statement::Continue(_) => self.visit_continue_statement(), + Statement::IODeclaration(iodeclaration) => todo!(), + } + } + + fn visit_classical_declaration(&mut self, statement: &ClassicalDeclaration) { + self.start_line(); + self.visit_classical_type(&statement.type_); + write!(self.stream, " ").unwrap(); + self.visit_identifier(&statement.identifier); + self.end_statement(); + } + + fn visit_quantum_declaration(&mut self, statement: &QuantumDeclaration) { + self.start_line(); + write!(self.stream, "qubit").unwrap(); + write!(self.stream, " ").unwrap(); + self.visit_identifier(&statement.identifier); + self.end_statement(); + } + + fn visit_quantum_gate_modifier(&mut self, statement: &QuantumGateModifier) { + write!(self.stream, "{}", self.modifier_lookup[&statement.modifier]).unwrap(); + if let Some(argument) = &statement.argument { + write!(self.stream, "(").unwrap(); + self.visit_expression(argument); + write!(self.stream, ")").unwrap(); + } + } + + fn visit_quantum_instruction(&mut self, instruction: &QuantumInstruction) { + match instruction { + QuantumInstruction::GateCall(instruction) => self.visit_quantum_gate_call(instruction), + QuantumInstruction::Reset(instruction) => self.visit_quantum_reset(instruction), + QuantumInstruction::Barrier(instruction) => self.visit_quantum_barrier(instruction), + QuantumInstruction::Delay(instruction) => todo!(), + } + } + + fn visit_quantum_gate_call(&mut self, instruction: &GateCall) { + self.start_line(); + if let Some(modifiers) = &instruction.modifiers { + self.visit_modifier_sequence(modifiers, "", " @ ", " @ "); + } + self.visit_identifier(&instruction.quantum_gate_name); + if !instruction.parameters.is_empty() { + self.visit_expression_sequence(&instruction.parameters, "(", ")", ", "); + } + write!(self.stream, " ").unwrap(); + let index_identifier_list: Vec = instruction + .index_identifier_list + .iter() + .map(|identifier| Expression::Identifier(identifier.clone())) + .collect(); + self.visit_expression_sequence(&index_identifier_list, "", "", ", "); + self.end_statement(); + } + + fn visit_quantum_barrier(&mut self, instruction: &Barrier) { + self.start_line(); + write!(self.stream, "barrier ").unwrap(); + let index_identifier_vec: Vec = instruction + .index_identifier_list + .iter() + .map(|identifier| Expression::Identifier(identifier.clone())) + .collect(); + let index_identifier_list: &[Expression] = &index_identifier_vec; + self.visit_expression_sequence(index_identifier_list, "", "", ", "); + self.end_statement(); + } + + fn visit_quantum_reset(&mut self, instruction: &Reset) { + self.start_line(); + write!(self.stream, "reset ").unwrap(); + self.visit_identifier(&instruction.identifier); + self.end_statement(); + } + + fn visit_quantum_gate_signature(&mut self, node: &QuantumGateSignature) { + self.visit_identifier(&node.name); + if let Some(params) = &node.params { + if !params.is_empty() { + self.visit_expression_sequence(params, "(", ")", ", "); + } + } + write!(self.stream, " ").unwrap(); + let qarg_list: Vec = node + .qarg_list + .iter() + .map(|qarg| Expression::Identifier(qarg.clone())) + .collect(); + self.visit_expression_sequence(&qarg_list, "", "", ", "); + } + + fn visit_quantum_measurement(&mut self, node: &QuantumMeasurement) { + write!(self.stream, "measure ").unwrap(); + let identifier_vec: Vec = node + .identifier_list + .iter() + .map(|identifier| Expression::Identifier(identifier.clone())) + .collect(); + let identifier_list = &identifier_vec; + self.visit_expression_sequence(identifier_list, "", "", ", "); + } + + fn visit_quantum_measurement_assignment(&mut self, node: &QuantumMeasurementAssignment) { + self.start_line(); + self.visit_identifier(&node.identifier); + write!(self.stream, " = ").unwrap(); + self.visit_quantum_measurement(&node.quantum_measurement); + self.end_statement(); + } + + fn visit_quantum_gate_definition(&mut self, statement: &QuantumGateDefinition) { + self.start_line(); + write!(self.stream, "gate ").unwrap(); + self.visit_quantum_gate_signature(&statement.quantum_gate_signature); + write!(self.stream, " ").unwrap(); + self.visit_quantum_block(&statement.quantum_block); + self.end_line(); + } + + fn visit_program_block(&mut self, node: &ProgramBlock) { + writeln!(self.stream, "{{").unwrap(); + self.current_indent += 1; + for statement in &node.statements { + self.visit_statement(statement); + } + self.current_indent -= 1; + self.start_line(); + write!(self.stream, "}}").unwrap(); + } + + // The code is the same as visit_program_block + fn visit_quantum_block(&mut self, node: &QuantumBlock) { + writeln!(self.stream, "{{").unwrap(); + self.current_indent += 1; + for statement in &node.statements { + self.visit_statement(statement); + } + self.current_indent -= 1; + self.start_line(); + write!(self.stream, "}}").unwrap(); + } + + fn visit_classical_type(&mut self, node: &ClassicalType) { + match node { + ClassicalType::Float(type_) => self.visit_float_type(type_), + ClassicalType::Bool => self.visit_bool_type(), + ClassicalType::Int(type_) => self.visit_int_type(type_), + ClassicalType::Uint(type_) => self.visit_uint_type(type_), + ClassicalType::Bit => self.visit_bit_type(), + ClassicalType::BitArray(type_) => self.visit_bit_array_type(type_), + } + } + + fn visit_float_type(&mut self, type_: &Float) { + write!(self.stream, "float[{}]", self.float_width_lookup[type_]).unwrap() + } + + fn visit_bool_type(&mut self) { + write!(self.stream, "bool").unwrap() + } + + fn visit_int_type(&mut self, type_: &Int) { + write!(self.stream, "int").unwrap(); + if let Some(size) = type_.size { + write!(self.stream, "[{}]", size).unwrap(); + } + } + + fn visit_uint_type(&mut self, type_: &Uint) { + write!(self.stream, "uint").unwrap(); + if let Some(size) = type_.size { + write!(self.stream, "[{}]", size).unwrap(); + } + } + + fn visit_bit_type(&mut self) { + write!(self.stream, "bit").unwrap() + } + + fn visit_bit_array_type(&mut self, type_: &BitArray) { + write!(self.stream, "bit[{}]", type_.size).unwrap() + } + + fn visit_expression(&mut self, node: &Expression) { + match node { + Expression::Parameter(expression) => self.visit_parameter(expression), + Expression::Range(expression) => self.visit_range(expression), + Expression::Identifier(expression) => self.visit_identifier(expression), + Expression::SubscriptedIdentifier(expression) => { + self.visit_subscript_identifier(expression) + } + Expression::Constant(expression) => self.visit_constant(expression), + Expression::IntegerLiteral(expression) => self.visit_integer_literal(expression), + Expression::BooleanLiteral(expression) => self.visit_boolean_literal(expression), + Expression::BitstringLiteral(_) => { + panic!("BasicPrinter: BitStringLiteral has not been supported yet.") + } + Expression::DurationLiteral(expression) => self.visit_duration_literal(expression), + Expression::Unary(expression) => self.visit_unary(expression), + Expression::Binary(expression) => self.visit_binary(expression), + Expression::Cast(expression) => self.visit_cast(expression), + Expression::Index(expression) => self.visit_index(expression), + } + } + + fn visit_parameter(&mut self, expression: &Parameter) { + write!(self.stream, "{}", expression.obj).unwrap(); + } + + fn visit_range(&mut self, expression: &Range) { + if let Some(start) = &expression.start { + self.visit_expression(start); + } + write!(self.stream, ":").unwrap(); + if let Some(step) = &expression.step { + self.visit_expression(step); + write!(self.stream, ":").unwrap(); + } + if let Some(end) = &expression.end { + self.visit_expression(end); + } + } + + fn visit_index_set(&mut self, node: &IndexSet) { + self.visit_expression_sequence(&node.values, "{", "}", ", "); + } + + fn visit_identifier(&mut self, expression: &Identifier) { + write!(self.stream, "{}", expression.string).unwrap(); + } + + fn visit_subscript_identifier(&mut self, expression: &SubscriptedIdentifier) { + write!(self.stream, "{}", expression.string).unwrap(); + write!(self.stream, "[").unwrap(); + self.visit_expression(&expression.subscript); + write!(self.stream, "]").unwrap(); + } + + fn visit_constant(&mut self, expression: &Constant) { + write!(self.stream, "{}", self.constant_lookup[expression]).unwrap(); + } + + fn visit_integer_literal(&mut self, expression: &IntegerLiteral) { + write!(self.stream, "{}", expression.value).unwrap(); + } + + fn visit_boolean_literal(&mut self, expression: &BooleanLiteral) { + write!( + self.stream, + "{}", + if expression.value { "true" } else { "false" } + ) + .unwrap(); + } + + fn visit_duration_literal(&mut self, expression: &DurationLiteral) { + write!(self.stream, "{}{}", expression.value, expression.unit).unwrap(); + } + + fn visit_unary(&mut self, expression: &Unary) { + write!(self.stream, "{}", expression.op).unwrap(); + let op = OP::UnaryOp(&expression.op); + if matches!( + *expression.operand, + Expression::Unary(_) | Expression::Binary(_) + ) && self.binding_power[&op].left < self.binding_power[&op].right + { + write!(self.stream, "(").unwrap(); + self.visit_expression(&expression.operand); + write!(self.stream, ")").unwrap(); + } else { + self.visit_expression(&expression.operand); + } + } + + fn visit_binary(&mut self, expression: &Binary) { + let op = OP::BinaryOp(&expression.op); + if matches!( + *expression.left, + Expression::Unary(_) | Expression::Binary(_) + ) && self.binding_power[&op].left < self.binding_power[&op].right + { + write!(self.stream, "(").unwrap(); + self.visit_expression(&expression.left); + write!(self.stream, ")").unwrap(); + } else { + self.visit_expression(&expression.left); + } + write!(self.stream, "{}", expression.op).unwrap(); + if matches!( + *expression.right, + Expression::Unary(_) | Expression::Binary(_) + ) && self.binding_power[&op].left < self.binding_power[&op].right + { + write!(self.stream, "(").unwrap(); + self.visit_expression(&expression.right); + write!(self.stream, ")").unwrap(); + } else { + self.visit_expression(&expression.right); + } + } + + fn visit_index(&mut self, expression: &Index) { + if matches!( + *expression.target, + Expression::Unary(_) | Expression::Binary(_) + ) { + write!(self.stream, "(").unwrap(); + self.visit_expression(&expression.target); + write!(self.stream, ")").unwrap(); + } else { + self.visit_expression(&expression.target); + } + write!(self.stream, "[").unwrap(); + self.visit_expression(&expression.index); + write!(self.stream, "]").unwrap(); + } + + fn visit_cast(&mut self, expression: &Cast) { + self.visit_classical_type(&expression.type_); + write!(self.stream, "(").unwrap(); + self.visit_expression(&expression.operand); + write!(self.stream, ")").unwrap(); + } + + fn visit_break_statement(&mut self) { + self.write_statement("break"); + } + + fn visit_continue_statement(&mut self) { + self.write_statement("continue"); + } + + fn visit_assignment_statement(&mut self, statement: &Assignment) { + self.start_line(); + self.visit_identifier(&statement.lvalue); + write!(self.stream, " = ").unwrap(); + self.visit_identifier_for_switch(&statement.rvalue); + self.end_statement(); + } + + // Hacky for switch + fn visit_identifier_for_switch(&mut self, identifiers: &Vec) { + if identifiers.len() > 1 { + write!(self.stream, "[").unwrap(); + } + for identifier in identifiers { + self.visit_identifier(identifier); + write!(self.stream, ",").unwrap(); + } + if identifiers.len() > 1 { + write!(self.stream, "]").unwrap(); + } + } +} diff --git a/crates/qasm3/src/symbols.rs b/crates/qasm3/src/symbols.rs new file mode 100644 index 000000000000..f8ceba8b12db --- /dev/null +++ b/crates/qasm3/src/symbols.rs @@ -0,0 +1,455 @@ +// Copyright contributors to the openqasm-parser project +// SPDX-License-Identifier: Apache-2.0 + +// Defines data structures and api for symbols, scope, and symbol tables. + +use hashbrown::HashMap; +use oq3_semantics::types; +use oq3_semantics::types::Type; + +// OQ3 +// * "The lifetime of each identifier begins when it is declared, and ends +// at the completion of the scope it was declared in." +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScopeType { + /// Top-level + Global, + /// Body of `gate` and `def` + Subroutine, + /// `cal` and `defcal` blocks + Calibration, + /// Control flow blocks + Local, +} + +// This wrapped `usize` serves as +// * A unique label for instances of `Symbol`. +// * An index into `all_symbols: Vec`. +// * The values in `SymbolMap`. +// +// I am assuming that we can clone `SymbolId` willy-nilly +// because it is no more expensive than a reference. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SymbolId(usize); + +impl SymbolId { + pub fn new() -> SymbolId { + SymbolId(0) + } + + /// Post-increment the value, and return the old value. + /// This is used for getting a new `SymbolId`. + pub fn post_increment(&mut self) -> SymbolId { + let old_val = self.clone(); + self.0 += 1; + old_val + } +} + +impl Default for SymbolId { + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum SymbolError { + MissingBinding, + AlreadyBound, +} + +pub type SymbolIdResult = Result; +pub type SymbolRecordResult<'a> = Result, SymbolError>; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Symbol { + name: String, + typ: Type, + // ast_node: SyntaxNode, +} + +pub trait SymbolType { + /// Return the `Type` of `symbol`, which is `Type::Undefined` if + /// `self` is an `Option` with value `None`. + fn symbol_type(&self) -> &Type; +} + +impl Symbol { + fn new(name: T, typ: &Type) -> Symbol { + Symbol { + name: name.to_string(), + typ: typ.clone(), + } + // fn new(name: T, typ: &Type, ast_node: &SyntaxNode) -> Symbol { + // Symbol { name: name.to_string(), typ: typ.clone() } + // Symbol { name: name.to_string(), typ: typ.clone(), ast_node: ast_node.clone() } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl SymbolType for Symbol { + fn symbol_type(&self) -> &Type { + &self.typ + } +} + +/// A structure for temporarily collecting information about +/// a symbol. +/// * `Symbol` contains `name: String` and the `Type`. +/// * `symbol_id` wraps a `usize` that serves as +/// * a unique label +/// * the index into the `Vec` of all symbols. +/// * the value in `SymbolMap`: `name` -> `symbol_id`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SymbolRecord<'a> { + symbol: &'a Symbol, + symbol_id: SymbolId, +} + +impl SymbolRecord<'_> { + pub fn new(symbol: &Symbol, symbol_id: SymbolId) -> SymbolRecord<'_> { + SymbolRecord { symbol, symbol_id } + } + + pub fn symbol_id(&self) -> SymbolId { + self.symbol_id.clone() + } + + pub fn symbol(&self) -> &Symbol { + self.symbol + } +} + +// This trait is a bit heavy weight for what it does. +pub trait SymbolErrorTrait { + fn to_symbol_id(&self) -> SymbolIdResult; + fn as_tuple(&self) -> (SymbolIdResult, Type); +} + +impl SymbolErrorTrait for SymbolRecordResult<'_> { + fn to_symbol_id(&self) -> SymbolIdResult { + self.clone().map(|record| record.symbol_id) + } + + fn as_tuple(&self) -> (SymbolIdResult, Type) { + (self.to_symbol_id(), self.symbol_type().clone()) + } +} + +impl SymbolType for Option> { + fn symbol_type(&self) -> &Type { + match self { + Some(symbol_record) => symbol_record.symbol_type(), + None => &Type::Undefined, + } + } +} + +impl SymbolType for Result, SymbolError> { + fn symbol_type(&self) -> &Type { + match self { + Ok(symbol_record) => symbol_record.symbol_type(), + Err(_) => &Type::Undefined, + } + } +} + +impl SymbolType for SymbolRecord<'_> { + fn symbol_type(&self) -> &Type { + self.symbol.symbol_type() + } +} + +/// A `SymbolMap` is a map from `names` to `SymbolId` for a single instance +/// of a scope. +/// A `SymbolTable` is a stack of `SymbolMap`s together with a `Vec` mapping +/// `SymbolId as usize` to `Symbol`s. +#[derive(Clone, Debug, PartialEq, Eq)] +#[allow(dead_code)] +struct SymbolMap { + table: HashMap, + scope_type: ScopeType, +} + +impl SymbolMap { + fn new(scope_type: ScopeType) -> SymbolMap { + SymbolMap { + table: HashMap::::new(), + scope_type, + } + } + + pub fn insert(&mut self, name: T, sym: SymbolId) { + self.table.insert(name.to_string(), sym); + } + + pub fn get_symbol_id(&self, name: &str) -> Option<&SymbolId> { + self.table.get(name) + } + + pub fn len(&self) -> usize { + self.table.len() + } + + pub fn contains_name(&self, name: &str) -> bool { + self.table.contains_key(name) + } + + /// Return the `ScopeType` of the `SymbolMap` of the current, or top-most, scope. + pub fn scope_type(&self) -> ScopeType { + self.scope_type.clone() + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SymbolTable { + /// A stack each of whose elements represent a scope mapping `name: String` to `SymbolId`. + symbol_table_stack: Vec, + /// A list of all `Symbol`s with no explicit scope information. Indices are `SymbolId as usize`. + all_symbols: Vec, + /// A counter that is incremented after each new symbol is created. + symbol_id_counter: SymbolId, +} + +impl SymbolTable { + // This will be called if `include "stdgates.inc"` is encountered. At present we don't have any include guard. + // FIXME: This function allocates a vector. The caller iterates over the vector. + // Would be nice to return the `FlatMap` instead. I tried doing this, but it was super compilcated. + // The compiler helps with which trait to use as the return type. But then tons of bugs occur within + // the body. + /// Define gates in standard library "as if" a file of definitions (or declarations) had been read. + pub(crate) fn standard_library_gates(&mut self) -> Vec<&str> { + let g1q0p = ( + vec![ + "x", "y", "z", "h", "s", "sdg", "t", "tdg", "sx", /* 2.0 */ "id", + ], + [0, 1], + ); + let g1q1p = (vec!["p", "rx", "ry", "rz", /* 2.0 */ "phase", "u1"], [1, 1]); + let g1q2p = (vec![/* 2.0 */ "u2"], [2, 1]); + let g1q3p = (vec![/* 2.0 */ "u3"], [3, 1]); + let g2q0p = (vec!["cx", "cy", "cz", "ch", "swap", /* 2.0 */ "CX"], [0, 2]); + let g2q1p = (vec!["cp", "crx", "cry", "crz", /* 2.0 */ "cphase"], [1, 2]); + let g2q4p = (vec!["cu"], [4, 2]); + let g3q0p = (vec!["ccx", "cswap"], [0, 3]); + let all_gates = vec![g1q0p, g1q1p, g1q2p, g1q3p, g2q0p, g2q1p, g2q4p, g3q0p]; + // If `new_binding` returns `Err`, we push `name` onto a vector which will be + // used by the caller to record errors. Here flat_map and filter are used to + // select filter the names. + all_gates + .into_iter() + .flat_map(|(names, [n_cl, n_qu])| { + names + .into_iter() + .filter(|name| { + // The side effect of the test is important! + self.new_binding(name, &Type::Gate(n_cl, n_qu)).is_err() + }) + .collect::>() + }) + .collect::>() + } + + /// Return an iterator giving information about all gate declarations. Each element + /// is a tuple of (gate name, symbol id, num classical params, num quantum params). + /// `gphase` is not included here. It is treated specially. + /// `U` is also filtered out, as it is builtin. + pub fn gates(&self) -> impl Iterator { + self.all_symbols.iter().enumerate().filter_map(|(n, sym)| { + if let Type::Gate(num_cl, num_qu) = &sym.symbol_type() { + if sym.name() == "U" { + None + } else { + Some((sym.name(), SymbolId(n), *num_cl, *num_qu)) + } + } else { + None + } + }) + } + + /// Return a list of hardware qubits referenced in the program as a + /// `Vec` of 2-tuples. In each tuple the first item is the name and + /// the second is the `SymbolId`. + pub fn hardware_qubits(&self) -> Vec<(&str, SymbolId)> { + self.all_symbols + .iter() + .enumerate() + .filter_map(|(n, sym)| { + if let Type::HardwareQubit = &sym.symbol_type() { + Some((sym.name(), SymbolId(n))) + } else { + None + } + }) + .collect() + } +} + +#[allow(dead_code)] +impl SymbolTable { + /// Create a new `SymbolTable` and initialize with the global scope. + pub fn new() -> SymbolTable { + let mut symbol_table = SymbolTable { + symbol_id_counter: SymbolId::new(), + symbol_table_stack: Vec::::new(), + all_symbols: Vec::::new(), + }; + symbol_table.enter_scope(ScopeType::Global); + // Define global, built-in constants, and the single built-in gate + for const_name in ["pi", "π", "euler", "ℇ", "tau", "τ"] { + let _ = + symbol_table.new_binding(const_name, &Type::Float(Some(64), types::IsConst::True)); + } + let _ = symbol_table.new_binding("U", &Type::Gate(3, 1)); // U(a, b, c) q + symbol_table + } + + fn number_of_scopes(&self) -> usize { + self.symbol_table_stack.len() + } + + /// Enter a new scope of type `scope_type`. New bindings will occur in this + /// scope. This scope will be the first one searched when resolving symbols. + /// Certain symbols are excepted, such as gate names, which are always global. + pub(crate) fn enter_scope(&mut self, scope_type: ScopeType) { + if scope_type == ScopeType::Global && self.number_of_scopes() > 0 { + panic!("The unique global scope must be the first scope.") + } + self.symbol_table_stack.push(SymbolMap::new(scope_type)) + } + + /// Exit the current scope and return to the enclosing scope. + pub fn exit_scope(&mut self) { + // Trying to exit the global scope is a programming error. + assert!(self.symbol_table_stack.len() > 1); + self.symbol_table_stack.pop(); + } + + // Make a new binding without checking first whether a binding exists in + // this scope. + fn new_binding_no_check(&mut self, name: &str, typ: &Type) -> SymbolId { + // Create new symbol and symbol id. + // let symbol = Symbol::new(name, typ, ast_node); + let symbol = Symbol::new(name, typ); + + // Push the symbol onto list of all symbols (in all scopes). Index + // to this symbol will be `id_count`. + self.all_symbols.push(symbol); + + // The "current" SymbolId has not yet been unused. + // Get the current SymbolId and increment the counter. + let current_symbol_id = self.symbol_id_counter.post_increment(); + + // Map `name` to `symbol_id`. + self.current_scope_mut() + .insert(name, current_symbol_id.clone()); + current_symbol_id + } + + /// If a binding for `name` exists in the current scope, return `Err(SymbolError::AlreadyBound)`. + /// Otherwise, create a new Symbol from `name` and `typ`, bind `name` to + /// this Symbol in the current scope, and return the Symbol. + pub fn new_binding(&mut self, name: &str, typ: &Type) -> Result { + // Can't create a binding if it already exists in the current scope. + if self.current_scope_contains_name(name) { + return Err(SymbolError::AlreadyBound); + } + Ok(self.new_binding_no_check(name, typ)) + } + + // Symbol table for current (latest) scope in stack, mutable ref + fn current_scope_mut(&mut self) -> &mut SymbolMap { + self.symbol_table_stack.last_mut().unwrap() + } + + // Symbol table for current (latest) scope in stack, immutable ref + fn current_scope(&self) -> &SymbolMap { + self.symbol_table_stack.last().unwrap() + } + + /// Return the `ScopeType` of the current, or top-most, scope. + pub(crate) fn current_scope_type(&self) -> ScopeType { + self.current_scope().scope_type() + } + + pub(crate) fn in_global_scope(&self) -> bool { + self.current_scope_type() == ScopeType::Global + } + + // Return `true` if `name` is bound in current scope. + fn current_scope_contains_name(&self, name: &str) -> bool { + self.current_scope().contains_name(name) + } + + /// Return `true` if `name` is bound in all the scope. + pub fn all_scopes_contains_name(&self, name: &str) -> bool { + self.symbol_table_stack + .iter() + .any(|table| table.contains_name(name)) + } + + // /// Look up and return the `SymbolId` that `name` is bound to in the current scope. + // pub fn lookup_current_scope(&self, name: &str) -> Option<&SymbolId> { + // self.current_scope().get(name) + // } + + // FIXME: Can we make this private? + // This is only used in tests. The tests are in a different crate, so this function + // must be public. But it is not needed for anything other than tests. + /// Return the length (number of bindings) in the current scope. + pub fn len_current_scope(&self) -> usize { + self.current_scope().len() + } + + /// Look up `name` in the stack of symbol tables. Return `SymbolRecord` + /// if the symbol is found. Otherwise `Err(SymbolError::MissingBinding)`. + pub fn lookup(&self, name: &str) -> Result { + for table in self.symbol_table_stack.iter().rev() { + if let Some(symbol_id) = table.get_symbol_id(name) { + return Ok(SymbolRecord::new( + &self.all_symbols[symbol_id.0], + symbol_id.clone(), + )); + } + } + Err(SymbolError::MissingBinding) // `name` not found in any scope. + } + + /// Try to lookup `name`. If a binding is found return the `SymbolId`, otherwise create a new binding + /// and return the new `SymbolId`. + pub fn lookup_or_new_binding(&mut self, name: &str, typ: &Type) -> SymbolId { + match self.lookup(name) { + Ok(symbol_record) => symbol_record.symbol_id, + Err(_) => self.new_binding_no_check(name, typ), + } + } + + /// Simple dump of all symbols. This could be improved. + pub fn dump(&self) { + for (n, sym) in self.all_symbols.iter().enumerate() { + println!("{n} {:?}", sym); + } + } +} + +impl Default for SymbolTable { + fn default() -> Self { + Self::new() + } +} + +use std::ops::Index; +impl Index<&SymbolId> for SymbolTable { + type Output = Symbol; + + // Interface for retrieving `Symbol`s by `SymbolId`. + // Indexing into the symbol table with a `SymbolId` (which wraps an integer) + // returns the `Symbol`, which contains the name and type. + fn index(&self, symbol_id: &SymbolId) -> &Self::Output { + &self.all_symbols[symbol_id.0] + } +} From 319b0e89e7fc5ea0fca61f87b3acc9ff33103f08 Mon Sep 17 00:00:00 2001 From: to24toro Date: Wed, 15 Jan 2025 16:25:49 +0900 Subject: [PATCH 2/2] refactoring exporter and ast --- crates/qasm3/src/ast.rs | 28 +- crates/qasm3/src/exporter.rs | 1065 ++++++++++++++-------------------- 2 files changed, 448 insertions(+), 645 deletions(-) diff --git a/crates/qasm3/src/ast.rs b/crates/qasm3/src/ast.rs index d2b9361765cb..b64f01028ce3 100644 --- a/crates/qasm3/src/ast.rs +++ b/crates/qasm3/src/ast.rs @@ -90,11 +90,11 @@ impl Float { impl Display for Float { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let float_str = match self { - Float::Half => "16".to_string(), - Float::Single => "32".to_string(), - Float::Double => "64".to_string(), - Float::Quad => "128".to_string(), - Float::Oct => "256".to_string(), + Float::Half => "16", + Float::Single => "32", + Float::Double => "64", + Float::Quad => "128", + Float::Oct => "256", }; write!(f, "{}", float_str) } @@ -106,7 +106,6 @@ pub struct Int { } #[derive(Debug, Clone)] - pub struct Uint { pub size: Option, } @@ -159,7 +158,6 @@ pub struct Identifier { pub struct ClassicalDeclaration { pub type_: ClassicalType, pub identifier: Identifier, - // pub initializer: Option>, } #[derive(Debug, Clone)] @@ -196,10 +194,6 @@ pub enum Statement { QuantumMeasurementAssignment(QuantumMeasurementAssignment), Assignment(Assignment), QuantumGateDefinition(QuantumGateDefinition), - // Branch(Branch), - // ForLoop(ForLoop), - // WhileLoop(WhileLoop), - // Switch(Switch), Break(Break), Continue(Continue), } @@ -241,7 +235,6 @@ pub struct QuantumMeasurementAssignment { pub quantum_measurement: QuantumMeasurement, } -#[derive(Debug)] pub struct QuantumGateSignature { pub name: Identifier, pub qarg_list: Vec, @@ -351,6 +344,7 @@ pub enum OP<'a> { UnaryOp(&'a UnaryOp), BinaryOp(&'a BinaryOp), } + #[derive(Debug)] pub struct Unary { pub op: UnaryOp, @@ -366,12 +360,12 @@ pub enum UnaryOp { impl Display for UnaryOp { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let unit_str = match self { + let op_str = match self { UnaryOp::LogicNot => "!", UnaryOp::BitNot => "~", UnaryOp::Default => "", }; - write!(f, "{}", unit_str) + write!(f, "{}", op_str) } } @@ -394,7 +388,7 @@ pub enum BinaryOp { impl Display for BinaryOp { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let unit_str = match self { + let op_str = match self { BinaryOp::BitAnd => "&", BinaryOp::BitOr => "|", BinaryOp::BitXor => "^", @@ -409,7 +403,7 @@ impl Display for BinaryOp { BinaryOp::ShiftLeft => "<<", BinaryOp::ShiftRight => ">>", }; - write!(f, "{}", unit_str) + write!(f, "{}", op_str) } } @@ -435,4 +429,4 @@ pub struct Index { #[derive(Debug)] pub struct IndexSet { pub values: Vec, -} +} \ No newline at end of file diff --git a/crates/qasm3/src/exporter.rs b/crates/qasm3/src/exporter.rs index c139b7e71908..cbd3c3db8edb 100644 --- a/crates/qasm3/src/exporter.rs +++ b/crates/qasm3/src/exporter.rs @@ -7,6 +7,7 @@ use crate::ast::{ Reset, Statement, Version, }; use crate::printer::BasicPrinter; +use crate::symbols::SymbolError; use crate::symbols::SymbolTable; use hashbrown::{HashMap, HashSet}; use oq3_semantics::types::{IsConst, Type}; @@ -22,7 +23,7 @@ use thiserror::Error; use lazy_static::lazy_static; use regex::Regex; -type ExporterResult = std::result::Result; +type ExporterResult = Result; #[derive(Error, Debug)] pub enum QASM3ExporterError { @@ -30,8 +31,6 @@ pub enum QASM3ExporterError { Error(String), #[error("Symbol '{0}' is not found in the table")] SymbolNotFound(String), - #[error("Failed to rename symbol: {0}")] - RenameFailed(String), #[error("PyError: {0}")] PyErr(PyErr), #[error("Not in global scope")] @@ -44,15 +43,21 @@ impl From for QASM3ExporterError { } } +impl From for QASM3ExporterError { + fn from(err: SymbolError) -> Self { + QASM3ExporterError::Error("Symbol Table error: Missing binding or Already exists".to_string()) + } +} + lazy_static! { static ref GLOBAL_COUNTER: Mutex = Mutex::new(0); } fn get_next_counter_value() -> usize { let mut counter = GLOBAL_COUNTER.lock().unwrap(); - let value = *counter; + let val = *counter; *counter += 1; - value + val } lazy_static! { @@ -113,6 +118,42 @@ lazy_static! { static ref VALID_IDENTIFIER: Regex = Regex::new(r"(^[\w][\w\d]*$|^\$\d+$)").unwrap(); } +fn name_allowed(symbol_table: &SymbolTable, name: &str, unique: bool) -> bool { + if unique { + RESERVED_KEYWORDS.contains(name) || symbol_table.all_scopes_contains_name(name) + } else { + RESERVED_KEYWORDS.contains(name) + } +} + +fn escape_invalid_identifier( + symbol_table: &SymbolTable, + name: &str, + allow_rename: bool, + unique: bool, +) -> String { + let base = if allow_rename { + format!( + "_{}", + name.chars() + .map(|c| if c.is_alphanumeric() || c == '_' { + c + } else { + '_' + }) + .collect::() + ) + } else { + name.to_string() + }; + + let mut new_name = base.clone(); + while !name_allowed(symbol_table, &new_name, unique) { + new_name = format!("{}_{}", base, get_next_counter_value()); + } + new_name +} + pub struct Exporter { includes: Vec<&'static str>, basis_gates: Vec<&'static str>, @@ -131,7 +172,7 @@ impl Exporter { allow_aliasing: bool, indent: &'static str, ) -> Self { - Exporter { + Self { includes, basis_gates, disable_constants, @@ -141,12 +182,7 @@ impl Exporter { } } - pub fn dump( - &self, - circuit_data: &CircuitData, - islayout: bool, - ) -> String { - let mut stream = String::new(); + pub fn dump(&self, circuit_data: &CircuitData, islayout: bool) -> String { let mut builder = QASM3Builder::new( circuit_data, islayout, @@ -157,14 +193,16 @@ impl Exporter { ); match builder.build_program() { Ok(program) => { - BasicPrinter::new(&mut stream, self.indent.to_string(), false) + let mut output = String::new(); + BasicPrinter::new(&mut output, self.indent.to_string(), false) .visit(&Node::Program(&program)); - stream + output } Err(e) => format!("Error: {:?}", e), } } } + #[derive(Debug, Clone)] struct BuildScope<'a> { circuit_data: &'a CircuitData, @@ -174,37 +212,33 @@ struct BuildScope<'a> { impl<'a> BuildScope<'a> { fn new(circuit_data: &'a CircuitData) -> Self { - let qubit_map: HashMap = Python::with_gil(|py| { + let qubit_map = Python::with_gil(|py| { let qubits = circuit_data.qubits(); qubits .bits() .iter() .map(|bit| { let bound_bit = bit.bind(py); - let qubit = qubits.find(bound_bit).unwrap_or_else(|| { - panic!("Qubit not found"); - }); - (qubit, qubit) + let found = qubits.find(bound_bit).expect("Qubit not found"); + (found, found) }) .collect() }); - let clbit_map: HashMap = Python::with_gil(|py| { + let clbit_map = Python::with_gil(|py| { let clbits = circuit_data.clbits(); clbits .bits() .iter() .map(|bit| { let bound_bit = bit.bind(py); - let clbit = clbits.find(bound_bit).unwrap_or_else(|| { - panic!("Clbit not found"); - }); - (clbit, clbit) + let found = clbits.find(bound_bit).expect("Clbit not found"); + (found, found) }) .collect() }); - BuildScope { + Self { circuit_data, qubit_map, clbit_map, @@ -227,23 +261,23 @@ impl Iterator for Counter { type Item = usize; fn next(&mut self) -> Option { - let next_value = self.current; + let n = self.current; self.current = self.current.wrapping_add(1); - Some(next_value) + Some(n) } } pub struct QASM3Builder<'a> { - buildins: HashSet<&'static str>, + builtin_instr: HashSet<&'static str>, loose_bit_prefix: &'static str, loose_qubit_prefix: &'static str, - gate_parameter_prefix: &'static str, + gate_param_prefix: &'static str, gate_qubit_prefix: &'static str, circuit_scope: BuildScope<'a>, - islayout: bool, + is_layout: bool, symbol_table: SymbolTable, - global_io_declarations: Vec, - global_classical_forward_declarations: Vec, + global_io_decls: Vec, + global_classical_decls: Vec, includes: Vec<&'static str>, basis_gates: Vec<&'static str>, disable_constants: bool, @@ -254,14 +288,14 @@ pub struct QASM3Builder<'a> { impl<'a> QASM3Builder<'a> { pub fn new( circuit_data: &'a CircuitData, - islayout: bool, + is_layout: bool, includes: Vec<&'static str>, basis_gates: Vec<&'static str>, disable_constants: bool, allow_aliasing: bool, ) -> Self { - QASM3Builder { - buildins: [ + Self { + builtin_instr: [ "barrier", "measure", "reset", @@ -274,13 +308,13 @@ impl<'a> QASM3Builder<'a> { .collect(), loose_bit_prefix: "_bit", loose_qubit_prefix: "_qubit", - gate_parameter_prefix: "_gate_p", + gate_param_prefix: "_gate_p", gate_qubit_prefix: "_gate_q", circuit_scope: BuildScope::new(circuit_data), - islayout, + is_layout, symbol_table: SymbolTable::new(), - global_io_declarations: Vec::new(), - global_classical_forward_declarations: Vec::new(), + global_io_decls: Vec::new(), + global_classical_decls: Vec::new(), includes, basis_gates, disable_constants, @@ -289,276 +323,90 @@ impl<'a> QASM3Builder<'a> { } } - #[allow(dead_code)] - fn define_gate( - &mut self, - instruction: &PackedInstruction, - ) -> ExporterResult { - let operation = &instruction.op; - if operation.name() == "cx" && operation.num_qubits() == 2 { - let (control, target) = ( - Identifier { - string: "c".to_string(), - }, - Identifier { - string: "t".to_string(), - }, - ); - let call = GateCall { - quantum_gate_name: Identifier { - string: "U".to_string(), - }, - index_identifier_list: vec![control.clone(), target.clone()], - parameters: vec![ - Expression::Constant(Constant::PI), - Expression::IntegerLiteral(IntegerLiteral { value: 0 }), - Expression::Constant(Constant::PI), - ], - modifiers: Some(vec![QuantumGateModifier { - modifier: QuantumGateModifierName::Ctrl, - argument: None, - }]), - }; - - Ok(QuantumGateDefinition { - quantum_gate_signature: QuantumGateSignature { - name: Identifier { - string: "cx".to_string(), - }, - qarg_list: vec![control, target], - params: None, - }, - quantum_block: QuantumBlock { - statements: vec![Statement::QuantumInstruction(QuantumInstruction::GateCall( - call, - ))], - }, - }) - } else { - let qubit_map: HashMap = Python::with_gil(|py| { - let qubits = self.circuit_scope.circuit_data.qubits(); - qubits - .bits() - .iter() - .map(|bit| { - let bound_bit = bit.bind(py); - let qubit = qubits.find(bound_bit).unwrap_or_else(|| { - panic!("Qubit not found"); - }); - (qubit, qubit) - }) - .collect() - }); - - let clbit_map: HashMap = Python::with_gil(|py| { - let clbits = self.circuit_scope.circuit_data.clbits(); - clbits - .bits() - .iter() - .map(|bit| { - let bound_bit = bit.bind(py); - let clbit = clbits.find(bound_bit).unwrap_or_else(|| { - panic!("Clbit not found"); - }); - (clbit, clbit) - }) - .collect() - }); - - let operation_def_owned = operation.definition(instruction.params_view()).ok_or( - QASM3ExporterError::Error("Failed to get operation definition".to_string()), - )?; - - let body = { - let tmp_scope = BuildScope { - circuit_data: unsafe { std::mem::transmute::<&_, &'a _>(&operation_def_owned) }, - qubit_map, - clbit_map, - }; - - let old_scope = std::mem::replace(&mut self.circuit_scope, tmp_scope); - let body = QuantumBlock { - statements: self.build_current_scope()?, - }; - self.circuit_scope = old_scope; - body - }; - - let defn = &operation_def_owned; - let params: Vec = (0..defn.num_parameters()) - .map(|i| { - let name = format!("{}_{}", self.gate_parameter_prefix, i); - let symbol = if self - .symbol_table - .new_binding(&name, &Type::Float(Some(64), IsConst::False)) - .is_ok() - { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - } else { - let rename = - escape_invalid_identifier(&self.symbol_table, &name, true, false); - if self - .symbol_table - .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) - .is_err() - { - return Err(QASM3ExporterError::RenameFailed(name)); - } + pub fn build_program(&mut self) -> ExporterResult { + self.register_basis_gates(); + let header = self.build_header(); - self.symbol_table - .lookup(&rename) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - }; - Ok(Expression::Parameter(Parameter { - obj: symbol.symbol().name().to_string(), - })) - }) - .collect::>>()?; + self.hoist_global_params()?; + self.hoist_classical_bits()?; + let qubit_decls = self.build_qubit_decls()?; + let main_stmts = self.build_top_level_stmts()?; - let qubits: Vec = (0..defn.num_parameters()) - .map(|i| { - let name = format!("{}_{}", self.gate_qubit_prefix, i); - let symbol = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - } else { - let rename = - escape_invalid_identifier(&self.symbol_table, &name, true, false); - if self - .symbol_table - .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) - .is_err() - { - return Err(QASM3ExporterError::RenameFailed(name)); - } + let mut all_stmts = Vec::new(); + for decl in &self.global_io_decls { + all_stmts.push(Statement::IODeclaration(decl.clone())); + } + for decl in &self.global_classical_decls { + all_stmts.push(Statement::ClassicalDeclaration(decl.clone())); + } + for decl in qubit_decls { + all_stmts.push(Statement::QuantumDeclaration(decl)); + } + all_stmts.extend(main_stmts); - self.symbol_table - .lookup(&rename) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - }; + Ok(Program { + header, + statements: all_stmts, + }) + } - Ok(Identifier { - string: symbol.symbol().name().to_string(), - }) - }) - .collect::>>()?; - if self - .symbol_table - .new_binding( - operation.name(), - &Type::Gate( - operation.num_clbits() as usize, - operation.num_qubits() as usize, - ), - ) - .is_ok() - {} - Ok(QuantumGateDefinition { - quantum_gate_signature: QuantumGateSignature { - name: Identifier { - string: operation.name().to_string(), - }, - qarg_list: qubits, - params: Some(params), - }, - quantum_block: body, - }) + fn register_basis_gates(&mut self) { + for gate in &self.basis_gates { + let _ = self.symbol_table.new_binding(gate, &Type::Gate(0, 1)); } } - pub fn build_program(&mut self) -> ExporterResult { - for builtin in self.basis_gates.iter() { - let _ = self.symbol_table.new_binding(builtin, &Type::Gate(0, 1)); - } - let header = Header { + fn build_header(&mut self) -> Header { + let includes = self + .includes + .iter() + .map(|fname| { + if *fname == "stdgates.inc" { + self.symbol_table.standard_library_gates(); + } + Include { + filename: fname.to_string(), + } + }) + .collect(); + Header { version: Some(Version { version_number: "3.0".to_string(), }), - includes: self.build_includes(), - }; - let _ = self.hoist_global_parameter_declaration(); - let _ = self.hoist_classical_register_declarations(); - let qubit_declarations = self.build_quantum_declarations()?; - - let main_statements = self.build_current_scope()?; - - let mut statements = Vec::new(); - for global_io_declaration in self.global_io_declarations.iter() { - statements.push(Statement::IODeclaration(global_io_declaration.to_owned())); - } - for global_classical_forward_declaration in - self.global_classical_forward_declarations.iter() - { - statements.push(Statement::ClassicalDeclaration( - global_classical_forward_declaration.to_owned(), - )); - } - for qubit_declaration in qubit_declarations.into_iter() { - statements.push(Statement::QuantumDeclaration(qubit_declaration)); - } - statements.extend(main_statements); - Ok(Program { header, statements }) - } - - fn build_includes(&mut self) -> Vec { - let mut includes = Vec::new(); - for filename in &self.includes { - if *filename == "stdgates.inc" { - self.symbol_table.standard_library_gates(); - } - includes.push(Include { - filename: filename.to_string(), - }); + includes, } - includes } fn assert_global_scope(&self) -> ExporterResult<()> { if !self.symbol_table.in_global_scope() { - drop(QASM3ExporterError::NotInGlobalScopeError); + return Err(QASM3ExporterError::NotInGlobalScopeError); } Ok(()) } - fn hoist_global_parameter_declaration(&mut self) -> ExporterResult<()> { - let _ = self.assert_global_scope(); - let circuit_data = self.circuit_scope.circuit_data; - + fn hoist_global_params(&mut self) -> ExporterResult<()> { + self.assert_global_scope()?; Python::with_gil(|py| { - for parameter in circuit_data.get_parameters(py) { - let name: String = parameter.getattr("name")?.extract()?; - - let parameter = if self + for param in self.circuit_scope.circuit_data.get_parameters(py) { + let raw_name: String = param.getattr("name")?.extract()?; + let sym = match self .symbol_table - .new_binding(&name, &Type::Float(Some(64), IsConst::False)) - .is_ok() + .new_binding(&raw_name, &Type::Float(Some(64), IsConst::False)) { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) - } else { - let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); - if self - .symbol_table - .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) - .is_err() - { - return Err(QASM3ExporterError::RenameFailed(name)); + Ok(_) => self.symbol_table.lookup(&raw_name), + Err(_) => { + let rename = + escape_invalid_identifier(&self.symbol_table, &raw_name, true, false); + self.symbol_table + .new_binding(&rename, &Type::Float(Some(64), IsConst::False))?; + self.symbol_table.lookup(&rename) } - - self.symbol_table - .lookup(&rename) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) - }; - - self.global_io_declarations.push(IODeclaration { + }?; + self.global_io_decls.push(IODeclaration { modifier: IOModifier::Input, type_: ClassicalType::Float(Float::Double), identifier: Identifier { - string: parameter?.symbol().name().to_string(), + string: sym.symbol().name().to_string(), }, }); } @@ -566,409 +414,370 @@ impl<'a> QASM3Builder<'a> { }) } + fn hoist_classical_bits(&mut self) -> ExporterResult<()> { + self.assert_global_scope()?; + let num_clbits = self.circuit_scope.circuit_data.num_clbits(); + let mut decls = Vec::new(); + + for i in 0..num_clbits { + let raw_name = format!("{}{}", self.loose_bit_prefix, i); + let sym = match self + .symbol_table + .new_binding(&raw_name, &Type::Bit(IsConst::False)) + { + Ok(_) => self.symbol_table.lookup(&raw_name), + Err(_) => { + let rename = + escape_invalid_identifier(&self.symbol_table, &raw_name, true, false); + self.symbol_table + .new_binding(&rename, &Type::Bit(IsConst::False))?; + self.symbol_table.lookup(&rename) + } + }?; + decls.push(ClassicalDeclaration { + type_: ClassicalType::Bit, + identifier: Identifier { + string: sym.symbol().name().to_string(), + }, + }); + } + self.global_classical_decls.extend(decls); + Ok(()) + } + + fn build_qubit_decls(&mut self) -> ExporterResult> { + self.assert_global_scope()?; + let num_qubits = self.circuit_scope.circuit_data.num_qubits(); + let mut decls = Vec::new(); + + if self.is_layout { + self.loose_qubit_prefix = "$"; + } + for i in 0..num_qubits { + let raw_name = format!("{}{}", self.loose_qubit_prefix, i); + let sym = match self.symbol_table.new_binding(&raw_name, &Type::Qubit) { + Ok(_) => self.symbol_table.lookup(&raw_name), + Err(_) => { + if self.is_layout { + // If layout, return error directly + return Err(QASM3ExporterError::SymbolNotFound(raw_name)); + } + let rename = + escape_invalid_identifier(&self.symbol_table, &raw_name, true, false); + self.symbol_table.new_binding(&rename, &Type::Qubit)?; + self.symbol_table.lookup(&rename) + } + }?; + decls.push(QuantumDeclaration { + identifier: Identifier { + string: sym.symbol().name().to_string(), + }, + designator: None, + }); + } + Ok(decls) + } + + fn build_top_level_stmts(&mut self) -> ExporterResult> { + let mut stmts = Vec::new(); + for instr in self.circuit_scope.circuit_data.data() { + self.build_instruction(instr, &mut stmts)?; + } + Ok(stmts) + } + fn build_instruction( &mut self, instruction: &PackedInstruction, - statements: &mut Vec, + stmts: &mut Vec, ) -> ExporterResult<()> { - let op_name = instruction.op.name(); + let name = instruction.op.name(); + if instruction.op.control_flow() { - if op_name == "for_loop" { - return Err(QASM3ExporterError::Error(format!( - "Unsupported Python interface of control flow condition: {}", - op_name - ))); - } else if op_name == "while_loop" { - return Err(QASM3ExporterError::Error(format!( - "Unsupported Python interface of control flow condition: {}", - op_name - ))); - } else if op_name == "if_else" { - return Err(QASM3ExporterError::Error(format!( - "Unsupported Python interface of control flow condition: {}", - op_name - ))); - } else if op_name == "switch_case" { - return Err(QASM3ExporterError::Error(format!( - "Unsupported Python interface of control flow condition: {}", - op_name - ))); - } else { - return Err(QASM3ExporterError::Error(format!( - "Unsupported Python interface of control flow condition: {}", - op_name - ))); + return Err(QASM3ExporterError::Error(format!( + "Control flow is not supported: {}", + name + ))); + } + + match name { + "barrier" => self.handle_barrier(instruction, stmts), + "measure" => self.handle_measure(instruction, stmts), + "reset" => self.handle_reset(instruction, stmts), + "delay" => self.handle_delay(instruction, stmts), + "break_loop" => { + stmts.push(Statement::Break(Break {})); + Ok(()) } - } else { - let qubits = self - .circuit_scope - .circuit_data - .qargs_interner() - .get(instruction.qubits); - if instruction.op.name() == "barrier" { - let mut barrier_qubits = Vec::new(); - for qubit in qubits { - let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); - let qubit_symbol = self - .symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; - barrier_qubits.push(Identifier { - string: qubit_symbol.symbol().name().to_string(), - }); - } - statements.push(Statement::QuantumInstruction(QuantumInstruction::Barrier( - Barrier { - index_identifier_list: barrier_qubits, - }, - ))); - } else if instruction.op.name() == "measure" { - let mut measured_qubits = Vec::new(); - for qubit in qubits { - let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); - let qubit_symbol = self - .symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; - measured_qubits.push(Identifier { - string: qubit_symbol.symbol().name().to_string(), - }); - } - let measurement = QuantumMeasurement { - identifier_list: measured_qubits, - }; - let clbits = self - .circuit_scope - .circuit_data - .cargs_interner() - .get(instruction.clbits); - let name = format!("{}{}", self.loose_bit_prefix, clbits[0].0); - let clbit_symbol = self - .symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; - statements.push(Statement::QuantumMeasurementAssignment( - QuantumMeasurementAssignment { - identifier: Identifier { - string: clbit_symbol.symbol().name().to_string(), - }, - quantum_measurement: measurement, - }, - )); - } else if instruction.op.name() == "reset" { - for qubit in qubits { - let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); - let qubit_symbol = self - .symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; - statements.push(Statement::QuantumInstruction(QuantumInstruction::Reset( - Reset { - identifier: Identifier { - string: qubit_symbol.symbol().name().to_string(), - }, - }, - ))); - } - } else if instruction.op.name() == "delay" { - let delay = self.build_delay(instruction)?; - statements.push(Statement::QuantumInstruction(QuantumInstruction::Delay( - delay, - ))); - } else if instruction.op.name() == "break_loop" { - statements.push(Statement::Break(Break {})); - } else if instruction.op.name() == "continue_loop" { - statements.push(Statement::Continue(Continue {})); - } else if instruction.op.name() == "store" { + "continue_loop" => { + stmts.push(Statement::Continue(Continue {})); + Ok(()) + } + "store" => { panic!("Store is not yet supported"); - } else { - statements.push(Statement::QuantumInstruction(QuantumInstruction::GateCall( - self.build_gate_call(instruction)?, + } + _ => { + let gate_call = self.build_gate_call(instruction)?; + stmts.push(Statement::QuantumInstruction(QuantumInstruction::GateCall( + gate_call, ))); + Ok(()) } } - Ok(()) } - fn build_gate_call(&mut self, instruction: &PackedInstruction) -> ExporterResult { - let gate_identifier = if self - .symbol_table - .standard_library_gates() - .contains(&instruction.op.name()) - { - Identifier { - string: instruction.op.name().to_string(), - } - } else { - panic!( - "Non-standard gate calls are not yet supported, but received: {}", - instruction.op.name() - ); - }; - let qubits = self + fn handle_barrier( + &mut self, + instr: &PackedInstruction, + stmts: &mut Vec, + ) -> ExporterResult<()> { + let qargs = self .circuit_scope .circuit_data .qargs_interner() - .get(instruction.qubits); - let parameters: Vec = if self.disable_constants { - if instruction.params_view().is_empty() { - Vec::::new() - } else { - instruction - .params_view() - .iter() - .map(|param| match param { - Param::Float(value) => Expression::Parameter(Parameter { - obj: value.to_string(), - }), - Param::ParameterExpression(_) => { - panic!("Parameter expressions are not yet supported") - } - Param::Obj(_) => panic!("Objects are not yet supported"), - }) - .collect() - } - } else { - panic!("'disable_constant = true' are not yet supported"); - }; - - let index_identifier_list: Vec = qubits - .iter() - .map(|qubit| { - let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name)) - .map(|qubit_symbol| Identifier { - string: qubit_symbol.symbol().name().to_string(), - }) - }) - .collect::>>()?; - - Ok(GateCall { - quantum_gate_name: gate_identifier, - index_identifier_list, - parameters, - modifiers: None, - }) - } - - fn hoist_classical_register_declarations(&mut self) -> ExporterResult<()> { - let _ = self.assert_global_scope(); - let circuit_data = self.circuit_scope.circuit_data; - let mut classical_declarations: Vec = Vec::new(); - for i in 0..circuit_data.num_clbits() { - let name = format!("{}{}", self.loose_bit_prefix, i); - let clbit = if self - .symbol_table - .new_binding(&name, &Type::Bit(IsConst::False)) - .is_ok() - { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - } else { - let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); - if self - .symbol_table - .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) - .is_err() - { - return Err(QASM3ExporterError::RenameFailed(rename)); - } - - self.symbol_table - .lookup(&rename) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - }; - - classical_declarations.push(ClassicalDeclaration { - type_: ClassicalType::Bit, - identifier: Identifier { - string: clbit.symbol().name().to_string(), - }, + .get(instr.qubits); + let mut qubit_ids = Vec::new(); + for q in qargs { + let name = format!("{}{}", self.loose_qubit_prefix, q.0); + let symbol = self.symbol_table.lookup(&name)?; + qubit_ids.push(Identifier { + string: symbol.symbol().name().to_string(), }); } - self.global_classical_forward_declarations - .extend(classical_declarations); + stmts.push(Statement::QuantumInstruction(QuantumInstruction::Barrier( + Barrier { + index_identifier_list: qubit_ids, + }, + ))); Ok(()) } - fn build_quantum_declarations(&mut self) -> ExporterResult> { - let _ = self.assert_global_scope(); - let circuit_data = self.circuit_scope.circuit_data; - let mut qubit_declarations: Vec = Vec::new(); - if self.islayout { - self.loose_qubit_prefix = "$"; - for i in 0..circuit_data.num_qubits() { - let name = format!("{}{}", self.loose_qubit_prefix, i); - let qubit = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - } else { - return Err(QASM3ExporterError::SymbolNotFound(name)); - }; - - qubit_declarations.push(QuantumDeclaration { - identifier: Identifier { - string: qubit.symbol().name().to_string(), - }, - designator: None, - }); - } - return Ok(qubit_declarations); + fn handle_measure( + &mut self, + instr: &PackedInstruction, + stmts: &mut Vec, + ) -> ExporterResult<()> { + let qargs = self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instr.qubits); + let mut qubits = Vec::new(); + for q in qargs { + let name = format!("{}{}", self.loose_qubit_prefix, q.0); + let symbol = self.symbol_table.lookup(&name)?; + qubits.push(Identifier { + string: symbol.symbol().name().to_string(), + }); } - for i in 0..circuit_data.num_qubits() { - let name = format!("{}{}", self.loose_qubit_prefix, i); - let qubit = if self.symbol_table.new_binding(&name, &Type::Qubit).is_ok() { - self.symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - } else { - let rename = escape_invalid_identifier(&self.symbol_table, &name, true, false); - if self - .symbol_table - .new_binding(&rename, &Type::Float(Some(64), IsConst::False)) - .is_err() - { - return Err(QASM3ExporterError::RenameFailed(rename)); - } - - self.symbol_table - .lookup(&rename) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))? - }; + let measurement = QuantumMeasurement { + identifier_list: qubits, + }; - qubit_declarations.push(QuantumDeclaration { + let cargs = self + .circuit_scope + .circuit_data + .cargs_interner() + .get(instr.clbits); + let c_name = format!("{}{}", self.loose_bit_prefix, cargs[0].0); + let clbit_symbol = self.symbol_table.lookup(&c_name)?; + stmts.push(Statement::QuantumMeasurementAssignment( + QuantumMeasurementAssignment { identifier: Identifier { - string: qubit.symbol().name().to_string(), + string: clbit_symbol.symbol().name().to_string(), }, - designator: None, - }); - } - Ok(qubit_declarations) + quantum_measurement: measurement, + }, + )); + Ok(()) } - fn build_current_scope(&mut self) -> ExporterResult> { - let mut statements: Vec = Vec::::new(); - for instruction in self.circuit_scope.circuit_data.data() { - self.build_instruction(instruction, &mut statements)?; + fn handle_reset( + &mut self, + instr: &PackedInstruction, + stmts: &mut Vec, + ) -> ExporterResult<()> { + let qargs = self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instr.qubits); + for q in qargs { + let name = format!("{}{}", self.loose_qubit_prefix, q.0); + let sym = self.symbol_table.lookup(&name)?; + stmts.push(Statement::QuantumInstruction(QuantumInstruction::Reset( + Reset { + identifier: Identifier { + string: sym.symbol().name().to_string(), + }, + }, + ))); } - Ok(statements) + Ok(()) } - fn build_delay(&self, instruction: &PackedInstruction) -> ExporterResult { - if instruction.op.num_clbits() > 0 { + fn handle_delay( + &self, + instr: &PackedInstruction, + stmts: &mut Vec, + ) -> ExporterResult<()> { + if instr.op.num_clbits() > 0 { return Err(QASM3ExporterError::Error( - "Delay instruction cannot have classical bits".to_string(), + "Delay cannot have classical bits".to_string(), )); } + let delay = self.build_delay(instr)?; + stmts.push(Statement::QuantumInstruction(QuantumInstruction::Delay( + delay, + ))); + Ok(()) + } + fn build_delay(&self, instr: &PackedInstruction) -> ExporterResult { let duration_value = Python::with_gil(|py| { - if let Some(duration) = instruction.extra_attrs.duration() { - match duration.bind(py).extract::() { - Ok(value) => Ok(value), - Err(_) => Err(QASM3ExporterError::Error( - "Failed to extract duration value".to_string(), - )), - } - } else { - Err(QASM3ExporterError::Error( - "Failed to extract duration value".to_string(), - )) - } + let dur = instr.extra_attrs.duration().ok_or_else(|| { + QASM3ExporterError::Error("Failed to extract duration".to_string()) + })?; + dur.bind(py).extract::().map_err(|_| { + QASM3ExporterError::Error("Failed to extract duration value".to_string()) + }) })?; - let default_duration_unit = "us"; - let duration_unit = instruction - .extra_attrs - .unit() - .unwrap_or(default_duration_unit); - let duration_literal = if duration_unit == "ps" { + let default_unit = "us"; + let unit = instr.extra_attrs.unit().unwrap_or(default_unit); + + let mut map = HashMap::new(); + map.insert("ns", DurationUnit::Nanosecond); + map.insert("us", DurationUnit::Microsecond); + map.insert("ms", DurationUnit::Millisecond); + map.insert("s", DurationUnit::Second); + map.insert("dt", DurationUnit::Sample); + + let duration_literal = if unit == "ps" { DurationLiteral { value: duration_value * 1000.0, unit: DurationUnit::Nanosecond, } } else { - let unit_map: HashMap<&str, DurationUnit> = HashMap::from([ - ("ns", DurationUnit::Nanosecond), - ("us", DurationUnit::Microsecond), - ("ms", DurationUnit::Millisecond), - ("s", DurationUnit::Second), - ("dt", DurationUnit::Sample), - ]); - - match unit_map.get(duration_unit) { - Some(mapped_unit) => DurationLiteral { - value: duration_value, - unit: mapped_unit.clone(), - }, - None => { - return Err(QASM3ExporterError::Error(format!( - "Unknown duration unit: {}", - duration_unit - ))) - } + let found = map.get(unit).ok_or_else(|| { + QASM3ExporterError::Error(format!("Unknown duration unit: {}", unit)) + })?; + DurationLiteral { + value: duration_value, + unit: found.clone(), } }; let mut qubits = Vec::new(); - - for qubit in self + let qargs = self .circuit_scope .circuit_data .qargs_interner() - .get(instruction.qubits) - { - let name = format!("{}{}", self.loose_qubit_prefix, qubit.0); - let qubit_symbol = self - .symbol_table - .lookup(&name) - .map_err(|_| QASM3ExporterError::SymbolNotFound(name))?; + .get(instr.qubits); + for q in qargs { + let name = format!("{}{}", self.loose_qubit_prefix, q.0); + let sym = self.symbol_table.lookup(&name)?; qubits.push(Identifier { - string: qubit_symbol.symbol().name().to_string(), + string: sym.symbol().name().to_string(), }); } - Ok(Delay { duration: duration_literal, qubits, }) } -} -fn name_allowed(symbol_table: &SymbolTable, name: &str, unique: bool) -> bool { - if unique { - RESERVED_KEYWORDS.contains(name) || symbol_table.all_scopes_contains_name(name) - } else { - RESERVED_KEYWORDS.contains(name) + fn build_gate_call(&mut self, instr: &PackedInstruction) -> ExporterResult { + let op_name = instr.op.name(); + if !self.symbol_table.standard_library_gates().contains(&op_name) + && !self.symbol_table.all_scopes_contains_name(op_name) + { + panic!("Non-standard gate calls are not yet supported: {}", op_name); + } + let params = if self.disable_constants { + instr + .params_view() + .iter() + .map(|param| match param { + Param::Float(val) => Expression::Parameter(Parameter { + obj: val.to_string(), + }), + Param::ParameterExpression(_) => { + panic!("Parameter expressions not supported yet") + } + Param::Obj(_) => panic!("Objects not supported yet"), + }) + .collect() + } else { + panic!("Constant parameters not supported in this sample"); + }; + + let qargs = self + .circuit_scope + .circuit_data + .qargs_interner() + .get(instr.qubits); + let mut qubit_ids = Vec::new(); + for q in qargs { + let name = format!("{}{}", self.loose_qubit_prefix, q.0); + let sym = self.symbol_table.lookup(&name)?; + qubit_ids.push(Identifier { + string: sym.symbol().name().to_string(), + }); + } + + Ok(GateCall { + quantum_gate_name: Identifier { + string: op_name.to_string(), + }, + index_identifier_list: qubit_ids, + parameters: params, + modifiers: None, + }) } -} -fn escape_invalid_identifier( - symbol_table: &SymbolTable, - name: &str, - allow_rename: bool, - unique: bool, -) -> String { - let base = if allow_rename { - format!( - "_{}", - name.chars() - .map(|c| if c.is_alphanumeric() || c == '_' { - c - } else { - '_' - }) - .collect::() - ) - } else { - name.to_string() - }; - let mut updated_name = base.clone(); - while !name_allowed(symbol_table, &base, unique) { - updated_name = format!("{}_{}", base, get_next_counter_value()); + #[allow(dead_code)] + fn define_gate(&mut self, instr: &PackedInstruction) -> ExporterResult { + let operation = &instr.op; + if operation.name() == "cx" && operation.num_qubits() == 2 { + let ctrl_id = Identifier { + string: "c".to_string(), + }; + let tgt_id = Identifier { + string: "t".to_string(), + }; + let call = GateCall { + quantum_gate_name: Identifier { + string: "U".to_string(), + }, + index_identifier_list: vec![ctrl_id.clone(), tgt_id.clone()], + parameters: vec![ + Expression::Constant(Constant::PI), + Expression::IntegerLiteral(IntegerLiteral { value: 0 }), + Expression::Constant(Constant::PI), + ], + modifiers: Some(vec![QuantumGateModifier { + modifier: QuantumGateModifierName::Ctrl, + argument: None, + }]), + }; + return Ok(QuantumGateDefinition { + quantum_gate_signature: QuantumGateSignature { + name: Identifier { + string: "cx".to_string(), + }, + qarg_list: vec![ctrl_id, tgt_id], + params: None, + }, + quantum_block: QuantumBlock { + statements: vec![Statement::QuantumInstruction(QuantumInstruction::GateCall( + call, + ))], + }, + }); + } + Err(QASM3ExporterError::Error( + "Custom gate definition not supported in this sample".to_string(), + )) } - updated_name }