Skip to content

Commit

Permalink
Merge branch 'main' into move-target
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss authored May 15, 2024
2 parents 7bfb9cd + d452225 commit d434f02
Show file tree
Hide file tree
Showing 18 changed files with 469 additions and 209 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/accelerate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ version = "0.1.0"
features = ["ndarray"]

[dependencies.pulp]
version = "0.18.10"
version = "0.18.12"
features = ["macro"]
18 changes: 1 addition & 17 deletions crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,7 @@ impl CircuitInstruction {
)
}

fn __getstate__(&self, py: Python<'_>) -> PyObject {
(
self.operation.bind(py),
self.qubits.bind(py),
self.clbits.bind(py),
)
.into_py(py)
}

fn __setstate__(&mut self, _py: Python<'_>, state: &Bound<PyTuple>) -> PyResult<()> {
self.operation = state.get_item(0)?.extract()?;
self.qubits = state.get_item(1)?.extract()?;
self.clbits = state.get_item(2)?.extract()?;
Ok(())
}

pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult<PyObject> {
fn __getnewargs__(&self, py: Python<'_>) -> PyResult<PyObject> {
Ok((
self.operation.bind(py),
self.qubits.bind(py),
Expand Down
283 changes: 283 additions & 0 deletions crates/circuit/src/dag_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// 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 crate::circuit_instruction::CircuitInstruction;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PySequence, PyString, PyTuple};
use pyo3::{intern, PyObject, PyResult};

/// Parent class for DAGOpNode, DAGInNode, and DAGOutNode.
#[pyclass(module = "qiskit._accelerate.circuit", subclass)]
#[derive(Clone, Debug)]
pub struct DAGNode {
#[pyo3(get, set)]
pub _node_id: isize,
}

#[pymethods]
impl DAGNode {
#[new]
#[pyo3(signature=(nid=-1))]
fn new(nid: isize) -> Self {
DAGNode { _node_id: nid }
}

fn __getstate__(&self) -> isize {
self._node_id
}

fn __setstate__(&mut self, nid: isize) {
self._node_id = nid;
}

fn __lt__(&self, other: &DAGNode) -> bool {
self._node_id < other._node_id
}

fn __gt__(&self, other: &DAGNode) -> bool {
self._node_id > other._node_id
}

fn __str__(_self: &Bound<DAGNode>) -> String {
format!("{}", _self.as_ptr() as usize)
}

fn __hash__(&self, py: Python) -> PyResult<isize> {
self._node_id.into_py(py).bind(py).hash()
}
}

/// Object to represent an Instruction at a node in the DAGCircuit.
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
pub struct DAGOpNode {
pub instruction: CircuitInstruction,
#[pyo3(get)]
pub sort_key: PyObject,
}

#[pymethods]
impl DAGOpNode {
#[new]
fn new(
py: Python,
op: PyObject,
qargs: Option<&Bound<PySequence>>,
cargs: Option<&Bound<PySequence>>,
dag: Option<&Bound<PyAny>>,
) -> PyResult<(Self, DAGNode)> {
let qargs =
qargs.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?;
let cargs =
cargs.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?;

let sort_key = match dag {
Some(dag) => {
let cache = dag
.getattr(intern!(py, "_key_cache"))?
.downcast_into_exact::<PyDict>()?;
let cache_key = PyTuple::new_bound(py, [&qargs, &cargs]);
match cache.get_item(&cache_key)? {
Some(key) => key,
None => {
let indices: PyResult<Vec<_>> = qargs
.iter()
.chain(cargs.iter())
.map(|bit| {
dag.call_method1(intern!(py, "find_bit"), (bit,))?
.getattr(intern!(py, "index"))
})
.collect();
let index_strs: Vec<_> =
indices?.into_iter().map(|i| format!("{:04}", i)).collect();
let key = PyString::new_bound(py, index_strs.join(",").as_str());
cache.set_item(&cache_key, &key)?;
key.into_any()
}
}
}
None => qargs.str()?.into_any(),
};

Ok((
DAGOpNode {
instruction: CircuitInstruction {
operation: op,
qubits: qargs.unbind(),
clbits: cargs.unbind(),
},
sort_key: sort_key.unbind(),
},
DAGNode { _node_id: -1 },
))
}

fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
let state = (slf.as_ref()._node_id, &slf.sort_key);
(
py.get_type_bound::<Self>(),
(
&slf.instruction.operation,
&slf.instruction.qubits,
&slf.instruction.clbits,
),
state,
)
.into_py(py)
}

fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
let (nid, sort_key): (isize, PyObject) = state.extract()?;
slf.as_mut()._node_id = nid;
slf.sort_key = sort_key;
Ok(())
}

#[getter]
fn get_op(&self, py: Python) -> PyObject {
self.instruction.operation.clone_ref(py)
}

#[setter]
fn set_op(&mut self, op: PyObject) {
self.instruction.operation = op;
}

#[getter]
fn get_qargs(&self, py: Python) -> Py<PyTuple> {
self.instruction.qubits.clone_ref(py)
}

#[setter]
fn set_qargs(&mut self, qargs: Py<PyTuple>) {
self.instruction.qubits = qargs;
}

#[getter]
fn get_cargs(&self, py: Python) -> Py<PyTuple> {
self.instruction.clbits.clone_ref(py)
}

#[setter]
fn set_cargs(&mut self, cargs: Py<PyTuple>) {
self.instruction.clbits = cargs;
}

/// Returns the Instruction name corresponding to the op for this node
#[getter]
fn get_name(&self, py: Python) -> PyResult<PyObject> {
Ok(self
.instruction
.operation
.bind(py)
.getattr(intern!(py, "name"))?
.unbind())
}

/// Sets the Instruction name corresponding to the op for this node
#[setter]
fn set_name(&self, py: Python, new_name: PyObject) -> PyResult<()> {
self.instruction
.operation
.bind(py)
.setattr(intern!(py, "name"), new_name)
}

/// Returns a representation of the DAGOpNode
fn __repr__(&self, py: Python) -> PyResult<String> {
Ok(format!(
"DAGOpNode(op={}, qargs={}, cargs={})",
self.instruction.operation.bind(py).repr()?,
self.instruction.qubits.bind(py).repr()?,
self.instruction.clbits.bind(py).repr()?
))
}
}

/// Object to represent an incoming wire node in the DAGCircuit.
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
pub struct DAGInNode {
#[pyo3(get)]
wire: PyObject,
#[pyo3(get)]
sort_key: PyObject,
}

#[pymethods]
impl DAGInNode {
#[new]
fn new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> {
Ok((
DAGInNode {
wire,
sort_key: PyList::empty_bound(py).str()?.into_any().unbind(),
},
DAGNode { _node_id: -1 },
))
}

fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
let state = (slf.as_ref()._node_id, &slf.sort_key);
(py.get_type_bound::<Self>(), (&slf.wire,), state).into_py(py)
}

fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
let (nid, sort_key): (isize, PyObject) = state.extract()?;
slf.as_mut()._node_id = nid;
slf.sort_key = sort_key;
Ok(())
}

/// Returns a representation of the DAGInNode
fn __repr__(&self, py: Python) -> PyResult<String> {
Ok(format!("DAGInNode(wire={})", self.wire.bind(py).repr()?))
}
}

