Skip to content

Commit

Permalink
Fix: Add functional python Bits
Browse files Browse the repository at this point in the history
  • Loading branch information
raynelfss committed Jan 2, 2025
1 parent 5f3dd1c commit 5e66a66
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 26 deletions.
110 changes: 100 additions & 10 deletions crates/circuit/src/bit.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use std::hash::{DefaultHasher, Hash, Hasher};

use pyo3::prelude::*;
use pyo3::{prelude::*, types::PyDict};

use crate::{
circuit_data::CircuitError,
interner::Interned,
register::{Register, RegisterAsKey},
};

/// Object representing a Python bit, that allows us to keep backwards compatibility
/// with the previous structure.
#[pyclass(name = "Bit")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[pyclass(name = "Bit", module = "qiskit._accelerate.bit", subclass)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PyBit {
register: Option<RegisterAsKey>, // Register identifier
index: Option<u32>, // Index within Register
Expand All @@ -20,10 +21,13 @@ pub struct PyBit {
impl PyBit {
#[new]
#[pyo3(signature=(register=None, index=None))]
pub fn new(register: Option<RegisterAsKey>, index: Option<u32>) -> Self {
Self {
register,
index,
pub fn new(register: Option<RegisterAsKey>, index: Option<u32>) -> PyResult<Self> {
match (&register, index) {
(None, None) => Ok(Self { register, index }),
(Some(_), Some(_)) => Ok(Self { register, index }),
_ => Err(CircuitError::new_err(
"You should provide both an index and a register, not just one of them.",
)),
}
}

Expand All @@ -38,17 +42,103 @@ impl PyBit {
slf.is(&other)
}

fn __hash__(slf: Bound<'_, Self>) -> PyResult<isize> {
let borrowed = slf.borrow();
fn __hash__(&self, py: Python<'_>) -> PyResult<isize> {
if let (Some(reg), Some(idx)) = (self.register.as_ref(), self.index) {
return (reg.reduce(), idx).to_object(py).bind(py).hash();
}

// If registers are unavailable, hash by pointer value.
let mut hasher = DefaultHasher::new();
borrowed.hash(&mut hasher);
let pointer_val = self as *const Self;
pointer_val.hash(&mut hasher);
Ok(hasher.finish() as isize)
}

fn __copy__(slf: Bound<Self>) -> Bound<Self> {
slf
}

#[pyo3(signature = (_memo=None))]
fn __deepcopy__<'py>(
slf: Bound<'py, Self>,
_memo: Option<Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyBit>> {
let borrowed: PyRef<Self> = slf.borrow();
if borrowed.index.is_none() && borrowed.register.is_none() {
return Ok(slf);
}
let copy = slf
.get_type()
.call_method1("__new__", (slf.get_type(),))?
.downcast_into::<PyBit>()?;
let mut copy_mut = copy.borrow_mut();
copy_mut.register = borrowed.register.clone();
copy_mut.index = borrowed.index;
Ok(copy)
}

fn __getstate__(slf: PyRef<'_, Self>) -> (Option<(String, u32)>, Option<u32>) {
(
slf.register.as_ref().map(|reg| {
let (name, num_qubits) = reg.reduce();
(name.to_string(), num_qubits)
}),
slf.index.as_ref().copied(),
)
}

fn __setstate__(mut slf: PyRefMut<'_, Self>, state: (Option<(String, u32)>, Option<u32>)) {
slf.register = state
.0
.map(|(name, num_qubits)| RegisterAsKey::new(name.as_str(), num_qubits));
slf.index = state.1;
}
}

macro_rules! create_py_bit {
($name:ident, $pyname:literal, $module:literal) => {
#[pyclass(name=$pyname, extends=PyBit, subclass, module=$module)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct $name();

#[pymethods]
impl $name {
#[new]
#[pyo3(signature = (register=None, index=None))]
pub fn py_new(
register: Option<RegisterAsKey>,
index: Option<u32>,
) -> PyResult<(Self, PyBit)> {
Ok((Self(), PyBit::new(register, index)?))
}
}
};
}

// Create python instances
create_py_bit! {PyQubit, "Qubit", "qiskit._accelerate.bit"}
create_py_bit! {PyClbit, "Clbit", "qiskit._accelerate.bit"}

/// Keeps information about where a qubit is located within the circuit.
#[derive(Debug, Clone)]
pub struct BitInfo<T: Register + Hash + Eq> {
register_idx: Interned<T>,
index: u32,
}

impl<T: Register + Hash + Eq> BitInfo<T> {
pub fn new(register_idx: Interned<T>, index: u32) -> Self {
Self {
register_idx,
index,
}
}

pub fn register_index(&self) -> Interned<T> {
self.register_idx
}

pub fn index(&self) -> u32 {
self.index
}
}
3 changes: 3 additions & 0 deletions crates/circuit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ impl From<Clbit> for BitType {
}

pub fn circuit(m: &Bound<PyModule>) -> PyResult<()> {
m.add_class::<bit::PyBit>()?;
m.add_class::<bit::PyClbit>()?;
m.add_class::<bit::PyQubit>()?;
m.add_class::<circuit_data::CircuitData>()?;
m.add_class::<circuit_instruction::CircuitInstruction>()?;
m.add_class::<dag_circuit::DAGCircuit>()?;
Expand Down
48 changes: 32 additions & 16 deletions crates/circuit/src/register.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
use indexmap::IndexSet;
use pyo3::{intern, types::PyAnyMethods, FromPyObject};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::{
hash::{Hash, Hasher},
sync::Mutex,
};

use crate::{
interner::{Interned, Interner},
Clbit, Qubit,
};

static REGISTER_INSTANCE_COUNTER: Mutex<u32> = Mutex::new(0);
static PREFIX: &str = "reg";

/// This represents the hash value of a Register according to the register's
/// name and number of qubits.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegisterAsKey(u64);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RegisterAsKey(String, u32);

impl RegisterAsKey {
pub fn new(name: Option<&str>, num_qubits: u32) -> Self {
let mut hasher = DefaultHasher::default();
(name, num_qubits).hash(&mut hasher);
Self(hasher.finish())
pub fn new(name: &str, num_qubits: u32) -> Self {
Self(name.to_string(), num_qubits)
}

pub fn reduce(&self) -> (&str, u32) {
(self.0.as_str(), self.1)
}
}

impl<'py> FromPyObject<'py> for RegisterAsKey {
fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
let (name, num_qubits) = (
ob.getattr(intern!(ob.py(), "name"))?
.extract::<Option<String>>()?,
ob.getattr(intern!(ob.py(), "num_qubits"))?.extract()?,
ob.getattr(intern!(ob.py(), "name"))?.extract()?,
ob.len()? as u32,
);
Ok(RegisterAsKey::new(
name.as_deref(),
num_qubits,
))
Ok(RegisterAsKey(name, num_qubits))
}
}
/// Described the desired behavior of a Register.
Expand All @@ -53,11 +57,23 @@ macro_rules! create_register {
#[derive(Debug, Clone, Eq)]
pub struct $name {
register: IndexSet<<$name as Register>::Bit>,
name: Option<String>,
name: String,
}

impl $name {
pub fn new(size: usize, name: Option<String>) -> Self {
let name = if let Some(name) = name {
name
} else {
let count = if let Ok(ref mut count) = REGISTER_INSTANCE_COUNTER.try_lock() {
let curr = **count;
**count += 1;
curr
} else {
panic!("Could not access register counter.")
};
format!("{}{}", PREFIX, count)
};
Self {
register: (0..size).map(|bit| <$bit>::new(bit)).collect(),
name,
Expand Down Expand Up @@ -87,7 +103,7 @@ macro_rules! create_register {

impl Hash for $name {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.name.as_ref(), self.len()).hash(state);
(self.name.as_str(), self.len()).hash(state);
}
}

Expand Down

0 comments on commit 5e66a66

Please sign in to comment.