Skip to content

Commit

Permalink
Begin work on new OpenQASM 3 importer
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelishman committed Jan 17, 2024
1 parent 7039037 commit 549edb1
Show file tree
Hide file tree
Showing 11 changed files with 1,740 additions and 14 deletions.
606 changes: 593 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions crates/qasm3/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "qiskit-qasm3"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[lib]
name = "qiskit_qasm3"
crate-type = ["cdylib"]

[features]
# This is a test-only shim removable feature. See the root `Cargo.toml`.
default = ["extension-module"]
extension-module = ["pyo3/extension-module"]

[dependencies]
pyo3.workspace = true
hashbrown.version = "0.14.0"
oq3_semantics = { git = "https://github.com/Qiskit/openqasm3_parser.git", rev = "57407ee" }
4 changes: 4 additions & 0 deletions crates/qasm3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# `qiskit._qasm3`

This crate is the Rust-level Qiskit interface to an OpenQASM 3 parser. The parser itself does not know
about Qiskit, and this crate interfaces with it in a Qiskit-specific manner to produce `QuantumCircuit`s.
330 changes: 330 additions & 0 deletions crates/qasm3/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2024
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use pyo3::prelude::*;
use pyo3::types::{PyString, PyTuple};

use hashbrown::HashMap;

use oq3_semantics::asg;
use oq3_semantics::symbols::{SymbolId, SymbolTable, SymbolType};
use oq3_semantics::types::{ArrayDims, Type};

use crate::circuit::{PyCircuit, PyCircuitModule, PyClassicalRegister, PyGate, PyQuantumRegister};
use crate::error::QASM3ImporterError;
use crate::expr;

/// Our internal symbol table mapping base symbols to the Python-space object that represents them.
#[derive(Default)]
pub struct PySymbolTable {
/// Gate-constructor objects.
pub gates: HashMap<SymbolId, PyGate>,
/// Scalar `Qubit` objects.
pub qubits: HashMap<SymbolId, Py<PyAny>>,
/// Scalar `Clbit` objects.
pub clbits: HashMap<SymbolId, Py<PyAny>>,
/// `QuantumRegister` objects.
pub qregs: HashMap<SymbolId, PyQuantumRegister>,
/// `ClassicalRegister` objects.
pub cregs: HashMap<SymbolId, PyClassicalRegister>,
}

struct BuilderState {
/// The base circuit under construction.
qc: PyCircuit,
/// Symbol table mapping AST symbols into typed Python / Rust objects. This is owned state; we
/// mutate it and build it up as we parse the AST.
symbols: PySymbolTable,
/// Handle to the constructor object for Python-space objects.
module: PyCircuitModule,
/// Constructors for gate objects.
pygates: HashMap<String, PyGate>,
}

