Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: drop pyo3 core dep #355

Merged
merged 6 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
752 changes: 379 additions & 373 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ tket2::passes::apply_greedy_commutation(&mut circ);

## Features

- `pyo3`
Enables some python bindings via pyo3. See the `tket2-py` folder for more.

- `portmatching`
Enables pattern matching using the `portmatching` crate.

Expand Down
4 changes: 1 addition & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ build:

# Run all the tests.
test language="[rust|python]" : (_run_lang language \
"poetry run cargo test --all-features" \
"poetry run cargo test --all-features --workspace" \
"poetry run maturin develop && poetry run pytest"
)
# Note: We cannot use `cargo test --workspace` because there
# are two crates that use pyo3, and that causes a linking conflict.

# Auto-fix all clippy warnings.
fix language="[rust|python]": (_run_lang language \
Expand Down
3 changes: 2 additions & 1 deletion tket2-py/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ test = false
bench = false

[dependencies]
tket2 = { workspace = true, features = ["pyo3", "portmatching"] }
tket2 = { workspace = true, features = ["portmatching"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tket-json-rs = { workspace = true, features = ["pyo3"] }
Expand All @@ -29,3 +29,4 @@ num_cpus = { workspace = true }
derive_more = { workspace = true }
itertools = { workspace = true }
portmatching = { workspace = true }
strum = { workspace = true }
2 changes: 0 additions & 2 deletions tket2-py/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ pub fn module(py: Python<'_>) -> PyResult<Bound<'_, PyModule>> {
m.add_class::<PyWire>()?;
m.add_class::<WireIter>()?;
m.add_class::<PyCircuitCost>()?;
m.add_class::<Tk2Op>()?;
m.add_class::<PyCustom>()?;
m.add_class::<PyHugrType>()?;
m.add_class::<Pauli>()?;
m.add_class::<PyTypeBound>()?;

m.add_function(wrap_pyfunction!(validate_hugr, &m)?)?;
Expand Down
4 changes: 3 additions & 1 deletion tket2-py/src/circuit/tk2circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use tket2::passes::CircuitChunks;
use tket2::{Circuit, Tk2Op};
use tket_json_rs::circuit_json::SerialCircuit;

use crate::ops::PyTk2Op;
use crate::rewrite::PyCircuitRewrite;
use crate::utils::ConvertPyErr;

Expand Down Expand Up @@ -127,7 +128,8 @@ impl Tk2Circuit {
"Could not convert circuit operation to a `Tk2Op`: {e}"
))
})?;
let cost = cost_fn.call1((tk2_op,))?;
let tk2_py_op = PyTk2Op::from(tk2_op);
let cost = cost_fn.call1((tk2_py_op,))?;
Ok(PyCircuitCost {
cost: cost.to_object(py),
})
Expand Down
2 changes: 2 additions & 0 deletions tket2-py/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Python bindings for TKET2.
pub mod circuit;
pub mod ops;
pub mod optimiser;
pub mod passes;
pub mod pattern;
Expand All @@ -12,6 +13,7 @@ use pyo3::prelude::*;
#[pymodule]
fn _tket2(py: Python, m: &Bound<PyModule>) -> PyResult<()> {
add_submodule(py, m, circuit::module(py)?)?;
add_submodule(py, m, ops::module(py)?)?;
add_submodule(py, m, optimiser::module(py)?)?;
add_submodule(py, m, passes::module(py)?)?;
add_submodule(py, m, pattern::module(py)?)?;
Expand Down
202 changes: 202 additions & 0 deletions tket2-py/src/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//! Bindings for rust-defined operations

use derive_more::From;
use hugr::ops::NamedOp;
use pyo3::prelude::*;
use std::str::FromStr;
use strum::IntoEnumIterator;
use tket2::{Pauli, Tk2Op};

/// The module definition
pub fn module(py: Python<'_>) -> PyResult<Bound<'_, PyModule>> {
let m = PyModule::new_bound(py, "ops")?;
m.add_class::<PyTk2Op>()?;
m.add_class::<PyPauli>()?;
Ok(m)
}

/// Enum of Tket2 operations in hugr.
///
/// Python equivalent of [`Tk2Op`].
///
/// [`Tk2Op`]: tket2::ops::Tk2Op
#[pyclass]
#[pyo3(name = "Tk2Op")]
#[derive(Debug, Clone, From)]
#[repr(transparent)]
pub struct PyTk2Op {
/// Rust representation of the operation.
pub op: Tk2Op,
}

#[pymethods]
impl PyTk2Op {
/// Initialize a new `PyTk2Op` from a python string.
#[new]
fn new(op: &str) -> PyResult<Self> {
Ok(Self {
op: Tk2Op::from_str(op)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?,
})
}

/// Iterate over the operations.
#[staticmethod]
pub fn values() -> PyTk2OpIter {
PyTk2OpIter { it: Tk2Op::iter() }
}

/// Get the string name of the operation.
#[getter]
pub fn name(&self) -> String {
self.op.name().to_string()
}

/// Get the qualified name of the operation, including the extension.
#[getter]
pub fn qualified_name(&self) -> String {
self.op.exposed_name().to_string()
}

/// String representation of the operation.
pub fn __repr__(&self) -> String {
self.qualified_name()
}

/// String representation of the operation.
pub fn __str__(&self) -> String {
self.name()
}

/// Check if two operations are equal.
pub fn __eq__(&self, other: &PyTk2Op) -> bool {
self.op == other.op
}
}

/// Iterator over the operations.
#[pyclass]
#[pyo3(name = "Tk2OpIter")]
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct PyTk2OpIter {
/// Iterator over the operations.
pub it: <Tk2Op as IntoEnumIterator>::Iterator,
}

#[pymethods]
impl PyTk2OpIter {
/// Iterate over the operations.
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}

/// Get the next operation.
pub fn __next__(&mut self) -> Option<PyTk2Op> {
self.it.next().map(|op| PyTk2Op { op })
}
}

/// Pauli matrices
///
/// Python equivalent of [`Pauli`].
///
/// [`Pauli`]: tket2::ops::Pauli
#[pyclass]
#[pyo3(name = "Pauli")]
#[derive(Debug, Clone, From)]
#[repr(transparent)]
pub struct PyPauli {
/// Rust representation of the pauli matrix.
pub p: Pauli,
}

#[pymethods]
impl PyPauli {
/// Initialize a new `PyPauli` from a python string.
#[new]
fn new(p: &str) -> PyResult<Self> {
Ok(Self {
p: Pauli::from_str(p)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?,
})
}

/// Iterate over the pauli matrices.
#[staticmethod]
pub fn values() -> PyPauliIter {
PyPauliIter { it: Pauli::iter() }
}

/// Return the Pauli matrix as a string.
#[getter]
pub fn name(&self) -> String {
format!("{}", self.p)
}

/// Pauli identity matrix.
#[staticmethod]
#[pyo3(name = "I")]
fn i() -> Self {
Self { p: Pauli::I }
}

/// Pauli X matrix.
#[staticmethod]
#[pyo3(name = "X")]
fn x() -> Self {
Self { p: Pauli::X }
}

/// Pauli Y matrix.
#[staticmethod]
#[pyo3(name = "Y")]
fn y() -> Self {
Self { p: Pauli::Y }
}

/// Pauli Z matrix.
#[pyo3(name = "Z")]
#[staticmethod]
fn z() -> Self {
Self { p: Pauli::Z }
}

/// String representation of the Pauli matrix.
pub fn __repr__(&self) -> String {
format!("{:?}", self.p)
}

/// String representation of the Pauli matrix.
pub fn __str__(&self) -> String {
format!("{}", self.p)
}

/// Check if two Pauli matrices are equal.
pub fn __eq__(&self, other: &PyPauli) -> bool {
self.p == other.p
}
}

/// Iterator over the Pauli matrices.
#[pyclass]
#[pyo3(name = "PauliIter")]
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct PyPauliIter {
/// Iterator over the Pauli matrices.
pub it: <Pauli as IntoEnumIterator>::Iterator,
}

#[pymethods]
impl PyPauliIter {
/// Iterate over the operations.
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}

/// Get the next Pauli matrix.
pub fn __next__(&mut self) -> Option<PyPauli> {
self.it.next().map(|p| PyPauli { p })
}
}
2 changes: 1 addition & 1 deletion tket2-py/test/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from tket2.circuit import (
Tk2Circuit,
Tk2Op,
to_hugr_dot,
)
from tket2.ops import Tk2Op


@dataclass
Expand Down
18 changes: 18 additions & 0 deletions tket2-py/test/test_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import tket2
from tket2.ops import Tk2Op, Pauli


def test_ops_roundtrip():
for op in Tk2Op:
assert Tk2Op._from_rs(op._to_rs()) == op

for op in tket2._tket2.ops.Tk2Op.values():
assert Tk2Op._from_rs(op)._to_rs() == op


def test_pauli_roundtrip():
for pauli in Pauli:
assert Pauli._from_rs(pauli._to_rs()) == pauli

for pauli in tket2._tket2.ops.Pauli.values():
assert Pauli._from_rs(pauli)._to_rs() == pauli
36 changes: 2 additions & 34 deletions tket2-py/tket2/_tket2/circuit.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from enum import Enum, auto
from enum import Enum
from typing import Any, Callable
from pytket._tket.circuit import Circuit
from tket2._tket2.ops import Tk2Op

class Tk2Circuit:
"""Rust representation of a TKET2 circuit."""
Expand Down Expand Up @@ -116,31 +117,6 @@ class CircuitCost:

The cost object must implement __add__, __sub__, __eq__, and __lt__."""

class Tk2Op(Enum):
"""A Tket2 built-in operation."""

H = auto()
CX = auto()
T = auto()
S = auto()
X = auto()
Y = auto()
Z = auto()
Tdg = auto()
Sdg = auto()
ZZMax = auto()
Measure = auto()
RzF64 = auto()
RxF64 = auto()
PhasedX = auto()
ZZPhase = auto()
AngleAdd = auto()
CZ = auto()
TK1 = auto()
QAlloc = auto()
QFree = auto()
Reset = auto()

class CustomOp:
"""A HUGR custom operation."""

Expand Down Expand Up @@ -177,14 +153,6 @@ class HugrType:
def bool() -> HugrType:
"""Boolean type (HUGR 2-ary unit sum)."""

class Pauli(Enum):
"""Simple enum representation of Pauli matrices."""

I = auto() # noqa: E741
X = auto()
Y = auto()
Z = auto()

class TypeBound(Enum):
"""HUGR type bounds."""

Expand Down
Loading
Loading