/// Object to represent an outgoing wire node in the DAGCircuit.
#[pyclass(module = "qiskit._accelerate.circuit", extends=DAGNode)]
pub struct DAGOutNode {
#[pyo3(get)]
wire: PyObject,
#[pyo3(get)]
sort_key: PyObject,
}

#[pymethods]
impl DAGOutNode {
#[new]
fn new(py: Python, wire: PyObject) -> PyResult<(Self, DAGNode)> {
Ok((
DAGOutNode {
wire,
sort_key: PyList::empty_bound(py).str()?.into_any().unbind(),
},
DAGNode { _node_id: -1 },
))
}

fn __reduce__(slf: PyRef<Self>, py: Python) -> PyObject {
let state = (slf.as_ref()._node_id, &slf.sort_key);
(py.get_type_bound::<Self>(), (&slf.wire,), state).into_py(py)
}

fn __setstate__(mut slf: PyRefMut<Self>, state: &Bound<PyAny>) -> PyResult<()> {
let (nid, sort_key): (isize, PyObject) = state.extract()?;
slf.as_mut()._node_id = nid;
slf.sort_key = sort_key;
Ok(())
}

/// Returns a representation of the DAGOutNode
fn __repr__(&self, py: Python) -> PyResult<String> {
Ok(format!("DAGOutNode(wire={})", self.wire.bind(py).repr()?))
}
}
5 changes: 5 additions & 0 deletions crates/circuit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