impl BuilderState {
fn declare_classical(
&mut self,
py: Python,
ast_symbols: &SymbolTable,
decl: &asg::DeclareClassical,
) -> PyResult<()> {
let name_id = decl
.name()
.as_ref()
.map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?;
let name_symbol = &ast_symbols[name_id];
match name_symbol.symbol_type() {
Type::Bit(is_const) => {
if is_const.clone().into() {
Err(QASM3ImporterError::new_err("cannot handle consts"))
} else if decl.initializer().is_some() {
Err(QASM3ImporterError::new_err(
"cannot handle initialised bits",
))
} else {
self.add_clbit(py, name_id.clone())
}
}
Type::BitArray(dims, is_const) => {
if is_const.clone().into() {
Err(QASM3ImporterError::new_err("cannot handle consts"))
} else if decl.initializer().is_some() {
Err(QASM3ImporterError::new_err(
"cannot handle initialised registers",
))
} else {
match dims {
ArrayDims::D1(size) => {
self.add_creg(py, name_id.clone(), name_symbol.name(), *size)
}
_ => Err(QASM3ImporterError::new_err(
"cannot handle quantum registers with more than one dimension",
)),
}
}
}
ty => Err(QASM3ImporterError::new_err(format!(
"unhandled classical type: {:?}",
ty,
))),
}
}

fn declare_quantum(
&mut self,
py: Python,
ast_symbols: &SymbolTable,
decl: &asg::DeclareQuantum,
) -> PyResult<()> {
let name_id = decl
.name()
.as_ref()
.map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?;
let name_symbol = &ast_symbols[name_id];
match name_symbol.symbol_type() {
Type::Qubit => self.add_qubit(py, name_id.clone()),
Type::QubitArray(dims) => match dims {
ArrayDims::D1(size) => {
self.add_qreg(py, name_id.clone(), name_symbol.name(), *size)
}
_ => Err(QASM3ImporterError::new_err(
"cannot handle quantum registers with more than one dimension",
)),
},
_ => unreachable!(),
}
}

fn call_gate(
&mut self,
py: Python,
ast_symbols: &SymbolTable,
call: &asg::GateCall,
) -> PyResult<()> {
if call.modifier().is_some() {
return Err(QASM3ImporterError::new_err("gate modifiers not handled"));
}
let gate_id = call
.name()
.as_ref()
.map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?;
let gate = self.symbols.gates.get(gate_id).ok_or_else(|| {
QASM3ImporterError::new_err(format!("internal logic error: unknown gate {:?}", gate_id))
})?;
let params = PyTuple::new(
py,
call.params()
.as_ref()
.map(|params| params as &[asg::TExpr])
.unwrap_or_default()
.iter()
.map(|param| expr::eval_gate_param(py, &self.symbols, ast_symbols, param))
.collect::<PyResult<Vec<_>>>()?,
);
let qargs = call.qubits();
if params.len() != gate.num_params() {
return Err(QASM3ImporterError::new_err(format!(
"incorrect number of params to '{}': expected {}, got {}",
gate.name(),
gate.num_params(),
params.len(),
)));
}
if qargs.len() != gate.num_qubits() {
return Err(QASM3ImporterError::new_err(format!(
"incorrect number of quantum arguments to '{}': expected {}, got {}",
gate.name(),
gate.num_qubits(),
qargs.len(),
)));
}
let gate_instance = gate.construct(py, params)?;
for qubits in expr::broadcast_qubits(py, &self.symbols, ast_symbols, qargs)? {
self.qc.append(
py,
self.module
.new_instruction(py, gate_instance.clone_ref(py), qubits, ())?,
)?;
}
Ok(())
}

fn define_gate(
&mut self,
_py: Python,
ast_symbols: &SymbolTable,
decl: &asg::GateDeclaration,
) -> PyResult<()> {
let name_id = decl
.name()
.as_ref()
.map_err(|err| QASM3ImporterError::new_err(format!("internal error {:?}", err)))?;
let name_symbol = &ast_symbols[name_id];
let pygate = self.pygates.get(name_symbol.name()).ok_or_else(|| {
QASM3ImporterError::new_err(format!(
"can't handle non-built-in gate: '{}'",
name_symbol.name()
))
})?;
let defined_num_params = decl.params().as_ref().map_or(0, Vec::len);
let defined_num_qubits = decl.qubits().len();
if pygate.num_params() != defined_num_params {
return Err(QASM3ImporterError::new_err(format!(
"given constructor for '{}' expects {} parameters, but is defined as taking {}",
pygate.name(),
pygate.num_params(),
defined_num_params,
)));
}
if pygate.num_qubits() != defined_num_qubits {
return Err(QASM3ImporterError::new_err(format!(
"given constructor for '{}' expects {} qubits, but is defined as taking {}",
pygate.name(),
pygate.num_qubits(),
defined_num_qubits,
)));
}
self.symbols.gates.insert(name_id.clone(), pygate.clone());
Ok(())
}

fn add_qubit(&mut self, py: Python, ast_symbol: SymbolId) -> PyResult<()> {
let qubit = self.module.new_qubit(py)?;
if self
.symbols
.qubits
.insert(ast_symbol, qubit.clone_ref(py))
.is_some()
{
panic!("internal logic error: attempted to add the same qubit multiple times")
}
self.qc.add_qubit(py, qubit)
}

fn add_clbit(&mut self, py: Python, ast_symbol: SymbolId) -> PyResult<()> {
let clbit = self.module.new_clbit(py)?;
if self
.symbols
.clbits
.insert(ast_symbol, clbit.clone_ref(py))
.is_some()
{
panic!("internal logic error: attempted to add the same clbit multiple times")
}
self.qc.add_clbit(py, clbit)
}

fn add_qreg<T: IntoPy<Py<PyString>>>(
&mut self,
py: Python,
ast_symbol: SymbolId,
name: T,
size: usize,
) -> PyResult<()> {
let qreg = self.module.new_qreg(py, name, size)?;
self.qc.add_qreg(py, &qreg)?;
if self.symbols.qregs.insert(ast_symbol, qreg).is_some() {
panic!("internal logic error: attempted to add the same register multiple times")
}
Ok(())
}

fn add_creg<T: IntoPy<Py<PyString>>>(
&mut self,
py: Python,
ast_symbol: SymbolId,
name: T,
size: usize,
) -> PyResult<()> {
let creg = self.module.new_creg(py, name, size)?;
self.qc.add_creg(py, &creg)?;
if self.symbols.cregs.insert(ast_symbol, creg).is_some() {
panic!("internal logic error: attempted to add the same register multiple times")
}
Ok(())
}
}

pub fn convert_asg(
py: Python,
program: &asg::Program,
ast_symbols: &SymbolTable,
gate_constructors: HashMap<String, PyGate>,
) -> PyResult<PyCircuit> {
let module = PyCircuitModule::import(py)?;
let mut state = BuilderState {
qc: module.new_circuit(py)?,
symbols: Default::default(),
pygates: gate_constructors,
module,
};
for statement in program.stmts().iter() {
match statement {
asg::Stmt::DeclareClassical(decl) => state.declare_classical(py, ast_symbols, decl)?,
asg::Stmt::DeclareQuantum(decl) => state.declare_quantum(py, ast_symbols, decl)?,
asg::Stmt::GateCall(call) => state.call_gate(py, ast_symbols, call)?,
asg::Stmt::GateDeclaration(decl) => state.define_gate(py, ast_symbols, decl)?,
asg::Stmt::Alias
| asg::Stmt::AnnotatedStmt(_)
| asg::Stmt::Assignment(_)
| asg::Stmt::Barrier
| asg::Stmt::Block(_)
| asg::Stmt::Box
| asg::Stmt::Break
| asg::Stmt::Cal
| asg::Stmt::Continue
| asg::Stmt::Def
| asg::Stmt::DefCal
| asg::Stmt::Delay
| asg::Stmt::End
| asg::Stmt::ExprStmt(_)
| asg::Stmt::Extern
| asg::Stmt::For
| asg::Stmt::GPhaseCall(_)
| asg::Stmt::IODeclaration
| asg::Stmt::If(_)
| asg::Stmt::Include(_)
| asg::Stmt::NullStmt
| asg::Stmt::OldStyleDeclaration
| asg::Stmt::Pragma(_)
| asg::Stmt::Reset
| asg::Stmt::Return
| asg::Stmt::While(_) => {
return Err(QASM3ImporterError::new_err(format!(
"this statement is not yet handled during OpenQASM 3 import: {:?}",
statement
)));
}
}
}
Ok(state.qc)
}
Loading

0 comments on commit 549edb1

Please sign in to comment.