Skip to content

Commit

Permalink
feat: convert guppy programs into pytket circuits
Browse files Browse the repository at this point in the history
  • Loading branch information
aborgna-q committed Jun 17, 2024
1 parent 425d6b4 commit 865afdb
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 6 deletions.
10 changes: 6 additions & 4 deletions tket2-py/src/circuit/tk2circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use hugr::{Hugr, HugrView, Wire};
use serde::Serialize;
use tket2::circuit::CircuitHash;
use tket2::extension::REGISTRY;
use tket2::passes::pytket::lower_to_pytket;
use tket2::passes::CircuitChunks;
use tket2::serialize::TKETDecode;
use tket2::{Circuit, Tk2Op};
Expand Down Expand Up @@ -73,9 +74,8 @@ impl Tk2Circuit {

/// Convert the [`Tk2Circuit`] to a tket1 circuit.
pub fn to_tket1<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
SerialCircuit::encode(&self.circ)
.convert_pyerrs()?
.to_tket1(py)
let circ = lower_to_pytket(&self.circ).convert_pyerrs()?;
SerialCircuit::encode(&circ).convert_pyerrs()?.to_tket1(py)
}

/// Apply a rewrite on the circuit.
Expand Down Expand Up @@ -109,7 +109,9 @@ impl Tk2Circuit {

/// Encode the circuit as a tket1 json string.
pub fn to_tket1_json(&self) -> PyResult<String> {
Ok(serde_json::to_string(&SerialCircuit::encode(&self.circ).convert_pyerrs()?).unwrap())
// Try to simplify tuple pack-unpack pairs, and other operations not supported by pytket.
let circ = lower_to_pytket(&self.circ).convert_pyerrs()?;
Ok(serde_json::to_string(&SerialCircuit::encode(&circ).convert_pyerrs()?).unwrap())
}

/// Decode a tket1 json string to a circuit.
Expand Down
6 changes: 6 additions & 0 deletions tket2-py/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ create_py_exception!(
"Error from a `PullForward` operation"
);

create_py_exception!(
tket2::passes::pytket::PytketLoweringError,
PyPytketLoweringError,
"Errors that can occur while removing high-level operations from HUGR intended to be encoded as a pytket circuit."
);

#[pyfunction]
fn greedy_depth_reduce<'py>(circ: &Bound<'py, PyAny>) -> PyResult<(Bound<'py, PyAny>, u32)> {
let py = circ.py();
Expand Down
41 changes: 40 additions & 1 deletion tket2-py/test/test_guppy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import no_type_check
import pytket.circuit
from tket2.circuit import Tk2Circuit

import math
Expand All @@ -9,8 +10,46 @@
from guppylang.prelude.builtins import py
from guppylang.prelude.quantum import measure, phased_x, qubit, rz, zz_max

import pytket

def test_load_compiled_module():

def test_load_pure_circuit():
module = GuppyModule("test")
module.load(quantum)

@guppy(module)
@no_type_check
def my_func(
q0: qubit,
q1: qubit,
) -> tuple[qubit, qubit]:
q0 = phased_x(q0, py(math.pi / 2), py(-math.pi / 2))
q0 = rz(q0, py(math.pi))
q1 = phased_x(q1, py(math.pi / 2), py(-math.pi / 2))
q1 = rz(q1, py(math.pi))
q0, q1 = zz_max(q0, q1)
q0 = rz(q0, py(math.pi))
q1 = rz(q1, py(math.pi))
return (q0, q1)

Check warning on line 33 in tket2-py/test/test_guppy.py

View check run for this annotation

Codecov / codecov/patch

tket2-py/test/test_guppy.py#L26-L33

Added lines #L26 - L33 were not covered by tests

# Compile the module
hugr = module.compile()
json = hugr.to_raw().to_json()

# Load it from the JSON string
circ = Tk2Circuit.from_guppy_json(json, "my_func")
assert circ.num_operations() == 7

# Convert into a pytket Circuit
tk1 = circ.to_tket1()
assert tk1.n_gates == 7
assert tk1.n_qubits == 2

gates = list(tk1)
assert gates[4].op.type == pytket.circuit.OpType.ZZMax


def test_load_hybrid_circuit():
module = GuppyModule("test")
module.load(quantum)

Expand Down
3 changes: 3 additions & 0 deletions tket2/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ pub use commutation::{apply_greedy_commutation, PullForwardError};
pub mod chunks;
pub use chunks::CircuitChunks;

pub mod pytket;
pub use pytket::lower_to_pytket;

pub mod tuple_unpack;
pub use tuple_unpack::find_tuple_unpack_rewrites;
39 changes: 39 additions & 0 deletions tket2/src/passes/pytket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! This module contains routines needed for normalizing a circuit
//! into a form that can be encoded as a pytket legacy circuit.
//!
//! This is a best-effort attempt, and may not always succeed.
use itertools::Itertools;

use crate::serialize::pytket::OpConvertError;
use crate::Circuit;

use super::find_tuple_unpack_rewrites;

/// Try to lower a circuit to a form that can be encoded as a pytket legacy circuit.
pub fn lower_to_pytket(circ: &Circuit) -> Result<Circuit, PytketLoweringError> {
let mut circ = circ
.extract_dfg()
.map_err(|_| PytketLoweringError::NonLocalOperations)?;

// Remove sequences of tuple pack-unpack operations,
// typically generated by guppy.
let rewrites = find_tuple_unpack_rewrites(&circ).collect_vec();
for rewrite in rewrites {
rewrite.apply(&mut circ).unwrap();
}

Ok(circ)
}

/// Errors that can occur during the lowering process.
#[derive(Clone, PartialEq, Debug, thiserror::Error)]
pub enum PytketLoweringError {
/// An error occurred during the conversion of an operation.
#[error("operation conversion error: {0}")]
OpConversionError(#[from] OpConvertError),
/// The circuit is not fully-contained in a region.
/// Function calls are not supported.
#[error("Non-local operations found. Function calls are not supported.")]
NonLocalOperations,
}
25 changes: 24 additions & 1 deletion tket2/src/serialize/pytket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use crate::circuit::Circuit;
use self::decoder::JsonDecoder;
use self::encoder::JsonEncoder;

pub use crate::passes::pytket::lower_to_pytket;

/// Prefix used for storing metadata in the hugr nodes.
pub const METADATA_PREFIX: &str = "TKET1_JSON";
/// The global phase specified as metadata.
Expand Down Expand Up @@ -92,7 +94,7 @@ impl TKETDecode for SerialCircuit {
}

/// Error type for conversion between `Op` and `OpType`.
#[derive(Debug, Error)]
#[derive(Clone, PartialEq, Debug, Error)]
pub enum OpConvertError {
/// The serialized operation is not supported.
#[error("Unsupported serialized pytket operation: {0:?}")]
Expand Down Expand Up @@ -123,20 +125,41 @@ pub fn load_tk1_json_str(json: &str) -> Result<Circuit, TK1ConvertError> {
}

/// Save a circuit to file in TK1 JSON format.
///
/// You may need to normalize the circuit using [`lower_to_pytket`] before saving.
///
/// # Errors
///
/// Returns an error if the circuit is not flat or if it contains operations not
/// supported by pytket.
pub fn save_tk1_json_file(circ: &Circuit, path: impl AsRef<Path>) -> Result<(), TK1ConvertError> {
let file = fs::File::create(path)?;
let writer = io::BufWriter::new(file);
save_tk1_json_writer(circ, writer)
}

/// Save a circuit in TK1 JSON format to a writer.
///
/// You may need to normalize the circuit using [`lower_to_pytket`] before saving.
///
/// # Errors
///
/// Returns an error if the circuit is not flat or if it contains operations not
/// supported by pytket.
pub fn save_tk1_json_writer(circ: &Circuit, w: impl io::Write) -> Result<(), TK1ConvertError> {
let serial_circ = SerialCircuit::encode(circ)?;
serde_json::to_writer(w, &serial_circ)?;
Ok(())
}

/// Save a circuit in TK1 JSON format to a String.
///
/// You may need to normalize the circuit using [`lower_to_pytket`] before saving.
///
/// # Errors
///
/// Returns an error if the circuit is not flat or if it contains operations not
/// supported by pytket.
pub fn save_tk1_json_str(circ: &Circuit) -> Result<String, TK1ConvertError> {
let mut buf = io::BufWriter::new(Vec::new());
save_tk1_json_writer(circ, &mut buf)?;
Expand Down

0 comments on commit 865afdb

Please sign in to comment.