pub mod circuit_data;
pub mod circuit_instruction;
pub mod dag_node;
pub mod intern_context;

use pyo3::prelude::*;
Expand All @@ -30,6 +31,10 @@ pub enum SliceOrInt<'a> {
#[pymodule]
pub fn circuit(m: Bound<PyModule>) -> PyResult<()> {
m.add_class::<circuit_data::CircuitData>()?;
m.add_class::<dag_node::DAGNode>()?;
m.add_class::<dag_node::DAGInNode>()?;
m.add_class::<dag_node::DAGOutNode>()?;
m.add_class::<dag_node::DAGOpNode>()?;
m.add_class::<circuit_instruction::CircuitInstruction>()?;
Ok(())
}
14 changes: 12 additions & 2 deletions crates/qasm2/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,13 @@ impl<'a> ExprParser<'a> {
| TokenType::Sin
| TokenType::Sqrt
| TokenType::Tan => Ok(Some(Atom::Function(token.ttype.into()))),
TokenType::Real => Ok(Some(Atom::Const(token.real(self.context)))),
TokenType::Integer => Ok(Some(Atom::Const(token.int(self.context) as f64))),
// This deliberately parses an _integer_ token as a float, since all OpenQASM 2.0
// integers can be interpreted as floats, and doing that allows us to gracefully handle
// cases where a huge float would overflow a `usize`. Never mind that in such a case,
// there's almost certainly precision loss from the floating-point representating
// having insufficient mantissa digits to faithfully represent the angle mod 2pi;
// that's not our fault in the parser.
TokenType::Real | TokenType::Integer => Ok(Some(Atom::Const(token.real(self.context)))),
TokenType::Pi => Ok(Some(Atom::Const(f64::consts::PI))),
TokenType::Id => {
let id = token.text(self.context);
Expand Down Expand Up @@ -698,6 +703,11 @@ impl<'a> ExprParser<'a> {

/// Parse a single expression completely. This is the only public entry point to the
/// operator-precedence parser.
///
/// .. note::
///
/// This evaluates in a floating-point context, including evaluating integer tokens, since
/// the only places that expressions are valid in OpenQASM 2 is during gate applications.
pub fn parse_expression(&mut self, cause: &Token) -> PyResult<Expr> {
self.eval_expression(0, cause)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/qasm2/src/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ impl Token {
}

/// If the token is a real number, this method can be called to evaluate its value. Panics if
/// the token is not a real number.
/// the token is not a float or an integer.
pub fn real(&self, context: &TokenContext) -> f64 {
if self.ttype != TokenType::Real {
if !(self.ttype == TokenType::Real || self.ttype == TokenType::Integer) {
panic!()
}
context.text[self.index].parse().unwrap()
Expand Down
3 changes: 3 additions & 0 deletions qiskit/circuit/_classical_resource_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,6 @@ def visit_binary(self, node, /):

def visit_cast(self, node, /):
return expr.Cast(node.operand.accept(self), node.type, implicit=node.implicit)

def visit_index(self, node, /):
return expr.Index(node.target.accept(self), node.index.accept(self), node.type)
Loading

0 comments on commit d434f02

Please sign in to comment.